Update display names for team players after team updates

Resolves #1912
This commit is contained in:
Camotoy 2021-10-05 17:06:15 -04:00
parent 175d9aff48
commit b65ba2cb52
No known key found for this signature in database
GPG Key ID: 7EEFB66FE798081F
7 changed files with 249 additions and 157 deletions

View File

@ -41,7 +41,6 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.geysermc.connector.entity.living.ArmorStandEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@ -256,10 +255,7 @@ public class Entity {
setAir((int) entityMetadata.getValue());
break;
case 2: // custom name
if (entityMetadata.getValue() instanceof Component message) {
// Always translate even if it's a TextMessage since there could be translatable parameters
metadata.put(EntityData.NAMETAG, MessageTranslator.convertMessage(message, session.getLocale()));
}
setDisplayName(session, (Component) entityMetadata.getValue());
break;
case 3: // is custom name visible
if (!this.is(PlayerEntity.class))
@ -310,6 +306,21 @@ public class Entity {
return false;
}
/**
* @return the translated string display
*/
protected String setDisplayName(GeyserSession session, Component name) {
if (name != null) {
String displayName = MessageTranslator.convertMessage(name, session.getLocale());
metadata.put(EntityData.NAMETAG, displayName);
return displayName;
} else if (!metadata.getString(EntityData.NAMETAG).isEmpty()) {
// Clear nametag
metadata.put(EntityData.NAMETAG, "");
}
return null;
}
/**
* Set the height and width of the entity's bounding box
*/

View File

@ -29,6 +29,7 @@ import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
@ -39,9 +40,11 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData;
import com.nukkitx.protocol.bedrock.packet.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.LivingEntity;
import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity;
@ -53,6 +56,7 @@ import org.geysermc.connector.scoreboard.Score;
import org.geysermc.connector.scoreboard.Team;
import org.geysermc.connector.scoreboard.UpdateType;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -64,6 +68,9 @@ public class PlayerEntity extends LivingEntity {
private String username;
private boolean playerList = true; // Player is in the player list
@Getter(AccessLevel.NONE)
private String displayName;
/**
* Saves the parrot currently on the player's left shoulder; otherwise null
*/
@ -79,6 +86,7 @@ public class PlayerEntity extends LivingEntity {
profile = gameProfile;
uuid = gameProfile.getId();
username = gameProfile.getName();
displayName = username;
// For the OptionalPack, set all bits as invisible by default as this matches Java Edition behavior
metadata.put(EntityData.MARK_VARIANT, 0xff);
@ -240,23 +248,6 @@ public class PlayerEntity extends LivingEntity {
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
super.updateBedrockMetadata(entityMetadata, session);
if (entityMetadata.getId() == 2) {
String username = this.username;
Component name = (Component) entityMetadata.getValue();
if (name != null) {
username = MessageTranslator.convertMessage(name);
}
Team team = session.getWorldCache().getScoreboard().getTeamFor(username);
if (team != null) {
String displayName = "";
if (team.isVisibleFor(session.getPlayerEntity().getUsername())) {
displayName = MessageTranslator.toChatColor(team.getColor()) + username;
displayName = team.getCurrentData().getDisplayName(displayName);
}
metadata.put(EntityData.NAMETAG, displayName);
}
}
// Extra hearts - is not metadata but an attribute on Bedrock
if (entityMetadata.getId() == 15) {
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
@ -319,6 +310,65 @@ public class PlayerEntity extends LivingEntity {
}
}
@Override
protected String setDisplayName(GeyserSession session, Component name) {
String displayName = super.setDisplayName(session, name);
this.displayName = displayName != null ? displayName : username;
// Update if we know this player has a team
updateDisplayName(session, null, false);
return this.displayName;
}
//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(GeyserSession session, @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);
}
boolean needsUpdate;
String newDisplayName = this.displayName;
if (team != null) {
if (team.isVisibleFor(session.getPlayerEntity().getUsername())) {
TeamColor color = team.getColor();
String chatColor;
if (color == TeamColor.NONE) {
chatColor = ChatColor.RESET;
} else {
chatColor = MessageTranslator.toChatColor(color);
}
// We have to emulate what modern Java text already does for us and add the color to each section
String prefix = team.getCurrentData().getPrefix();
String suffix = team.getCurrentData().getSuffix();
newDisplayName = chatColor + prefix + chatColor + this.displayName + chatColor + suffix;
} else {
// The name is not visible to the session player; clear name
newDisplayName = "";
}
needsUpdate = useGivenTeam && !newDisplayName.equals(metadata.getString(EntityData.NAMETAG, null));
metadata.put(EntityData.NAMETAG, newDisplayName);
} else if (useGivenTeam) {
// The name has reset, if it was previously something else
needsUpdate = !newDisplayName.equals(metadata.getString(EntityData.NAMETAG));
metadata.put(EntityData.NAMETAG, this.displayName);
} else {
needsUpdate = false;
}
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);
}
}
@Override
protected void setDimensions(Pose pose) {
float height;

View File

@ -25,13 +25,16 @@
package org.geysermc.connector.network.session.cache;
import it.unimi.dsi.fastutil.longs.*;
import it.unimi.dsi.fastutil.longs.Long2LongMap;
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter;
import org.geysermc.connector.entity.Tickable;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.Tickable;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;

View File

@ -28,8 +28,6 @@ package org.geysermc.connector.network.translators.chat;
import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.gson.legacyimpl.NBTLegacyHoverEventSerializer;
@ -38,9 +36,10 @@ import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.LanguageUtils;
import java.util.HashMap;
import java.util.EnumMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
public class MessageTranslator {
@ -54,7 +53,7 @@ public class MessageTranslator {
.build();
// Store team colors for player names
private static final Map<TeamColor, TextDecoration> TEAM_FORMATS = new HashMap<>();
private static final Map<TeamColor, String> TEAM_COLORS = new EnumMap<>(TeamColor.class);
// Legacy formatting character
private static final String BASE = "\u00a7";
@ -62,11 +61,36 @@ public class MessageTranslator {
// Reset character
private static final String RESET = BASE + "r";
/* Various regexes to fix formatting for Bedrock's specifications */
private static final Pattern STRIKETHROUGH_UNDERLINE = Pattern.compile("\u00a7[mn]");
private static final Pattern COLOR_CHARACTERS = Pattern.compile("\u00a7([0-9a-f])");
private static final Pattern DOUBLE_RESET = Pattern.compile("\u00a7r\u00a7r");
static {
TEAM_FORMATS.put(TeamColor.OBFUSCATED, TextDecoration.OBFUSCATED);
TEAM_FORMATS.put(TeamColor.BOLD, TextDecoration.BOLD);
TEAM_FORMATS.put(TeamColor.STRIKETHROUGH, TextDecoration.STRIKETHROUGH);
TEAM_FORMATS.put(TeamColor.ITALIC, TextDecoration.ITALIC);
TEAM_COLORS.put(TeamColor.NONE, "");
TEAM_COLORS.put(TeamColor.BLACK, BASE + "0");
TEAM_COLORS.put(TeamColor.DARK_BLUE, BASE + "1");
TEAM_COLORS.put(TeamColor.DARK_GREEN, BASE + "2");
TEAM_COLORS.put(TeamColor.DARK_AQUA, BASE + "3");
TEAM_COLORS.put(TeamColor.DARK_RED, BASE + "4");
TEAM_COLORS.put(TeamColor.DARK_PURPLE, BASE + "5");
TEAM_COLORS.put(TeamColor.GOLD, BASE + "6");
TEAM_COLORS.put(TeamColor.GRAY, BASE + "7");
TEAM_COLORS.put(TeamColor.DARK_GRAY, BASE + "8");
TEAM_COLORS.put(TeamColor.BLUE, BASE + "9");
TEAM_COLORS.put(TeamColor.GREEN, BASE + "a");
TEAM_COLORS.put(TeamColor.AQUA, BASE + "b");
TEAM_COLORS.put(TeamColor.RED, BASE + "c");
TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, BASE + "d");
TEAM_COLORS.put(TeamColor.YELLOW, BASE + "e");
TEAM_COLORS.put(TeamColor.WHITE, BASE + "f");
// Formats, not colors
TEAM_COLORS.put(TeamColor.OBFUSCATED, BASE + "k");
TEAM_COLORS.put(TeamColor.BOLD, BASE + "l");
TEAM_COLORS.put(TeamColor.STRIKETHROUGH, BASE + "m");
TEAM_COLORS.put(TeamColor.ITALIC, BASE + "o");
// Tell MCProtocolLib to use our serializer
DefaultComponentSerializer.set(GSON_SERIALIZER);
@ -88,12 +112,12 @@ public class MessageTranslator {
String legacy = LegacyComponentSerializer.legacySection().serialize(message);
// Strip strikethrough and underline as they are not supported on bedrock
legacy = legacy.replaceAll("\u00a7[mn]", "");
legacy = STRIKETHROUGH_UNDERLINE.matcher(legacy).replaceAll("");
// Make color codes reset formatting like Java
// See https://minecraft.gamepedia.com/Formatting_codes#Usage
legacy = legacy.replaceAll("\u00a7([0-9a-f])", "\u00a7r\u00a7$1");
legacy = legacy.replaceAll("\u00a7r\u00a7r", "\u00a7r");
legacy = COLOR_CHARACTERS.matcher(legacy).replaceAll("\u00a7r\u00a7$1");
legacy = DOUBLE_RESET.matcher(legacy).replaceAll("\u00a7r");
return legacy;
} catch (Exception e) {
@ -158,84 +182,6 @@ public class MessageTranslator {
return GSON_SERIALIZER.serialize(component);
}
/**
* Convert a {@link NamedTextColor} into a string for inserting into messages
*
* @param color {@link NamedTextColor} to convert
* @return The converted color string
*/
private static String getColor(NamedTextColor color) {
StringBuilder str = new StringBuilder(BASE);
if (color.equals(NamedTextColor.BLACK)) {
str.append("0");
} else if (color.equals(NamedTextColor.DARK_BLUE)) {
str.append("1");
} else if (color.equals(NamedTextColor.DARK_GREEN)) {
str.append("2");
} else if (color.equals(NamedTextColor.DARK_AQUA)) {
str.append("3");
} else if (color.equals(NamedTextColor.DARK_RED)) {
str.append("4");
} else if (color.equals(NamedTextColor.DARK_PURPLE)) {
str.append("5");
} else if (color.equals(NamedTextColor.GOLD)) {
str.append("6");
} else if (color.equals(NamedTextColor.GRAY)) {
str.append("7");
} else if (color.equals(NamedTextColor.DARK_GRAY)) {
str.append("8");
} else if (color.equals(NamedTextColor.BLUE)) {
str.append("9");
} else if (color.equals(NamedTextColor.GREEN)) {
str.append("a");
} else if (color.equals(NamedTextColor.AQUA)) {
str.append("b");
} else if (color.equals(NamedTextColor.RED)) {
str.append("c");
} else if (color.equals(NamedTextColor.LIGHT_PURPLE)) {
str.append("d");
} else if (color.equals(NamedTextColor.YELLOW)) {
str.append("e");
} else if (color.equals(NamedTextColor.WHITE)) {
str.append("f");
} else {
return "";
}
return str.toString();
}
/**
* Convert a {@link TextDecoration} into a string for inserting into messages
*
* @param format {@link TextDecoration} to convert
* @return The converted chat formatting string
*/
private static String getFormat(TextDecoration format) {
StringBuilder str = new StringBuilder(BASE);
switch (format) {
case OBFUSCATED:
str.append("k");
break;
case BOLD:
str.append("l");
break;
case STRIKETHROUGH:
str.append("m");
break;
case UNDERLINED:
str.append("n");
break;
case ITALIC:
str.append("o");
break;
default:
return "";
}
return str.toString();
}
/**
* Convert a team color to a chat color
*
@ -243,16 +189,7 @@ public class MessageTranslator {
* @return The chat color character
*/
public static String toChatColor(TeamColor teamColor) {
if (teamColor.equals(TeamColor.NONE)) {
return "";
}
NamedTextColor textColor = NamedTextColor.NAMES.value(teamColor.name().toLowerCase());
if (textColor != null) {
return getColor(textColor);
}
return getFormat(TEAM_FORMATS.get(teamColor));
return TEAM_COLORS.getOrDefault(teamColor, "");
}
/**

View File

@ -25,7 +25,9 @@
package org.geysermc.connector.network.translators.java.scoreboard;
import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamAction;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerTeamPacket;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.GeyserLogger;
@ -37,9 +39,9 @@ import org.geysermc.connector.scoreboard.Scoreboard;
import org.geysermc.connector.scoreboard.ScoreboardUpdater;
import org.geysermc.connector.scoreboard.Team;
import org.geysermc.connector.scoreboard.UpdateType;
import org.geysermc.connector.utils.LanguageUtils;
import java.util.Arrays;
import java.util.Set;
@Translator(packet = ServerTeamPacket.class)
public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
@ -60,48 +62,77 @@ public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
Team team = scoreboard.getTeam(packet.getTeamName());
switch (packet.getAction()) {
case CREATE ->
scoreboard.registerNewTeam(packet.getTeamName(), packet.getPlayers())
.setName(MessageTranslator.convertMessage(packet.getDisplayName()))
.setColor(packet.getColor())
.setNameTagVisibility(packet.getNameTagVisibility())
.setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.getLocale()))
.setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.getLocale()));
case CREATE -> {
team = scoreboard.registerNewTeam(packet.getTeamName(), packet.getPlayers())
.setName(MessageTranslator.convertMessage(packet.getDisplayName()))
.setColor(packet.getColor())
.setNameTagVisibility(packet.getNameTagVisibility())
.setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.getLocale()))
.setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.getLocale()));
if (packet.getPlayers().length != 0) {
if ((team.getNameTagVisibility() != NameTagVisibility.ALWAYS && !team.isVisibleFor(session.getPlayerEntity().getUsername()))
|| team.getColor() != TeamColor.NONE
|| !team.getCurrentData().getPrefix().isEmpty()
|| !team.getCurrentData().getSuffix().isEmpty()) {
// Something is here that would modify entity names
scoreboard.updateEntityNames(team, true);
}
}
}
case UPDATE -> {
if (team == null) {
logger.debug(LanguageUtils.getLocaleStringLog(
"geyser.network.translator.team.failed_not_registered",
packet.getAction(), packet.getTeamName()
));
if (logger.isDebug()) {
logger.debug("Error while translating Team Packet " + packet.getAction()
+ "! Scoreboard Team " + packet.getTeamName() + " is not registered."
);
}
return;
}
TeamColor oldColor = team.getColor();
NameTagVisibility oldVisibility = team.getNameTagVisibility();
String oldPrefix = team.getCurrentData().getPrefix();
String oldSuffix = team.getCurrentData().getSuffix();
team.setName(MessageTranslator.convertMessage(packet.getDisplayName()))
.setColor(packet.getColor())
.setNameTagVisibility(packet.getNameTagVisibility())
.setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.getLocale()))
.setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.getLocale()))
.setUpdateType(UpdateType.UPDATE);
if (oldVisibility != team.getNameTagVisibility()
|| oldColor != team.getColor()
|| !oldPrefix.equals(team.getCurrentData().getPrefix())
|| !oldSuffix.equals(team.getCurrentData().getSuffix())) {
// Update entities attached to this team as something about their nameplates have changed
scoreboard.updateEntityNames(team, false);
}
}
case ADD_PLAYER -> {
if (team == null) {
logger.debug(LanguageUtils.getLocaleStringLog(
"geyser.network.translator.team.failed_not_registered",
packet.getAction(), packet.getTeamName()
));
if (logger.isDebug()) {
logger.debug("Error while translating Team Packet " + packet.getAction()
+ "! Scoreboard Team " + packet.getTeamName() + " is not registered."
);
}
return;
}
team.addEntities(packet.getPlayers());
Set<String> added = team.addEntities(packet.getPlayers());
scoreboard.updateEntityNames(team, added, true);
}
case REMOVE_PLAYER -> {
if (team == null) {
logger.debug(LanguageUtils.getLocaleStringLog(
"geyser.network.translator.team.failed_not_registered",
packet.getAction(), packet.getTeamName()
));
if (logger.isDebug()) {
logger.debug("Error while translating Team Packet " + packet.getAction()
+ "! Scoreboard Team " + packet.getTeamName() + " is not registered."
);
}
return;
}
team.removeEntities(packet.getPlayers());
Set<String> removed = team.removeEntities(packet.getPlayers());
scoreboard.updateEntityNames(null, removed, true);
}
case REMOVE -> scoreboard.removeTeam(packet.getTeamName());
}

View File

@ -33,10 +33,12 @@ import com.nukkitx.protocol.bedrock.packet.SetScorePacket;
import lombok.Getter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.GeyserLogger;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.LanguageUtils;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@ -127,7 +129,8 @@ public final class Scoreboard {
return team;
}
team = new Team(this, teamName).addEntities(players);
team = new Team(this, teamName);
team.addEntities(players);
teams.put(teamName, team);
return team;
}
@ -155,6 +158,9 @@ public final class Scoreboard {
Team remove = teams.remove(teamName);
if (remove != null) {
remove.setUpdateType(REMOVE);
// We need to use the direct entities list here, so #refreshSessionPlayerDisplays also updates accordingly
// With the player's lack of a team in visibility checks
updateEntityNames(remove, remove.getEntities(), true);
}
}
@ -326,4 +332,47 @@ public final class Scoreboard {
}
return null;
}
/**
* Updates the display names of all entities in a given team.
* @param teamChange the players have either joined or left the team. Used for optimizations when just the display name updated.
*/
public void updateEntityNames(Team team, boolean teamChange) {
Set<String> names = new HashSet<>(team.getEntities());
updateEntityNames(team, names, teamChange);
}
/**
* Updates the display name of a set of entities within a given team. The team may also be null if the set is being removed
* from a team.
*/
public void updateEntityNames(@Nullable Team team, Set<String> names, boolean teamChange) {
if (names.remove(session.getPlayerEntity().getUsername()) && teamChange) {
// If the player's team changed, then other entities' teams may modify their visibility based on team status
refreshSessionPlayerDisplays();
}
if (!names.isEmpty()) {
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(session, team, true);
if (names.isEmpty()) {
break;
}
}
}
}
}
/**
* If the team's player was refreshed, then we need to go through every entity and check...
*/
private void refreshSessionPlayerDisplays() {
for (Entity entity : session.getEntityCache().getEntities().values()) {
if (entity instanceof PlayerEntity player) {
Team playerTeam = session.getWorldCache().getScoreboard().getTeamFor(player.getUsername());
player.updateDisplayName(session, playerTeam, true);
}
}
}
}

View File

@ -33,8 +33,7 @@ import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
@Getter
@ -43,7 +42,7 @@ public final class Team {
private final Scoreboard scoreboard;
private final String id;
@Getter(AccessLevel.NONE)
@Getter(AccessLevel.PACKAGE)
private final Set<String> entities;
@Setter private NameTagVisibility nameTagVisibility;
@Setter private TeamColor color;
@ -60,16 +59,16 @@ public final class Team {
entities = new ObjectOpenHashSet<>();
}
public Team addEntities(String... names) {
List<String> added = new ArrayList<>();
public Set<String> addEntities(String... names) {
Set<String> added = new HashSet<>();
for (String name : names) {
if (entities.add(name)) {
added.add(name);
}
}
if (added.size() == 0) {
return this;
if (added.isEmpty()) {
return added;
}
// we don't have to change the updateType,
// because the scores itself need updating, not the team
@ -81,13 +80,21 @@ public final class Team {
}
}
}
return this;
return added;
}
public void removeEntities(String... names) {
/**
* @return all removed entities from this team
*/
public Set<String> removeEntities(String... names) {
Set<String> removed = new HashSet<>();
for (String name : names) {
entities.remove(name);
if (entities.remove(name)) {
removed.add(name);
}
}
return removed;
}
public boolean hasEntity(String name) {
@ -172,7 +179,11 @@ public final class Team {
public boolean isVisibleFor(String entity) {
return switch (nameTagVisibility) {
case HIDE_FOR_OTHER_TEAMS -> hasEntity(entity);
case HIDE_FOR_OTHER_TEAMS -> {
// Player must be in a team in order for HIDE_FOR_OTHER_TEAMS to be triggered
Team team = scoreboard.getTeamFor(entity);
yield team == null || team == this;
}
case HIDE_FOR_OWN_TEAM -> !hasEntity(entity);
case ALWAYS -> true;
case NEVER -> false;