Refactor scoreboard usage

In preparation for team colors, we need to unify scoreboard usage into a
single scoreboard per player.

Rather than creating a new scoreboard for each desired sidebar
objective, we provide an interface FSidebarProvider that allows
implementation-agnostic retrieval of the title and lines of the desired
sidebar, along with appropriate methods in the new implementation of
FScoreboard.

The new FScoreboard implementation manages the currently-visible
sidebar, with methods to set default (non-timed, with update interval)
and temporary (timed, no update interval) sidebars.

Temporary sidebars are replaced with the default sidebar upon
expiration.
This commit is contained in:
eueln 2014-10-15 15:19:17 -05:00 committed by t00thpick1
parent bed6fe0741
commit 25696f32aa
12 changed files with 336 additions and 304 deletions

View File

@ -6,7 +6,7 @@ import com.massivecraft.factions.iface.EconomyParticipator;
import com.massivecraft.factions.iface.RelationParticipator;
import com.massivecraft.factions.integration.Econ;
import com.massivecraft.factions.integration.Worldguard;
import com.massivecraft.factions.scoreboards.FInfoBoard;
import com.massivecraft.factions.scoreboards.sidebar.FInfoSidebar;
import com.massivecraft.factions.scoreboards.FScoreboard;
import com.massivecraft.factions.struct.ChatMode;
import com.massivecraft.factions.struct.Permission;
@ -121,16 +121,6 @@ public class FPlayer extends PlayerEntity implements EconomyParticipator {
}
}
public FScoreboard activeBoard;
public FScoreboard getActiveBoard() {
return this.activeBoard;
}
public void setActiveBoard(FScoreboard board) {
this.activeBoard = board;
}
// FIELD: autoSafeZoneEnabled
private transient boolean autoSafeZoneEnabled;
@ -551,7 +541,7 @@ public class FPlayer extends PlayerEntity implements EconomyParticipator {
Faction toShow = Board.getFactionAt(getLastStoodAt());
if (shouldShowScoreboard(toShow)) {
// Shows them the scoreboard instead of sending a message in chat. Will disappear after a few seconds.
new FInfoBoard(getPlayer(), toShow, true);
FScoreboard.get(this).setTemporarySidebar(new FInfoSidebar(toShow));
} else {
String msg = P.p.txt.parse("<i>") + " ~ " + toShow.getTag(this);
if (toShow.getDescription().length() > 0) {

View File

@ -2,7 +2,6 @@ package com.massivecraft.factions.cmd;
import com.massivecraft.factions.FPlayer;
import com.massivecraft.factions.P;
import com.massivecraft.factions.scoreboards.FDefaultBoard;
import com.massivecraft.factions.scoreboards.FScoreboard;
import com.massivecraft.factions.struct.Permission;
import com.massivecraft.factions.zcore.util.TL;
@ -39,12 +38,7 @@ public class CmdSB extends FCommand {
@Override
public void perform() {
boolean toggle = toggle(me.getPlayer().getUniqueId());
if(!toggle && fme.getActiveBoard() != null) {
fme.getActiveBoard().cancel();
} else if(toggle && P.p.getConfig().getBoolean("scoreboards.default-enabled", true)){
FScoreboard board = new FDefaultBoard(fme);
fme.setActiveBoard(board);
}
FScoreboard.get(fme).setSidebarVisibility(toggle);
me.sendMessage(TL.TOGGLE_SB.toString().replace("{value}", String.valueOf(toggle)));
}

View File

@ -1,8 +1,8 @@
package com.massivecraft.factions.listeners;
import com.massivecraft.factions.*;
import com.massivecraft.factions.scoreboards.FDefaultBoard;
import com.massivecraft.factions.scoreboards.FScoreboard;
import com.massivecraft.factions.scoreboards.sidebar.FDefaultSidebar;
import com.massivecraft.factions.struct.Permission;
import com.massivecraft.factions.struct.Relation;
import com.massivecraft.factions.struct.Role;
@ -55,17 +55,11 @@ public class FactionsPlayerListener implements Listener {
}
}, 33L); // Don't ask me why.
if (P.p.getConfig().getBoolean("scoreboard.default-enabled", false) && P.p.cmdBase.cmdSB.showBoard(me)) {
Bukkit.getScheduler().runTaskLater(P.p, new Runnable() { // I think we still have to delay this a few seconds.
@Override
public void run() {
if (me.getPlayer().isOnline()) { // In case people are quickly joining and quitting.
FScoreboard board = new FDefaultBoard(me);
me.setActiveBoard(board);
}
}
}, 20L);
FScoreboard.init(me);
if (P.p.getConfig().getBoolean("scoreboard.default-enabled", false)) {
FScoreboard.get(me).setDefaultSidebar(new FDefaultSidebar(), P.p.getConfig().getInt("default-update-interval", 20));
}
FScoreboard.get(me).setSidebarVisibility(P.p.cmdBase.cmdSB.showBoard(me));
}
@EventHandler(priority = EventPriority.NORMAL)
@ -81,6 +75,8 @@ public class FactionsPlayerListener implements Listener {
if (myFaction != null) {
myFaction.memberLoggedOff();
}
FScoreboard.remove(me);
}
// Holds the next time a player can have a map shown.

View File

@ -0,0 +1,137 @@
package com.massivecraft.factions.scoreboards;
import com.google.common.base.Splitter;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team;
import java.util.*;
public class BufferedObjective {
private final Scoreboard scoreboard;
private final String baseName;
private Objective current;
private List<Team> currentTeams = new ArrayList<Team>();
private String title;
private DisplaySlot displaySlot;
private int objPtr;
private int teamPtr;
private boolean requiresUpdate = false;
private final Map<Integer, String> contents = new HashMap<Integer, String>();
public BufferedObjective(Scoreboard scoreboard) {
this.scoreboard = scoreboard;
this.baseName = createBaseName();
current = scoreboard.registerNewObjective(getNextObjectiveName(), "dummy");
}
private String createBaseName() {
Random random = new Random();
StringBuilder builder = new StringBuilder();
while (builder.length() < 14) {
builder.append(Integer.toHexString(random.nextInt()));
}
return builder.toString().substring(0, 14);
}
public void setTitle(String title) {
if (this.title == null || !this.title.equals(title)) {
this.title = title;
requiresUpdate = true;
}
}
public void setDisplaySlot(DisplaySlot slot) {
this.displaySlot = slot;
current.setDisplaySlot(slot);
}
public void setAllLines(List<String> lines) {
for (int i = 0; i < lines.size(); i++) {
setLine(lines.size() - i, lines.get(i));
}
}
public void setLine(int lineNumber, String content) {
if (content.length() > 48) {
content = content.substring(0, 48);
}
content = ChatColor.translateAlternateColorCodes('&', content);
if (contents.get(lineNumber) == null || !contents.get(lineNumber).equals(content)) {
contents.put(lineNumber, content);
requiresUpdate = true;
}
}
// Hides the objective from the display slot until flip() is called
public void hide() {
if (displaySlot != null) {
scoreboard.clearSlot(displaySlot);
}
}
@SuppressWarnings("deprecation")
public void flip() {
if (!requiresUpdate) {
return;
}
requiresUpdate = false;
Objective buffer = scoreboard.registerNewObjective(getNextObjectiveName(), "dummy");
buffer.setDisplayName(title);
List<Team> bufferTeams = new ArrayList<Team>();
for (Map.Entry<Integer, String> entry : contents.entrySet()) {
if (entry.getValue().length() > 16) {
Team team = scoreboard.registerNewTeam(getNextTeamName());
bufferTeams.add(team);
Iterator<String> split = Splitter.fixedLength(16).split(entry.getValue()).iterator();
team.setPrefix(split.next());
String name = split.next();
if (split.hasNext()) { // We only guarantee two splits
team.setSuffix(split.next());
}
team.addPlayer(Bukkit.getOfflinePlayer(name));
buffer.getScore(name).setScore(entry.getKey());
} else {
buffer.getScore(entry.getValue()).setScore(entry.getKey());
}
}
if (displaySlot != null) {
buffer.setDisplaySlot(displaySlot);
}
// Unregister _ALL_ the old things
current.unregister();
Iterator<Team> it = currentTeams.iterator();
while (it.hasNext()) {
it.next().unregister();
it.remove();
}
current = buffer;
currentTeams = bufferTeams;
}
private String getNextObjectiveName() {
return baseName + "_" + ((objPtr++) % 2);
}
private String getNextTeamName() {
return baseName.substring(0, 10) + "_" + ((teamPtr++) % 999999);
}
}

View File

@ -1,99 +0,0 @@
package com.massivecraft.factions.scoreboards;
import com.massivecraft.factions.FPlayer;
import com.massivecraft.factions.P;
import com.massivecraft.factions.integration.Econ;
import com.massivecraft.factions.scoreboards.tasks.UpdateTask;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Score;
import org.bukkit.scoreboard.Scoreboard;
import java.util.List;
import java.util.logging.Level;
public class FDefaultBoard implements FScoreboard {
private FPlayer fPlayer;
private Scoreboard scoreboard;
private Objective objective;
private int taskId;
public FDefaultBoard(FPlayer player) {
this.fPlayer = player;
this.scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
setup();
apply(fPlayer.getPlayer());
}
public void setup() {
objective = scoreboard.registerNewObjective("default", "dummy");
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
this.taskId = Bukkit.getScheduler().runTaskTimer(P.p, new UpdateTask(this), 40L, P.p.getConfig().getLong("default-update-interval", 20L)).getTaskId();
update(objective);
}
public void apply(Player player) {
player.setScoreboard(scoreboard);
}
public void remove(Player player) {
player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard());
}
public void update(Objective buffer) {
if(fPlayer.getPlayer() == null || !fPlayer.getPlayer().isOnline()) {
cancel();
return;
}
buffer.setDisplayName(ChatColor.translateAlternateColorCodes('&', P.p.getConfig().getString("scoreboard.default-title", "i love drt")));
List<String> list = P.p.getConfig().getStringList("scoreboard.default");
int place = 16; // list.size();
if (list == null) {
P.p.debug(Level.WARNING, "scoreboard.default is null :(");
return;
}
for (String s : list) {
String replaced = replace(s);
String awesome = replaced.length() > 16 ? replaced.substring(0, 15) : replaced;
Score score = buffer.getScore(awesome);
score.setScore(place);
place--;
if (place < 0) {
break; // Let's not let the scoreboard get too big.
}
}
buffer.setDisplaySlot(DisplaySlot.SIDEBAR);
if(!buffer.getName().equalsIgnoreCase("default")) {
objective.unregister(); // unregister so we don't have to worry about duplicate names.
this.objective = buffer;
}
}
private String replace(String s) {
String faction = !fPlayer.getFaction().isNone() ? fPlayer.getFaction().getTag() : "factionless";
s = s.replace("{name}", fPlayer.getName())
.replace("{power}", String.valueOf(fPlayer.getPowerRounded()))
.replace("{balance}", String.valueOf(Econ.getFriendlyBalance(fPlayer.getPlayer().getUniqueId())))
.replace("{faction}", faction)
.replace("{maxPower}", String.valueOf(fPlayer.getPowerMaxRounded()))
.replace("{totalOnline}", String.valueOf(Bukkit.getServer().getOnlinePlayers().size()));
return ChatColor.translateAlternateColorCodes('&', s);
}
public void cancel() {
Bukkit.getScheduler().cancelTask(taskId);
}
public Scoreboard getScoreboard() {
return this.scoreboard;
}
}

View File

@ -1,98 +0,0 @@
package com.massivecraft.factions.scoreboards;
import com.massivecraft.factions.FPlayer;
import com.massivecraft.factions.FPlayers;
import com.massivecraft.factions.Faction;
import com.massivecraft.factions.P;
import com.massivecraft.factions.scoreboards.tasks.ExpirationTask;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Score;
import org.bukkit.scoreboard.Scoreboard;
import java.util.List;
import java.util.logging.Level;
public class FInfoBoard implements FScoreboard {
private Faction faction;
private Objective objective;
private Scoreboard scoreboard;
private FPlayer fPlayer;
public FInfoBoard(Player player, Faction faction, boolean timed) {
this.fPlayer = FPlayers.i.get(player);
this.faction = faction;
Scoreboard former = player.getScoreboard();
setup();
apply(player);
if (timed) {
new ExpirationTask(player.getName(), scoreboard, former).runTaskLater(P.p, P.p.getConfig().getInt("scoreboard.expiration", 7) * 20L); // remove after 10 seconds.
}
}
public void setup() {
scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
objective = scoreboard.registerNewObjective("FBoard", "dummy");
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
update(objective);
}
/**
* Filters lots of things in accordance with le config.
*
* @param s String to replace.
*
* @return new String with values instead of placeholders.
*/
private String replace(String s) {
boolean raidable = faction.getLandRounded() > faction.getPower();
FPlayer fLeader = faction.getFPlayerAdmin();
String leader = fLeader == null ? "Server" : fLeader.getName().substring(0, fLeader.getName().length() > 14 ? 13 : fLeader.getName().length());
return ChatColor.translateAlternateColorCodes('&', s.replace("{power}", String.valueOf(faction.getPowerRounded())).replace("{online}", String.valueOf(faction.getOnlinePlayers().size())).replace("{members}", String.valueOf(faction.getFPlayers().size())).replace("{leader}", leader).replace("{chunks}", String.valueOf(faction.getLandRounded())).replace("{raidable}", String.valueOf(raidable)));
}
public void apply(Player player) {
player.setScoreboard(scoreboard);
}
public void remove(Player player) {
player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard());
}
public void update(Objective objective) {
objective.setDisplayName(faction.getRelationTo(fPlayer).getColor() + faction.getTag());
List<String> list = P.p.getConfig().getStringList("scoreboard.finfo");
int place = 16; // list.size();
if (list == null) {
P.p.debug(Level.WARNING, "scoreboard.finfo is null :(");
return;
}
for (String s : list) {
String replaced = replace(s);
String awesome = replaced.length() > 16 ? replaced.substring(0, 15) : replaced;
Score score = objective.getScore(awesome);
score.setScore(place);
place--;
if (place < 0) {
break; // Let's not let the scoreboard get too big.
}
}
}
public void cancel() {
}
public Scoreboard getScoreboard() {
return this.scoreboard;
}
}

