Hub / src / main / java / com / lifeknight / relaymchub / Main.java
Main.java
Raw
package com.lifeknight.relaymchub;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.lifeknight.relaymchub.commands.*;
import com.lifeknight.relaymchub.commands.map.SetSpawnCommand;
import com.lifeknight.relaymchub.commands.map.ShareMapCommand;
import com.lifeknight.relaymchub.commands.menu.*;
import com.lifeknight.relaymchub.commands.movement.FlyCommand;
import com.lifeknight.relaymchub.commands.movement.LeaveCommand;
import com.lifeknight.relaymchub.commands.movement.SpawnCommand;
import com.lifeknight.relaymchub.commands.movement.TeleportCommand;
import com.lifeknight.relaymchub.commands.queue.DuelCommand;
import com.lifeknight.relaymchub.commands.queue.RematchCommand;
import com.lifeknight.relaymchub.miscellaneous.GameKit;
import com.lifeknight.relaymchub.miscellaneous.MessageProcessor;
import com.lifeknight.relaymchub.miscellaneous.SmartGUIUtils;
import com.lifeknight.relaymchub.player.HubPlayer;
import com.lifeknight.relaymchub.statistics.StatisticsLeaderboard;
import com.lifeknight.relaymcutils.RelayMCUtils;
import com.lifeknight.relaymcutils.event.NotEnoughMemoryEvent;
import com.lifeknight.relaymcutils.network.MessageClient;
import com.lifeknight.relaymcutils.player.CustomDuelsKit;
import com.lifeknight.relaymcutils.player.CustomMap;
import com.lifeknight.relaymcutils.player.DuelMap;
import com.lifeknight.relaymcutils.player.SmartPlayer;
import com.lifeknight.relaymcutils.player.commands.ParkourCommand;
import com.lifeknight.relaymcutils.player.commands.ProfileCommand;
import com.lifeknight.relaymcutils.player.commands.StatisticsCommand;
import com.lifeknight.relaymcutils.utilities.Configuration;
import com.lifeknight.relaymcutils.utilities.Parkour;
import com.lifeknight.relaymcutils.utilities.SmartNPC;
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.GameAction;
import com.lifeknight.relayutils.game.GameType;
import com.lifeknight.relayutils.game.MainGame;
import com.lifeknight.relayutils.game.SpecificGameAction;
import com.lifeknight.relayutils.player.data.SmartDatabase;
import com.lifeknight.relayutils.utilities.ComponentBuilder;
import org.bukkit.*;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.potion.PotionEffectType;

import java.io.File;
import java.io.IOException;
import java.sql.ResultSet;
import java.util.*;
import java.util.concurrent.TimeUnit;

public final class Main extends JavaPlugin implements Listener {
    public static Configuration configuration;
    public static String hubType = "main";
    public static MainGame mainGame;
    public static String PLUGIN_ID = "relaymchub";
    public static Main instance;
    public static boolean pvpArenaEnabled;
    private static boolean leaderboardsAreaEnabled;
    private static Location leaderboardsArea;
    public static int ticks = 0;
    public static boolean isAboutToRestart = false;

    public static Location arenaPosA;
    public static Location arenaPosB;

    public static float yaw = 0F;
    public static float leaderboardYaw = 0F;

    public static boolean isLimbo() {
        return Text.comparablyEquals(hubType, "limbo");
    }

    public static final List<CustomMap> CUSTOM_MAP_TEMPLATES = new ArrayList<>();

    public static CustomMap getWallTemplate() {
        return Miscellaneous.match(CUSTOM_MAP_TEMPLATES, customMap -> customMap.getId().equalsIgnoreCase("walledtemplate"));
    }

    public static boolean hasWallTemplate() {
        return getWallTemplate() != null;
    }

    public static boolean isMainLobby() {
        return mainGame == null;
    }

    private static final List<CustomMap> RECENT_MAPS = new ArrayList<>();
    private static final List<List<CustomMap>> POPULAR_MAPS =
            Miscellaneous.getList(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());

    public static List<CustomMap> getCustomMaps(boolean recent, int timeFrame) {
        return recent ? RECENT_MAPS : POPULAR_MAPS.get(timeFrame);
    }

    private static final List<CustomDuelsKit> RECENT_KITS = new ArrayList<>();
    private static final List<List<CustomDuelsKit>> POPULAR_KITS =
            Miscellaneous.getList(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());

    public static List<CustomDuelsKit> getCustomKits(boolean recent, int timeFrame) {
        return recent ? RECENT_KITS : POPULAR_KITS.get(timeFrame);
    }

    public static List<DuelMap> getMaps(GameType gameType) {
        if (gameType == GameType.DUELS_CRYSTAL) {
            return Miscellaneous.getList(DuelMap.INSANE_TERRAIN, DuelMap.CRAZY_TERRAIN, DuelMap.EPIC_TERRAIN, DuelMap.NATURAL_PLAINS, DuelMap.NATURAL_DESERT, DuelMap.NATURAL_BEDROCK, DuelMap.ARENA);
        }

        if (gameType == GameType.DUELS_AXE) {
            return Miscellaneous.getList(DuelMap.CASUAL_COURTYARD, DuelMap.ARENA, DuelMap.PERILOUS_PRISON, DuelMap.BLUE_KINGDOM, DuelMap.RED_PANDA, DuelMap.MUSHROOM_FOREST, DuelMap.KUNG_FOO_FORTRESS, DuelMap.COMBAT_CITY);
        }

        if (gameType == GameType.DUELS_SWORD) {
            return Miscellaneous.getList(DuelMap.COMBAT_CITY, DuelMap.PERILOUS_PRISON, DuelMap.CASUAL_COURTYARD, DuelMap.ARENA, DuelMap.INSANE_TERRAIN, DuelMap.CRAZY_TERRAIN, DuelMap.KUNG_FOO_FORTRESS);
        }

        if (gameType == GameType.DUELS_UHC) {
            return Miscellaneous.getList(DuelMap.NATURAL_PLAINS, DuelMap.NATURAL_DESERT, DuelMap.CRAZY_TERRAIN, DuelMap.INSANE_TERRAIN, DuelMap.EPIC_TERRAIN, DuelMap.MUSHROOM_FOREST, DuelMap.RED_PANDA, DuelMap.BLUE_KINGDOM, DuelMap.ARENA, DuelMap.CASUAL_COURTYARD);
        }

        if (gameType == GameType.DUELS_DIAMOND_POT || gameType == GameType.DUELS_NETHERITE_POT) {
            return Miscellaneous.getList(DuelMap.PERILOUS_PRISON, DuelMap.CASUAL_COURTYARD, DuelMap.COMBAT_CITY, DuelMap.ARENA, DuelMap.KUNG_FOO_FORTRESS, DuelMap.INSANE_TERRAIN, DuelMap.CRAZY_TERRAIN);
        }

        return new ArrayList<>();
    }

    public static void schedule(Runnable runnable, double delay) {
        RelayMCUtils.schedule(runnable, delay);
    }

    @Override
    public void onEnable() {
        World overworld = getWorld();

        instance = this;

        overworld.setAutoSave(false);
        this.readConfig();

        if (hubType == null) {
            hubType = "";
        }

        mainGame = MainGame.getMainGame(hubType);

        RelayMCUtils.set(this, Text.toComparable(hubType));
        PluginManager pluginManager = this.getServer().getPluginManager();

        pluginManager.registerEvents(new MessageProcessor(), this);
        pluginManager.registerEvents(new ExtraListeners(), this);
        pluginManager.registerEvents(this, this);
        RelayMCUtils.registerCommand("leave", new LeaveCommand());
        RelayMCUtils.registerCommand(PLUGIN_ID, new RelayHubCommand());
        if (!isLimbo()) {
            RelayMCUtils.registerCommand("rmch", new RelayHubCommand());
            RelayMCUtils.registerCommand("fly", new FlyCommand());
            RelayMCUtils.registerCommand("queue", new QueueCommand());
            RelayMCUtils.registerCommand("settings", new SettingsCommand());
            RelayMCUtils.registerCommand("vanish", new VanishCommand());
            RelayMCUtils.registerCommand("kit", new KitCommand());
            RelayMCUtils.registerCommand("spawn", new SpawnCommand());
            RelayMCUtils.registerCommand("stuck", new SpawnCommand());
            RelayMCUtils.registerCommand("statistics", new StatisticsCommand());
            RelayMCUtils.registerCommand("profile", new ProfileCommand());
            RelayMCUtils.registerCommand("leaderboards", new LeaderboardsCommand());
            RelayMCUtils.registerCommand("parkour", new ParkourCommand());
            RelayMCUtils.registerCommand("duel", new DuelCommand());
            RelayMCUtils.registerCommand("nick", new NicknameCommand());
            RelayMCUtils.registerCommand("setspawn", new SetSpawnCommand());
            RelayMCUtils.registerCommand("hide", new HideCommand());
            RelayMCUtils.registerCommand("afk", new AFKCommand());
            RelayMCUtils.registerCommand("rematch", new RematchCommand());
            RelayMCUtils.registerCommand("custommaps", new CustomMapsCommand());
            RelayMCUtils.registerCommand("customkits", new CustomKitsCommand());
            RelayMCUtils.registerCommand("cosmetics", new CosmeticsCommand());
            RelayMCUtils.registerCommand("sharekit", new ShareKitCommand());
            RelayMCUtils.registerCommand("sharemap", new ShareMapCommand());
            RelayMCUtils.registerCommand("setmap", new SetMapCommand());
            RelayMCUtils.registerCommand("teleport", new TeleportCommand());
            RelayMCUtils.registerCommand("custommap", new CustomMapCommand());
        }


        this.getServer().getScheduler().scheduleSyncRepeatingTask(this, Main::repeat, 0, 1);
        RelayMCUtils.setMinMemory(96);

        overworld.setPVP(true);
        overworld.setGameRule(GameRule.FALL_DAMAGE, false);
        overworld.setGameRule(GameRule.DO_TILE_DROPS, false);
        overworld.setGameRule(GameRule.DO_ENTITY_DROPS, false);

        if (isDuelsLobby()) {
            scheduleSyncDelayedTask(Main::getMapPresets, 2);
        }

        scheduleSyncDelayedTask(() -> {
            for (SmartNPC smartNPC : SmartNPC.getSmartNPCs()) {
                Collection<ArmorStand> armorStands = getWorld().getNearbyEntitiesByType(ArmorStand.class, smartNPC.getLocation().toLocation(getWorld()), 1);
                for (ArmorStand armorStand : armorStands) {
                    if (!armorStand.getUniqueId().equals(smartNPC.getLowerArmorStand().getUniqueId()) && !armorStand.getUniqueId().equals(smartNPC.getUpperArmorStand().getUniqueId())) {
                        armorStand.remove();
                    }
                }
            }

            for (Parkour parkour : Parkour.PARKOUR_LIST) {
                for (Location checkpoint : parkour.getCheckpoints()) {
                    Collection<ArmorStand> armorStands = getWorld().getNearbyEntitiesByType(ArmorStand.class, checkpoint, 1);
                    for (ArmorStand armorStand : armorStands) {
                        if (!parkour.getArmorStands().contains(armorStand) && StatisticsLeaderboard.getFromStand(armorStand) == null) {
                            armorStand.remove();
                        }
                    }
                }
            }

            for (StatisticsLeaderboard leaderboard : StatisticsLeaderboard.LEADERBOARDS) {
                Collection<ArmorStand> armorStands = getWorld().getNearbyEntitiesByType(ArmorStand.class, leaderboard.getLocation(), 1);
                for (ArmorStand armorStand : armorStands) {
                    if (StatisticsLeaderboard.getFromStand(armorStand) == null &&
                            Miscellaneous.match(Parkour.PARKOUR_LIST, parkour -> Miscellaneous.match(parkour.getArmorStands(), armorStand1 -> armorStand1 == armorStand) != null) == null) {
                        armorStand.remove();
                    }
                }
            }
        }, 10);

        scheduleSyncDelayedTask(() -> {
            for (ArmorStand entitiesByClass : overworld.getEntitiesByClass(ArmorStand.class)) {
                entitiesByClass.remove();
            }
        }, 5);
        scheduleSyncDelayedTask(() -> {

            configuration = new SpecialConfiguration(this.getDataFolder(), PLUGIN_ID);
            RelayMCUtils.setAvailable(true);
        }, 15);
        info("Enabled.");
        RelayMCUtils.setAvailable(false);
        SmartGUIUtils.initialize();
    }

    private static void getMapPresets() {
        List<String> ids = Miscellaneous.getList("walledtemplate", "laketemplate", "cavetemplate", "islandtemplate", "bedrocktemplate");
        if (RelayUtils.testing) {

            ids.addAll(Miscellaneous.processList(Miscellaneous.getList(DuelMap.CASUAL_COURTYARD, DuelMap.BLUE_KINGDOM), DuelMap::getCodeName));
        }
        for (String id : ids) {
            async(() -> {
                CustomMap customMap = CustomMap.getCustomMap(id);
                if (customMap != null) {
                    CUSTOM_MAP_TEMPLATES.add(customMap);
                }
            });
        }
    }

    public void saveConfig() {
        File dataFolder = this.getDataFolder();
        info("Saving config.");
        RelayUtils.tryAndPrintException(() -> {
            if (!dataFolder.exists()) {
                dataFolder.mkdirs();
            }

            File fileConfiguration = new File(dataFolder, "data.yml");

            if (!fileConfiguration.exists()) {
                try {
                    if (!fileConfiguration.createNewFile()) {
                        try {
                            throw new Exception("Could not create configuration file!");
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

                YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(fileConfiguration);

                yamlConfiguration.set("testing", RelayUtils.testing);
                yamlConfiguration.set("hubType", hubType);
                yamlConfiguration.set("spawnYaw", yaw);
                yamlConfiguration.set("pvpArena.enabled", false);
                yamlConfiguration.set("pvpArenaPos1", Miscellaneous.getList(0, 0, 0));
                yamlConfiguration.set("pvpArenaPos2", Miscellaneous.getList(0, 0, 0));
                yamlConfiguration.set("leaderboardsArea.enabled", false);
                yamlConfiguration.set("leaderboardsArea.coords", Miscellaneous.getList(0, 0, 0));
                yamlConfiguration.set("leaderboardsArea.yaw", leaderboardYaw);
                try {
                    yamlConfiguration.save(fileConfiguration);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "save hub config");
    }

    public void readConfig() {
        File dataFolder = this.getDataFolder();
        info("Reading config.");
        RelayUtils.tryAndPrintException(() -> {
            World overworld = Bukkit.getWorld("overworld");

            File fileConfiguration = new File(dataFolder, "data.yml");
            if (!fileConfiguration.exists()) {
                this.saveConfig();
            }

            YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(fileConfiguration);
            hubType = yamlConfiguration.getString("hubType");
            yaw = (float) yamlConfiguration.getDouble("spawnYaw");
            pvpArenaEnabled = yamlConfiguration.getBoolean("pvpArena.enabled");
            List<Integer> pvpArenaPos1 = yamlConfiguration.getIntegerList("pvpArenaPos1");
            List<Integer> pvpArenaPos2 = yamlConfiguration.getIntegerList("pvpArenaPos2");

            int x1 = pvpArenaPos1.get(0);
            int y1 = pvpArenaPos1.get(1);
            int z1 = pvpArenaPos1.get(2);

            int x2 = pvpArenaPos2.get(0);
            int y2 = pvpArenaPos2.get(1);
            int z2 = pvpArenaPos2.get(2);

            arenaPosA = new Location(overworld, x1, y1, z1);
            arenaPosB = new Location(overworld, x2, y2, z2);

            leaderboardsAreaEnabled = yamlConfiguration.getBoolean("leaderboardsArea.enabled");
            List<Integer> leaderboardsAreaCoords = yamlConfiguration.getIntegerList("leaderboardsArea.coords");
            leaderboardYaw = (float) yamlConfiguration.getDouble("leaderboardsArea.yaw");
            leaderboardsArea = new Location(overworld, leaderboardsAreaCoords.get(0), leaderboardsAreaCoords.get(1), leaderboardsAreaCoords.get(2));
            leaderboardsArea.setYaw(leaderboardYaw);
        }, "read hub config");

        this.saveConfig();
    }

    public static void broadcastMessage(String format, Object... data) {
        Bukkit.broadcastMessage(RelayUtils.format(format, data));
    }

    public static void async(Runnable runnable) {
        RelayMCUtils.async(runnable, false);
    }

    private static boolean scheduledRestartTriggered = false;

    @EventHandler
    public void onNotEnoughMemory(NotEnoughMemoryEvent event) {
        if (!scheduledRestartTriggered) {
            for (HubPlayer onlineHubPlayer : HubPlayer.getOnlineHubPlayers()) {
                onlineHubPlayer.playSound(Sound.BLOCK_NOTE_BLOCK_SNARE);
                onlineHubPlayer.sendMessage(new ComponentBuilder(ComponentBuilder.RED_ORANGE).line().newLine().color(ComponentBuilder.RED).append("This lobby will be restarting in 60 seconds.").newLine().color(ComponentBuilder.RED_ORANGE).line());
            }
            isAboutToRestart = true;
            scheduleSyncDelayedTask(() -> {
                for (HubPlayer onlineHubPlayer : HubPlayer.getOnlineHubPlayers()) {
                    onlineHubPlayer.sendErrorMessage("Server restarting.");
                    scheduleSyncDelayedTask(() -> {
                        onlineHubPlayer.sendToMainHub();
                        onlineHubPlayer.getSmartPlayer().scheduleKickIfOnline(1, "This lobby has restarted. You may rejoin the network.");
                    }, 8.5);
                    for (int i = 0; i < 10; i++) {
                        scheduleSyncDelayedTask(() -> {
                            if (onlineHubPlayer.isOnline()) {
                                onlineHubPlayer.sendToHubType(getMainGame());
                            }
                        }, i);
                    }
                }

                info("Scheduled restart in 10 seconds.");
                scheduleSyncDelayedTask(RelayUtils::restart, 10);
            }, 55);
        }
        scheduledRestartTriggered = true;
    }

    public static MainGame getMainGame() {
        return mainGame;
    }

    public static void addPlayerToStats(HubPlayer hubPlayer) {
        SmartDatabase.addToStatisticsTableBasic(getMainGame(), hubPlayer.getUUID());
    }

    public static List<ResultSet> getLeaderboards(String column) {
        List<ResultSet> resultSets = new ArrayList<>();
        try {
            if (column.contains("parkour")) {
                resultSets.add(SmartDatabase.executeQuery(getSQLForLeaderboard(column.substring(8), "parkour_leaderboard")).get(5, TimeUnit.SECONDS));
            } else if (column.contains("arena")) {
                String replacement = column.substring(6);
                if (replacement.contains("max")) {
                    replacement = "max_killstreak";
                }
                info("arena column: %s", replacement);
                resultSets.add(SmartDatabase.executeQuery(getSQLForLeaderboard(replacement, "arena_stats")).get(5, TimeUnit.SECONDS));
            } else if (Miscellaneous.getList("credits", "experience", "time_played").contains(column)) {
                if (column.equalsIgnoreCase("time_played")) {
                    column = "timePlayed";
                }
                resultSets.add(SmartDatabase.executeQuery(getSQLForLeaderboard(column, "profile")).get(5, TimeUnit.SECONDS));
            } else {
                resultSets.add(SmartDatabase.executeQuery(getSQLForLeaderboard(column, "leaderboard")).get(5, TimeUnit.SECONDS));
                resultSets.add(SmartDatabase.executeQuery(getSQLForLeaderboard(column, "leaderboard_weekly")).get(5, TimeUnit.SECONDS));
                resultSets.add(SmartDatabase.executeQuery(getSQLForLeaderboard(column, "leaderboard_daily")).get(5, TimeUnit.SECONDS));
            }
        } catch (Exception exception) {
            error("An error occurred while attempting to get leaderboards for statistic (%s): %s", column, exception.getMessage());
        }

        return resultSets;
    }

    public static String getSQLForLeaderboard(String column, String leaderboardName) {
        if (leaderboardName.contains("parkour")) {
            return String.format("SELECT `id`, `time` FROM `parkour_leaderboard` WHERE `parkour_id`='%s' ORDER BY `parkour_leaderboard`.`time` ASC LIMIT 100", column);
        }

        return String.format("SELECT `id`, `%s` FROM `%s` ORDER BY `%s`.`%s` DESC LIMIT 100",
                column,
                leaderboardName,
                leaderboardName,
                column);
    }

    public static String shortenNumber(Number number) {
        if (number.intValue() == number.doubleValue()) {
            return String.valueOf(Math.max(0, number.intValue()));
        }

        return Text.shortenDouble(number.doubleValue(), 2);
    }

    public static String getScoreboardName() {
        if (isGameLobby()) {
            return getMainGame().getName().toUpperCase();
        }

        return "RELAY";
    }

    public static boolean hasKits() {
        return GameKit.gameHasKits(getMainGame());
    }

    public static World getWorld() {
        return Bukkit.getWorld("overworld");
    }

    public static Location getSpawnLocation() {
        return getWorld().getSpawnLocation();
    }

    public static List<SpecificGameAction> getSpecificGameActions() {
        return SpecificGameAction.getSpecificGameActions(getMainGame());
    }

    public static GameAction getGameAction(String value) {
        for (GameAction gameAction : getGameActions()) {
            if (Text.toComparable(value).equals(Text.toComparable(gameAction.getCodeName()))) {
                return gameAction;
            }
        }

        return null;
    }

    public static List<GameAction> getGameActions() {
        return GameAction.getGameActions(getMainGame());
    }

    public static boolean hasLeaderboardsArea() {
        return leaderboardsAreaEnabled;
    }

    public static Location getLeaderboardsArea() {
        return leaderboardsArea;
    }

    @Override
    public void onDisable() {
        if (configuration == null) {
            configuration = new SpecialConfiguration(this.getDataFolder(), PLUGIN_ID);
        } else {
            configuration.updateConfigurationFromVariables();
        }

        for (ArmorStand entitiesByClass : getWorld().getEntitiesByClass(ArmorStand.class)) {
            entitiesByClass.remove();
        }

        try {
            File file = new File(getWorld().getWorldFolder().getPath() + "/entities");
            if (file.exists()) {
                for (File listFile : file.listFiles()) {
                    listFile.deleteOnExit();
                }
            }
        } catch (Exception e) {
            RelayUtils.printError("Couldn't delete entities folder", e);
        }

        SmartNPC.clear();
        SmartPlayer.shutdown();
        SmartDatabase.shutdown();

        for (HubPlayer onlineHubPlayer : HubPlayer.getOnlineHubPlayers()) {
            onlineHubPlayer.disableAllCosmetics();
        }

        MessageClient.end();
        info("Ended message client.");

        info("Disabled.");
    }

    public static void info(String format, Object... data) {
        RelayUtils.info(format, data);
    }

    public static void warn(String format, Object... data) {
        RelayUtils.warn(format, data);
    }

    public static void error(String format, Object... data) {
        RelayUtils.error(format, data);
    }

    public static void repeat() {
        HubPlayer.onTick();

        if (RelayMCUtils.getTicks() % (20 * 30) == 0) {
            StatisticsLeaderboard.updateLeaderboards();
        }
        if (RelayMCUtils.getTicks() % (20 * 60) == 0) {
            async(Main::updateCustomMapsList);
            async(Main::updateCustomKitsList);
        }

        if (RelayMCUtils.getTicks() % ( 20 * 60) == 0) {
            info("------------------\nRemaining Memory: %d MB\n--------------------", Miscellaneous.getRemainingMemoryMegaBytes());
        }

        StatisticsLeaderboard.onTick();
    }

    private static void updateCustomMapsList() {
        if (!isDuelsLobby()) {
            return;
        }
        try {
            String query = RelayUtils.format("SELECT * FROM `%s` WHERE `visibility`='1' ORDER BY `published` DESC LIMIT 45", SmartDatabase.CUSTOM_MAP_TABLE_NAME);

            ResultSet recent = SmartDatabase.executeQuery(query).get(30, TimeUnit.SECONDS);

            RECENT_MAPS.clear();
            while (recent.next()) {
                try {
                    CustomMap customMap = CustomMap.getCustomMap(recent, true);
                    if (customMap != null) {
                        Miscellaneous.addIfAbsent(RECENT_MAPS, customMap);
                    }
                } catch (Exception e) {
                    RelayUtils.printError("processing custom map", e);
                }
            }
            List<String> timeFrames = Miscellaneous.getList("", "_daily", "_weekly", "_monthly");
            for (int i = 0; i < timeFrames.size(); i++) {
                String timeFrame = timeFrames.get(i);
                String databaseName = SmartDatabase.CUSTOM_MAP_TABLE_NAME + timeFrame;
                List<CustomMap> popularList = POPULAR_MAPS.get(i);
                if (i == 0) {
                    query = RelayUtils.format("SELECT * FROM `%s` WHERE `visibility`='1' ORDER BY `plays` DESC LIMIT 45", databaseName);
                } else {
                    query = RelayUtils.format("SELECT * FROM `%s` ORDER BY `plays` DESC LIMIT 45", databaseName);
                }
                ResultSet popular = SmartDatabase.executeQuery(query).get(30, TimeUnit.SECONDS);

                popularList.clear();
                while (popular.next()) {
                    try {

                        CustomMap customMap;
                        if (i == 0) {
                            customMap = CustomMap.getCustomMap(popular, true);
                        } else {
                            customMap = CustomMap.getCustomMap(popular.getString("id"));
                        }
                        if (customMap != null) {
                            Miscellaneous.addIfAbsent(popularList, customMap);
                            customMap.setPlays(i, popular.getInt("plays"));
                            customMap.setVotes(i, popular.getInt("votes"));
                        }
                    } catch (Exception e) {
                        RelayUtils.printError("processing custom map", e);
                    }

                }
            }
        } catch (Exception exception) {
            RelayUtils.printError("getting custom maps", exception);
        }
    }

    private static void updateCustomKitsList() {
        if (!isDuelsLobby()) {
            return;
        }
        try {
            ResultSet recent = SmartDatabase.executeQuery("SELECT * FROM `%s` WHERE `visibility`='1' ORDER BY `published` DESC LIMIT 45", SmartDatabase.CUSTOM_KIT_TABLE_NAME).get(30, TimeUnit.SECONDS);

            RECENT_KITS.clear();
            while (recent.next()) {
                try {
                    CustomDuelsKit customMap = CustomDuelsKit.getCustomDuelsKit(recent, true);
                    if (customMap != null) {
                        Miscellaneous.addIfAbsent(RECENT_KITS, customMap);
                    }
                } catch (Exception e) {
                    RelayUtils.printError("processing custom kit", e);
                }
            }
            List<String> timeFrames = Miscellaneous.getList("", "_daily", "_weekly", "_monthly");
            for (int i = 0; i < timeFrames.size(); i++) {
                String timeFrame = timeFrames.get(i);
                String databaseName = SmartDatabase.CUSTOM_KIT_TABLE_NAME + timeFrame;
                List<CustomDuelsKit> popularList = POPULAR_KITS.get(i);
                String query;
                if (i == 0) {
                    query = RelayUtils.format("SELECT * FROM `%s` WHERE `visibility`='1' ORDER BY `plays` DESC LIMIT 45", databaseName);
                } else {
                    query = RelayUtils.format("SELECT * FROM `%s` ORDER BY `plays` DESC LIMIT 45", databaseName);
                }
                ResultSet popular = SmartDatabase.executeQuery(query).get(30, TimeUnit.SECONDS);

                popularList.clear();
                while (popular.next()) {
                    try {
                        CustomDuelsKit customDuelsKit;
                        if (i == 0) {
                            customDuelsKit = CustomDuelsKit.getCustomDuelsKit(popular, true);
                        } else {
                            customDuelsKit = CustomDuelsKit.getCustomDuelsKit(popular.getString("id"));
                        }
                        if (customDuelsKit != null) {
                            Miscellaneous.addIfAbsent(popularList, customDuelsKit);
                            customDuelsKit.setPlays(i, popular.getInt("plays"));
                            customDuelsKit.setVotes(i, popular.getInt("votes"));
                        }
                    } catch (Exception e) {
                        RelayUtils.printError("processing custom kit", e);
                    }

                }
            }
        } catch (Exception exception) {
            RelayUtils.printError("getting custom kits", exception);
        }
    }

    public static void scheduleSyncDelayedTask(Runnable runnable, double secondDelay) {
        RelayMCUtils.scheduleSyncDelayedTask(runnable, secondDelay);
    }

    public static void synchronous(Runnable runnable) {
        scheduleSyncDelayedTask(runnable, 0);
    }

    public static void onPlayerCount(Map<GameType, Integer> counts) {
        Map<String, Integer> totals = new HashMap<>();
        int totalPartyCount = 0;
        for (GameType gameType : counts.keySet()) {
            if (gameType != null && gameType.getMainGame() == MainGame.PARTY) {
                totalPartyCount += counts.get(gameType);
            }
        }

        for (MainGame mainGame : MainGame.values()) {
            int total = 0;
            for (GameType gameType : counts.keySet()) {
                if (gameType.getMainGame() == mainGame) {
                    total += counts.get(gameType);
                }
            }
            totals.put(mainGame.getName(), total);
        }

        for (SmartNPC smartNPC : SmartNPC.getSmartNPCs()) {
            totals.forEach((s, integer) -> {
                if (Text.comparablyContains(smartNPC.lowerName.getValue(), s)) {
                    smartNPC.setUpperName(ChatColor.YELLOW.toString() + integer + " playing");
                }
            });

            if (Text.comparablyContains(smartNPC.lowerName.getValue(), "games")) {
                smartNPC.setUpperName(ChatColor.YELLOW.toString() + totalPartyCount + " playing");
            }

            for (GameType gameType : counts.keySet()) {
                if (Text.comparablyContains(smartNPC.lowerName.getValue(), gameType)) {
                    smartNPC.setUpperName(ChatColor.YELLOW.toString() + counts.get(gameType) + " playing");
                } else if (gameType != null) {
/*
                    SmartGUI smartGUI = smartNPC.commandOrMessage.getValue();
                    if (smartGUI != null && Text.comparablyEquals(smartGUI.getName(), gameType.getFullPrettyName())) {
                        smartNPC.setUpperName(ChatColor.YELLOW.toString() + counts.get(gameType) + " playing");
                    } else {
*/
                    String command = smartNPC.commandOrMessage.getValue();
                    if (command.contains("/") && Text.comparablyContains(command, gameType.getFullPrettyName())) {
                        smartNPC.setUpperName(ChatColor.YELLOW.toString() + counts.get(gameType) + " playing");
//                        }
                    }
                }
            }
        }

        SmartGUIUtils.onPlayerCount(counts);
    }

    public static void onCustomKitsCount(String argument) {
        JsonObject jsonObject = RelayUtils.parseJsonObject(argument);

        JsonArray current = jsonObject.get("current").getAsJsonArray();
        JsonArray queued = jsonObject.get("queued").getAsJsonArray();

        List<QueueData> currentGames = Miscellaneous.processList(current, jsonElement -> QueueData.fromString(jsonElement.getAsString()));
        List<QueueData> queuedGames = Miscellaneous.processList(queued, jsonElement -> QueueData.fromString(jsonElement.getAsString()));

        SmartGUIUtils.currentGames = currentGames;
        SmartGUIUtils.queuedGames = queuedGames;
        synchronous(() -> SmartGUIUtils.updateQueueGUIs(currentGames, queuedGames));
    }

    public static boolean isGameLobby() {
        return mainGame != null;
    }

    public static boolean isDuelsLobby() {
        return mainGame == MainGame.DUELS;
    }

    public static String getName(PotionEffectType type) {
        return Text.prettify(type.getName());
    }
}