summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormbax <github@phozop.net>2013-03-20 14:14:42 -0400
committerWesley Wolfe <weswolf@aol.com>2013-04-03 23:32:54 -0500
commit073bbd5979f141b293167dafe52d2f95adea6166 (patch)
treedec5c14762ae4cf008dd2f3b4b0e97a8595f9cdf
parente0bdf959e0b7366b98f3cc216992bc23a2e3f221 (diff)
downloadbukkit-073bbd5979f141b293167dafe52d2f95adea6166.tar
bukkit-073bbd5979f141b293167dafe52d2f95adea6166.tar.gz
bukkit-073bbd5979f141b293167dafe52d2f95adea6166.tar.lz
bukkit-073bbd5979f141b293167dafe52d2f95adea6166.tar.xz
bukkit-073bbd5979f141b293167dafe52d2f95adea6166.zip
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.
-rw-r--r--src/main/java/org/bukkit/Bukkit.java5
-rw-r--r--src/main/java/org/bukkit/Server.java10
-rw-r--r--src/main/java/org/bukkit/command/SimpleCommandMap.java1
-rw-r--r--src/main/java/org/bukkit/command/defaults/ScoreboardCommand.java627
-rw-r--r--src/main/java/org/bukkit/entity/Player.java16
-rw-r--r--src/main/java/org/bukkit/scoreboard/Criterias.java20
-rw-r--r--src/main/java/org/bukkit/scoreboard/DisplaySlot.java10
-rw-r--r--src/main/java/org/bukkit/scoreboard/Objective.java98
-rw-r--r--src/main/java/org/bukkit/scoreboard/Score.java51
-rw-r--r--src/main/java/org/bukkit/scoreboard/Scoreboard.java123
-rw-r--r--src/main/java/org/bukkit/scoreboard/ScoreboardManager.java29
-rw-r--r--src/main/java/org/bukkit/scoreboard/Team.java175
-rw-r--r--src/main/javadoc/org/bukkit/scoreboard/package-info.java6
13 files changed, 1171 insertions, 0 deletions
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.
+ * <p>
+ * 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<String> MAIN_CHOICES = ImmutableList.of("objectives", "players", "teams");
+ private static final List<String> OBJECTIVES_CHOICES = ImmutableList.of("list", "add", "remove", "setdisplay");
+ private static final List<String> OBJECTIVES_CRITERIA = ImmutableList.of("health", "playerKillCount", "totalKillCount", "deathCount", "dummy");
+ private static final List<String> PLAYERS_CHOICES = ImmutableList.of("set", "add", "remove", "reset", "list");
+ private static final List<String> TEAMS_CHOICES = ImmutableList.of("add", "remove", "join", "leave", "empty", "list", "option");
+ private static final List<String> TEAMS_OPTION_CHOICES = ImmutableList.of("color", "friendlyfire", "seeFriendlyInvisibles");
+ private static final Map<String, DisplaySlot> OBJECTIVES_DISPLAYSLOT = ImmutableMap.of("belowName", DisplaySlot.BELOW_NAME, "list", DisplaySlot.PLAYER_LIST, "sidebar", DisplaySlot.SIDEBAR);
+ private static final Map<String, ChatColor> TEAMS_OPTION_COLOR = ImmutableMap.<String, ChatColor>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<String> 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 <objectives|players|teams>");
+ return false;
+ }
+
+ final Scoreboard mainScoreboard = Bukkit.getScoreboardManager().getMainScoreboard();
+
+ if (args[0].equalsIgnoreCase("objectives")) {
+ if (args.length == 1) {
+ sender.sendMessage(ChatColor.RED + "Usage: /scoreboard objectives <list|add|remove|setdisplay>");
+ return false;
+ }
+ if (args[1].equalsIgnoreCase("list")) {
+ Set<Objective> 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 <name> <criteriaType> [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 <name>");
+ 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 <slot> [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 <set|add|remove|reset|list>");
+ 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 <player> <objective> <score>");
+ } else if (args[1].equalsIgnoreCase("add")) {
+ sender.sendMessage(ChatColor.RED + "/scoreboard players add <player> <objective> <count>");
+ } else {
+ sender.sendMessage(ChatColor.RED + "/scoreboard players remove <player> <objective> <count>");
+ }
+ 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 <player>");
+ 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 <player>");
+ return false;
+ }
+ if (args.length == 2) {
+ Set<OfflinePlayer> 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<Score> 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 <list|add|remove|empty|join|leave|option>");
+ return false;
+ }
+ if (args[1].equalsIgnoreCase("list")) {
+ if (args.length == 2) {
+ Set<Team> 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<OfflinePlayer> 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 <name> [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 <name>");
+ 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 <name>");
+ 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<OfflinePlayer> 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 <team> [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<String> addedPlayers = new HashSet<String>();
+ 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<String> left = new HashSet<String>();
+ Set<String> noTeam = new HashSet<String>();
+ 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 <team> <friendlyfire|color|seefriendlyinvisibles> <value>");
+ 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 <team> <friendlyfire|color|seefriendlyinvisibles> <value>");
+ 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 <objectives|players|teams>");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public List<String> 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<String>());
+ }
+ if (args.length > 1) {
+ if (args[0].equalsIgnoreCase("objectives")) {
+ if (args.length == 2) {
+ return StringUtil.copyPartialMatches(args[1], OBJECTIVES_CHOICES, new ArrayList<String>());
+ }
+ if (args[1].equalsIgnoreCase("add")) {
+ if (args.length == 4) {
+ return StringUtil.copyPartialMatches(args[3], OBJECTIVES_CRITERIA, new ArrayList<String>());
+ }
+ } else if (args[1].equalsIgnoreCase("remove")) {
+ if (args.length == 3) {
+ return StringUtil.copyPartialMatches(args[2], this.getCurrentObjectives(), new ArrayList<String>());
+ }
+ } else if (args[1].equalsIgnoreCase("setdisplay")) {
+ if (args.length == 3) {
+ return StringUtil.copyPartialMatches(args[2], OBJECTIVES_DISPLAYSLOT.keySet(), new ArrayList<String>());
+ }
+ if (args.length == 4) {
+ return StringUtil.copyPartialMatches(args[3], this.getCurrentObjectives(), new ArrayList<String>());
+ }
+ }
+ } else if (args[0].equalsIgnoreCase("players")) {
+ if (args.length == 2) {
+ return StringUtil.copyPartialMatches(args[1], PLAYERS_CHOICES, new ArrayList<String>());
+ }
+ 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<String>());
+ }
+ } else {
+ if (args.length == 3) {
+ return StringUtil.copyPartialMatches(args[2], this.getCurrentPlayers(), new ArrayList<String>());
+ }
+ }
+ } else if (args[0].equalsIgnoreCase("teams")) {
+ if (args.length == 2) {
+ return StringUtil.copyPartialMatches(args[1], TEAMS_CHOICES, new ArrayList<String>());
+ }
+ if (args[1].equalsIgnoreCase("join")) {
+ if (args.length == 3) {
+ return StringUtil.copyPartialMatches(args[2], this.getCurrentTeams(), new ArrayList<String>());
+ }
+ 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<String>());
+ }
+ if (args.length == 4) {
+ return StringUtil.copyPartialMatches(args[3], TEAMS_OPTION_CHOICES, new ArrayList<String>());
+ }
+ if (args.length == 5) {
+ if (args[3].equalsIgnoreCase("color")) {
+ return StringUtil.copyPartialMatches(args[4], TEAMS_OPTION_COLOR.keySet(), new ArrayList<String>());
+ } else {
+ return StringUtil.copyPartialMatches(args[4], BOOLEAN, new ArrayList<String>());
+ }
+ }
+ } else {
+ if (args.length == 3) {
+ return StringUtil.copyPartialMatches(args[2], this.getCurrentTeams(), new ArrayList<String>());
+ }
+ }
+ }
+ }
+
+ return ImmutableList.of();
+ }
+
+ private static String offlinePlayerSetToString(Set<OfflinePlayer> 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<String> 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<String> getCurrentObjectives() {
+ List<String> list = new ArrayList<String>();
+ for (Objective objective : Bukkit.getScoreboardManager().getMainScoreboard().getObjectives()) {
+ list.add(objective.getName());
+ }
+ Collections.sort(list, String.CASE_INSENSITIVE_ORDER);
+ return list;
+ }
+
+ private List<String> getCurrentPlayers() {
+ List<String> list = new ArrayList<String>();
+ for (OfflinePlayer player : Bukkit.getScoreboardManager().getMainScoreboard().getPlayers()) {
+ list.add(player.getName());
+ }
+ Collections.sort(list, String.CASE_INSENSITIVE_ORDER);
+ return list;
+ }
+
+ private List<String> getCurrentTeams() {
+ List<String> list = new ArrayList<String>();
+ 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<Objective> getObjectivesByCriteria(String criteria) throws IllegalArgumentException;
+
+ /**
+ * Gets all Objectives on this Scoreboard
+ *
+ * @return An immutable set of all Objectives on this Scoreboard
+ */
+ Set<Objective> 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<Score> 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<Team> 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<OfflinePlayer> 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.
+ * <p>
+ * 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<OfflinePlayer> 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.
+ * <p>
+ * 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.
+ * <p>
+ */
+package org.bukkit.scoreboard;
+