View File

@ -1,20 +1,103 @@
package com.massivecraft.factions.scoreboards;
import com.massivecraft.factions.FPlayer;
import com.massivecraft.factions.P;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Scoreboard;
public interface FScoreboard {
import java.util.HashMap;
import java.util.Map;
public void apply(Player player);
public class FScoreboard {
private static Map<Player, FScoreboard> fscoreboards = new HashMap<Player, FScoreboard>();
public void remove(Player player);
private final Scoreboard scoreboard;
private final FPlayer fplayer;
private final BufferedObjective bufferedObjective;
private FSidebarProvider defaultProvider;
private FSidebarProvider temporaryProvider;
private boolean removed = false;
public void update(Objective objective);
public void setup();
public Scoreboard getScoreboard();
public void cancel();
public static void init(FPlayer fplayer) {
fscoreboards.put(fplayer.getPlayer(), new FScoreboard(fplayer));
}
public static void remove(FPlayer fplayer) {
fscoreboards.remove(fplayer.getPlayer()).removed = true;
}
public static FScoreboard get(FPlayer player) {
return get(player.getPlayer());
}
public static FScoreboard get(Player player) {
return fscoreboards.get(player);
}
private FScoreboard(FPlayer fplayer) {
this.fplayer = fplayer;
this.scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
this.bufferedObjective = new BufferedObjective(scoreboard);
fplayer.getPlayer().setScoreboard(scoreboard);
}
public void setSidebarVisibility(boolean visible) {
bufferedObjective.setDisplaySlot(visible ? DisplaySlot.SIDEBAR : null);
}
public void setDefaultSidebar(final FSidebarProvider provider, int updateInterval) {
defaultProvider = provider;
if (temporaryProvider == null) {
// We have no temporary provider; update the BufferedObjective!
updateObjective();
}
new BukkitRunnable() {
@Override
public void run() {
if (removed || provider != defaultProvider) {
cancel();
return;
}
if (temporaryProvider == null) {
updateObjective();
}
}
}.runTaskTimer(P.p, updateInterval, updateInterval);
}
public void setTemporarySidebar(final FSidebarProvider provider) {
temporaryProvider = provider;
updateObjective();
new BukkitRunnable() {
@Override
public void run() {
if (removed) {
return;
}
if (temporaryProvider == provider) {
temporaryProvider = null;
updateObjective();
}
}
}.runTaskLater(P.p, P.p.getConfig().getInt("scoreboard.expiration", 7) * 20);
}
private void updateObjective() {
FSidebarProvider provider = temporaryProvider != null ? temporaryProvider : defaultProvider;
if (provider == null) {
bufferedObjective.hide();
} else {
bufferedObjective.setTitle(provider.getTitle(fplayer));
bufferedObjective.setAllLines(provider.getLines(fplayer));
bufferedObjective.flip();
}
}
}

View File

@ -0,0 +1,10 @@
package com.massivecraft.factions.scoreboards;
import com.massivecraft.factions.FPlayer;
import java.util.List;
public abstract class FSidebarProvider {
public abstract String getTitle(FPlayer fplayer);
public abstract List<String> getLines(FPlayer fplayer);
}

View File

@ -0,0 +1,41 @@
package com.massivecraft.factions.scoreboards.sidebar;
import com.massivecraft.factions.FPlayer;
import com.massivecraft.factions.P;
import com.massivecraft.factions.integration.Econ;
import com.massivecraft.factions.scoreboards.FSidebarProvider;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import java.util.List;
import java.util.ListIterator;
public class FDefaultSidebar extends FSidebarProvider {
@Override
public String getTitle(FPlayer fplayer) {
return ChatColor.translateAlternateColorCodes('&', P.p.getConfig().getString("scoreboard.default-title", "i love drt"));
}
@Override
public List<String> getLines(FPlayer fplayer) {
List<String> lines = P.p.getConfig().getStringList("scoreboard.default");
ListIterator<String> it = lines.listIterator();
while (it.hasNext()) {
it.set(replace(fplayer, it.next()));
}
return lines;
}
private String replace(FPlayer fplayer, String s) {
String faction = !fplayer.getFaction().isNone() ? fplayer.getFaction().getTag() : "factionless";
s = s.replace("{name}", fplayer.getName())
.replace("{power}", String.valueOf(fplayer.getPowerRounded()))
.replace("{balance}", String.valueOf(Econ.getFriendlyBalance(fplayer.getPlayer().getUniqueId())))
.replace("{faction}", faction)
.replace("{maxPower}", String.valueOf(fplayer.getPowerMaxRounded()))
.replace("{totalOnline}", String.valueOf(Bukkit.getServer().getOnlinePlayers().size()));
return ChatColor.translateAlternateColorCodes('&', s);
}
}

View File

@ -0,0 +1,47 @@
package com.massivecraft.factions.scoreboards.sidebar;
import com.massivecraft.factions.FPlayer;
import com.massivecraft.factions.Faction;
import com.massivecraft.factions.P;
import com.massivecraft.factions.scoreboards.FSidebarProvider;
import org.bukkit.ChatColor;
import java.util.List;
import java.util.ListIterator;
public class FInfoSidebar extends FSidebarProvider {
private final Faction faction;
public FInfoSidebar(Faction faction) {
this.faction = faction;
}
@Override
public String getTitle(FPlayer fplayer) {
return faction.getRelationTo(fplayer).getColor() + faction.getTag();
}
@Override
public List<String> getLines(FPlayer fplayer) {
List<String> lines = P.p.getConfig().getStringList("scoreboard.finfo");
ListIterator<String> it = lines.listIterator();
while (it.hasNext()) {
it.set(replaceFInfoTags(it.next()));
}
return lines;
}
private String replaceFInfoTags(String s) {
boolean raidable = faction.getLandRounded() > faction.getPower();
FPlayer fLeader = faction.getFPlayerAdmin();
String leader = fLeader == null ? "Server" : fLeader.getName().substring(0, fLeader.getName().length() > 14 ? 13 : fLeader.getName().length());
return ChatColor.translateAlternateColorCodes('&', s.replace("{power}", String.valueOf(faction.getPowerRounded()))
.replace("{online}", String.valueOf(faction.getOnlinePlayers().size()))
.replace("{members}", String.valueOf(faction.getFPlayers().size()))
.replace("{leader}", leader)
.replace("{chunks}", String.valueOf(faction.getLandRounded()))
.replace("{raidable}", String.valueOf(raidable)));
}
}

View File

@ -1,43 +0,0 @@
package com.massivecraft.factions.scoreboards.tasks;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scoreboard.Scoreboard;
/**
* Class that can be used to simply reset a Player's scoreboard in the future.
*/
public class ExpirationTask extends BukkitRunnable {
private String name;
private Scoreboard board;
private Scoreboard former;
public ExpirationTask(String name, Scoreboard scoreboard) {
this.board = scoreboard;
this.name = name;
}
public ExpirationTask(String name, Scoreboard scoreboard, Scoreboard former) {
this.board = scoreboard;
this.name = name;
this.former = former;
}
@Override
public void run() {
Player player = Bukkit.getPlayer(name);
if (player == null) {
return;
}
if (player.getScoreboard().equals(board)) { // In case someone else changed the board.
if(former != null) {
player.setScoreboard(former); // restore their old scoreboard
} else {
player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard());
}
}
}
}

View File

@ -1,26 +0,0 @@
package com.massivecraft.factions.scoreboards.tasks;
import com.massivecraft.factions.scoreboards.FScoreboard;
import java.util.Random;
public class UpdateTask implements Runnable {
private FScoreboard board;
private final Random random;
public UpdateTask(FScoreboard board) {
this.board = board;
this.random = new Random();
}
@Override
public void run() {
board.update(board.getScoreboard().registerNewObjective(getRandomString(), "dummy"));
}
// Just can't be the same as the last one. WHAT ARE THE ODDS
private String getRandomString() {
return String.valueOf(random.nextInt(10000)) + String.valueOf(random.nextInt(10000));
}
}