Skip to content

Instantly share code, notes, and snippets.

@serega6531
Last active October 25, 2022 08:18
Show Gist options
  • Save serega6531/4acd23ac188c8c568287 to your computer and use it in GitHub Desktop.
Save serega6531/4acd23ac188c8c568287 to your computer and use it in GitHub Desktop.
A simple based on ProtocolLib no-flashing scoreboard manager with rows up to 32 chars
import com.comphenix.packetwrapper.WrapperPlayServerScoreboardTeam;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.wrappers.EnumWrappers;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scoreboard.*;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* <p>A simple scoreboard manager based on ProtocolLib with for-each-player customization</p>
* <p>Usage:</p>
* <pre>
* {@code NoFlashPacket nf = NoFlashPacket.start(plugin, new ScoreboardTextProvider() {...}, 20);
* nf.addPlayer(player);
* }
* </pre>
* <p>
* <p>Latest version can be found at <a href="https://gist.github.com/serega6531/4acd23ac188c8c568287">my gist</a></p>
* <p>PacketWrapper classes can be found <a href="https://github.com/dmulloy2/PacketWrapper/tree/master/PacketWrapper/src/main/java/com/comphenix/packetwrapper">here</a></p>
*
* @author serega6531
*/
@SuppressWarnings({"unused", "UnusedReturnValue"})
public class NoFlashPacket extends BukkitRunnable {
private Scoreboard board;
private Objective obj;
private Team[] teams;
private ScoreboardTextProvider textProvider;
private List<String> players = new ArrayList<>();
private List<String> rows;
private int max;
private Map<String, List<ScoreboardRow>> prev = null;
private Map<String, List<ScoreboardRow>> send = new HashMap<>();
private static final Pattern regex = Pattern.compile("§[0-9a-f]§r");
private static List<NoFlashPacket> scoreboardHeap = new ArrayList<>();
private NoFlashPacket(Plugin plugin, ScoreboardTextProvider textProvider, int max) {
this.board = Bukkit.getScoreboardManager().getNewScoreboard();
this.textProvider = textProvider;
obj = board.getObjective("noflashpacket");
if (obj == null)
obj = board.registerNewObjective("noflashpacket", "dummy");
setTitle(textProvider.getScoreboardTitle());
obj.setDisplaySlot(DisplaySlot.SIDEBAR);
if (max > 16)
max = 16;
this.max = max;
teams = new Team[max];
for (int i = 0; i < max; i++) {
teams[i] = board.getTeam("team" + (i + 1));
if (teams[i] == null) {
teams[i] = board.registerNewTeam("team" + (i + 1));
teams[i].addPlayer(Bukkit.getOfflinePlayer(ChatColor.values()[i].toString() + ChatColor.RESET));
}
}
setList(textProvider.getScoreboardText());
ProtocolLibrary.getProtocolManager().addPacketListener(
new PacketAdapter(plugin, PacketType.Play.Server.SCOREBOARD_SCORE) {
@Override
public void onPacketSending(PacketEvent event) {
PacketContainer packet = event.getPacket();
if (packet.getScoreboardActions().read(0) == EnumWrappers.ScoreboardAction.REMOVE &&
regex.matcher(packet.getStrings().read(0)).matches() &&
!players.contains(event.getPlayer().getName())) {
event.setCancelled(true);
}
}
}
);
}
private NoFlashPacket setList(List<String> list) {
if (list.size() > max)
list = list.subList(0, max);
rows = new ArrayList<>(list);
return this;
}
private NoFlashPacket setTitle(String title) {
if (title.length() > 32)
title = title.substring(0, 32);
if (!obj.getDisplayName().equals(title))
obj.setDisplayName(title);
return this;
}
@Override
public void run() {
Iterator<String> iter = players.iterator();
while (iter.hasNext()) {
Player p = Bukkit.getPlayerExact(iter.next());
if (p == null)
iter.remove();
}
if (players.size() == 0)
return; //wait until someone shows up
grabNewRows();
prev = new HashMap<>(send);
send.clear();
players.forEach(p -> formatRows(Bukkit.getPlayerExact(p)));
updateRows();
}
private void grabNewRows() {
if (!textProvider.getScoreboardTitle().equals(obj.getDisplayName())) {
setTitle(textProvider.getScoreboardTitle());
}
setList(textProvider.getScoreboardText());
}
private void formatRows(Player p) {
send.put(p.getName(),
rows.stream()
.map(row -> new ScoreboardRow(textProvider.format(p, row)))
.collect(Collectors.toList())
);
}
private void updateRows() {
int size = rows.size() <= max ? rows.size() : max;
for (int i = 0; i < rows.size(); i++) {
updateRow(size, i);
}
for (int i = teams.length; i > rows.size(); i--)
board.resetScores(teams[i - 1].getPlayers().iterator().next());
}
private void updateRow(int size, int num) {
Team team = teams[size - num - 1];
Score score = obj.getScore(team.getPlayers().iterator().next());
players.stream()
.map(Bukkit::getPlayerExact)
.forEach(p -> {
/*if(!send.containsKey(p.getName()))
return;*/
ScoreboardRow row = send.get(p.getName()).get(num);
if(prev == null || !prev.containsKey(p.getName()) || !row.equals(prev.get(p.getName()).get(num))) {
WrapperPlayServerScoreboardTeam wrapper = new WrapperPlayServerScoreboardTeam();
wrapper.setPlayers(Collections.emptyList());
wrapper.setMode(WrapperPlayServerScoreboardTeam.Mode.TEAM_UPDATED);
wrapper.setName(team.getName());
wrapper.setDisplayName(team.getName());
wrapper.setPrefix(row.getPrefix());
wrapper.setSuffix(row.getSuffix());
wrapper.sendPacket(p);
}
});
if (score.getScore() != size - num)
score.setScore(size - num);
}
public boolean hasPlayer(Player p) {
return players.contains(p.getName());
}
/**
* Add player to scoreboard
*
* @param p Player to be added
*/
public void addPlayer(Player p) {
scoreboardHeap.stream()
.filter(sc -> sc.hasPlayer(p))
.forEach(sc -> sc.removePlayer(p));
players.add(p.getName());
formatRows(p);
p.setScoreboard(board);
updateRows();
}
/**
* Remove player from scoreboard. He will be returned to main scoreboard
*
* @param p Player to be removed
*/
public void removePlayer(Player p) {
players.remove(p.getName());
removePlayer0(p);
}
private void removePlayer0(Player p) {
send.remove(p.getName());
p.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
}
/**
* Create scoreboard system with all available parameters
*
* @param pl Plugin for scheduler
* @param textProvider Implementation for ScoreboardTextProvider interface
* @param max Max count of rows in scoreboard (reduction may slightly improve performance), default 16
* @param delay Ticks between scoreboard updating
* @return NoFlash object
*/
public static NoFlashPacket start(Plugin pl, ScoreboardTextProvider textProvider, int max, long delay) {
NoFlashPacket nf = new NoFlashPacket(pl, textProvider, max);
scoreboardHeap.add(nf);
nf.runTaskTimer(pl, 1L, delay);
return nf;
}
/**
* Create scoreboard system with new scoreboard and all 16 scoreboard rows available
*
* @param pl Plugin for scheduler
* @param textProvider Implementation for ScoreboardTextProvider interface
* @param delay Ticks between scoreboard updating
* @return NoFlash object
*/
@SuppressWarnings("SameParameterValue")
public static NoFlashPacket start(Plugin pl, ScoreboardTextProvider textProvider, long delay) {
return start(pl, textProvider, 16, delay);
}
/**
* Stop scoreboard system. All players will be returned to main scoreboard
*/
public void stop() {
this.cancel();
obj.unregister();
Iterator<String> iter = players.iterator();
while (iter.hasNext()) {
String p = iter.next();
iter.remove();
removePlayer0(Bukkit.getPlayerExact(p));
}
scoreboardHeap.remove(this);
}
/**
* Interface to get new title & rows and to format rows for the specified player
*/
public interface ScoreboardTextProvider {
List<String> getScoreboardText();
String getScoreboardTitle();
String format(Player p, String row);
}
private class ScoreboardRow {
private String prefix, suffix;
public ScoreboardRow(String row) {
if (row.length() <= 16) {
prefix = row;
suffix = "";
} else { //up to 16+16, color pair is in single part
int cut = findCutPoint(row);
prefix = row.substring(0, cut);
suffix = continueColors(prefix) + row.substring(cut, row.length());
if (suffix.length() > 16) {
suffix = suffix.substring(0, 16);
}
}
}
private int findCutPoint(String s) {
for (int i = 16; i > 0; i--) {
if (s.charAt(i - 1) == ChatColor.COLOR_CHAR && ChatColor.getByChar(s.charAt(i)) != null)
continue;
return i;
}
return 16;
}
private String continueColors(String prefix) {
ChatColor activeColor = null;
Set<ChatColor> formats = new HashSet<>();
for (int i = 0; i < prefix.length() - 1; i++) {
char c1 = prefix.charAt(i);
char c2 = prefix.charAt(i + 1);
ChatColor color = ChatColor.getByChar(c2);
if (c1 == ChatColor.COLOR_CHAR && color != null) {
if (color == ChatColor.RESET) {
activeColor = null;
formats.clear();
} else if (color.isColor()) {
activeColor = color;
} else {
formats.add(color);
}
}
}
StringBuffer sb = new StringBuffer();
if (activeColor != null)
sb.append(activeColor.toString());
formats.forEach(format -> sb.append(format.toString()));
return sb.toString();
}
public String getPrefix() {
return prefix;
}
public String getSuffix() {
return suffix;
}
@Override
public String toString() {
return prefix + suffix;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ScoreboardRow that = (ScoreboardRow) o;
return prefix.equals(that.prefix) && suffix.equals(that.suffix);
}
@Override
public int hashCode() {
int result = prefix.hashCode();
result = 31 * result + suffix.hashCode();
return result;
}
}
}
@ketsusama
Copy link

