Skip to content

Instantly share code, notes, and snippets.

@mkotb
Last active December 24, 2022 02:29
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save mkotb/d99eccdcc78a43ffb707 to your computer and use it in GitHub Desktop.
Save mkotb/d99eccdcc78a43ffb707 to your computer and use it in GitHub Desktop.
Non-flickering scoreboard implementation; create scoreboards with ease.
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.*;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class SimpleScoreboard {
private static Map<String, OfflinePlayer> cache = new HashMap<>();
private Scoreboard scoreboard;
private String title;
private Map<String, Integer> scores;
private Objective obj;
private List<Team> teams;
private List<Integer> removed;
private Set<String> updated;
public SimpleScoreboard(String title) {
this.scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
this.title = ChatColor.translateAlternateColorCodes('&', title);
this.scores = new ConcurrentHashMap<>();
this.teams = Collections.synchronizedList(Lists.newArrayList());
this.removed = Lists.newArrayList();
this.updated = Collections.synchronizedSet(new HashSet<>());
}
public void add(String text, Integer score) {
text = ChatColor.translateAlternateColorCodes('&', text);
if (remove(score, text, false) || !scores.containsValue(score)) {
updated.add(text);
}
scores.put(text, score);
}
public boolean remove(Integer score, String text) {
return remove(score, text, true);
}
public boolean remove(Integer score, String n, boolean b) {
String toRemove = get(score, n);
if (toRemove == null)
return false;
scores.remove(toRemove);
if(b)
removed.add(score);
return true;
}
public String get(int score, String n) {
String str = null;
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
if (entry.getValue().equals(score) &&
!entry.getKey().equals(n)) {
str = entry.getKey();
}
}
return str;
}
private Map.Entry<Team, OfflinePlayer> createTeam(String text, int pos) {
Team team;
ChatColor color = ChatColor.values()[pos];
OfflinePlayer result;
if (!cache.containsKey(color.toString()))
cache.put(color.toString(), getOfflinePlayerSkipLookup(color.toString()));
result = cache.get(color.toString());
try {
team = scoreboard.registerNewTeam("text-" + (teams.size() + 1));
} catch (IllegalArgumentException e) {
team = scoreboard.getTeam("text-" + (teams.size()));
}
applyText(team, text, result);
teams.add(team);
return new AbstractMap.SimpleEntry<>(team, result);
}
private void applyText(Team team, String text, OfflinePlayer result) {
Iterator<String> iterator = Splitter.fixedLength(16).split(text).iterator();
String prefix = iterator.next();
team.setPrefix(prefix);
if(!team.hasPlayer(result))
team.addPlayer(result);
if (text.length() > 16) {
String prefixColor = ChatColor.getLastColors(prefix);
String suffix = iterator.next();
if (prefix.endsWith(String.valueOf(ChatColor.COLOR_CHAR))) {
prefix = prefix.substring(0, prefix.length() - 1);
team.setPrefix(prefix);
prefixColor = ChatColor.getByChar(suffix.charAt(0)).toString();
suffix = suffix.substring(1);
}
if (prefixColor == null)
prefixColor = "";
if (suffix.length() > 16) {
suffix = suffix.substring(0, (13 - prefixColor.length())); // cut off suffix, done if text is over 30 characters
}
team.setSuffix((prefixColor.equals("") ? ChatColor.RESET : prefixColor) + suffix);
}
}
public void update() {
if (updated.isEmpty()) {
return;
}
if (obj == null) {
obj = scoreboard.registerNewObjective((title.length() > 16 ? title.substring(0, 15) : title), "dummy");
obj.setDisplayName(title);
obj.setDisplaySlot(DisplaySlot.SIDEBAR);
}
removed.stream().forEach((remove) -> {
for (String s : scoreboard.getEntries()) {
Score score = obj.getScore(s);
if (score == null)
continue;
if (score.getScore() != remove)
continue;
scoreboard.resetScores(s);
}
});
removed.clear();
int index = scores.size();
for (Map.Entry<String, Integer> text : scores.entrySet()) {
Team t = scoreboard.getTeam(ChatColor.values()[text.getValue()].toString());
Map.Entry<Team, OfflinePlayer> team;
if(!updated.contains(text.getKey())) {
continue;
}
if(t != null) {
String color = ChatColor.values()[text.getValue()].toString();
if (!cache.containsKey(color)) {
cache.put(color, getOfflinePlayerSkipLookup(color));
}
team = new AbstractMap.SimpleEntry<>(t, cache.get(color));
applyText(team.getKey(), text.getKey(), team.getValue());
index -= 1;
continue;
} else {
team = createTeam(text.getKey(), text.getValue());
}
Integer score = text.getValue() != null ? text.getValue() : index;
obj.getScore(team.getValue()).setScore(score);
index -= 1;
}
updated.clear();
}
public void setTitle(String title) {
this.title = ChatColor.translateAlternateColorCodes('&', title);
if(obj != null)
obj.setDisplayName(this.title);
}
public void reset() {
for (Team t : teams)
t.unregister();
teams.clear();
scores.clear();
}
public Scoreboard getScoreboard() {
return scoreboard;
}
public void send(Player... players) {
for (Player p : players)
p.setScoreboard(scoreboard);
}
private final UUID invalidUserUUID = UUID.nameUUIDFromBytes("InvalidUsername".getBytes(Charsets.UTF_8));
private Class<?> gameProfileClass;
private Constructor<?> gameProfileConstructor;
private Constructor<?> craftOfflinePlayerConstructor;
@SuppressWarnings("deprecation")
private OfflinePlayer getOfflinePlayerSkipLookup(String name) {
try {
if (gameProfileConstructor == null) {
try { // 1.7
gameProfileClass = Class.forName("net.minecraft.util.com.mojang.authlib.GameProfile");
} catch (ClassNotFoundException e) { // 1.8
gameProfileClass = Class.forName("com.mojang.authlib.GameProfile");
}
gameProfileConstructor = gameProfileClass.getDeclaredConstructor(UUID.class, String.class);
gameProfileConstructor.setAccessible(true);
}
if (craftOfflinePlayerConstructor == null) {
Class<?> serverClass = Bukkit.getServer().getClass();
Class<?> craftOfflinePlayerClass = Class.forName(serverClass.getName()
.replace("CraftServer", "CraftOfflinePlayer"));
craftOfflinePlayerConstructor = craftOfflinePlayerClass.getDeclaredConstructor(
serverClass, gameProfileClass
);
craftOfflinePlayerConstructor.setAccessible(true);
}
Object gameProfile = gameProfileConstructor.newInstance(invalidUserUUID, name);
Object craftOfflinePlayer = craftOfflinePlayerConstructor.newInstance(Bukkit.getServer(), gameProfile);
return (OfflinePlayer) craftOfflinePlayer;
} catch (Throwable t) { // Fallback if fail
return Bukkit.getOfflinePlayer(name);
}
}
}
@058c37a272bed3464d47f0b01038a16a

