Last active
October 25, 2022 08:18
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@ketsusama, it was error in regular expression, try new version