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() {