Upload folder via GUI - src
This commit is contained in:
@@ -7,6 +7,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
import net.md_5.bungee.api.event.ServerSwitchEvent;
|
||||
import net.md_5.bungee.api.event.TabCompleteEvent;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
@@ -1217,22 +1218,256 @@ public class ScoreboardModule implements Module, Listener {
|
||||
|
||||
// ── Farb-Hilfsmethoden ────────────────────────────────────────────────────
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// Farb-Parser: Birdflop-kompatibel
|
||||
// Unterstützte Formate (alle gleichzeitig nutzbar):
|
||||
//
|
||||
// &#RRGGBB → Pro-Zeichen Hex (Birdflop Standard-Output)
|
||||
// {#RRGGBB} → Bracket-Format
|
||||
// <#RRGGBB> → MiniMessage Kurzform
|
||||
// <color:#RRGGBB> → MiniMessage color-Tag
|
||||
// <gradient:#C1:#C2:Text> → Farbverlauf (beliebig viele Farb-Stopps)
|
||||
// <shadow:#C:Text> → Text in Schattenfarbe
|
||||
// <b> <i> <u> <st> <obf> → Formatierungen
|
||||
// &l &o &n &m &k &r → Standard-Formatierungen
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
private static String c(String s) {
|
||||
if (s == null) return " ";
|
||||
return ChatColor.translateAlternateColorCodes('&', hexToSection(s));
|
||||
s = parseMiniMessage(s); // MiniMessage-Tags (<gradient:>, <shadow:>, <#>, <color:>, <b> usw.)
|
||||
s = parseHexAmpersand(s); // &#RRGGBB und {#RRGGBB}
|
||||
return net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', s);
|
||||
}
|
||||
|
||||
private static String hexToSection(String text) {
|
||||
if (text == null || !text.contains("&#")) return text == null ? "" : text;
|
||||
private static String stripColors(String s) {
|
||||
return s == null ? "" : net.md_5.bungee.api.ChatColor.stripColor(c(s));
|
||||
}
|
||||
|
||||
// ── MiniMessage Haupt-Dispatcher ─────────────────────────────────────────
|
||||
|
||||
private static String parseMiniMessage(String text) {
|
||||
if (text == null || !text.contains("<")) return text == null ? "" : text;
|
||||
// gradient-Tags als erstes, weil sie anderen Text enthalten können
|
||||
text = parseGradientTags(text);
|
||||
// shadow-Tags
|
||||
text = parseShadowTags(text);
|
||||
// Einfache Tags: <color:#>, <#>, <b>, <i>, <u>, <st>, <obf>, </...>
|
||||
text = parseSimpleTags(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
// ── <gradient:#C1:#C2:...:TEXT> ──────────────────────────────────────────
|
||||
|
||||
private static String parseGradientTags(String text) {
|
||||
if (!text.contains("<gradient:")) return text;
|
||||
StringBuilder result = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
int start = text.indexOf("<gradient:", i);
|
||||
if (start < 0) { result.append(text, i, text.length()); break; }
|
||||
result.append(text, i, start);
|
||||
// Schließendes > suchen (mit Tiefenzähler für verschachtelte <...>)
|
||||
int end = findClosingAngle(text, start + 1);
|
||||
if (end < 0) { result.append(text, i, text.length()); break; }
|
||||
String inner = text.substring(start + 1, end); // "gradient:#C1:#C2:TEXT"
|
||||
result.append(applyGradientTag(inner));
|
||||
i = end + 1;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parst "gradient:#C1:#C2:#C3:TEXT" → eingefärbten Text.
|
||||
* TEXT darf selbst wieder §-Codes oder &-Codes enthalten (z.B. &l für Bold).
|
||||
*/
|
||||
private static String applyGradientTag(String inner) {
|
||||
// inner = "gradient:COLOR:COLOR:...:TEXT"
|
||||
// Farben beginnen mit # oder mit & gefolgt von einem Hex-Code
|
||||
java.util.List<String> colors = new java.util.ArrayList<>();
|
||||
// Trenne am ersten Doppelpunkt nach "gradient"
|
||||
int firstColon = inner.indexOf(':'); // nach "gradient"
|
||||
if (firstColon < 0) return inner;
|
||||
String rest = inner.substring(firstColon + 1);
|
||||
|
||||
// Lese Farb-Stopps (jeder Teil beginnt mit #)
|
||||
// TEXT ist alles ab dem ersten Teil der NICHT mit # beginnt
|
||||
StringBuilder textSb = new StringBuilder();
|
||||
boolean inText = false;
|
||||
String[] parts = rest.split(":", -1);
|
||||
for (int p = 0; p < parts.length; p++) {
|
||||
String part = parts[p];
|
||||
if (!inText && part.startsWith("#") && part.length() == 7) {
|
||||
colors.add(part);
|
||||
} else {
|
||||
// Ab hier Text (inkl. Doppelpunkte wieder zusammensetzen)
|
||||
inText = true;
|
||||
if (textSb.length() > 0) textSb.append(":");
|
||||
textSb.append(part);
|
||||
}
|
||||
}
|
||||
if (colors.size() < 2) return textSb.toString();
|
||||
|
||||
// Shadow-Tags im Text zuerst auflösen (können im Gradient-Text stecken)
|
||||
String rawText = parseShadowTags(textSb.toString());
|
||||
return applyGradient(rawText, colors);
|
||||
}
|
||||
|
||||
private static String applyGradient(String text, java.util.List<String> colorStops) {
|
||||
if (text == null || text.isEmpty()) return text;
|
||||
// §-Codes und &-Codes aus Text herausfiltern für Längenberechnung
|
||||
String plain = text
|
||||
.replaceAll("\u00A7[0-9a-fk-orx]", "")
|
||||
.replaceAll("&[0-9a-fA-Fk-orK-OR]", "")
|
||||
.replaceAll("\u00A7x(\u00A7[0-9a-fA-F]){6}", ""); // §x§R§R§G§G§B§B
|
||||
int len = plain.length();
|
||||
if (len == 0) return text;
|
||||
if (len == 1) return resolveColorToSection(colorStops.get(0)) + text;
|
||||
|
||||
int[][] rgbStops = new int[colorStops.size()][3];
|
||||
for (int s = 0; s < colorStops.size(); s++) rgbStops[s] = hexToRgb(colorStops.get(s));
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
int charIdx = 0;
|
||||
int ci = 0;
|
||||
while (ci < text.length()) {
|
||||
char ch = text.charAt(ci);
|
||||
|
||||
// §x§R§R§G§G§B§B durchreichen (bereits aufgelöste Hex-Farbe z.B. von shadow)
|
||||
if (ch == '\u00A7' && ci + 1 < text.length() && text.charAt(ci + 1) == 'x') {
|
||||
// Lese die 12 folgenden Zeichen (§x + 6x §digit)
|
||||
if (ci + 13 < text.length() + 1) {
|
||||
result.append(text, ci, Math.min(ci + 14, text.length()));
|
||||
ci = Math.min(ci + 14, text.length());
|
||||
} else {
|
||||
result.append(ch); ci++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// §-Formatcode durchreichen
|
||||
if (ch == '\u00A7' && ci + 1 < text.length()) {
|
||||
result.append(ch).append(text.charAt(ci + 1)); ci += 2; continue;
|
||||
}
|
||||
// &-Formatcode durchreichen
|
||||
if (ch == '&' && ci + 1 < text.length() && "&0123456789abcdefABCDEFklmnorKLMNOR".indexOf(text.charAt(ci+1)) >= 0) {
|
||||
result.append(ch).append(text.charAt(ci + 1)); ci += 2; continue;
|
||||
}
|
||||
|
||||
// Normales Zeichen → Farbe interpolieren
|
||||
float t = len <= 1 ? 0f : (float) charIdx / (len - 1);
|
||||
int segments = colorStops.size() - 1;
|
||||
float scaled = t * segments;
|
||||
int seg = Math.min((int) scaled, segments - 1);
|
||||
float segT = scaled - seg;
|
||||
int[] c1 = rgbStops[seg], c2 = rgbStops[seg + 1];
|
||||
int r = clamp((int)(c1[0] + (c2[0] - c1[0]) * segT));
|
||||
int g = clamp((int)(c1[1] + (c2[1] - c1[1]) * segT));
|
||||
int b = clamp((int)(c1[2] + (c2[2] - c1[2]) * segT));
|
||||
String hex = String.format("%02X%02X%02X", r, g, b);
|
||||
appendHexSection(result, hex);
|
||||
result.append(ch);
|
||||
charIdx++;
|
||||
ci++;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// ── <shadow:#RRGGBB:TEXT> ─────────────────────────────────────────────────
|
||||
|
||||
private static String parseShadowTags(String text) {
|
||||
if (text == null || !text.contains("<shadow:")) return text == null ? "" : text;
|
||||
StringBuilder result = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
int start = text.indexOf("<shadow:", i);
|
||||
if (start < 0) { result.append(text, i, text.length()); break; }
|
||||
result.append(text, i, start);
|
||||
int end = findClosingAngle(text, start + 1);
|
||||
if (end < 0) { result.append(text, i, text.length()); break; }
|
||||
String inner = text.substring(start + 1, end); // "shadow:#RRGGBB:TEXT"
|
||||
// Format: shadow:COLOR:TEXT
|
||||
int firstColon = inner.indexOf(':');
|
||||
int secondColon = firstColon >= 0 ? inner.indexOf(':', firstColon + 1) : -1;
|
||||
if (firstColon < 0 || secondColon < 0) { result.append(text, i, end + 1); i = end + 1; continue; }
|
||||
String colorPart = inner.substring(firstColon + 1, secondColon).trim();
|
||||
String content = inner.substring(secondColon + 1);
|
||||
result.append(resolveColorToSection(colorPart)).append(content);
|
||||
i = end + 1;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// ── Einfache MiniMessage-Tags ─────────────────────────────────────────────
|
||||
|
||||
private static String parseSimpleTags(String text) {
|
||||
if (text == null || !text.contains("<")) return text == null ? "" : text;
|
||||
// Ersetzungstabelle
|
||||
text = text.replace("<b>", "&l").replace("</b>", "&r");
|
||||
text = text.replace("<i>", "&o").replace("</i>", "&r");
|
||||
text = text.replace("<u>", "&n").replace("</u>", "&r");
|
||||
text = text.replace("<st>", "&m").replace("</st>", "&r");
|
||||
text = text.replace("<obf>", "&k").replace("</obf>", "&r");
|
||||
text = text.replace("<reset>", "&r").replace("</reset>", "");
|
||||
// Closing-Tags entfernen (werden nach Verarbeitung nicht mehr benötigt)
|
||||
text = text.replaceAll("</gradient>", "");
|
||||
text = text.replaceAll("</shadow>", "");
|
||||
text = text.replaceAll("</color>", "");
|
||||
// <color:#RRGGBB> und <#RRGGBB>
|
||||
StringBuilder result = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
char ch = text.charAt(i);
|
||||
if (ch != '<') { result.append(ch); i++; continue; }
|
||||
// <color:#RRGGBB>
|
||||
if (text.startsWith("<color:#", i)) {
|
||||
int end = text.indexOf('>', i);
|
||||
if (end > 0) {
|
||||
String hex = text.substring(i + 7, end).trim();
|
||||
if (hex.startsWith("#") && hex.length() == 7 && hex.substring(1).matches("[0-9a-fA-F]{6}")) {
|
||||
appendHexSection(result, hex.substring(1));
|
||||
i = end + 1; continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// <#RRGGBB>
|
||||
if (text.startsWith("<#", i) && i + 9 <= text.length()) {
|
||||
int end = text.indexOf('>', i);
|
||||
if (end == i + 8) {
|
||||
String hex = text.substring(i + 2, end);
|
||||
if (hex.matches("[0-9a-fA-F]{6}")) {
|
||||
appendHexSection(result, hex);
|
||||
i = end + 1; continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.append(ch); i++;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// ── &#RRGGBB und {#RRGGBB} ───────────────────────────────────────────────
|
||||
|
||||
private static String parseHexAmpersand(String text) {
|
||||
if (text == null) return "";
|
||||
if (!text.contains("&#") && !text.contains("{#")) return text;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
if (i + 7 <= text.length() && text.charAt(i) == '&' && text.charAt(i+1) == '#') {
|
||||
// &#RRGGBB
|
||||
if (i + 7 < text.length() + 1 && i + 8 <= text.length()
|
||||
&& text.charAt(i) == '&' && text.charAt(i+1) == '#') {
|
||||
String hex = text.substring(i+2, i+8);
|
||||
if (hex.matches("[0-9a-fA-F]{6}")) {
|
||||
sb.append('\u00A7').append('x');
|
||||
for (char ch : hex.toCharArray()) sb.append('\u00A7').append(ch);
|
||||
i += 8; continue;
|
||||
appendHexSection(sb, hex); i += 8; continue;
|
||||
}
|
||||
}
|
||||
// {#RRGGBB}
|
||||
if (i + 8 < text.length() && text.charAt(i) == '{' && text.charAt(i+1) == '#') {
|
||||
int end = text.indexOf('}', i+2);
|
||||
if (end == i + 8) {
|
||||
String hex = text.substring(i+2, i+8);
|
||||
if (hex.matches("[0-9a-fA-F]{6}")) {
|
||||
appendHexSection(sb, hex); i += 9; continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append(text.charAt(i++));
|
||||
@@ -1240,17 +1475,65 @@ public class ScoreboardModule implements Module, Listener {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String stripColors(String s) {
|
||||
return s == null ? "" : ChatColor.stripColor(c(s));
|
||||
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
|
||||
|
||||
private static void appendHexSection(StringBuilder sb, String hex) {
|
||||
sb.append('\u00A7').append('x');
|
||||
for (char ch : hex.toCharArray()) sb.append('\u00A7').append(ch);
|
||||
}
|
||||
|
||||
private static String resolveColorToSection(String color) {
|
||||
if (color == null) return "";
|
||||
color = color.trim();
|
||||
if (color.startsWith("#") && color.length() == 7
|
||||
&& color.substring(1).matches("[0-9a-fA-F]{6}")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
appendHexSection(sb, color.substring(1));
|
||||
return sb.toString();
|
||||
}
|
||||
if (color.startsWith("&") && color.length() == 2) return "\u00A7" + color.charAt(1);
|
||||
return color;
|
||||
}
|
||||
|
||||
private static int[] hexToRgb(String color) {
|
||||
String hex = color == null ? "" : color.trim();
|
||||
if (hex.startsWith("#")) hex = hex.substring(1);
|
||||
if (hex.length() != 6) return new int[]{255, 255, 255};
|
||||
try {
|
||||
return new int[]{
|
||||
Integer.parseInt(hex.substring(0,2), 16),
|
||||
Integer.parseInt(hex.substring(2,4), 16),
|
||||
Integer.parseInt(hex.substring(4,6), 16)
|
||||
};
|
||||
} catch (Exception e) { return new int[]{255,255,255}; }
|
||||
}
|
||||
|
||||
private static int clamp(int v) { return Math.max(0, Math.min(255, v)); }
|
||||
|
||||
/**
|
||||
* Findet das schließende '>' für ein Tag das bei fromIndex beginnt.
|
||||
* Berücksichtigt verschachtelte <...>.
|
||||
*/
|
||||
private static int findClosingAngle(String text, int fromIndex) {
|
||||
int depth = 0;
|
||||
for (int i = fromIndex; i < text.length(); i++) {
|
||||
char ch = text.charAt(i);
|
||||
if (ch == '<') depth++;
|
||||
else if (ch == '>') { if (depth == 0) return i; depth--; }
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ── Config ───────────────────────────────────────────────────────────────
|
||||
|
||||
private void ensureConfigExists() {
|
||||
File f = new File(plugin.getDataFolder(), CONFIG_FILE);
|
||||
if (f.exists()) return;
|
||||
if (!plugin.getDataFolder().exists()) plugin.getDataFolder().mkdirs();
|
||||
String content =
|
||||
String content =
|
||||
"# ScoreboardModule Konfiguration\n" +
|
||||
"# Platzhalter Spieler: %player% %rank% %money% %server% %compass% %health% %hearts% %date%\n" +
|
||||
"# %ping% %online% %maxplayers% %time% %playtime% %news%\n" +
|
||||
@@ -1258,59 +1541,59 @@ public class ScoreboardModule implements Module, Listener {
|
||||
"# Platzhalter Admin: %tps% %ram% %proxymem% %uptime% %servers%\n" +
|
||||
"# Gradient: %gradient:FARBE1:FARBE2:TEXT%\n" +
|
||||
"# Sonstiges: %line%\n" +
|
||||
"# Farben: &-Codes und Hex &#FF6600\n\n" +
|
||||
"# Farben: &-Codes und Hex &#FF6600\n" +
|
||||
"\n" +
|
||||
"scoreboard.enabled=true\n" +
|
||||
"scoreboard.update_interval=500\n" +
|
||||
"scoreboard.title=&lViper Network\n" +
|
||||
"scoreboard.admin_title=&l[Admin] Panel\n" +
|
||||
"scoreboard.supporter_title=&l[Support] Panel\n\n" +
|
||||
"scoreboard.supporter_title=&l[Support] Panel\n" +
|
||||
"\n" +
|
||||
"scoreboard.ticker.text=\n" +
|
||||
"scoreboard.ticker.width=26\n" +
|
||||
"scoreboard.ticker.speed=1\n\n" +
|
||||
"scoreboard.ticker.speed=1\n" +
|
||||
"\n" +
|
||||
"scoreboard.rainbow.enabled=true\n" +
|
||||
"# wave=fließende Welle, chars=Regenbogen pro Buchstabe, line=eine Farbe\n" +
|
||||
"scoreboard.rainbow.mode=wave\n" +
|
||||
"# Wellengeschwindigkeit: 1=sehr langsam, 10=normal, 50=schnell\n" +
|
||||
"scoreboard.rainbow.speed=10\n" +
|
||||
"# Leer = voller HSB-Regenbogen\n" +
|
||||
"scoreboard.rainbow.colors=#FF0000,#FF6600,#FFFF00,#00FF00,#00FFFF,#0000FF,#FF00FF\n\n" +
|
||||
"scoreboard.rainbow.colors=#FF0000,#FF6600,#FFFF00,#00FF00,#00FFFF,#0000FF,#FF00FF\n" +
|
||||
"\n" +
|
||||
"scoreboard.admin_permission=statusapi.scoreboard.admin\n" +
|
||||
"scoreboard.supporter_permission=statusapi.scoreboard.supporter\n\n" +
|
||||
"scoreboard.supporter_permission=statusapi.scoreboard.supporter\n" +
|
||||
"\n" +
|
||||
"scoreboard.time_format=HH:mm\n" +
|
||||
"scoreboard.date_format=dd.MM.yyyy\n" +
|
||||
"scoreboard.timezone=Europe/Berlin\n" +
|
||||
"scoreboard.money_format=#,##0.00\n" +
|
||||
"scoreboard.money_decimal_separator=,\n\n" +
|
||||
"scoreboard.money_decimal_separator=,\n" +
|
||||
"\n" +
|
||||
"# SEPARATOR – wird als %line% Placeholder genutzt\n" +
|
||||
"# scoreboard.separator=&8&m-------------------- (Standard)\n" +
|
||||
"# scoreboard.separator=&8&m==================== (Doppelt)\n" +
|
||||
"# scoreboard.separator=&8&m~~~~~~~~~~~~~~~~~~~~ (Wellig)\n" +
|
||||
"# scoreboard.separator=&8&m──────────────────── (Duenn)\n" +
|
||||
"# scoreboard.separator=&8&m════════════════════ (Dick)\n" +
|
||||
"# scoreboard.separator=%gradient:&8:&7:────────────────────% (Gradient)\n" +
|
||||
"# scoreboard.separator= (Leer)\n" +
|
||||
"scoreboard.separator=&8&m--------------------\n" +
|
||||
"# News-Ticker (erscheint als %news% Placeholder)\n" +
|
||||
"scoreboard.news.text=&eWillkommen auf Viper Network!\n" +
|
||||
"scoreboard.news.prefix=&8[&6News&8] &r\n" +
|
||||
"scoreboard.news.width=20\n" +
|
||||
"scoreboard.news.speed=1\n\n" +
|
||||
"scoreboard.news.speed=1\n" +
|
||||
"\n" +
|
||||
"scoreboard.rotation_interval=4\n" +
|
||||
"# ===================================================\n" +
|
||||
"# ZEILEN - max 15 sichtbar\n" +
|
||||
"# ===================================================\n" +
|
||||
"scoreboard.lines.1=%line%\n" +
|
||||
"scoreboard.lines.2=%gradient:&b:&f:&b:&l> Player Info:%\n" +
|
||||
"scoreboard.lines.2=%gradient:&6:&f:&6:&l> Player Info:%\n" +
|
||||
"scoreboard.lines.3=&7%rank% &f%player%\n" +
|
||||
"scoreboard.lines.4=\n" +
|
||||
"scoreboard.lines.5=&7Spielzeit: &f%playtime%\n" +
|
||||
"scoreboard.lines.5.2=&7Leben: &c%health%\n" +
|
||||
"scoreboard.lines.5.3=&7Hunger: B4513%foodsym%\n" +
|
||||
"scoreboard.lines.6=\n" +
|
||||
"scoreboard.lines.7=%gradient:&b:&f:&b:&l> Money:%\n" +
|
||||
"scoreboard.lines.7=%gradient:&6:&f:&6:&l> Money:%\n" +
|
||||
"scoreboard.lines.8=&a$%money%\n" +
|
||||
"scoreboard.lines.9=\n" +
|
||||
"scoreboard.lines.10=%gradient:&b:&f:&b:&l> Server Info:%\n" +
|
||||
"scoreboard.lines.10=%gradient:&6:&f:&6:&l> Server Info:%\n" +
|
||||
"scoreboard.lines.11=&f%server%\n" +
|
||||
"scoreboard.lines.11.2=&7Ping: &f%ping%ms &8| &7Online: &f%online%\n" +
|
||||
"scoreboard.lines.12=\n" +
|
||||
@@ -1321,13 +1604,13 @@ public class ScoreboardModule implements Module, Listener {
|
||||
"# ADMIN-ZEILEN\n" +
|
||||
"# ===================================================\n" +
|
||||
"scoreboard.admin_lines.1=%line%\n" +
|
||||
"scoreboard.admin_lines.2=%gradient:&b:&f:&b:&l> Player Info:%\n" +
|
||||
"scoreboard.admin_lines.2=%gradient:&6:&f:&6:&l> Player Info:%\n" +
|
||||
"scoreboard.admin_lines.3=&7%rank% &f%player%\n" +
|
||||
"scoreboard.admin_lines.4=&7Gamemode: &f%gamemode%\n" +
|
||||
"scoreboard.admin_lines.5=&7Leben: &c%health%\n" +
|
||||
"scoreboard.admin_lines.5.2=&7Hunger: B4513%foodsym%\n" +
|
||||
"scoreboard.admin_lines.6=\n" +
|
||||
"scoreboard.admin_lines.7=%gradient:&b:&f:&b:&l> Server Info:%\n" +
|
||||
"scoreboard.admin_lines.7=%gradient:&6:&f:&6:&l> Server Info:%\n" +
|
||||
"scoreboard.admin_lines.8=&f%server% &8| &7RAM: &e%ram%\n" +
|
||||
"scoreboard.admin_lines.8.2=&7Proxy: &f%uptime%\n" +
|
||||
"scoreboard.admin_lines.9=\n" +
|
||||
@@ -1355,8 +1638,9 @@ public class ScoreboardModule implements Module, Listener {
|
||||
"scoreboard.supporter_lines.12=&7Zeit: &f%time%\n" +
|
||||
"scoreboard.supporter_lines.13=\n" +
|
||||
"scoreboard.supporter_lines.14=%line%\n" +
|
||||
"scoreboard.supporter_lines.15=&7%compass%\n";
|
||||
try (OutputStream out = new FileOutputStream(f)) {
|
||||
"scoreboard.supporter_lines.15=&7%compass%\n" +
|
||||
"";
|
||||
try (OutputStream out = new FileOutputStream(f)) {
|
||||
out.write(content.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[ScoreboardModule] Config: " + e.getMessage());
|
||||
@@ -1554,6 +1838,35 @@ public class ScoreboardModule implements Module, Listener {
|
||||
*
|
||||
* Aliase: /sb, /togglesb
|
||||
*/
|
||||
private static final List<String> SB_SUBS = Arrays.asList("hide", "show", "player", "admin", "supporter");
|
||||
|
||||
/** Tab-Completion für /scoreboard via TabCompleteEvent */
|
||||
@EventHandler
|
||||
public void onTabComplete(TabCompleteEvent event) {
|
||||
if (!(event.getSender() instanceof ProxiedPlayer)) return;
|
||||
String cursor = event.getCursor();
|
||||
if (cursor == null) return;
|
||||
String lower = cursor.toLowerCase();
|
||||
boolean match = lower.startsWith("/scoreboard ") || lower.startsWith("/sb ")
|
||||
|| lower.startsWith("/togglesb ");
|
||||
if (!match) return;
|
||||
|
||||
ProxiedPlayer p = (ProxiedPlayer) event.getSender();
|
||||
int spaceIdx = cursor.indexOf(' ');
|
||||
String typed = spaceIdx >= 0 ? cursor.substring(spaceIdx + 1).toLowerCase() : "";
|
||||
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
for (String sub : SB_SUBS) {
|
||||
// Supporter und Admin nur anzeigen wenn Berechtigung vorhanden
|
||||
if (sub.equals("admin") && !p.hasPermission(adminPermission)) continue;
|
||||
if (sub.equals("supporter") && !p.hasPermission(supporterPermission)
|
||||
&& !p.hasPermission(adminPermission)) continue;
|
||||
if (sub.startsWith(typed)) suggestions.add(sub);
|
||||
}
|
||||
event.getSuggestions().clear();
|
||||
event.getSuggestions().addAll(suggestions);
|
||||
}
|
||||
|
||||
private class ScoreboardToggleCommand extends Command {
|
||||
|
||||
ScoreboardToggleCommand() {
|
||||
@@ -1653,4 +1966,5 @@ public class ScoreboardModule implements Module, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -23,12 +23,14 @@ import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.awt.Color;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TablistModule implements Module, Listener {
|
||||
|
||||
private static final String CONFIG_FILE = "tablist.properties";
|
||||
private static final String CONFIG_FILE = "tablist.properties";
|
||||
// ── NEU: Server-Symbol-Config ──────────────────────────────────────────────
|
||||
|
||||
// Leerer Skin (grauer Kopf) für Platzhalter-Slots
|
||||
private static final net.md_5.bungee.protocol.data.Property[] EMPTY_SKIN = {
|
||||
@@ -48,6 +50,18 @@ public class TablistModule implements Module, Listener {
|
||||
// Skin-Cache (pro Spieler)
|
||||
private final ConcurrentHashMap<UUID, net.md_5.bungee.protocol.data.Property[]> skinCache = new ConcurrentHashMap<>();
|
||||
|
||||
// ── NEU: Server-Symbol-Map (serverName lowercase → colored symbol string) ─
|
||||
private final Map<String, String> serverSymbols = new LinkedHashMap<>();
|
||||
|
||||
// ── NEU: Spalten-Header-Modus ──────────────────────────────────────────────
|
||||
// "full" → bisheriges Verhalten: Spalten-Header belegt Zeile 0 (große Markierung)
|
||||
// "none" → kein Header; Zeile 0 ist frei für Spieler oder bleibt leer
|
||||
// "small" → kein Slot-Header, aber der Spalten-Name erscheint im Tab-Header/Footer
|
||||
// (empfohlen für MuckiDEE: kleine Markierungen bleiben, große weg)
|
||||
private String columnHeaderMode = "none"; // default: keine großen Markierungen
|
||||
// player_display: "server" = Server-basiert (default) | "custom" = alle zusammen, links→rechts nach Rang
|
||||
private String playerDisplayMode = "server";
|
||||
|
||||
// Config
|
||||
private boolean enabled = true;
|
||||
private int updateInterval = 5;
|
||||
@@ -63,19 +77,24 @@ public class TablistModule implements Module, Listener {
|
||||
private String compactHeader1 = "&6&lViper Network &8• &7%online% Spieler online";
|
||||
private String compactHeader2 = "";
|
||||
private String compactHeader3 = "";
|
||||
private boolean compactHeader1Spacer = false;
|
||||
private boolean compactHeader2Spacer = false;
|
||||
private boolean compactHeader3Spacer = false;
|
||||
private String compactFooter1 = "";
|
||||
private String compactFooter2 = "&7Zeit: &f%time% &8| &7Spieler: &f%online% &8| &7Ping: &f%ping%ms";
|
||||
private String compactFooter3 = "&7Kontostand: &a$%balance% &8| &7Server: &f%server% &8| &7Welt: &f%world%";
|
||||
private String compactFooter4 = "";
|
||||
private String compactFooter5 = "";
|
||||
private String compactFooter6 = "";
|
||||
private boolean compactFooter1Spacer = false;
|
||||
private boolean compactFooter2Spacer = false;
|
||||
private boolean compactFooter3Spacer = false;
|
||||
private boolean compactFooter4Spacer = false;
|
||||
private boolean compactFooter5Spacer = false;
|
||||
private boolean compactFooter6Spacer = false;
|
||||
|
||||
private String colorSrvHeader = "&6&l";
|
||||
private boolean showFooterServerList = true;
|
||||
private String columnHeaderMode = "none";
|
||||
private final Map<String, String> serverSymbols = new LinkedHashMap<>();
|
||||
private boolean showFooterServerList = true; // tablist.compact.footer.serverlist=true/false
|
||||
private String timeFormat = "HH:mm:ss / h:mm a";
|
||||
private String timeZone = "Europe/Berlin";
|
||||
private SimpleDateFormat sdf;
|
||||
@@ -129,7 +148,8 @@ public class TablistModule implements Module, Listener {
|
||||
plugin.getLogger().info("[TablistModule] Tablist-Spalten: " + getServerOrder());
|
||||
recalculateGrid();
|
||||
}, 3L, TimeUnit.SECONDS);
|
||||
plugin.getLogger().info("[TablistModule] Aktiviert. Grid=" + columns + "x" + rows + " layout=" + layoutMode);
|
||||
plugin.getLogger().info("[TablistModule] Aktiviert. Grid=" + columns + "x" + rows + " layout=" + layoutMode
|
||||
+ " column_header=" + columnHeaderMode + " symbols=" + serverSymbols.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -150,13 +170,12 @@ public class TablistModule implements Module, Listener {
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
if (configuredTabSize > 0) tabSize = configuredTabSize; // manuell gesetzt in tablist.properties
|
||||
if (configuredTabSize > 0) tabSize = configuredTabSize;
|
||||
tabSizeMax = tabSize;
|
||||
rows = ROWS; // immer 20 – Minecraft-Client-Pflicht
|
||||
rows = ROWS;
|
||||
boolean hasInfo = !"compact".equalsIgnoreCase(layoutMode);
|
||||
int serverCount = getServerOrder().size();
|
||||
int needed = (hasInfo ? 1 : 0) + Math.max(1, serverCount);
|
||||
// Spalten = benötigte Spalten, aber max was tab-size erlaubt (tab-size/20)
|
||||
columns = Math.max(hasInfo ? 2 : 1, Math.min(needed, tabSize / ROWS));
|
||||
total = ROWS * columns;
|
||||
if (needed > tabSize / ROWS) {
|
||||
@@ -182,7 +201,6 @@ public class TablistModule implements Module, Listener {
|
||||
if (skin != null && skin.length > 0) skinCache.put(p.getUniqueId(), skin);
|
||||
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
|
||||
updateTablist(p);
|
||||
// Nach 2s nochmals für alle damit der neue Spieler mit Kopf erscheint
|
||||
ProxyServer.getInstance().getScheduler().schedule(plugin, this::updateAll, 2L, TimeUnit.SECONDS);
|
||||
}, 2L, TimeUnit.SECONDS);
|
||||
}
|
||||
@@ -192,25 +210,18 @@ public class TablistModule implements Module, Listener {
|
||||
if (!enabled) return;
|
||||
ProxiedPlayer switched = e.getPlayer();
|
||||
|
||||
// Skin sofort cachen (noch auf dem alten Server, LoginProfile noch verfügbar)
|
||||
net.md_5.bungee.protocol.data.Property[] skin = fetchSkin(switched);
|
||||
if (skin != null && skin.length > 0) skinCache.put(switched.getUniqueId(), skin);
|
||||
|
||||
// Nach 1s: alle Fake-Slots bei allen Viewern entfernen → erzwingt frisches ADD_PLAYER mit neuem Skin
|
||||
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
|
||||
// Skin nochmals versuchen (jetzt auf neuem Server)
|
||||
net.md_5.bungee.protocol.data.Property[] freshSkin = fetchSkin(switched);
|
||||
if (freshSkin != null && freshSkin.length > 0) skinCache.put(switched.getUniqueId(), freshSkin);
|
||||
|
||||
// Alle Slots bei allen Viewern entfernen
|
||||
for (ProxiedPlayer viewer : ProxyServer.getInstance().getPlayers()) {
|
||||
try { removeFakeSlots(viewer); } catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// Sofort neu aufbauen (kein weiterer Delay nötig da removeFakeSlots synchron ist)
|
||||
updateAll();
|
||||
|
||||
// Nochmal nach 2s als Sicherheit
|
||||
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
|
||||
net.md_5.bungee.protocol.data.Property[] s2 = fetchSkin(switched);
|
||||
if (s2 != null && s2.length > 0) skinCache.put(switched.getUniqueId(), s2);
|
||||
@@ -227,8 +238,6 @@ public class TablistModule implements Module, Listener {
|
||||
public void onDisconnect(PlayerDisconnectEvent e) {
|
||||
if (!enabled) return;
|
||||
skinCache.remove(e.getPlayer().getUniqueId());
|
||||
// Erst alle Fake-Slots entfernen, dann nach kurzer Pause neu aufbauen
|
||||
// So verschwindet der Kopf des Spielers zuverlässig
|
||||
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
|
||||
for (ProxiedPlayer viewer : ProxyServer.getInstance().getPlayers()) {
|
||||
try { removeFakeSlots(viewer); } catch (Exception ignored) {}
|
||||
@@ -242,7 +251,6 @@ public class TablistModule implements Module, Listener {
|
||||
|
||||
private void updateAll() {
|
||||
recalculateGrid();
|
||||
// Fehlende Skins nachladen
|
||||
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||
if (!skinCache.containsKey(p.getUniqueId())) {
|
||||
net.md_5.bungee.protocol.data.Property[] skin = fetchSkin(p);
|
||||
@@ -254,10 +262,15 @@ public class TablistModule implements Module, Listener {
|
||||
|
||||
private void recalculateGrid() {
|
||||
boolean hasInfo = !"compact".equalsIgnoreCase(layoutMode);
|
||||
int serverCount = getServerOrder().size();
|
||||
int needed = (hasInfo ? 1 : 0) + Math.max(1, serverCount);
|
||||
// Spalten = benötigte Spalten, aber max was tab-size erlaubt (tab-size/20)
|
||||
int newColumns = Math.max(hasInfo ? 2 : 1, Math.min(needed, tabSizeMax / ROWS));
|
||||
int newColumns;
|
||||
if ("custom".equalsIgnoreCase(playerDisplayMode)) {
|
||||
// Custom-Modus: immer 3 Spalten (konfigurierbar via tab_size)
|
||||
newColumns = Math.min(3, tabSizeMax / ROWS);
|
||||
} else {
|
||||
int serverCount = getServerOrder().size();
|
||||
int needed = (hasInfo ? 1 : 0) + Math.max(1, serverCount);
|
||||
newColumns = Math.max(hasInfo ? 2 : 1, Math.min(needed, tabSizeMax / ROWS));
|
||||
}
|
||||
int newTotal = ROWS * newColumns;
|
||||
if (newColumns == columns && newTotal == total) return;
|
||||
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||
@@ -267,7 +280,7 @@ public class TablistModule implements Module, Listener {
|
||||
columns = newColumns;
|
||||
total = newTotal;
|
||||
initUuids();
|
||||
plugin.getLogger().info("[TablistModule] Grid: " + columns + "x" + rows + "=" + total + " (" + serverCount + " Server)");
|
||||
plugin.getLogger().info("[TablistModule] Grid: " + columns + "x" + rows + "=" + total + " (" + getServerOrder().size() + " Server, Modus: " + playerDisplayMode + ")");
|
||||
}
|
||||
|
||||
private void updateTablist(ProxiedPlayer viewer) {
|
||||
@@ -288,7 +301,12 @@ public class TablistModule implements Module, Listener {
|
||||
header = c(headerLine1) + "\n" + c(headerLine2) + "\n" + c(headerLine3);
|
||||
footer = c(footerLine1) + "\n" + c(footerLine2) + "\n" + c(footerLine3);
|
||||
}
|
||||
viewer.setTabHeader(new TextComponent(header), new TextComponent(footer));
|
||||
// fromLegacyText parst §x§R§R§G§G§B§B Hex-Sequenzen korrekt
|
||||
net.md_5.bungee.api.chat.BaseComponent[] hComps = net.md_5.bungee.api.chat.TextComponent.fromLegacyText(header);
|
||||
net.md_5.bungee.api.chat.BaseComponent[] fComps = net.md_5.bungee.api.chat.TextComponent.fromLegacyText(footer);
|
||||
viewer.setTabHeader(
|
||||
new net.md_5.bungee.api.chat.TextComponent(hComps),
|
||||
new net.md_5.bungee.api.chat.TextComponent(fComps));
|
||||
hideRealPlayers(viewer);
|
||||
sendSlots(viewer, buildItems(viewer));
|
||||
} catch (Exception ex) {
|
||||
@@ -300,7 +318,7 @@ public class TablistModule implements Module, Listener {
|
||||
|
||||
private String buildCompactHeader(ProxiedPlayer viewer, String srv, String world, String rank, String time, String balance, int online) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
appendLine(sb, compactHeader1, false, viewer, srv, world, rank, time, balance, online);
|
||||
appendLine(sb, compactHeader1, compactHeader1Spacer, viewer, srv, world, rank, time, balance, online);
|
||||
appendLine(sb, compactHeader2, compactHeader2Spacer, viewer, srv, world, rank, time, balance, online);
|
||||
appendLine(sb, compactHeader3, compactHeader3Spacer, viewer, srv, world, rank, time, balance, online);
|
||||
return sb.toString();
|
||||
@@ -321,9 +339,11 @@ public class TablistModule implements Module, Listener {
|
||||
if (sb.length() > 0) sb.append("\n");
|
||||
sb.append(c(sLine.toString()));
|
||||
}
|
||||
appendLine(sb, compactFooter2, false, viewer, srv, world, rank, time, balance, online);
|
||||
appendLine(sb, compactFooter3, false, viewer, srv, world, rank, time, balance, online);
|
||||
appendLine(sb, compactFooter2, compactFooter2Spacer, viewer, srv, world, rank, time, balance, online);
|
||||
appendLine(sb, compactFooter3, compactFooter3Spacer, viewer, srv, world, rank, time, balance, online);
|
||||
appendLine(sb, compactFooter4, compactFooter4Spacer, viewer, srv, world, rank, time, balance, online);
|
||||
appendLine(sb, compactFooter5, compactFooter5Spacer, viewer, srv, world, rank, time, balance, online);
|
||||
appendLine(sb, compactFooter6, compactFooter6Spacer, viewer, srv, world, rank, time, balance, online);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -331,7 +351,7 @@ public class TablistModule implements Module, Listener {
|
||||
boolean empty = line == null || line.trim().isEmpty();
|
||||
if (empty && !spacer) return;
|
||||
if (sb.length() > 0) sb.append("\n");
|
||||
sb.append(empty ? " " : c(replacePlaceholders(line, viewer, srv, world, rank, time, balance, online)));
|
||||
sb.append(empty ? "\u00A0" : c(replacePlaceholders(line, viewer, srv, world, rank, time, balance, online)));
|
||||
}
|
||||
|
||||
// ── Items ──────────────────────────────────────────────────────────────────
|
||||
@@ -343,6 +363,7 @@ public class TablistModule implements Module, Listener {
|
||||
for (int i = 0; i < total; i++) { texts[i] = " "; skins[i] = EMPTY_SKIN; pings[i] = 0; }
|
||||
|
||||
boolean compact = "compact".equalsIgnoreCase(layoutMode);
|
||||
// Ob der Spalten-Header einen Slot belegt (= "full") oder nicht (= "none"/"small")
|
||||
boolean useSlotHeader = "full".equalsIgnoreCase(columnHeaderMode);
|
||||
|
||||
// Info-Spalte (nur classic)
|
||||
@@ -372,26 +393,62 @@ public class TablistModule implements Module, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
// Server-Spalten
|
||||
List<String> servers = getServerOrder();
|
||||
int startCol = compact ? 0 : 1;
|
||||
for (int col = startCol; col < columns && (col - startCol) < servers.size(); col++) {
|
||||
int base = col * rows;
|
||||
int row = 0;
|
||||
String sName = servers.get(col - startCol);
|
||||
if (useSlotHeader) row = set(texts, base, row, c(colorSrvHeader + capitalize(sName)));
|
||||
ServerInfo si = ProxyServer.getInstance().getServerInfo(sName);
|
||||
if (si != null) {
|
||||
for (ProxiedPlayer p : sortPlayersByRank(new ArrayList<>(si.getPlayers()))) {
|
||||
if (row >= rows) break;
|
||||
String prefix = getLuckPermsPrefix(p);
|
||||
String symbol = getServerSymbol(p);
|
||||
if ("custom".equalsIgnoreCase(playerDisplayMode)) {
|
||||
// ── Custom-Modus: alle Spieler zusammen, nach Rang sortiert ──────────
|
||||
// Minecraft Tab-Grid ist spaltenweise aufgebaut (Spalte 1 = Slots 0-19, Spalte 2 = Slots 20-39)
|
||||
// "Links nach rechts" = Zeile 0 über alle Spalten, dann Zeile 1 usw.
|
||||
// Spieler 0 → Spalte 0 Zeile 0, Spieler 1 → Spalte 1 Zeile 0, Spieler 2 → Spalte 2 Zeile 0
|
||||
// Spieler 3 → Spalte 0 Zeile 1, Spieler 4 → Spalte 1 Zeile 1 usw.
|
||||
List<ProxiedPlayer> allPlayers = new ArrayList<>(ProxyServer.getInstance().getPlayers());
|
||||
allPlayers = sortPlayersByRank(allPlayers);
|
||||
int startCol = compact ? 0 : 1;
|
||||
int usedCols = columns - startCol;
|
||||
int maxSlots = usedCols * rows;
|
||||
int playerIdx = 0;
|
||||
// Zeile für Zeile iterieren, innerhalb jeder Zeile alle Spalten
|
||||
outer:
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int col = startCol; col < columns; col++) {
|
||||
if (playerIdx >= allPlayers.size()) break outer;
|
||||
ProxiedPlayer p = allPlayers.get(playerIdx++);
|
||||
int base = col * rows;
|
||||
String prefix = getLuckPermsPrefix(p);
|
||||
String symbol = getServerSymbol(p);
|
||||
String nameStr = p.getName() + (symbol.isEmpty() ? "" : " " + symbol);
|
||||
set(texts, base, row, prefix.isEmpty() ? c("&7" + nameStr) : c(prefix + "&r " + nameStr));
|
||||
set(texts, base, row, prefix.isEmpty()
|
||||
? c("&7" + nameStr)
|
||||
: c(prefix + "&r " + nameStr));
|
||||
net.md_5.bungee.protocol.data.Property[] skin = skinCache.get(p.getUniqueId());
|
||||
skins[base + row] = (skin != null && skin.length > 0) ? skin : EMPTY_SKIN;
|
||||
pings[base + row] = p.getPing() < 0 ? 1 : p.getPing();
|
||||
row++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ── Server-Modus: pro Spalte ein Server (default) ──────────────────
|
||||
List<String> servers = getServerOrder();
|
||||
int startCol = compact ? 0 : 1;
|
||||
for (int col = startCol; col < columns && (col - startCol) < servers.size(); col++) {
|
||||
int base = col * rows;
|
||||
int row = 0;
|
||||
String sName = servers.get(col - startCol);
|
||||
if (useSlotHeader) {
|
||||
row = set(texts, base, row, c(colorSrvHeader + capitalize(sName)));
|
||||
}
|
||||
ServerInfo si = ProxyServer.getInstance().getServerInfo(sName);
|
||||
if (si != null) {
|
||||
for (ProxiedPlayer p : sortPlayersByRank(new ArrayList<>(si.getPlayers()))) {
|
||||
if (row >= rows) break;
|
||||
String prefix = getLuckPermsPrefix(p);
|
||||
String symbol = getServerSymbol(p);
|
||||
String nameStr = p.getName() + (symbol.isEmpty() ? "" : " " + symbol);
|
||||
set(texts, base, row, prefix.isEmpty()
|
||||
? c("&7" + nameStr)
|
||||
: c(prefix + "&r " + nameStr));
|
||||
net.md_5.bungee.protocol.data.Property[] skin = skinCache.get(p.getUniqueId());
|
||||
skins[base + row] = (skin != null && skin.length > 0) ? skin : EMPTY_SKIN;
|
||||
pings[base + row] = p.getPing() < 0 ? 1 : p.getPing();
|
||||
row++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,7 +462,9 @@ public class TablistModule implements Module, Listener {
|
||||
item.setGamemode(0);
|
||||
item.setPing(pings[i]);
|
||||
item.setListed(true);
|
||||
item.setDisplayName(new TextComponent(texts[i] == null || texts[i].isEmpty() ? " " : texts[i]));
|
||||
String dn = texts[i] == null || texts[i].isEmpty() ? " " : texts[i];
|
||||
item.setDisplayName(new net.md_5.bungee.api.chat.TextComponent(
|
||||
net.md_5.bungee.api.chat.TextComponent.fromLegacyText(dn)));
|
||||
items[i] = item;
|
||||
}
|
||||
return items;
|
||||
@@ -418,20 +477,33 @@ public class TablistModule implements Module, Listener {
|
||||
if (sendPacketQueuedMethod == null) return;
|
||||
try {
|
||||
Collection<ProxiedPlayer> online = ProxyServer.getInstance().getPlayers();
|
||||
|
||||
// Sammle alle UUIDs die versteckt werden sollen:
|
||||
// 1. Echte Spieler
|
||||
// 2. BungeeCord-interne Server-Eintraege (werden von BC selbst in die Tablist geschrieben)
|
||||
List<UUID> toHide = new ArrayList<>();
|
||||
for (ProxiedPlayer p : online) toHide.add(p.getUniqueId());
|
||||
|
||||
// BungeeCord schreibt fuer jeden Server einen eigenen Eintrag mit einer
|
||||
// deterministischen UUID (nameUUIDFromBytes des Server-Namens).
|
||||
// Diese auch auf listed=false setzen damit die grossen Spalten-Header verschwinden.
|
||||
for (String srvName : ProxyServer.getInstance().getServers().keySet()) {
|
||||
try {
|
||||
toHide.add(UUID.nameUUIDFromBytes(("OfflinePlayer:" + srvName).getBytes(StandardCharsets.UTF_8)));
|
||||
toHide.add(UUID.nameUUIDFromBytes(srvName.getBytes(StandardCharsets.UTF_8)));
|
||||
UUID srvUuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + srvName).getBytes(StandardCharsets.UTF_8));
|
||||
toHide.add(srvUuid);
|
||||
UUID srvUuid2 = UUID.nameUUIDFromBytes(srvName.getBytes(StandardCharsets.UTF_8));
|
||||
toHide.add(srvUuid2);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
if (toHide.isEmpty()) return;
|
||||
PlayerListItemUpdate pkt = new PlayerListItemUpdate();
|
||||
pkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED));
|
||||
Item[] items = new Item[toHide.size()];
|
||||
int idx = 0;
|
||||
for (UUID uuid : toHide) { Item it = new Item(); it.setUuid(uuid); it.setListed(false); items[idx++] = it; }
|
||||
for (UUID uuid : toHide) {
|
||||
Item it = new Item(); it.setUuid(uuid); it.setListed(false); items[idx++] = it;
|
||||
}
|
||||
pkt.setItems(items);
|
||||
sendPacketQueuedMethod.invoke(viewer, pkt);
|
||||
} catch (Exception e) { plugin.getLogger().warning("[TablistModule] hideRealPlayers: " + e.getMessage()); }
|
||||
@@ -440,7 +512,6 @@ public class TablistModule implements Module, Listener {
|
||||
@SuppressWarnings("unchecked")
|
||||
private void sendSlots(ProxiedPlayer viewer, Item[] items) {
|
||||
if (sendPacketQueuedMethod == null) return;
|
||||
// Immer vollständiges ADD_PLAYER – einfach und zuverlässig
|
||||
PlayerListItemUpdate pkt = new PlayerListItemUpdate();
|
||||
pkt.setActions(EnumSet.of(
|
||||
PlayerListItemUpdate.Action.ADD_PLAYER,
|
||||
@@ -473,11 +544,16 @@ public class TablistModule implements Module, Listener {
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* ── ULTIMATE: Gibt das konfigurierte Server-Symbol für den Spieler zurück.
|
||||
* Leer wenn kein Symbol für den aktuellen Server definiert ist.
|
||||
*/
|
||||
private String getServerSymbol(ProxiedPlayer player) {
|
||||
if (serverSymbols.isEmpty() || player.getServer() == null) return "";
|
||||
String raw = serverSymbols.get(player.getServer().getInfo().getName().toLowerCase());
|
||||
String srvKey = player.getServer().getInfo().getName().toLowerCase();
|
||||
String raw = serverSymbols.get(srvKey);
|
||||
if (raw == null || raw.isEmpty()) return "";
|
||||
return c(raw);
|
||||
return c(raw); // Farb-Codes und Hex-Farben auflösen
|
||||
}
|
||||
|
||||
private net.md_5.bungee.protocol.data.Property[] fetchSkin(ProxiedPlayer player) {
|
||||
@@ -495,7 +571,6 @@ public class TablistModule implements Module, Listener {
|
||||
List<String> list;
|
||||
if (!serverOrder.isEmpty()) {
|
||||
list = new ArrayList<>(serverOrder);
|
||||
// Versteckte Server auch aus manueller Liste entfernen
|
||||
list.removeIf(s -> hiddenServers.contains(s.toLowerCase()));
|
||||
} else {
|
||||
list = new ArrayList<>();
|
||||
@@ -565,6 +640,7 @@ public class TablistModule implements Module, Listener {
|
||||
Object cache = usr.getClass().getMethod("getCachedData").invoke(usr);
|
||||
Object meta = cache.getClass().getMethod("getMetaData", qo).invoke(cache, opts);
|
||||
Object pfx = meta.getClass().getMethod("getPrefix").invoke(meta);
|
||||
// ── HEX-Farben auch im Prefix auflösen ───────────────────────
|
||||
if (pfx != null) return c(pfx.toString());
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
@@ -582,7 +658,6 @@ public class TablistModule implements Module, Listener {
|
||||
|
||||
private String replacePlaceholders(String text, ProxiedPlayer viewer, String srv, String world, String rank, String time, String balance, int online) {
|
||||
if (text == null) return "";
|
||||
// PAPI zuerst, native Tokens danach (überschreiben PAPI-Werte falls gleicher Name)
|
||||
String result = resolvePapiPlaceholders(text, viewer.getUniqueId());
|
||||
result = result.replace("%player%", viewer.getName()).replace("%rank%", rank)
|
||||
.replace("%server%", srv).replace("%world%", world).replace("%time%", time)
|
||||
@@ -593,7 +668,7 @@ public class TablistModule implements Module, Listener {
|
||||
|
||||
private static String resolvePapiPlaceholders(String text, UUID uuid) {
|
||||
if (text == null || !text.contains("%")) return text;
|
||||
java.util.Map<String, String> papiMap = net.viper.status.StatusAPI.playerPapi.get(uuid);
|
||||
Map<String, String> papiMap = net.viper.status.StatusAPI.playerPapi.get(uuid);
|
||||
if (papiMap == null || papiMap.isEmpty()) return text;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int i = 0;
|
||||
@@ -619,54 +694,315 @@ public class TablistModule implements Module, Listener {
|
||||
if (base + row < total) arr[base + row] = text == null ? " " : text; return row + 1;
|
||||
}
|
||||
|
||||
// ── Farb-Auflösung ─────────────────────────────────────────────────────────
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// Farb-Parser: Birdflop-kompatibel
|
||||
// Unterstützte Formate (alle gleichzeitig nutzbar):
|
||||
//
|
||||
// &#RRGGBB → Pro-Zeichen Hex (Birdflop Standard-Output)
|
||||
// {#RRGGBB} → Bracket-Format
|
||||
// <#RRGGBB> → MiniMessage Kurzform
|
||||
// <color:#RRGGBB> → MiniMessage color-Tag
|
||||
// <gradient:#C1:#C2:Text> → Farbverlauf (beliebig viele Farb-Stopps)
|
||||
// <shadow:#C:Text> → Text in Schattenfarbe
|
||||
// <b> <i> <u> <st> <obf> → Formatierungen
|
||||
// &l &o &n &m &k &r → Standard-Formatierungen
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
private static String c(String s) {
|
||||
if (s == null) return "";
|
||||
s = replaceHexColors(s);
|
||||
return ChatColor.translateAlternateColorCodes('&', s);
|
||||
if (s == null) return " ";
|
||||
s = parseMiniMessage(s); // MiniMessage-Tags (<gradient:>, <shadow:>, <#>, <color:>, <b> usw.)
|
||||
s = parseHexAmpersand(s); // &#RRGGBB und {#RRGGBB}
|
||||
return net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', s);
|
||||
}
|
||||
|
||||
private static String replaceHexColors(String text) {
|
||||
if (text == null) return null;
|
||||
if (!text.contains("&#") && !text.contains("{#") && !text.contains("<#")) return text;
|
||||
private static String stripColors(String s) {
|
||||
return s == null ? "" : net.md_5.bungee.api.ChatColor.stripColor(c(s));
|
||||
}
|
||||
|
||||
// ── MiniMessage Haupt-Dispatcher ─────────────────────────────────────────
|
||||
|
||||
private static String parseMiniMessage(String text) {
|
||||
if (text == null || !text.contains("<")) return text == null ? "" : text;
|
||||
// gradient-Tags als erstes, weil sie anderen Text enthalten können
|
||||
text = parseGradientTags(text);
|
||||
// shadow-Tags
|
||||
text = parseShadowTags(text);
|
||||
// Einfache Tags: <color:#>, <#>, <b>, <i>, <u>, <st>, <obf>, </...>
|
||||
text = parseSimpleTags(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
// ── <gradient:#C1:#C2:...:TEXT> ──────────────────────────────────────────
|
||||
|
||||
private static String parseGradientTags(String text) {
|
||||
if (!text.contains("<gradient:")) return text;
|
||||
StringBuilder result = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
int start = text.indexOf("<gradient:", i);
|
||||
if (start < 0) { result.append(text, i, text.length()); break; }
|
||||
result.append(text, i, start);
|
||||
// Schließendes > suchen (mit Tiefenzähler für verschachtelte <...>)
|
||||
int end = findClosingAngle(text, start + 1);
|
||||
if (end < 0) { result.append(text, i, text.length()); break; }
|
||||
String inner = text.substring(start + 1, end); // "gradient:#C1:#C2:TEXT"
|
||||
result.append(applyGradientTag(inner));
|
||||
i = end + 1;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parst "gradient:#C1:#C2:#C3:TEXT" → eingefärbten Text.
|
||||
* TEXT darf selbst wieder §-Codes oder &-Codes enthalten (z.B. &l für Bold).
|
||||
*/
|
||||
private static String applyGradientTag(String inner) {
|
||||
// inner = "gradient:COLOR:COLOR:...:TEXT"
|
||||
// Farben beginnen mit # oder mit & gefolgt von einem Hex-Code
|
||||
java.util.List<String> colors = new java.util.ArrayList<>();
|
||||
// Trenne am ersten Doppelpunkt nach "gradient"
|
||||
int firstColon = inner.indexOf(':'); // nach "gradient"
|
||||
if (firstColon < 0) return inner;
|
||||
String rest = inner.substring(firstColon + 1);
|
||||
|
||||
// Lese Farb-Stopps (jeder Teil beginnt mit #)
|
||||
// TEXT ist alles ab dem ersten Teil der NICHT mit # beginnt
|
||||
StringBuilder textSb = new StringBuilder();
|
||||
boolean inText = false;
|
||||
String[] parts = rest.split(":", -1);
|
||||
for (int p = 0; p < parts.length; p++) {
|
||||
String part = parts[p];
|
||||
if (!inText && part.startsWith("#") && part.length() == 7) {
|
||||
colors.add(part);
|
||||
} else {
|
||||
// Ab hier Text (inkl. Doppelpunkte wieder zusammensetzen)
|
||||
inText = true;
|
||||
if (textSb.length() > 0) textSb.append(":");
|
||||
textSb.append(part);
|
||||
}
|
||||
}
|
||||
if (colors.size() < 2) return textSb.toString();
|
||||
|
||||
// Shadow-Tags im Text zuerst auflösen (können im Gradient-Text stecken)
|
||||
String rawText = parseShadowTags(textSb.toString());
|
||||
return applyGradient(rawText, colors);
|
||||
}
|
||||
|
||||
private static String applyGradient(String text, java.util.List<String> colorStops) {
|
||||
if (text == null || text.isEmpty()) return text;
|
||||
// §-Codes und &-Codes aus Text herausfiltern für Längenberechnung
|
||||
String plain = text
|
||||
.replaceAll("\u00A7[0-9a-fk-orx]", "")
|
||||
.replaceAll("&[0-9a-fA-Fk-orK-OR]", "")
|
||||
.replaceAll("\u00A7x(\u00A7[0-9a-fA-F]){6}", ""); // §x§R§R§G§G§B§B
|
||||
int len = plain.length();
|
||||
if (len == 0) return text;
|
||||
if (len == 1) return resolveColorToSection(colorStops.get(0)) + text;
|
||||
|
||||
int[][] rgbStops = new int[colorStops.size()][3];
|
||||
for (int s = 0; s < colorStops.size(); s++) rgbStops[s] = hexToRgb(colorStops.get(s));
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
int charIdx = 0;
|
||||
int ci = 0;
|
||||
while (ci < text.length()) {
|
||||
char ch = text.charAt(ci);
|
||||
|
||||
// §x§R§R§G§G§B§B durchreichen (bereits aufgelöste Hex-Farbe z.B. von shadow)
|
||||
if (ch == '\u00A7' && ci + 1 < text.length() && text.charAt(ci + 1) == 'x') {
|
||||
// Lese die 12 folgenden Zeichen (§x + 6x §digit)
|
||||
if (ci + 13 < text.length() + 1) {
|
||||
result.append(text, ci, Math.min(ci + 14, text.length()));
|
||||
ci = Math.min(ci + 14, text.length());
|
||||
} else {
|
||||
result.append(ch); ci++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// §-Formatcode durchreichen
|
||||
if (ch == '\u00A7' && ci + 1 < text.length()) {
|
||||
result.append(ch).append(text.charAt(ci + 1)); ci += 2; continue;
|
||||
}
|
||||
// &-Formatcode durchreichen
|
||||
if (ch == '&' && ci + 1 < text.length() && "&0123456789abcdefABCDEFklmnorKLMNOR".indexOf(text.charAt(ci+1)) >= 0) {
|
||||
result.append(ch).append(text.charAt(ci + 1)); ci += 2; continue;
|
||||
}
|
||||
|
||||
// Normales Zeichen → Farbe interpolieren
|
||||
float t = len <= 1 ? 0f : (float) charIdx / (len - 1);
|
||||
int segments = colorStops.size() - 1;
|
||||
float scaled = t * segments;
|
||||
int seg = Math.min((int) scaled, segments - 1);
|
||||
float segT = scaled - seg;
|
||||
int[] c1 = rgbStops[seg], c2 = rgbStops[seg + 1];
|
||||
int r = clamp((int)(c1[0] + (c2[0] - c1[0]) * segT));
|
||||
int g = clamp((int)(c1[1] + (c2[1] - c1[1]) * segT));
|
||||
int b = clamp((int)(c1[2] + (c2[2] - c1[2]) * segT));
|
||||
String hex = String.format("%02X%02X%02X", r, g, b);
|
||||
appendHexSection(result, hex);
|
||||
result.append(ch);
|
||||
charIdx++;
|
||||
ci++;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// ── <shadow:#RRGGBB:TEXT> ─────────────────────────────────────────────────
|
||||
|
||||
private static String parseShadowTags(String text) {
|
||||
if (text == null || !text.contains("<shadow:")) return text == null ? "" : text;
|
||||
StringBuilder result = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
int start = text.indexOf("<shadow:", i);
|
||||
if (start < 0) { result.append(text, i, text.length()); break; }
|
||||
result.append(text, i, start);
|
||||
int end = findClosingAngle(text, start + 1);
|
||||
if (end < 0) { result.append(text, i, text.length()); break; }
|
||||
String inner = text.substring(start + 1, end); // "shadow:#RRGGBB:TEXT"
|
||||
// Format: shadow:COLOR:TEXT
|
||||
int firstColon = inner.indexOf(':');
|
||||
int secondColon = firstColon >= 0 ? inner.indexOf(':', firstColon + 1) : -1;
|
||||
if (firstColon < 0 || secondColon < 0) { result.append(text, i, end + 1); i = end + 1; continue; }
|
||||
String colorPart = inner.substring(firstColon + 1, secondColon).trim();
|
||||
String content = inner.substring(secondColon + 1);
|
||||
result.append(resolveColorToSection(colorPart)).append(content);
|
||||
i = end + 1;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// ── Einfache MiniMessage-Tags ─────────────────────────────────────────────
|
||||
|
||||
private static String parseSimpleTags(String text) {
|
||||
if (text == null || !text.contains("<")) return text == null ? "" : text;
|
||||
// Ersetzungstabelle
|
||||
text = text.replace("<b>", "&l").replace("</b>", "&r");
|
||||
text = text.replace("<i>", "&o").replace("</i>", "&r");
|
||||
text = text.replace("<u>", "&n").replace("</u>", "&r");
|
||||
text = text.replace("<st>", "&m").replace("</st>", "&r");
|
||||
text = text.replace("<obf>", "&k").replace("</obf>", "&r");
|
||||
text = text.replace("<reset>", "&r").replace("</reset>", "");
|
||||
// Closing-Tags entfernen (werden nach Verarbeitung nicht mehr benötigt)
|
||||
text = text.replaceAll("</gradient>", "");
|
||||
text = text.replaceAll("</shadow>", "");
|
||||
text = text.replaceAll("</color>", "");
|
||||
// <color:#RRGGBB> und <#RRGGBB>
|
||||
StringBuilder result = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
char ch = text.charAt(i);
|
||||
if (ch != '<') { result.append(ch); i++; continue; }
|
||||
// <color:#RRGGBB>
|
||||
if (text.startsWith("<color:#", i)) {
|
||||
int end = text.indexOf('>', i);
|
||||
if (end > 0) {
|
||||
String hex = text.substring(i + 7, end).trim();
|
||||
if (hex.startsWith("#") && hex.length() == 7 && hex.substring(1).matches("[0-9a-fA-F]{6}")) {
|
||||
appendHexSection(result, hex.substring(1));
|
||||
i = end + 1; continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// <#RRGGBB>
|
||||
if (text.startsWith("<#", i) && i + 9 <= text.length()) {
|
||||
int end = text.indexOf('>', i);
|
||||
if (end == i + 8) {
|
||||
String hex = text.substring(i + 2, end);
|
||||
if (hex.matches("[0-9a-fA-F]{6}")) {
|
||||
appendHexSection(result, hex);
|
||||
i = end + 1; continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.append(ch); i++;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// ── &#RRGGBB und {#RRGGBB} ───────────────────────────────────────────────
|
||||
|
||||
private static String parseHexAmpersand(String text) {
|
||||
if (text == null) return "";
|
||||
if (!text.contains("&#") && !text.contains("{#")) return text;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
if (i + 7 <= text.length() && text.charAt(i) == '&' && text.charAt(i+1) == '#') {
|
||||
// &#RRGGBB
|
||||
if (i + 7 < text.length() + 1 && i + 8 <= text.length()
|
||||
&& text.charAt(i) == '&' && text.charAt(i+1) == '#') {
|
||||
String hex = text.substring(i+2, i+8);
|
||||
if (hex.matches("[0-9a-fA-F]{6}")) {
|
||||
sb.append('\u00A7').append('x');
|
||||
for (char ch : hex.toCharArray()) sb.append('\u00A7').append(ch);
|
||||
i += 8; continue;
|
||||
appendHexSection(sb, hex); i += 8; continue;
|
||||
}
|
||||
}
|
||||
// {#RRGGBB}
|
||||
if (i + 8 < text.length() && text.charAt(i) == '{' && text.charAt(i+1) == '#') {
|
||||
int end = text.indexOf('}', i+2);
|
||||
if (end == i+8) {
|
||||
if (end == i + 8) {
|
||||
String hex = text.substring(i+2, i+8);
|
||||
if (hex.matches("[0-9a-fA-F]{6}")) {
|
||||
sb.append('\u00A7').append('x');
|
||||
for (char ch : hex.toCharArray()) sb.append('\u00A7').append(ch);
|
||||
i += 9; continue;
|
||||
appendHexSection(sb, hex); i += 9; continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Format 3: <#RRGGBB>
|
||||
if (i + 8 < text.length() && text.charAt(i) == '<' && text.charAt(i+1) == '#') {
|
||||
int end = text.indexOf('>', i+2);
|
||||
if (end == i+8) {
|
||||
String hex = text.substring(i+2, i+8);
|
||||
if (hex.matches("[0-9a-fA-F]{6}")) {
|
||||
sb.append('\u00A7').append('x');
|
||||
for (char ch : hex.toCharArray()) sb.append('\u00A7').append(ch);
|
||||
i += 9; continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append(text.charAt(i)); i++;
|
||||
sb.append(text.charAt(i++));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
|
||||
|
||||
private static void appendHexSection(StringBuilder sb, String hex) {
|
||||
sb.append('\u00A7').append('x');
|
||||
for (char ch : hex.toCharArray()) sb.append('\u00A7').append(ch);
|
||||
}
|
||||
|
||||
private static String resolveColorToSection(String color) {
|
||||
if (color == null) return "";
|
||||
color = color.trim();
|
||||
if (color.startsWith("#") && color.length() == 7
|
||||
&& color.substring(1).matches("[0-9a-fA-F]{6}")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
appendHexSection(sb, color.substring(1));
|
||||
return sb.toString();
|
||||
}
|
||||
if (color.startsWith("&") && color.length() == 2) return "\u00A7" + color.charAt(1);
|
||||
return color;
|
||||
}
|
||||
|
||||
private static int[] hexToRgb(String color) {
|
||||
String hex = color == null ? "" : color.trim();
|
||||
if (hex.startsWith("#")) hex = hex.substring(1);
|
||||
if (hex.length() != 6) return new int[]{255, 255, 255};
|
||||
try {
|
||||
return new int[]{
|
||||
Integer.parseInt(hex.substring(0,2), 16),
|
||||
Integer.parseInt(hex.substring(2,4), 16),
|
||||
Integer.parseInt(hex.substring(4,6), 16)
|
||||
};
|
||||
} catch (Exception e) { return new int[]{255,255,255}; }
|
||||
}
|
||||
|
||||
private static int clamp(int v) { return Math.max(0, Math.min(255, v)); }
|
||||
|
||||
/**
|
||||
* Findet das schließende '>' für ein Tag das bei fromIndex beginnt.
|
||||
* Berücksichtigt verschachtelte <...>.
|
||||
*/
|
||||
private static int findClosingAngle(String text, int fromIndex) {
|
||||
int depth = 0;
|
||||
for (int i = fromIndex; i < text.length(); i++) {
|
||||
char ch = text.charAt(i);
|
||||
if (ch == '<') depth++;
|
||||
else if (ch == '>') { if (depth == 0) return i; depth--; }
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
private static String fakeName(int i) { return String.format("~vt%03d", i); }
|
||||
private static String capitalize(String s){ return s==null||s.isEmpty()?s:Character.toUpperCase(s.charAt(0))+s.substring(1); }
|
||||
private static String rep(char ch, int n) { StringBuilder sb=new StringBuilder(n); for(int i=0;i<n;i++) sb.append(ch); return sb.toString(); }
|
||||
@@ -689,6 +1025,12 @@ public class TablistModule implements Module, Listener {
|
||||
"tablist.server_order=\n" +
|
||||
"tablist.hidden_servers=\n" +
|
||||
"tablist.rank_order=owner,mod,primo,vip,scout,bewohner\n\n" +
|
||||
"# column_header: full = großer Spalten-Header (alte Markierung)\n" +
|
||||
"# none = kein Header, Zeile 0 ist für Spieler frei (MuckiDEE-Wunsch)\n" +
|
||||
"# small = wie none, aber Server-Namen erscheinen im Tab-Footer\n" +
|
||||
"tablist.column_header=none\n" +
|
||||
"# player_display: server = Server-basiert (default) | custom = alle zusammen nach Rang sortiert\n" +
|
||||
"tablist.player_display=server\n\n" +
|
||||
"tablist.header.line1=&8&m" + sep + "\n" +
|
||||
"tablist.header.line2= &6&lViper Network\n" +
|
||||
"tablist.header.line3=&8&m" + sep + "\n\n" +
|
||||
@@ -710,10 +1052,12 @@ public class TablistModule implements Module, Listener {
|
||||
"tablist.compact.footer.line3=&7Kontostand: &a$%balance% &8| &7Server: &f%server% &8| &7Welt: &f%world%\n" +
|
||||
"tablist.compact.footer.line3.spacer=false\n" +
|
||||
"tablist.compact.footer.line4=\n" +
|
||||
"tablist.compact.footer.line4.spacer=false\n\n" +
|
||||
"tablist.compact.footer.line4.spacer=false\n" +
|
||||
"tablist.compact.footer.line5=\n" +
|
||||
"tablist.compact.footer.line5.spacer=false\n" +
|
||||
"tablist.compact.footer.line6=\n" +
|
||||
"tablist.compact.footer.line6.spacer=false\n\n" +
|
||||
"tablist.color.server_header=&6&l\n" +
|
||||
"# column_header: full=großer Header | none=kein Header (Zeile 0 frei) | small=nur im Footer\n" +
|
||||
"tablist.column_header=none\n" +
|
||||
"# Server-Liste im Footer anzeigen (true/false)\n" +
|
||||
"tablist.compact.footer.serverlist=true\n" +
|
||||
"tablist.time_format=HH:mm:ss / h:mm a\n" +
|
||||
@@ -743,10 +1087,16 @@ public class TablistModule implements Module, Listener {
|
||||
"tablist.info.teamspeak.label=&b&lTeamspeak:\n" +
|
||||
"tablist.info.teamspeak.type=teamspeak\n" +
|
||||
"tablist.info.teamspeak.value=&fts.viper-network.de\n" +
|
||||
"\n# Server-Symbole hinter dem Spielernamen\n" +
|
||||
"\n# ── Server-Symbole ───────────────────────────────────────────────────\n" +
|
||||
"# Format: tablist.symbol.<servername>=&FarbCode Symbol\n" +
|
||||
"# Farben: & + Code (z.B. &6 = Gold) oder &#RRGGBB / {#RRGGBB} / <#RRGGBB>\n" +
|
||||
"# Emojis und Unicode-Symbole werden unterstützt.\n" +
|
||||
"# Der Symbol-Text erscheint hinter dem Spielernamen in der Tablist.\n" +
|
||||
"tablist.symbol.lobby=&f\uD83C\uDFE0\n" +
|
||||
"tablist.symbol.sv1=&6\u26CF\uFE0F\n";
|
||||
"tablist.symbol.sv1=&6\u26CF\uFE0F\n" +
|
||||
"# tablist.symbol.farmwelt=&a\uD83C\uDF3F\n" +
|
||||
"# tablist.symbol.spielerwelt=&e\u2728\n" +
|
||||
"# tablist.symbol.game=&d\uD83C\uDFAE\n";
|
||||
try (OutputStream out = new FileOutputStream(f)) { out.write(content.getBytes(StandardCharsets.UTF_8)); }
|
||||
catch (Exception e) { plugin.getLogger().warning("[TablistModule] Config: " + e.getMessage()); }
|
||||
}
|
||||
@@ -772,6 +1122,10 @@ public class TablistModule implements Module, Listener {
|
||||
enabled = Boolean.parseBoolean(get.apply("tablist.enabled", "true"));
|
||||
updateInterval = parseInt(get.apply("tablist.update_interval", "5"), 5);
|
||||
layoutMode = get.apply("tablist.layout", "compact").trim().toLowerCase();
|
||||
// ── UPGRADE: column_header Modus ──────────────────────────────────────
|
||||
columnHeaderMode = get.apply("tablist.column_header", "none").trim().toLowerCase();
|
||||
playerDisplayMode = get.apply("tablist.player_display", "server").trim().toLowerCase();
|
||||
|
||||
headerLine1 = get.apply("tablist.header.line1", headerLine1);
|
||||
headerLine2 = get.apply("tablist.header.line2", headerLine2);
|
||||
headerLine3 = get.apply("tablist.header.line3", headerLine3);
|
||||
@@ -781,17 +1135,23 @@ public class TablistModule implements Module, Listener {
|
||||
compactHeader1 = get.apply("tablist.compact.header.line1", compactHeader1);
|
||||
compactHeader2 = get.apply("tablist.compact.header.line2", compactHeader2);
|
||||
compactHeader3 = get.apply("tablist.compact.header.line3", compactHeader3);
|
||||
compactHeader1Spacer = Boolean.parseBoolean(get.apply("tablist.compact.header.line1.spacer", "false"));
|
||||
compactHeader2Spacer = Boolean.parseBoolean(get.apply("tablist.compact.header.line2.spacer", "false"));
|
||||
compactHeader3Spacer = Boolean.parseBoolean(get.apply("tablist.compact.header.line3.spacer", "false"));
|
||||
compactFooter1 = get.apply("tablist.compact.footer.line1", compactFooter1);
|
||||
compactFooter2 = get.apply("tablist.compact.footer.line2", compactFooter2);
|
||||
compactFooter3 = get.apply("tablist.compact.footer.line3", compactFooter3);
|
||||
compactFooter4 = get.apply("tablist.compact.footer.line4", compactFooter4);
|
||||
compactFooter5 = get.apply("tablist.compact.footer.line5", compactFooter5);
|
||||
compactFooter6 = get.apply("tablist.compact.footer.line6", compactFooter6);
|
||||
compactFooter1Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line1.spacer", "false"));
|
||||
compactFooter2Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line2.spacer", "false"));
|
||||
compactFooter3Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line3.spacer", "false"));
|
||||
compactFooter4Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line4.spacer", "false"));
|
||||
compactFooter5Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line5.spacer", "false"));
|
||||
compactFooter6Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line6.spacer", "false"));
|
||||
colorSrvHeader = get.apply("tablist.color.server_header", colorSrvHeader);
|
||||
showFooterServerList = Boolean.parseBoolean(get.apply("tablist.compact.footer.serverlist", "true"));
|
||||
columnHeaderMode = get.apply("tablist.column_header", "none").trim().toLowerCase();
|
||||
timeFormat = get.apply("tablist.time_format", timeFormat);
|
||||
timeZone = get.apply("tablist.timezone", timeZone);
|
||||
try { sdf = new SimpleDateFormat(timeFormat); sdf.setTimeZone(java.util.TimeZone.getTimeZone(timeZone)); }
|
||||
@@ -829,16 +1189,22 @@ public class TablistModule implements Module, Listener {
|
||||
infoEntries.add(new InfoEntry("&b&lTeamspeak:", "teamspeak", "&fts.viper-network.de", true));
|
||||
}
|
||||
|
||||
// Server-Symbole
|
||||
// ── Server-Symbole aus tablist.properties ─────────────────────────────
|
||||
// Format: tablist.symbol.<servername>=&FarbCode Symbol
|
||||
// Beispiel: tablist.symbol.lobby=&f🏠
|
||||
// tablist.symbol.sv1=&6⛏️
|
||||
serverSymbols.clear();
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (key.startsWith("tablist.symbol.")) {
|
||||
String srvName = key.substring("tablist.symbol.".length()).trim().toLowerCase();
|
||||
String symbol = entry.getValue().trim();
|
||||
if (!srvName.isEmpty() && !symbol.isEmpty())
|
||||
if (!srvName.isEmpty() && !symbol.isEmpty()) {
|
||||
serverSymbols.put(srvName, symbol);
|
||||
plugin.getLogger().info("[TablistModule] Symbol: " + srvName + " → " + symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: StatusAPI
|
||||
main: net.viper.status.StatusAPI
|
||||
version: 4.1.1
|
||||
version: 4.1.2
|
||||
author: M_Viper
|
||||
description: StatusAPI für BungeeCord inkl. Update-Checker, Modul-System und ChatModule
|
||||
# Mindestanforderung: Minecraft 1.20 / BungeeCord mit PlayerChatEvent-Unterstützung
|
||||
|
||||
Reference in New Issue
Block a user