diff --git a/Changelog.txt b/Changelog.txt index 82f250c92..d58b8ab1f 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -31,6 +31,11 @@ Version 1.4.07-dev + Added ability to give lore to items in treasures.yml - use the key "Lore" to set, expects a list of strings. + Added Quartz and Name Tags to the default Excavation treasures + Added a warning message if the server is running NoCheatPlus without CompatNoCheatPlus + + Added cooldown to commands with heavy database access to prevent denial of service + + Added /mcscoreboard keep, to keep the scoreboard up forever + + Added Rainbow Mode to scoreboards + + Added new /mccooldowns command to show all ability cooldowns + + Commands may now both print text and display a scoreboard + Killing a custom entity will automatically add it to the custom entity config file with default values. = Fixed bug which allowed players to bypass fishing's exploit prevention = Fixed bug where FakeEntityDamageByEntityEvent wasn't being fired @@ -71,6 +76,8 @@ Version 1.4.07-dev ! Party item share category states are now saved when the server shuts down. ! When using "Super Breaker" or "Giga Driller" abilities extra tool durability is used (again) ! Mob healthbars are automatically disabled when the plugin "HealthBar" is found + ! Massively improved scoreboard handling + ! Reworked scoreboard configuration (config.yml) - **you will need to update** - The /mmoupdate command has been removed. It is replaced by /mcconvert database - Removed Abilities.Tools.Durability_Loss_Enabled, set Abilities.Tools.Durability_Loss to 0 to disable instead. - Removed Skills.Fishing.Shake_UnlockLevel from advanced.yml, now using Skills.Fishing.Rank_Levels.Rank_1 instead. diff --git a/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java b/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java index 426bbbcdc..482042d24 100644 --- a/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java +++ b/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java @@ -461,7 +461,7 @@ public final class ExperienceAPI { * @return the position on the leaderboard */ public static int getPlayerRankSkill(String playerName, String skillType) { - return mcMMO.getDatabaseManager().readRank(getOfflineProfile(playerName).getPlayerName()).get(getNonChildSkillType(skillType).toString()); + return mcMMO.getDatabaseManager().readRank(getOfflineProfile(playerName).getPlayerName()).get(getNonChildSkillType(skillType)); } @@ -477,7 +477,7 @@ public final class ExperienceAPI { * @return the position on the power level leaderboard */ public static int getPlayerRankOverall(String playerName) { - return mcMMO.getDatabaseManager().readRank(getOfflineProfile(playerName).getPlayerName()).get("ALL"); + return mcMMO.getDatabaseManager().readRank(getOfflineProfile(playerName).getPlayerName()).get(null); } /** diff --git a/src/main/java/com/gmail/nossr50/commands/McscoreboardCommand.java b/src/main/java/com/gmail/nossr50/commands/McscoreboardCommand.java index dbe887137..7b1c46be2 100644 --- a/src/main/java/com/gmail/nossr50/commands/McscoreboardCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/McscoreboardCommand.java @@ -2,141 +2,73 @@ package com.gmail.nossr50.commands; import java.util.ArrayList; import java.util.List; - import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; -import org.bukkit.entity.Player; import org.bukkit.util.StringUtil; -import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.config.Config; -import com.gmail.nossr50.datatypes.skills.SkillType; -import com.gmail.nossr50.util.StringUtils; +import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.util.commands.CommandUtils; -import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.scoreboards.ScoreboardManager; - import com.google.common.collect.ImmutableList; public class McscoreboardCommand implements TabExecutor { - private static final List SCOREBOARD_TYPES = ImmutableList.of("clear", "rank", "stats", "top"); + private static final List FIRST_ARGS = ImmutableList.of("keep", "time", "clear", "reset"); @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (CommandUtils.noConsoleUsage(sender)) { return true; } - - Player player = (Player) sender; - - switch (args.length) { - case 0: - clearScoreboard(player); - return true; - - case 1: - if (args[0].equalsIgnoreCase("clear")) { - clearScoreboard(player); - } - else if (args[0].equalsIgnoreCase("rank")) { - if (!Config.getInstance().getMcrankScoreboardEnabled()) { - sender.sendMessage("This scoreboard is not enabled."); //TODO: Localize - return true; - } - - ScoreboardManager.setupPlayerScoreboard(player.getName()); - ScoreboardManager.enablePlayerRankScoreboard(player); - } - else if (args[0].equalsIgnoreCase("stats")) { - if (!Config.getInstance().getMcstatsScoreboardsEnabled()) { - sender.sendMessage("This scoreboard is not enabled."); //TODO: Localize - return true; - } - - ScoreboardManager.setupPlayerScoreboard(player.getName()); - ScoreboardManager.enablePlayerStatsScoreboard(UserManager.getPlayer(player)); - } - else if (args[0].equalsIgnoreCase("top")) { - if (!Config.getInstance().getMctopScoreboardEnabled()) { - sender.sendMessage("This scoreboard is not enabled."); //TODO: Localize - return true; - } - - ScoreboardManager.enableGlobalStatsScoreboard(player, "all", 1); - } - else { - return false; - } - - return true; - - case 2: - if (!args[0].equalsIgnoreCase("top")) { - return false; - } - - if (!Config.getInstance().getMctopScoreboardEnabled()) { - sender.sendMessage("This scoreboard is not enabled."); //TODO: Localize - return true; - } - - if (StringUtils.isInt(args[1])) { - ScoreboardManager.enableGlobalStatsScoreboard(player, "all", Math.abs(Integer.parseInt(args[1]))); - return true; - } - - if (CommandUtils.isInvalidSkill(sender, args[1])) { - return true; - } - - ScoreboardManager.enableGlobalStatsScoreboard(player, args[1], 1); - return true; - - case 3: - if (!args[0].equalsIgnoreCase("top")) { - return false; - } - - if (!Config.getInstance().getMctopScoreboardEnabled()) { - sender.sendMessage("This scoreboard is not enabled."); //TODO: Localize - return true; - } - - if (CommandUtils.isInvalidSkill(sender, args[1])) { - return true; - } - - if (CommandUtils.isInvalidInteger(sender, args[2])) { - return true; - } - - ScoreboardManager.enableGlobalStatsScoreboard(player, args[1], Math.abs(Integer.parseInt(args[2]))); - return true; - - default: - return false; + if (args.length == 0) { + help(sender); + return true; } + + if (args[0].equalsIgnoreCase("clear") || args[0].equalsIgnoreCase("reset")) { + ScoreboardManager.clearBoard(sender.getName()); + sender.sendMessage(LocaleLoader.getString("Commands.Scoreboard.Clear")); + } + else if (args[0].equalsIgnoreCase("keep")) { + if (!Config.getInstance().getAllowKeepBoard()) { + sender.sendMessage(LocaleLoader.getString("Commands.Disabled")); + return true; + } + ScoreboardManager.keepBoard(sender.getName()); + sender.sendMessage(LocaleLoader.getString("Commands.Scoreboard.Keep")); + } + else if (args[0].equalsIgnoreCase("time") || args[0].equalsIgnoreCase("timer")) { + if (args.length == 1) { + help(sender); + return true; + } + if (CommandUtils.isInvalidInteger(sender, args[1])) { + return true; + } + ScoreboardManager.setRevertTimer(sender.getName(), Math.abs(Integer.parseInt(args[1]))); + } + else { + help(sender); + } + return true; + } + + private void help(CommandSender sender) { + sender.sendMessage(LocaleLoader.getString("Commands.Scoreboard.Help.0")); + sender.sendMessage(LocaleLoader.getString("Commands.Scoreboard.Help.1")); + sender.sendMessage(LocaleLoader.getString("Commands.Scoreboard.Help.2")); + sender.sendMessage(LocaleLoader.getString("Commands.Scoreboard.Help.3")); } @Override public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { switch (args.length) { case 1: - return StringUtil.copyPartialMatches(args[0], SCOREBOARD_TYPES, new ArrayList(SCOREBOARD_TYPES.size())); - case 2: - if (args[0].equalsIgnoreCase("top")) { - return StringUtil.copyPartialMatches(args[1], SkillType.SKILL_NAMES, new ArrayList(SkillType.SKILL_NAMES.size())); - } - // Fallthrough - + return StringUtil.copyPartialMatches(args[0], FIRST_ARGS, new ArrayList(FIRST_ARGS.size())); default: - return ImmutableList.of(); + break; } - } - - private void clearScoreboard(Player player) { - player.setScoreboard(mcMMO.p.getServer().getScoreboardManager().getMainScoreboard()); - player.sendMessage("Your scoreboard has been cleared!"); //TODO: Locale + return ImmutableList.of(); } } diff --git a/src/main/java/com/gmail/nossr50/commands/player/InspectCommand.java b/src/main/java/com/gmail/nossr50/commands/player/InspectCommand.java index ca02eb82b..0fe327dc9 100644 --- a/src/main/java/com/gmail/nossr50/commands/player/InspectCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/player/InspectCommand.java @@ -29,10 +29,6 @@ public class InspectCommand implements TabExecutor { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { switch (args.length) { case 1: - if (sender instanceof Player && Config.getInstance().getInspectScoreboardEnabled()) { - ScoreboardManager.setupPlayerScoreboard(sender.getName()); - } - String playerName = Misc.getMatchedPlayerName(args[0]); McMMOPlayer mcMMOPlayer = UserManager.getPlayer(playerName, true); @@ -44,9 +40,9 @@ public class InspectCommand implements TabExecutor { return true; } - if (sender instanceof Player && Config.getInstance().getInspectScoreboardEnabled()) { - ScoreboardManager.enablePlayerInspectScoreboardOffline((Player) sender, profile); - return true; + if (sender instanceof Player && Config.getInstance().getInspectUseBoard()) { + ScoreboardManager.enablePlayerInspectScoreboard((Player) sender, profile); + if (!Config.getInstance().getInspectUseChat()) return true; } sender.sendMessage(LocaleLoader.getString("Inspect.OfflineStats", playerName)); @@ -80,9 +76,9 @@ public class InspectCommand implements TabExecutor { return true; } - if (sender instanceof Player && Config.getInstance().getInspectScoreboardEnabled()) { - ScoreboardManager.enablePlayerInspectScoreboardOnline((Player) sender, mcMMOPlayer); - return true; + if (sender instanceof Player && Config.getInstance().getInspectUseBoard()) { + ScoreboardManager.enablePlayerInspectScoreboard((Player) sender, mcMMOPlayer.getProfile()); + if (!Config.getInstance().getInspectUseChat()) return true; } sender.sendMessage(LocaleLoader.getString("Inspect.Stats", target.getName())); diff --git a/src/main/java/com/gmail/nossr50/commands/player/MccooldownCommand.java b/src/main/java/com/gmail/nossr50/commands/player/MccooldownCommand.java new file mode 100644 index 000000000..a8753cb7b --- /dev/null +++ b/src/main/java/com/gmail/nossr50/commands/player/MccooldownCommand.java @@ -0,0 +1,99 @@ +package com.gmail.nossr50.commands.player; + +import java.util.List; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.permissions.Permissible; + +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.config.Config; +import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.datatypes.skills.AbilityType; +import com.gmail.nossr50.locale.LocaleLoader; +import com.gmail.nossr50.util.Misc; +import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.commands.CommandUtils; +import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.scoreboards.ScoreboardManager; +import com.gmail.nossr50.util.skills.SkillUtils; +import com.google.common.collect.ImmutableList; + +public class MccooldownCommand implements TabExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (CommandUtils.noConsoleUsage(sender)) { + return true; + } + + switch (args.length) { + case 0: + Player player = (Player) sender; + + if (Config.getInstance().getCooldownUseBoard()) { + ScoreboardManager.enablePlayerCooldownScoreboard(player); + if (!Config.getInstance().getCooldownUseChat()) return true; + } + + PlayerProfile profile = UserManager.getPlayer(player).getProfile(); + + player.sendMessage(LocaleLoader.getString("Commands.Cooldowns.Header")); + player.sendMessage(LocaleLoader.getString("mcMMO.NoSkillNote")); + + for (AbilityType ability : AbilityType.NORMAL_ABILITIES) { + if (!hasPermission(player, ability)) { + continue; + } + + int seconds = SkillUtils.calculateTimeLeft(ability, profile, player); + + if (seconds <= 0) { + player.sendMessage(LocaleLoader.getString("Commands.Cooldowns.Row.Y", ability.getAbilityName())); + } + else { + player.sendMessage(LocaleLoader.getString("Commands.Cooldowns.Row.N", ability.getAbilityName(), Integer.toString(seconds))); + } + } + + return true; + + default: + return false; + } + } + + private boolean hasPermission(Permissible permissible, AbilityType ability) { + switch (ability) { + case BERSERK: + return Permissions.berserk(permissible); + case BLAST_MINING: + return Permissions.remoteDetonation(permissible); + case BLOCK_CRACKER: + return Permissions.blockCracker(permissible); + case GIGA_DRILL_BREAKER: + return Permissions.gigaDrillBreaker(permissible); + case GREEN_TERRA: + return Permissions.greenTerra(permissible); + case LEAF_BLOWER: + return Permissions.leafBlower(permissible); + case SERRATED_STRIKES: + return Permissions.serratedStrikes(permissible); + case SKULL_SPLITTER: + return Permissions.skullSplitter(permissible); + case SUPER_BREAKER: + return Permissions.superBreaker(permissible); + case TREE_FELLER: + return Permissions.treeFeller(permissible); + default: + mcMMO.p.getLogger().warning("MccooldownCommand - couldn't check permission for AbilityType." + ability.name()); + return false; + } + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + return ImmutableList.of(); + } +} diff --git a/src/main/java/com/gmail/nossr50/commands/player/McrankCommand.java b/src/main/java/com/gmail/nossr50/commands/player/McrankCommand.java index 00a7adb59..7ae4beeee 100644 --- a/src/main/java/com/gmail/nossr50/commands/player/McrankCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/player/McrankCommand.java @@ -13,13 +13,12 @@ import org.bukkit.util.StringUtil; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.config.Config; import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.runnables.commands.McrankCommandAsyncTask; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.commands.CommandUtils; import com.gmail.nossr50.util.player.UserManager; -import com.gmail.nossr50.util.scoreboards.ScoreboardManager; - import com.google.common.collect.ImmutableList; public class McrankCommand implements TabExecutor { @@ -36,13 +35,7 @@ public class McrankCommand implements TabExecutor { return true; } - if (Config.getInstance().getMcrankScoreboardEnabled()) { - ScoreboardManager.setupPlayerScoreboard(sender.getName()); - ScoreboardManager.enablePlayerRankScoreboard((Player) sender); - } - else { - display(sender, sender.getName()); - } + display(sender, sender.getName()); return true; @@ -66,13 +59,7 @@ public class McrankCommand implements TabExecutor { return true; } - if (sender instanceof Player && Config.getInstance().getMcrankScoreboardEnabled()) { - ScoreboardManager.setupPlayerScoreboard(sender.getName()); - ScoreboardManager.enablePlayerRankScoreboardOthers((Player) sender, playerName); - } - else { - display(sender, playerName); - } + display(sender, playerName); return true; default: @@ -92,6 +79,17 @@ public class McrankCommand implements TabExecutor { } private void display(CommandSender sender, String playerName) { - new McrankCommandAsyncTask(playerName, sender).runTaskAsynchronously(mcMMO.p); + if (sender instanceof Player) { + McMMOPlayer mcpl = UserManager.getPlayer(sender.getName()); + if (mcpl.getDatabaseATS() + Misc.PLAYER_DATABASE_COOLDOWN_MILLIS > System.currentTimeMillis()) { + sender.sendMessage(LocaleLoader.getString("Commands.Database.Cooldown")); + return; + } + mcpl.actualizeDatabaseATS(); + } + + boolean useBoard = (sender instanceof Player) && (Config.getInstance().getRankUseBoard()); + boolean useChat = useBoard ? Config.getInstance().getRankUseChat() : true; + new McrankCommandAsyncTask(playerName, sender, useBoard, useChat).runTaskAsynchronously(mcMMO.p); } } diff --git a/src/main/java/com/gmail/nossr50/commands/player/McstatsCommand.java b/src/main/java/com/gmail/nossr50/commands/player/McstatsCommand.java index 5711ecc73..a68972aa5 100644 --- a/src/main/java/com/gmail/nossr50/commands/player/McstatsCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/player/McstatsCommand.java @@ -8,7 +8,6 @@ import org.bukkit.command.TabExecutor; import org.bukkit.entity.Player; import com.gmail.nossr50.config.Config; -import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.util.commands.CommandUtils; import com.gmail.nossr50.util.player.UserManager; @@ -26,28 +25,25 @@ public class McstatsCommand implements TabExecutor { switch (args.length) { case 0: Player player = (Player) sender; - McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); - if (Config.getInstance().getMcstatsScoreboardsEnabled()) { - ScoreboardManager.setupPlayerScoreboard(player.getName()); - ScoreboardManager.enablePlayerStatsScoreboard(mcMMOPlayer); + if (Config.getInstance().getStatsUseBoard()) { + ScoreboardManager.enablePlayerStatsScoreboard(player); + if (!Config.getInstance().getStatsUseChat()) return true; + } + player.sendMessage(LocaleLoader.getString("Stats.Own.Stats")); + player.sendMessage(LocaleLoader.getString("mcMMO.NoSkillNote")); + + CommandUtils.printGatheringSkills(player); + CommandUtils.printCombatSkills(player); + CommandUtils.printMiscSkills(player); + + int powerLevelCap = Config.getInstance().getPowerLevelCap(); + + if (powerLevelCap != Integer.MAX_VALUE) { + player.sendMessage(LocaleLoader.getString("Commands.PowerLevel.Capped", UserManager.getPlayer(player).getPowerLevel(), powerLevelCap)); } else { - player.sendMessage(LocaleLoader.getString("Stats.Own.Stats")); - player.sendMessage(LocaleLoader.getString("mcMMO.NoSkillNote")); - - CommandUtils.printGatheringSkills(player); - CommandUtils.printCombatSkills(player); - CommandUtils.printMiscSkills(player); - - int powerLevelCap = Config.getInstance().getPowerLevelCap(); - - if (powerLevelCap != Integer.MAX_VALUE) { - player.sendMessage(LocaleLoader.getString("Commands.PowerLevel.Capped", UserManager.getPlayer(player).getPowerLevel(), powerLevelCap)); - } - else { - player.sendMessage(LocaleLoader.getString("Commands.PowerLevel", UserManager.getPlayer(player).getPowerLevel())); - } + player.sendMessage(LocaleLoader.getString("Commands.PowerLevel", UserManager.getPlayer(player).getPowerLevel())); } return true; diff --git a/src/main/java/com/gmail/nossr50/commands/player/MctopCommand.java b/src/main/java/com/gmail/nossr50/commands/player/MctopCommand.java index d688aa2af..a16bc0eab 100644 --- a/src/main/java/com/gmail/nossr50/commands/player/MctopCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/player/MctopCommand.java @@ -11,36 +11,39 @@ import org.bukkit.util.StringUtil; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.config.Config; +import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.SkillType; +import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.runnables.commands.MctopCommandAsyncTask; +import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.StringUtils; import com.gmail.nossr50.util.commands.CommandUtils; -import com.gmail.nossr50.util.scoreboards.ScoreboardManager; - +import com.gmail.nossr50.util.player.UserManager; import com.google.common.collect.ImmutableList; public class MctopCommand implements TabExecutor { - private SkillType skill; @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + SkillType skill; + switch (args.length) { case 0: - display(1, "ALL", sender, command); + display(1, null, sender, command); return true; case 1: if (StringUtils.isInt(args[0])) { - display(Math.abs(Integer.parseInt(args[0])), "ALL", sender, command); + display(Math.abs(Integer.parseInt(args[0])), null, sender, command); return true; } - if (!extractSkill(sender, args[0])) { + if ((skill = extractSkill(sender, args[0])) == null) { return true; } - display(1, skill.toString(), sender, command); + display(1, skill, sender, command); return true; case 2: @@ -48,11 +51,11 @@ public class MctopCommand implements TabExecutor { return true; } - if (!extractSkill(sender, args[0])) { + if ((skill = extractSkill(sender, args[0])) == null) { return true; } - display(Math.abs(Integer.parseInt(args[1])), skill.toString(), sender, command); + display(Math.abs(Integer.parseInt(args[1])), skill, sender, command); return true; default: @@ -70,35 +73,41 @@ public class MctopCommand implements TabExecutor { } } - private void display(int page, String skill, CommandSender sender, Command command) { - if (!skill.equalsIgnoreCase("all") && !Permissions.mctop(sender, this.skill)) { + private void display(int page, SkillType skill, CommandSender sender, Command command) { + if (skill != null && !Permissions.mctop(sender, skill)) { sender.sendMessage(command.getPermissionMessage()); return; } - if (sender instanceof Player && Config.getInstance().getMctopScoreboardEnabled()) { - ScoreboardManager.enableGlobalStatsScoreboard((Player) sender, skill, page); - } - else { - display(page, skill, sender); + if (sender instanceof Player) { + McMMOPlayer mcpl = UserManager.getPlayer(sender.getName()); + if (mcpl.getDatabaseATS() + Misc.PLAYER_DATABASE_COOLDOWN_MILLIS > System.currentTimeMillis()) { + sender.sendMessage(LocaleLoader.getString("Commands.Database.Cooldown")); + return; + } + mcpl.actualizeDatabaseATS(); } + + display(page, skill, sender); } - private void display(int page, String query, CommandSender sender) { - new MctopCommandAsyncTask(page, query, sender).runTaskAsynchronously(mcMMO.p); + private void display(int page, SkillType skill, CommandSender sender) { + boolean useBoard = (sender instanceof Player) && (Config.getInstance().getTopUseBoard()); + boolean useChat = useBoard ? Config.getInstance().getTopUseChat() : true; + + new MctopCommandAsyncTask(page, skill, sender, useBoard, useChat).runTaskAsynchronously(mcMMO.p); } - private boolean extractSkill(CommandSender sender, String skillName) { + private SkillType extractSkill(CommandSender sender, String skillName) { if (CommandUtils.isInvalidSkill(sender, skillName)) { - return false; + return null; + } + SkillType skill = SkillType.getSkill(skillName); + + if (skill != null && CommandUtils.isChildSkill(sender, skill)) { + return null; } - skill = SkillType.getSkill(skillName); - - if (CommandUtils.isChildSkill(sender, skill)) { - return false; - } - - return true; + return skill; } } diff --git a/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java index 40dc995ab..4c8ee37a0 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java @@ -69,17 +69,15 @@ public abstract class SkillCommand implements TabExecutor { permissionsCheck(); dataCalculations(); + if (Config.getInstance().getSkillUseBoard()) { + ScoreboardManager.enablePlayerSkillScoreboard(player, skill); + } + if (!skill.isChildSkill()) { player.sendMessage(LocaleLoader.getString("Skills.Header", skillName)); player.sendMessage(LocaleLoader.getString("Commands.XPGain", LocaleLoader.getString("Commands.XPGain." + StringUtils.getCapitalized(skill.toString())))); + player.sendMessage(LocaleLoader.getString("Effects.Level", (int) skillValue, profile.getSkillXpLevel(skill), profile.getXpToLevel(skill))); - if (Config.getInstance().getSkillScoreboardEnabled()) { - ScoreboardManager.setupPlayerScoreboard(player.getName()); - ScoreboardManager.enablePlayerSkillScoreboard(mcMMOPlayer, skill); - } - else { - player.sendMessage(LocaleLoader.getString("Effects.Level", (int) skillValue, profile.getSkillXpLevel(skill), profile.getXpToLevel(skill))); - } } else { player.sendMessage(LocaleLoader.getString("Skills.Header", skillName + " " + LocaleLoader.getString("Skills.Child"))); diff --git a/src/main/java/com/gmail/nossr50/config/Config.java b/src/main/java/com/gmail/nossr50/config/Config.java index dc38268e6..2673641d2 100644 --- a/src/main/java/com/gmail/nossr50/config/Config.java +++ b/src/main/java/com/gmail/nossr50/config/Config.java @@ -45,26 +45,52 @@ public class Config extends AutoUpdateConfigLoader { } /* Scoreboards */ - if (getMcrankScoreboardTime() != -1 && getMcrankScoreboardTime() <= 0) { - reason.add("Scoreboards.Mcrank.Display_Time should be greater than 0 or -1!"); + if (getRankScoreboardTime() != -1 && getRankScoreboardTime() <= 0) { + reason.add("Scoreboard.Types.Rank.Display_Time should be greater than 0, or -1!"); } - if (getMcstatsScoreboardTime() != -1 && getMcstatsScoreboardTime() <= 0) { - reason.add("Scoreboards.Mcstats.Display_Time should be greater than 0 or -1!"); + if (getStatsScoreboardTime() != -1 && getStatsScoreboardTime() <= 0) { + reason.add("Scoreboard.Types.Stats.Display_Time should be greater than 0, or -1!"); } - if (getMctopScoreboardTime() != -1 && getMctopScoreboardTime() <= 0) { - reason.add("Scoreboards.Mctop.Display_Time should be greater than 0 or -1!"); + if (getTopScoreboardTime() != -1 && getTopScoreboardTime() <= 0) { + reason.add("Scoreboard.Types.Top.Display_Time should be greater than 0, or -1!"); } if (getInspectScoreboardTime() != -1 && getInspectScoreboardTime() <= 0) { - reason.add("Scoreboards.Inspect.Display_Time should be greater than 0 or -1!"); + reason.add("Scoreboard.Types.Inspect.Display_Time should be greater than 0, or -1!"); } if (getSkillScoreboardTime() != -1 && getSkillScoreboardTime() <= 0) { - reason.add("Scoreboards.Skillname.Display_Time should be greater than 0 or -1!"); + reason.add("Scoreboard.Types.Skill.Display_Time should be greater than 0, or -1!"); } + if (getSkillLevelUpTime() != -1 && getSkillScoreboardTime() <= 0) { + reason.add("Scoreboard.Types.Skill.Display_Time should be greater than 0, or -1!"); + } + + if (!(getRankUseChat() || getRankUseBoard())) { + reason.add("Either Board or Print in Scoreboard.Types.Rank must be true!"); + } + + if (!(getTopUseChat() || getTopUseBoard())) { + reason.add("Either Board or Print in Scoreboard.Types.Top must be true!"); + } + + if (!(getStatsUseChat() || getStatsUseBoard())) { + reason.add("Either Board or Print in Scoreboard.Types.Stats must be true!"); + } + + if (!(getInspectUseChat() || getInspectUseBoard())) { + reason.add("Either Board or Print in Scoreboard.Types.Inspect must be true!"); + } + + /* Skill.Print setting removed, as I can't think of a good use for it + if (!(getSkillUseChat() || getSkillUseBoard())) { + reason.add("Either Board or Print in Scoreboard.Commands.Skill must be true!"); + } + // */ + /* Database Purging */ if (getPurgeInterval() < -1) { reason.add("Database_Purging.Purge_Interval should be greater than, or equal to -1!"); @@ -212,22 +238,36 @@ public class Config extends AutoUpdateConfigLoader { public int getMobHealthbarTime() { return config.getInt("Mob_Healthbar.Display_Time", 3); } /* Scoreboards */ - public boolean getMcrankScoreboardEnabled() { return config.getBoolean("Scoreboards.Mcrank.Use", true); } - public int getMcrankScoreboardTime() { return config.getInt("Scoreboards.Mcrank.Display_Time", 10); } + public boolean getRankUseChat() { return config.getBoolean("Scoreboard.Types.Rank.Print", false); } + public boolean getRankUseBoard() { return config.getBoolean("Scoreboard.Types.Rank.Board", true); } + public int getRankScoreboardTime() { return config.getInt("Scoreboard.Types.Rank.Display_Time", 10); } - public boolean getMcstatsScoreboardsEnabled() { return config.getBoolean("Scoreboards.Mcstats.Use", true); } - public int getMcstatsScoreboardTime() { return config.getInt("Scoreboards.Mcstats.Display_Time", 10); } + public boolean getTopUseChat() { return config.getBoolean("Scoreboard.Types.Top.Print", true); } + public boolean getTopUseBoard() { return config.getBoolean("Scoreboard.Types.Top.Board", true); } + public int getTopScoreboardTime() { return config.getInt("Scoreboard.Types.Top.Display_Time", 15); } - public boolean getMctopScoreboardEnabled() { return config.getBoolean("Scoreboards.Mctop.Use", true); } - public int getMctopScoreboardTime() { return config.getInt("Scoreboards.Mctop.Display_Time", 10); } + public boolean getStatsUseChat() { return config.getBoolean("Scoreboard.Types.Stats.Print", true); } + public boolean getStatsUseBoard() { return config.getBoolean("Scoreboard.Types.Stats.Board", true); } + public int getStatsScoreboardTime() { return config.getInt("Scoreboard.Types.Stats.Display_Time", 10); } - public boolean getInspectScoreboardEnabled() { return config.getBoolean("Scoreboards.Inspect.Use", true); } - public int getInspectScoreboardTime() { return config.getInt("Scoreboards.Inspect.Display_Time", 10); } + public boolean getInspectUseChat() { return config.getBoolean("Scoreboard.Types.Inspect.Print", true); } + public boolean getInspectUseBoard() { return config.getBoolean("Scoreboard.Types.Inspect.Board", true); } + public int getInspectScoreboardTime() { return config.getInt("Scoreboard.Types.Inspect.Display_Time", 25); } - public boolean getSkillScoreboardEnabled() { return config.getBoolean("Scoreboards.Skillname.Use", true); } - public int getSkillScoreboardTime() { return config.getInt("Scoreboards.Skillname.Display_Time", 10); } + public boolean getCooldownUseChat() { return config.getBoolean("Scoreboard.Types.Cooldown.Print", false); } + public boolean getCooldownUseBoard() { return config.getBoolean("Scoreboard.Types.Cooldown.Board", true); } + public int getCooldownScoreboardTime() { return config.getInt("Scoreboard.Types.Cooldown.Display_Time", 41); } - public boolean getPowerLevelsEnabled() { return config.getBoolean("Scoreboards.Power_Level.Use", false); } + // public boolean getSkillUseChat() { return config.getBoolean("Scoreboard.Types.Skill.Print", false); } + public boolean getSkillUseBoard() { return config.getBoolean("Scoreboard.Types.Skill.Board", true); } + public int getSkillScoreboardTime() { return config.getInt("Scoreboard.Types.Skill.Display_Time", 30); } + public boolean getSkillLevelUpBoard() { return config.getBoolean("Scoreboard.Types.Skill.LevelUp_Board", true); } + public int getSkillLevelUpTime() { return config.getInt("Scoreboard.Types.Skill.LevelUp_Time", 5); } + + public boolean getPowerLevelTagsEnabled() { return config.getBoolean("Scoreboard.Power_Level_Tags", false); } + + public boolean getAllowKeepBoard() { return config.getBoolean("Scoreboard.Allow_Keep", true); } + public boolean getScoreboardRainbows() { return config.getBoolean("Scoreboard.Rainbows", false); } /* Database Purging */ public int getPurgeInterval() { return config.getInt("Database_Purging.Purge_Interval", -1); } @@ -324,8 +364,8 @@ public class Config extends AutoUpdateConfigLoader { public boolean getAbilitiesEnabled() { return config.getBoolean("Abilities.Enabled", true); } public boolean getAbilitiesOnlyActivateWhenSneaking() { return config.getBoolean("Abilities.Activation.Only_Activate_When_Sneaking", false); } - public int getCooldown(AbilityType ability) { return config.getInt("Abilities.Cooldowns." + ability.toString()); } - public int getMaxLength(AbilityType ability) { return config.getInt("Abilities.Max_Seconds." + ability.toString()); } + public int getCooldown(AbilityType ability) { return config.getInt("Abilities.Cooldowns." + ability.getConfigString()); } + public int getMaxLength(AbilityType ability) { return config.getInt("Abilities.Max_Seconds." + ability.getConfigString()); } /* Durability Settings */ public int getAbilityToolDamage() { return config.getInt("Abilities.Tools.Durability_Loss", 1); } diff --git a/src/main/java/com/gmail/nossr50/database/DatabaseManager.java b/src/main/java/com/gmail/nossr50/database/DatabaseManager.java index 717f835c4..11f17a6b9 100644 --- a/src/main/java/com/gmail/nossr50/database/DatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/DatabaseManager.java @@ -7,6 +7,7 @@ import com.gmail.nossr50.config.Config; import com.gmail.nossr50.datatypes.database.DatabaseType; import com.gmail.nossr50.datatypes.database.PlayerStat; import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.datatypes.skills.SkillType; public interface DatabaseManager { // One month in milliseconds @@ -48,15 +49,18 @@ public interface DatabaseManager { * @param statsPerPage The number of stats per page * @return the requested leaderboard information */ - public List readLeaderboard(String skillName, int pageNumber, int statsPerPage); + public List readLeaderboard(SkillType skill, int pageNumber, int statsPerPage); /** - * Retrieve rank info. + * Retrieve rank info into a HashMap from SkillType to the rank. + *

+ * The special value null is used to represent the Power + * Level rank (the combination of all skill levels). * * @param playerName The name of the user to retrieve the rankings for * @return the requested rank information */ - public Map readRank(String playerName); + public Map readRank(String playerName); /** * Add a new user to the database. diff --git a/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java index 2d7787bbc..2ebc49455 100644 --- a/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java @@ -285,24 +285,24 @@ public final class FlatfileDatabaseManager implements DatabaseManager { } } - public List readLeaderboard(String skillName, int pageNumber, int statsPerPage) { + public List readLeaderboard(SkillType skill, int pageNumber, int statsPerPage) { updateLeaderboards(); - List statsList = skillName.equalsIgnoreCase("all") ? powerLevels : playerStatHash.get(SkillType.getSkill(skillName)); + List statsList = skill == null ? powerLevels : playerStatHash.get(skill); int fromIndex = (Math.max(pageNumber, 1) - 1) * statsPerPage; return statsList.subList(Math.min(fromIndex, statsList.size()), Math.min(fromIndex + statsPerPage, statsList.size())); } - public Map readRank(String playerName) { + public Map readRank(String playerName) { updateLeaderboards(); - Map skills = new HashMap(); + Map skills = new HashMap(); for (SkillType skill : SkillType.NON_CHILD_SKILLS) { - skills.put(skill.name(), getPlayerRank(playerName, playerStatHash.get(skill))); + skills.put(skill, getPlayerRank(playerName, playerStatHash.get(skill))); } - skills.put("ALL", getPlayerRank(playerName, powerLevels)); + skills.put(null, getPlayerRank(playerName, powerLevels)); return skills; } @@ -400,7 +400,16 @@ public final class FlatfileDatabaseManager implements DatabaseManager { e.printStackTrace(); } finally { - tryClose(in); + // I have no idea why it's necessary to inline tryClose() here, but it removes + // a resource leak warning, and I'm trusting the compiler on this one. + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } } } diff --git a/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java index ae6c10ff3..4e14b4bca 100644 --- a/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java @@ -188,11 +188,11 @@ public final class SQLDatabaseManager implements DatabaseManager { return success; } - public List readLeaderboard(String skillName, int pageNumber, int statsPerPage) { + public List readLeaderboard(SkillType skill, int pageNumber, int statsPerPage) { List stats = new ArrayList(); if (checkConnected()) { - String query = skillName.equalsIgnoreCase("ALL") ? "taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing" : skillName; + String query = skill == null ? "taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing" : skill.name().toLowerCase(); ResultSet resultSet = null; PreparedStatement statement = null; @@ -230,8 +230,8 @@ public final class SQLDatabaseManager implements DatabaseManager { return stats; } - public Map readRank(String playerName) { - Map skills = new HashMap(); + public Map readRank(String playerName) { + Map skills = new HashMap(); if (checkConnected()) { ResultSet resultSet; @@ -262,7 +262,7 @@ public final class SQLDatabaseManager implements DatabaseManager { while (resultSet.next()) { if (resultSet.getString("user").equalsIgnoreCase(playerName)) { - skills.put(skillType.name(), rank + resultSet.getRow()); + skills.put(skillType, rank + resultSet.getRow()); break; } } @@ -299,7 +299,7 @@ public final class SQLDatabaseManager implements DatabaseManager { while (resultSet.next()) { if (resultSet.getString("user").equalsIgnoreCase(playerName)) { - skills.put("ALL", rank + resultSet.getRow()); + skills.put(null, rank + resultSet.getRow()); break; } } diff --git a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java index 61f997be2..ddbd8407e 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java +++ b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java @@ -97,6 +97,7 @@ public class McMMOPlayer { private int recentlyHurt; private int respawnATS; private int teleportATS; + private long databaseATS; private int chimeraWingLastUse; private Location teleportCommence; @@ -427,6 +428,14 @@ public class McMMOPlayer { teleportATS = (int) (System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR); } + public long getDatabaseATS() { + return databaseATS; + } + + public void actualizeDatabaseATS() { + databaseATS = System.currentTimeMillis(); + } + /* * Repair Anvil Placement */ diff --git a/src/main/java/com/gmail/nossr50/datatypes/skills/AbilityType.java b/src/main/java/com/gmail/nossr50/datatypes/skills/AbilityType.java index 569bcc5d9..265e99997 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/skills/AbilityType.java +++ b/src/main/java/com/gmail/nossr50/datatypes/skills/AbilityType.java @@ -1,5 +1,7 @@ package com.gmail.nossr50.datatypes.skills; +import java.util.List; + import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockState; @@ -11,9 +13,11 @@ import com.gmail.nossr50.util.BlockUtils; import com.gmail.nossr50.util.EventUtils; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.StringUtils; +import com.google.common.collect.ImmutableList; public enum AbilityType { BERSERK( + "Unarmed.Skills.Berserk.Name", "Unarmed.Skills.Berserk.On", "Unarmed.Skills.Berserk.Off", "Unarmed.Skills.Berserk.Other.On", @@ -21,6 +25,7 @@ public enum AbilityType { "Unarmed.Skills.Berserk.Other.Off"), SUPER_BREAKER( + "Mining.Skills.SuperBreaker.Name", "Mining.Skills.SuperBreaker.On", "Mining.Skills.SuperBreaker.Off", "Mining.Skills.SuperBreaker.Other.On", @@ -28,6 +33,7 @@ public enum AbilityType { "Mining.Skills.SuperBreaker.Other.Off"), GIGA_DRILL_BREAKER( + "Excavation.Skills.GigaDrillBreaker.Name", "Excavation.Skills.GigaDrillBreaker.On", "Excavation.Skills.GigaDrillBreaker.Off", "Excavation.Skills.GigaDrillBreaker.Other.On", @@ -35,6 +41,7 @@ public enum AbilityType { "Excavation.Skills.GigaDrillBreaker.Other.Off"), GREEN_TERRA( + "Herbalism.Skills.GTe.Name", "Herbalism.Skills.GTe.On", "Herbalism.Skills.GTe.Off", "Herbalism.Skills.GTe.Other.On", @@ -42,6 +49,7 @@ public enum AbilityType { "Herbalism.Skills.GTe.Other.Off"), SKULL_SPLITTER( + "Axes.Skills.SS.Name", "Axes.Skills.SS.On", "Axes.Skills.SS.Off", "Axes.Skills.SS.Other.On", @@ -49,6 +57,7 @@ public enum AbilityType { "Axes.Skills.SS.Other.Off"), TREE_FELLER( + "Woodcutting.Skills.TreeFeller.Name", "Woodcutting.Skills.TreeFeller.On", "Woodcutting.Skills.TreeFeller.Off", "Woodcutting.Skills.TreeFeller.Other.On", @@ -56,40 +65,81 @@ public enum AbilityType { "Woodcutting.Skills.TreeFeller.Other.Off"), SERRATED_STRIKES( + "Swords.Skills.SS.Name", "Swords.Skills.SS.On", "Swords.Skills.SS.Off", "Swords.Skills.SS.Other.On", "Swords.Skills.SS.Refresh", "Swords.Skills.SS.Other.Off"), + /** + * Has cooldown - but has to share a skill with Super Breaker, so needs special treatment + */ BLAST_MINING( + "Mining.Blast.Name", null, null, "Mining.Blast.Other.On", "Mining.Blast.Refresh", null), + /** + * No cooldown - always active + */ LEAF_BLOWER( null, null, null, null, + null, null), + /** + * Not a first-class Ability - part of Berserk + */ BLOCK_CRACKER( null, null, null, null, + null, null); + private String abilityName; private String abilityOn; private String abilityOff; private String abilityPlayer; private String abilityRefresh; private String abilityPlayerOff; - private AbilityType(String abilityOn, String abilityOff, String abilityPlayer, String abilityRefresh, String abilityPlayerOff) { + /** + * Those abilities that have a cooldown saved to the database. + */ + public static final List NORMAL_ABILITIES; + /** + * Those abilities that do not have a cooldown saved to the database. + */ + public static final List NON_NORMAL_ABILITIES; + + static { + NORMAL_ABILITIES = ImmutableList.of( + BERSERK, + SUPER_BREAKER, + GIGA_DRILL_BREAKER, + GREEN_TERRA, + SKULL_SPLITTER, + TREE_FELLER, + SERRATED_STRIKES, + BLAST_MINING + ); + NON_NORMAL_ABILITIES = ImmutableList.of( + LEAF_BLOWER, + BLOCK_CRACKER + ); + } + + private AbilityType(String abilityName, String abilityOn, String abilityOff, String abilityPlayer, String abilityRefresh, String abilityPlayerOff) { + this.abilityName = abilityName; this.abilityOn = abilityOn; this.abilityOff = abilityOff; this.abilityPlayer = abilityPlayer; @@ -105,6 +155,17 @@ public enum AbilityType { return Config.getInstance().getMaxLength(this); } + /** + * May return null + * @return ability name, or null if unavailable + */ + public String getAbilityName() { + if (this.abilityName == null) { + return null; + } + return LocaleLoader.getString(this.abilityName); + } + public String getAbilityOn() { return LocaleLoader.getString(this.abilityOn); } @@ -125,6 +186,11 @@ public enum AbilityType { return LocaleLoader.getString(this.abilityRefresh); } + public String getConfigString() { + // If toString() changes, place old code here to not break config.yml + return this.toString(); + } + @Override public String toString() { String baseString = name(); diff --git a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java index 6b037ed95..486822db4 100644 --- a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java @@ -57,7 +57,6 @@ import com.gmail.nossr50.util.MobHealthbarUtils; import com.gmail.nossr50.util.Motd; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; -import com.gmail.nossr50.util.scoreboards.ScoreboardManager; import com.gmail.nossr50.util.skills.SkillUtils; public class PlayerListener implements Listener { @@ -363,7 +362,6 @@ public class PlayerListener implements Listener { } UserManager.addUser(player).actualizeRespawnATS(); - ScoreboardManager.enablePowerLevelDisplay(player); if (Config.getInstance().getMOTDEnabled() && Permissions.motd(player)) { Motd.displayAll(player); diff --git a/src/main/java/com/gmail/nossr50/listeners/ScoreboardsListener.java b/src/main/java/com/gmail/nossr50/listeners/ScoreboardsListener.java new file mode 100644 index 000000000..cc84494d1 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/listeners/ScoreboardsListener.java @@ -0,0 +1,49 @@ +package com.gmail.nossr50.listeners; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent; +import com.gmail.nossr50.events.experience.McMMOPlayerXpGainEvent; +import com.gmail.nossr50.events.skills.abilities.McMMOPlayerAbilityActivateEvent; +import com.gmail.nossr50.util.Misc; +import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.scoreboards.ScoreboardManager; +import com.gmail.nossr50.util.skills.SkillUtils; + +public class ScoreboardsListener implements Listener { + private final mcMMO plugin; + + public ScoreboardsListener(final mcMMO plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent e) { + ScoreboardManager.setupPlayer(e.getPlayer()); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent e) { + ScoreboardManager.teardownPlayer(e.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerLevelUp(McMMOPlayerLevelUpEvent e) { + ScoreboardManager.handleLevelUp(e.getPlayer(), e.getSkill()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerXp(McMMOPlayerXpGainEvent e) { + ScoreboardManager.handleXp(e.getPlayer(), e.getSkill()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onAbility(McMMOPlayerAbilityActivateEvent e) { + ScoreboardManager.cooldownUpdate(e.getPlayer(), e.getSkill(), SkillUtils.calculateTimeLeft(UserManager.getPlayer(e.getPlayer()).getProfile().getSkillDATS(e.getAbility()) * Misc.TIME_CONVERSION_FACTOR, e.getAbility().getCooldown(), e.getPlayer())); + } +} diff --git a/src/main/java/com/gmail/nossr50/locale/LocaleLoader.java b/src/main/java/com/gmail/nossr50/locale/LocaleLoader.java index 8d0808379..52693bef1 100644 --- a/src/main/java/com/gmail/nossr50/locale/LocaleLoader.java +++ b/src/main/java/com/gmail/nossr50/locale/LocaleLoader.java @@ -7,6 +7,7 @@ import java.util.ResourceBundle; import org.bukkit.ChatColor; +import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.config.Config; public final class LocaleLoader { @@ -40,6 +41,7 @@ public final class LocaleLoader { return getString(key, enBundle, messageArguments); } catch (MissingResourceException ex2) { + mcMMO.p.getLogger().warning("Could not find locale string: " + key); return '!' + key + '!'; } } diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index 33d6330b6..035e116cf 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -25,6 +25,7 @@ import com.gmail.nossr50.listeners.BlockListener; import com.gmail.nossr50.listeners.EntityListener; import com.gmail.nossr50.listeners.InventoryListener; import com.gmail.nossr50.listeners.PlayerListener; +import com.gmail.nossr50.listeners.ScoreboardsListener; import com.gmail.nossr50.listeners.SelfListener; import com.gmail.nossr50.listeners.WorldListener; import com.gmail.nossr50.locale.LocaleLoader; @@ -33,6 +34,7 @@ import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.runnables.SaveTimerTask; import com.gmail.nossr50.runnables.database.UserPurgeTask; import com.gmail.nossr50.runnables.party.PartyAutoKickTask; +import com.gmail.nossr50.runnables.player.PowerLevelUpdatingTask; import com.gmail.nossr50.runnables.skills.BleedTimerTask; import com.gmail.nossr50.skills.child.ChildConfig; import com.gmail.nossr50.skills.repair.config.RepairConfigManager; @@ -48,6 +50,7 @@ import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManagerFactory; import com.gmail.nossr50.util.commands.CommandRegistrationManager; import com.gmail.nossr50.util.experience.FormulaManager; import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.scoreboards.ScoreboardManager; import net.gravitydevelopment.updater.mcmmo.Updater; import net.gravitydevelopment.updater.mcmmo.Updater.UpdateResult; @@ -152,6 +155,7 @@ public class mcMMO extends JavaPlugin { for (Player player : getServer().getOnlinePlayers()) { UserManager.addUser(player); // In case of reload add all users back into UserManager + ScoreboardManager.setupPlayer(player); } debug("Version " + getDescription().getVersion() + " is enabled!"); @@ -191,6 +195,7 @@ public class mcMMO extends JavaPlugin { try { UserManager.saveAll(); // Make sure to save player information if the server shuts down PartyManager.saveParties(); // Save our parties + ScoreboardManager.teardownAll(); formulaManager.saveFormula(); placeStore.saveAll(); // Save our metadata placeStore.cleanUp(); // Cleanup empty metadata stores @@ -379,6 +384,7 @@ public class mcMMO extends JavaPlugin { pluginManager.registerEvents(new EntityListener(this), this); pluginManager.registerEvents(new InventoryListener(this), this); pluginManager.registerEvents(new SelfListener(), this); + pluginManager.registerEvents(new ScoreboardsListener(this), this); pluginManager.registerEvents(new WorldListener(this), this); } @@ -415,6 +421,9 @@ public class mcMMO extends JavaPlugin { else if (kickIntervalTicks > 0) { new PartyAutoKickTask().runTaskTimer(this, kickIntervalTicks, kickIntervalTicks); } + + // Update power level tag scoreboards + new PowerLevelUpdatingTask().runTaskTimer(this, 2 * Misc.TICK_CONVERSION_FACTOR, 2 * Misc.TICK_CONVERSION_FACTOR); } private void checkModConfigs() { diff --git a/src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandAsyncTask.java b/src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandAsyncTask.java index 64326e9a2..a2930cf4b 100644 --- a/src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandAsyncTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandAsyncTask.java @@ -2,25 +2,34 @@ package com.gmail.nossr50.runnables.commands; import java.util.Map; +import org.apache.commons.lang.Validate; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.datatypes.skills.SkillType; public class McrankCommandAsyncTask extends BukkitRunnable { private final String playerName; private final CommandSender sender; + private final boolean useBoard, useChat; - public McrankCommandAsyncTask(String playerName, CommandSender sender) { + public McrankCommandAsyncTask(String playerName, CommandSender sender, boolean useBoard, boolean useChat) { + Validate.isTrue(useBoard || useChat, "Attempted to start a rank retrieval with both board and chat off"); + Validate.notNull(sender, "Attempted to start a rank retrieval with no recipient"); + if (useBoard) Validate.isTrue(sender instanceof Player, "Attempted to start a rank retrieval displaying scoreboard to a non-player"); this.playerName = playerName; this.sender = sender; + this.useBoard = useBoard; + this.useChat = useChat; } @Override public void run() { - Map skills = mcMMO.getDatabaseManager().readRank(playerName); + Map skills = mcMMO.getDatabaseManager().readRank(playerName); - new McrankCommandDisplayTask(skills, sender, playerName).runTaskLater(mcMMO.p, 1); + new McrankCommandDisplayTask(skills, sender, playerName, useBoard, useChat).runTaskLater(mcMMO.p, 1); } } diff --git a/src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandDisplayTask.java b/src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandDisplayTask.java index 0ae99c30c..656582758 100644 --- a/src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandDisplayTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandDisplayTask.java @@ -10,20 +10,37 @@ import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.datatypes.skills.SkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.scoreboards.ScoreboardManager; +import com.gmail.nossr50.util.skills.SkillUtils; +/** + * Display the results of McrankCommandAsyncTask to the sender. + */ public class McrankCommandDisplayTask extends BukkitRunnable { - private final Map skills; + private final Map skills; private final CommandSender sender; private final String playerName; + private final boolean useBoard, useChat; - public McrankCommandDisplayTask(Map skills, CommandSender sender, String playerName) { + /*package-private*/ McrankCommandDisplayTask(Map skills, CommandSender sender, String playerName, boolean useBoard, boolean useChat) { this.skills = skills; this.sender = sender; this.playerName = playerName; + this.useBoard = useBoard; + this.useChat = useChat; } @Override public void run() { + if (useBoard) { + displayBoard(); + } + if (useChat){ + displayChat(); + } + } + + private void displayChat() { Player player = mcMMO.p.getServer().getPlayerExact(playerName); Integer rank; @@ -35,11 +52,20 @@ public class McrankCommandDisplayTask extends BukkitRunnable { continue; } - rank = skills.get(skill.name()); + rank = skills.get(skill); sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Skill", skill.getSkillName(), (rank == null ? LocaleLoader.getString("Commands.mcrank.Unranked") : rank))); } - rank = skills.get("ALL"); + rank = skills.get(null); sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Overall", (rank == null ? LocaleLoader.getString("Commands.mcrank.Unranked") : rank))); } + + public void displayBoard() { + if (playerName == null || sender.getName().equalsIgnoreCase(playerName)) { + ScoreboardManager.showPlayerRankScoreboard((Player) sender, skills); + } + else { + ScoreboardManager.showPlayerRankScoreboardOthers((Player) sender, playerName, skills); + } + } } diff --git a/src/main/java/com/gmail/nossr50/runnables/commands/MctopCommandAsyncTask.java b/src/main/java/com/gmail/nossr50/runnables/commands/MctopCommandAsyncTask.java index 881c881b8..2ddb51de3 100644 --- a/src/main/java/com/gmail/nossr50/runnables/commands/MctopCommandAsyncTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/commands/MctopCommandAsyncTask.java @@ -2,27 +2,36 @@ package com.gmail.nossr50.runnables.commands; import java.util.List; +import org.apache.commons.lang.Validate; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.datatypes.database.PlayerStat; +import com.gmail.nossr50.datatypes.skills.SkillType; public class MctopCommandAsyncTask extends BukkitRunnable { - private CommandSender sender; - private String skill; - private int page; + private final CommandSender sender; + private final SkillType skill; + private final int page; + private final boolean useBoard, useChat; - public MctopCommandAsyncTask(int page, String skill, CommandSender sender) { + public MctopCommandAsyncTask(int page, SkillType skill, CommandSender sender, boolean useBoard, boolean useChat) { + Validate.isTrue(useBoard || useChat, "Attempted to start a rank retrieval with both board and chat off"); + Validate.notNull(sender, "Attempted to start a rank retrieval with no recipient"); + if (useBoard) Validate.isTrue(sender instanceof Player, "Attempted to start a rank retrieval displaying scoreboard to a non-player"); this.page = page; this.skill = skill; this.sender = sender; + this.useBoard = useBoard; + this.useChat = useChat; } @Override public void run() { final List userStats = mcMMO.getDatabaseManager().readLeaderboard(skill, page, 10); - new MctopCommandDisplayTask(userStats, page, skill, sender).runTaskLater(mcMMO.p, 1); + new MctopCommandDisplayTask(userStats, page, skill, sender, useBoard, useChat).runTaskLater(mcMMO.p, 1); } } diff --git a/src/main/java/com/gmail/nossr50/runnables/commands/MctopCommandDisplayTask.java b/src/main/java/com/gmail/nossr50/runnables/commands/MctopCommandDisplayTask.java index 3bfb9afb7..aeaa1eeed 100644 --- a/src/main/java/com/gmail/nossr50/runnables/commands/MctopCommandDisplayTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/commands/MctopCommandDisplayTask.java @@ -4,44 +4,70 @@ import java.util.List; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import com.gmail.nossr50.datatypes.database.PlayerStat; +import com.gmail.nossr50.datatypes.skills.SkillType; import com.gmail.nossr50.locale.LocaleLoader; -import com.gmail.nossr50.util.StringUtils; +import com.gmail.nossr50.util.scoreboards.ScoreboardManager; +import com.gmail.nossr50.util.skills.SkillUtils; +/** + * Display the results of {@link MctopCommandAsyncTask} to the sender. + */ public class MctopCommandDisplayTask extends BukkitRunnable { - private List userStats; - private CommandSender sender; - private String skill; - private int page; + private final List userStats; + private final CommandSender sender; + private final SkillType skill; + private final int page; + private final boolean useBoard, useChat; - public MctopCommandDisplayTask(List userStats, int page, String skill, CommandSender sender) { + /*package-private*/ MctopCommandDisplayTask(List userStats, int page, SkillType skill, CommandSender sender, boolean useBoard, boolean useChat) { this.userStats = userStats; this.page = page; this.skill = skill; this.sender = sender; + this.useBoard = useBoard; + this.useChat = useChat; } @Override public void run() { - if (skill.equalsIgnoreCase("all")) { + if (useBoard) { + displayBoard(); + } + if (useChat) { + displayChat(); + } + sender.sendMessage(LocaleLoader.getString("Commands.mctop.Tip")); + } + + private void displayChat() { + if (skill == null) { sender.sendMessage(LocaleLoader.getString("Commands.PowerLevel.Leaderboard")); } else { - sender.sendMessage(LocaleLoader.getString("Commands.Skill.Leaderboard", StringUtils.getCapitalized(skill))); + sender.sendMessage(LocaleLoader.getString("Commands.Skill.Leaderboard", skill.getSkillName())); } int place = (page * 10) - 9; for (PlayerStat stat : userStats) { - String digit = ((place < 10) ? "0" : "") + String.valueOf(place); - - // Format: 1. Playername - skill value - sender.sendMessage(digit + ". " + ChatColor.GREEN + stat.name + " - " + ChatColor.WHITE + stat.statVal); + // Format: + // 01. Playername - skill value + // 12. Playername - skill value + sender.sendMessage(String.format("%2d. %s%s - %s%s", place, ChatColor.GREEN, stat.name, ChatColor.WHITE, stat.statVal)); place++; } + } - sender.sendMessage(LocaleLoader.getString("Commands.mctop.Tip")); + private void displayBoard() { + if (skill == null) { + ScoreboardManager.showTopPowerScoreboard((Player) sender, page, userStats); + } + else { + ScoreboardManager.showTopScoreboard((Player) sender, skill, page, userStats); + } } } diff --git a/src/main/java/com/gmail/nossr50/runnables/player/PowerLevelUpdatingTask.java b/src/main/java/com/gmail/nossr50/runnables/player/PowerLevelUpdatingTask.java new file mode 100644 index 000000000..fd5c8ec6f --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/player/PowerLevelUpdatingTask.java @@ -0,0 +1,14 @@ +package com.gmail.nossr50.runnables.player; + +import org.bukkit.scheduler.BukkitRunnable; + +import com.gmail.nossr50.util.scoreboards.ScoreboardManager; + +public class PowerLevelUpdatingTask extends BukkitRunnable { + @Override + public void run() { + if (!ScoreboardManager.powerLevelHeartbeat()) { + this.cancel(); + } + } +} diff --git a/src/main/java/com/gmail/nossr50/runnables/scoreboards/ScoreboardChangeTask.java b/src/main/java/com/gmail/nossr50/runnables/scoreboards/ScoreboardChangeTask.java deleted file mode 100644 index 364e4e5e2..000000000 --- a/src/main/java/com/gmail/nossr50/runnables/scoreboards/ScoreboardChangeTask.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.gmail.nossr50.runnables.scoreboards; - -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scoreboard.Scoreboard; - -import com.gmail.nossr50.util.scoreboards.ScoreboardManager; - -public class ScoreboardChangeTask extends BukkitRunnable { - private Player player; - private Scoreboard oldScoreboard; - - public ScoreboardChangeTask(Player player, Scoreboard oldScoreboard) { - this.player = player; - this.oldScoreboard = oldScoreboard; - } - - @Override - public void run() { - if (player.isOnline()) { - player.setScoreboard(oldScoreboard); - ScoreboardManager.enablePowerLevelDisplay(player); - } - - ScoreboardManager.clearPendingTask(player.getName()); - } -} diff --git a/src/main/java/com/gmail/nossr50/util/Misc.java b/src/main/java/com/gmail/nossr50/util/Misc.java index 06cdf771c..1faa6712e 100644 --- a/src/main/java/com/gmail/nossr50/util/Misc.java +++ b/src/main/java/com/gmail/nossr50/util/Misc.java @@ -33,6 +33,7 @@ public final class Misc { public static final int TIME_CONVERSION_FACTOR = 1000; public static final int TICK_CONVERSION_FACTOR = 20; + public static final long PLAYER_DATABASE_COOLDOWN_MILLIS = 1750; public static final int PLAYER_RESPAWN_COOLDOWN_SECONDS = 5; public static final double SKILL_MESSAGE_MAX_SENDING_DISTANCE = 10.0; diff --git a/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java b/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java index c26364013..46fce328e 100644 --- a/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java +++ b/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java @@ -30,6 +30,7 @@ import com.gmail.nossr50.commands.hardcore.VampirismCommand; import com.gmail.nossr50.commands.party.PartyCommand; import com.gmail.nossr50.commands.party.teleport.PtpCommand; import com.gmail.nossr50.commands.player.InspectCommand; +import com.gmail.nossr50.commands.player.MccooldownCommand; import com.gmail.nossr50.commands.player.McrankCommand; import com.gmail.nossr50.commands.player.McstatsCommand; import com.gmail.nossr50.commands.player.MctopCommand; @@ -206,6 +207,15 @@ public final class CommandRegistrationManager { command.setExecutor(new InspectCommand()); } + private static void registerMccooldownCommand() { + PluginCommand command = mcMMO.p.getCommand("mccooldown"); + command.setDescription(LocaleLoader.getString("Commands.Description.mccooldown")); + command.setPermission("mcmmo.commands.mccooldown"); + command.setPermissionMessage(permissionsMessage); + command.setUsage(LocaleLoader.getString("Commands.Usage.0", "mccooldowns")); + command.setExecutor(new MccooldownCommand()); + } + private static void registerMcabilityCommand() { PluginCommand command = mcMMO.p.getCommand("mcability"); command.setDescription(LocaleLoader.getString("Commands.Description.mcability")); @@ -375,8 +385,7 @@ public final class CommandRegistrationManager { command.setDescription("Change the current mcMMO scoreboard being displayed"); //TODO: Localize command.setPermission("mcmmo.commands.mcscoreboard"); command.setPermissionMessage(permissionsMessage); - command.setUsage(LocaleLoader.getString("Commands.Usage.1", "mcscoreboard", "")); - command.setUsage(command.getUsage() + "\n" + LocaleLoader.getString("Commands.Usage.3", "mcscoreboard", "top", "[" + LocaleLoader.getString("Commands.Usage.Skill") + "]", "[" + LocaleLoader.getString("Commands.Usage.Page") + "]")); + command.setUsage(LocaleLoader.getString("Commands.Usage.1", "mcscoreboard", "")); command.setExecutor(new McscoreboardCommand()); } @@ -427,6 +436,7 @@ public final class CommandRegistrationManager { // Player Commands registerInspectCommand(); + registerMccooldownCommand(); registerMcrankCommand(); registerMcstatsCommand(); registerMctopCommand(); diff --git a/src/main/java/com/gmail/nossr50/util/commands/CommandUtils.java b/src/main/java/com/gmail/nossr50/util/commands/CommandUtils.java index 54a0f3d15..18c6f8a9b 100644 --- a/src/main/java/com/gmail/nossr50/util/commands/CommandUtils.java +++ b/src/main/java/com/gmail/nossr50/util/commands/CommandUtils.java @@ -3,6 +3,7 @@ package com.gmail.nossr50.util.commands; import java.util.ArrayList; import java.util.List; +import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -97,10 +98,9 @@ public final class CommandUtils { return true; } - PlayerProfile playerProfile = new PlayerProfile(playerName, false); - - if (unloadedProfile(sender, playerProfile)) { - return false; + OfflinePlayer player = Bukkit.getOfflinePlayer(playerName); + if (!player.hasPlayedBefore()) { + sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist")); } sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist")); diff --git a/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardManager.java b/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardManager.java index e413a0fd0..033042efb 100644 --- a/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardManager.java +++ b/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardManager.java @@ -1,328 +1,387 @@ package com.gmail.nossr50.util.scoreboards; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Random; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; -import org.bukkit.Server; +import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.scoreboard.DisplaySlot; import org.bukkit.scoreboard.Objective; -import org.bukkit.scoreboard.Scoreboard; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.config.Config; import com.gmail.nossr50.datatypes.database.PlayerStat; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.datatypes.skills.AbilityType; import com.gmail.nossr50.datatypes.skills.SkillType; import com.gmail.nossr50.locale.LocaleLoader; -import com.gmail.nossr50.runnables.scoreboards.ScoreboardChangeTask; import com.gmail.nossr50.util.Misc; -import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; +import com.google.common.collect.ImmutableMap; public class ScoreboardManager { - private static final Map PLAYER_SCOREBOARDS = new HashMap(); - private static final Scoreboard GLOBAL_STATS_SCOREBOARD = mcMMO.p.getServer().getScoreboardManager().getNewScoreboard(); + static final Map PLAYER_SCOREBOARDS = new HashMap(); - private final static String PLAYER_STATS_HEADER = LocaleLoader.getString("Scoreboard.Header.PlayerStats"); - private final static String PLAYER_RANK_HEADER = LocaleLoader.getString("Scoreboard.Header.PlayerRank"); - private final static String PLAYER_INSPECT_HEADER = LocaleLoader.getString("Scoreboard.Header.PlayerInspect"); - private final static String POWER_LEVEL_HEADER = LocaleLoader.getString("Scoreboard.Header.PowerLevel"); + // do not localize; these are internal identifiers + static final String SIDEBAR_OBJECTIVE = "mcmmo_sidebar"; + static final String POWER_OBJECTIVE = "mcmmo_pwrlvl"; - private final static String POWER_LEVEL = LocaleLoader.getString("Scoreboard.Misc.PowerLevel"); - private final static String LEVEL = LocaleLoader.getString("Scoreboard.Misc.Level"); - private final static String CURRENT_XP = LocaleLoader.getString("Scoreboard.Misc.CurrentXP"); - private final static String REMAINING_XP = LocaleLoader.getString("Scoreboard.Misc.RemainingXP"); - private final static String OVERALL = LocaleLoader.getString("Scoreboard.Misc.Overall"); + static final String HEADER_STATS = LocaleLoader.getString("Scoreboard.Header.PlayerStats"); + static final String HEADER_COOLDOWNS = LocaleLoader.getString("Scoreboard.Header.PlayerCooldowns"); + static final String HEADER_RANK = LocaleLoader.getString("Scoreboard.Header.PlayerRank"); + static final String TAG_POWER_LEVEL = LocaleLoader.getString("Scoreboard.Header.PowerLevel"); - private final static List SCOREBOARD_TASKS = new ArrayList(); + static final String POWER_LEVEL = LocaleLoader.getString("Scoreboard.Misc.PowerLevel"); - public static void setupPlayerScoreboard(String playerName) { - if (PLAYER_SCOREBOARDS.containsKey(playerName)) { - return; - } + static final OfflinePlayer LABEL_POWER_LEVEL = getOfflinePlayer(POWER_LEVEL); + static final OfflinePlayer LABEL_LEVEL = getOfflinePlayer(LocaleLoader.getString("Scoreboard.Misc.Level")); + static final OfflinePlayer LABEL_CURRENT_XP = getOfflinePlayer(LocaleLoader.getString("Scoreboard.Misc.CurrentXP")); + static final OfflinePlayer LABEL_REMAINING_XP = getOfflinePlayer(LocaleLoader.getString("Scoreboard.Misc.RemainingXP")); + static final OfflinePlayer LABEL_ABILITY_COOLDOWN = getOfflinePlayer(LocaleLoader.getString("Scoreboard.Misc.Cooldown")); + static final OfflinePlayer LABEL_OVERALL = getOfflinePlayer(LocaleLoader.getString("Scoreboard.Misc.Overall")); - PLAYER_SCOREBOARDS.put(playerName, mcMMO.p.getServer().getScoreboardManager().getNewScoreboard()); - } - - public static void enablePowerLevelDisplay(Player player) { - if (!Config.getInstance().getPowerLevelsEnabled()) { - return; - } - - Scoreboard scoreboard = player.getScoreboard(); - Objective objective; - - if (scoreboard.getObjective(DisplaySlot.BELOW_NAME) == null) { - objective = scoreboard.registerNewObjective(POWER_LEVEL_HEADER.substring(0, Math.min(POWER_LEVEL_HEADER.length(), 16)), "dummy"); - - objective.getScore(player).setScore(UserManager.getPlayer(player).getPowerLevel()); - objective.setDisplaySlot(DisplaySlot.BELOW_NAME); + static final Map skillLabels; + static final Map abilityLabelsColored; + static final Map abilityLabelsSkill; + static { + ImmutableMap.Builder b = ImmutableMap.builder(); + ImmutableMap.Builder c = ImmutableMap.builder(); + ImmutableMap.Builder d = ImmutableMap.builder(); + if (Config.getInstance().getScoreboardRainbows()) { + Random shuffler = new Random(Bukkit.getWorlds().get(0).getSeed()); + List colors = Arrays.asList( + ChatColor.WHITE, + ChatColor.YELLOW, + ChatColor.LIGHT_PURPLE, + ChatColor.RED, + ChatColor.AQUA, + ChatColor.GREEN, + ChatColor.DARK_GRAY, + ChatColor.BLUE, + ChatColor.DARK_PURPLE, + ChatColor.DARK_RED, + ChatColor.DARK_AQUA, + ChatColor.DARK_GREEN, + ChatColor.DARK_BLUE + ); + Collections.shuffle(colors, shuffler); + int i = 0; + for (SkillType type : SkillType.values()) { + // Include child skills + b.put(type, getOfflinePlayer(colors.get(i) + type.getSkillName())); + if (type.getAbility() != null) { + // the toString is the properly formatted verison for abilities + c.put(type.getAbility(), getOfflinePlayer(colors.get(i) + type.getAbility().getAbilityName())); + if (type == SkillType.MINING) { + c.put(AbilityType.BLAST_MINING, getOfflinePlayer(colors.get(i) + AbilityType.BLAST_MINING.getAbilityName())); + } + } + if (++i == colors.size()) i = 0; + } } else { - objective = scoreboard.getObjective(POWER_LEVEL_HEADER.substring(0, Math.min(POWER_LEVEL_HEADER.length(), 16))); + for (SkillType type : SkillType.values()) { + // Include child skills + b.put(type, getOfflinePlayer(ChatColor.GREEN + type.getSkillName())); + if (type.getAbility() != null) { + // the toString is the properly formatted verison for abilities + c.put(type.getAbility(), getOfflinePlayerDots(ChatColor.AQUA + type.getAbility().getAbilityName())); + if (type == SkillType.MINING) { + c.put(AbilityType.BLAST_MINING, getOfflinePlayerDots(ChatColor.AQUA + AbilityType.BLAST_MINING.getAbilityName())); + } + } + } + } - if (objective != null) { - objective.getScore(player).setScore(UserManager.getPlayer(player).getPowerLevel()); + for (AbilityType type : AbilityType.NORMAL_ABILITIES) { + if (type == AbilityType.BLAST_MINING) { + // Special-case: get a different color + d.put(AbilityType.BLAST_MINING, getOfflinePlayerDots(ChatColor.BLUE + AbilityType.BLAST_MINING.getAbilityName())); } else { - mcMMO.p.debug("Another plugin is using this scoreboard slot, so power levels cannot be enabled."); //TODO: Locale + d.put(type, getOfflinePlayerDots(ChatColor.AQUA + type.getAbilityName())); } } + + skillLabels = b.build(); + abilityLabelsColored = c.build(); + abilityLabelsSkill = d.build(); } - public static void enablePlayerSkillScoreboard(McMMOPlayer mcMMOPlayer, SkillType skill) { - Player player = mcMMOPlayer.getPlayer(); - Scoreboard oldScoreboard = player.getScoreboard(); - Scoreboard newScoreboard = PLAYER_SCOREBOARDS.get(player.getName()); - Objective objective = newScoreboard.getObjective(skill.getSkillName()); + private static List dirtyPowerLevels = new ArrayList(); - if (objective == null) { - objective = newScoreboard.registerNewObjective(skill.getSkillName(), "dummy"); + private static OfflinePlayer getOfflinePlayer(String name) { + if (name.length() > 16) { + name = name.substring(0, 16); + } + return Bukkit.getOfflinePlayer(name); + } + + private static OfflinePlayer getOfflinePlayerDots(String name) { + if (name.length() > 16) { + name = name.substring(0, 16 - 2) + ".."; + } + return Bukkit.getOfflinePlayer(name); + } + + public enum SidebarType { + NONE, + SKILL_BOARD, + STATS_BOARD, + COOLDOWNS_BOARD, + RANK_BOARD, + TOP_BOARD; + } + + // **** Listener call-ins **** // + + // Called by PlayerJoinEvent listener + public static void setupPlayer(Player p) { + PLAYER_SCOREBOARDS.put(p.getName(), ScoreboardWrapper.create(p)); + dirtyPowerLevels.add(p.getName()); + } + + // Called by PlayerQuitEvent listener + public static void teardownPlayer(Player p) { + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.remove(p.getName()); + if (wrapper.revertTask != null) { + wrapper.revertTask.cancel(); + } + } + + // Called in onDisable() + public static void teardownAll() { + for (Player p : Bukkit.getOnlinePlayers()) { + teardownPlayer(p); + } + } + + // Called by ScoreboardWrapper when its Player logs off and an action tries to be performed + public static void cleanup(ScoreboardWrapper wrapper) { + PLAYER_SCOREBOARDS.remove(wrapper.playerName); + if (wrapper.revertTask != null) { + wrapper.revertTask.cancel(); + } + } + + // Called by internal level-up event listener + public static void handleLevelUp(Player player, SkillType skill) { + // Selfboards + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(player.getName()); + if ((wrapper.isSkillScoreboard() && wrapper.targetSkill == skill) || (wrapper.isStatsScoreboard()) && wrapper.isBoardShown()) { + wrapper.doSidebarUpdateSoon(); } - updatePlayerSkillScores(mcMMOPlayer.getProfile(), skill, objective); - changeScoreboard(player, oldScoreboard, newScoreboard, Config.getInstance().getSkillScoreboardTime()); - } - - public static void enablePlayerStatsScoreboard(McMMOPlayer mcMMOPlayer) { - Player player = mcMMOPlayer.getPlayer(); - Scoreboard oldScoreboard = player.getScoreboard(); - Scoreboard newScoreboard = PLAYER_SCOREBOARDS.get(player.getName()); - Objective objective = newScoreboard.getObjective(PLAYER_STATS_HEADER.substring(0, Math.min(PLAYER_STATS_HEADER.length(), 16))); - - if (objective == null) { - objective = newScoreboard.registerNewObjective(PLAYER_STATS_HEADER.substring(0, Math.min(PLAYER_STATS_HEADER.length(), 16)), "dummy"); - } - - updatePlayerStatsScores(mcMMOPlayer, objective); - changeScoreboard(player, oldScoreboard, newScoreboard, Config.getInstance().getMcstatsScoreboardTime()); - } - - public static void enablePlayerRankScoreboard(Player player) { - Scoreboard oldScoreboard = player.getScoreboard(); - Scoreboard newScoreboard = PLAYER_SCOREBOARDS.get(player.getName()); - Objective objective = newScoreboard.getObjective(PLAYER_RANK_HEADER.substring(0, Math.min(PLAYER_RANK_HEADER.length(), 16))); - - if (objective == null) { - objective = newScoreboard.registerNewObjective(PLAYER_RANK_HEADER.substring(0, Math.min(PLAYER_RANK_HEADER.length(), 16)), "dummy"); - } - - updatePlayerRankScores(player, objective); - changeScoreboard(player, oldScoreboard, newScoreboard, Config.getInstance().getMcrankScoreboardTime()); - } - - public static void enablePlayerRankScoreboardOthers(Player player, String targetName) { - Scoreboard oldScoreboard = player.getScoreboard(); - Scoreboard newScoreboard = PLAYER_SCOREBOARDS.get(player.getName()); - Objective objective = newScoreboard.getObjective(PLAYER_RANK_HEADER.substring(0, Math.min(PLAYER_RANK_HEADER.length(), 16))); - - if (objective == null) { - objective = newScoreboard.registerNewObjective(PLAYER_RANK_HEADER.substring(0, Math.min(PLAYER_RANK_HEADER.length(), 16)), "dummy"); - } - - updatePlayerRankOthersScores(targetName, objective); - changeScoreboard(player, oldScoreboard, newScoreboard, Config.getInstance().getMcrankScoreboardTime()); - } - - public static void enablePlayerInspectScoreboardOnline(Player player, McMMOPlayer mcMMOTarget) { - Scoreboard oldScoreboard = player.getScoreboard(); - Scoreboard newScoreboard = PLAYER_SCOREBOARDS.get(player.getName()); - Objective objective = newScoreboard.getObjective(PLAYER_INSPECT_HEADER.substring(0, Math.min(PLAYER_INSPECT_HEADER.length(), 16))); - - if (objective == null) { - objective = newScoreboard.registerNewObjective(PLAYER_INSPECT_HEADER.substring(0, Math.min(PLAYER_INSPECT_HEADER.length(), 16)), "dummy"); - } - - updatePlayerInspectOnlineScores(mcMMOTarget, objective); - changeScoreboard(player, oldScoreboard, newScoreboard, Config.getInstance().getInspectScoreboardTime()); - } - - public static void enablePlayerInspectScoreboardOffline(Player player, PlayerProfile targetProfile) { - Scoreboard oldScoreboard = player.getScoreboard(); - Scoreboard newScoreboard = PLAYER_SCOREBOARDS.get(player.getName()); - Objective objective = newScoreboard.getObjective(PLAYER_INSPECT_HEADER.substring(0, Math.min(PLAYER_INSPECT_HEADER.length(), 16))); - - if (objective == null) { - objective = newScoreboard.registerNewObjective(PLAYER_INSPECT_HEADER.substring(0, Math.min(PLAYER_INSPECT_HEADER.length(), 16)), "dummy"); - } - - updatePlayerInspectOfflineScores(targetProfile, objective); - changeScoreboard(player, oldScoreboard, newScoreboard, Config.getInstance().getInspectScoreboardTime()); - } - - public static void enableGlobalStatsScoreboard(Player player, String skillName, int pageNumber) { - Objective oldObjective = GLOBAL_STATS_SCOREBOARD.getObjective(skillName); - Scoreboard oldScoreboard = player.getScoreboard(); - - if (oldObjective != null) { - oldObjective.unregister(); - } - - Objective newObjective = GLOBAL_STATS_SCOREBOARD.registerNewObjective(skillName, "dummy"); - newObjective.setDisplayName(ChatColor.GOLD + (skillName.equalsIgnoreCase("all") ? POWER_LEVEL : SkillType.getSkill(skillName).getSkillName())); - - updateGlobalStatsScores(player, newObjective, skillName, pageNumber); - changeScoreboard(player, oldScoreboard, GLOBAL_STATS_SCOREBOARD, Config.getInstance().getMctopScoreboardTime()); - } - - private static void updatePlayerSkillScores(PlayerProfile profile, SkillType skill, Objective objective) { - Server server = mcMMO.p.getServer(); - int currentXP = profile.getSkillXpLevel(skill); - - objective.getScore(server.getOfflinePlayer(LEVEL)).setScore(profile.getSkillLevel(skill)); - objective.getScore(server.getOfflinePlayer(CURRENT_XP)).setScore(currentXP); - objective.getScore(server.getOfflinePlayer(REMAINING_XP)).setScore(profile.getXpToLevel(skill) - currentXP); - - objective.setDisplaySlot(DisplaySlot.SIDEBAR); - } - - private static void updatePlayerStatsScores(McMMOPlayer mcMMOPlayer, Objective objective) { - Player player = mcMMOPlayer.getPlayer(); - PlayerProfile profile = mcMMOPlayer.getProfile(); - Server server = mcMMO.p.getServer(); - - for (SkillType skill : SkillType.NON_CHILD_SKILLS) { - if (!Permissions.skillEnabled(player, skill)) { - continue; - } - - objective.getScore(server.getOfflinePlayer(skill.getSkillName())).setScore(profile.getSkillLevel(skill)); - } - - objective.getScore(server.getOfflinePlayer(ChatColor.GOLD + POWER_LEVEL)).setScore(mcMMOPlayer.getPowerLevel()); - objective.setDisplaySlot(DisplaySlot.SIDEBAR); - } - - private static void updatePlayerRankScores(Player player, Objective objective) { + // Otherboards String playerName = player.getName(); - Server server = mcMMO.p.getServer(); - Integer rank; - - Map skills = mcMMO.getDatabaseManager().readRank(playerName); - - for (SkillType skill : SkillType.NON_CHILD_SKILLS) { - if (!Permissions.skillEnabled(player, skill)) { - continue; - } - - rank = skills.get(skill.name()); - - if (rank != null) { - objective.getScore(server.getOfflinePlayer(skill.getSkillName())).setScore(rank); + for (ScoreboardWrapper w : PLAYER_SCOREBOARDS.values()) { + if (w.isStatsScoreboard() && playerName.equals(w.targetPlayer) && wrapper.isBoardShown()) { + wrapper.doSidebarUpdateSoon(); } } - rank = skills.get("ALL"); - - if (rank != null) { - objective.getScore(server.getOfflinePlayer(ChatColor.GOLD + OVERALL)).setScore(rank); + if (Config.getInstance().getPowerLevelTagsEnabled()) { + dirtyPowerLevels.add(player.getName()); } - objective.setDisplaySlot(DisplaySlot.SIDEBAR); + if (Config.getInstance().getSkillLevelUpBoard()) { + enablePlayerSkillLevelUpScoreboard(player, skill); + } } - private static void updatePlayerRankOthersScores(String targetName, Objective objective) { - Server server = mcMMO.p.getServer(); - Integer rank; + // Called by internal xp event listener + public static void handleXp(Player player, SkillType skill) { + // Selfboards + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(player.getName()); + if (wrapper.isSkillScoreboard() && wrapper.targetSkill == skill && wrapper.isBoardShown()) { + wrapper.doSidebarUpdateSoon(); + } + } - Map skills = mcMMO.getDatabaseManager().readRank(targetName); + // Called by internal ability event listeners + public static void cooldownUpdate(Player player, SkillType skill, int cooldownSeconds) { + // Selfboards + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(player.getName()); + if ((wrapper.isCooldownScoreboard() || wrapper.isSkillScoreboard() && wrapper.targetSkill == skill) && wrapper.isBoardShown()) { + wrapper.doSidebarUpdateSoon(); + } + } - for (SkillType skill : SkillType.NON_CHILD_SKILLS) { - rank = skills.get(skill.name()); + // **** Setup methods **** // - if (rank != null) { - objective.getScore(server.getOfflinePlayer(skill.getSkillName())).setScore(rank); + public static void enablePlayerSkillScoreboard(Player player, SkillType skill) { + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(player.getName()); + + wrapper.setOldScoreboard(); + wrapper.setTypeSkill(skill); + + changeScoreboard(wrapper, Config.getInstance().getSkillScoreboardTime()); + } + + public static void enablePlayerSkillLevelUpScoreboard(Player player, SkillType skill) { + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(player.getName()); + + // Do NOT run if already shown + if (wrapper.isBoardShown()) { + return; + } + + wrapper.setOldScoreboard(); + wrapper.setTypeSkill(skill); + + changeScoreboard(wrapper, Config.getInstance().getSkillLevelUpTime()); + } + + public static void enablePlayerStatsScoreboard(Player player) { + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(player.getName()); + + wrapper.setOldScoreboard(); + wrapper.setTypeSelfStats(); + + changeScoreboard(wrapper, Config.getInstance().getStatsScoreboardTime()); + } + + public static void enablePlayerInspectScoreboard(Player player, PlayerProfile targetProfile) { + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(player.getName()); + + wrapper.setOldScoreboard(); + wrapper.setTypeInspectStats(targetProfile); + + changeScoreboard(wrapper, Config.getInstance().getInspectScoreboardTime()); + } + + public static void enablePlayerCooldownScoreboard(Player player) { + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(player.getName()); + + wrapper.setOldScoreboard(); + wrapper.setTypeCooldowns(); + + changeScoreboard(wrapper, Config.getInstance().getCooldownScoreboardTime()); + } + + public static void showPlayerRankScoreboard(Player bukkitPlayer, Map rank) { + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(bukkitPlayer.getName()); + + wrapper.setOldScoreboard(); + wrapper.setTypeSelfRank(); + wrapper.acceptRankData(rank); + + changeScoreboard(wrapper, Config.getInstance().getRankScoreboardTime()); + } + + public static void showPlayerRankScoreboardOthers(Player bukkitPlayer, String targetName, Map rank) { + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(bukkitPlayer.getName()); + + wrapper.setOldScoreboard(); + wrapper.setTypeInspectRank(targetName); + wrapper.acceptRankData(rank); + + changeScoreboard(wrapper, Config.getInstance().getRankScoreboardTime()); + } + + public static void showTopScoreboard(Player player, SkillType skill, int pageNumber, List stats) { + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(player.getName()); + + wrapper.setOldScoreboard(); + wrapper.setTypeTop(skill, pageNumber); + wrapper.acceptLeaderboardData(stats); + + changeScoreboard(wrapper, Config.getInstance().getTopScoreboardTime()); + } + + public static void showTopPowerScoreboard(Player player, int pageNumber, List stats) { + ScoreboardWrapper wrapper = PLAYER_SCOREBOARDS.get(player.getName()); + + wrapper.setOldScoreboard(); + wrapper.setTypeTopPower(pageNumber); + wrapper.acceptLeaderboardData(stats); + + changeScoreboard(wrapper, Config.getInstance().getTopScoreboardTime()); + } + + // **** Helper methods **** // + + /** + * @return false if power levels are disabled + */ + public static boolean powerLevelHeartbeat() { + Objective mainObjective = getPowerLevelObjective(); + if (mainObjective == null) { + return false; // indicates + } + + if (!dirtyPowerLevels.isEmpty()) + mcMMO.p.getLogger().info(dirtyPowerLevels.toString()); + for (String playerName : dirtyPowerLevels) { + McMMOPlayer mcpl = UserManager.getPlayer(playerName); + Player player = mcpl.getPlayer(); + int power = mcpl.getPowerLevel(); + + mainObjective.getScore(player).setScore(power); + for (ScoreboardWrapper wrapper : PLAYER_SCOREBOARDS.values()) { + wrapper.updatePowerLevel(player, power); } } + dirtyPowerLevels.clear(); - rank = skills.get("ALL"); - - if (rank != null) { - objective.getScore(server.getOfflinePlayer(ChatColor.GOLD + OVERALL)).setScore(rank); - } - - objective.setDisplayName(PLAYER_RANK_HEADER + ": " + targetName); - objective.setDisplaySlot(DisplaySlot.SIDEBAR); + return true; } - private static void updatePlayerInspectOnlineScores(McMMOPlayer mcMMOTarget, Objective objective) { - Player target = mcMMOTarget.getPlayer(); - PlayerProfile profile = mcMMOTarget.getProfile(); - Server server = mcMMO.p.getServer(); - int powerLevel = 0; - int skillLevel; - - for (SkillType skill : SkillType.NON_CHILD_SKILLS) { - if (!Permissions.skillEnabled(target, skill)) { - continue; + /** + * Gets or creates the power level objective on the main scoreboard. + *

+ * If power levels are disabled, the objective is deleted and null is + * returned. + * + * @return the main scoreboard objective, or null if disabled + */ + public static Objective getPowerLevelObjective() { + if (!Config.getInstance().getPowerLevelTagsEnabled()) { + Objective obj = Bukkit.getScoreboardManager().getMainScoreboard().getObjective(POWER_OBJECTIVE); + if (obj != null) { + obj.unregister(); + mcMMO.p.debug("Removed leftover scoreboard objects from Power Level Tags."); } - - skillLevel = profile.getSkillLevel(skill); - objective.getScore(server.getOfflinePlayer(skill.getSkillName())).setScore(skillLevel); - powerLevel += skillLevel; + return null; } - objective.getScore(server.getOfflinePlayer(ChatColor.GOLD + POWER_LEVEL)).setScore(powerLevel); - objective.setDisplayName(PLAYER_INSPECT_HEADER + target.getName()); - objective.setDisplaySlot(DisplaySlot.SIDEBAR); - } - - private static void updatePlayerInspectOfflineScores(PlayerProfile targetProfile, Objective objective) { - Server server = mcMMO.p.getServer(); - int powerLevel = 0; - int skillLevel; - - for (SkillType skill : SkillType.NON_CHILD_SKILLS) { - skillLevel = targetProfile.getSkillLevel(skill); - objective.getScore(server.getOfflinePlayer(skill.getSkillName())).setScore(skillLevel); - powerLevel += skillLevel; + Objective powerObj = Bukkit.getScoreboardManager().getMainScoreboard().getObjective(POWER_OBJECTIVE); + if (powerObj == null) { + powerObj = Bukkit.getScoreboardManager().getMainScoreboard().registerNewObjective(POWER_OBJECTIVE, "dummy"); + powerObj.setDisplayName(TAG_POWER_LEVEL); + powerObj.setDisplaySlot(DisplaySlot.BELOW_NAME); } - - objective.getScore(server.getOfflinePlayer(ChatColor.GOLD + POWER_LEVEL)).setScore(powerLevel); - objective.setDisplayName(PLAYER_INSPECT_HEADER + targetProfile.getPlayerName()); + return powerObj; } - private static void updateGlobalStatsScores(Player player, Objective objective, String skillName, int pageNumber) { - int position = (pageNumber * 15) - 14; - String startPosition = ((position < 10) ? "0" : "") + String.valueOf(position); - String endPosition = String.valueOf(position + 14); - Server server = mcMMO.p.getServer(); - - for (PlayerStat stat : mcMMO.getDatabaseManager().readLeaderboard(skillName, pageNumber, 15)) { - String playerName = stat.name; - playerName = (playerName.equals(player.getName()) ? ChatColor.GOLD : "") + playerName; - - if (playerName.length() > 16) { - playerName = playerName.substring(0, 16); - } - - objective.getScore(server.getOfflinePlayer(playerName)).setScore(stat.statVal); + private static void changeScoreboard(ScoreboardWrapper wrapper, int displayTime) { + if (displayTime == -1) { + wrapper.showBoardWithNoRevert(); } - - objective.setDisplayName(objective.getDisplayName() + " (" + startPosition + " - " + endPosition + ")"); - objective.setDisplaySlot(DisplaySlot.SIDEBAR); - } - - private static void changeScoreboard(Player player, Scoreboard oldScoreboard, Scoreboard newScoreboard, int displayTime) { - if (oldScoreboard != newScoreboard) { - String playerName = player.getName(); - - player.setScoreboard(newScoreboard); - enablePowerLevelDisplay(player); - - if (displayTime != -1 && !SCOREBOARD_TASKS.contains(playerName)) { - new ScoreboardChangeTask(player, oldScoreboard).runTaskLater(mcMMO.p, displayTime * Misc.TICK_CONVERSION_FACTOR); - SCOREBOARD_TASKS.add(playerName); - } + else { + wrapper.showBoardAndScheduleRevert(displayTime * Misc.TICK_CONVERSION_FACTOR); } } - public static void clearPendingTask(String playerName) { - SCOREBOARD_TASKS.remove(playerName); + public static void clearBoard(String playerName) { + PLAYER_SCOREBOARDS.get(playerName).tryRevertBoard(); + } + + public static void keepBoard(String playerName) { + if (Config.getInstance().getAllowKeepBoard()) { + PLAYER_SCOREBOARDS.get(playerName).cancelRevert(); + } + } + + public static void setRevertTimer(String playerName, int seconds) { + PLAYER_SCOREBOARDS.get(playerName).showBoardAndScheduleRevert(seconds * Misc.TICK_CONVERSION_FACTOR);; } } diff --git a/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java b/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java new file mode 100644 index 000000000..ded2fd1fd --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java @@ -0,0 +1,543 @@ +package com.gmail.nossr50.util.scoreboards; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.Score; +import org.bukkit.scoreboard.Scoreboard; + +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.config.Config; +import com.gmail.nossr50.datatypes.database.PlayerStat; +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.datatypes.skills.AbilityType; +import com.gmail.nossr50.datatypes.skills.SkillType; +import com.gmail.nossr50.locale.LocaleLoader; +import com.gmail.nossr50.skills.child.FamilyTree; +import com.gmail.nossr50.util.Misc; +import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.scoreboards.ScoreboardManager.SidebarType; +import com.gmail.nossr50.util.skills.SkillUtils; + +public class ScoreboardWrapper { + + // Initialization variables + public final String playerName; + private final Scoreboard board; + private boolean tippedKeep = false; + private boolean tippedClear = false; + + // Internal usage variables (should exist) + private SidebarType sidebarType; + private Objective sidebarObj; + private Objective powerObj; + + // Parameter variables (May be null / invalid) + private Scoreboard oldBoard = null; + public String targetPlayer = null; + public SkillType targetSkill = null; + private PlayerProfile targetProfile = null; + public int leaderboardPage = -1; + + private ScoreboardWrapper(String playerName, Scoreboard s) { + this.playerName = playerName; + board = s; + sidebarType = SidebarType.NONE; + sidebarObj = board.registerNewObjective(ScoreboardManager.SIDEBAR_OBJECTIVE, "dummy"); + powerObj = board.registerNewObjective(ScoreboardManager.POWER_OBJECTIVE, "dummy"); + if (Config.getInstance().getPowerLevelTagsEnabled()) { + powerObj.setDisplayName(ScoreboardManager.TAG_POWER_LEVEL); + powerObj.setDisplaySlot(DisplaySlot.BELOW_NAME); + for (McMMOPlayer mcpl : UserManager.getPlayers()) { + powerObj.getScore(mcpl.getPlayer()).setScore(mcpl.getPowerLevel()); + } + } + } + + public static ScoreboardWrapper create(Player p) { + return new ScoreboardWrapper(p.getName(), mcMMO.p.getServer().getScoreboardManager().getNewScoreboard()); + } + + public BukkitTask updateTask = null; + private class ScoreboardQuickUpdate extends BukkitRunnable { + @Override + public void run() { + ScoreboardWrapper.this.updateSidebar(); + updateTask = null; + } + } + + public BukkitTask revertTask = null; + private class ScoreboardChangeTask extends BukkitRunnable { + @Override + public void run() { + ScoreboardWrapper.this.tryRevertBoard(); + revertTask = null; + } + } + + public BukkitTask cooldownTask = null; + private class ScoreboardCooldownTask extends BukkitRunnable { + @Override + public void run() { + ScoreboardWrapper wrapper = ScoreboardWrapper.this; + // Stop updating if it's no longer something displaying cooldowns + if (wrapper.isBoardShown() && (wrapper.isSkillScoreboard() || wrapper.isCooldownScoreboard())) { + wrapper.doSidebarUpdateSoon(); + } + else { + wrapper.stopCooldownUpdating(); + } + } + } + + + public void doSidebarUpdateSoon() { + if (updateTask == null) { + // To avoid spamming the scheduler, store the instance and run 2 ticks later + updateTask = new ScoreboardQuickUpdate().runTaskLater(mcMMO.p, 2L); + } + } + + private void startCooldownUpdating() { + if (cooldownTask == null) { + // Repeat every 5 seconds. + // Cancels once all cooldowns are done, using stopCooldownUpdating(). + cooldownTask = new ScoreboardCooldownTask().runTaskTimer(mcMMO.p, 5 * Misc.TICK_CONVERSION_FACTOR, 5 * Misc.TICK_CONVERSION_FACTOR); + } + } + + private void stopCooldownUpdating() { + if (cooldownTask != null) { + try { + cooldownTask.cancel(); + } catch (Throwable ignored) {} + cooldownTask = null; + } + } + + public boolean isSkillScoreboard() { + return sidebarType == SidebarType.SKILL_BOARD; + } + + public boolean isCooldownScoreboard() { + return sidebarType == SidebarType.COOLDOWNS_BOARD; + } + + public boolean isStatsScoreboard() { + return sidebarType == SidebarType.STATS_BOARD; + } + + /** + * Set the old scoreboard, for use in reverting. + */ + public void setOldScoreboard() { + Player player = Bukkit.getPlayerExact(playerName); + if (player == null) { + ScoreboardManager.cleanup(this); + return; + } + + Scoreboard old = player.getScoreboard(); + if (old == board) { // Already displaying it + if (oldBoard == null) { + // (Shouldn't happen) Use failsafe value - we're already displaying our board, but we don't have the one we should revert to + oldBoard = Bukkit.getScoreboardManager().getMainScoreboard(); + } + else { + // Do nothing, we already have a prev board + } + } + else { + oldBoard = old; + } + } + + public void showBoardWithNoRevert() { + Player player = Bukkit.getPlayerExact(playerName); + if (player == null) { + ScoreboardManager.cleanup(this); + return; + } + + if (revertTask != null) { + revertTask.cancel(); + } + player.setScoreboard(board); + revertTask = null; + } + + public void showBoardAndScheduleRevert(int ticks) { + Player player = Bukkit.getPlayerExact(playerName); + if (player == null) { + ScoreboardManager.cleanup(this); + return; + } + + if (revertTask != null) { + revertTask.cancel(); + } + player.setScoreboard(board); + revertTask = new ScoreboardChangeTask().runTaskLater(mcMMO.p, ticks); + + // TODO is there any way to do the time that looks acceptable? + // player.sendMessage(LocaleLoader.getString("Commands.Scoreboard.Timer", StringUtils.capitalize(sidebarType.toString().toLowerCase()), ticks / 20F)); + if (!tippedKeep) { + tippedKeep = true; + player.sendMessage(LocaleLoader.getString("Commands.Scoreboard.Tip.Keep")); + } + else if (!tippedClear) { + tippedClear = true; + player.sendMessage(LocaleLoader.getString("Commands.Scoreboard.Tip.Clear")); + } + } + + public void tryRevertBoard() { + Player player = Bukkit.getPlayerExact(playerName); + if (player == null) { + ScoreboardManager.cleanup(this); + return; + } + + if (oldBoard != null) { + if (player.getScoreboard() == board) { + player.setScoreboard(oldBoard); + oldBoard = null; + } + else { + mcMMO.p.debug("Not reverting scoreboard for " + playerName + " - scoreboard was changed by another plugin (Consider disabling the mcMMO scoreboards if you don't want them!)"); + } + } + else { + // Was already reverted + } + + if (revertTask != null) { + revertTask.cancel(); + revertTask = null; + } + sidebarType = SidebarType.NONE; + targetPlayer = null; + targetSkill = null; + targetProfile = null; + leaderboardPage = -1; + } + + public boolean isBoardShown() { + Player player = Bukkit.getPlayerExact(playerName); + if (player == null) { + ScoreboardManager.cleanup(this); + return false; + } + + return player.getScoreboard() == board; + } + + public void cancelRevert() { + if (revertTask != null) { + revertTask.cancel(); + } + revertTask = null; + } + + // Board Type Changing 'API' methods + + public void setTypeNone() { + this.sidebarType = SidebarType.NONE; + + targetPlayer = null; + targetSkill = null; + targetProfile = null; + leaderboardPage = -1; + + loadObjective(""); + } + + public void setTypeSkill(SkillType skill) { + this.sidebarType = SidebarType.SKILL_BOARD; + targetSkill = skill; + + targetPlayer = null; + targetProfile = null; + leaderboardPage = -1; + + loadObjective(ScoreboardManager.skillLabels.get(skill).getName()); + } + + public void setTypeSelfStats() { + this.sidebarType = SidebarType.STATS_BOARD; + + targetPlayer = null; + targetSkill = null; + targetProfile = null; + leaderboardPage = -1; + + loadObjective(ScoreboardManager.HEADER_STATS); + } + + public void setTypeInspectStats(PlayerProfile profile) { + this.sidebarType = SidebarType.STATS_BOARD; + targetPlayer = profile.getPlayerName(); + targetProfile = profile; + + targetSkill = null; + leaderboardPage = -1; + + loadObjective(LocaleLoader.getString("Scoreboard.Header.PlayerInspect", targetPlayer)); + } + + public void setTypeCooldowns() { + this.sidebarType = SidebarType.COOLDOWNS_BOARD; + + targetPlayer = null; + targetSkill = null; + targetProfile = null; + leaderboardPage = -1; + + loadObjective(ScoreboardManager.HEADER_COOLDOWNS); + } + + public void setTypeSelfRank() { + this.sidebarType = SidebarType.RANK_BOARD; + targetPlayer = null; + + targetSkill = null; + targetProfile = null; + leaderboardPage = -1; + + loadObjective(ScoreboardManager.HEADER_RANK); + } + + public void setTypeInspectRank(String otherPlayer) { + this.sidebarType = SidebarType.RANK_BOARD; + targetPlayer = otherPlayer; + + targetSkill = null; + targetProfile = null; + leaderboardPage = -1; + + loadObjective(ScoreboardManager.HEADER_RANK); + } + + public void setTypeTopPower(int page) { + this.sidebarType = SidebarType.TOP_BOARD; + leaderboardPage = page; + targetSkill = null; + + targetPlayer = null; + targetProfile = null; + + int endPosition = page * 15; + int startPosition = endPosition - 14; + loadObjective(String.format("%s (%2d - %2d)", ScoreboardManager.POWER_LEVEL, startPosition, endPosition)); + } + + public void setTypeTop(SkillType skill, int page) { + this.sidebarType = SidebarType.TOP_BOARD; + leaderboardPage = page; + targetSkill = skill; + + targetPlayer = null; + targetProfile = null; + + int endPosition = page * 15; + int startPosition = endPosition - 14; + loadObjective(String.format("%s (%2d - %2d)", ScoreboardManager.skillLabels.get(skill).getName(), startPosition, endPosition)); + } + + // Setup for after a board type change + protected void loadObjective(String displayName) { + sidebarObj.unregister(); + sidebarObj = board.registerNewObjective(ScoreboardManager.SIDEBAR_OBJECTIVE, "dummy"); + + if (displayName.length() > 32) { + displayName = displayName.substring(0, 32); + } + sidebarObj.setDisplayName(displayName); + + updateSidebar(); + // Do last! Minimize packets! + sidebarObj.setDisplaySlot(DisplaySlot.SIDEBAR); + } + + /** + * Load new values into the sidebar. + */ + private void updateSidebar() { + try { + updateTask.cancel(); + } catch (Throwable ignored) {} // catch NullPointerException and IllegalStateException and any Error; don't care + updateTask = null; + + if (sidebarType == SidebarType.NONE) { + return; + } + + Player bukkitPlayer = Bukkit.getPlayerExact(playerName); + if (bukkitPlayer == null) { + ScoreboardManager.cleanup(this); + return; + } + + McMMOPlayer mcPlayer = UserManager.getPlayer(bukkitPlayer); + PlayerProfile profile = mcPlayer.getProfile(); + + switch (sidebarType) { + case NONE: + break; + + case SKILL_BOARD: + Validate.notNull(targetSkill); + if (!targetSkill.isChildSkill()) { + int currentXP = profile.getSkillXpLevel(targetSkill); + sidebarObj.getScore(ScoreboardManager.LABEL_CURRENT_XP).setScore(currentXP); + sidebarObj.getScore(ScoreboardManager.LABEL_REMAINING_XP).setScore(profile.getXpToLevel(targetSkill) - currentXP); + } + else { + Set parents = FamilyTree.getParents(targetSkill); + for (SkillType parentSkill : parents) { + sidebarObj.getScore(ScoreboardManager.skillLabels.get(parentSkill)).setScore(profile.getSkillLevel(parentSkill)); + } + } + sidebarObj.getScore(ScoreboardManager.LABEL_LEVEL).setScore(profile.getSkillLevel(targetSkill)); + if (targetSkill.getAbility() != null) { + if (targetSkill != SkillType.MINING) { + AbilityType ab = targetSkill.getAbility(); + Score cooldown = sidebarObj.getScore(ScoreboardManager.abilityLabelsSkill.get(ab)); + int seconds = SkillUtils.calculateTimeLeft(profile.getSkillDATS(ab) * Misc.TIME_CONVERSION_FACTOR, ab.getCooldown(), bukkitPlayer); + seconds = (seconds <= 0) ? 0 : seconds; + if (seconds == 0) { + cooldown.setScore(0); + stopCooldownUpdating(); + } + else { + cooldown.setScore(seconds); + startCooldownUpdating(); + } + } else { + // Special-Case: Mining has two abilities, both with cooldowns + AbilityType sb = AbilityType.SUPER_BREAKER; + AbilityType bm = AbilityType.BLAST_MINING; + Score cooldownSB = sidebarObj.getScore(ScoreboardManager.abilityLabelsSkill.get(sb)); + Score cooldownBM = sidebarObj.getScore(ScoreboardManager.abilityLabelsSkill.get(bm)); + int secondsSB = SkillUtils.calculateTimeLeft(profile.getSkillDATS(sb) * Misc.TIME_CONVERSION_FACTOR, sb.getCooldown(), bukkitPlayer); + int secondsBM = SkillUtils.calculateTimeLeft(profile.getSkillDATS(bm) * Misc.TIME_CONVERSION_FACTOR, bm.getCooldown(), bukkitPlayer); + secondsSB = (secondsSB <= 0) ? 0 : secondsSB; + secondsBM = (secondsBM <= 0) ? 0 : secondsBM; + if (secondsSB == 0 && secondsBM == 0) { + cooldownSB.setScore(0); + cooldownBM.setScore(0); + stopCooldownUpdating(); + } + else { + cooldownSB.setScore(secondsSB); + cooldownBM.setScore(secondsBM); + startCooldownUpdating(); + } + } + } + break; + + case COOLDOWNS_BOARD: + boolean anyCooldownsActive = false; + for (AbilityType ability : AbilityType.NORMAL_ABILITIES) { + int seconds = SkillUtils.calculateTimeLeft(profile.getSkillDATS(ability) * Misc.TIME_CONVERSION_FACTOR, ability.getCooldown(), bukkitPlayer); + seconds = (seconds <= 0) ? 0 : seconds; + if (seconds != 0) { + anyCooldownsActive = true; + } + sidebarObj.getScore(ScoreboardManager.abilityLabelsColored.get(ability)).setScore(seconds); + } + + if (anyCooldownsActive) { + startCooldownUpdating(); + } + else { + stopCooldownUpdating(); + } + break; + + case STATS_BOARD: + // Select the profile to read from + PlayerProfile prof; + if (targetProfile != null) { + prof = targetProfile; // offline + } + else if (targetPlayer == null) { + prof = profile; // self + } + else { + prof = UserManager.getPlayer(targetPlayer).getProfile(); // online + } + // Calculate power level here + int powerLevel = 0; + for (SkillType skill : SkillType.values()) { // Include child skills, but not in power level + int level = prof.getSkillLevel(skill); + if (!skill.isChildSkill()) + powerLevel += level; + + // TODO: Verify that this is what we want - calculated in power level but not displayed + if (!Permissions.skillEnabled(bukkitPlayer, skill)) { + continue; + } + sidebarObj.getScore(ScoreboardManager.skillLabels.get(skill)).setScore(level); + } + sidebarObj.getScore(ScoreboardManager.LABEL_POWER_LEVEL).setScore(powerLevel); + break; + + case RANK_BOARD: + case TOP_BOARD: + /* + * @see #acceptRankData(Map rank) + * @see #acceptLeaderboardData(List stats) + */ + break; + + } + } + + public void acceptRankData(Map rankData) { + Integer rank; + Player bukkitPlayer = Bukkit.getPlayerExact(playerName); + + for (SkillType skill : SkillType.NON_CHILD_SKILLS) { + if (!Permissions.skillEnabled(bukkitPlayer, skill)) { + continue; + } + + rank = rankData.get(skill); + if (rank != null) { + sidebarObj.getScore(ScoreboardManager.skillLabels.get(skill)).setScore(rank); + } + } + rank = rankData.get(null); + if (rank != null) { + sidebarObj.getScore(ScoreboardManager.LABEL_POWER_LEVEL).setScore(rank); + } + } + + public void acceptLeaderboardData(List leaderboardData) { + for (PlayerStat stat : leaderboardData) { + String statname = stat.name; + if (statname.equals(playerName)) { + statname = ChatColor.GOLD + "--You--"; + } + sidebarObj.getScore(Bukkit.getOfflinePlayer(statname)).setScore(stat.statVal); + } + } + + public void updatePowerLevel(Player leveledPlayer, int newPowerLevel) { + powerObj.getScore(leveledPlayer).setScore(newPowerLevel); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index bbde61be7..50de24b51 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -27,35 +27,62 @@ General: # Should mcMMO over-write configs to update, or make new ones ending in .new? Config_Update_Overwrite: true -Scoreboards: - # Should mcMMO use scoreboards for /inspect? - # Amount of time (in seconds) to display. To display permanently, set to -1 - Inspect: - Use: true - Display_Time: 10 - # Should mcMMO use scoreboards for /mcrank? - # Amount of time (in seconds) to display. To display permanently, set to -1 - Mcrank: - Use: true - Display_Time: 10 - # Should mcMMO use scoreboards for /mcstats? - # Amount of time (in seconds) to display. To display permanently, set to -1 - Mcstats: - Use: true - Display_Time: 10 - # Should mcMMO use scoreboards for /mctop? - # Amount of time (in seconds) to display. To display permanently, set to -1 - Mctop: - Use: true - Display_Time: 10 - # Should mcMMO use scoreboards for /skillname (/mining, /fishing, etc.)? - # Amount of time (in seconds) to display. To display permanently, set to -1 - Skillname: - Use: true - Display_Time: 10 - # Should mcMMO display power levels on scoreboards? (below player name-tags) - Power_Level: - Use: false +# +# Settings for the mcMMO scoreboards +### +Scoreboard: + # Display player's power levels below their names? + Power_Level_Tags: false + + # Allow players to use "/mcscoreboard keep" to keep the scoreboard up + Allow_Keep: true + + # Add some more color on the board :-) + Rainbows: false + + # Settings for each type of scoreboard + Types: + # Settings for /mcrank + # The sub-options (Print, Board, Display_Time) are the same for each type. + Rank: + # Should the command output be printed in chat? + Print: false + # Should the command output be displayed in the scoreboard sidebar? + Board: true + # Amount of time (seconds) to display in the sidebar before clearing. + # To display permanently, use "/mcscoreboard keep" or set to -1 + Display_Time: 15 + # Settings for /mctop + Top: + Print: true + Board: true + Display_Time: 15 + # Settings for /mcstats + Stats: + Print: false + Board: true + Display_Time: 15 + # Settings for /inspect + Inspect: + Print: false + Board: true + Display_Time: 20 + # Settings for /mccooldown + Cooldown: + Print: true + Board: true + Display_Time: 41 + # Settings for / (e.g. /mining, /unarmed) + # No "print" option is given here; the information will always be displayed in chat. + # It should also be noted that this display is pretty dang cool. + Skill: + Board: true + Display_Time: 30 + + # Should the board be shown when a player levels up, and for how long? + # It is recommended to NOT have LevelUp_Time be -1, as this may confuse players. + LevelUp_Board: true + LevelUp_Time: 5 Mob_Healthbar: # Default display for mob health bars - HEARTS, BAR, or DISABLED diff --git a/src/main/resources/locale/locale_en_US.properties b/src/main/resources/locale/locale_en_US.properties index b6805f713..e705ce003 100644 --- a/src/main/resources/locale/locale_en_US.properties +++ b/src/main/resources/locale/locale_en_US.properties @@ -74,6 +74,7 @@ Axes.Effect.8=Greater Impact Axes.Effect.9=Deal bonus damage to unarmored foes Axes.Listener=Axes: Axes.SkillName=AXES +Axes.Skills.SS.Name=Skull Splitter Axes.Skills.SS.Off=[[RED]]**Skull Splitter has worn off** Axes.Skills.SS.On=[[GREEN]]**Skull Splitter ACTIVATED** Axes.Skills.SS.Refresh=[[GREEN]]Your [[YELLOW]]Skull Splitter [[GREEN]]ability is refreshed! @@ -91,6 +92,7 @@ Excavation.Effect.3=Ability to dig for treasure Excavation.Effect.Length=[[RED]]Giga Drill Breaker Length: [[YELLOW]]{0}s Excavation.Listener=Excavation: Excavation.SkillName=EXCAVATION +Excavation.Skills.GigaDrillBreaker.Name=Giga Drill Breaker Excavation.Skills.GigaDrillBreaker.Off=[[RED]]**Giga Drill Breaker has worn off** Excavation.Skills.GigaDrillBreaker.On=[[GREEN]]**GIGA DRILL BREAKER ACTIVATED** Excavation.Skills.GigaDrillBreaker.Refresh=[[GREEN]]Your [[YELLOW]]Giga Drill Breaker [[GREEN]]ability is refreshed! @@ -162,6 +164,7 @@ Herbalism.Effect.13=Spread mycelium to dirt & grass Herbalism.HylianLuck=[[GREEN]]The luck of Hyrule is with you today! Herbalism.Listener=Herbalism: Herbalism.SkillName=HERBALISM +Herbalism.Skills.GTe.Name=Green Terra Herbalism.Skills.GTe.Off=[[RED]]**Green Terra has worn off** Herbalism.Skills.GTe.On=[[GREEN]]**GREEN TERRA ACTIVATED** Herbalism.Skills.GTe.Refresh=[[GREEN]]Your [[YELLOW]]Green Terra [[GREEN]]ability is refreshed! @@ -190,6 +193,7 @@ Mining.Effect.Decrease=[[RED]]Demolitions Expert Damage Decrease: [[YELLOW]]{0} Mining.Effect.DropChance=[[RED]]Double Drop Chance: [[YELLOW]]{0} Mining.Listener=Mining: Mining.SkillName=MINING +Mining.Skills.SuperBreaker.Name=Super Breaker Mining.Skills.SuperBreaker.Off=[[RED]]**Super Breaker has worn off** Mining.Skills.SuperBreaker.On=[[GREEN]]**SUPER BREAKER ACTIVATED** Mining.Skills.SuperBreaker.Other.Off=[[RED]]Super Breaker[[GREEN]] has worn off for [[YELLOW]]{0} @@ -198,6 +202,7 @@ Mining.Skills.SuperBreaker.Refresh=[[GREEN]]Your [[YELLOW]]Super Breaker [[GREEN Mining.Skillup=[[YELLOW]]Mining skill increased by {0}. Total ({1}) #Blast Mining +Mining.Blast.Name=Blast Mining Mining.Blast.Boom=[[GRAY]]**BOOM** Mining.Blast.Effect=+{0} ore yield, -{1} debris yield, {2}x drops Mining.Blast.Radius.Increase=[[RED]]Blast Radius Increase: [[YELLOW]]+{0} @@ -278,6 +283,7 @@ Swords.Effect.6=Bleed Swords.Effect.7=Apply a bleed DoT Swords.Listener=Swords: Swords.SkillName=SWORDS +Swords.Skills.SS.Name=Serrated Strikes Swords.Skills.SS.Off=[[RED]]**Serrated Strikes has worn off** Swords.Skills.SS.On=[[GREEN]]**SERRATED STRIKES ACTIVATED** Swords.Skills.SS.Refresh=[[GREEN]]Your [[YELLOW]]Serrated Strikes [[GREEN]]ability is refreshed! @@ -360,6 +366,7 @@ Unarmed.Effect.8=Iron Grip Unarmed.Effect.9=Prevents you from being disarmed Unarmed.Listener=Unarmed: Unarmed.SkillName=UNARMED +Unarmed.Skills.Berserk.Name=Berserk Unarmed.Skills.Berserk.Off=[[RED]]**Berserk has worn off** Unarmed.Skills.Berserk.On=[[GREEN]]**BERSERK ACTIVATED** Unarmed.Skills.Berserk.Other.Off=[[RED]]Berserk[[GREEN]] has worn off for [[YELLOW]]{0} @@ -381,6 +388,7 @@ Woodcutting.Effect.4=Double Drops Woodcutting.Effect.5=Double the normal loot Woodcutting.Listener=Woodcutting: Woodcutting.SkillName=WOODCUTTING +Woodcutting.Skills.TreeFeller.Name=Tree Feller Woodcutting.Skills.TreeFeller.Off=[[RED]]**Tree Feller has worn off** Woodcutting.Skills.TreeFeller.On=[[GREEN]]**TREE FELLER ACTIVATED** Woodcutting.Skills.TreeFeller.Refresh=[[GREEN]]Your [[YELLOW]]Tree Feller [[GREEN]]ability is refreshed! @@ -422,6 +430,10 @@ Commands.AdminChat.Off=Admin Chat only [[RED]]Off Commands.AdminChat.On=Admin Chat only [[GREEN]]On Commands.AdminToggle=[[RED]]- Toggle admin chat Commands.Chat.Console=*Console* +Commands.Cooldowns.Header=[[GOLD]]--= [[GREEN]]mcMMO Ability Cooldowns[[GOLD]] =-- +Commands.Cooldowns.Row.N=\ [[RED]]{0}[[WHITE]] - [[GOLD]]{1} seconds left +Commands.Cooldowns.Row.Y=\ [[AQUA]]{0}[[WHITE]] - [[DARK_GREEN]]Ready! +Commands.Database.Cooldown=[[RED]]You must wait 1 second before using this command again. Commands.Disabled=[[RED]]This command is disabled. Commands.DoesNotExist= [[RED]]Player does not exist in the database! Commands.GodMode.Disabled=[[YELLOW]]mcMMO Godmode Disabled @@ -511,6 +523,15 @@ Commands.PowerLevel=[[DARK_RED]]POWER LEVEL: [[GREEN]]{0} Commands.Reset.All=[[GREEN]]All of your skill levels have been reset successfully. Commands.Reset.Single=[[GREEN]]Your {0} skill level has been reset successfully. Commands.Reset=[[RED]]Reset a skill's level to 0 +Commands.Scoreboard.Clear=[[DARK_AQUA]]mcMMO scoreboard cleared. +Commands.Scoreboard.Keep=[[DARK_AQUA]]The mcMMO scoreboard will stay up until you use [[GREEN]]/mcscoreboard clear[[DARK_AQUA]]. +#Commands.Scoreboard.Timer=[[BLUE]]This scoreboard will remain visible for [[GOLD]]{1}[[BLUE]] seconds. +Commands.Scoreboard.Help.0=[[GOLD]] == [[GREEN]]Help for [[RED]]/mcscoreboard[[GOLD]] == +Commands.Scoreboard.Help.1=[[DARK_AQUA]]/mcscoreboard[[AQUA]] clear [[WHITE]] - clear the McMMO scoreboard +Commands.Scoreboard.Help.2=[[DARK_AQUA]]/mcscoreboard[[AQUA]] keep [[WHITE]] - keep the mcMMO scoreboard up +Commands.Scoreboard.Help.3=[[DARK_AQUA]]/mcscoreboard[[AQUA]] time [n] [[WHITE]] - clear the McMMO scoreboard after [[LIGHT_PURPLE]]n[[WHITE]] seconds +Commands.Scoreboard.Tip.Keep=[[GOLD]]Tip: Use [[RED]]/mcscoreboard keep[[GOLD]] to keep the scoreboard from going away. +Commands.Scoreboard.Tip.Clear=[[GOLD]]Tip: Use [[RED]]/mcscoreboard clear[[GOLD]] to get rid of the scoreboard. Commands.Skill.Invalid=[[RED]]That is not a valid skillname! Commands.Skill.Leaderboard=[[YELLOW]]--mcMMO [[BLUE]]{0}[[YELLOW]] Leaderboard-- Commands.SkillInfo=[[RED]]- View detailed information about a skill @@ -829,6 +850,7 @@ Commands.Description.addxp=Add mcMMO XP to a user Commands.Description.hardcore=Modify the mcMMO hardcore percentage or toggle hardcore mode on/off Commands.Description.inspect=View detailed mcMMO info on another player Commands.Description.mcability=Toggle mcMMO abilities being readied on right-click on/off +Commands.Description.mccooldown=View all of the mcMMO ability cooldowns Commands.Description.mcgod=Toggle mcMMO god-mode on/off Commands.Description.mchud=Change your mcMMO HUD style Commands.Description.mcmmo=Show a brief description of mcMMO @@ -837,6 +859,7 @@ Commands.Description.mcpurge=Purge users with no mcMMO levels and users who have Commands.Description.mcrank=Show mcMMO ranking for a player Commands.Description.mcrefresh=Refresh all cooldowns for mcMMO Commands.Description.mcremove=Remove a user from the mcMMO database +Commands.Description.mcscoreboard=Manage your mcMMO Scoreboard Commands.Description.mcstats=Show your mcMMO levels and XP Commands.Description.mctop=Show mcMMO leader boards Commands.Description.mmoedit=Edit mcMMO levels for a user @@ -857,15 +880,17 @@ UpdateChecker.Outdated=You are using an outdated version of mcMMO! UpdateChecker.NewAvailable=There is a new version available on BukkitDev. #SCOREBOARD HEADERS -Scoreboard.Header.PlayerStats=mcMMO Stats -Scoreboard.Header.PlayerRank=mcMMO Rankings -Scoreboard.Header.PlayerInspect=mcMMO Stats: -Scoreboard.Header.PowerLevel=Power Level -Scoreboard.Misc.PowerLevel=Power Level -Scoreboard.Misc.Level=Level -Scoreboard.Misc.CurrentXP=Current XP -Scoreboard.Misc.RemainingXP=Remaining XP -Scoreboard.Misc.Overall=Overall +Scoreboard.Header.PlayerStats=[[YELLOW]]mcMMO Stats +Scoreboard.Header.PlayerCooldowns=[[YELLOW]]mcMMO Cooldowns +Scoreboard.Header.PlayerRank=[[YELLOW]]mcMMO Rankings +Scoreboard.Header.PlayerInspect=[[YELLOW]]mcMMO Stats: {0} +Scoreboard.Header.PowerLevel=[[RED]]Power Level +Scoreboard.Misc.PowerLevel=[[GOLD]]Power Level +Scoreboard.Misc.Level=[[DARK_AQUA]]Level +Scoreboard.Misc.CurrentXP=[[GREEN]]Current XP +Scoreboard.Misc.RemainingXP=[[YELLOW]]Remaining XP +Scoreboard.Misc.Cooldown=[[LIGHT_PURPLE]]Cooldown +Scoreboard.Misc.Overall=[[GOLD]]Overall #DATABASE RECOVERY Recovery.Notice=[[RED]]Notice: mcMMO was [[DARK_RED]]unable to load your data.[[RED]] Retrying 5 times... diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 2cfaf929d..e7fb8230a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -36,6 +36,9 @@ commands: description: Toggle whether or not abilities get readied on right click mcrefresh: description: Refresh all cooldowns for mcMMO + mccooldown: + description: Show the cooldowns on all your mcMMO abilities + aliases: [mccooldowns] mcgod: description: Toggle mcMMO god-mode on/off mcstats: @@ -104,7 +107,7 @@ commands: aliases: [mcmobhealth] description: Change the style of the mob healthbar mcscoreboard: - description: Change the current mcMMO scoreboard being displayed + description: Manage your mcMMO Scoreboard kraken: aliases: [mckraken] description: Unleash the kraken! @@ -692,6 +695,7 @@ permissions: mcmmo.commands.herbalism: true mcmmo.commands.inspect: true mcmmo.commands.mcability: true + mcmmo.commands.mccooldown: true mcmmo.commands.mcmmo.all: true mcmmo.commands.mcnotify: true mcmmo.commands.mcrank: true @@ -828,6 +832,8 @@ permissions: description: Allows access to the mcconvert command for databases mcmmo.commands.mcconvert.experience: description: Allows access to the mcconvert command for experience + mcmmo.commands.mccooldown: + description: Allows access to the mccooldowns command mcmmo.commands.mcgod: description: Allows access to the mcgod command mcmmo.commands.mcgod.others: