package com.lifeknight.relaymcbungeemain; import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteStreams; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.lifeknight.relaymcbungeemain.commands.amenities.*; import com.lifeknight.relaymcbungeemain.commands.chat.*; import com.lifeknight.relaymcbungeemain.commands.game.*; import com.lifeknight.relaymcbungeemain.commands.links.*; import com.lifeknight.relaymcbungeemain.commands.staff.*; import com.lifeknight.relaymcbungeemain.commands.staff.punishments.*; import com.lifeknight.relaymcbungeemain.network.MessageServer; import com.lifeknight.relaymcbungeemain.network.MessageServerHandler; import com.lifeknight.relaymcbungeemain.network.ServerMessageProcessor; import com.lifeknight.relaymcbungeemain.player.SmartPlayer; import com.lifeknight.relaymcbungeemain.queue.GameServer; import com.lifeknight.relaymcbungeemain.queue.Queue; import com.lifeknight.relaymcbungeemain.queue.ServerDetails; import com.lifeknight.relaymcbungeemain.utilities.*; import com.lifeknight.relayutils.RelayUtils; import com.lifeknight.relayutils.basic.Miscellaneous; import com.lifeknight.relayutils.basic.Text; import com.lifeknight.relayutils.data.QueueData; import com.lifeknight.relayutils.game.GameDetails; import com.lifeknight.relayutils.game.GameType; import com.lifeknight.relayutils.game.MainGame; import com.lifeknight.relayutils.network.Command; import com.lifeknight.relayutils.network.Message; import com.lifeknight.relayutils.network.Recipient; import com.lifeknight.relayutils.player.data.SmartDatabase; import com.lifeknight.relayutils.utilities.ComponentBuilder; 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.BaseComponent; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.event.PluginMessageEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.chat.ComponentSerializer; import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.ConfigurationProvider; import net.md_5.bungee.config.YamlConfiguration; import net.md_5.bungee.event.EventHandler; import java.io.*; import java.lang.instrument.Instrumentation; import java.util.*; import java.util.concurrent.TimeUnit; import static com.lifeknight.relayutils.basic.Text.getSimpleDateFormat; import static net.md_5.bungee.api.ChatColor.*; public final class Main extends Plugin implements Listener { public static Main instance; public static final String CHANNEL = "relay:mc"; public static int uniquePlayers = 0; private static int tickCount = 0; private static boolean enabled; private final static String CONFIG_FILE = "relaymc.yml"; public static final long START_OF_2021 = 1609459200000L; private static final long SUNDAY_3_13_2022_4_AM = 1647158400000L; private static final long DATE_10_1_2023_4AM = 1696147200000L; public static final String DISCORD_URL = "https://discord.gg/tsMyACa4Yj"; public static final List> playerCountLog = new ArrayList<>(); public static final Map onlineLog = new HashMap<>(); private static long nextRestart = SUNDAY_3_13_2022_4_AM; private static long restartTime = Long.MIN_VALUE; private static boolean restartMessageSent = false; public static boolean isRestarting = false; public static boolean maintenanceMode = true; public static boolean open = true; public static List whitelistedPlayers = new ArrayList<>(); private static final Map MAIN_GAME_TO_PLUGIN = new HashMap<>(); private static long lastDailyReset = 0L; private static long lastWeeklyReset = 0L; private static long lastMonthlyReset = 0L; public static boolean queueOpen = true; public static String queueCloseMessage; public static int maxPlayers = 100; public static int infoMessageDelay = 15; private static BaseComponent[] latestInfoMessage = null; private static List infoMessages = Miscellaneous.processList(Miscellaneous.getList( RelayUtils.format("%sFound a bug with the server? Use %s/bugreport %sor join our Discord server and report it!", RED, DARK_PURPLE, RED), RelayUtils.format("%sHave any ideas for new minigames, or suggestions for the server? Join our discord with %s/discord %sand put it in #suggestions!", GREEN, DARK_GREEN, GREEN), RelayUtils.format("%sHave any questions about the server? Use %s/discord %sto join our Discord server and ask our staff.", YELLOW, DARK_PURPLE, YELLOW), RelayUtils.format("%sCaught a player who appears to be cheating? %sUse %s/DISCORD %sto join our Discord server to report them or use %s/report!", RED, YELLOW, DARK_PURPLE, YELLOW, RED), RelayUtils.format("%sCurious on how Manhunt has been enhanced on Relay? Join our Discord server with %s/discord %sto see!", YELLOW, DARK_PURPLE, YELLOW), RelayUtils.format("%sBuy ranks on our store (/store) to help us get more servers and expand our network!", YELLOW), RelayUtils.format("%sHelp our server grow by voting (/vote)!", GREEN)), s -> new BaseComponent[]{new TextComponent(s)}); public static long gamesPlayed = 0; public static long peakPlayers = 0; public static int newPlayers = 0; public static final List SCHEDULED_TASKS = new ArrayList<>(); private static File file; private static Configuration bungeeConfig; private static volatile Instrumentation globalInstrumentation; private static Map gamesPlayedMap = new HashMap<>(); public static void premain(final String agentArgs, final Instrumentation inst) { globalInstrumentation = inst; } public static Instrumentation getInstrumentation() { return globalInstrumentation; } public static int getOnNetworkPlayerCount() { int i = 0; for (SmartPlayer onlineSmartPlayer : SmartPlayer.getOnlineSmartPlayers()) { if (onlineSmartPlayer.getServer() != null && !onlineSmartPlayer.getServer().isLimbo()) { i++; } } return i; } public static int getOnlineCount() { return SmartPlayer.getOnlineSmartPlayers().size(); } public static void onGamePlayed(GameType gameType1) { gamesPlayed++; gamesPlayedMap.put(gameType1, gamesPlayedMap.getOrDefault(gameType1, 0) + 1); } @Override public void onEnable() { instance = this; enabled = true; RelayUtils.setName("Proxy"); RelayUtils.setLogger(this.getLogger()); PluginManager pluginManager = this.getProxy().getPluginManager(); pluginManager.registerCommand(this, new RelayMCCommand()); pluginManager.registerCommand(this, new MaintenanceCommand()); pluginManager.registerCommand(this, new BroadcastCommand()); pluginManager.registerCommand(this, new SudoCommand()); pluginManager.registerCommand(this, new GroupCommand()); pluginManager.registerCommand(this, new PurchaseCommand()); pluginManager.registerCommand(this, new CreditsCommand()); pluginManager.registerCommand(this, new MessagesCommand()); pluginManager.registerCommand(this, new ManhuntQuitsCommand()); pluginManager.registerCommand(this, new ServersCommand()); pluginManager.registerCommand(this, new PunishmentsCommand()); pluginManager.registerCommand(this, new IPBanCommand()); pluginManager.registerCommand(this, new BanCommand()); pluginManager.registerCommand(this, new MuteCommand()); pluginManager.registerCommand(this, new WarnCommand()); pluginManager.registerCommand(this, new TempBanCommand()); pluginManager.registerCommand(this, new TempMuteCommand()); pluginManager.registerCommand(this, new TempWarnCommand()); pluginManager.registerCommand(this, new UnBanCommand()); pluginManager.registerCommand(this, new UnMuteCommand()); pluginManager.registerCommand(this, new ReportCommand()); pluginManager.registerCommand(this, new BugReportCommand()); pluginManager.registerCommand(this, new ReportsCommand()); pluginManager.registerCommand(this, new HelpCommand()); pluginManager.registerCommand(this, new StoreCommand()); pluginManager.registerCommand(this, new PremiumCommand()); pluginManager.registerCommand(this, new DiscordCommand()); pluginManager.registerCommand(this, new YouTubeCommand()); pluginManager.registerCommand(this, new TwitterCommand()); pluginManager.registerCommand(this, new VoteCommand()); pluginManager.registerCommand(this, new WhereAmICommand()); pluginManager.registerCommand(this, new PingCommand()); pluginManager.registerCommand(this, new PlayCommand()); pluginManager.registerCommand(this, new CancelCommand()); pluginManager.registerCommand(this, new RejoinCommand()); pluginManager.registerCommand(this, new DuelAcceptCommand()); pluginManager.registerCommand(this, new ManhuntAcceptCommand()); pluginManager.registerCommand(this, new ManhuntCommand()); pluginManager.registerCommand(this, new HubCommand()); pluginManager.registerCommand(this, new FriendCommand()); pluginManager.registerCommand(this, new FriendListCommand()); pluginManager.registerCommand(this, new BlockCommand()); pluginManager.registerCommand(this, new UnblockCommand()); pluginManager.registerCommand(this, new MessageCommand()); pluginManager.registerCommand(this, new ReplyCommand()); pluginManager.registerCommand(this, new PartyCommand()); pluginManager.registerCommand(this, new PartyListCommand()); pluginManager.registerCommand(this, new ChatCommand()); pluginManager.registerCommand(this, new AllChatCommand()); pluginManager.registerCommand(this, new PartyChatCommand()); pluginManager.registerCommand(this, new StaffChatCommand()); pluginManager.registerCommand(this, new SpectateCommand()); pluginManager.registerCommand(this, new NicknameCommand()); pluginManager.registerListener(this, this); pluginManager.registerListener(this, new Listeners()); this.getProxy().registerChannel(CHANNEL); this.readConfigurationFile(); this.saveConfigurationFile(); schedule(() -> { setupConfig(); MessageServer.start(RelayUtils.getPort()); }, 5); SmartDatabase.setup(); PlayerUtilities.startup(); BotUtils.startup(); RelayUtils.prepareProfileDatabase(); RelayUtils.prepareAllStatisticsTables(); RelayUtils.prepareLeaderboard(); RelayUtils.prepareGamePlayerDatabase(); RelayUtils.prepareArenaDatabase(); RelayUtils.prepareParkourTables(); RelayUtils.prepareCustomKitsLeaderboard(); RelayUtils.prepareCustomMapsLeaderboard(); updateNextRestartTime(); async(() -> { while (enabled) { try { this.onTick(); } catch (Exception e) { RelayUtils.printError("on tick", e); } if (enabled) { try { Thread.sleep(50); } catch (InterruptedException exception) { error("Tried to sleep thread, error occurred: %s", exception.getMessage()); } } } }); for (GameType ignored : Miscellaneous.filter(GameType.getGameTypes(), GameType::isEnabled)) { playerCountLog.add(new HashMap<>()); } SmartPlayer.getOrCreate("lifeknight"); } public static void addInfoMessage(BaseComponent[] components) { infoMessages.add(components); save(); } public void readConfigurationFile() { try { File dataFolder = this.getDataFolder(); if (!dataFolder.exists()) { dataFolder.mkdir(); } File fileConfiguration = new File(dataFolder, CONFIG_FILE); ConfigurationProvider configurationProvider = ConfigurationProvider.getProvider(YamlConfiguration.class); if (!fileConfiguration.exists()) { createPlaceholderServerList(); this.saveConfigurationFile(); } else { Configuration configuration = configurationProvider.load(fileConfiguration); RelayUtils.testing = configuration.getBoolean("testing"); try { infoMessages = Miscellaneous.processList( configuration.getStringList("infoMessages"), s -> ComponentSerializer.parse(ChatColor.translateAlternateColorCodes('&', s)) ); } catch (Exception exception) { error("An error occurred while reading info messages: %s", exception.getMessage()); } infoMessageDelay = configuration.getInt("infoMessageDelay", infoMessageDelay); lastMonthlyReset = configuration.getLong("lastMonthlyReset", System.currentTimeMillis()); lastWeeklyReset = configuration.getLong("lastWeeklyReset", System.currentTimeMillis()); lastDailyReset = configuration.getLong("lastDailyReset", System.currentTimeMillis()); setLastResetTimes(); maxPlayers = configuration.getInt("maxPlayers", maxPlayers); maintenanceMode = configuration.getBoolean("maintenance", true); whitelistedPlayers = Miscellaneous.processList(configuration.getStringList("whitelist"), RelayUtils::getUUID); Server.clear(); for (MainGame value : MainGame.values()) { List hubNames = configuration.get(value.getComparableName() + "Hubs", new ArrayList<>()); List serverNames = configuration.get(value.getComparableName() + "Servers", new ArrayList<>()); for (String hubName : hubNames) { Server.create(hubName, value, ServerType.HUB); } for (String serverName : serverNames) { Server.create(serverName, value, ServerType.GAME); } } List mainHubNames = configuration.get("mainHubs", new ArrayList<>()); for (String mainHubName : mainHubNames) { Server.create(mainHubName, null, ServerType.HUB); } List limboNames = configuration.get("limbos", new ArrayList<>()); for (String limboName : limboNames) { Server.create(limboName, null, ServerType.LIMBO); } } for (File file : dataFolder.listFiles((dir, name) -> name.contains(".jar"))) { for (MainGame mainGame : MainGame.values()) { if (Text.toComparable(file.getName()).contains(mainGame.getComparableName())) { if (MAIN_GAME_TO_PLUGIN.containsKey(mainGame)) { File current = MAIN_GAME_TO_PLUGIN.get(mainGame); Version currentV = Utilities.parseVersion(current.getName()); Version newV = Utilities.parseVersion(file.getName()); if (newV.isGreater(currentV)) { info("Remapping %s with %s.", file.getName(), mainGame.getComparableName()); MAIN_GAME_TO_PLUGIN.put(mainGame, file); } } else { info("Mapping %s with %s.", file.getName(), mainGame.getComparableName()); MAIN_GAME_TO_PLUGIN.put(mainGame, file); } break; } } if (!MAIN_GAME_TO_PLUGIN.containsValue(file)) { info("Setting %s as hub plugin.", file.getName()); MAIN_GAME_TO_PLUGIN.put(null, file); } } } catch (Exception exception) { error("An error occurred in the reading of the configuration file: %s", exception.getMessage()); this.saveConfigurationFile(); } updatePlugins(); } private void setLastResetTimes() { lastDailyReset = SUNDAY_3_13_2022_4_AM; while (lastDailyReset + 24 * 60 * 60 * 1000L < System.currentTimeMillis()) { lastDailyReset += 24 * 60 * 60 * 1000L; } lastWeeklyReset = SUNDAY_3_13_2022_4_AM; while (lastWeeklyReset + 7 * 24 * 60 * 60 * 1000L < System.currentTimeMillis()) { lastWeeklyReset += 7 * 24 * 60 * 60 * 1000L; } lastMonthlyReset = DATE_10_1_2023_4AM; while (lastMonthlyReset + 30 * 24 * 60 * 60 * 1000L < System.currentTimeMillis()) { lastMonthlyReset += 30 * 24 * 60 * 60 * 1000L; } } public static void reload() { instance.readConfigurationFile(); } public static void save() { instance.saveConfigurationFile(); } public void saveConfigurationFile() { try { File dataFolder = this.getDataFolder(); if (!dataFolder.exists()) { dataFolder.mkdir(); } File fileConfiguration = new File(dataFolder, CONFIG_FILE); ConfigurationProvider configurationProvider = ConfigurationProvider.getProvider(YamlConfiguration.class); if (!fileConfiguration.exists()) { fileConfiguration.createNewFile(); } Configuration configuration = configurationProvider.load(fileConfiguration); configuration.set("port", RelayUtils.PROXY_MESSAGE_SERVER_PORT); configuration.set("testing", RelayUtils.testing); configuration.set("infoMessages", Miscellaneous.processList(infoMessages, ComponentSerializer::toString)); configuration.set("infoMessageDelay", infoMessageDelay); configuration.set("lastDailyReset", lastDailyReset); configuration.set("lastWeeklyReset", lastWeeklyReset); configuration.set("lastMonthlyReset", lastMonthlyReset); configuration.set("maxPlayers", maxPlayers); configuration.set("whitelist", Miscellaneous.processList(whitelistedPlayers, RelayUtils::getName)); for (MainGame value : MainGame.values()) { configuration.set(value.getComparableName() + "Hubs", Miscellaneous.processList(Server.getServersOfType(value, false), Server::getName)); configuration.set(value.getComparableName() + "Servers", Miscellaneous.processList(Server.getServersOfType(value, true), Server::getName)); } configuration.set("mainHubs", Miscellaneous.processList(Server.getServersOfType(null, false), Server::getName)); configuration.set("limbos", Miscellaneous.processList(Server.getLimboServers(), Server::getName)); configurationProvider.save(configuration, fileConfiguration); } catch (Exception exception) { exception.printStackTrace(); } } @Override public void onDisable() { enabled = false; this.getProxy().unregisterChannel(CHANNEL); this.logPlayerCounts(); MessageServer.end(); SmartDatabase.shutdown(); BotUtils.shutdown(); info("Disabled."); } @EventHandler public void onPluginMessageReceived(PluginMessageEvent event) { if (event.getTag().equalsIgnoreCase(CHANNEL)) { try { ByteArrayDataInput in = ByteStreams.newDataInput(event.getData()); Message message = Message.fromJson(RelayUtils.parseJsonArray(in.readUTF())); onMessageReceived(message); } catch (Exception exception) { error("An error occurred while trying to process message: %s", exception.getMessage()); } } } public static void onMessageReceived(Message message) { Recipient recipient = message.getRecipientRecipient(); if (!(Recipient.PROXY == recipient || Recipient.NONE == recipient)) { try { MessageServerHandler.sendMessageToServer(message); } catch (Exception exception) { error("Attempted to forward a message to a server, no server found (%s): %s", MessageServerHandler.CHANNELS.keySet(), message); } } else { try { ServerMessageProcessor.processMessage(message); } catch (Exception exception) { RelayUtils.printError("process message", exception, 8); } } } public void onTick() { log("on tick"); tickCount++; if (restartTime != Long.MIN_VALUE && System.currentTimeMillis() >= restartTime && !isRestarting) { restart(); return; } if (isOfSeconds(10)) { broadcastPlayerCounts(); broadcastHubs(); } if (!RelayUtils.testing && isOfSeconds(5)) { BotUtils.updatePlayerCount(isOfSeconds(10)); } if (isOfSeconds(1)) { broadcastPing(); disconnectInactive(); boolean update = false; if (lastDailyReset != 0 && System.currentTimeMillis() - lastDailyReset >= getTimeRequired(0)) { update = true; this.resetLeaderboard(0); } if (lastWeeklyReset != 0 && System.currentTimeMillis() - lastWeeklyReset >= getTimeRequired(1)) { update = true; this.resetLeaderboard(1); } if (lastMonthlyReset != 0 && System.currentTimeMillis() - lastMonthlyReset >= getTimeRequired(2)) { update = true; this.resetLeaderboard(2); } if (update) { RelayUtils.prepareLeaderboard(); RelayUtils.prepareCustomKitsLeaderboard(); RelayUtils.prepareCustomMapsLeaderboard(); } } if (isOfSeconds(180)) { sendStatsMessage(); } if (tickCount % (infoMessageDelay * 60 * 20) == 0) { synchronous(Main::broadcastInfoMessage); } if (Miscellaneous.isWithinRange(nextRestart, System.currentTimeMillis(), 30 * 60 * 1000L) && !restartMessageSent) { Main.info("Scheduling restart for normal restarts."); scheduleRestart(); } else if (Miscellaneous.getRemainingMemoryMegaBytes() <= 64 && !restartMessageSent) { warn("Scheduling restart. (Remaining memory: %d MB)", Miscellaneous.getRemainingMemoryMegaBytes()); scheduleRestart(10 * 60 * 1000L); } SmartPlayer.onTick(); runScheduledTasks(); } private void runScheduledTasks() { List delete = new ArrayList<>(); SCHEDULED_TASKS.removeIf(Objects::isNull); for (int i = 0; i < SCHEDULED_TASKS.size(); i++) { Task scheduledTask = SCHEDULED_TASKS.get(i); try { if (System.currentTimeMillis() - scheduledTask.initialize >= scheduledTask.waitTime) { delete.add(i); scheduledTask.runnable.run(); } } catch (Exception e) { RelayUtils.printError("run a schedule task", e, 5); BotUtils.onScheduledTaskError(e); } } for (Integer integer : Miscellaneous.reverse(delete)) { SCHEDULED_TASKS.remove((int) integer); } } private void logPlayerCounts() { List gameTypes = Miscellaneous.filter(GameType.getGameTypes(), GameType::isEnabled); long time = System.currentTimeMillis(); for (int i = 0; i < gameTypes.size(); i++) { playerCountLog.get(i).put(time, getPlayerCount(gameTypes.get(i))); } onlineLog.put(time, SmartPlayer.getOnlineSmartPlayers().size()); updatePlayerCountLog(); } public static void updatePlayerCountLog() { Map totals = new HashMap<>(); for (int j = 0; j < playerCountLog.get(0).size(); j++) { for (Map longIntegerMap : playerCountLog) { Map.Entry val = Miscellaneous.getList(longIntegerMap.entrySet()).get(j); totals.put(val.getKey(), totals.getOrDefault(val.getKey(), 0) + val.getValue()); } } StringBuilder csv = new StringBuilder(); csv.append("Time (epoch),Online,In-Game"); for (GameType gameType : Miscellaneous.filter(GameType.getGameTypes(), GameType::isEnabled)) { csv.append(",").append(gameType.getFullPrettyName()); } csv.append("\n"); for (Long aLong : totals.keySet()) { csv.append(aLong).append(","); csv.append(onlineLog.get(aLong)).append(","); csv.append(totals.get(aLong)); for (Map longIntegerMap : playerCountLog) { csv.append(","); csv.append(longIntegerMap.get(aLong)); } csv.append("\n"); } try { File folder = new File(instance.getDataFolder(), "/logs/playercounts"); if (!folder.exists()) { folder.mkdirs(); } File file1 = new File(folder, getDateTimeString(RelayUtils.INITIALIZATION_TIME).replace(":", "-").replace("/", "-").replace(" ", "-") + ".txt"); if (!file1.exists()) { file1.createNewFile(); } PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file1))); writer.write(csv.toString()); writer.close(); } catch (Exception exception) { error("Could not write to log file: %s", exception.getMessage()); } try { File folder = new File(instance.getDataFolder(), "/logs/reports"); if (!folder.exists()) { folder.mkdirs(); } File file1 = new File(folder, "Reports " + getDateTimeString(RelayUtils.INITIALIZATION_TIME).replace(":", "-").replace("/", "-").replace(" ", "-") + ".txt"); if (!file1.exists()) { file1.createNewFile(); } PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file1))); writer.write(getReport()); writer.close(); } catch (Exception exception) { error("Could not write to log file (2): %s", exception.getMessage()); } } private static String getReport() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("Uptime: ").append(Text.getShortTextualFormattedTime(System.currentTimeMillis() - RelayUtils.INITIALIZATION_TIME)).append("\n"); stringBuilder.append("Peak Players: ").append(peakPlayers).append("\n"); stringBuilder.append("Unique Players: ").append(uniquePlayers).append("\n"); stringBuilder.append("New Players: ").append(newPlayers).append("\n"); stringBuilder.append("Games Played: ").append("\n"); stringBuilder.append("Total: ").append(gamesPlayed).append("\n"); for (Map.Entry gameTypeIntegerEntry : gamesPlayedMap.entrySet()) { stringBuilder.append(" - ").append(gameTypeIntegerEntry.getKey().getFullPrettyName()).append(": ").append(gameTypeIntegerEntry.getValue()).append("\n"); } return stringBuilder.toString(); } public static String getDateTimeString(long milliseconds) { return getSimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(milliseconds) + " EST"; } private int getPlayerCount(GameType gameType) { int count = 0; for (GameServer gameServer : GameServer.GAME_SERVERS) { ServerDetails details = gameServer.getServerDetails(); if (details == null) { warn("Details were null: %s", gameServer.getRecipient()); } else { for (GameDetails gameDetail : details.gameDetails) { if (gameDetail != null && gameDetail.getGameType() == gameType) { count += gameDetail.getPlayerCount(); } } } } return count; } public static void scheduleRestart(long time) { ComponentBuilder componentBuilder = new ComponentBuilder(ComponentBuilder.RED).line().newLine().color(ComponentBuilder.YELLOW); componentBuilder.append("The network will be restarting in ").color(ComponentBuilder.RED_ORANGE).append(Text.formatTimeFromMilliseconds(time)).color(ComponentBuilder.YELLOW).append(" to improve performance.").newLine(); componentBuilder.color(ComponentBuilder.RED).line(); queueOpen = false; queueCloseMessage = RED + "The network will be restarting at " + Text.getTimeString(System.currentTimeMillis() + time); restartMessageSent = true; restartTime = System.currentTimeMillis() + time; } public static void restart() { info("---------------\nRESTARTING SERVER\n---------------"); isRestarting = true; for (SmartPlayer onlineSmartPlayer : SmartPlayer.getOnlineSmartPlayers()) { if (!onlineSmartPlayer.isStaff()) { onlineSmartPlayer.kick("The network is restarting. Try joining in 60 seconds."); } } schedule(RelayUtils::restart, 10); } public static void scheduleRestart() { scheduleRestart(30 * 60 * 1000L); } public static void sendStatsMessage(CommandSender commandSender) { ComponentBuilder componentBuilder = new ComponentBuilder(); componentBuilder.color(ComponentBuilder.GREEN); componentBuilder.append("Up Since: %s (%s)", Text.getDateTimeString(RelayUtils.INITIALIZATION_TIME), Text.getShortTextualFormattedTime(System.currentTimeMillis() - RelayUtils.INITIALIZATION_TIME)).newLine(); long restart = restartMessageSent ? restartTime : nextRestart; componentBuilder.append("Restart time: %s (in %s)", Text.getDateTimeString(restart), Text.getShortTextualFormattedTime(restart - System.currentTimeMillis())).newLine(); componentBuilder.append("SmartPlayer count: %d", SmartPlayer.SMART_PLAYERS.size()).newLine(); componentBuilder.append("Unique Players: %d", uniquePlayers).newLine(); componentBuilder.append("New Players: %d", newPlayers).newLine(); componentBuilder.append("Online: %d", SmartPlayer.getOnlineSmartPlayers().size()).newLine(); componentBuilder.append("In Game: %d", GameServer.getPlayersInGame()).newLine(); componentBuilder.append("Games Played: %d", gamesPlayed).newLine(); componentBuilder.append("Peak Players: %d", peakPlayers).newLine(); componentBuilder.append("MB of memory: %d", Miscellaneous.getRemainingMemoryMegaBytes()).newLine(); commandSender.sendMessage(componentBuilder.getResult()); } public static void sendStatsMessage() { sendStatsMessage(instance.getProxy().getConsole()); } public static void open() { open = true; } public static void async(Runnable runnable) { log("async"); ProxyServer.getInstance().getScheduler().runAsync(instance, runnable); } public static File getPlugin(Server server) { if (server == null) return null; if (server.isHub() || server.isLimbo()) { return MAIN_GAME_TO_PLUGIN.get(null); } return MAIN_GAME_TO_PLUGIN.get(server.getMainGame()); } public static void restartGameServers() { for (Server gameServer : Server.getGameServers()) { if (gameServer.isActive() && gameServer.isEmpty()) { gameServer.sendMessage(Command.RESTART); } } info("Restarted empty game servers."); } public static void restartHubs() { for (Server hubServer : Server.getHubServers()) { if (hubServer.isActive() && hubServer.isEmpty()) { hubServer.sendMessage(Command.RESTART); } } info("Restarted empty hubs."); } public static void startServers() { for (Server server : Miscellaneous.with(Server.getGameServers(), Server.getHubServers())) { if (!server.isActive()) { server.start(); } } } public static boolean isSecond() { return isOfSeconds(1); } public static boolean isOfSeconds(int seconds) { return tickCount % (20 * seconds) == 0; } public static void updateNextRestartTime() { int iteration = 0; while (nextRestart < System.currentTimeMillis()) { switch (iteration) { case 0: case 2: nextRestart += 2 * 24 * 60 * 60 * 1000L; break; case 1: nextRestart += 3 * 24 * 60 * 60 * 1000L; break; } if (iteration == 2) { iteration = 0; } else { iteration++; } } } public void resetLeaderboard(int i) { String leaderboardName = ""; if (i == 1) { leaderboardName = "weekly"; lastWeeklyReset = System.currentTimeMillis(); } else if (i == 0) { leaderboardName = "daily"; lastDailyReset = System.currentTimeMillis(); } else if (i == 2) { leaderboardName = "monthly"; lastMonthlyReset = System.currentTimeMillis(); } for (String leaderboard : Miscellaneous.getList("leaderboard", SmartDatabase.CUSTOM_MAP_TABLE_NAME, SmartDatabase.CUSTOM_KIT_TABLE_NAME)) { SmartDatabase.executeUpdate("TRUNCATE `%s`", leaderboard + "_" + leaderboardName); } schedule(() -> Utilities.broadcast("%s%s leaderboards %s%shave been reset!", GREEN, i == 1 ? "Weekly" : (i == 0 ? "Daily" : "Monthly"), AQUA, BOLD), 60); for (Server gameServer : Server.getGameServers()) { if (gameServer.isActive()) { gameServer.sendMessage(Command.OTHER, "leaderboard_reset", "leaderboard_" + leaderboardName); } } this.saveConfigurationFile(); } public static long getTimeRequired(int i) { if (i == 0) { return 24 * 60 * 60 * 1000L; } if (i == 2) { return 30 * 24 * 60 * 60 * 1000L; } return 7 * 24 * 60 * 60 * 1000L; } public static void info(String format, Object... data) { instance.getLogger().info(RelayUtils.format(format, data)); } public static void warn(String format, Object... data) { instance.getLogger().warning(RelayUtils.format(format, data)); } public static void error(String format, Object... data) { instance.getLogger().severe(RelayUtils.format(format, data)); } public static void log(String format, Object... data) { RelayUtils.log(format, data); } public static void schedule(Runnable runnable, long seconds) { schedule(runnable, seconds, TimeUnit.SECONDS); } public static void scheduleMillis(Runnable runnable, long milliseconds) { schedule(runnable, milliseconds, TimeUnit.MILLISECONDS); } public static void schedule(Runnable runnable, long time, TimeUnit timeUnit) { SCHEDULED_TASKS.add(new Task(runnable, System.currentTimeMillis(), timeUnit.toMillis(time))); /* ProxyServer.getInstance().getScheduler().schedule(instance, () -> { }, time, timeUnit); */ } public static void synchronous(Runnable runnable) { schedule(runnable, 0); } public static void broadcastPlayerCounts() { Map gameTypeToCount = new HashMap<>(); for (GameType gameType : GameType.getGameTypes(false, false)) { gameTypeToCount.put(gameType, 0); } for (GameServer gameServer : GameServer.GAME_SERVERS) { ServerDetails details = gameServer.getServerDetails(); if (details == null) { warn("Details were null: %s", gameServer.getRecipient()); return; } for (GameDetails gameDetail : details.gameDetails) { if (gameDetail != null && gameDetail.getGameType() != null) { int currentCount = gameTypeToCount.get(gameDetail.getGameType()); gameTypeToCount.put(gameDetail.getGameType(), currentCount + gameDetail.getPlayerCount()); } } } for (GameType gameType : GameType.getGameTypes(false, false)) { int count = gameTypeToCount.get(gameType); gameTypeToCount.put(gameType, count + Queue.getQueueSize(gameType)); } StringBuilder stringBuilder = new StringBuilder(); for (GameType gameType : gameTypeToCount.keySet()) { stringBuilder.append(gameType.toString()).append(":").append(gameTypeToCount.get(gameType)).append(","); } sendMessageToHubs(new Message(Command.PLAYER_COUNT, stringBuilder.toString())); } public static void broadcastHubs() { JsonArray jsonArray = new JsonArray(); for (MainGame mainGame : Miscellaneous.with(Miscellaneous.getList(MainGame.values()), (MainGame) null)) { JsonObject hubGroup = new JsonObject(); hubGroup.addProperty("type", mainGame == null ? "main" : mainGame.getComparableName()); JsonArray hubsOfType = new JsonArray(); for (Server server : Server.getAvailableServersOfType(mainGame, false)) { JsonObject hub = new JsonObject(); hub.addProperty("name", server.getName()); hub.addProperty("count", server.getPlayerCount()); hubsOfType.add(hub); } hubGroup.add("hubs", hubsOfType); jsonArray.add(hubGroup); } sendMessageToHubs(new Message(Command.HUBS, jsonArray)); } public static void broadcastPublicQueues() { JsonObject games = new JsonObject(); JsonArray currentGames = new JsonArray(); JsonArray queuedGames = new JsonArray(); for (QueueData customKitsDatum : GameServer.getGameData()) { currentGames.add(customKitsDatum.toString()); } for (QueueData customKitsDatum : Queue.getQueuedGames()) { queuedGames.add(customKitsDatum.toString()); } games.add("current", currentGames); games.add("queued", queuedGames); sendMessageToHubs(new Message(Command.OTHER, "duelsgames", games)); } public static void broadcastPing() { sendMessageToGameServers(new Message(Command.OTHER, "ping")); sendMessageToHubs(new Message(Command.OTHER, "ping")); } public static void disconnectInactive() { for (MessageServerHandler messageServerHandler : getMessageServerHandlers()) { if (Miscellaneous.enoughTimeHasPassed(messageServerHandler.getLastPing(), 3, TimeUnit.SECONDS)) { messageServerHandler.closeConnection(); } } } public static void broadcastInfoMessage() { BaseComponent[] next = Miscellaneous.getNextIndex(infoMessages, latestInfoMessage); if (next != null) { for (SmartPlayer onlineSmartPlayer : SmartPlayer.getOnlineSmartPlayers()) { if (onlineSmartPlayer.isOnHub()) { onlineSmartPlayer.sendMessage(next); } } } latestInfoMessage = next; } public static void setMaintenanceMode(boolean mode) { maintenanceMode = mode; if (maintenanceMode) { ComponentBuilder componentBuilder = new ComponentBuilder(); componentBuilder.color(ComponentBuilder.YELLOW); componentBuilder.line().newLine(); componentBuilder.color(ComponentBuilder.RED_ORANGE).append("Server is entering").color(ComponentBuilder.ORANGE_YELLOW).bold().append(" MAINTENANCE MODE!").newLine(); componentBuilder.color(ComponentBuilder.YELLOW).line(); Utilities.broadcast(componentBuilder); schedule(() -> { ComponentBuilder kickMessage = new ComponentBuilder(); kickMessage.color(ComponentBuilder.BLUE).append("Relay").newLine().newLine(); kickMessage.color(ComponentBuilder.YELLOW).append("Relay has entered maintenance mode.").newLine(); kickMessage.color(DARK_PURPLE).append("Discord: ").link(Main.DISCORD_URL, true); for (SmartPlayer onlineSmartPlayer : SmartPlayer.getOnlineSmartPlayers()) { if (!(onlineSmartPlayer.isStaff() || whitelistedPlayers.contains(onlineSmartPlayer.getUUID()))) { onlineSmartPlayer.kick(kickMessage); } } }, 3); } } public static List getMessageServerHandlers() { List list = Miscellaneous.getList(MessageServerHandler.CHANNELS.values()); list.removeIf(Objects::isNull); return list; } public static void updatePlugins() { List messageServerHandlers = getMessageServerHandlers(); for (int i = 0; i < messageServerHandlers.size(); i++) { int finalI = i; scheduleMillis(() -> { MessageServerHandler messageServerHandler = messageServerHandlers.get(finalI); messageServerHandler.getPluginVersion(); }, 500L * i); } } public static void sendMessageToHubs(Message message) { for (MessageServerHandler messageServerHandler : getMessageServerHandlers()) { if (messageServerHandler.hasServer() && messageServerHandler.getServer().isHub()) { messageServerHandler.sendMessage(message); } } } public static void sendMessageToGameServers(Message message) { for (MessageServerHandler messageServerHandler : getMessageServerHandlers()) { if (messageServerHandler.hasServer() && !messageServerHandler.getServer().isHub()) { messageServerHandler.sendMessage(message); } } } public static void createPlaceholderServerList() { createPlaceholderServers(null, "hub", 3, ServerType.HUB); createPlaceholderServers(null, "z", 3, ServerType.LIMBO); for (MainGame value : MainGame.values()) { String prefix = getPrefix(value.getName()); createPlaceholderServers(value, prefix + "lobby", 3, ServerType.HUB); createPlaceholderServers(value, prefix, 10, ServerType.GAME); } } public static String getPrefix(String name) { StringBuilder stringBuilder = new StringBuilder(); for (String s : name.split(" ")) { stringBuilder.append(Character.toLowerCase(s.charAt(0))); } return stringBuilder.toString(); } public static void createPlaceholderServers(MainGame mainGame, String prefix, int count, ServerType serverType) { for (int i = 0; i < count; i++) { Server.create(prefix + get1000Number(i + 1), mainGame, serverType); } } public static String get1000Number(int number) { if (number < 10) { return "00" + number; } if (number < 100) { return "0" + number; } return String.valueOf(number); } public static void addToConfig(ServerInfo serverInfo) { if (bungeeConfig == null) { return; } bungeeConfig.set("servers." + serverInfo.getName() + ".motd", serverInfo.getMotd().replace(ChatColor.COLOR_CHAR, '&')); bungeeConfig.set("servers." + serverInfo.getName() + ".address", Utilities.socketAddressToString(serverInfo.getSocketAddress())); bungeeConfig.set("servers." + serverInfo.getName() + ".restricted", serverInfo.isRestricted()); saveBungeeConfig(); } public static void removeFromConfig(ServerInfo serverInfo) { removeFromConfig(serverInfo.getName()); } public static void removeFromConfig(String name) { if (bungeeConfig == null) { return; } bungeeConfig.set("servers." + name, null); saveBungeeConfig(); } private static void saveBungeeConfig() { if (bungeeConfig == null) { return; } try { YamlConfiguration.getProvider(YamlConfiguration.class).save(bungeeConfig, file); } catch (IOException e) { e.printStackTrace(); } } private static void setupConfig() { FileInputStream fis = null; InputStreamReader isr = null; try { file = new File(ProxyServer.getInstance().getPluginsFolder().getParentFile(), "config.yml"); fis = new FileInputStream(file); isr = new InputStreamReader(fis); bungeeConfig = YamlConfiguration.getProvider(YamlConfiguration.class).load(isr); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fis != null) { fis.close(); } if (isr != null) { isr.close(); } } catch (IOException ignored) { } } } public static boolean networkIsFull() { return networkIsFull(false); } public static boolean networkIsFull(boolean excludeOne) { if (excludeOne) { return Main.getOnNetworkPlayerCount() > Main.maxPlayers || Server.allHubsAreFull(); } return Main.getOnNetworkPlayerCount() >= Main.maxPlayers || Server.allHubsAreFull(); } public static void doAndLogDuration(String name, Runnable action) { long startTime = System.currentTimeMillis(); action.run(); if (RelayUtils.log) { info("Time taken for %s: %s", name, Text.addCommasToNumber(System.currentTimeMillis() - startTime)); } } private record Task(Runnable runnable, long initialize, long waitTime) { } }