From 073bbd5979f141b293167dafe52d2f95adea6166 Mon Sep 17 00:00:00 2001 From: mbax Date: Wed, 20 Mar 2013 14:14:42 -0400 Subject: Add Scoreboard API and Command. Adds BUKKIT-3776, BUKKIT-3834 The implementation is designed around having both a main scoreboard and numberous plugin managed scoreboards that can be displayed to specific players. Plugin managed scoreboards are active so long as a reference is kept by a plugin, or it has been registered as a player's active scoreboard. Objects specific to a scoreboard remain active until unregistered (which remove a reference to the owning scoreboard), but quickly fail if accessed post-unregistration. --- src/main/java/org/bukkit/Bukkit.java | 5 + src/main/java/org/bukkit/Server.java | 10 + .../java/org/bukkit/command/SimpleCommandMap.java | 1 + .../bukkit/command/defaults/ScoreboardCommand.java | 627 +++++++++++++++++++++ src/main/java/org/bukkit/entity/Player.java | 16 + src/main/java/org/bukkit/scoreboard/Criterias.java | 20 + .../java/org/bukkit/scoreboard/DisplaySlot.java | 10 + src/main/java/org/bukkit/scoreboard/Objective.java | 98 ++++ src/main/java/org/bukkit/scoreboard/Score.java | 51 ++ .../java/org/bukkit/scoreboard/Scoreboard.java | 123 ++++ .../org/bukkit/scoreboard/ScoreboardManager.java | 29 + src/main/java/org/bukkit/scoreboard/Team.java | 175 ++++++ .../org/bukkit/scoreboard/package-info.java | 6 + 13 files changed, 1171 insertions(+) create mode 100644 src/main/java/org/bukkit/command/defaults/ScoreboardCommand.java create mode 100644 src/main/java/org/bukkit/scoreboard/Criterias.java create mode 100644 src/main/java/org/bukkit/scoreboard/DisplaySlot.java create mode 100644 src/main/java/org/bukkit/scoreboard/Objective.java create mode 100644 src/main/java/org/bukkit/scoreboard/Score.java create mode 100644 src/main/java/org/bukkit/scoreboard/Scoreboard.java create mode 100644 src/main/java/org/bukkit/scoreboard/ScoreboardManager.java create mode 100644 src/main/java/org/bukkit/scoreboard/Team.java create mode 100644 src/main/javadoc/org/bukkit/scoreboard/package-info.java (limited to 'src/main') diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java index 2e793784..005085a9 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java @@ -24,6 +24,7 @@ import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.messaging.Messenger; import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scoreboard.ScoreboardManager; import com.avaje.ebean.config.ServerConfig; import org.bukkit.inventory.ItemFactory; @@ -395,4 +396,8 @@ public final class Bukkit { public static ItemFactory getItemFactory() { return server.getItemFactory(); } + + public static ScoreboardManager getScoreboardManager() { + return server.getScoreboardManager(); + } } diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java index cceb0d5b..ed02a8a1 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -26,6 +26,7 @@ import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.messaging.Messenger; import org.bukkit.plugin.messaging.PluginMessageRecipient; import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scoreboard.ScoreboardManager; import com.avaje.ebean.config.ServerConfig; import org.bukkit.inventory.ItemFactory; @@ -689,4 +690,13 @@ public interface Server extends PluginMessageRecipient { * @see ItemFactory */ ItemFactory getItemFactory(); + + /** + * Gets the instance of the scoreboard manager. + *

