diff --git a/src/de/steamwar/bungeecore/BungeeCore.java b/src/de/steamwar/bungeecore/BungeeCore.java index a1827524..e319ed69 100644 --- a/src/de/steamwar/bungeecore/BungeeCore.java +++ b/src/de/steamwar/bungeecore/BungeeCore.java @@ -69,6 +69,7 @@ public class BungeeCore extends Plugin { public static final Map serverPermissions = new HashMap<>(); public static final Map commands = new HashMap<>(); + public static Node local; private ErrorLogger errorLogger; private TablistManager tablistManager; @@ -110,9 +111,8 @@ public class BungeeCore extends Plugin { new Fabric(); new SubserverProtocolFixer(); - new Node.LocalNode(); + local = new Node.LocalNode(); //new Node.RemoteNode("lx"); - //new Node.RemoteNode("az"); commands.put("/tp", null); commands.put("/bc", null); diff --git a/src/de/steamwar/bungeecore/Node.java b/src/de/steamwar/bungeecore/Node.java index f07293aa..6c221200 100644 --- a/src/de/steamwar/bungeecore/Node.java +++ b/src/de/steamwar/bungeecore/Node.java @@ -19,6 +19,7 @@ package de.steamwar.bungeecore; +import net.md_5.bungee.BungeeCord; import net.md_5.bungee.api.ProxyServer; import java.io.*; @@ -30,7 +31,6 @@ import java.util.logging.Level; public abstract class Node { private static final List OPENJ9_ARGS = Arrays.asList("-Xgc:excessiveGCratio=80", "-Xsyslog:none", "-Xtrace:none", "-Xdisableexplicitgc", "-XX:+AlwaysPreTouch", "-XX:+CompactStrings", "-XX:-HeapDumpOnOutOfMemory", "-XX:+ExitOnOutOfMemoryError", "-Dlog4j.configurationFile=log4j2.xml"); - private static final double MIN_FREE_MEM = 4.0 * 1024 * 1024; // 4 GiB private static final Set JAVA_8 = new HashSet<>(); static { JAVA_8.add("paper-1.8.8.jar"); @@ -40,43 +40,57 @@ public abstract class Node { JAVA_8.add("spigot-1.10.2.jar"); } + private static final long MIN_FREE_MEM = 4 * 1024 * 1024L; // 4 GiB + private static final double MAX_LOAD = 0.8; + private static final List nodes = new ArrayList<>(); - public static Node local = null; public static Node getNode() { - Node node = local; - double minLoad = local.getLoad(); - if(minLoad < 0.5) - return local; - - synchronized (nodes) { - Iterator it = nodes.iterator(); - while(it.hasNext()) { - Node n = it.next(); - double load = n.getLoad(); - if (load < minLoad) { - minLoad = load; - node = n; - } else if (load == Double.POSITIVE_INFINITY) { - BungeeCore.get().getLogger().log(Level.SEVERE, "Removing " + n.getName() + " due to infinite load!"); - it.remove(); - } - } + for(Node node : nodes) { + if(node.belowLoadLimit) + return node; } - return node; + return null; } public static void forEach(Consumer consumer) { - consumer.accept(local); - synchronized (nodes) { - nodes.forEach(consumer); - } + nodes.forEach(consumer); } public abstract ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String xmx, String... dParams); - public abstract void execute(String... command); - public abstract String getName(); - public abstract double getLoad(); + + protected abstract ProcessBuilder prepareExecution(String... command); + protected abstract void calcLoadLimit(); + + protected final String hostname; + protected volatile boolean belowLoadLimit = true; + + private long previousCpuLoaded = 0; + private long previousCpuTotal = 0; + + protected Node(String hostname) { + this.hostname = hostname; + nodes.add(this); + BungeeCord.getInstance().getScheduler().schedule(BungeeCore.get(), this::calcLoadLimit, 1, 2, TimeUnit.SECONDS); + } + + public void execute(String... command) { + try { + prepareExecution(command).start().waitFor(); + } catch (IOException e) { + throw new SecurityException("Could not execute command", e); + } catch (InterruptedException e) { + ProxyServer.getInstance().getLogger().log(Level.SEVERE, "Interrupted during execution", e); + Thread.currentThread().interrupt(); + } + } + + public boolean belowLoadLimit() { + return belowLoadLimit; + } + public String getName() { + return hostname; + } protected void constructServerstart(File directory, List cmd, String serverJar, String worldDir, String levelName, int port, String xmx, String... dParams) { if (JAVA_8.contains(serverJar)) @@ -106,26 +120,31 @@ public abstract class Node { cmd.add("nogui"); } - protected void execute(ProcessBuilder builder) { - try { - builder.start().waitFor(); - } catch (IOException e) { - throw new SecurityException("Could not execute command", e); - } catch (InterruptedException e) { - ProxyServer.getInstance().getLogger().log(Level.SEVERE, "Interrupted during execution", e); - Thread.currentThread().interrupt(); + protected void calcLoadLimit(BufferedReader stat, BufferedReader meminfo) throws IOException { + String[] cpuline = stat.readLine().split(" "); // 0-1: prefix, 2: user, 3: nice, 4: system, 5: idle, 6: iowait, 7: irq, 8: softirq, 9: steal, 10: guest, 11: guest_nice + long cpuLoaded = Long.parseLong(cpuline[2]) + Long.parseLong(cpuline[4]) + Long.parseLong(cpuline[6]) + Long.parseLong(cpuline[7]) + Long.parseLong(cpuline[8]) + Long.parseLong(cpuline[9]) + Long.parseLong(cpuline[10]) + Long.parseLong(cpuline[11]); + long cpuTotal = cpuLoaded + Long.parseLong(cpuline[3]) + Long.parseLong(cpuline[5]); + + cpuLoaded -= previousCpuLoaded; + cpuTotal -= previousCpuTotal; + previousCpuLoaded += cpuLoaded; + previousCpuTotal += cpuTotal; + + String line = meminfo.readLine(); + while(!line.startsWith("MemAvailable")) { + line = meminfo.readLine(); } + + long availableMem = Long.parseLong(line.replaceAll(" +", " ").split(" ")[1]); + belowLoadLimit = cpuLoaded / (double)cpuTotal <= MAX_LOAD && availableMem >= MIN_FREE_MEM; } public static class LocalNode extends Node { - private static final File meminfo = new File("/proc/meminfo"); - private static final File loadavg = new File("/proc/loadavg"); - - private final int cores; + private static final File MEMINFO = new File("/proc/meminfo"); + private static final File STAT = new File("/proc/stat"); public LocalNode() { - this.cores = Runtime.getRuntime().availableProcessors(); - local = this; + super("sw"); } @Override @@ -138,74 +157,28 @@ public abstract class Node { } @Override - public void execute(String... command) { - execute(new ProcessBuilder(command)); + protected void calcLoadLimit() { + try (BufferedReader loadavg = new BufferedReader(new InputStreamReader(new FileInputStream(STAT)))) { + try (BufferedReader meminfo = new BufferedReader(new InputStreamReader(new FileInputStream(MEMINFO)))) { + calcLoadLimit(loadavg, meminfo); + } + } catch (IOException e) { + BungeeCore.get().getLogger().log(Level.SEVERE, "Could not read local load", e); + belowLoadLimit = false; + } } @Override - public String getName() { - return "local"; - } - - @Override - public double getLoad() { - double totalMem; - double freeMem; - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(meminfo)))) { - totalMem = Double.parseDouble(bufferedReader.readLine().replaceAll(" +", " ").split(" ")[1]); - bufferedReader.readLine(); - freeMem = Double.parseDouble(bufferedReader.readLine().replaceAll(" +", " ").split(" ")[1]); - } catch (IOException e) { - throw new SecurityException("Could not read local memory", e); - } - - double cpuLoad; - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(loadavg)))) { - cpuLoad = Double.parseDouble(bufferedReader.readLine().split(" ")[0]); - } catch (IOException e) { - throw new SecurityException("Could not read local cpu", e); - } - - return cpuLoad / cores + (freeMem > MIN_FREE_MEM ? 0 : ((totalMem - freeMem) / totalMem)); + protected ProcessBuilder prepareExecution(String... command) { + return new ProcessBuilder(command); } } public static class RemoteNode extends Node { - private final int cores; - private final String remote; - public RemoteNode(String remote) { - this.remote = remote; - - //Determine core count - Process process; - try { - process = new ProcessBuilder("ssh", remote, "nproc").start(); - if(!process.waitFor(5, TimeUnit.SECONDS)) - throw new IOException("Timeout of " + remote + " on init"); - } catch (IOException e) { - BungeeCore.get().getLogger().log(Level.SEVERE, "Could not initialize " + remote); - cores = 1; - return; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - cores = 1; - return; - } - - int c; - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - c = Integer.parseInt(bufferedReader.readLine()); - } catch (IOException | NumberFormatException e) { - BungeeCore.get().getLogger().log(Level.SEVERE, "Could not read cores of" + remote, e); - c = 1; - } - cores = c; - BungeeCore.get().getLogger().log(Level.INFO, "Adding node " + remote + " with " + cores + " cores."); - - synchronized (nodes) { - nodes.add(this); - } + public RemoteNode(String hostname) { + super(hostname); + BungeeCore.get().getLogger().log(Level.INFO, "Added node " + hostname); } @Override @@ -214,7 +187,7 @@ public abstract class Node { cmd.add("ssh"); cmd.add("-L"); cmd.add(port + ":localhost:" + port); - cmd.add(remote); + cmd.add(hostname); cmd.add("cd"); cmd.add(directory.getPath()); cmd.add(";"); @@ -223,43 +196,30 @@ public abstract class Node { } @Override - public void execute(String... command) { + protected ProcessBuilder prepareExecution(String... command) { List cmd = new ArrayList<>(); cmd.add("ssh"); - cmd.add(remote); + cmd.add(hostname); cmd.addAll(Arrays.asList(command)); - execute(new ProcessBuilder(cmd)); + return new ProcessBuilder(cmd); } @Override - public String getName() { - return remote; - } - - @Override - public double getLoad() { - Process process; + protected void calcLoadLimit() { try { - process = new ProcessBuilder("ssh", remote, "cat /proc/loadavg;cat /proc/meminfo").start(); - if(!process.waitFor(1, TimeUnit.SECONDS)) - return Double.POSITIVE_INFINITY; + Process process = prepareExecution("cat /proc/stat /proc/meminfo").start(); + if(process.waitFor(1, TimeUnit.SECONDS)) + throw new IOException(hostname + " timeout"); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + calcLoadLimit(reader, reader); + } } catch (IOException e) { - BungeeCore.get().getLogger().log(Level.SEVERE, "Could starting process to read load", e); - return Double.POSITIVE_INFINITY; + if(belowLoadLimit) + BungeeCore.get().getLogger().log(Level.SEVERE, "Could read remote load", e); + belowLoadLimit = false; } catch (InterruptedException e) { Thread.currentThread().interrupt(); - return Double.POSITIVE_INFINITY; - } - - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - double cpuLoad = Double.parseDouble(bufferedReader.readLine().split(" ")[0]); - double totalMem = Double.parseDouble(bufferedReader.readLine().replaceAll(" +", " ").split(" ")[1]); - bufferedReader.readLine(); - double freeMem = Double.parseDouble(bufferedReader.readLine().replaceAll(" +", " ").split(" ")[1]); - return cpuLoad / cores + (freeMem > MIN_FREE_MEM ? 0 : ((totalMem - freeMem) / totalMem)); - } catch (IOException e) { - BungeeCore.get().getLogger().log(Level.SEVERE, "Could read load", e); - return Double.POSITIVE_INFINITY; + belowLoadLimit = false; } } } diff --git a/src/de/steamwar/bungeecore/ServerStarter.java b/src/de/steamwar/bungeecore/ServerStarter.java index a00b5312..9373b57f 100644 --- a/src/de/steamwar/bungeecore/ServerStarter.java +++ b/src/de/steamwar/bungeecore/ServerStarter.java @@ -4,6 +4,7 @@ import de.steamwar.bungeecore.sql.EventFight; import de.steamwar.bungeecore.sql.SteamwarUser; import de.steamwar.bungeecore.sql.Team; import de.steamwar.bungeecore.sql.Tutorial; +import de.steamwar.messages.ChatSender; import net.md_5.bungee.api.connection.ProxiedPlayer; import java.io.File; @@ -67,7 +68,7 @@ public class ServerStarter { public ServerStarter event(EventFight eventFight) { arena(eventFight.getSpielmodus(), eventFight.getMap()); - node = Node.local; + node = BungeeCore.local; worldDir = EVENT_PATH; worldCleanup = () -> {}; arguments.put("fightID", String.valueOf(eventFight.getFightID())); @@ -246,8 +247,15 @@ public class ServerStarter { int port = portrange.freePort(); String serverName = serverNameProvider.apply(port); - if(node == null) + if(node == null) { node = Node.getNode(); + if(node == null) { + for (ProxiedPlayer p : playersToSend) + ChatSender.of(p).system("SERVER_START_OVERLOAD"); + + return null; + } + } if(worldName == null) worldName = serverToWorldName(serverName); diff --git a/src/de/steamwar/bungeecore/commands/BauCommand.java b/src/de/steamwar/bungeecore/commands/BauCommand.java index 4c1fa119..dfc0a5b3 100644 --- a/src/de/steamwar/bungeecore/commands/BauCommand.java +++ b/src/de/steamwar/bungeecore/commands/BauCommand.java @@ -275,7 +275,7 @@ public class BauCommand extends SWCommand { break; } } - SubserverSystem.deleteFolder(Node.local, world); + SubserverSystem.deleteFolder(BungeeCore.local, world); Message.send("BAU_DELETE_DELETED", player); }); } diff --git a/src/de/steamwar/bungeecore/commands/ChallengeCommand.java b/src/de/steamwar/bungeecore/commands/ChallengeCommand.java index 9ec135ea..6f050ac9 100644 --- a/src/de/steamwar/bungeecore/commands/ChallengeCommand.java +++ b/src/de/steamwar/bungeecore/commands/ChallengeCommand.java @@ -20,21 +20,13 @@ package de.steamwar.bungeecore.commands; import de.steamwar.bungeecore.*; -import de.steamwar.bungeecore.listeners.mods.ModLoaderBlocker; import de.steamwar.bungeecore.sql.IgnoreSystem; import de.steamwar.command.SWCommand; -import de.steamwar.command.TypeMapper; import de.steamwar.command.TypeValidator; -import net.md_5.bungee.BungeeCord; -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.connection.ProxiedPlayer; -import java.util.ArrayList; -import java.util.Collection; import java.util.LinkedList; -import java.util.stream.Collectors; import static de.steamwar.bungeecore.Storage.challenges; @@ -52,8 +44,10 @@ public class ChallengeCommand extends SWCommand { challenges.remove(p); Subserver arena = new ServerStarter().arena(mode, map).blueLeader(player).redLeader(target).start(); - Message.broadcast("CHALLENGE_BROADCAST", "CHALLENGE_BROADCAST_HOVER", - new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/arena " + arena.getServer().getName()), mode.getDisplayName(), p.getName(), target.getName()); + if(arena != null) { + Message.broadcast("CHALLENGE_BROADCAST", "CHALLENGE_BROADCAST_HOVER", + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/arena " + arena.getServer().getName()), mode.getDisplayName(), p.getName(), target.getName()); + } }else{ if(!challenges.containsKey(p)){ challenges.put(p, new LinkedList<>()); diff --git a/src/de/steamwar/bungeecore/commands/FightCommand.java b/src/de/steamwar/bungeecore/commands/FightCommand.java index 1666e17e..5911db55 100644 --- a/src/de/steamwar/bungeecore/commands/FightCommand.java +++ b/src/de/steamwar/bungeecore/commands/FightCommand.java @@ -97,7 +97,7 @@ public class FightCommand extends SWCommand { String command = precommand + mode.getChatName() + " Random"; start.setBold(true); start.setColor(ChatColor.GRAY); - start.setText(sender.parseToLegacy("FIGHT_ARENA_RANDOM") + " "); + start.setText(sender.parseToPlain("FIGHT_ARENA_RANDOM") + " "); start.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("§e" + command))); start.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command)); current = new TextComponent(); @@ -197,11 +197,13 @@ public class FightCommand extends SWCommand { } @Register - public void challenge(@Validator("arenaPlayer") ProxiedPlayer player, @Mapper("nonHistoricArenaMode") @OptionalValue("") @AllowNull ArenaMode arenaMode, @Mapper("arenaMap") @OptionalValue("") @AllowNull String map) { + public void fight(@Validator("arenaPlayer") ProxiedPlayer player, @Mapper("nonHistoricArenaMode") @OptionalValue("") @AllowNull ArenaMode arenaMode, @Mapper("arenaMap") @OptionalValue("") @AllowNull String map) { createArena(player, "/fight ", true, arenaMode, map, false, (p, mode, m) -> { Subserver arena = new ServerStarter().arena(mode, m).blueLeader(p).start(); - Message.broadcast("FIGHT_BROADCAST", "FIGHT_BROADCAST_HOVER" - , new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/arena " + arena.getServer().getName()), mode.getDisplayName(), p.getName()); + if(arena != null) { + Message.broadcast("FIGHT_BROADCAST", "FIGHT_BROADCAST_HOVER" + , new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/arena " + arena.getServer().getName()), mode.getDisplayName(), p.getName()); + } }); } diff --git a/src/de/steamwar/bungeecore/commands/HistoricCommand.java b/src/de/steamwar/bungeecore/commands/HistoricCommand.java index 6c77fab9..03e7abbb 100644 --- a/src/de/steamwar/bungeecore/commands/HistoricCommand.java +++ b/src/de/steamwar/bungeecore/commands/HistoricCommand.java @@ -33,11 +33,13 @@ public class HistoricCommand extends SWCommand { } @Register - public void challenge(@Validator("arenaPlayer") ProxiedPlayer player, @Mapper("nonHistoricArenaMode") @OptionalValue("") @AllowNull ArenaMode arenaMode, @Mapper("arenaMap") @OptionalValue("") @AllowNull String map) { + public void historic(@Validator("arenaPlayer") ProxiedPlayer player, @Mapper("nonHistoricArenaMode") @OptionalValue("") @AllowNull ArenaMode arenaMode, @Mapper("arenaMap") @OptionalValue("") @AllowNull String map) { FightCommand.createArena(player, "/historic ", true, arenaMode, map, true, (p, mode, m) -> { Subserver arena = new ServerStarter().arena(mode, m).blueLeader(p).start(); - Message.broadcast("HISTORIC_BROADCAST", "HISTORIC_BROADCAST_HOVER", - new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/arena " + arena.getServer().getName()), mode.getDisplayName(), p.getName()); + if(arena != null) { + Message.broadcast("HISTORIC_BROADCAST", "HISTORIC_BROADCAST_HOVER", + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/arena " + arena.getServer().getName()), mode.getDisplayName(), p.getName()); + } }); } } diff --git a/src/de/steamwar/bungeecore/commands/StatCommand.java b/src/de/steamwar/bungeecore/commands/StatCommand.java index 753f2537..ffae1687 100644 --- a/src/de/steamwar/bungeecore/commands/StatCommand.java +++ b/src/de/steamwar/bungeecore/commands/StatCommand.java @@ -52,6 +52,6 @@ public class StatCommand extends SWCommand { throw new SecurityException(e.getMessage(), e); } - Node.forEach(node -> Message.send("STAT_SERVER", sender, node.getName(), node.getLoad(), serverCount.getOrDefault(node.getName(), 0))); + Node.forEach(node -> Message.send("STAT_SERVER", sender, node.getName(), node.belowLoadLimit(), serverCount.getOrDefault(node.getName(), 0))); } } diff --git a/src/de/steamwar/bungeecore/commands/TutorialCommand.java b/src/de/steamwar/bungeecore/commands/TutorialCommand.java index 72d0e2af..30cc2961 100644 --- a/src/de/steamwar/bungeecore/commands/TutorialCommand.java +++ b/src/de/steamwar/bungeecore/commands/TutorialCommand.java @@ -156,8 +156,8 @@ public class TutorialCommand extends SWCommand { File tutorialWorld = tutorial.world(); if (tutorialWorld.exists()) - SubserverSystem.deleteFolder(Node.local, tutorialWorld.getPath()); - ServerStarter.copyWorld(Node.local, tempWorld.getPath(), tutorialWorld.getPath()); + SubserverSystem.deleteFolder(BungeeCore.local, tutorialWorld.getPath()); + ServerStarter.copyWorld(BungeeCore.local, tempWorld.getPath(), tutorialWorld.getPath()); Message.send("TUTORIAL_CREATED", player); }, 1, TimeUnit.SECONDS); } diff --git a/src/de/steamwar/bungeecore/sql/Tutorial.java b/src/de/steamwar/bungeecore/sql/Tutorial.java index 75916f2e..7c82f066 100644 --- a/src/de/steamwar/bungeecore/sql/Tutorial.java +++ b/src/de/steamwar/bungeecore/sql/Tutorial.java @@ -19,7 +19,7 @@ package de.steamwar.bungeecore.sql; -import de.steamwar.bungeecore.Node; +import de.steamwar.bungeecore.BungeeCore; import de.steamwar.bungeecore.ServerStarter; import de.steamwar.bungeecore.SubserverSystem; @@ -124,7 +124,7 @@ public class Tutorial { public void delete() { delete.update(id); - SubserverSystem.deleteFolder(Node.local, world().getPath()); + SubserverSystem.deleteFolder(BungeeCore.local, world().getPath()); } public File world() { diff --git a/src/de/steamwar/messages/BungeeCore.properties b/src/de/steamwar/messages/BungeeCore.properties index 8a4a678a..9872dfea 100644 --- a/src/de/steamwar/messages/BungeeCore.properties +++ b/src/de/steamwar/messages/BungeeCore.properties @@ -18,6 +18,8 @@ DEV_UNKNOWN_SERVER=§cPlease specify a dev server. DISABLED=§cCurrently disabled. +SERVER_START_OVERLOAD=§cServer start cancelled due to overload. Please try again later. + #ModLoader blocker MODLOADER_INSTALLED=§7You play with §e{0} §7client. Therefore you can't join arenas. MODLOADER_INSTALLED_FABRIC=§7You play with §e{0} §7client. You can join arenas only with the FabricModSender https://steamwar.de/downloads installed. @@ -112,7 +114,7 @@ MOD_YELLOW_PLUR=§7Deactivate the mods\n§e{0}\n§7to continue playing on §eSte #Various commands ALERT=§f{0} -STAT_SERVER=§7Server §e{0}§8: §7Load §e{1} §7Server count §e{2} +STAT_SERVER=§7Server §e{0}§8: §7Below limit §e{1} §7Server count §e{2} #Ban&Mute-Command PUNISHMENT_USAGE=§8/§7{0} §8[§eplayer§8] [§edd§8.§emm§8.§eyyyy §7or §edd§8.§emm§8.§eyyyy§8_§ehh§8:§emm §7or §eperma§8] [§ereason§8] diff --git a/src/de/steamwar/messages/BungeeCore_de.properties b/src/de/steamwar/messages/BungeeCore_de.properties index c2bc2d3b..e0341a77 100644 --- a/src/de/steamwar/messages/BungeeCore_de.properties +++ b/src/de/steamwar/messages/BungeeCore_de.properties @@ -14,6 +14,8 @@ DEV_UNKNOWN_SERVER=§cBitte gib einen DevServer an. DISABLED=§cDerzeit deaktiviert. +SERVER_START_OVERLOAD=§cDer Serverstart wurde aufgrund von Überlastung abgebrochen. Versuche es später erneut. + #ModLoader blocker MODLOADER_INSTALLED=§7Du spielst mit §e{0} §7Client. Daher kannst du keinen Arenen beitreten. MODLOADER_INSTALLED_FABRIC=§7Du spielst mit §e{0} §7Client. Nur mit dem FabricModSender https://steamwar.de/downloads kannst du Arenen beitreten. @@ -97,7 +99,7 @@ MOD_YELLOW_SING=§7Deaktiviere den Mod §e{0}§7, um weiter auf §eSteam§8War MOD_YELLOW_PLUR=§7Deaktiviere die Mods\n§e{0}\n§7um weiter auf §eSteam§8War §7spielen zu können. #Various commands -STAT_SERVER=§7Server §e{0}§8: §7Load §e{1} §7Serveranzahl §e{2} +STAT_SERVER=§7Server §e{0}§8: §7Startfähig §e{1} §7Serveranzahl §e{2} #Ban&Mute-Command PUNISHMENT_USAGE=§8/§7{0} §8[§eSpieler§8] [§edd§8.§emm§8.§eyyyy §7oder §edd§8.§emm§8.§eyyyy§8_§ehh§8:§emm §7oder §eperma§8] [§eGrund§8] diff --git a/src/de/steamwar/messages/ChatSender.java b/src/de/steamwar/messages/ChatSender.java index 6879505f..5b79204f 100644 --- a/src/de/steamwar/messages/ChatSender.java +++ b/src/de/steamwar/messages/ChatSender.java @@ -107,6 +107,14 @@ public interface ChatSender { return new TextComponent(parse(prefixed, message)); } + default String parseToPlain(String format, Object... params) { + return parseToPlain(new Message(format, params)); + } + + default String parseToPlain(Message message) { + return parseToComponent(false, message).toPlainText(); + } + default String parseToLegacy(String format, Object... params) { return parseToLegacy(new Message(format, params)); }