package net.glowstone.scoreboard; import com.flowpowered.networking.Message; import com.google.common.collect.ImmutableSet; import net.glowstone.constants.GlowDisplaySlot; import net.glowstone.entity.GlowPlayer; import net.glowstone.net.message.play.scoreboard.ScoreboardDisplayMessage; import net.glowstone.net.message.play.scoreboard.ScoreboardObjectiveMessage; import net.glowstone.net.message.play.scoreboard.ScoreboardScoreMessage; import net.glowstone.net.message.play.scoreboard.ScoreboardTeamMessage; import org.apache.commons.lang3.Validate; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.scoreboard.*; import java.util.*; /** * Scoreboard implementation. */ public final class GlowScoreboard implements Scoreboard { // Objectives private final EnumMap<DisplaySlot, GlowObjective> displaySlots = new EnumMap<>(DisplaySlot.class); private final HashMap<String, GlowObjective> objectives = new HashMap<>(); private final HashMap<String, Set<GlowObjective>> criteriaMap = new HashMap<>(); // Score map - kept up to date by each objective private final HashMap<String, Set<GlowScore>> scoreMap = new HashMap<>(); // Teams private final HashMap<String, GlowTeam> teams = new HashMap<>(); private final HashMap<OfflinePlayer, GlowTeam> playerTeamMap = new HashMap<>(); // Players who are watching this scoreboard private final HashSet<GlowPlayer> players = new HashSet<>(); //////////////////////////////////////////////////////////////////////////// // Internals /** * Send a player this scoreboard's contents and subscribe them to future * changes. * @param player The player to subscribe. */ public void subscribe(GlowPlayer player) { // send all the setup stuff // objectives for (GlowObjective objective : objectives.values()) { player.getSession().send(ScoreboardObjectiveMessage.create(objective.getName(), objective.getDisplayName())); } // display slots for (DisplaySlot slot : DisplaySlot.values()) { GlowObjective objective = displaySlots.get(slot); String name = objective != null ? objective.getName() : ""; player.getSession().send(new ScoreboardDisplayMessage(GlowDisplaySlot.getId(slot), name)); } // scores for (Map.Entry<String, Set<GlowScore>> entry : scoreMap.entrySet()) { for (GlowScore score : entry.getValue()) { player.getSession().send(new ScoreboardScoreMessage(entry.getKey(), score.getObjective().getName(), score.getScore())); } } // teams for (GlowTeam team : teams.values()) { player.getSession().send(team.getCreateMessage()); } // add to player set players.add(player); } /** * Clear the player's scoreboard contents and unsubscribe them from * future changes. * @param player The player to unsubscribe. */ public void unsubscribe(GlowPlayer player) { // remove from player set players.remove(player); // send all the teardown stuff // teams for (GlowTeam team : teams.values()) { player.getSession().send(ScoreboardTeamMessage.remove(team.getName())); } // display slots for (DisplaySlot slot : DisplaySlot.values()) { player.getSession().send(new ScoreboardDisplayMessage(GlowDisplaySlot.getId(slot), "")); } // objectives for (GlowObjective objective : objectives.values()) { player.getSession().send(ScoreboardObjectiveMessage.remove(objective.getName())); } } /** * Broadcast a scoreboard update to all subscribed players. * @param message The message to send. */ void broadcast(Message message) { for (GlowPlayer player : players) { player.getSession().send(message); } } /** * Set the objective displayed in the given slot. * @param slot The display slot. * @param objective The objective to display there, possibly null. */ void setDisplaySlot(DisplaySlot slot, GlowObjective objective) { GlowObjective previous = displaySlots.put(slot, objective); // previous objective is no longer in this display slot if (previous != null) { previous.displaySlot = null; } // new objective is now in this display slot if (objective != null) { // update objective's display slot broadcast(new ScoreboardDisplayMessage(GlowDisplaySlot.getId(slot), objective.getName())); objective.displaySlot = slot; } else { // no objective broadcast(new ScoreboardDisplayMessage(GlowDisplaySlot.getId(slot), "")); } } /** * Unregister an objective from the scoreboard. * @param objective The objective to unregister. */ void removeObjective(GlowObjective objective) { if (objective.displaySlot != null) { setDisplaySlot(objective.displaySlot, null); } getForCriteria(objective.getCriteria()).remove(objective); objectives.remove(objective.getName()); broadcast(ScoreboardObjectiveMessage.remove(objective.getName())); } /** * Unregister a team from the scoreboard. * @param team The team to unregister. */ void removeTeam(GlowTeam team) { for (OfflinePlayer player : team.getPlayers()) { playerTeamMap.remove(player); } teams.remove(team.getName()); broadcast(ScoreboardTeamMessage.remove(team.getName())); } /** * Get the internal set of objectives corresponding to a given criteria. * @param criteria The criteria to look up. * @return The set of objectives. */ Set<GlowObjective> getForCriteria(String criteria) { Set<GlowObjective> result = criteriaMap.get(criteria); if (result == null) { result = new HashSet<>(); criteriaMap.put(criteria, result); } return result; } /** * Get the internal set of scores corresponding to a given entry. * @param entry The entry to look up. * @return The set of scores. */ Set<GlowScore> getScoresForName(String entry) { Set<GlowScore> result = scoreMap.get(entry); if (result == null) { result = new HashSet<>(); scoreMap.put(entry, result); } return result; } /** * Update what team a player is associated with. * @param player The player. * @param team The team, or null for no team. */ void setPlayerTeam(OfflinePlayer player, GlowTeam team) { GlowTeam previous = playerTeamMap.put(player, team); if (previous != null && previous.hasPlayer(player)) { previous.rawRemovePlayer(player); broadcast(ScoreboardTeamMessage.removePlayers(previous.getName(), Arrays.asList(player.getName()))); } if (team != null) { broadcast(ScoreboardTeamMessage.addPlayers(team.getName(), Arrays.asList(player.getName()))); } } //////////////////////////////////////////////////////////////////////////// // Objectives public Objective registerNewObjective(String name, String criteria) throws IllegalArgumentException { Validate.notNull(name, "Name cannot be null"); Validate.notNull(criteria, "Criteria cannot be null"); Validate.isTrue(!objectives.containsKey(name), "Objective \"" + name + "\" already exists"); GlowObjective objective = new GlowObjective(this, name, criteria); objectives.put(name, objective); getForCriteria(criteria).add(objective); broadcast(ScoreboardObjectiveMessage.create(name, objective.getDisplayName(), RenderType.INTEGER)); return objective; } public Objective getObjective(String name) throws IllegalArgumentException { return objectives.get(name); } public Set<Objective> getObjectivesByCriteria(String criteria) throws IllegalArgumentException { return ImmutableSet.<Objective>copyOf(getForCriteria(criteria)); } public Set<Objective> getObjectives() { return ImmutableSet.<Objective>copyOf(objectives.values()); } public Objective getObjective(DisplaySlot slot) throws IllegalArgumentException { Validate.notNull(slot, "Slot cannot be null"); return displaySlots.get(slot); } public void clearSlot(DisplaySlot slot) throws IllegalArgumentException { Validate.notNull(slot, "Slot cannot be null"); setDisplaySlot(slot, null); } //////////////////////////////////////////////////////////////////////////// // Teams public Team registerNewTeam(String name) throws IllegalArgumentException { Validate.notNull(name, "Name cannot be null"); Validate.isTrue(!teams.containsKey(name), "Team \"" + name + "\" already exists"); GlowTeam team = new GlowTeam(this, name); teams.put(name, team); broadcast(team.getCreateMessage()); return team; } public Team getPlayerTeam(OfflinePlayer player) throws IllegalArgumentException { Validate.notNull(player, "Player cannot be null"); return playerTeamMap.get(player); } @Override public Team getEntryTeam(String teamName) throws IllegalArgumentException { return getTeam(teamName); // TODO: is this the same as getTeam? } public Team getTeam(String teamName) throws IllegalArgumentException { Validate.notNull(teamName, "Team name cannot be null"); return teams.get(teamName); } public Set<Team> getTeams() { return ImmutableSet.<Team>copyOf(teams.values()); } //////////////////////////////////////////////////////////////////////////// // Scores public Set<String> getEntries() { return ImmutableSet.copyOf(scoreMap.keySet()); } public Set<Score> getScores(String entry) throws IllegalArgumentException { Validate.notNull(entry, "Entry cannot be null"); Set<GlowScore> scoreSet = scoreMap.get(entry); if (scoreSet == null) { return ImmutableSet.of(); } else { return ImmutableSet.<Score>copyOf(scoreSet); } } public void resetScores(String entry) throws IllegalArgumentException { Validate.notNull(entry, "Entry cannot be null"); for (GlowObjective objective : objectives.values()) { broadcast(ScoreboardScoreMessage.remove(entry, objective.getName())); objective.deleteScore(entry); } scoreMap.remove(entry); } //////////////////////////////////////////////////////////////////////////// // OfflinePlayer methods @Deprecated public Set<OfflinePlayer> getPlayers() { Set<OfflinePlayer> result = new HashSet<>(); for (String name : getEntries()) { result.add(Bukkit.getOfflinePlayer(name)); } return Collections.unmodifiableSet(result); } @Deprecated public Set<Score> getScores(OfflinePlayer player) throws IllegalArgumentException { Validate.notNull(player, "Player cannot be null"); return getScores(player.getName()); } @Deprecated public void resetScores(OfflinePlayer player) throws IllegalArgumentException { Validate.notNull(player, "Player cannot be null"); resetScores(player.getName()); } }