diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 3e3a298bd..3501eb296 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -100,6 +100,7 @@ public class PlayerEntity extends LivingEntity { super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw); this.username = username; + this.nametag = username; this.texturesProperty = texturesProperty; } @@ -119,7 +120,7 @@ public class PlayerEntity extends LivingEntity { } // The name can't be updated later (the entity metadata for it is ignored), so we need to check for this now - updateDisplayName(null, false); + updateDisplayName(session.getWorldCache().getScoreboard().getTeamFor(username)); AddPlayerPacket addPlayerPacket = new AddPlayerPacket(); addPlayerPacket.setUuid(uuid); @@ -315,19 +316,10 @@ public class PlayerEntity extends LivingEntity { } //todo this will become common entity logic once UUID support is implemented for them - /** - * @param useGivenTeam even if there is no team, update the username in the entity metadata anyway, and don't look for a team - */ - public void updateDisplayName(@Nullable Team team, boolean useGivenTeam) { - if (team == null && !useGivenTeam) { - // Only search for the team if we are not supposed to use the given team - // If the given team is null, this is intentional that we are being removed from the team - team = session.getWorldCache().getScoreboard().getTeamFor(username); - } - + public void updateDisplayName(@Nullable Team team) { boolean needsUpdate; - String newDisplayName = this.username; if (team != null) { + String newDisplayName; if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { TeamColor color = team.getColor(); String chatColor = MessageTranslator.toChatColor(color); @@ -339,23 +331,16 @@ public class PlayerEntity extends LivingEntity { // The name is not visible to the session player; clear name newDisplayName = ""; } - needsUpdate = useGivenTeam && !newDisplayName.equals(nametag); - nametag = newDisplayName; - dirtyMetadata.put(EntityData.NAMETAG, newDisplayName); - } else if (useGivenTeam) { - // The name has reset, if it was previously something else - needsUpdate = !newDisplayName.equals(nametag); - dirtyMetadata.put(EntityData.NAMETAG, this.username); + needsUpdate = !newDisplayName.equals(this.nametag); + this.nametag = newDisplayName; } else { - needsUpdate = false; + // The name has reset, if it was previously something else + needsUpdate = !this.nametag.equals(this.username); + this.nametag = this.username; } if (needsUpdate) { - // Update the metadata as it won't be updated later - SetEntityDataPacket packet = new SetEntityDataPacket(); - packet.getMetadata().put(EntityData.NAMETAG, newDisplayName); - packet.setRuntimeEntityId(geyserId); - session.sendUpstreamPacket(packet); + dirtyMetadata.put(EntityData.NAMETAG, this.nametag); } } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index f26d5846d..56e1e67f8 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -30,6 +30,7 @@ import com.nukkitx.protocol.bedrock.data.ScoreInfo; import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetScorePacket; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; @@ -56,6 +57,13 @@ public final class Scoreboard { @Getter private final Map objectiveSlots = new EnumMap<>(ScoreboardPosition.class); private final Map teams = new ConcurrentHashMap<>(); // updated on multiple threads + /** + * Required to preserve vanilla behavior, which also uses a map. + * Otherwise, for example, if TAB has a team for a player and vanilla has a team, "race conditions" that do not + * match vanilla could occur. + */ + @Getter + private final Map playerToTeam = new Object2ObjectOpenHashMap<>(); private int lastAddScoreCount = 0; private int lastRemoveScoreCount = 0; @@ -333,12 +341,7 @@ public final class Scoreboard { } public Team getTeamFor(String entity) { - for (Team team : teams.values()) { - if (team.hasEntity(entity)) { - return team; - } - } - return null; + return playerToTeam.get(entity); } public void removeTeam(String teamName) { @@ -380,7 +383,8 @@ public final class Scoreboard { for (Entity entity : session.getEntityCache().getEntities().values()) { // This more complex logic is for the future to iterate over all entities, not just players if (entity instanceof PlayerEntity player && names.remove(player.getUsername())) { - player.updateDisplayName(team, true); + player.updateDisplayName(team); + player.updateBedrockMetadata(); if (names.isEmpty()) { break; } @@ -396,7 +400,8 @@ public final class Scoreboard { for (Entity entity : session.getEntityCache().getEntities().values()) { if (entity instanceof PlayerEntity player) { Team playerTeam = session.getWorldCache().getScoreboard().getTeamFor(player.getUsername()); - player.updateDisplayName(playerTeam, true); + player.updateDisplayName(playerTeam); + player.updateBedrockMetadata(); } } } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java index d7840627f..34db4a048 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java @@ -65,6 +65,7 @@ public final class Team { if (entities.add(name)) { added.add(name); } + scoreboard.getPlayerToTeam().put(name, this); } if (added.isEmpty()) { @@ -93,6 +94,7 @@ public final class Team { if (entities.remove(name)) { removed.add(name); } + scoreboard.getPlayerToTeam().remove(name, this); } return removed; }