From 4127eea4014625d8e1479da1d84d30f3a9691866 Mon Sep 17 00:00:00 2001 From: R0bbyYT Date: Tue, 4 Aug 2020 12:44:07 +0200 Subject: [PATCH 1/6] Support for all living entities / Added team builder / Comments --- .../minestom/server/entity/LivingEntity.java | 39 ++ .../net/minestom/server/entity/Player.java | 31 +- .../packet/server/play/TeamsPacket.java | 139 +++++- .../net/minestom/server/scoreboard/Team.java | 443 ++++++++++++++---- .../server/scoreboard/TeamBuilder.java | 347 ++++++++++++++ .../server/scoreboard/TeamManager.java | 135 +++++- 6 files changed, 1016 insertions(+), 118 deletions(-) create mode 100644 src/main/java/net/minestom/server/scoreboard/TeamBuilder.java diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index bafc7ba4c..810934236 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -13,6 +13,7 @@ import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.player.PlayerConnection; +import net.minestom.server.scoreboard.Team; import net.minestom.server.sound.Sound; import net.minestom.server.sound.SoundCategory; import net.minestom.server.utils.Position; @@ -54,6 +55,8 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { */ private long fireDamagePeriod = 1000L; + private Team team; + public LivingEntity(EntityType entityType, Position spawnPosition) { super(entityType, spawnPosition); setupAttributes(); @@ -505,4 +508,40 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { fireDamagePeriod = timeUnit.toMilliseconds(fireDamagePeriod); this.fireDamagePeriod = fireDamagePeriod; } + + /** + * Change the {@link Team} for the entity + * + * @param team The new team + */ + public void setTeam(Team team) { + if (this.team == team) return; + + String member; + + if (this instanceof Player) { + Player player = (Player) this; + member = player.getUsername(); + } else { + member = this.uuid.toString(); + } + + if (this.team != null) { + this.team.removeMember(member); + } + + this.team = team; + if (team != null) { + team.addMember(member); + } + } + + /** + * Gets the {@link Team} of the entity + * + * @return the {@link Team} + */ + public Team getTeam() { + return team; + } } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 6351b80be..1137ef94c 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -109,7 +109,6 @@ public class Player extends LivingEntity implements CommandSender { private int blockBreakTime; private Set bossBars = new CopyOnWriteArraySet<>(); - private Team team; private BelowNameScoreboard belowNameScoreboard; /** @@ -527,8 +526,8 @@ public class Player extends LivingEntity implements CommandSender { viewerConnection.sendPacket(getRemovePlayerToList()); // Team - if (team != null && team.getPlayers().size() == 1) // If team only contains "this" player - viewerConnection.sendPacket(team.createTeamDestructionPacket()); + if (this.getTeam() != null && this.getTeam().getMembers().size() == 1) // If team only contains "this" player + viewerConnection.sendPacket(this.getTeam().createTeamDestructionPacket()); return result; } @@ -1371,21 +1370,6 @@ public class Player extends LivingEntity implements CommandSender { return heldSlot; } - public void setTeam(Team team) { - if (this.team == team) - return; - - if (this.team != null) { - this.team.removePlayer(this); - } - - this.team = team; - if (team != null) { - team.addPlayer(this); - sendPacketToViewers(team.getTeamsCreationPacket()); // FIXME: only if viewer hasn't already register this team - } - } - public void setBelowNameScoreboard(BelowNameScoreboard belowNameScoreboard) { if (this.belowNameScoreboard == belowNameScoreboard) return; @@ -1402,6 +1386,13 @@ public class Player extends LivingEntity implements CommandSender { } } + @Override + public void setTeam(Team team) { + super.setTeam(team); + if(team != null) + getPlayerConnection().sendPacket(team.getTeamsCreationPacket()); + } + /** * Used to get the {@link CustomBlock} that the player is currently mining * @@ -2005,8 +1996,8 @@ public class Player extends LivingEntity implements CommandSender { } // Team - if (team != null) - connection.sendPacket(team.getTeamsCreationPacket()); + if (this.getTeam() != null) + connection.sendPacket(this.getTeam().getTeamsCreationPacket()); EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket(); entityHeadLookPacket.entityId = getEntityId(); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/TeamsPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/TeamsPacket.java index 2c1d08a00..7ece592d9 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/TeamsPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/TeamsPacket.java @@ -4,35 +4,73 @@ import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; +/** + * The packet creates or updates teams + */ public class TeamsPacket implements ServerPacket { + /** + * The registry name of the team + */ public String teamName; + /** + * The action of the packet + */ public Action action; + /** + * The display name for the team + */ public String teamDisplayName; + /** + * The friendly flags to + */ public byte friendlyFlags; + /** + * Visibility state for the name tag + */ public NameTagVisibility nameTagVisibility; + /** + * Rule for the collision + */ public CollisionRule collisionRule; + /** + * The color of the team + */ public int teamColor; + /** + * The prefix of the team + */ public String teamPrefix; + /** + * The suffix of the team + */ public String teamSuffix; + /** + * An array with all entities in the team + */ public String[] entities; + /** + * Writes data into the {@link PacketWriter} + * + * @param writer The writer to writes + */ @Override public void write(PacketWriter writer) { - writer.writeSizedString(teamName); - writer.writeByte((byte) action.ordinal()); + writer.writeSizedString(this.teamName); + writer.writeByte((byte) this.action.ordinal()); switch (action) { case CREATE_TEAM: case UPDATE_TEAM_INFO: - writer.writeSizedString(teamDisplayName); - writer.writeByte(friendlyFlags); - writer.writeSizedString(nameTagVisibility.getIdentifier()); - writer.writeSizedString(collisionRule.getIdentifier()); - writer.writeVarInt(teamColor); - writer.writeSizedString(teamPrefix); - writer.writeSizedString(teamSuffix); + writer.writeSizedString(this.teamDisplayName); + writer.writeByte(this.friendlyFlags); + writer.writeSizedString(this.nameTagVisibility.getIdentifier()); + writer.writeSizedString(this.collisionRule.getIdentifier()); + writer.writeVarInt(this.teamColor); + writer.writeSizedString(this.teamPrefix); + writer.writeSizedString(this.teamSuffix); break; case REMOVE_TEAM: @@ -45,48 +83,127 @@ public class TeamsPacket implements ServerPacket { } + /** + * Gets the identifier of the packet + * + * @return the identifier + */ @Override public int getId() { return ServerPacketIdentifier.TEAMS; } + /** + * An enumeration which representing all actions for the packet + */ public enum Action { + /** + * An action to create a new team + */ CREATE_TEAM, + /** + * An action to remove a team + */ REMOVE_TEAM, + /** + * An action to update the team information + */ UPDATE_TEAM_INFO, + /** + * An action to add player to the team + */ ADD_PLAYERS_TEAM, + /** + * An action to remove player from the team + */ REMOVE_PLAYERS_TEAM } + /** + * An enumeration which representing all visibility states for the name tags + */ public enum NameTagVisibility { + /** + * The name tag is visible + */ ALWAYS("always"), + /** + * Hides the name tag for other teams + */ HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"), + /** + * Hides the name tag for the own team + */ HIDE_FOR_OWN_TEAM("hideForOwnTeam"), + /** + * The name tag is invisible + */ NEVER("never"); - private String identifier; + /** + * The identifier for the client + */ + private final String identifier; + /** + * Default constructor + * + * @param identifier The client identifier + */ NameTagVisibility(String identifier) { this.identifier = identifier; } + /** + * Gets the client identifier + * + * @return the identifier + */ public String getIdentifier() { return identifier; } } + /** + * An enumeration which representing all rules for the collision + */ public enum CollisionRule { + /** + * Can push all objects and can be pushed by all objects + */ ALWAYS("always"), + /** + * Can push objects of other teams, but teammates cannot + */ PUSH_OTHER_TEAMS("pushOtherTeams"), + /** + * Can only push objects of the same team + */ PUSH_OWN_TEAM("pushOwnTeam"), + /** + * Cannot push an object, but neither can they be pushed + */ NEVER("never"); - private String identifier; + /** + * The identifier for the client + */ + private final String identifier; + /** + * Default constructor + * + * @param identifier The identifier for the client + */ CollisionRule(String identifier) { this.identifier = identifier; } + /** + * Gets the identifier of the rule + * + * @return the identifier + */ public String getIdentifier() { return identifier; } diff --git a/src/main/java/net/minestom/server/scoreboard/Team.java b/src/main/java/net/minestom/server/scoreboard/Team.java index cc322a25b..a84ab1e2c 100644 --- a/src/main/java/net/minestom/server/scoreboard/Team.java +++ b/src/main/java/net/minestom/server/scoreboard/Team.java @@ -1,142 +1,350 @@ package net.minestom.server.scoreboard; import io.netty.buffer.ByteBuf; +import net.minestom.server.MinecraftServer; import net.minestom.server.chat.ChatColor; import net.minestom.server.chat.ColoredText; +import net.minestom.server.entity.LivingEntity; import net.minestom.server.entity.Player; +import net.minestom.server.network.PacketWriterUtils; import net.minestom.server.network.packet.server.play.TeamsPacket; +import net.minestom.server.network.packet.server.play.TeamsPacket.CollisionRule; +import net.minestom.server.network.packet.server.play.TeamsPacket.NameTagVisibility; import net.minestom.server.utils.PacketUtils; import java.util.Collections; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; +/** + * This object represents a team on a scoreboard that has a common display theme and other properties. + */ public class Team { - private String teamName; - private ColoredText teamDisplayName = ColoredText.of(""); - private byte friendlyFlags = 0x00; - private TeamsPacket.NameTagVisibility nameTagVisibility = TeamsPacket.NameTagVisibility.ALWAYS; - private TeamsPacket.CollisionRule collisionRule = TeamsPacket.CollisionRule.NEVER; - private ChatColor teamColor = ChatColor.WHITE; + /** + * A collection of all registered entities who are on the team + */ + private final Set members; + /** + * Creation packet for the team to create + */ + private final TeamsPacket teamsCreationPacket; + /** + * A byte buf to destroy the team + */ + private final ByteBuf teamsDestroyPacket; - private ColoredText prefix = ColoredText.of(""); - private ColoredText suffix = ColoredText.of(""); + /** + * The registry name of the team + */ + private final String teamName; + /** + * The display name of the team + */ + private ColoredText teamDisplayName; + /** + * A BitMask + */ + private byte friendlyFlags; + /** + * The visibility of the team + */ + private NameTagVisibility nameTagVisibility; + /** + * The collision rule of the team + */ + private CollisionRule collisionRule; - private String[] entities = new String[0]; - private Set players = new CopyOnWriteArraySet<>(); + /** + * Used to color the name of players on the team
+ * The color of a team defines how the names of the team members are visualized + */ + private ChatColor teamColor; - private TeamsPacket teamsCreationPacket; + /** + * Shown before the names of the players who belong to this team + */ + private ColoredText prefix; + /** + * Shown after the names of the player who belong to this team + */ + private ColoredText suffix; - private ByteBuf teamsDestroyPacket; + /** + * Identifiers for the entities in this team + */ + private String[] entities; + /** + * Default constructor to creates a team + * + * @param teamName The registry name for the team + */ protected Team(String teamName) { this.teamName = teamName; - teamsCreationPacket = new TeamsPacket(); - teamsCreationPacket.teamName = teamName; - teamsCreationPacket.action = TeamsPacket.Action.CREATE_TEAM; - teamsCreationPacket.teamDisplayName = teamDisplayName.toString(); - teamsCreationPacket.friendlyFlags = friendlyFlags; - teamsCreationPacket.nameTagVisibility = nameTagVisibility; - teamsCreationPacket.collisionRule = collisionRule; - teamsCreationPacket.teamColor = teamColor.getId(); - teamsCreationPacket.teamPrefix = prefix.toString(); - teamsCreationPacket.teamSuffix = suffix.toString(); - teamsCreationPacket.entities = entities; + this.teamDisplayName = ColoredText.of(""); + this.friendlyFlags = 0x00; + this.nameTagVisibility = NameTagVisibility.ALWAYS; + this.collisionRule = CollisionRule.ALWAYS; - TeamsPacket destroyPacket = new TeamsPacket(); - destroyPacket.teamName = teamName; - destroyPacket.action = TeamsPacket.Action.REMOVE_TEAM; - teamsDestroyPacket = PacketUtils.writePacket(destroyPacket); // Directly write packet since it will not change + this.teamColor = ChatColor.WHITE; + this.prefix = ColoredText.of(""); + this.suffix = ColoredText.of(""); + + this.entities = new String[0]; + this.members = new CopyOnWriteArraySet<>(); + + // Initializes creation packet + this.teamsCreationPacket = new TeamsPacket(); + this.teamsCreationPacket.teamName = teamName; + this.teamsCreationPacket.action = TeamsPacket.Action.CREATE_TEAM; + this.teamsCreationPacket.teamDisplayName = this.teamDisplayName.toString(); + this.teamsCreationPacket.friendlyFlags = this.friendlyFlags; + this.teamsCreationPacket.nameTagVisibility = this.nameTagVisibility; + this.teamsCreationPacket.collisionRule = this.collisionRule; + this.teamsCreationPacket.teamColor = this.teamColor.getId(); + this.teamsCreationPacket.teamPrefix = this.prefix.toString(); + this.teamsCreationPacket.teamSuffix = this.suffix.toString(); + this.teamsCreationPacket.entities = this.entities; + + // Directly write packet since it will not change + this.teamsDestroyPacket = PacketUtils.writePacket(this.createTeamDestructionPacket()); } - public void addPlayer(Player player) { - final String newElement = player.getUsername(); + /** + * Adds a member to the {@link Team} + *
+ * This member can be a {@link Player} or an {@link LivingEntity} + * + * @param member The member to be added + */ + public void addMember(String member) { + final String[] entitiesCache = new String[this.entities.length + 1]; + System.arraycopy(this.entities, 0, entitiesCache, 0, this.entities.length); + entitiesCache[this.entities.length] = member; + this.entities = entitiesCache; + this.teamsCreationPacket.entities = this.entities; - TeamsPacket addPlayerPacket = new TeamsPacket(); - addPlayerPacket.teamName = teamName; + // Adds a new member to the team + this.members.add(member); + + // Initializes add player packet + final TeamsPacket addPlayerPacket = new TeamsPacket(); + addPlayerPacket.teamName = this.teamName; addPlayerPacket.action = TeamsPacket.Action.ADD_PLAYERS_TEAM; - addPlayerPacket.entities = new String[]{newElement}; - for (Player p : players) { - p.getPlayerConnection().sendPacket(addPlayerPacket); - } - - String[] entitiesCache = new String[entities.length + 1]; - System.arraycopy(entities, 0, entitiesCache, 0, entities.length); - entitiesCache[entities.length] = newElement; - this.entities = entitiesCache; - this.teamsCreationPacket.entities = entities; - - this.players.add(player); - player.getPlayerConnection().sendPacket(teamsCreationPacket); + addPlayerPacket.entities = new String[]{member}; + // Sends to all online players the add player packet + PacketWriterUtils.writeAndSend(MinecraftServer.getConnectionManager().getOnlinePlayers(), addPlayerPacket); } - public void removePlayer(Player player) { - TeamsPacket removePlayerPacket = new TeamsPacket(); - removePlayerPacket.teamName = teamName; + /** + * Removes a member from the {@link Team} + * + * @param member The member to be removed + */ + public void removeMember(String member) { + // Initializes remove player packet + final TeamsPacket removePlayerPacket = new TeamsPacket(); + removePlayerPacket.teamName = this.teamName; removePlayerPacket.action = TeamsPacket.Action.REMOVE_PLAYERS_TEAM; - removePlayerPacket.entities = new String[]{player.getUsername()}; - for (Player p : players) { - p.getPlayerConnection().sendPacket(removePlayerPacket); - } + removePlayerPacket.entities = new String[]{member}; + // Sends to all online player teh remove player packet + PacketWriterUtils.writeAndSend(MinecraftServer.getConnectionManager().getOnlinePlayers(), removePlayerPacket); - this.players.remove(player); - player.getPlayerConnection().sendPacket(teamsDestroyPacket, true); // TODO do not destroy, simply remove the player from the team + // Removes the player from the + this.members.remove(member); - String[] entitiesCache = new String[entities.length - 1]; + final String[] entitiesCache = new String[this.entities.length - 1]; int count = 0; - for (Player p : players) { - entitiesCache[count++] = p.getUsername(); + for (String teamMember : this.members) { + entitiesCache[count++] = teamMember; } this.entities = entitiesCache; - this.teamsCreationPacket.entities = entities; + this.teamsCreationPacket.entities = this.entities; } + /** + * Change the display name of the team + *

+ * Warning: This is only changed on the server side + * + * @param teamDisplayName The new display name + */ public void setTeamDisplayName(ColoredText teamDisplayName) { this.teamDisplayName = teamDisplayName; this.teamsCreationPacket.teamDisplayName = teamDisplayName.toString(); + } + + /** + * Change the display name of the team and sends an update packet + * + * @param teamDisplayName The new display name + */ + public void updateTeamDisplayName(ColoredText teamDisplayName) { + this.setTeamDisplayName(teamDisplayName); sendUpdatePacket(); } - public void setNameTagVisibility(TeamsPacket.NameTagVisibility nameTagVisibility) { - this.nameTagVisibility = nameTagVisibility; - this.teamsCreationPacket.nameTagVisibility = nameTagVisibility; + /** + * Change the {@link NameTagVisibility} of the team + *

+ * Warning: This is only changed on the server side + * + * @param visibility The new tag visibility + */ + public void setNameTagVisibility(NameTagVisibility visibility) { + this.nameTagVisibility = visibility; + this.teamsCreationPacket.nameTagVisibility = visibility; + } + + /** + * Change the {@link NameTagVisibility} of the team and sends an update packet + * + * @param nameTagVisibility The new tag visibility + */ + public void updateNameTagVisibility(NameTagVisibility nameTagVisibility) { + this.setNameTagVisibility(nameTagVisibility); sendUpdatePacket(); } - public void setCollisionRule(TeamsPacket.CollisionRule collisionRule) { - this.collisionRule = collisionRule; - this.teamsCreationPacket.collisionRule = collisionRule; + /** + * Change the {@link CollisionRule} of the team + *

+ * Warning: This is only changed on the server side + * + * @param rule The new rule + */ + public void setCollisionRule(CollisionRule rule) { + this.collisionRule = rule; + this.teamsCreationPacket.collisionRule = rule; + } + + /** + * Change the collision rule of the team and sends an update packet + * + * @param collisionRule The new collision rule + */ + public void updateCollisionRule(CollisionRule collisionRule) { + this.setCollisionRule(collisionRule); sendUpdatePacket(); } - public void setTeamColor(ChatColor teamColor) { - this.teamColor = teamColor; - this.teamsCreationPacket.teamColor = teamColor.getId(); + /** + * Change the color of the team + *

+ * Warning: This is only changed on the server side + * + * @param color The new team color + */ + public void setTeamColor(ChatColor color) { + this.teamColor = color; + this.teamsCreationPacket.teamColor = color.getId(); + } + + /** + * Change the color of the team and sends an update packet + * + * @param teamColor The new team color + */ + public void updateTeamColor(ChatColor teamColor) { + this.setTeamColor(teamColor); sendUpdatePacket(); } + /** + * Change the prefix of the team + *

+ * Warning: This is only changed on the server side + * + * @param prefix The new prefix + */ public void setPrefix(ColoredText prefix) { this.prefix = prefix; this.teamsCreationPacket.teamPrefix = prefix.toString(); + } + + /** + * Change the prefix of the team and sends an update packet + * + * @param prefix The new prefix + */ + public void updatePrefix(ColoredText prefix) { + this.setPrefix(prefix); sendUpdatePacket(); } + /** + * Change the suffix of the team + *

+ * Warning: This is only changed on the server side + * + * @param suffix The new suffix + */ public void setSuffix(ColoredText suffix) { this.suffix = suffix; this.teamsCreationPacket.teamSuffix = suffix.toString(); + } + + /** + * Change the suffix of the team and sends an update packet + * + * @param suffix The new suffix + */ + public void updateSuffix(ColoredText suffix) { + this.setSuffix(suffix); sendUpdatePacket(); } + /** + * Change the friendly flags of the team + *

+ * Warning: This is only changed on the server side + * + * @param flag The new friendly flag + */ + public void setFriendlyFlags(byte flag) { + this.friendlyFlags = flag; + this.teamsCreationPacket.friendlyFlags = flag; + } + + /** + * Change the friendly flags of the team and sends an update packet + * + * @param flag The new friendly flag + */ + public void updateFriendlyFlags(byte flag) { + this.setFriendlyFlags(flag); + this.sendUpdatePacket(); + } + + /** + * Gets the registry name of the team + * + * @return the registry name + */ public String getTeamName() { return teamName; } + /** + * Gets the creation packet to add a team + * + * @return the packet to add the team + */ public TeamsPacket getTeamsCreationPacket() { return teamsCreationPacket; } + public ByteBuf getTeamsDestroyPacket() { + return teamsDestroyPacket; + } + + /** + * Creates an destruction packet to remove the team + * + * @return the packet to remove the team + */ public TeamsPacket createTeamDestructionPacket() { TeamsPacket teamsPacket = new TeamsPacket(); teamsPacket.teamName = teamName; @@ -144,22 +352,99 @@ public class Team { return teamsPacket; } - public Set getPlayers() { - return Collections.unmodifiableSet(players); + /** + * Obtains an unmodifiable {@link Set} of registered players who are on the team + * + * @return an unmodifiable {@link Set} of registered players + */ + public Set getMembers() { + return Collections.unmodifiableSet(members); } - private void sendUpdatePacket() { - TeamsPacket updatePacket = new TeamsPacket(); - updatePacket.teamName = teamName; + /** + * Gets the display name of the team + * + * @return the display name + */ + public ColoredText getTeamDisplayName() { + return teamDisplayName; + } + + /** + * Gets the friendly flags of the team + * + * @return the friendly flags + */ + public byte getFriendlyFlags() { + return friendlyFlags; + } + + /** + * Gets the tag visibility of the team + * + * @return the tag visibility + */ + public NameTagVisibility getNameTagVisibility() { + return nameTagVisibility; + } + + /** + * Gets the collision rule of the team + * + * @return the collision rule + */ + public CollisionRule getCollisionRule() { + return collisionRule; + } + + /** + * Gets the color of the team + * + * @return the team color + */ + public ChatColor getTeamColor() { + return teamColor; + } + + /** + * Gets the prefix of the team + * + * @return the team prefix + */ + public ColoredText getPrefix() { + return prefix; + } + + /** + * Gets the suffix of the team + * + * @return the suffix team + */ + public ColoredText getSuffix() { + return suffix; + } + + public String[] getEntities() { + return entities; + } + + /** + * Sends an {@link TeamsPacket.Action#UPDATE_TEAM_INFO} packet + */ + public void sendUpdatePacket() { + final TeamsPacket updatePacket = new TeamsPacket(); + updatePacket.teamName = this.teamName; updatePacket.action = TeamsPacket.Action.UPDATE_TEAM_INFO; - updatePacket.teamDisplayName = teamDisplayName.toString(); - updatePacket.friendlyFlags = friendlyFlags; - updatePacket.nameTagVisibility = nameTagVisibility; - updatePacket.collisionRule = collisionRule; - updatePacket.teamColor = teamColor.getId(); - updatePacket.teamPrefix = prefix.toString(); - updatePacket.teamSuffix = suffix.toString(); + updatePacket.teamDisplayName = this.teamDisplayName.toString(); + updatePacket.friendlyFlags = this.friendlyFlags; + updatePacket.nameTagVisibility = this.nameTagVisibility; + updatePacket.collisionRule = this.collisionRule; + updatePacket.teamColor = this.teamColor.getId(); + updatePacket.teamPrefix = this.prefix.toString(); + updatePacket.teamSuffix = this.suffix.toString(); ByteBuf buffer = PacketUtils.writePacket(updatePacket); - players.forEach(p -> p.getPlayerConnection().sendPacket(buffer, true)); + for (Player onlinePlayer : MinecraftServer.getConnectionManager().getOnlinePlayers()) { + onlinePlayer.getPlayerConnection().sendPacket(buffer, true); + } } } diff --git a/src/main/java/net/minestom/server/scoreboard/TeamBuilder.java b/src/main/java/net/minestom/server/scoreboard/TeamBuilder.java new file mode 100644 index 000000000..d27920413 --- /dev/null +++ b/src/main/java/net/minestom/server/scoreboard/TeamBuilder.java @@ -0,0 +1,347 @@ +package net.minestom.server.scoreboard; + +import net.minestom.server.chat.ChatColor; +import net.minestom.server.chat.ColoredText; +import net.minestom.server.network.packet.server.play.TeamsPacket.CollisionRule; +import net.minestom.server.network.packet.server.play.TeamsPacket.NameTagVisibility; + +/** + * A builder which represents a fluent Object to built teams + */ +public class TeamBuilder { + + /** + * The management for the teams + */ + private final TeamManager teamManager; + /** + * The team to create + */ + private final Team team; + /** + * True, if it should send an update packet + */ + private boolean updateTeam; + + /** + * Creates an team builder + * + * @param name The name of the new team + * @param teamManager The manager for the team + */ + public TeamBuilder(String name, TeamManager teamManager) { + this(teamManager.exists(name) ? teamManager.getTeam(name) : new Team(name), teamManager); + } + + /** + * Creates an team builder + * + * @param team The new team + * @param teamManager The manager for the team + */ + private TeamBuilder(Team team, TeamManager teamManager) { + this.team = team; + this.teamManager = teamManager; + this.updateTeam = false; + } + + /** + * Updates the prefix of the {@link Team} + * + * @param prefix The new prefix + * @return this builder, for chaining + */ + public TeamBuilder updatePrefix(String prefix) { + return this.updatePrefix(ColoredText.of(prefix)); + } + + /** + * Updates the prefix of the {@link Team} + * + * @param prefix The new prefix + * @return this builder, for chaining + */ + public TeamBuilder updatePrefix(ColoredText prefix) { + this.team.updatePrefix(prefix); + return this; + } + + /** + * Updates the color of the {@link Team} + * + * @param color The new color + * @return this builder, for chaining + */ + public TeamBuilder updateTeamColor(ChatColor color) { + this.team.updateTeamColor(color); + return this; + } + + /** + * Updates the suffix of the {@link Team} + * + * @param suffix The new suffix + * @return this builder, for chaining + */ + public TeamBuilder updateSuffix(String suffix) { + return updateSuffix(ColoredText.of(suffix)); + } + + /** + * Updates the suffix of the {@link Team} + * + * @param suffix The new suffix + * @return this builder, for chaining + */ + public TeamBuilder updateSuffix(ColoredText suffix) { + this.team.updateSuffix(suffix); + return this; + } + + /** + * Updates the display name of the {@link Team} + * + * @param displayName The new display name + * @return this builder, for chaining + */ + public TeamBuilder updateTeamDisplayName(String displayName) { + return this.updateTeamDisplayName(ColoredText.of(displayName)); + } + + /** + * Updates the display name of the {@link Team} + * + * @param displayName The new display name + * @return this builder, for chaining + */ + public TeamBuilder updateTeamDisplayName(ColoredText displayName) { + this.team.updateTeamDisplayName(displayName); + return this; + } + + /** + * Updates the {@link CollisionRule} of the {@link Team} + * + * @param rule The new rule + * @return this builder, for chaining + */ + public TeamBuilder updateCollisionRule(CollisionRule rule) { + this.team.updateCollisionRule(rule); + return this; + } + + /** + * Updates the {@link NameTagVisibility} of the {@link Team} + * + * @param visibility The new tag visibility + * @return this builder, for chaining + */ + public TeamBuilder updateNameTagVisibility(NameTagVisibility visibility) { + this.team.updateNameTagVisibility(visibility); + return this; + } + + /** + * Updates the friendly flags of the {@link Team} + * + * @param flag The new friendly flag + * @return this builder, for chaining + */ + public TeamBuilder updateFriendlyFlags(byte flag) { + this.team.updateFriendlyFlags(flag); + return this; + } + + /** + * Updates the friendly flags for allow friendly fire + * + * @return this builder, for chaining + */ + public TeamBuilder updateAllowFriendlyFire() { + return this.updateFriendlyFlags((byte) 0x01); + } + + /** + * Updates the friendly flags to sees invisible players of own team + * + * @return this builder, for chaining + */ + public TeamBuilder updateSeeInvisiblePlayers() { + return this.updateFriendlyFlags((byte) 0x01); + } + + /** + * Change the prefix of the {@link Team} without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @param prefix The new prefix + * @return this builder, for chaining + */ + public TeamBuilder prefix(String prefix) { + return this.prefix(ColoredText.of(prefix)); + } + + /** + * Change the prefix of the {@link Team} without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @param prefix The new prefix + * @return this builder, for chaining + */ + public TeamBuilder prefix(ColoredText prefix) { + this.team.setPrefix(prefix); + return this; + } + + /** + * Change the suffix of the {@link Team} without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @param suffix The new suffix + * @return this builder, for chaining + */ + public TeamBuilder suffix(String suffix) { + this.team.setSuffix(ColoredText.of(suffix)); + return this; + } + + /** + * Change the suffix of the {@link Team} without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @param suffix The new suffix + * @return this builder, for chaining + */ + public TeamBuilder suffix(ColoredText suffix) { + this.team.setSuffix(suffix); + return this; + } + + /** + * Change the color of the {@link Team} without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @param color The new team color + * @return this builder, for chaining + */ + public TeamBuilder teamColor(ChatColor color) { + this.team.setTeamColor(color); + return this; + } + + /** + * Change the display name of the {@link Team} without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @param displayName The new display name + * @return this builder, for chaining + */ + public TeamBuilder teamDisplayName(String displayName) { + return this.teamDisplayName(ColoredText.of(displayName)); + } + + /** + * Change the display name of the {@link Team} without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @param displayName The new display name + * @return this builder, for chaining + */ + public TeamBuilder teamDisplayName(ColoredText displayName) { + this.team.setTeamDisplayName(displayName); + return this; + } + + /** + * Change the {@link CollisionRule} of the {@link Team} without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @param rule The new rule + * @return this builder, for chaining + */ + public TeamBuilder collisionRule(CollisionRule rule) { + this.team.setCollisionRule(rule); + return this; + } + + /** + * Change the {@link NameTagVisibility} of the {@link Team} without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @param visibility The new tag visibility + * @return this builder, for chaining + */ + public TeamBuilder nameTagVisibility(NameTagVisibility visibility) { + this.team.setNameTagVisibility(visibility); + return this; + } + + /** + * Change the friendly flags of the {@link Team} without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @param flag The new flag + * @return this builder, for chaining + */ + public TeamBuilder friendlyFlags(byte flag) { + this.team.setFriendlyFlags(flag); + return this; + } + + /** + * Change the friendly flags for allow friendly fire without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @return this builder, for chaining + */ + public TeamBuilder allowFriendlyFire() { + return this.friendlyFlags((byte) 0x01); + } + + /** + * Change the friendly flags to sees invisible players of own team without an update packet + *

+ * Warning: If you do not call {@link #updateTeamPacket()}, this is only changed of the server side + * + * @return this builder, for chaining + */ + public TeamBuilder seeInvisiblePlayers() { + return this.friendlyFlags((byte) 0x01); + } + + /** + * Allows to send an update packet when the team is built + * + * @return this builder, for chaining + */ + public TeamBuilder updateTeamPacket() { + this.updateTeam = true; + return this; + } + + /** + * Built a team + * + * @return the built team + */ + public Team build() { + if (!this.teamManager.exists(this.team)) this.teamManager.registerNewTeam(this.team); + if (this.updateTeam) { + this.team.sendUpdatePacket(); + this.updateTeam = false; + } + return this.team; + } + +} diff --git a/src/main/java/net/minestom/server/scoreboard/TeamManager.java b/src/main/java/net/minestom/server/scoreboard/TeamManager.java index 9a27f87d5..eb9687230 100644 --- a/src/main/java/net/minestom/server/scoreboard/TeamManager.java +++ b/src/main/java/net/minestom/server/scoreboard/TeamManager.java @@ -1,21 +1,140 @@ package net.minestom.server.scoreboard; +import net.minestom.server.MinecraftServer; +import net.minestom.server.chat.ChatColor; +import net.minestom.server.chat.ColoredText; +import net.minestom.server.entity.Player; +import net.minestom.server.network.PacketWriterUtils; +import net.minestom.server.network.packet.server.ServerPacket; + import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -// TODO +/** + * An object which manages all the {@link Team}'s + */ public final class TeamManager { - // Represents all registered teams - private Set teams = new CopyOnWriteArraySet<>(); + /** + * Represents all registered teams + */ + private final Set teams; - public Team createTeam(String teamName) { - Team team = new Team(teamName); - this.teams.add(team); - return team; + /** + * Default constructor + */ + public TeamManager() { + this.teams = new CopyOnWriteArraySet<>(); } + /** + * Registers a new {@link Team} + * + * @param team The team to be registered + */ + protected void registerNewTeam(Team team) { + this.teams.add(team); + this.broadcastPacket(team.getTeamsCreationPacket()); + } + + /** + * Initializes a new {@link TeamBuilder} for creating a team + * + * @param name The registry name of the team + * @return the team builder + */ + public TeamBuilder createBuilder(String name) { + return new TeamBuilder(name, this); + } + + /** + * Creates a {@link Team} with only the registry name + * + * @param name The registry name + * @return the created {@link Team} + */ + public Team createTeam(String name) { + return this.createBuilder(name).build(); + } + + /** + * Creates a {@link Team} with the registry name, prefix, suffix and the team color + * + * @param name The registry name + * @param prefix The team prefix + * @param teamColor The team color + * @param suffix The team suffix + * @return the created {@link Team} with a prefix, teamColor and suffix + */ + public Team createTeam(String name, ColoredText prefix, ChatColor teamColor, ColoredText suffix) { + return this.createBuilder(name).prefix(prefix).teamColor(teamColor).suffix(suffix).updateTeamPacket().build(); + } + + /** + * Creates a {@link Team} with the registry name, display name, prefix, suffix and the team colro + * + * @param name The registry name + * @param displayName The display name + * @param prefix The team prefix + * @param teamColor The team color + * @param suffix The team suffix + * @return the created {@link Team} with a prefix, teamColor, suffix and the display name + */ + public Team createTeam(String name, ColoredText displayName, ColoredText prefix, ChatColor teamColor, ColoredText suffix) { + return this.createBuilder(name).teamDisplayName(displayName).prefix(prefix).teamColor(teamColor).suffix(suffix).updateTeamPacket().build(); + } + + /** + * Gets a {@link Team} with the given name + * + * @param teamName The registry name of the team + * @return a registered {@link Team} or {@code null} + */ + public Team getTeam(String teamName) { + for (Team team : this.teams) { + if (team.getTeamName().equals(teamName)) return team; + } + return null; + } + + /** + * Checks if the given name a registry name of a registered {@link Team} + * + * @param teamName The name of the team + * @return {@code true} if the team is registered, otherwise {@code false} + */ + public boolean exists(String teamName) { + for (Team team : this.teams) { + if (team.getTeamName().equals(teamName)) return true; + } + return false; + } + + /** + * Checks if the given {@link Team} registered + * + * @param team The searched team + * @return {@code true} if the team is registered, otherwise {@code false} + */ + public boolean exists(Team team) { + return this.exists(team.getTeamName()); + } + + /** + * Gets a {@link Set} with all registered {@link Team}'s + * + * @return a {@link Set} with all registered {@link Team}'s + */ public Set getTeams() { - return teams; + return this.teams; + } + + /** + * Broadcasts to all online {@link Player}'s a {@link ServerPacket} + * + * @param packet The packet to broadcast + */ + private void broadcastPacket(ServerPacket packet) { + PacketWriterUtils.writeAndSend(MinecraftServer.getConnectionManager().getOnlinePlayers(), packet); } } From 70c969ced7c26ae569f22fad1f55be8ed07ce295 Mon Sep 17 00:00:00 2001 From: R0bbyYT Date: Wed, 5 Aug 2020 11:02:54 +0200 Subject: [PATCH 2/6] Deletes teams, gets players/entities of a team --- .../server/scoreboard/TeamManager.java | 76 +++++++++++++++++++ .../minestom/server/utils/UniqueIdUtils.java | 23 ++++++ 2 files changed, 99 insertions(+) create mode 100644 src/main/java/net/minestom/server/utils/UniqueIdUtils.java diff --git a/src/main/java/net/minestom/server/scoreboard/TeamManager.java b/src/main/java/net/minestom/server/scoreboard/TeamManager.java index eb9687230..238b3c8fb 100644 --- a/src/main/java/net/minestom/server/scoreboard/TeamManager.java +++ b/src/main/java/net/minestom/server/scoreboard/TeamManager.java @@ -1,12 +1,17 @@ package net.minestom.server.scoreboard; +import io.netty.buffer.ByteBuf; import net.minestom.server.MinecraftServer; import net.minestom.server.chat.ChatColor; import net.minestom.server.chat.ColoredText; +import net.minestom.server.entity.LivingEntity; import net.minestom.server.entity.Player; import net.minestom.server.network.PacketWriterUtils; import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.utils.UniqueIdUtils; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -37,6 +42,30 @@ public final class TeamManager { this.broadcastPacket(team.getTeamsCreationPacket()); } + /** + * Deletes a {@link Team} + * + * @param registryName The registry name of team + * @return {@code true} if the team was deleted, otherwise {@code false} + */ + public boolean deleteTeam(String registryName) { + Team team = this.getTeam(registryName); + if (team == null) return false; + return this.deleteTeam(team); + } + + /** + * Deletes a {@link Team} + * + * @param team The team to be deleted + * @return {@code true} if the team was deleted, otherwise {@code false} + */ + public boolean deleteTeam(Team team) { + // Sends to all online players a team destroy packet + this.broadcastBuffer(team.getTeamsDestroyPacket()); + return this.teams.remove(team); + } + /** * Initializes a new {@link TeamBuilder} for creating a team * @@ -120,6 +149,42 @@ public final class TeamManager { return this.exists(team.getTeamName()); } + /** + * Gets a {@link List} with all registered {@link Player} in the team + *
+ * Note: The list exclude all entities. To get all entities of the team, you can use {@link #getEntities(Team)} + * + * @param team The team + * @return a {@link List} with all registered {@link Player} + */ + public List getPlayers(Team team) { + List players = new ArrayList<>(); + for (String member : team.getMembers()) { + boolean match = UniqueIdUtils.isUniqueId(member); + + if (!match) players.add(member); + } + return players; + } + + /** + * Gets a {@link List} with all registered {@link LivingEntity} in the team + *
+ * Note: The list exclude all players. To get all players of the team, you can use {@link #getPlayers(Team)} + * + * @param team The team + * @return a {@link List} with all registered {@link LivingEntity} + */ + public List getEntities(Team team) { + List entities = new ArrayList<>(); + for (String member : team.getMembers()) { + boolean match = UniqueIdUtils.isUniqueId(member); + + if (match) entities.add(member); + } + return entities; + } + /** * Gets a {@link Set} with all registered {@link Team}'s * @@ -137,4 +202,15 @@ public final class TeamManager { private void broadcastPacket(ServerPacket packet) { PacketWriterUtils.writeAndSend(MinecraftServer.getConnectionManager().getOnlinePlayers(), packet); } + + /** + * Broadcasts to all online {@link Player}'s a buffer + * + * @param buffer The buffer to broadcast + */ + private void broadcastBuffer(ByteBuf buffer) { + for (Player onlinePlayer : MinecraftServer.getConnectionManager().getOnlinePlayers()) { + onlinePlayer.getPlayerConnection().sendPacket(buffer, true); + } + } } diff --git a/src/main/java/net/minestom/server/utils/UniqueIdUtils.java b/src/main/java/net/minestom/server/utils/UniqueIdUtils.java new file mode 100644 index 000000000..4774bc436 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/UniqueIdUtils.java @@ -0,0 +1,23 @@ +package net.minestom.server.utils; + +import java.util.UUID; +import java.util.regex.Pattern; + +/** + * An utilities class for {@link UUID} + */ +public class UniqueIdUtils { + + public static final Pattern UNIQUE_ID_PATTERN = Pattern.compile("\\b[0-9a-f]{8}\\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\\b[0-9a-f]{12}\\b"); + + /** + * Checks whether the {@code input} string is an {@link UUID} + * + * @param input The input string to be checked + * @return {@code true} if the input an unique identifier, otherwise {@code false} + */ + public static boolean isUniqueId(String input) { + return input.matches(UNIQUE_ID_PATTERN.pattern()); + } + +} From 1ad20209d0f1dc29406026b9df9a19b9eedddc98 Mon Sep 17 00:00:00 2001 From: R0bbyYT Date: Fri, 7 Aug 2020 12:16:56 +0200 Subject: [PATCH 3/6] BelowNameTag synchronisation, documentation --- .../net/minestom/server/entity/Player.java | 17 +- .../play/ScoreboardObjectivePacket.java | 26 ++- .../scoreboard/BelowNameScoreboard.java | 121 +++++++++---- .../minestom/server/scoreboard/Sidebar.java | 169 ++++++++++++++++-- .../minestom/server/utils/UniqueIdUtils.java | 2 +- 5 files changed, 274 insertions(+), 61 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 1137ef94c..dd15f8f76 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -109,7 +109,7 @@ public class Player extends LivingEntity implements CommandSender { private int blockBreakTime; private Set bossBars = new CopyOnWriteArraySet<>(); - private BelowNameScoreboard belowNameScoreboard; + private BelowNameScoreboard belowNameScoreboard = null; /** * Last damage source to hit this player, used to display the death message. @@ -1370,26 +1370,25 @@ public class Player extends LivingEntity implements CommandSender { return heldSlot; } + /** + * Change the tag below the name + * + * @param belowNameScoreboard The new below name tag + */ public void setBelowNameScoreboard(BelowNameScoreboard belowNameScoreboard) { - if (this.belowNameScoreboard == belowNameScoreboard) - return; + if (this.belowNameScoreboard == belowNameScoreboard) return; if (this.belowNameScoreboard != null) { this.belowNameScoreboard.removeViewer(this); } this.belowNameScoreboard = belowNameScoreboard; - if (belowNameScoreboard != null) { - belowNameScoreboard.addViewer(this); - belowNameScoreboard.displayScoreboard(this); - getViewers().forEach(player -> belowNameScoreboard.addViewer(player)); - } } @Override public void setTeam(Team team) { super.setTeam(team); - if(team != null) + if (team != null) getPlayerConnection().sendPacket(team.getTeamsCreationPacket()); } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java index 45ad1e17c..46f4969be 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java @@ -7,10 +7,24 @@ import net.minestom.server.network.packet.server.ServerPacketIdentifier; public class ScoreboardObjectivePacket implements ServerPacket { + /** + * An unique name for the objective + */ public String objectiveName; + /** + * 0 = create the scoreboard
+ * 1 = to remove the scoreboard
+ * 2 = to update the display text + */ public byte mode; + /** + * The text to be displayed for the score + */ public ColoredText objectiveValue; - public int type; + /** + * The type how the score is displayed + */ + public Type type; @Override public void write(PacketWriter writer) { @@ -19,7 +33,7 @@ public class ScoreboardObjectivePacket implements ServerPacket { if (mode == 0 || mode == 2) { writer.writeSizedString(objectiveValue.toString()); - writer.writeVarInt(type); + writer.writeVarInt(type.ordinal()); } } @@ -27,4 +41,12 @@ public class ScoreboardObjectivePacket implements ServerPacket { public int getId() { return ServerPacketIdentifier.SCOREBOARD_OBJECTIVE; } + + /** + * This enumeration represents all available types for the scoreboard objective + */ + public enum Type { + INTEGER, + HEARTS + } } diff --git a/src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java b/src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java index 1894ba64b..7d2173afc 100644 --- a/src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java +++ b/src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java @@ -11,68 +11,115 @@ import net.minestom.server.network.player.PlayerConnection; import java.util.Collections; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.atomic.AtomicInteger; -// TODO fix score and objective refresh +/** + * Represents a scoreboard which rendered a tag below the name + */ public class BelowNameScoreboard implements Viewable { - private static final AtomicInteger counter = new AtomicInteger(); + private final Set viewers = new CopyOnWriteArraySet<>(); + private final String objectiveName; - // WARNING: you shouldn't create scoreboards/teams with the same prefixes as those - private static final String SCOREBOARD_PREFIX = "bn-"; - private static final String TEAM_PREFIX = "bnt-"; + private final ScoreboardObjectivePacket scoreboardObjectivePacket; + private final ScoreboardObjectivePacket destructionObjectivePacket; + private final DisplayScoreboardPacket displayScoreboardPacket; - private Set viewers = new CopyOnWriteArraySet<>(); + /** + * Creates a new below name scoreboard + * + * @param name The objective name of the scoreboard + * @param value The value of the scoreboard + */ + public BelowNameScoreboard(String name, String value) { + this.objectiveName = name; - private String objectiveName; + this.scoreboardObjectivePacket = this.getCreationObjectivePacket(value); - private ScoreboardObjectivePacket scoreboardObjectivePacket; - private DisplayScoreboardPacket displayScoreboardPacket; + this.displayScoreboardPacket = new DisplayScoreboardPacket(); + this.displayScoreboardPacket.position = 2; // Below name + this.displayScoreboardPacket.scoreName = this.objectiveName; - public BelowNameScoreboard() { - this.objectiveName = SCOREBOARD_PREFIX + counter.incrementAndGet(); - - scoreboardObjectivePacket = new ScoreboardObjectivePacket(); - scoreboardObjectivePacket.objectiveName = objectiveName; - scoreboardObjectivePacket.mode = 0; - scoreboardObjectivePacket.objectiveValue = ColoredText.of(objectiveName); - scoreboardObjectivePacket.type = 0; - - displayScoreboardPacket = new DisplayScoreboardPacket(); - displayScoreboardPacket.position = 2; // Below name - displayScoreboardPacket.scoreName = objectiveName; + this.destructionObjectivePacket = this.getDestructionObjectivePacket(); } - public void updateScore(Player player, int score) { - UpdateScorePacket updateScorePacket = new UpdateScorePacket(); - updateScorePacket.entityName = player.getUsername(); - updateScorePacket.action = 0; // Create/update - updateScorePacket.objectiveName = objectiveName; - updateScorePacket.value = score; + /** + * Creates a creation objective packet for the tag below name + * + * @param value The value of the tag + * @return the creation objective packet + */ + private ScoreboardObjectivePacket getCreationObjectivePacket(String value) { + ScoreboardObjectivePacket packet = new ScoreboardObjectivePacket(); + packet.objectiveName = this.objectiveName; + packet.mode = 0; // Create/Update + packet.objectiveValue = ColoredText.of(value); + packet.type = ScoreboardObjectivePacket.Type.INTEGER; - sendPacketToViewers(updateScorePacket); + return packet; + } + + /** + * Creates the destruction objective packet for the tag below name + * + * @return the destruction objective packet + */ + private ScoreboardObjectivePacket getDestructionObjectivePacket() { + ScoreboardObjectivePacket packet = new ScoreboardObjectivePacket(); + packet.objectiveName = this.objectiveName; + packet.mode = 1; + packet.objectiveValue = ColoredText.of(""); + packet.type = ScoreboardObjectivePacket.Type.INTEGER; + + return packet; + } + + /** + * Updates the score of a {@link Player} + * + * @param player The player + * @param score The new score + */ + public void updateScore(Player player, int score) { + UpdateScorePacket packet = new UpdateScorePacket(); + packet.entityName = player.getUsername(); + packet.action = 0; //Create/Update + packet.objectiveName = this.objectiveName; + packet.value = score; + + // Sends to all viewers an update packet + sendPacketToViewers(packet); } @Override public boolean addViewer(Player player) { boolean result = this.viewers.add(player); - PlayerConnection playerConnection = player.getPlayerConnection(); - playerConnection.sendPacket(scoreboardObjectivePacket); + PlayerConnection connection = player.getPlayerConnection(); + + if (result) { + connection.sendPacket(this.scoreboardObjectivePacket); + connection.sendPacket(this.displayScoreboardPacket); + + player.setBelowNameScoreboard(this); + } + return result; } @Override public boolean removeViewer(Player player) { - return this.viewers.remove(player); + boolean result = this.viewers.remove(player); + PlayerConnection connection = player.getPlayerConnection(); + + if (result) { + connection.sendPacket(this.destructionObjectivePacket); + player.setBelowNameScoreboard(null); + } + + return result; } @Override public Set getViewers() { return Collections.unmodifiableSet(viewers); } - - public void displayScoreboard(Player player) { - PlayerConnection playerConnection = player.getPlayerConnection(); - playerConnection.sendPacket(displayScoreboardPacket); - } } diff --git a/src/main/java/net/minestom/server/scoreboard/Sidebar.java b/src/main/java/net/minestom/server/scoreboard/Sidebar.java index 45a18ffc4..5c4d25afd 100644 --- a/src/main/java/net/minestom/server/scoreboard/Sidebar.java +++ b/src/main/java/net/minestom/server/scoreboard/Sidebar.java @@ -18,30 +18,40 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; +/** + * Represents a sidebar which can contain up to 16 {@link ScoreboardLine}'s + */ public class Sidebar implements Viewable { - private static final AtomicInteger counter = new AtomicInteger(); + private static final AtomicInteger COUNTER = new AtomicInteger(); // WARNING: you shouldn't create scoreboards/teams with the same prefixes as those private static final String SCOREBOARD_PREFIX = "sb-"; private static final String TEAM_PREFIX = "sbt-"; - // Limited by notchian client, do not change + /** + * Limited by the notch client, do not change + */ private static final int MAX_LINES_COUNT = 15; - private Set viewers = new CopyOnWriteArraySet<>(); + private final Set viewers = new CopyOnWriteArraySet<>(); - private ConcurrentLinkedQueue lines = new ConcurrentLinkedQueue<>(); - private IntLinkedOpenHashSet availableColors = new IntLinkedOpenHashSet(); + private final ConcurrentLinkedQueue lines = new ConcurrentLinkedQueue<>(); + private final IntLinkedOpenHashSet availableColors = new IntLinkedOpenHashSet(); - private String objectiveName; + private final String objectiveName; private String title; + /** + * Creates a new sidebar + * + * @param title The title of the sidebar + */ public Sidebar(String title) { this.title = title; - this.objectiveName = SCOREBOARD_PREFIX + counter.incrementAndGet(); + this.objectiveName = SCOREBOARD_PREFIX + COUNTER.incrementAndGet(); // Fill available colors for entities name showed in scoreboard for (int i = 0; i < 16; i++) { @@ -49,6 +59,11 @@ public class Sidebar implements Viewable { } } + /** + * Changes the {@link Sidebar} title + * + * @param title The new sidebar title + */ public void setTitle(String title) { this.title = title; @@ -56,11 +71,16 @@ public class Sidebar implements Viewable { scoreboardObjectivePacket.objectiveName = objectiveName; scoreboardObjectivePacket.mode = 2; // Update display text scoreboardObjectivePacket.objectiveValue = ColoredText.of(title); - scoreboardObjectivePacket.type = 0; + scoreboardObjectivePacket.type = ScoreboardObjectivePacket.Type.INTEGER; sendPacketToViewers(scoreboardObjectivePacket); } + /** + * Creates a new {@link ScoreboardLine} + * + * @param scoreboardLine The new scoreboard line + */ public void createLine(ScoreboardLine scoreboardLine) { synchronized (lines) { Check.stateCondition(lines.size() >= MAX_LINES_COUNT, "You cannot have more than " + MAX_LINES_COUNT + " lines"); @@ -84,6 +104,12 @@ public class Sidebar implements Viewable { } } + /** + * Updates a line content through the given identifier + * + * @param id The identifier of the line + * @param content The new content for the line + */ public void updateLineContent(String id, ColoredText content) { final ScoreboardLine scoreboardLine = getLine(id); if (scoreboardLine != null) { @@ -92,6 +118,12 @@ public class Sidebar implements Viewable { } } + /** + * Updates the score of a line through the given identifier + * + * @param id The identifier of the team + * @param score The new score for the line + */ public void updateLineScore(String id, int score) { final ScoreboardLine scoreboardLine = getLine(id); if (scoreboardLine != null) { @@ -100,6 +132,12 @@ public class Sidebar implements Viewable { } } + /** + * Gets a {@link ScoreboardLine} through the given identifier + * + * @param id The identifier of the line + * @return a {@link ScoreboardLine} or {@code null} + */ public ScoreboardLine getLine(String id) { for (ScoreboardLine line : lines) { if (line.id.equals(id)) @@ -108,6 +146,11 @@ public class Sidebar implements Viewable { return null; } + /** + * Removes a {@link ScoreboardLine} through the given identifier + * + * @param id The identifier of the line + */ public void removeLine(String id) { synchronized (lines) { Iterator iterator = lines.iterator(); @@ -134,7 +177,7 @@ public class Sidebar implements Viewable { scoreboardObjectivePacket.objectiveName = objectiveName; scoreboardObjectivePacket.mode = 0; // Create scoreboard scoreboardObjectivePacket.objectiveValue = ColoredText.of(title); - scoreboardObjectivePacket.type = 0; // Type integer + scoreboardObjectivePacket.type = ScoreboardObjectivePacket.Type.INTEGER; // Type integer DisplayScoreboardPacket displayScoreboardPacket = new DisplayScoreboardPacket(); displayScoreboardPacket.position = 1; // Sidebar @@ -171,15 +214,33 @@ public class Sidebar implements Viewable { return viewers; } + /** + * This class is used to create a line for the sidebar. + */ public static class ScoreboardLine { - private String id; // ID used to modify the line later + /** + * The identifier is used to modify the line later + */ + private String id; + /** + * The content for the line + */ private ColoredText content; + /** + * The score of the line + */ private int line; private String teamName; - private int colorName; // Name of the score (entityName) which is essentially an ID + /** + * The name of the score ({@code entityName}) which is essentially an identifier + */ + private int colorName; private String entityName; + /** + * The sidebar team of the line + */ private SidebarTeam sidebarTeam; public ScoreboardLine(String id, ColoredText content, int line) { @@ -187,17 +248,32 @@ public class Sidebar implements Viewable { this.content = content; this.line = line; - this.teamName = TEAM_PREFIX + counter.incrementAndGet(); + this.teamName = TEAM_PREFIX + COUNTER.incrementAndGet(); } + /** + * Gets the identifier of the line + * + * @return the line identifier + */ public String getId() { return id; } + /** + * Gets the content of the line + * + * @return The line content + */ public ColoredText getContent() { return sidebarTeam == null ? content : sidebarTeam.getPrefix(); } + /** + * Gets the position of the line + * + * @return the line position + */ public int getLine() { return line; } @@ -208,6 +284,9 @@ public class Sidebar implements Viewable { } } + /** + * Creates a new {@link SidebarTeam} + */ private void createTeam() { this.entityName = ChatParser.COLOR_CHAR + Integer.toHexString(colorName); @@ -220,6 +299,12 @@ public class Sidebar implements Viewable { } } + /** + * Gets a score creation packet + * + * @param objectiveName The objective name to be updated + * @return a {@link UpdateScorePacket} + */ private UpdateScorePacket getScoreCreationPacket(String objectiveName) { UpdateScorePacket updateScorePacket = new UpdateScorePacket(); updateScorePacket.entityName = entityName; @@ -229,6 +314,12 @@ public class Sidebar implements Viewable { return updateScorePacket; } + /** + * Gets a score destruction packet + * + * @param objectiveName The objective name to be destroyed + * @return a {@link UpdateScorePacket} + */ private UpdateScorePacket getScoreDestructionPacket(String objectiveName) { UpdateScorePacket updateScorePacket = new UpdateScorePacket(); updateScorePacket.entityName = entityName; @@ -237,18 +328,33 @@ public class Sidebar implements Viewable { return updateScorePacket; } + /** + * Gets a line score update packet + * + * @param objectiveName The objective name to be updated + * @param score The new score + * @return a {@link UpdateScorePacket} + */ private UpdateScorePacket getLineScoreUpdatePacket(String objectiveName, int score) { UpdateScorePacket updateScorePacket = getScoreCreationPacket(objectiveName); updateScorePacket.value = score; return updateScorePacket; } + /** + * Refresh the prefix of the {@link SidebarTeam} + * + * @param content The new content + */ private void refreshContent(ColoredText content) { this.sidebarTeam.refreshPrefix(content); } } + /** + * This class is used to create a team for the sidebar + */ private static class SidebarTeam { private String teamName; @@ -262,6 +368,14 @@ public class Sidebar implements Viewable { private int teamColor = 2; + /** + * The constructor to creates a team + * + * @param teamName The registry name of the team + * @param prefix The team prefix + * @param suffix The team suffix + * @param entityName The team entity name + */ private SidebarTeam(String teamName, ColoredText prefix, ColoredText suffix, String entityName) { this.teamName = teamName; this.prefix = prefix; @@ -269,6 +383,11 @@ public class Sidebar implements Viewable { this.entityName = entityName; } + /** + * Gets a team creation packet + * + * @return a {@link TeamsPacket} which creates a new team + */ private TeamsPacket getCreationPacket() { TeamsPacket teamsPacket = new TeamsPacket(); teamsPacket.teamName = teamName; @@ -284,6 +403,11 @@ public class Sidebar implements Viewable { return teamsPacket; } + /** + * Gets a team destruction packet + * + * @return a {@link TeamsPacket} which destroyed a team + */ private TeamsPacket getDestructionPacket() { TeamsPacket teamsPacket = new TeamsPacket(); teamsPacket.teamName = teamName; @@ -291,6 +415,12 @@ public class Sidebar implements Viewable { return teamsPacket; } + /** + * Updates the prefix of the {@link SidebarTeam} + * + * @param prefix The new prefix + * @return a {@link TeamsPacket} with the updated prefix + */ private TeamsPacket updatePrefix(ColoredText prefix) { TeamsPacket teamsPacket = new TeamsPacket(); teamsPacket.teamName = teamName; @@ -305,14 +435,29 @@ public class Sidebar implements Viewable { return teamsPacket; } + /** + * Gets the entity name of the team + * + * @return the entity name + */ private String getEntityName() { return entityName; } + /** + * Gets the prefix of the team + * + * @return the prefix + */ private ColoredText getPrefix() { return prefix; } + /** + * Refresh the prefix of the {@link SidebarTeam} + * + * @param prefix The refreshed prefix + */ private void refreshPrefix(ColoredText prefix) { this.prefix = prefix; } diff --git a/src/main/java/net/minestom/server/utils/UniqueIdUtils.java b/src/main/java/net/minestom/server/utils/UniqueIdUtils.java index 4774bc436..a0999d351 100644 --- a/src/main/java/net/minestom/server/utils/UniqueIdUtils.java +++ b/src/main/java/net/minestom/server/utils/UniqueIdUtils.java @@ -6,7 +6,7 @@ import java.util.regex.Pattern; /** * An utilities class for {@link UUID} */ -public class UniqueIdUtils { +public final class UniqueIdUtils { public static final Pattern UNIQUE_ID_PATTERN = Pattern.compile("\\b[0-9a-f]{8}\\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\\b[0-9a-f]{12}\\b"); From bbe9cda40b67c9732d20999edd6d6507dc6a85c4 Mon Sep 17 00:00:00 2001 From: R0bbyYT Date: Sat, 8 Aug 2020 13:36:15 +0200 Subject: [PATCH 4/6] Renamed BelowNameScoreboard to BelowNameTag --- .../java/net/minestom/server/entity/Player.java | 16 ++++++++-------- ...elowNameScoreboard.java => BelowNameTag.java} | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) rename src/main/java/net/minestom/server/scoreboard/{BelowNameScoreboard.java => BelowNameTag.java} (94%) diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index dd15f8f76..83497888b 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -34,7 +34,7 @@ import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.recipe.Recipe; import net.minestom.server.recipe.RecipeManager; import net.minestom.server.resourcepack.ResourcePack; -import net.minestom.server.scoreboard.BelowNameScoreboard; +import net.minestom.server.scoreboard.BelowNameTag; import net.minestom.server.scoreboard.Team; import net.minestom.server.sound.Sound; import net.minestom.server.sound.SoundCategory; @@ -109,7 +109,7 @@ public class Player extends LivingEntity implements CommandSender { private int blockBreakTime; private Set bossBars = new CopyOnWriteArraySet<>(); - private BelowNameScoreboard belowNameScoreboard = null; + private BelowNameTag belowNameTag = null; /** * Last damage source to hit this player, used to display the death message. @@ -1373,16 +1373,16 @@ public class Player extends LivingEntity implements CommandSender { /** * Change the tag below the name * - * @param belowNameScoreboard The new below name tag + * @param belowNameTag The new below name tag */ - public void setBelowNameScoreboard(BelowNameScoreboard belowNameScoreboard) { - if (this.belowNameScoreboard == belowNameScoreboard) return; + public void setBelowNameTag(BelowNameTag belowNameTag) { + if (this.belowNameTag == belowNameTag) return; - if (this.belowNameScoreboard != null) { - this.belowNameScoreboard.removeViewer(this); + if (this.belowNameTag != null) { + this.belowNameTag.removeViewer(this); } - this.belowNameScoreboard = belowNameScoreboard; + this.belowNameTag = belowNameTag; } @Override diff --git a/src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java b/src/main/java/net/minestom/server/scoreboard/BelowNameTag.java similarity index 94% rename from src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java rename to src/main/java/net/minestom/server/scoreboard/BelowNameTag.java index 7d2173afc..672aca71d 100644 --- a/src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java +++ b/src/main/java/net/minestom/server/scoreboard/BelowNameTag.java @@ -15,7 +15,7 @@ import java.util.concurrent.CopyOnWriteArraySet; /** * Represents a scoreboard which rendered a tag below the name */ -public class BelowNameScoreboard implements Viewable { +public class BelowNameTag implements Viewable { private final Set viewers = new CopyOnWriteArraySet<>(); private final String objectiveName; @@ -30,7 +30,7 @@ public class BelowNameScoreboard implements Viewable { * @param name The objective name of the scoreboard * @param value The value of the scoreboard */ - public BelowNameScoreboard(String name, String value) { + public BelowNameTag(String name, String value) { this.objectiveName = name; this.scoreboardObjectivePacket = this.getCreationObjectivePacket(value); @@ -99,7 +99,7 @@ public class BelowNameScoreboard implements Viewable { connection.sendPacket(this.scoreboardObjectivePacket); connection.sendPacket(this.displayScoreboardPacket); - player.setBelowNameScoreboard(this); + player.setBelowNameTag(this); } return result; @@ -112,7 +112,7 @@ public class BelowNameScoreboard implements Viewable { if (result) { connection.sendPacket(this.destructionObjectivePacket); - player.setBelowNameScoreboard(null); + player.setBelowNameTag(null); } return result; From 141eed466caf92bb8387d077773c84f59655029d Mon Sep 17 00:00:00 2001 From: R0bbyYT Date: Sat, 8 Aug 2020 23:22:58 +0200 Subject: [PATCH 5/6] Added Scoreboard interface and TabList Scoreboard --- .../server/scoreboard/BelowNameTag.java | 76 +++-------------- .../server/scoreboard/Scoreboard.java | 82 ++++++++++++++++++ .../minestom/server/scoreboard/Sidebar.java | 27 +++--- .../minestom/server/scoreboard/TabList.java | 85 +++++++++++++++++++ 4 files changed, 192 insertions(+), 78 deletions(-) create mode 100644 src/main/java/net/minestom/server/scoreboard/Scoreboard.java create mode 100644 src/main/java/net/minestom/server/scoreboard/TabList.java diff --git a/src/main/java/net/minestom/server/scoreboard/BelowNameTag.java b/src/main/java/net/minestom/server/scoreboard/BelowNameTag.java index 672aca71d..03210fc64 100644 --- a/src/main/java/net/minestom/server/scoreboard/BelowNameTag.java +++ b/src/main/java/net/minestom/server/scoreboard/BelowNameTag.java @@ -1,11 +1,7 @@ package net.minestom.server.scoreboard; -import net.minestom.server.Viewable; -import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Player; -import net.minestom.server.network.packet.server.play.DisplayScoreboardPacket; import net.minestom.server.network.packet.server.play.ScoreboardObjectivePacket; -import net.minestom.server.network.packet.server.play.UpdateScorePacket; import net.minestom.server.network.player.PlayerConnection; import java.util.Collections; @@ -15,14 +11,17 @@ import java.util.concurrent.CopyOnWriteArraySet; /** * Represents a scoreboard which rendered a tag below the name */ -public class BelowNameTag implements Viewable { +public class BelowNameTag implements Scoreboard { + + /** + * WARNING: You shouldn't create scoreboards with the same prefix as those + */ + public static final String BELOW_NAME_TAG_PREFIX = "bnt-"; private final Set viewers = new CopyOnWriteArraySet<>(); private final String objectiveName; private final ScoreboardObjectivePacket scoreboardObjectivePacket; - private final ScoreboardObjectivePacket destructionObjectivePacket; - private final DisplayScoreboardPacket displayScoreboardPacket; /** * Creates a new below name scoreboard @@ -31,63 +30,14 @@ public class BelowNameTag implements Viewable { * @param value The value of the scoreboard */ public BelowNameTag(String name, String value) { - this.objectiveName = name; + this.objectiveName = BELOW_NAME_TAG_PREFIX + name; - this.scoreboardObjectivePacket = this.getCreationObjectivePacket(value); - - this.displayScoreboardPacket = new DisplayScoreboardPacket(); - this.displayScoreboardPacket.position = 2; // Below name - this.displayScoreboardPacket.scoreName = this.objectiveName; - - this.destructionObjectivePacket = this.getDestructionObjectivePacket(); + this.scoreboardObjectivePacket = this.getCreationObjectivePacket(value, ScoreboardObjectivePacket.Type.INTEGER); } - /** - * Creates a creation objective packet for the tag below name - * - * @param value The value of the tag - * @return the creation objective packet - */ - private ScoreboardObjectivePacket getCreationObjectivePacket(String value) { - ScoreboardObjectivePacket packet = new ScoreboardObjectivePacket(); - packet.objectiveName = this.objectiveName; - packet.mode = 0; // Create/Update - packet.objectiveValue = ColoredText.of(value); - packet.type = ScoreboardObjectivePacket.Type.INTEGER; - - return packet; - } - - /** - * Creates the destruction objective packet for the tag below name - * - * @return the destruction objective packet - */ - private ScoreboardObjectivePacket getDestructionObjectivePacket() { - ScoreboardObjectivePacket packet = new ScoreboardObjectivePacket(); - packet.objectiveName = this.objectiveName; - packet.mode = 1; - packet.objectiveValue = ColoredText.of(""); - packet.type = ScoreboardObjectivePacket.Type.INTEGER; - - return packet; - } - - /** - * Updates the score of a {@link Player} - * - * @param player The player - * @param score The new score - */ - public void updateScore(Player player, int score) { - UpdateScorePacket packet = new UpdateScorePacket(); - packet.entityName = player.getUsername(); - packet.action = 0; //Create/Update - packet.objectiveName = this.objectiveName; - packet.value = score; - - // Sends to all viewers an update packet - sendPacketToViewers(packet); + @Override + public String getObjectiveName() { + return this.objectiveName; } @Override @@ -97,7 +47,7 @@ public class BelowNameTag implements Viewable { if (result) { connection.sendPacket(this.scoreboardObjectivePacket); - connection.sendPacket(this.displayScoreboardPacket); + connection.sendPacket(this.getDisplayScoreboardPacket((byte) 2)); player.setBelowNameTag(this); } @@ -111,7 +61,7 @@ public class BelowNameTag implements Viewable { PlayerConnection connection = player.getPlayerConnection(); if (result) { - connection.sendPacket(this.destructionObjectivePacket); + connection.sendPacket(this.getDestructionObjectivePacket()); player.setBelowNameTag(null); } diff --git a/src/main/java/net/minestom/server/scoreboard/Scoreboard.java b/src/main/java/net/minestom/server/scoreboard/Scoreboard.java new file mode 100644 index 000000000..2361ae226 --- /dev/null +++ b/src/main/java/net/minestom/server/scoreboard/Scoreboard.java @@ -0,0 +1,82 @@ +package net.minestom.server.scoreboard; + +import net.minestom.server.Viewable; +import net.minestom.server.chat.ColoredText; +import net.minestom.server.entity.Player; +import net.minestom.server.network.packet.server.play.DisplayScoreboardPacket; +import net.minestom.server.network.packet.server.play.ScoreboardObjectivePacket; +import net.minestom.server.network.packet.server.play.UpdateScorePacket; + +/** + * This interface represents all scoreboard of Minecraft + */ +public interface Scoreboard extends Viewable { + + /** + * Creates a creation objective packet + * + * @param value The value for the objective + * @param type The type for the objective + * @return the creation objective packet + */ + default ScoreboardObjectivePacket getCreationObjectivePacket(String value, ScoreboardObjectivePacket.Type type) { + final ScoreboardObjectivePacket packet = new ScoreboardObjectivePacket(); + packet.objectiveName = this.getObjectiveName(); + packet.mode = 0; // Create Scoreboard + packet.objectiveValue = ColoredText.of(value); + packet.type = type; + + return packet; + } + + /** + * Creates the destruction objective packet + * + * @return the destruction objective packet + */ + default ScoreboardObjectivePacket getDestructionObjectivePacket() { + final ScoreboardObjectivePacket packet = new ScoreboardObjectivePacket(); + packet.objectiveName = this.getObjectiveName(); + packet.mode = 1; // Destroy Scoreboard + + return packet; + } + + /** + * Creates the {@link DisplayScoreboardPacket} + * + * @param position The position of the scoreboard + * @return the created display scoreboard packet + */ + default DisplayScoreboardPacket getDisplayScoreboardPacket(byte position) { + final DisplayScoreboardPacket packet = new DisplayScoreboardPacket(); + packet.position = position; + packet.scoreName = this.getObjectiveName(); + + return packet; + } + + /** + * Updates the score of a {@link Player} + * + * @param player The player + * @param score The new score + */ + default void updateScore(Player player, int score) { + final UpdateScorePacket packet = new UpdateScorePacket(); + packet.entityName = player.getUsername(); + packet.action = 0; // Create/Update score + packet.objectiveName = this.getObjectiveName(); + packet.value = score; + + sendPacketsToViewers(packet); + } + + /** + * Gets the objective name of the scoreboard + * + * @return the objective name + */ + String getObjectiveName(); + +} diff --git a/src/main/java/net/minestom/server/scoreboard/Sidebar.java b/src/main/java/net/minestom/server/scoreboard/Sidebar.java index 5c4d25afd..27fdc53f6 100644 --- a/src/main/java/net/minestom/server/scoreboard/Sidebar.java +++ b/src/main/java/net/minestom/server/scoreboard/Sidebar.java @@ -1,7 +1,6 @@ package net.minestom.server.scoreboard; import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet; -import net.minestom.server.Viewable; import net.minestom.server.chat.ChatParser; import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Player; @@ -21,11 +20,13 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Represents a sidebar which can contain up to 16 {@link ScoreboardLine}'s */ -public class Sidebar implements Viewable { +public class Sidebar implements Scoreboard { private static final AtomicInteger COUNTER = new AtomicInteger(); - // WARNING: you shouldn't create scoreboards/teams with the same prefixes as those + /** + * WARNING: You shouldn't create scoreboards/teams with the same prefixes as those + */ private static final String SCOREBOARD_PREFIX = "sb-"; private static final String TEAM_PREFIX = "sbt-"; @@ -173,15 +174,8 @@ public class Sidebar implements Viewable { final boolean result = this.viewers.add(player); PlayerConnection playerConnection = player.getPlayerConnection(); - ScoreboardObjectivePacket scoreboardObjectivePacket = new ScoreboardObjectivePacket(); - scoreboardObjectivePacket.objectiveName = objectiveName; - scoreboardObjectivePacket.mode = 0; // Create scoreboard - scoreboardObjectivePacket.objectiveValue = ColoredText.of(title); - scoreboardObjectivePacket.type = ScoreboardObjectivePacket.Type.INTEGER; // Type integer - - DisplayScoreboardPacket displayScoreboardPacket = new DisplayScoreboardPacket(); - displayScoreboardPacket.position = 1; // Sidebar - displayScoreboardPacket.scoreName = objectiveName; + ScoreboardObjectivePacket scoreboardObjectivePacket = this.getCreationObjectivePacket(this.title, ScoreboardObjectivePacket.Type.INTEGER); + DisplayScoreboardPacket displayScoreboardPacket = this.getDisplayScoreboardPacket((byte) 1); playerConnection.sendPacket(scoreboardObjectivePacket); // Creative objective playerConnection.sendPacket(displayScoreboardPacket); // Show sidebar scoreboard (wait for scores packet) @@ -197,9 +191,7 @@ public class Sidebar implements Viewable { public boolean removeViewer(Player player) { boolean result = this.viewers.remove(player); PlayerConnection playerConnection = player.getPlayerConnection(); - ScoreboardObjectivePacket scoreboardObjectivePacket = new ScoreboardObjectivePacket(); - scoreboardObjectivePacket.objectiveName = objectiveName; - scoreboardObjectivePacket.mode = 1; // Remove + ScoreboardObjectivePacket scoreboardObjectivePacket = this.getDestructionObjectivePacket(); playerConnection.sendPacket(scoreboardObjectivePacket); for (ScoreboardLine line : lines) { @@ -214,6 +206,11 @@ public class Sidebar implements Viewable { return viewers; } + @Override + public String getObjectiveName() { + return this.objectiveName; + } + /** * This class is used to create a line for the sidebar. */ diff --git a/src/main/java/net/minestom/server/scoreboard/TabList.java b/src/main/java/net/minestom/server/scoreboard/TabList.java new file mode 100644 index 000000000..7e9192b13 --- /dev/null +++ b/src/main/java/net/minestom/server/scoreboard/TabList.java @@ -0,0 +1,85 @@ +package net.minestom.server.scoreboard; + +import net.minestom.server.entity.Player; +import net.minestom.server.network.packet.server.play.ScoreboardObjectivePacket; +import net.minestom.server.network.player.PlayerConnection; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Represents the {@link Player} tab list as a {@link Scoreboard} + */ +public class TabList implements Scoreboard { + + /** + * WARNING: You shouldn't create scoreboards with the same prefix as those + */ + private static final String TAB_LIST_PREFIX = "tl-"; + + private final Set viewers; + private final String objectiveName; + + private ScoreboardObjectivePacket.Type type; + + public TabList(String name, ScoreboardObjectivePacket.Type type) { + this.viewers = new CopyOnWriteArraySet<>(); + this.objectiveName = TAB_LIST_PREFIX + name; + + this.type = type; + } + + /** + * Gets the scoreboard objective type + * + * @return the scoreboard objective type + */ + public ScoreboardObjectivePacket.Type getType() { + return type; + } + + /** + * Changes the scoreboard objective type + * + * @param type The new type for the objective + */ + public void setType(ScoreboardObjectivePacket.Type type) { + this.type = type; + } + + @Override + public boolean addViewer(Player player) { + boolean result = this.viewers.add(player); + PlayerConnection connection = player.getPlayerConnection(); + + if (result) { + connection.sendPacket(this.getCreationObjectivePacket("", this.type)); + connection.sendPacket(this.getDisplayScoreboardPacket((byte) 0)); + } + + return result; + } + + @Override + public boolean removeViewer(Player player) { + boolean result = this.viewers.remove(player); + PlayerConnection connection = player.getPlayerConnection(); + + if (result) { + connection.sendPacket(this.getDestructionObjectivePacket()); + } + + return result; + } + + @Override + public Set getViewers() { + return Collections.unmodifiableSet(this.viewers); + } + + @Override + public String getObjectiveName() { + return this.objectiveName; + } +} From ba07fa78f0a2b6f0a4787499d206a68c02657940 Mon Sep 17 00:00:00 2001 From: R0bbyYT Date: Sun, 9 Aug 2020 17:10:58 +0200 Subject: [PATCH 6/6] Fixed --- .../net/minestom/server/entity/Player.java | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 7f8cb8edd..85b759036 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -33,7 +33,7 @@ import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.recipe.Recipe; import net.minestom.server.recipe.RecipeManager; import net.minestom.server.resourcepack.ResourcePack; -import net.minestom.server.scoreboard.BelowNameScoreboard; +import net.minestom.server.scoreboard.BelowNameTag; import net.minestom.server.scoreboard.Team; import net.minestom.server.sound.Sound; import net.minestom.server.sound.SoundCategory; @@ -107,8 +107,7 @@ public class Player extends LivingEntity implements CommandSender { private byte targetLastStage; private int blockBreakTime; - private Team team; - private BelowNameScoreboard belowNameScoreboard; + private BelowNameTag belowNameTag; /** * Last damage source to hit this player, used to display the death message. @@ -524,8 +523,8 @@ public class Player extends LivingEntity implements CommandSender { viewerConnection.sendPacket(getRemovePlayerToList()); // Team - if (team != null && team.getPlayers().size() == 1) // If team only contains "this" player - viewerConnection.sendPacket(team.createTeamDestructionPacket()); + if (this.getTeam() != null && this.getTeam().getMembers().size() == 1) // If team only contains "this" player + viewerConnection.sendPacket(this.getTeam().createTeamDestructionPacket()); return result; } @@ -1370,34 +1369,24 @@ public class Player extends LivingEntity implements CommandSender { } public void setTeam(Team team) { - if (this.team == team) - return; - - if (this.team != null) { - this.team.removePlayer(this); - } - - this.team = team; - if (team != null) { - team.addPlayer(this); - sendPacketToViewers(team.getTeamsCreationPacket()); // FIXME: only if viewer hasn't already register this team - } + super.setTeam(team); + if(team != null) + getPlayerConnection().sendPacket(team.getTeamsCreationPacket()); } - public void setBelowNameScoreboard(BelowNameScoreboard belowNameScoreboard) { - if (this.belowNameScoreboard == belowNameScoreboard) - return; + /** + * Change the tag below the name + * + * @param belowNameTag The new below name tag + */ + public void setBelowNameTag(BelowNameTag belowNameTag) { + if (this.belowNameTag == belowNameTag) return; - if (this.belowNameScoreboard != null) { - this.belowNameScoreboard.removeViewer(this); + if (this.belowNameTag != null) { + this.belowNameTag.removeViewer(this); } - this.belowNameScoreboard = belowNameScoreboard; - if (belowNameScoreboard != null) { - belowNameScoreboard.addViewer(this); - belowNameScoreboard.displayScoreboard(this); - getViewers().forEach(player -> belowNameScoreboard.addViewer(player)); - } + this.belowNameTag = belowNameTag; } /** @@ -1969,8 +1958,8 @@ public class Player extends LivingEntity implements CommandSender { } // Team - if (team != null) - connection.sendPacket(team.getTeamsCreationPacket()); + if (this.getTeam() != null) + connection.sendPacket(this.getTeam().getTeamsCreationPacket()); EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket(); entityHeadLookPacket.entityId = getEntityId();