Hello serega ,
My methode is probably not good , can you fix my error please
My code

`
public class ScoreboardsManager {

public static void Refresh(final Player player) {
NoFlashPacket nf = NoFlashPacket.start(Main.get(), new ScoreboardTextProvider() {

    @Override
    public String getScoreboardTitle() {
    String title;
    title = "§cEn attente";
    return title;
    }

    @Override
    public List<String> getScoreboardText() {
    final int playerscount = Bukkit.getServer().getOnlinePlayers().size();
    ArrayList<String> list = new ArrayList<>();
    list.add("§fKits: None");
    list.add("§aJoueurs: §f" + playerscount);
    list.add("§7§m--------------");
    return list;
    }

    @Override
    public String format(Player p, String row) {
    p = (Player) Bukkit.getOnlinePlayers();
    row = "16";
    return row;
    }
}, 16, 20);
nf.addPlayer(player);
}

}
`
I have an error with "NoFlashPacket nf = NoFlashPacket.start(Main.get(), new ScoreboardTextProvider() {
"
and console log

Caused by: java.lang.ExceptionInInitializerError at be.ketsu.API.managers.ScoreboardsManager.Refresh(ScoreboardsManager.java:20) ~[?:?] at be.ketsu.API.listeners.PlayerConnectionListener.onJoin(PlayerConnectionListener.java:28) ~[?:?] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_102] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_102] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_102] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_102] at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:301) ~[dev.jar:git-PaperSpigot-6c710c1-ba32592] ... 14 more Caused by: java.util.regex.PatternSyntaxException: Dangling meta character '?' near index 0 ??[0-9a-f]??r ^ at java.util.regex.Pattern.error(Pattern.java:1955) ~[?:1.8.0_102] at java.util.regex.Pattern.sequence(Pattern.java:2123) ~[?:1.8.0_102] at java.util.regex.Pattern.expr(Pattern.java:1996) ~[?:1.8.0_102] at java.util.regex.Pattern.compile(Pattern.java:1696) ~[?:1.8.0_102] at java.util.regex.Pattern.<init>(Pattern.java:1351) ~[?:1.8.0_102] at java.util.regex.Pattern.compile(Pattern.java:1028) ~[?:1.8.0_102] at be.ketsu.API.packets.NoFlashPacket.<clinit>(NoFlashPacket.java:63) ~[?:?] at be.ketsu.API.managers.ScoreboardsManager.Refresh(ScoreboardsManager.java:63) ~[?:?] at be.ketsu.API.listeners.PlayerConnectionListener.onJoin(PlayerConnectionListener.java:28) ~[?:?] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_102] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_102] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_102] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_102] at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:301) ~[dev.jar:git-PaperSpigot-6c710c1-ba32592] ... 14 more
Thanks you in advance

@serega6531
Copy link
Author

@ketsusama, it was error in regular expression, try new version

@serega6531
Copy link
Author

@ketsusama, btw you should create just one scoreboard and add each player using addPlayer().

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