+ * This will only exist after the first world has loaded. + * + * @return the scoreboard manager or null if no worlds are loaded. + */ + ScoreboardManager getScoreboardManager(); } diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java index 3a678e3e..df5f6ef1 100644 --- a/src/main/java/org/bukkit/command/SimpleCommandMap.java +++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java @@ -57,6 +57,7 @@ public class SimpleCommandMap implements CommandMap { fallbackCommands.add(new EnchantCommand()); fallbackCommands.add(new TestForCommand()); fallbackCommands.add(new EffectCommand()); + fallbackCommands.add(new ScoreboardCommand()); } public SimpleCommandMap(final Server server) { diff --git a/src/main/java/org/bukkit/command/defaults/ScoreboardCommand.java b/src/main/java/org/bukkit/command/defaults/ScoreboardCommand.java new file mode 100644 index 00000000..6127f4b7 --- /dev/null +++ b/src/main/java/org/bukkit/command/defaults/ScoreboardCommand.java @@ -0,0 +1,627 @@ +package org.bukkit.command.defaults; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.Score; +import org.bukkit.scoreboard.Scoreboard; +import org.bukkit.scoreboard.Team; +import org.bukkit.util.StringUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class ScoreboardCommand extends VanillaCommand { + + private static final List MAIN_CHOICES = ImmutableList.of("objectives", "players", "teams"); + private static final List OBJECTIVES_CHOICES = ImmutableList.of("list", "add", "remove", "setdisplay"); + private static final List OBJECTIVES_CRITERIA = ImmutableList.of("health", "playerKillCount", "totalKillCount", "deathCount", "dummy"); + private static final List PLAYERS_CHOICES = ImmutableList.of("set", "add", "remove", "reset", "list"); + private static final List TEAMS_CHOICES = ImmutableList.of("add", "remove", "join", "leave", "empty", "list", "option"); + private static final List TEAMS_OPTION_CHOICES = ImmutableList.of("color", "friendlyfire", "seeFriendlyInvisibles"); + private static final Map OBJECTIVES_DISPLAYSLOT = ImmutableMap.of("belowName", DisplaySlot.BELOW_NAME, "list", DisplaySlot.PLAYER_LIST, "sidebar", DisplaySlot.SIDEBAR); + private static final Map TEAMS_OPTION_COLOR = ImmutableMap.builder() + .put("aqua", ChatColor.AQUA) + .put("black", ChatColor.BLACK) + .put("blue", ChatColor.BLUE) + .put("bold", ChatColor.BOLD) + .put("dark_aqua", ChatColor.DARK_AQUA) + .put("dark_blue", ChatColor.DARK_BLUE) + .put("dark_gray", ChatColor.DARK_GRAY) + .put("dark_green", ChatColor.DARK_GREEN) + .put("dark_purple", ChatColor.DARK_PURPLE) + .put("dark_red", ChatColor.DARK_RED) + .put("gold", ChatColor.GOLD) + .put("gray", ChatColor.GRAY) + .put("green", ChatColor.GREEN) + .put("italic", ChatColor.ITALIC) + .put("light_purple", ChatColor.LIGHT_PURPLE) + .put("obfuscated", ChatColor.MAGIC) // This is the important line + .put("red", ChatColor.RED) + .put("reset", ChatColor.RESET) + .put("strikethrough", ChatColor.STRIKETHROUGH) + .put("underline", ChatColor.UNDERLINE) + .put("white", ChatColor.WHITE) + .put("yellow", ChatColor.YELLOW) + .build(); + private static final List BOOLEAN = ImmutableList.of("true", "false"); + + public ScoreboardCommand() { + super("scoreboard"); + this.description = "Scoreboard control"; + this.usageMessage = "/scoreboard"; + this.setPermission("bukkit.command.scoreboard"); + } + + @Override + public boolean execute(CommandSender sender, String currentAlias, String[] args) { + if (!testPermission(sender)) + return true; + if (args.length < 1 || args[0].length() == 0) { + sender.sendMessage(ChatColor.RED + "Usage: /scoreboard "); + return false; + } + + final Scoreboard mainScoreboard = Bukkit.getScoreboardManager().getMainScoreboard(); + + if (args[0].equalsIgnoreCase("objectives")) { + if (args.length == 1) { + sender.sendMessage(ChatColor.RED + "Usage: /scoreboard objectives "); + return false; + } + if (args[1].equalsIgnoreCase("list")) { + Set objectives = mainScoreboard.getObjectives(); + if (objectives.isEmpty()) { + sender.sendMessage(ChatColor.RED + "There are no objectives on the scoreboard"); + return false; + } + sender.sendMessage(ChatColor.DARK_GREEN + "Showing " + objectives.size() + " objective(s) on scoreboard"); + for (Objective objective : objectives) { + sender.sendMessage("- " + objective.getName() + ": displays as '" + objective.getDisplayName() + "' and is type '" + objective.getCriteria() + "'"); + } + } else if (args[1].equalsIgnoreCase("add")) { + if (args.length < 4) { + sender.sendMessage(ChatColor.RED + "/scoreboard objectives add [display name ...]"); + return false; + } + String name = args[2]; + String criteria = args[3]; + + if (criteria == null) { + sender.sendMessage(ChatColor.RED + "Invalid objective criteria type. Valid types are: " + stringCollectionToString(OBJECTIVES_CRITERIA)); + } else if (name.length() > 16) { + sender.sendMessage(ChatColor.RED + "The name '" + name + "' is too long for an objective, it can be at most 16 characters long"); + } else if (mainScoreboard.getObjective(name) != null) { + sender.sendMessage(ChatColor.RED + "An objective with the name '" + name + "' already exists"); + } else { + String displayName = null; + if (args.length > 4) { + displayName = StringUtils.join(ArrayUtils.subarray(args, 4, args.length), ' '); + if (displayName.length() > 32) { + sender.sendMessage(ChatColor.RED + "The name '" + displayName + "' is too long for an objective, it can be at most 32 characters long"); + return false; + } + } + Objective objective = mainScoreboard.registerNewObjective(name, criteria); + if (displayName != null && displayName.length() > 0) { + objective.setDisplayName(displayName); + } + sender.sendMessage("Added new objective '" + name + "' successfully"); + } + } else if (args[1].equalsIgnoreCase("remove")) { + if (args.length != 3) { + sender.sendMessage(ChatColor.RED + "/scoreboard objectives remove "); + return false; + } + String name = args[2]; + Objective objective = mainScoreboard.getObjective(name); + if (objective == null) { + sender.sendMessage(ChatColor.RED + "No objective was found by the name '" + name + "'"); + } else { + objective.unregister(); + sender.sendMessage("Removed objective '" + name + "' successfully"); + } + } else if (args[1].equalsIgnoreCase("setdisplay")) { + if (args.length != 3 && args.length != 4) { + sender.sendMessage(ChatColor.RED + "/scoreboard objectives setdisplay [objective]"); + return false; + } + String slotName = args[2]; + DisplaySlot slot = OBJECTIVES_DISPLAYSLOT.get(slotName); + if (slot == null) { + sender.sendMessage(ChatColor.RED + "No such display slot '" + slotName + "'"); + } else { + if (args.length == 4) { + String objectiveName = args[3]; + Objective objective = mainScoreboard.getObjective(objectiveName); + if (objective == null) { + sender.sendMessage(ChatColor.RED + "No objective was found by the name '" + objectiveName + "'"); + return false; + } + + objective.setDisplaySlot(slot); + sender.sendMessage("Set the display objective in slot '" + slotName + "' to '" + objective.getName() + "'"); + } else { + Objective objective = mainScoreboard.getObjective(slot); + if (objective != null) { + objective.setDisplaySlot(null); + } + sender.sendMessage("Cleared objective display slot '" + slotName + "'"); + } + } + } + } else if (args[0].equalsIgnoreCase("players")) { + if (args.length == 1) { + sender.sendMessage(ChatColor.RED + "/scoreboard players "); + return false; + } + if (args[1].equalsIgnoreCase("set") || args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("remove")) { + if (args.length != 5) { + if (args[1].equalsIgnoreCase("set")) { + sender.sendMessage(ChatColor.RED + "/scoreboard players set "); + } else if (args[1].equalsIgnoreCase("add")) { + sender.sendMessage(ChatColor.RED + "/scoreboard players add "); + } else { + sender.sendMessage(ChatColor.RED + "/scoreboard players remove "); + } + return false; + } + String objectiveName = args[3]; + Objective objective = mainScoreboard.getObjective(objectiveName); + if (objective == null) { + sender.sendMessage(ChatColor.RED + "No objective was found by the name '" + objectiveName + "'"); + return false; + } else if (!objective.isModifiable()) { + sender.sendMessage(ChatColor.RED + "The objective '" + objectiveName + "' is read-only and cannot be set"); + return false; + } + + String valueString = args[4]; + int value; + try { + value = Integer.parseInt(valueString); + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "'" + valueString + "' is not a valid number"); + return false; + } + if (value < 1 && !args[1].equalsIgnoreCase("set")) { + sender.sendMessage(ChatColor.RED + "The number you have entered (" + value + ") is too small, it must be at least 1"); + return false; + } + + String playerName = args[2]; + if (playerName.length() > 16) { + sender.sendMessage(ChatColor.RED + "'" + playerName + "' is too long for a player name"); + return false; + } + Score score = objective.getScore(Bukkit.getOfflinePlayer(playerName)); + int newScore; + if (args[1].equalsIgnoreCase("set")) { + newScore = value; + } else if (args[1].equalsIgnoreCase("add")) { + newScore = score.getScore() + value; + } else { + newScore = score.getScore() - value; + } + score.setScore(newScore); + sender.sendMessage("Set score of " + objectiveName + " for player " + playerName + " to " + newScore); + } else if (args[1].equalsIgnoreCase("reset")) { + if (args.length != 3) { + sender.sendMessage(ChatColor.RED + "/scoreboard players reset "); + return false; + } + String playerName = args[2]; + if (playerName.length() > 16) { + sender.sendMessage(ChatColor.RED + "'" + playerName + "' is too long for a player name"); + return false; + } + mainScoreboard.resetScores(Bukkit.getOfflinePlayer(playerName)); + sender.sendMessage("Reset all scores of player " + playerName); + } else if (args[1].equalsIgnoreCase("list")) { + if (args.length > 3) { + sender.sendMessage(ChatColor.RED + "/scoreboard players list "); + return false; + } + if (args.length == 2) { + Set players = mainScoreboard.getPlayers(); + if (players.isEmpty()) { + sender.sendMessage(ChatColor.RED + "There are no tracked players on the scoreboard"); + } else { + sender.sendMessage(ChatColor.DARK_GREEN + "Showing " + players.size() + " tracked players on the scoreboard"); + sender.sendMessage(offlinePlayerSetToString(players)); + } + } else { + String playerName = args[2]; + if (playerName.length() > 16) { + sender.sendMessage(ChatColor.RED + "'" + playerName + "' is too long for a player name"); + return false; + } + Set scores = mainScoreboard.getScores(Bukkit.getOfflinePlayer(playerName)); + if (scores.isEmpty()) { + sender.sendMessage(ChatColor.RED + "Player " + playerName + " has no scores recorded"); + } else { + sender.sendMessage(ChatColor.DARK_GREEN + "Showing " + scores.size() + " tracked objective(s) for " + playerName); + for (Score score : scores) { + sender.sendMessage("- " + score.getObjective().getDisplayName() + ": " + score.getScore() + " (" + score.getObjective().getName() + ")"); + } + } + } + } + } else if (args[0].equalsIgnoreCase("teams")) { + if (args.length == 1) { + sender.sendMessage(ChatColor.RED + "/scoreboard teams "); + return false; + } + if (args[1].equalsIgnoreCase("list")) { + if (args.length == 2) { + Set teams = mainScoreboard.getTeams(); + if (teams.isEmpty()) { + sender.sendMessage(ChatColor.RED + "There are no teams registered on the scoreboard"); + } else { + sender.sendMessage(ChatColor.DARK_GREEN + "Showing " + teams.size() + " teams on the scoreboard"); + for (Team team : teams) { + sender.sendMessage("- " + team.getName() + ": '" + team.getDisplayName() + "' has " + team.getSize() + " players"); + } + } + } else if (args.length == 3) { + String teamName = args[2]; + Team team = mainScoreboard.getTeam(teamName); + if (team == null) { + sender.sendMessage(ChatColor.RED + "No team was found by the name '" + teamName + "'"); + } else { + Set players = team.getPlayers(); + if (players.isEmpty()) { + sender.sendMessage(ChatColor.RED + "Team " + team.getName() + " has no players"); + } else { + sender.sendMessage(ChatColor.DARK_GREEN + "Showing " + players.size() + " player(s) in team " + team.getName()); + sender.sendMessage(offlinePlayerSetToString(players)); + } + } + } else { + sender.sendMessage(ChatColor.RED + "/scoreboard teams list [name]"); + return false; + } + } else if (args[1].equalsIgnoreCase("add")) { + if (args.length < 3) { + sender.sendMessage(ChatColor.RED + "/scoreboard teams add [display name ...]"); + return false; + } + String name = args[2]; + if (name.length() > 16) { + sender.sendMessage(ChatColor.RED + "The name '" + name + "' is too long for a team, it can be at most 16 characters long"); + } else if (mainScoreboard.getTeam(name) != null) { + sender.sendMessage(ChatColor.RED + "A team with the name '" + name + "' already exists"); + } else { + String displayName = null; + if (args.length > 3) { + displayName = StringUtils.join(ArrayUtils.subarray(args, 4, args.length), ' '); + if (displayName.length() > 32) { + sender.sendMessage(ChatColor.RED + "The display name '" + displayName + "' is too long for a team, it can be at most 32 characters long"); + return false; + } + } + Team team = mainScoreboard.registerNewTeam(name); + if (displayName != null && displayName.length() > 0) { + team.setDisplayName(displayName); + } + sender.sendMessage("Added new team '" + team.getName() + "' successfully"); + } + } else if (args[1].equalsIgnoreCase("remove")) { + if (args.length != 3) { + sender.sendMessage(ChatColor.RED + "/scoreboard teams remove "); + return false; + } + String name = args[2]; + Team team = mainScoreboard.getTeam(name); + if (team == null) { + sender.sendMessage(ChatColor.RED + "No team was found by the name '" + name + "'"); + } else { + team.unregister(); + sender.sendMessage("Removed team " + name); + } + } else if (args[1].equalsIgnoreCase("empty")) { + if (args.length != 3) { + sender.sendMessage(ChatColor.RED + "/scoreboard teams clear "); + return false; + } + String name = args[2]; + Team team = mainScoreboard.getTeam(name); + if (team == null) { + sender.sendMessage(ChatColor.RED + "No team was found by the name '" + name + "'"); + } else { + Set players = team.getPlayers(); + if (players.isEmpty()) { + sender.sendMessage(ChatColor.RED + "Team " + team.getName() + " is already empty, cannot remove nonexistant players"); + } else { + for (OfflinePlayer player : players) { + team.removePlayer(player); + } + sender.sendMessage("Removed all " + players.size() + " player(s) from team " + team.getName()); + } + } + } else if (args[1].equalsIgnoreCase("join")) { + if ((sender instanceof Player) ? args.length < 3 : args.length < 4) { + sender.sendMessage(ChatColor.RED + "/scoreboard teams join [player...]"); + return false; + } + String teamName = args[2]; + Team team = mainScoreboard.getTeam(teamName); + if (team == null) { + sender.sendMessage(ChatColor.RED + "No team was found by the name '" + teamName + "'"); + } else { + Set addedPlayers = new HashSet(); + if ((sender instanceof Player) && args.length == 3) { + team.addPlayer((Player) sender); + addedPlayers.add(sender.getName()); + } else { + for (int i = 3; i < args.length; i++) { + String playerName = args[i]; + OfflinePlayer offlinePlayer; + Player player = Bukkit.getPlayerExact(playerName); + if (player != null) { + offlinePlayer = player; + } else { + offlinePlayer = Bukkit.getOfflinePlayer(playerName); + } + team.addPlayer(offlinePlayer); + addedPlayers.add(offlinePlayer.getName()); + } + } + String[] playerArray = addedPlayers.toArray(new String[0]); + StringBuilder builder = new StringBuilder(); + for (int x = 0; x < playerArray.length; x++) { + if (x == playerArray.length - 1) { + builder.append(" and "); + } else if (x > 0) { + builder.append(", "); + } + builder.append(playerArray[x]); + } + sender.sendMessage("Added " + addedPlayers.size() + " player(s) to team " + team.getName() + ": " + builder.toString()); + } + } else if (args[1].equalsIgnoreCase("leave")) { + if ((sender instanceof Player) ? args.length < 2 : args.length < 3) { + sender.sendMessage(ChatColor.RED + "/scoreboard teams leave [player...]"); + return false; + } + Set left = new HashSet(); + Set noTeam = new HashSet(); + if ((sender instanceof Player) && args.length == 3) { + Team team = mainScoreboard.getPlayerTeam((Player) sender); + if (team != null) { + team.removePlayer((Player) sender); + left.add(sender.getName()); + } else { + noTeam.add(sender.getName()); + } + } else { + for (int i = 3; i < args.length; i++) { + String playerName = args[i]; + OfflinePlayer offlinePlayer; + Player player = Bukkit.getPlayerExact(playerName); + if (player != null) { + offlinePlayer = player; + } else { + offlinePlayer = Bukkit.getOfflinePlayer(playerName); + } + Team team = mainScoreboard.getPlayerTeam(offlinePlayer); + if (team != null) { + team.removePlayer(offlinePlayer); + left.add(offlinePlayer.getName()); + } else { + noTeam.add(offlinePlayer.getName()); + } + } + } + if (!left.isEmpty()) { + sender.sendMessage("Removed " + left.size() + " player(s) from their teams: " + stringCollectionToString(left)); + } + if (!noTeam.isEmpty()) { + sender.sendMessage("Could not remove " + noTeam.size() + " player(s) from their teams: " + stringCollectionToString(noTeam)); + } + } else if (args[1].equalsIgnoreCase("option")) { + if (args.length != 4 && args.length != 5) { + sender.sendMessage(ChatColor.RED + "/scoreboard teams option "); + return false; + } + String teamName = args[2]; + Team team = mainScoreboard.getTeam(teamName); + if (team == null) { + sender.sendMessage(ChatColor.RED + "No team was found by the name '" + teamName + "'"); + return false; + } + String option = args[3].toLowerCase(); + if (!option.equals("friendlyfire") && !option.equals("color") && !option.equals("seefriendlyinvisibles")) { + sender.sendMessage(ChatColor.RED + "/scoreboard teams option "); + return false; + } + if (args.length == 4) { + if (option.equals("color")) { + sender.sendMessage(ChatColor.RED + "Valid values for option color are: " + stringCollectionToString(TEAMS_OPTION_COLOR.keySet())); + } else { + sender.sendMessage(ChatColor.RED + "Valid values for option " + option + " are: true and false"); + } + } else { + String value = args[4].toLowerCase(); + if (option.equals("color")) { + ChatColor color = TEAMS_OPTION_COLOR.get(value); + if (color == null) { + sender.sendMessage(ChatColor.RED + "Valid values for option color are: " + stringCollectionToString(TEAMS_OPTION_COLOR.keySet())); + return false; + } + team.setPrefix(color.toString()); + team.setSuffix(ChatColor.RESET.toString()); + } else { + if (!value.equals("true") && !value.equals("false")) { + sender.sendMessage(ChatColor.RED + "Valid values for option " + option + " are: true and false"); + return false; + } + if (option.equals("friendlyfire")) { + team.setAllowFriendlyFire(value.equals("true")); + } else { + team.setCanSeeFriendlyInvisibles(value.equals("true")); + } + } + sender.sendMessage("Set option " + option + " for team " + team.getName() + " to " + value); + } + } + } else { + sender.sendMessage(ChatColor.RED + "Usage: /scoreboard "); + return false; + } + return true; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + if (args.length == 1) { + return StringUtil.copyPartialMatches(args[0], MAIN_CHOICES, new ArrayList()); + } + if (args.length > 1) { + if (args[0].equalsIgnoreCase("objectives")) { + if (args.length == 2) { + return StringUtil.copyPartialMatches(args[1], OBJECTIVES_CHOICES, new ArrayList()); + } + if (args[1].equalsIgnoreCase("add")) { + if (args.length == 4) { + return StringUtil.copyPartialMatches(args[3], OBJECTIVES_CRITERIA, new ArrayList()); + } + } else if (args[1].equalsIgnoreCase("remove")) { + if (args.length == 3) { + return StringUtil.copyPartialMatches(args[2], this.getCurrentObjectives(), new ArrayList()); + } + } else if (args[1].equalsIgnoreCase("setdisplay")) { + if (args.length == 3) { + return StringUtil.copyPartialMatches(args[2], OBJECTIVES_DISPLAYSLOT.keySet(), new ArrayList()); + } + if (args.length == 4) { + return StringUtil.copyPartialMatches(args[3], this.getCurrentObjectives(), new ArrayList()); + } + } + } else if (args[0].equalsIgnoreCase("players")) { + if (args.length == 2) { + return StringUtil.copyPartialMatches(args[1], PLAYERS_CHOICES, new ArrayList()); + } + if (args[1].equalsIgnoreCase("set") || args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("remove")) { + if (args.length == 3) { + return super.tabComplete(sender, alias, args); + } + if (args.length == 4) { + return StringUtil.copyPartialMatches(args[3], this.getCurrentObjectives(), new ArrayList()); + } + } else { + if (args.length == 3) { + return StringUtil.copyPartialMatches(args[2], this.getCurrentPlayers(), new ArrayList()); + } + } + } else if (args[0].equalsIgnoreCase("teams")) { + if (args.length == 2) { + return StringUtil.copyPartialMatches(args[1], TEAMS_CHOICES, new ArrayList()); + } + if (args[1].equalsIgnoreCase("join")) { + if (args.length == 3) { + return StringUtil.copyPartialMatches(args[2], this.getCurrentTeams(), new ArrayList()); + } + if (args.length >= 4) { + return super.tabComplete(sender, alias, args); + } + } else if (args[1].equalsIgnoreCase("leave")) { + return super.tabComplete(sender, alias, args); + } else if (args[1].equalsIgnoreCase("option")) { + if (args.length == 3) { + return StringUtil.copyPartialMatches(args[2], this.getCurrentTeams(), new ArrayList()); + } + if (args.length == 4) { + return StringUtil.copyPartialMatches(args[3], TEAMS_OPTION_CHOICES, new ArrayList()); + } + if (args.length == 5) { + if (args[3].equalsIgnoreCase("color")) { + return StringUtil.copyPartialMatches(args[4], TEAMS_OPTION_COLOR.keySet(), new ArrayList()); + } else { + return StringUtil.copyPartialMatches(args[4], BOOLEAN, new ArrayList()); + } + } + } else { + if (args.length == 3) { + return StringUtil.copyPartialMatches(args[2], this.getCurrentTeams(), new ArrayList()); + } + } + } + } + + return ImmutableList.of(); + } + + private static String offlinePlayerSetToString(Set set) { + StringBuilder string = new StringBuilder(); + String lastValue = null; + for (OfflinePlayer value : set) { + string.append(lastValue = value.getName()).append(", "); + } + string.delete(string.length() - 2, Integer.MAX_VALUE); + if (string.length() != lastValue.length()) { + string.insert(string.length() - lastValue.length(), "and "); + } + return string.toString(); + + } + + private static String stringCollectionToString(Collection set) { + StringBuilder string = new StringBuilder(); + String lastValue = null; + for (String value : set) { + string.append(lastValue = value).append(", "); + } + string.delete(string.length() - 2, Integer.MAX_VALUE); + if (string.length() != lastValue.length()) { + string.insert(string.length() - lastValue.length(), "and "); + } + return string.toString(); + } + + private List getCurrentObjectives() { + List list = new ArrayList(); + for (Objective objective : Bukkit.getScoreboardManager().getMainScoreboard().getObjectives()) { + list.add(objective.getName()); + } + Collections.sort(list, String.CASE_INSENSITIVE_ORDER); + return list; + } + + private List getCurrentPlayers() { + List list = new ArrayList(); + for (OfflinePlayer player : Bukkit.getScoreboardManager().getMainScoreboard().getPlayers()) { + list.add(player.getName()); + } + Collections.sort(list, String.CASE_INSENSITIVE_ORDER); + return list; + } + + private List getCurrentTeams() { + List list = new ArrayList(); + for (Team team : Bukkit.getScoreboardManager().getMainScoreboard().getTeams()) { + list.add(team.getName()); + } + Collections.sort(list, String.CASE_INSENSITIVE_ORDER); + return list; + } +} diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java index 17999378..80c8cb8d 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java @@ -17,6 +17,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.conversations.Conversable; import org.bukkit.map.MapView; import org.bukkit.plugin.messaging.PluginMessageRecipient; +import org.bukkit.scoreboard.Scoreboard; /** * Represents a player, connected or not @@ -626,4 +627,19 @@ public interface Player extends HumanEntity, Conversable, CommandSender, Offline * @throws IllegalArgumentException Thrown if the URL is too long. */ public void setTexturePack(String url); + + /** + * Gets the Scoreboard displayed to this player + * + * @return The current scoreboard seen by this player + */ + public Scoreboard getScoreboard(); + + /** + * Sets the player's visible Scoreboard + * Scoreboard must be currently registered or an IllegalArgumentException is thrown + * + * @param scoreboard New Scoreboard for the player + */ + public void setScoreboard(Scoreboard scoreboard); } diff --git a/src/main/java/org/bukkit/scoreboard/Criterias.java b/src/main/java/org/bukkit/scoreboard/Criterias.java new file mode 100644 index 00000000..cd81c87f --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/Criterias.java @@ -0,0 +1,20 @@ +package org.bukkit.scoreboard; + +/** + * Criteria names which trigger an objective to be modified by actions in-game + */ +public class Criterias { + public static final String HEALTH; + public static final String PLAYER_KILLS; + public static final String TOTAL_KILLS; + public static final String DEATHS; + + static { + HEALTH="health"; + PLAYER_KILLS="playerKillCount"; + TOTAL_KILLS="totalKillCount"; + DEATHS="deathCount"; + } + + private Criterias() {} +} diff --git a/src/main/java/org/bukkit/scoreboard/DisplaySlot.java b/src/main/java/org/bukkit/scoreboard/DisplaySlot.java new file mode 100644 index 00000000..5d58a18b --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/DisplaySlot.java @@ -0,0 +1,10 @@ +package org.bukkit.scoreboard; + +/** + * Locations for displaying objectives to the player + */ +public enum DisplaySlot { + BELOW_NAME, + PLAYER_LIST, + SIDEBAR; +} diff --git a/src/main/java/org/bukkit/scoreboard/Objective.java b/src/main/java/org/bukkit/scoreboard/Objective.java new file mode 100644 index 00000000..5c09a0e9 --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/Objective.java @@ -0,0 +1,98 @@ +package org.bukkit.scoreboard; + +import org.bukkit.OfflinePlayer; + +/** + * An objective on a scoreboard that can show scores specific to players. This + * objective is only relevant to the display of the associated {@link + * #getScoreboard() scoreboard}. + */ +public interface Objective { + + /** + * Gets the name of this Objective + * + * @return this objective'ss name + * @throws IllegalStateException if this objective has been unregistered + */ + String getName() throws IllegalStateException; + + /** + * Gets the name displayed to players for this objective + * + * @return this objective's display name + * @throws IllegalStateException if this objective has been unregistered + */ + String getDisplayName() throws IllegalStateException; + + /** + * Sets the name displayed to players for this objective. + * + * @param displayName Display name to set + * @throws IllegalStateException if this objective has been unregistered + * @throws IllegalArgumentException if displayName is null + * @throws IllegalArgumentException if displayName is longer than 32 + * characters. + */ + void setDisplayName(String displayName) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the criteria this objective tracks. + * + * @return this objective's criteria + * @throws IllegalStateException if this objective has been unregistered + */ + String getCriteria() throws IllegalStateException; + + /** + * Gets if the objective's scores can be modified directly by a plugin. + * + * @return true if scores are modifiable + * @throws IllegalStateException if this objective has been unregistered + * @see Criterias#HEALTH + */ + boolean isModifiable() throws IllegalStateException; + + /** + * Gets the scoreboard to which this objective is attached. + * + * @return Owning scoreboard, or null if it has been {@link #unregister() + * unregistered} + */ + Scoreboard getScoreboard(); + + /** + * Unregisters this objective from the {@link Scoreboard scoreboard.} + * + * @throws IllegalStateException if this objective has been unregistered + */ + void unregister() throws IllegalStateException; + + /** + * Sets this objective to display on the specified slot for the + * scoreboard, removing it from any other display slot. + * + * @param slot display slot to change, or null to not display + * @throws IllegalStateException if this objective has been unregistered + */ + void setDisplaySlot(DisplaySlot slot) throws IllegalStateException; + + /** + * Gets the display slot this objective is displayed at. + * + * @return the display slot for this objective, or null if not displayed + * @throws IllegalStateException if this objective has been unregistered + */ + DisplaySlot getDisplaySlot() throws IllegalStateException; + + /** + * Gets a player's Score for an Objective on this Scoreboard + * + * @param objective Objective for the Score + * @param player Player for the Score + * @return Score tracking the Objective and player specified + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this objective has been unregistered + */ + Score getScore(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException; +} diff --git a/src/main/java/org/bukkit/scoreboard/Score.java b/src/main/java/org/bukkit/scoreboard/Score.java new file mode 100644 index 00000000..c8c34edb --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/Score.java @@ -0,0 +1,51 @@ +package org.bukkit.scoreboard; + +import org.bukkit.OfflinePlayer; + +/** + * A score entry for a {@link #getPlayer() player} on an {@link + * #getObjective() objective}. Changing this will not affect any other + * objective or scoreboard. + */ +public interface Score { + + /** + * Gets the OfflinePlayer being tracked by this Score + * + * @return this Score's tracked player + */ + OfflinePlayer getPlayer(); + + /** + * Gets the Objective being tracked by this Score + * + * @return this Score's tracked objective + */ + Objective getObjective(); + + /** + * Gets the current score + * + * @return the current score + * @throws IllegalStateException if the associated objective has been + * unregistered + */ + int getScore() throws IllegalStateException; + + /** + * Sets the current score. + * + * @param score New score + * @throws IllegalStateException if the associated objective has been + * unregistered + */ + void setScore(int score) throws IllegalStateException; + + /** + * Gets the scoreboard for the associated objective. + * + * @return the owning objective's scoreboard, or null if it has been + * {@link Objective#unregister() unregistered} + */ + Scoreboard getScoreboard(); +} diff --git a/src/main/java/org/bukkit/scoreboard/Scoreboard.java b/src/main/java/org/bukkit/scoreboard/Scoreboard.java new file mode 100644 index 00000000..3e75cf5d --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/Scoreboard.java @@ -0,0 +1,123 @@ +package org.bukkit.scoreboard; + +import java.util.Set; + +import org.bukkit.OfflinePlayer; + +/** + * A scoreboard + */ +public interface Scoreboard { + + /** + * Registers an Objective on this Scoreboard + * + * @param name Name of the Objective + * @param criteria Criteria for the Objective + * @return The registered Objective + * @throws IllegalArgumentException if name is null + * @throws IllegalArgumentException if criteria is null + * @throws IllegalArgumentException if an objective by that name already exists + */ + Objective registerNewObjective(String name, String criteria) throws IllegalArgumentException; + + /** + * Gets an Objective on this Scoreboard by name + * + * @param name Name of the Objective + * @return the Objective or null if it does not exist + * @throws IllegalArgumentException if name is null + */ + Objective getObjective(String name) throws IllegalArgumentException; + + /** + * Gets all Objectives of a Criteria on the Scoreboard + * + * @param criteria Criteria to search by + * @return an immutable set of Objectives using the specified Criteria + */ + Set getObjectivesByCriteria(String criteria) throws IllegalArgumentException; + + /** + * Gets all Objectives on this Scoreboard + * + * @return An immutable set of all Objectives on this Scoreboard + */ + Set getObjectives(); + + /** + * Gets the Objective currently displayed in a DisplaySlot on this Scoreboard + * + * @param slot The DisplaySlot + * @return the Objective currently displayed or null if nothing is displayed in that DisplaySlot + * @throws IllegalArgumentException if slot is null + */ + Objective getObjective(DisplaySlot slot) throws IllegalArgumentException; + + /** + * Gets all scores for a player on this Scoreboard + * + * @param player the player whose scores are being retrieved + * @return immutable set of all scores tracked for the player + * @throws IllegalArgumentException if player is null + */ + Set getScores(OfflinePlayer player) throws IllegalArgumentException; + + /** + * Removes all scores for a player on this Scoreboard + * + * @param player the player to drop all current scores + * @throws IllegalArgumentException if player is null + */ + void resetScores(OfflinePlayer player) throws IllegalArgumentException; + + /** + * Gets a player's Team on this Scoreboard + * + * @param player the player to search for + * @return the player's Team or null if the player is not on a team + * @throws IllegalArgumentException if player is null + */ + Team getPlayerTeam(OfflinePlayer player) throws IllegalArgumentException; + + /** + * Gets a Team by name on this Scoreboard + * + * @param teamName Team name + * @return the matching Team or null if no matches + * @throws IllegalArgumentException if teamName is null + */ + Team getTeam(String teamName) throws IllegalArgumentException; + + /** + * Gets all teams on this Scoreboard + * + * @return an immutable set of Teams + */ + Set getTeams(); + + /** + * Registers a Team on this Scoreboard + * + * @param name Team name + * @return registered Team + * @throws IllegalArgumentException if name is null + * @throws IllegalArgumentException if team by that name already exists + */ + Team registerNewTeam(String name) throws IllegalArgumentException; + + /** + * Gets all players tracked by this Scoreboard + * + * @return immutable set of all tracked players + */ + Set getPlayers(); + + /** + * Clears any objective in the specified slot. + * + * @param slot the slot to remove objectives + * @throws IllegalArgumentException if slot is null + */ + void clearSlot(DisplaySlot slot) throws IllegalArgumentException; +} diff --git a/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java b/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java new file mode 100644 index 00000000..00b67a18 --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java @@ -0,0 +1,29 @@ +package org.bukkit.scoreboard; + +import java.lang.ref.WeakReference; + +/** + * Manager of Scoreboards + */ +public interface ScoreboardManager { + + /** + * Gets the primary Scoreboard controlled by the server. + *

+ * This Scoreboard is saved by the server, is affected by the /scoreboard + * command, and is the scoreboard shown by default to players. + * + * @return the default sever scoreboard + */ + Scoreboard getMainScoreboard(); + + /** + * Gets a new Scoreboard to be tracked by the server. This scoreboard will + * be tracked as long as a reference is kept, either by a player or by a + * plugin. + * + * @return the registered Scoreboard + * @see WeakReference + */ + Scoreboard getNewScoreboard(); +} diff --git a/src/main/java/org/bukkit/scoreboard/Team.java b/src/main/java/org/bukkit/scoreboard/Team.java new file mode 100644 index 00000000..c0773e39 --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/Team.java @@ -0,0 +1,175 @@ +package org.bukkit.scoreboard; + +import java.util.Set; + +import org.bukkit.OfflinePlayer; +import org.bukkit.potion.PotionEffectType; + +/** + * A team on a scoreboard that has a common display theme and other + * properties. This team is only relevant to the display of the associated + * {@link #getScoreboard() scoreboard}. + */ +public interface Team { + + /** + * Gets the name of this Team + * + * @return Objective name + * @throws IllegalStateException if this team has been unregistered + */ + String getName() throws IllegalStateException; + + /** + * Gets the name displayed to players for this team + * + * @return Team display name + * @throws IllegalStateException if this team has been unregistered + */ + String getDisplayName() throws IllegalStateException; + + /** + * Sets the name displayed to players for this team + * + * @param displayName New display name + * @throws IllegalArgumentException if displayName is longer than 32 + * characters. + * @throws IllegalStateException if this team has been unregistered + */ + void setDisplayName(String displayName) throws IllegalStateException, IllegalArgumentException; + + /** + * Sets the prefix prepended to the display of players on this team. + * + * @return Team prefix + * @throws IllegalStateException if this team has been unregistered + */ + String getPrefix() throws IllegalStateException; + + /** + * Sets the prefix prepended to the display of players on this team. + * + * @param prefix New prefix + * @throws IllegalArgumentException if prefix is null + * @throws IllegalArgumentException if prefix is longer than 16 + * characters + * @throws IllegalStateException if this team has been unregistered + */ + void setPrefix(String prefix) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the suffix appended to the display of players on this team. + * + * @return the team's current suffix + * @throws IllegalStateException if this team has been unregistered + */ + String getSuffix() throws IllegalStateException; + + /** + * Sets the suffix appended to the display of players on this team. + * + * @param suffix the new suffix for this team. + * @throws IllegalArgumentException if suffix is null + * @throws IllegalArgumentException if suffix is longer than 16 + * characters + * @throws IllegalStateException if this team has been unregistered + */ + void setSuffix(String suffix) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the team friendly fire state + * + * @return true if friendly fire is enabled + * @throws IllegalStateException if this team has been unregistered + */ + boolean allowFriendlyFire() throws IllegalStateException; + + /** + * Sets the team friendly fire state + * + * @param enabled true if friendly fire is to be allowed + * @throws IllegalStateException if this team has been unregistered + */ + void setAllowFriendlyFire(boolean enabled) throws IllegalStateException; + + /** + * Gets the team's ability to see {@link PotionEffectType#INVISIBILITY + * invisible} teammates. + * + * @return true if team members can see invisible members + * @throws IllegalStateException if this team has been unregistered + */ + boolean canSeeFriendlyInvisibles() throws IllegalStateException; + + /** + * Sets the team's ability to see invisible {@link + * PotionEffectType#INVISIBILITY invisible} teammates. + * + * @param enabled true if invisible teammates are to be visible + * @throws IllegalStateException if this team has been unregistered + */ + void setCanSeeFriendlyInvisibles(boolean enabled) throws IllegalStateException; + + /** + * Gets the Set of players on the team + * + * @return players on the team + * @throws IllegalStateException if this team has been unregistered + */ + Set getPlayers() throws IllegalStateException; + + /** + * Gets the size of the team + * + * @return number of players on the team + * @throws IllegalStateException if this team has been unregistered + */ + int getSize() throws IllegalStateException; + + /** + * Gets the Scoreboard to which this team is attached + * + * @return Owning scoreboard, or null if this team has been {@link + * #unregister() unregistered} + */ + Scoreboard getScoreboard(); + + /** + * This puts the specified player onto this team for the scoreboard. + *

+ * This will remove the player from any other team on the scoreboard. + * + * @param player the player to add + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this team has been unregistered + */ + void addPlayer(OfflinePlayer player) throws IllegalStateException, IllegalArgumentException; + + /** + * Removes the player from this team. + * + * @param player the player to remove + * @return if the player was on this team + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this team has been unregistered + */ + boolean removePlayer(OfflinePlayer player) throws IllegalStateException, IllegalArgumentException; + + /** + * Unregisters this team from the Scoreboard + * + * @param team Team to unregister + * @throws IllegalStateException if this team has been unregistered + */ + void unregister() throws IllegalStateException; + + /** + * Checks to see if the specified player is a member of this team. + * + * @param player the player to search for + * @return true if the player is a member of this team + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this team has been unregistered + */ + boolean hasPlayer(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException; +} diff --git a/src/main/javadoc/org/bukkit/scoreboard/package-info.java b/src/main/javadoc/org/bukkit/scoreboard/package-info.java new file mode 100644 index 00000000..aad5e310 --- /dev/null +++ b/src/main/javadoc/org/bukkit/scoreboard/package-info.java @@ -0,0 +1,6 @@ +/** + * Interfaces used to manage the client side score display system. + *

+ */ +package org.bukkit.scoreboard; + -- cgit v1.2.3