package com.gmail.nossr50.util.scoreboards; import java.util.List; import java.util.Map; 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.player.UserManager; import com.gmail.nossr50.util.scoreboards.ScoreboardManager.SidebarType; import org.apache.commons.lang.Validate; public class ScoreboardWrapper { // Initialization variables public final String playerName; private final Scoreboard scoreboard; private boolean tippedKeep = false; private boolean tippedClear = false; // Internal usage variables (should exist) private SidebarType sidebarType; private Objective sidebarObjective; private Objective powerObjective; // 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 scoreboard) { this.playerName = playerName; this.scoreboard = scoreboard; sidebarType = SidebarType.NONE; sidebarObjective = this.scoreboard.registerNewObjective(ScoreboardManager.SIDEBAR_OBJECTIVE, "dummy"); powerObjective = this.scoreboard.registerNewObjective(ScoreboardManager.POWER_OBJECTIVE, "dummy"); if (Config.getInstance().getPowerLevelTagsEnabled()) { powerObjective.setDisplayName(ScoreboardManager.TAG_POWER_LEVEL); powerObjective.setDisplaySlot(DisplaySlot.BELOW_NAME); for (McMMOPlayer mcMMOPlayer : UserManager.getPlayers()) { powerObjective.getScore(mcMMOPlayer.getProfile().getPlayerName()).setScore(mcMMOPlayer.getPowerLevel()); } } } public static ScoreboardWrapper create(Player player) { return new ScoreboardWrapper(player.getName(), mcMMO.p.getServer().getScoreboardManager().getNewScoreboard()); } public BukkitTask updateTask = null; private class ScoreboardQuickUpdate extends BukkitRunnable { @Override public void run() { updateSidebar(); updateTask = null; } } public BukkitTask revertTask = null; private class ScoreboardChangeTask extends BukkitRunnable { @Override public void run() { tryRevertBoard(); revertTask = null; } } public BukkitTask cooldownTask = null; private class ScoreboardCooldownTask extends BukkitRunnable { @Override public void run() { // Stop updating if it's no longer something displaying cooldowns if (isBoardShown() && (isSkillScoreboard() || isCooldownScoreboard())) { doSidebarUpdateSoon(); } else { 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 = mcMMO.p.getServer().getPlayerExact(playerName); if (player == null) { ScoreboardManager.cleanup(this); return; } Scoreboard oldBoard = player.getScoreboard(); if (oldBoard == scoreboard) { // Already displaying it if (this.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 this.oldBoard = mcMMO.p.getServer().getScoreboardManager().getMainScoreboard(); } } else { this.oldBoard = oldBoard; } } public void showBoardWithNoRevert() { Player player = mcMMO.p.getServer().getPlayerExact(playerName); if (player == null) { ScoreboardManager.cleanup(this); return; } if (revertTask != null) { revertTask.cancel(); } player.setScoreboard(scoreboard); revertTask = null; } public void showBoardAndScheduleRevert(int ticks) { Player player = mcMMO.p.getServer().getPlayerExact(playerName); if (player == null) { ScoreboardManager.cleanup(this); return; } if (revertTask != null) { revertTask.cancel(); } player.setScoreboard(scoreboard); 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 = mcMMO.p.getServer().getPlayerExact(playerName); if (player == null) { ScoreboardManager.cleanup(this); return; } if (oldBoard != null) { if (player.getScoreboard() == scoreboard) { 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!)"); } } cancelRevert(); sidebarType = SidebarType.NONE; targetPlayer = null; targetSkill = null; targetProfile = null; leaderboardPage = -1; } public boolean isBoardShown() { Player player = mcMMO.p.getServer().getPlayerExact(playerName); if (player == null) { ScoreboardManager.cleanup(this); return false; } return player.getScoreboard() == scoreboard; } public void cancelRevert() { if (revertTask == null) { return; } 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)); } 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), startPosition, endPosition)); } // Setup for after a board type change protected void loadObjective(String displayName) { sidebarObjective.unregister(); sidebarObjective = scoreboard.registerNewObjective(ScoreboardManager.SIDEBAR_OBJECTIVE, "dummy"); if (displayName.length() > 32) { displayName = displayName.substring(0, 32); } sidebarObjective.setDisplayName(displayName); updateSidebar(); // Do last! Minimize packets! sidebarObjective.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 player = mcMMO.p.getServer().getPlayerExact(playerName); if (player == null) { ScoreboardManager.cleanup(this); return; } McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); switch (sidebarType) { case NONE: break; case SKILL_BOARD: Validate.notNull(targetSkill); if (!targetSkill.isChildSkill()) { int currentXP = mcMMOPlayer.getSkillXpLevel(targetSkill); sidebarObjective.getScore(ScoreboardManager.LABEL_CURRENT_XP).setScore(currentXP); sidebarObjective.getScore(ScoreboardManager.LABEL_REMAINING_XP).setScore(mcMMOPlayer.getXpToLevel(targetSkill) - currentXP); } else { for (SkillType parentSkill : FamilyTree.getParents(targetSkill)) { sidebarObjective.getScore(ScoreboardManager.skillLabels.get(parentSkill)).setScore(mcMMOPlayer.getSkillLevel(parentSkill)); } } sidebarObjective.getScore(ScoreboardManager.LABEL_LEVEL).setScore(mcMMOPlayer.getSkillLevel(targetSkill)); if (targetSkill.getAbility() != null) { boolean stopUpdating; if (targetSkill == SkillType.MINING) { // Special-Case: Mining has two abilities, both with cooldowns Score cooldownSB = sidebarObjective.getScore(ScoreboardManager.abilityLabelsSkill.get(AbilityType.SUPER_BREAKER)); Score cooldownBM = sidebarObjective.getScore(ScoreboardManager.abilityLabelsSkill.get(AbilityType.BLAST_MINING)); int secondsSB = Math.max(mcMMOPlayer.calculateTimeRemaining(AbilityType.SUPER_BREAKER), 0); int secondsBM = Math.max(mcMMOPlayer.calculateTimeRemaining(AbilityType.BLAST_MINING), 0); cooldownSB.setScore(secondsSB); cooldownBM.setScore(secondsBM); stopUpdating = (secondsSB == 0 && secondsBM == 0); } else { AbilityType ability = targetSkill.getAbility(); Score cooldown = sidebarObjective.getScore(ScoreboardManager.abilityLabelsSkill.get(ability)); int seconds = Math.max(mcMMOPlayer.calculateTimeRemaining(ability), 0); cooldown.setScore(seconds); stopUpdating = seconds == 0; } if (stopUpdating) { stopCooldownUpdating(); } else { startCooldownUpdating(); } } break; case COOLDOWNS_BOARD: boolean anyCooldownsActive = false; for (AbilityType ability : AbilityType.values()) { int seconds = Math.max(mcMMOPlayer.calculateTimeRemaining(ability), 0); if (seconds != 0) { anyCooldownsActive = true; } sidebarObjective.getScore(ScoreboardManager.abilityLabelsColored.get(ability)).setScore(seconds); } if (anyCooldownsActive) { startCooldownUpdating(); } else { stopCooldownUpdating(); } break; case STATS_BOARD: // Select the profile to read from PlayerProfile newProfile; if (targetProfile != null) { newProfile = targetProfile; // offline } else if (targetPlayer == null) { newProfile = mcMMOPlayer.getProfile(); // self } else { newProfile = UserManager.getPlayer(targetPlayer).getProfile(); // online } // Calculate power level here int powerLevel = 0; for (SkillType skill : SkillType.NON_CHILD_SKILLS) { // Don't include child skills, makes the list too long int level = newProfile.getSkillLevel(skill); powerLevel += level; // TODO: Verify that this is what we want - calculated in power level but not displayed if (!skill.getPermissions(player)) { continue; } sidebarObjective.getScore(ScoreboardManager.skillLabels.get(skill)).setScore(level); } sidebarObjective.getScore(ScoreboardManager.LABEL_POWER_LEVEL).setScore(powerLevel); break; case RANK_BOARD: case TOP_BOARD: /* * @see #acceptRankData(Map<SkillType, Integer> rank) * @see #acceptLeaderboardData(List<PlayerStat> stats) */ break; default: break; } } public void acceptRankData(Map<SkillType, Integer> rankData) { Integer rank; Player player = mcMMO.p.getServer().getPlayerExact(playerName); for (SkillType skill : SkillType.NON_CHILD_SKILLS) { if (!skill.getPermissions(player)) { continue; } rank = rankData.get(skill); if (rank != null) { sidebarObjective.getScore(ScoreboardManager.skillLabels.get(skill)).setScore(rank); } } rank = rankData.get(null); if (rank != null) { sidebarObjective.getScore(ScoreboardManager.LABEL_POWER_LEVEL).setScore(rank); } } public void acceptLeaderboardData(List<PlayerStat> leaderboardData) { for (PlayerStat stat : leaderboardData) { String name = stat.name; if (name.equals(playerName)) { name = ChatColor.GOLD + "--You--"; } sidebarObjective.getScore(name).setScore(stat.statVal); } } public void updatePowerLevel(Player player, int newPowerLevel) { powerObjective.getScore(player.getName()).setScore(newPowerLevel); } }