diff --git a/api/src/main/java/com/lunarclient/apollo/module/team/TeamMember.java b/api/src/main/java/com/lunarclient/apollo/module/team/TeamMember.java
index 7cf3befb6..c5bbf6fec 100644
--- a/api/src/main/java/com/lunarclient/apollo/module/team/TeamMember.java
+++ b/api/src/main/java/com/lunarclient/apollo/module/team/TeamMember.java
@@ -29,6 +29,7 @@
import lombok.Builder;
import lombok.Getter;
import net.kyori.adventure.text.Component;
+import org.jetbrains.annotations.Nullable;
/**
* Represents a team which can be shown on the client.
@@ -50,23 +51,25 @@ public final class TeamMember {
/**
* Returns the team member's {@link Component}.
*
- *
The display name is only used when the player
- * is out of render distance for the observer and when the
- * observer hovers over the marker.
+ * The display name is only shown when the player is outside
+ * the observer's render distance and when the observer hovers
+ * over the marker. If not provided, only the marker is displayed.
*
* @return the team member's display name
* @since 1.0.0
*/
- Component displayName;
+ @Nullable Component displayName;
/**
* Returns the team member's assigned {@link Color} - this will be used
* for any markers (such as on duration HUD, above head markers, etc).
*
+ * If not provided, the default color {@code 0xFFFFFFFF} is used.
+ *
* @return the team member's marker color
* @since 1.0.0
*/
- Color markerColor;
+ @Nullable Color markerColor;
/**
* Returns the team member's {@link ApolloLocation}.
@@ -78,6 +81,6 @@ public final class TeamMember {
* @return the team member location
* @since 1.0.0
*/
- ApolloLocation location;
+ @Nullable ApolloLocation location;
}
diff --git a/common/src/main/java/com/lunarclient/apollo/module/team/TeamModuleImpl.java b/common/src/main/java/com/lunarclient/apollo/module/team/TeamModuleImpl.java
index 44f302e02..a16abb5a8 100644
--- a/common/src/main/java/com/lunarclient/apollo/module/team/TeamModuleImpl.java
+++ b/common/src/main/java/com/lunarclient/apollo/module/team/TeamModuleImpl.java
@@ -24,14 +24,17 @@
package com.lunarclient.apollo.module.team;
import com.lunarclient.apollo.common.ApolloComponent;
+import com.lunarclient.apollo.common.location.ApolloLocation;
import com.lunarclient.apollo.network.NetworkTypes;
import com.lunarclient.apollo.player.AbstractApolloPlayer;
import com.lunarclient.apollo.recipients.Recipients;
import com.lunarclient.apollo.team.v1.ResetTeamMembersMessage;
import com.lunarclient.apollo.team.v1.UpdateTeamMembersMessage;
+import java.awt.Color;
import java.util.List;
import java.util.stream.Collectors;
import lombok.NonNull;
+import net.kyori.adventure.text.Component;
/**
* Provides the teams module.
@@ -43,13 +46,7 @@ public final class TeamModuleImpl extends TeamModule {
@Override
public void updateTeamMembers(@NonNull Recipients recipients, @NonNull List teamMembers) {
List teamMembersProto = teamMembers.stream()
- .map(teamMember -> com.lunarclient.apollo.team.v1.TeamMember.newBuilder()
- .setPlayerUuid(NetworkTypes.toProtobuf(teamMember.getPlayerUuid()))
- .setAdventureJsonPlayerName(ApolloComponent.toJson(teamMember.getDisplayName()))
- .setLocation(NetworkTypes.toProtobuf(teamMember.getLocation()))
- .setMarkerColor(NetworkTypes.toProtobuf(teamMember.getMarkerColor()))
- .build()
- )
+ .map(this::toProtobuf)
.collect(Collectors.toList());
UpdateTeamMembersMessage message = UpdateTeamMembersMessage.newBuilder()
@@ -65,4 +62,26 @@ public void resetTeamMembers(@NonNull Recipients recipients) {
recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
}
+ private com.lunarclient.apollo.team.v1.TeamMember toProtobuf(TeamMember member) {
+ com.lunarclient.apollo.team.v1.TeamMember.Builder builder = com.lunarclient.apollo.team.v1.TeamMember.newBuilder()
+ .setPlayerUuid(NetworkTypes.toProtobuf(member.getPlayerUuid()));
+
+ Component displayName = member.getDisplayName();
+ if (displayName != null) {
+ builder.setAdventureJsonPlayerName(ApolloComponent.toJson(displayName));
+ }
+
+ Color markerColor = member.getMarkerColor();
+ if (markerColor != null) {
+ builder.setMarkerColor(NetworkTypes.toProtobuf(markerColor));
+ }
+
+ ApolloLocation location = member.getLocation();
+ if (location != null) {
+ builder.setLocation(NetworkTypes.toProtobuf(location));
+ }
+
+ return builder.build();
+ }
+
}
diff --git a/docs/developers/lightweight/json/player-detection.mdx b/docs/developers/lightweight/json/player-detection.mdx
index 80b67ac2c..dafd5dbb7 100644
--- a/docs/developers/lightweight/json/player-detection.mdx
+++ b/docs/developers/lightweight/json/player-detection.mdx
@@ -16,7 +16,7 @@ This example demonstrates how to detect whether a player is using Lunar Client b
```java
public class ApolloPlayerJsonListener implements Listener {
- private final Set playersRunningApollo = new HashSet<>();
+ private static final Set PLAYERS_RUNNING_APOLLO = new HashSet<>();
public ApolloPlayerJsonListener(ApolloExamplePlugin plugin) {
Messenger messenger = Bukkit.getServer().getMessenger();
@@ -27,8 +27,8 @@ public class ApolloPlayerJsonListener implements Listener {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
- private boolean isPlayerRunningApollo(Player player) {
- return this.playersRunningApollo.contains(player.getUniqueId());
+ public static boolean isPlayerRunningApollo(Player player) {
+ return PLAYERS_RUNNING_APOLLO.contains(player.getUniqueId());
}
@EventHandler
@@ -43,7 +43,7 @@ public class ApolloPlayerJsonListener implements Listener {
// Sending the player's world name to the client is required for some modules
JsonPacketUtil.sendPacket(player, this.createUpdatePlayerWorldMessage(player));
- this.playersRunningApollo.add(player.getUniqueId());
+ PLAYERS_RUNNING_APOLLO.add(player.getUniqueId());
player.sendMessage("You are using LunarClient!");
}
diff --git a/docs/developers/lightweight/protobuf/player-detection.mdx b/docs/developers/lightweight/protobuf/player-detection.mdx
index 40c8af50a..5e324ae49 100644
--- a/docs/developers/lightweight/protobuf/player-detection.mdx
+++ b/docs/developers/lightweight/protobuf/player-detection.mdx
@@ -9,7 +9,7 @@ This example demonstrates how to detect whether a player is using Lunar Client b
```java
public class ApolloPlayerProtoListener implements Listener {
- private final Set playersRunningApollo = new HashSet<>();
+ private static final Set PLAYERS_RUNNING_APOLLO = new HashSet<>();
public ApolloPlayerProtoListener(ApolloExamplePlugin plugin) {
Bukkit.getServer().getMessenger().registerOutgoingPluginChannel(plugin, "lunar:apollo");
@@ -17,8 +17,8 @@ public class ApolloPlayerProtoListener implements Listener {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
- private boolean isPlayerRunningApollo(Player player) {
- return this.playersRunningApollo.contains(player.getUniqueId());
+ public static boolean isPlayerRunningApollo(Player player) {
+ return PLAYERS_RUNNING_APOLLO.contains(player.getUniqueId());
}
@EventHandler
@@ -33,7 +33,7 @@ public class ApolloPlayerProtoListener implements Listener {
// Sending the player's world name to the client is required for some modules
ProtobufPacketUtil.sendPacket(player, this.createUpdatePlayerWorldMessage(player));
- this.playersRunningApollo.add(player.getUniqueId());
+ PLAYERS_RUNNING_APOLLO.add(player.getUniqueId());
player.sendMessage("You are using LunarClient!");
}
diff --git a/docs/developers/modules/team.mdx b/docs/developers/modules/team.mdx
index 488965940..053fbec0c 100644
--- a/docs/developers/modules/team.mdx
+++ b/docs/developers/modules/team.mdx
@@ -27,8 +27,8 @@ Explore each integration by cycling through each tab, to find the best fit for y
```java
-private final Map teamsByTeamId = Maps.newHashMap();
-private final Map teamsByPlayerUuid = Maps.newHashMap();
+private final Map teamsByTeamId = new ConcurrentHashMap<>();
+private final Map teamsByPlayerUuid = new ConcurrentHashMap<>();
public TeamApiExample() {
if (ServerUtil.isFolia()) {
@@ -91,7 +91,7 @@ public class Team {
public Team() {
this.teamId = UUID.randomUUID();
- this.members = new HashMap<>();
+ this.members = new ConcurrentHashMap<>();
}
public void addMember(Player player) {
@@ -107,34 +107,78 @@ public class Team {
.ifPresent(TeamApiExample.this.teamModule::resetTeamMembers);
}
- private TeamMember createTeamMember(Player member) {
- Location location = member.getLocation();
+ private TeamMember createTeamMember(Player player, boolean withinPlayerTrackingRange) {
+ TeamMember.TeamMemberBuilder builder = TeamMember.builder()
+ .playerUuid(player.getUniqueId())
+ .markerColor(Color.WHITE);
- return TeamMember.builder()
- .playerUuid(member.getUniqueId())
- .displayName(Component.text()
- .content(member.getName())
- .color(NamedTextColor.WHITE)
- .build())
- .markerColor(Color.WHITE)
- .location(ApolloLocation.builder()
+ if (!withinPlayerTrackingRange) {
+ Location location = player.getLocation();
+
+ builder.location(ApolloLocation.builder()
.world(location.getWorld().getName())
.x(location.getX())
.y(location.getY())
.z(location.getZ())
- .build())
- .build();
+ .build());
+
+ builder.displayName(Component.text()
+ .content(player.getName())
+ .color(NamedTextColor.WHITE)
+ .build());
+ }
+
+ return builder.build();
}
- // The refresh method used for updating members locations
public void refresh() {
- List teammates = this.members.values()
- .stream().filter(Player::isOnline)
- .map(this::createTeamMember)
- .collect(Collectors.toList());
+ for (Player viewer : this.members.values()) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ if (!apolloPlayerOpt.isPresent()) {
+ continue;
+ }
+
+ List teammates = new ArrayList<>();
+
+ for (Player member : this.members.values()) {
+ if (viewer == member) {
+ continue;
+ }
+
+ if (!viewer.canSee(member)) {
+ continue;
+ }
+
+ if (!viewer.getWorld().getName().equals(member.getWorld().getName())) {
+ continue;
+ }
+
+ boolean withinPlayerTrackingRange = this.isWithinPlayerTrackingRange(viewer, member);
+ teammates.add(this.createTeamMember(member, withinPlayerTrackingRange));
+ }
+
+ apolloPlayerOpt.ifPresent(apolloPlayer -> {
+ TeamApiExample.this.teamModule.updateTeamMembers(apolloPlayer, teammates);
+ });
+ }
+ }
+
+ /**
+ * This uses a simple distance check based on Paper/Spigot defaults
+ * (96 blocks for players). Ideally, this could be checked directly through
+ * the server's internal entity tracker for exact tracking behavior, but that
+ * is not exposed in the Bukkit API.
+ *
+ * @param viewer the viewer
+ * @param member the member
+ * @return whether within player tracking range
+ */
+ private boolean isWithinPlayerTrackingRange(Player viewer, Player member) {
+ double maxDistance = 96;
+ double dx = viewer.getLocation().getX() - member.getLocation().getX();
+ double dz = viewer.getLocation().getZ() - member.getLocation().getZ();
- this.members.values().forEach(member -> Apollo.getPlayerManager().getPlayer(member.getUniqueId())
- .ifPresent(apolloPlayer -> TeamApiExample.this.teamModule.updateTeamMembers(apolloPlayer, teammates)));
+ return (dx * dx + dz * dz) <= (maxDistance * maxDistance);
}
public UUID getTeamId() {
@@ -237,8 +281,8 @@ Custom colors can be created from any RGB values using `new Color(int red, int g
```java
-private final Map teamsByTeamId = Maps.newHashMap();
-private final Map teamsByPlayerUuid = Maps.newHashMap();
+private final Map teamsByTeamId = new ConcurrentHashMap<>();
+private final Map teamsByPlayerUuid = new ConcurrentHashMap<>();
public TeamProtoExample() {
if (ServerUtil.isFolia()) {
@@ -301,7 +345,7 @@ public class Team {
public Team() {
this.teamId = UUID.randomUUID();
- this.members = new HashMap<>();
+ this.members = new ConcurrentHashMap<>();
}
public void addMember(Player player) {
@@ -317,31 +361,75 @@ public class Team {
ProtobufPacketUtil.sendPacket(player, message);
}
- private TeamMember createTeamMember(Player member) {
- return TeamMember.newBuilder()
- .setPlayerUuid(ProtobufUtil.createUuidProto(member.getUniqueId()))
- .setAdventureJsonPlayerName(AdventureUtil.toJson(
+ private TeamMember createTeamMember(Player player, boolean withinPlayerTrackingRange) {
+ TeamMember.Builder builder = TeamMember.newBuilder()
+ .setPlayerUuid(ProtobufUtil.createUuidProto(player.getUniqueId()))
+ .setMarkerColor(ProtobufUtil.createColorProto(Color.WHITE));
+
+ if (!withinPlayerTrackingRange) {
+ builder.setLocation(ProtobufUtil.createLocationProto(player.getLocation()));
+
+ builder.setAdventureJsonPlayerName(AdventureUtil.toJson(
Component.text()
- .content(member.getName())
+ .content(player.getName())
.color(NamedTextColor.WHITE)
.build()
- ))
- .setMarkerColor(ProtobufUtil.createColorProto(Color.WHITE))
- .setLocation(ProtobufUtil.createLocationProto(member.getLocation()))
- .build();
+ ));
+ }
+
+ return builder.build();
}
// The refresh method used for updating members locations
public void refresh() {
- List teammates = this.members.values().stream().filter(Player::isOnline)
- .map(this::createTeamMember)
- .collect(Collectors.toList());
+ for (Player viewer : this.members.values()) {
+ if (!ApolloPlayerProtoListener.isPlayerRunningApollo(viewer)) {
+ continue;
+ }
+
+ List teammates = new ArrayList<>();
- UpdateTeamMembersMessage message = UpdateTeamMembersMessage.newBuilder()
- .addAllMembers(teammates)
- .build();
+ for (Player member : this.members.values()) {
+ if (viewer == member) {
+ continue;
+ }
- this.members.values().forEach(member -> ProtobufPacketUtil.sendPacket(member, message));
+ if (!viewer.canSee(member)) {
+ continue;
+ }
+
+ if (!viewer.getWorld().getName().equals(member.getWorld().getName())) {
+ continue;
+ }
+
+ boolean withinPlayerTrackingRange = this.isWithinPlayerTrackingRange(viewer, member);
+ teammates.add(this.createTeamMember(member, withinPlayerTrackingRange));
+ }
+
+ UpdateTeamMembersMessage message = UpdateTeamMembersMessage.newBuilder()
+ .addAllMembers(teammates)
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+ }
+
+ /**
+ * This uses a simple distance check based on Paper/Spigot defaults
+ * (96 blocks for players). Ideally, this could be checked directly through
+ * the server's internal entity tracker for exact tracking behavior, but that
+ * is not exposed in the Bukkit API.
+ *
+ * @param viewer the viewer
+ * @param member the member
+ * @return whether within player tracking range
+ */
+ private boolean isWithinPlayerTrackingRange(Player viewer, Player member) {
+ double maxDistance = 96;
+ double dx = viewer.getLocation().getX() - member.getLocation().getX();
+ double dz = viewer.getLocation().getZ() - member.getLocation().getZ();
+
+ return (dx * dx + dz * dz) <= (maxDistance * maxDistance);
}
public UUID getTeamId() {
@@ -378,8 +466,8 @@ public class Team {
```java
-private final Map teamsByTeamId = Maps.newHashMap();
-private final Map teamsByPlayerUuid = Maps.newHashMap();
+private final Map teamsByTeamId = new ConcurrentHashMap<>();
+private final Map teamsByPlayerUuid = new ConcurrentHashMap<>();
public TeamJsonExample() {
if (ServerUtil.isFolia()) {
@@ -442,7 +530,7 @@ public class Team {
public Team() {
this.teamId = UUID.randomUUID();
- this.members = new HashMap<>();
+ this.members = new ConcurrentHashMap<>();
}
public void addMember(Player player) {
@@ -460,33 +548,75 @@ public class Team {
JsonPacketUtil.sendPacket(player, message);
}
- private JsonObject createTeamMember(Player member) {
+ private JsonObject createTeamMember(Player player, boolean withinPlayerTrackingRange) {
JsonObject message = new JsonObject();
- message.add("player_uuid", JsonUtil.createUuidObject(member.getUniqueId()));
- message.addProperty("adventure_json_player_name", AdventureUtil.toJson(
- Component.text()
- .content(member.getName())
- .color(NamedTextColor.WHITE)
- .build()
- ));
+ message.add("player_uuid", JsonUtil.createUuidObject(player.getUniqueId()));
message.add("marker_color", JsonUtil.createColorObject(Color.WHITE));
- message.add("location", JsonUtil.createLocationObject(member.getLocation()));
+
+ if (!withinPlayerTrackingRange) {
+ message.add("location", JsonUtil.createLocationObject(player.getLocation()));
+
+ message.addProperty("adventure_json_player_name", AdventureUtil.toJson(
+ Component.text()
+ .content(player.getName())
+ .color(NamedTextColor.WHITE)
+ .build()
+ ));
+ }
return message;
}
// The refresh method used for updating members locations
public void refresh() {
- JsonArray teammates = this.members.values()
- .stream().filter(Player::isOnline)
- .map(this::createTeamMember)
- .collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
+ for (Player viewer : this.members.values()) {
+ if (!ApolloPlayerJsonListener.isPlayerRunningApollo(viewer)) {
+ continue;
+ }
- JsonObject message = new JsonObject();
- message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.team.v1.UpdateTeamMembersMessage");
- message.add("members", teammates);
+ JsonArray teammates = new JsonArray();
+
+ for (Player member : this.members.values()) {
+ if (viewer == member) {
+ continue;
+ }
+
+ if (!viewer.canSee(member)) {
+ continue;
+ }
+
+ if (!viewer.getWorld().getName().equals(member.getWorld().getName())) {
+ continue;
+ }
+
+ boolean withinPlayerTrackingRange = this.isWithinPlayerTrackingRange(viewer, member);
+ teammates.add(this.createTeamMember(member, withinPlayerTrackingRange));
+ }
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.team.v1.UpdateTeamMembersMessage");
+ message.add("members", teammates);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+ }
- this.members.values().forEach(member -> JsonPacketUtil.sendPacket(member, message));
+ /**
+ * This uses a simple distance check based on Paper/Spigot defaults
+ * (96 blocks for players). Ideally, this could be checked directly through
+ * the server's internal entity tracker for exact tracking behavior, but that
+ * is not exposed in the Bukkit API.
+ *
+ * @param viewer the viewer
+ * @param member the member
+ * @return whether within player tracking range
+ */
+ private boolean isWithinPlayerTrackingRange(Player viewer, Player member) {
+ double maxDistance = 96;
+ double dx = viewer.getLocation().getX() - member.getLocation().getX();
+ double dz = viewer.getLocation().getZ() - member.getLocation().getZ();
+
+ return (dx * dx + dz * dz) <= (maxDistance * maxDistance);
}
public UUID getTeamId() {
diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/TeamApiExample.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/TeamApiExample.java
index 6985a24aa..7566affc2 100644
--- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/TeamApiExample.java
+++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/TeamApiExample.java
@@ -23,7 +23,6 @@
*/
package com.lunarclient.apollo.example.api.module;
-import com.google.common.collect.Maps;
import com.lunarclient.apollo.Apollo;
import com.lunarclient.apollo.common.location.ApolloLocation;
import com.lunarclient.apollo.example.ApolloExamplePlugin;
@@ -31,15 +30,16 @@
import com.lunarclient.apollo.example.util.ServerUtil;
import com.lunarclient.apollo.module.team.TeamMember;
import com.lunarclient.apollo.module.team.TeamModule;
+import com.lunarclient.apollo.player.ApolloPlayer;
import java.awt.Color;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
@@ -53,8 +53,8 @@ public class TeamApiExample extends TeamExample implements Listener {
private final TeamModule teamModule = Apollo.getModuleManager().getModule(TeamModule.class);
- private final Map teamsByTeamId = Maps.newHashMap();
- private final Map teamsByPlayerUuid = Maps.newHashMap();
+ private final Map teamsByTeamId = new ConcurrentHashMap<>();
+ private final Map teamsByPlayerUuid = new ConcurrentHashMap<>();
public TeamApiExample() {
if (ServerUtil.isFolia()) {
@@ -117,7 +117,7 @@ public class Team {
public Team() {
this.teamId = UUID.randomUUID();
- this.members = new HashMap<>();
+ this.members = new ConcurrentHashMap<>();
}
public void addMember(Player player) {
@@ -133,34 +133,78 @@ public void removeMember(Player player) {
.ifPresent(TeamApiExample.this.teamModule::resetTeamMembers);
}
- private TeamMember createTeamMember(Player member) {
- Location location = member.getLocation();
+ private TeamMember createTeamMember(Player player, boolean withinPlayerTrackingRange) {
+ TeamMember.TeamMemberBuilder builder = TeamMember.builder()
+ .playerUuid(player.getUniqueId())
+ .markerColor(Color.WHITE);
- return TeamMember.builder()
- .playerUuid(member.getUniqueId())
- .displayName(Component.text()
- .content(member.getName())
- .color(NamedTextColor.WHITE)
- .build())
- .markerColor(Color.WHITE)
- .location(ApolloLocation.builder()
+ if (!withinPlayerTrackingRange) {
+ Location location = player.getLocation();
+
+ builder.location(ApolloLocation.builder()
.world(location.getWorld().getName())
.x(location.getX())
.y(location.getY())
.z(location.getZ())
- .build())
- .build();
+ .build());
+
+ builder.displayName(Component.text()
+ .content(player.getName())
+ .color(NamedTextColor.WHITE)
+ .build());
+ }
+
+ return builder.build();
}
- // The refresh method used for updating members locations
public void refresh() {
- List teammates = this.members.values()
- .stream().filter(Player::isOnline)
- .map(this::createTeamMember)
- .collect(Collectors.toList());
+ for (Player viewer : this.members.values()) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ if (!apolloPlayerOpt.isPresent()) {
+ continue;
+ }
+
+ List teammates = new ArrayList<>();
+
+ for (Player member : this.members.values()) {
+ if (viewer == member) {
+ continue;
+ }
+
+ if (!viewer.canSee(member)) {
+ continue;
+ }
+
+ if (!viewer.getWorld().getName().equals(member.getWorld().getName())) {
+ continue;
+ }
+
+ boolean withinPlayerTrackingRange = this.isWithinPlayerTrackingRange(viewer, member);
+ teammates.add(this.createTeamMember(member, withinPlayerTrackingRange));
+ }
+
+ apolloPlayerOpt.ifPresent(apolloPlayer -> {
+ TeamApiExample.this.teamModule.updateTeamMembers(apolloPlayer, teammates);
+ });
+ }
+ }
- this.members.values().forEach(member -> Apollo.getPlayerManager().getPlayer(member.getUniqueId())
- .ifPresent(apolloPlayer -> TeamApiExample.this.teamModule.updateTeamMembers(apolloPlayer, teammates)));
+ /**
+ * This uses a simple distance check based on Paper/Spigot defaults
+ * (96 blocks for players). Ideally, this could be checked directly through
+ * the server's internal entity tracker for exact tracking behavior, but that
+ * is not exposed in the Bukkit API.
+ *
+ * @param viewer the viewer
+ * @param member the member
+ * @return whether within player tracking range
+ */
+ private boolean isWithinPlayerTrackingRange(Player viewer, Player member) {
+ double maxDistance = 96;
+ double dx = viewer.getLocation().getX() - member.getLocation().getX();
+ double dz = viewer.getLocation().getZ() - member.getLocation().getZ();
+
+ return (dx * dx + dz * dz) <= (maxDistance * maxDistance);
}
public UUID getTeamId() {
diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloPlayerJsonListener.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloPlayerJsonListener.java
index 26bb915be..9fcddc5fc 100644
--- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloPlayerJsonListener.java
+++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloPlayerJsonListener.java
@@ -42,7 +42,7 @@ public class ApolloPlayerJsonListener implements Listener {
private final ApolloExamplePlugin plugin;
- private final Set playersRunningApollo = new HashSet<>();
+ private static final Set PLAYERS_RUNNING_APOLLO = new HashSet<>();
public ApolloPlayerJsonListener(ApolloExamplePlugin plugin) {
this.plugin = plugin;
@@ -57,7 +57,7 @@ public ApolloPlayerJsonListener(ApolloExamplePlugin plugin) {
}
public void disable() {
- this.playersRunningApollo.clear();
+ PLAYERS_RUNNING_APOLLO.clear();
Messenger messenger = Bukkit.getServer().getMessenger();
messenger.unregisterIncomingPluginChannel(this.plugin, "lunar:apollo");
@@ -79,7 +79,7 @@ private void onRegisterChannel(PlayerRegisterChannelEvent event) {
// Sending the player's world name to the client is required for some modules
JsonPacketUtil.sendPacket(player, this.createUpdatePlayerWorldMessage(player));
- this.playersRunningApollo.add(player.getUniqueId());
+ PLAYERS_RUNNING_APOLLO.add(player.getUniqueId());
player.sendMessage("You are using LunarClient!");
}
@@ -98,8 +98,8 @@ private JsonObject createUpdatePlayerWorldMessage(Player player) {
return message;
}
- private boolean isPlayerRunningApollo(Player player) {
- return this.playersRunningApollo.contains(player.getUniqueId());
+ public static boolean isPlayerRunningApollo(Player player) {
+ return PLAYERS_RUNNING_APOLLO.contains(player.getUniqueId());
}
}
diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/TeamJsonExample.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/TeamJsonExample.java
index 8683030d2..735291822 100644
--- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/TeamJsonExample.java
+++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/TeamJsonExample.java
@@ -23,10 +23,10 @@
*/
package com.lunarclient.apollo.example.json.module;
-import com.google.common.collect.Maps;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.lunarclient.apollo.example.ApolloExamplePlugin;
+import com.lunarclient.apollo.example.json.listener.ApolloPlayerJsonListener;
import com.lunarclient.apollo.example.json.util.AdventureUtil;
import com.lunarclient.apollo.example.json.util.JsonPacketUtil;
import com.lunarclient.apollo.example.json.util.JsonUtil;
@@ -34,10 +34,10 @@
import com.lunarclient.apollo.example.util.ServerUtil;
import java.awt.Color;
import java.util.Collection;
-import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -49,8 +49,8 @@
public class TeamJsonExample extends TeamExample implements Listener {
- private final Map teamsByTeamId = Maps.newHashMap();
- private final Map teamsByPlayerUuid = Maps.newHashMap();
+ private final Map teamsByTeamId = new ConcurrentHashMap<>();
+ private final Map teamsByPlayerUuid = new ConcurrentHashMap<>();
public TeamJsonExample() {
if (ServerUtil.isFolia()) {
@@ -113,7 +113,7 @@ public class Team {
public Team() {
this.teamId = UUID.randomUUID();
- this.members = new HashMap<>();
+ this.members = new ConcurrentHashMap<>();
}
public void addMember(Player player) {
@@ -131,33 +131,75 @@ public void removeMember(Player player) {
JsonPacketUtil.sendPacket(player, message);
}
- private JsonObject createTeamMember(Player member) {
+ private JsonObject createTeamMember(Player player, boolean withinPlayerTrackingRange) {
JsonObject message = new JsonObject();
- message.add("player_uuid", JsonUtil.createUuidObject(member.getUniqueId()));
- message.addProperty("adventure_json_player_name", AdventureUtil.toJson(
- Component.text()
- .content(member.getName())
- .color(NamedTextColor.WHITE)
- .build()
- ));
+ message.add("player_uuid", JsonUtil.createUuidObject(player.getUniqueId()));
message.add("marker_color", JsonUtil.createColorObject(Color.WHITE));
- message.add("location", JsonUtil.createLocationObject(member.getLocation()));
+
+ if (!withinPlayerTrackingRange) {
+ message.add("location", JsonUtil.createLocationObject(player.getLocation()));
+
+ message.addProperty("adventure_json_player_name", AdventureUtil.toJson(
+ Component.text()
+ .content(player.getName())
+ .color(NamedTextColor.WHITE)
+ .build()
+ ));
+ }
return message;
}
// The refresh method used for updating members locations
public void refresh() {
- JsonArray teammates = this.members.values()
- .stream().filter(Player::isOnline)
- .map(this::createTeamMember)
- .collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
+ for (Player viewer : this.members.values()) {
+ if (!ApolloPlayerJsonListener.isPlayerRunningApollo(viewer)) {
+ continue;
+ }
- JsonObject message = new JsonObject();
- message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.team.v1.UpdateTeamMembersMessage");
- message.add("members", teammates);
+ JsonArray teammates = new JsonArray();
+
+ for (Player member : this.members.values()) {
+ if (viewer == member) {
+ continue;
+ }
+
+ if (!viewer.canSee(member)) {
+ continue;
+ }
+
+ if (!viewer.getWorld().getName().equals(member.getWorld().getName())) {
+ continue;
+ }
+
+ boolean withinPlayerTrackingRange = this.isWithinPlayerTrackingRange(viewer, member);
+ teammates.add(this.createTeamMember(member, withinPlayerTrackingRange));
+ }
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.team.v1.UpdateTeamMembersMessage");
+ message.add("members", teammates);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+ }
- this.members.values().forEach(member -> JsonPacketUtil.sendPacket(member, message));
+ /**
+ * This uses a simple distance check based on Paper/Spigot defaults
+ * (96 blocks for players). Ideally, this could be checked directly through
+ * the server's internal entity tracker for exact tracking behavior, but that
+ * is not exposed in the Bukkit API.
+ *
+ * @param viewer the viewer
+ * @param member the member
+ * @return whether within player tracking range
+ */
+ private boolean isWithinPlayerTrackingRange(Player viewer, Player member) {
+ double maxDistance = 96;
+ double dx = viewer.getLocation().getX() - member.getLocation().getX();
+ double dz = viewer.getLocation().getZ() - member.getLocation().getZ();
+
+ return (dx * dx + dz * dz) <= (maxDistance * maxDistance);
}
public UUID getTeamId() {
diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloPlayerProtoListener.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloPlayerProtoListener.java
index efcbcbe9e..f48349646 100644
--- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloPlayerProtoListener.java
+++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloPlayerProtoListener.java
@@ -42,7 +42,7 @@ public class ApolloPlayerProtoListener implements Listener {
private final ApolloExamplePlugin plugin;
- private final Set playersRunningApollo = new HashSet<>();
+ private static final Set PLAYERS_RUNNING_APOLLO = new HashSet<>();
public ApolloPlayerProtoListener(ApolloExamplePlugin plugin) {
this.plugin = plugin;
@@ -56,7 +56,7 @@ public ApolloPlayerProtoListener(ApolloExamplePlugin plugin) {
}
public void disable() {
- this.playersRunningApollo.clear();
+ PLAYERS_RUNNING_APOLLO.clear();
Messenger messenger = Bukkit.getServer().getMessenger();
messenger.unregisterOutgoingPluginChannel(this.plugin, "lunar:apollo");
@@ -77,7 +77,7 @@ private void onRegisterChannel(PlayerRegisterChannelEvent event) {
// Sending the player's world name to the client is required for some modules
ProtobufPacketUtil.sendPacket(player, this.createUpdatePlayerWorldMessage(player));
- this.playersRunningApollo.add(player.getUniqueId());
+ PLAYERS_RUNNING_APOLLO.add(player.getUniqueId());
player.sendMessage("You are using LunarClient!");
}
@@ -95,8 +95,8 @@ private UpdatePlayerWorldMessage createUpdatePlayerWorldMessage(Player player) {
.build();
}
- private boolean isPlayerRunningApollo(Player player) {
- return this.playersRunningApollo.contains(player.getUniqueId());
+ public static boolean isPlayerRunningApollo(Player player) {
+ return PLAYERS_RUNNING_APOLLO.contains(player.getUniqueId());
}
}
diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/TeamProtoExample.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/TeamProtoExample.java
index 35726ceae..e7ba82708 100644
--- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/TeamProtoExample.java
+++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/TeamProtoExample.java
@@ -23,9 +23,9 @@
*/
package com.lunarclient.apollo.example.proto.module;
-import com.google.common.collect.Maps;
import com.lunarclient.apollo.example.ApolloExamplePlugin;
import com.lunarclient.apollo.example.module.impl.TeamExample;
+import com.lunarclient.apollo.example.proto.listener.ApolloPlayerProtoListener;
import com.lunarclient.apollo.example.proto.util.AdventureUtil;
import com.lunarclient.apollo.example.proto.util.ProtobufPacketUtil;
import com.lunarclient.apollo.example.proto.util.ProtobufUtil;
@@ -34,14 +34,14 @@
import com.lunarclient.apollo.team.v1.TeamMember;
import com.lunarclient.apollo.team.v1.UpdateTeamMembersMessage;
import java.awt.Color;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
@@ -52,8 +52,8 @@
public class TeamProtoExample extends TeamExample implements Listener {
- private final Map teamsByTeamId = Maps.newHashMap();
- private final Map teamsByPlayerUuid = Maps.newHashMap();
+ private final Map teamsByTeamId = new ConcurrentHashMap<>();
+ private final Map teamsByPlayerUuid = new ConcurrentHashMap<>();
public TeamProtoExample() {
if (ServerUtil.isFolia()) {
@@ -116,7 +116,7 @@ public class Team {
public Team() {
this.teamId = UUID.randomUUID();
- this.members = new HashMap<>();
+ this.members = new ConcurrentHashMap<>();
}
public void addMember(Player player) {
@@ -132,31 +132,75 @@ public void removeMember(Player player) {
ProtobufPacketUtil.sendPacket(player, message);
}
- private TeamMember createTeamMember(Player member) {
- return TeamMember.newBuilder()
- .setPlayerUuid(ProtobufUtil.createUuidProto(member.getUniqueId()))
- .setAdventureJsonPlayerName(AdventureUtil.toJson(
+ private TeamMember createTeamMember(Player player, boolean withinPlayerTrackingRange) {
+ TeamMember.Builder builder = TeamMember.newBuilder()
+ .setPlayerUuid(ProtobufUtil.createUuidProto(player.getUniqueId()))
+ .setMarkerColor(ProtobufUtil.createColorProto(Color.WHITE));
+
+ if (!withinPlayerTrackingRange) {
+ builder.setLocation(ProtobufUtil.createLocationProto(player.getLocation()));
+
+ builder.setAdventureJsonPlayerName(AdventureUtil.toJson(
Component.text()
- .content(member.getName())
+ .content(player.getName())
.color(NamedTextColor.WHITE)
.build()
- ))
- .setMarkerColor(ProtobufUtil.createColorProto(Color.WHITE))
- .setLocation(ProtobufUtil.createLocationProto(member.getLocation()))
- .build();
+ ));
+ }
+
+ return builder.build();
}
// The refresh method used for updating members locations
public void refresh() {
- List teammates = this.members.values().stream().filter(Player::isOnline)
- .map(this::createTeamMember)
- .collect(Collectors.toList());
+ for (Player viewer : this.members.values()) {
+ if (!ApolloPlayerProtoListener.isPlayerRunningApollo(viewer)) {
+ continue;
+ }
+
+ List teammates = new ArrayList<>();
+
+ for (Player member : this.members.values()) {
+ if (viewer == member) {
+ continue;
+ }
- UpdateTeamMembersMessage message = UpdateTeamMembersMessage.newBuilder()
- .addAllMembers(teammates)
- .build();
+ if (!viewer.canSee(member)) {
+ continue;
+ }
+
+ if (!viewer.getWorld().getName().equals(member.getWorld().getName())) {
+ continue;
+ }
+
+ boolean withinPlayerTrackingRange = this.isWithinPlayerTrackingRange(viewer, member);
+ teammates.add(this.createTeamMember(member, withinPlayerTrackingRange));
+ }
+
+ UpdateTeamMembersMessage message = UpdateTeamMembersMessage.newBuilder()
+ .addAllMembers(teammates)
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+ }
- this.members.values().forEach(member -> ProtobufPacketUtil.sendPacket(member, message));
+ /**
+ * This uses a simple distance check based on Paper/Spigot defaults
+ * (96 blocks for players). Ideally, this could be checked directly through
+ * the server's internal entity tracker for exact tracking behavior, but that
+ * is not exposed in the Bukkit API.
+ *
+ * @param viewer the viewer
+ * @param member the member
+ * @return whether within player tracking range
+ */
+ private boolean isWithinPlayerTrackingRange(Player viewer, Player member) {
+ double maxDistance = 96;
+ double dx = viewer.getLocation().getX() - member.getLocation().getX();
+ double dz = viewer.getLocation().getZ() - member.getLocation().getZ();
+
+ return (dx * dx + dz * dz) <= (maxDistance * maxDistance);
}
public UUID getTeamId() {