diff --git a/src/main/java/net/viper/status/StatusAPI.java b/src/main/java/net/viper/status/StatusAPI.java index b221b95..5ed899d 100644 --- a/src/main/java/net/viper/status/StatusAPI.java +++ b/src/main/java/net/viper/status/StatusAPI.java @@ -9,6 +9,7 @@ import net.viper.status.modules.antibot.AntiBotModule; import net.viper.status.modules.network.NetworkInfoModule; import net.viper.status.modules.AutoMessage.AutoMessageModule; import net.viper.status.modules.customcommands.CustomCommandModule; +import net.viper.status.modules.serverswitcher.ServerSwitcherModule; import net.viper.status.stats.PlayerStats; import net.viper.status.stats.StatsModule; import net.viper.status.modules.verify.VerifyModule; @@ -87,6 +88,7 @@ public class StatusAPI extends Plugin implements Runnable { moduleManager.registerModule(new NetworkInfoModule()); moduleManager.registerModule(new AutoMessageModule()); moduleManager.registerModule(new CustomCommandModule()); + moduleManager.registerModule(new ServerSwitcherModule()); try { Class forumBridge = Class.forName("net.viper.status.modules.forum.ForumBridgeModule"); diff --git a/src/main/java/net/viper/status/modules/chat/ChatModule.java b/src/main/java/net/viper/status/modules/chat/ChatModule.java index 82263ab..05711b6 100644 --- a/src/main/java/net/viper/status/modules/chat/ChatModule.java +++ b/src/main/java/net/viper/status/modules/chat/ChatModule.java @@ -1193,7 +1193,9 @@ public class ChatModule implements Module, Listener { String player, String suffix, String message) { String serverColor = config.getServerColor(server); String serverDisplay = config.getServerDisplay(server); - String coloredServer = translateColors(serverColor + serverDisplay + "&r"); + // Nur den Servernamen-Teil vorübersetzen damit &#RRGGBB im Display-Namen + // korrekt sitzt; der Rest wird am Ausgabepunkt via translateColors() übersetzt. + String coloredServer = serverColor + serverDisplay + "&r"; return format .replace("{server}", coloredServer) @@ -1299,7 +1301,7 @@ public class ChatModule implements Module, Listener { private BaseComponent[] buildClickableMessage(String msgId, String formatted, String senderName) { ComponentBuilder builder = new ComponentBuilder(""); - builder.append(ChatColor.translateAlternateColorCodes('&', formatted), + builder.append(translateColors(formatted), ComponentBuilder.FormatRetention.NONE) .event((ClickEvent) null) .event((HoverEvent) null); diff --git a/src/main/java/net/viper/status/modules/serverswitcher/ServerSwitcherModule.java b/src/main/java/net/viper/status/modules/serverswitcher/ServerSwitcherModule.java new file mode 100644 index 0000000..f9a1519 --- /dev/null +++ b/src/main/java/net/viper/status/modules/serverswitcher/ServerSwitcherModule.java @@ -0,0 +1,273 @@ +package net.viper.status.modules.serverswitcher; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +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; +import net.md_5.bungee.event.EventHandler; +import net.viper.status.module.Module; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +public class ServerSwitcherModule implements Module { + + private static final String CONFIG_FILE = "serverswitcher.properties"; + + private Plugin plugin; + private boolean enabled = true; + private String permission = "serverswitcher.use"; + private String commandName = "go"; + private List aliases = new ArrayList<>(Arrays.asList("wechsel", "switch")); + private List serverWhitelist = new ArrayList<>(); + + private String colorHeader = "&8&m---&r &6&lServer-Menue &8&m---"; + private String colorEntry = "&7>> &e"; + private String colorOnline = "&a"; + private String colorOffline = "&c"; + private String colorSelf = "&7(Aktuell)"; + + @Override + public String getName() { + return "ServerSwitcherModule"; + } + + @Override + public void onEnable(Plugin plugin) { + this.plugin = plugin; + ensureConfigExists(); + loadConfig(); + + if (!enabled) { + plugin.getLogger().info("[ServerSwitcherModule] Deaktiviert."); + return; + } + + String[] aliasArray = aliases.toArray(new String[0]); + ProxyServer.getInstance().getPluginManager().registerCommand(plugin, + new GoCommand(commandName, permission, aliasArray)); + ProxyServer.getInstance().getPluginManager().registerListener(plugin, + new GoTabListener()); + + plugin.getLogger().info("[ServerSwitcherModule] Aktiviert. Command: /" + commandName + + " | Aliases: " + aliases + " | Permission: " + permission); + } + + @Override + public void onDisable(Plugin plugin) { + } + + private void ensureConfigExists() { + File target = new File(plugin.getDataFolder(), CONFIG_FILE); + if (target.exists()) return; + if (!plugin.getDataFolder().exists()) plugin.getDataFolder().mkdirs(); + + String defaults = + "# ServerSwitcherModule Konfiguration\n" + + "serverswitcher.enabled=true\n\n" + + "serverswitcher.command=go\n" + + "serverswitcher.aliases=wechsel,switch\n" + + "serverswitcher.permission=serverswitcher.use\n\n" + + "# Optionale Whitelist (leer = alle BungeeCord-Server)\n" + + "# Beispiel: serverswitcher.servers=lobby,citybuild,survival\n" + + "serverswitcher.servers=\n\n" + + "serverswitcher.color.header=&8&m---&r &6&lServer-Menü &8&m---\n" + + "serverswitcher.color.entry=&7>> &e\n" + + "serverswitcher.color.online=&a\n" + + "serverswitcher.color.offline=&c\n" + + "serverswitcher.color.self=&7(Aktuell)\n"; + + try (OutputStream out = new FileOutputStream(target)) { + out.write(defaults.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + plugin.getLogger().warning("[ServerSwitcherModule] Konnte " + CONFIG_FILE + " nicht erstellen: " + e.getMessage()); + } + } + + private void loadConfig() { + File file = new File(plugin.getDataFolder(), CONFIG_FILE); + if (!file.exists()) return; + + Properties props = new Properties(); + try (FileInputStream fis = new FileInputStream(file)) { + props.load(new InputStreamReader(fis, StandardCharsets.UTF_8)); + } catch (Exception e) { + plugin.getLogger().warning("[ServerSwitcherModule] Fehler beim Laden: " + e.getMessage()); + return; + } + + enabled = Boolean.parseBoolean(props.getProperty("serverswitcher.enabled", "true")); + commandName = props.getProperty("serverswitcher.command", "go").trim(); + permission = props.getProperty("serverswitcher.permission", "serverswitcher.use").trim(); + + aliases.clear(); + for (String a : props.getProperty("serverswitcher.aliases", "wechsel,switch").split(",")) { + String t = a.trim(); + if (!t.isEmpty()) aliases.add(t); + } + + serverWhitelist.clear(); + for (String s : props.getProperty("serverswitcher.servers", "").split(",")) { + String t = s.trim().toLowerCase(); + if (!t.isEmpty()) serverWhitelist.add(t); + } + + colorHeader = props.getProperty("serverswitcher.color.header", colorHeader); + colorEntry = props.getProperty("serverswitcher.color.entry", colorEntry); + colorOnline = props.getProperty("serverswitcher.color.online", colorOnline); + colorOffline = props.getProperty("serverswitcher.color.offline", colorOffline); + colorSelf = props.getProperty("serverswitcher.color.self", colorSelf); + } + + private List getServerList() { + if (!serverWhitelist.isEmpty()) return new ArrayList<>(serverWhitelist); + List list = new ArrayList<>(ProxyServer.getInstance().getServers().keySet()); + list.sort(String.CASE_INSENSITIVE_ORDER); + return list; + } + + private static String c(String text) { + return ChatColor.translateAlternateColorCodes('&', text); + } + + private static String capitalize(String s) { + if (s == null || s.isEmpty()) return s; + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } + + // ── Command ─────────────────────────────────────────────────────────────── + + private class GoCommand extends Command { + + GoCommand(String name, String permission, String[] aliases) { + super(name, permission.isEmpty() ? null : permission, aliases); + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (!(sender instanceof ProxiedPlayer)) { + sender.sendMessage(c("&cDieser Befehl ist nur für Spieler verfügbar.")); + return; + } + + ProxiedPlayer player = (ProxiedPlayer) sender; + + if (args.length >= 1) { + String target = args[0].trim(); + ServerInfo server = null; + for (Map.Entry entry : ProxyServer.getInstance().getServers().entrySet()) { + if (entry.getKey().equalsIgnoreCase(target)) { + server = entry.getValue(); + break; + } + } + + if (server == null) { + player.sendMessage(c("&cServer &e" + args[0] + " &cnicht gefunden.")); + return; + } + + if (player.getServer() != null + && player.getServer().getInfo().getName().equalsIgnoreCase(server.getName())) { + player.sendMessage(c("&7Du bist bereits auf &e" + server.getName() + "&7.")); + return; + } + + player.sendMessage(c("&7Verbinde mit &e" + server.getName() + "&7...")); + player.connect(server); + return; + } + + sendServerMenu(player); + } + + private void sendServerMenu(ProxiedPlayer player) { + player.sendMessage(c(colorHeader)); + + for (String serverName : getServerList()) { + ServerInfo info = ProxyServer.getInstance().getServerInfo(serverName); + if (info == null) continue; + + boolean isCurrent = player.getServer() != null + && player.getServer().getInfo().getName().equalsIgnoreCase(serverName); + int count = info.getPlayers().size(); + + TextComponent line = new TextComponent(c(colorEntry)); + TextComponent btn = new TextComponent(c((isCurrent ? colorOffline : colorOnline) + capitalize(serverName))); + + if (!isCurrent) { + btn.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, + "/" + commandName + " " + serverName)); + btn.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new ComponentBuilder(c("&7Klicken zum Verbinden\n&7Online: &a" + count + " Spieler")).create())); + } + + line.addExtra(btn); + line.addExtra(new TextComponent(c(" &8(&7" + count + " online&8)"))); + if (isCurrent) line.addExtra(new TextComponent(c(" " + colorSelf))); + + player.sendMessage(line); + } + + player.sendMessage(c("&8&m----------------------------")); + player.sendMessage(c("&7Tipp: &e/" + commandName + " &7für direkten Wechsel")); + } + } + + // ── Tab-Completion ───────────────────────────────────────────────────────── + + public class GoTabListener implements Listener { + + @EventHandler + public void onTabComplete(TabCompleteEvent event) { + String cursor = event.getCursor(); + if (cursor == null) return; + + String lower = cursor.toLowerCase(); + boolean matches = lower.startsWith("/" + commandName.toLowerCase() + " "); + if (!matches) { + for (String alias : aliases) { + if (lower.startsWith("/" + alias.toLowerCase() + " ")) { + matches = true; + break; + } + } + } + if (!matches) return; + + if (event.getSender() instanceof ProxiedPlayer) { + ProxiedPlayer p = (ProxiedPlayer) event.getSender(); + if (!permission.isEmpty() && !p.hasPermission(permission)) return; + } + + int spaceIdx = cursor.indexOf(' '); + String input = spaceIdx >= 0 ? cursor.substring(spaceIdx + 1).toLowerCase() : ""; + + List suggestions = new ArrayList<>(); + for (String server : getServerList()) { + if (server.toLowerCase().startsWith(input)) suggestions.add(server); + } + + event.getSuggestions().clear(); + event.getSuggestions().addAll(suggestions); + } + } +} diff --git a/src/main/resources/chat.yml b/src/main/resources/chat.yml index 6fe2fcd..b8d34c5 100644 --- a/src/main/resources/chat.yml +++ b/src/main/resources/chat.yml @@ -17,9 +17,9 @@ server-colors: skyblock: color: "&b" display: "SkyBlock" - creative: + citybuild: color: "&#A020E8" - display: "Creative" + display: "CityBuild" minigames: color: "&e" display: "MiniGames" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 40037c5..0724c89 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -176,6 +176,12 @@ commands: description: Report schliessen (Admin) usage: /reportclose + # ── ServerSwitcherModule ────────────────────────────────── + go: + description: Schneller Serverwechsel ueber Chat-Menue oder direkt + usage: /go [servername] + aliases: [wechsel, switch] + permissions: # ── StatusAPI Core ──────────────────────────────────────── statusapi.update.notify: @@ -263,3 +269,8 @@ permissions: commandblocker.admin: description: CommandBlocker verwalten (/cb) default: op + + # ── ServerSwitcherModule ────────────────────────────────── + serverswitcher.use: + description: Zugriff auf /go (Schneller Serverwechsel) + default: false