Merge pull request #36 from R0bbyYT/feature/scoreboard

Feature/scoreboard
This commit is contained in:
TheMode 2020-08-09 17:13:20 +02:00 committed by GitHub
commit cbf06e6963
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1557 additions and 234 deletions

View File

@ -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;
@ -55,6 +56,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();
@ -522,4 +525,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;
}
}

View File

@ -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();

View File

@ -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 <br>
* 1 = to remove the scoreboard<br>
* 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
}
}

View File

@ -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;
}

View File

@ -1,78 +0,0 @@
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;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
// TODO fix score and objective refresh
public class BelowNameScoreboard implements Viewable {
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 = "bn-";
private static final String TEAM_PREFIX = "bnt-";
private Set<Player> viewers = new CopyOnWriteArraySet<>();
private String objectiveName;
private ScoreboardObjectivePacket scoreboardObjectivePacket;
private DisplayScoreboardPacket displayScoreboardPacket;
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;
}
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;
sendPacketToViewers(updateScorePacket);
}
@Override
public boolean addViewer(Player player) {
boolean result = this.viewers.add(player);
PlayerConnection playerConnection = player.getPlayerConnection();
playerConnection.sendPacket(scoreboardObjectivePacket);
return result;
}
@Override
public boolean removeViewer(Player player) {
return this.viewers.remove(player);
}
@Override
public Set<Player> getViewers() {
return Collections.unmodifiableSet(viewers);
}
public void displayScoreboard(Player player) {
PlayerConnection playerConnection = player.getPlayerConnection();
playerConnection.sendPacket(displayScoreboardPacket);
}
}

View File

@ -0,0 +1,75 @@
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 a scoreboard which rendered a tag below the name
*/
public class BelowNameTag implements Scoreboard {
/**
* <b>WARNING:</b> You shouldn't create scoreboards with the same prefix as those
*/
public static final String BELOW_NAME_TAG_PREFIX = "bnt-";
private final Set<Player> viewers = new CopyOnWriteArraySet<>();
private final String objectiveName;
private final ScoreboardObjectivePacket scoreboardObjectivePacket;
/**
* Creates a new below name scoreboard
*
* @param name The objective name of the scoreboard
* @param value The value of the scoreboard
*/
public BelowNameTag(String name, String value) {
this.objectiveName = BELOW_NAME_TAG_PREFIX + name;
this.scoreboardObjectivePacket = this.getCreationObjectivePacket(value, ScoreboardObjectivePacket.Type.INTEGER);
}
@Override
public String getObjectiveName() {
return this.objectiveName;
}
@Override
public boolean addViewer(Player player) {
boolean result = this.viewers.add(player);
PlayerConnection connection = player.getPlayerConnection();
if (result) {
connection.sendPacket(this.scoreboardObjectivePacket);
connection.sendPacket(this.getDisplayScoreboardPacket((byte) 2));
player.setBelowNameTag(this);
}
return result;
}
@Override
public boolean removeViewer(Player player) {
boolean result = this.viewers.remove(player);
PlayerConnection connection = player.getPlayerConnection();
if (result) {
connection.sendPacket(this.getDestructionObjectivePacket());
player.setBelowNameTag(null);
}
return result;
}
@Override
public Set<Player> getViewers() {
return Collections.unmodifiableSet(viewers);
}
}

View File

@ -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();
}

View File

@ -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;
@ -18,30 +17,42 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
public class Sidebar implements Viewable {
/**
* Represents a sidebar which can contain up to 16 {@link ScoreboardLine}'s
*/
public class Sidebar implements Scoreboard {
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
/**
* <b>WARNING:</b> 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<Player> viewers = new CopyOnWriteArraySet<>();
private final Set<Player> viewers = new CopyOnWriteArraySet<>();
private ConcurrentLinkedQueue<ScoreboardLine> lines = new ConcurrentLinkedQueue<>();
private IntLinkedOpenHashSet availableColors = new IntLinkedOpenHashSet();
private final ConcurrentLinkedQueue<ScoreboardLine> 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 +60,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 +72,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 +105,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 +119,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 +133,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 +147,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<ScoreboardLine> iterator = lines.iterator();
@ -130,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 = 0; // 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)
@ -154,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) {
@ -171,15 +206,38 @@ 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.
*/
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 +245,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 +281,9 @@ public class Sidebar implements Viewable {
}
}
/**
* Creates a new {@link SidebarTeam}
*/
private void createTeam() {
this.entityName = ChatParser.COLOR_CHAR + Integer.toHexString(colorName);
@ -220,6 +296,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 +311,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 +325,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 +365,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 +380,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 +400,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 +412,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 +432,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;
}

View File

@ -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 {
/**
* <b>WARNING:</b> You shouldn't create scoreboards with the same prefix as those
*/
private static final String TAB_LIST_PREFIX = "tl-";
private final Set<Player> 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<Player> getViewers() {
return Collections.unmodifiableSet(this.viewers);
}
@Override
public String getObjectiveName() {
return this.objectiveName;
}
}

View File

@ -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<String> 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<Player> players = new CopyOnWriteArraySet<>();
/**
* Used to color the name of players on the team <br>
* 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}
* <br>
* 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
* <br><br>
* <b>Warning:</b> This is only changed on the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning:</b> This is only changed on the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning:</b> This is only changed on the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning:</b> This is only changed on the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning:</b> This is only changed on the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning:</b> This is only changed on the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning:</b> This is only changed on the <b>server side</b>
*
* @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<Player> 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<String> 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);
}
}
}

View File

@ -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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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
* <br><br>
* <b>Warning: </b> If you do not call {@link #updateTeamPacket()}, this is only changed of the <b>server side</b>
*
* @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;
}
}

View File

@ -1,21 +1,216 @@
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;
// TODO
/**
* An object which manages all the {@link Team}'s
*/
public final class TeamManager {
// Represents all registered teams
private Set<Team> teams = new CopyOnWriteArraySet<>();
/**
* Represents all registered teams
*/
private final Set<Team> 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());
}
/**
* 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
*
* @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 List} with all registered {@link Player} in the team
* <br>
* <b>Note:</b> 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<String> getPlayers(Team team) {
List<String> 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
* <br>
* <b>Note:</b> 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<String> getEntities(Team team) {
List<String> 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
*
* @return a {@link Set} with all registered {@link Team}'s
*/
public Set<Team> 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);
}
/**
* 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);
}
}
}

View File

@ -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 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");
/**
* 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());
}
}