nice credit 😢

@mkotb
Copy link
Author

mkotb commented Dec 5, 2015

you suck ducks

@058c37a272bed3464d47f0b01038a16a

ok

@Mrtenz
Copy link

Mrtenz commented Dec 27, 2015

It doesn't work on Java 7.

@orkun1675
Copy link

Amazing class! Thank you.
Small bug: Line 195 should read "obj.setDisplayName(this.title);"

@Phineas
Copy link

Phineas commented Jan 25, 2016

@M44rtenz Java 7 doesn't have streams. Idek why you're using Java 7, that's outdated now.

@ItsVinnyX
Copy link

Mine is still flickering 1.8 - 1.9 Any fixes? The community needs this.

@ItsVinnyX
Copy link

ItsVinnyX commented Jun 23, 2016

This is how I am making my scoreboard and it flickers:

`public void hubScoreboard(Player p) {
new BukkitRunnable() {
final ColorScrollPlus cs = new ColorScrollPlus(ChatColor.WHITE, "VenomSurge", "§a", "§f", "§a", true, false,
ScrollType.FORWARD);
ScoreboardAPI scoreboard;

        public void run() {

            if (cs.getScrollType() == ScrollType.FORWARD) {
                if (cs.getPosition() >= cs.getString().length()) {
                    cs.setScrollType(ScrollType.BACKWARD);
                }
            } else {
                if (cs.getPosition() <= -1) {
                    cs.setScrollType(ScrollType.FORWARD);
                }
            }
            scoreboard = new ScoreboardAPI(cs.next());
            scoreboard.add(C.dgray + C.bold + C.strike + "---------------", 7);
            if (plugin.permPlug.getRank(p).equals(RankType.OWNER)) {
                scoreboard.add(C.white + "Rank: " + RankUtil.Owner, 6);
            } else if (plugin.permPlug.getRank(p).equals(RankType.ADMIN)) {
                scoreboard.add(C.white + "Rank: " + RankUtil.Admin, 6);
            } else if (plugin.permPlug.getRank(p).equals(RankType.MOD)) {
                scoreboard.add(C.white + "Rank: " + RankUtil.Mod, 6);
            } else if (plugin.permPlug.getRank(p).equals(RankType.HELPER)) {
                scoreboard.add(C.white + "Rank: " + RankUtil.Helper, 6);
            } else if (plugin.permPlug.getRank(p).equals(RankType.MEMBER)) {
                scoreboard.add(C.white + "Rank: " + C.yellow + C.bold + "MEMBER", 6);
            }
            scoreboard.add(C.white + "Votes: " + C.green + "0", 5);
            scoreboard.add(" ", 4);
            scoreboard.add(C.white + "Lobby: " + C.green + "#1", 3);
            scoreboard.add(C.white + "Online: " + C.green + Bukkit.getOnlinePlayers().size(), 2);
            scoreboard.add(C.dgray + C.bold + C.strike + "---------------", 1);
            scoreboard.update();
            scoreboard.send(p);
        }
    }.runTaskTimer(plugin, 0, 3);
}`

@Fabb111
Copy link

Fabb111 commented Jun 27, 2016

It doesn't work for multiple players, right? (I mean if someone has e.g. 200 Coins and then the scoreboard updates for a player with 500 coins, player 1 & 2 have 500 coins in the scoreboard)

@Cuthmf
Copy link

Cuthmf commented Sep 27, 2016

I'm not getting update:

@eventhandler
public void wow(PlayerJoinEvent e) {
SimpleScoreboard ss = new SimpleScoreboard("WoW");
ss.add("Money: "+ a, 13);
ss.update();
ss.send(e.getPlayer());
new BukkitRunnable() {

        @Override
        public void run() {
            a = new Random().nextInt(900);
                ss.update();
                for (Player p : Bukkit.getServer().getOnlinePlayers()) {
                    ss.update();
                    ss.send(p);
                }


        }
    }.runTaskTimerAsynchronously(this, 20, 10);

@erikzimmermann
Copy link

It works perfect on 1.8, thanks a lot 👍

I just wrote some new code in it ;)

@matt11matthew
Copy link

Anyway I can get this to work for 1.7 with 40 lines I'm guessing using suffix and prefix?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment