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

import com.lifeknight.relaymchub.ExtraListeners;
import com.lifeknight.relaymchub.Main;
import com.lifeknight.relaymchub.commands.menu.KitCommand;
import com.lifeknight.relaymchub.cosmetics.Cosmetics;
import com.lifeknight.relaymchub.cosmetics.DefinedLobbyCosmetic;
import com.lifeknight.relaymchub.miscellaneous.Cooldown;
import com.lifeknight.relaymchub.miscellaneous.GameKit;
import com.lifeknight.relaymchub.miscellaneous.SmartGUIUtils;
import com.lifeknight.relaymchub.statistics.StatisticsLeaderboard;
import com.lifeknight.relaymcutils.RelayMCUtils;
import com.lifeknight.relaymcutils.network.ServerCommunicator;
import com.lifeknight.relaymcutils.player.*;
import com.lifeknight.relaymcutils.player.commands.CommandUtilities;
import com.lifeknight.relaymcutils.player.settings.GameSettings;
import com.lifeknight.relaymcutils.utilities.Parkour;
import com.lifeknight.relayutils.RelayUtils;
import com.lifeknight.relayutils.basic.Miscellaneous;
import com.lifeknight.relayutils.basic.SwearUtils;
import com.lifeknight.relayutils.basic.Text;
import com.lifeknight.relayutils.data.PartyData;
import com.lifeknight.relayutils.data.QueueData;
import com.lifeknight.relayutils.filter.*;
import com.lifeknight.relayutils.game.*;
import com.lifeknight.relayutils.network.Command;
import com.lifeknight.relayutils.player.ExtraData;
import com.lifeknight.relayutils.player.Group;
import com.lifeknight.relayutils.player.cosmetics.*;
import com.lifeknight.relayutils.player.data.GamePlayerDataColumn;
import com.lifeknight.relayutils.player.data.ProfileColumn;
import com.lifeknight.relayutils.player.data.SmartDatabase;
import com.lifeknight.relayutils.utilities.ComponentBuilder;
import com.lifeknight.relayutils.utilities.ItemUtilities;
import com.lifeknight.relayutils.utilities.PlayerUtilities;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import net.kyori.adventure.inventory.Book;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagString;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import org.bukkit.util.Vector;

import java.sql.ResultSet;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

import static com.lifeknight.relaymchub.miscellaneous.SmartGUIUtils.*;
import static com.lifeknight.relaymchub.player.EditCustomKitGUI.getKitItemsForCategory;
import static net.md_5.bungee.api.ChatColor.AQUA;
import static net.md_5.bungee.api.ChatColor.GOLD;
import static net.md_5.bungee.api.ChatColor.GREEN;
import static net.md_5.bungee.api.ChatColor.YELLOW;
import static org.bukkit.ChatColor.BLUE;
import static org.bukkit.ChatColor.BOLD;
import static org.bukkit.ChatColor.DARK_AQUA;
import static org.bukkit.ChatColor.DARK_GRAY;
import static org.bukkit.ChatColor.DARK_RED;
import static org.bukkit.ChatColor.GRAY;
import static org.bukkit.ChatColor.ITALIC;
import static org.bukkit.ChatColor.LIGHT_PURPLE;
import static org.bukkit.ChatColor.RED;
import static org.bukkit.ChatColor.UNDERLINE;
import static org.bukkit.ChatColor.WHITE;

public class HubPlayer {
    public static final Map<UUID, HubPlayer> HUB_PLAYERS = new HashMap<>();
    private final SmartPlayer smartPlayer;
    private final List<DefinedLobbyCosmetic> purchasedLobbyCosmetics = new ArrayList<>();
    private final List<DefinedLobbyCosmetic> lobbyCosmetics = new ArrayList<>();
    private final List<DefinedLobbyCosmetic> activeLobbyCosmetics = new ArrayList<>();
    private final Map<String, Long> cooldowns = new HashMap<>();
    public long connectTime = 0;
    private Map<GameAction, Number> overallStatistics = new HashMap<>();
    private Map<SpecificGameAction, Number> specificStatistics = new HashMap<>();
    private final List<Kit> kitConfigurations = new ArrayList<>();
    private final List<CustomDuelsKit> customDuelKits = new ArrayList<>();
    private boolean hasNoKits = false;
    private boolean hasNoMaps = false;

    private final List<CustomMap> customMaps = new ArrayList<>();
    private EditCustomKitGUI lastEditKitGui = null;

    private boolean isVanished = false;
    private boolean isHidingOthers = false;

    private int points = 0;

    private double pvpArenaHealth = -1;

    private long experience = 0;

    private DynamicScoreboard dynamicScoreboard;
    private DynamicScoreboard queueScoreboard;
    private DynamicScoreboard arenaScoreboard;
    private int queuePosition = 0;
    private int queueSize = 0;
    private int specificPosition = 0;
    private int specificSize = 0;
    private QueueData queueData = null;

    public List<Map<GameAction, Number>> previousLeaderboardStats = new ArrayList<>();
    public List<Map<String, Number>> previousLeaderboardExtraStats = new ArrayList<>();

    public boolean firstDataRetrieve = false;
    private boolean beingSentToHell;
    private Location lastLocation;
    private GameType queueType;
    private int typeSize;
    private long lastArenaDamage;
    private PartyData partyData;
    private boolean serverAvailable;
    private GameType duelGameType;
    private boolean isCreatingCustomKitCreative;
    public boolean isCreatingCustomKit = false;
    private boolean reOpeningCustomKit;
    private boolean isCreatingCustomMap;
    public boolean teleportedToCustomMap = false;
    public Kit lobbyKit = Kits.DUELS_LOBBY_SWORD;
    private Kit nextKit = null;
    private boolean inCombat;
    private CustomDuelsKit selectedCustomDuelsKit;
    public Location customMapCenter;
    private int customMapSize = 35;
    private int customMapHeight = 50;
    private List<Block> customMapBlocks;
    private CustomMap customMapEdited;
    private Location customMapSpawnA;
    private Location customMapSpawnB;
    private String customMapName;
    private Material customMapRepresentative;
    private boolean newCustomMap;
    private boolean customMapVisibility;

    private Map<String, Number> arenaStats = new HashMap<>();
    private boolean canRequeue = false;
    private boolean canSeePlayers = true;

    private long lastMovementTime = 0;
    public boolean AFKAgain = false;
    public boolean hasAFKMoved = false;
    private String limboQueuePosition = "Loading...";
    private String limboQueueSize = "";

    private Location previewCenter = null;
    public boolean isPreviewingMap = false;
    public boolean teleportedToPreviewMap = false;
    private final List<Block> previewMapBlocks = new ArrayList<>();
    private UUID previewQueueOwner = null;
    private CustomDuelsKit previewKit = null;
    private PreviewCustomKitGUI previewCustomKitGUI = null;
    CustomMap previewMap = null;

    private CustomDuelsKit previewLibraryKit = null;

    private UUID queuedAgainst = null;

    private final int nickLevel = Miscellaneous.getRandomIntBetweenRange(1, 8);
    private final List<CustomDuelsKit> recentKits = new ArrayList<>();
    private final List<CustomMap> recentMaps = new ArrayList<>();
    private final Map<String, CustomMap> publicCustomMaps = new HashMap<>();
    private final List<String> votedIDs = new ArrayList<>();

    private QueuePreferences queuePreferences = new QueuePreferences();

    private int timeFrame = 0;
    public List<HubPlayer> collaborationInvites = new ArrayList<>();
    private Map<GameType, Long> eloMap = new HashMap<>();
    private Map<GameType, Long> timeMap = new HashMap<>();

    public HubPlayer(SmartPlayer smartPlayer) {
        this.smartPlayer = smartPlayer;
        this.smartPlayer.setOnRetrieveData(this::onDataRetrieve);
        this.smartPlayer.setOnParkourStart(this::onParkour);

        HUB_PLAYERS.put(this.getUUID(), this);
    }

    private void onParkour(Boolean end) {
        if (!end) {
            if (!this.getInventory().contains(Material.RED_BED)) {
                this.disableAllCosmetics();
                this.smartPlayer.clear();
                this.giveParkourItems();
            }
        } else {
            this.spawn(false);
        }
    }

    private void giveParkourItems() {
        this.giveSmartItem(2, SmartGUIUtils.RESET);
        this.giveSmartItem(4, SmartGUIUtils.CHECKPOINT);
        this.giveSmartItem(6, SmartGUIUtils.EXIT);
        this.getInventory().setHeldItemSlot(4);
    }

    public static void onPlayerJoin(SmartPlayer smartPlayer) {
        if (smartPlayer == null) {
            Main.error("No player found on join");
        } else {
            Main.synchronous(() -> getHubPlayer(smartPlayer).onJoin());
        }
    }

    public static void onPlayerQuit(SmartPlayer smartPlayer) {
        getHubPlayer(smartPlayer).onDisconnect();
    }

    public static HubPlayer getHubPlayer(Player player) {
        if (player == null) return null;

        return getHubPlayer(SmartPlayer.getSmartPlayer(player));
    }

    public static HubPlayer getHubPlayer(SmartPlayer smartPlayer) {
        if (smartPlayer == null) return null;

        HubPlayer hubPlayer = getHubPlayer(smartPlayer.getUUID());

        if (hubPlayer == null) return new HubPlayer(smartPlayer);

        return hubPlayer;
    }

    public static HubPlayer getHubPlayer(UUID uuid) {
        return HUB_PLAYERS.get(uuid);
    }

    public static HubPlayer getHubPlayer(String name) {
        if (name.length() > 16) {
            return getHubPlayer(UUID.fromString(name));
        }
        return getHubPlayer(Bukkit.getPlayer(name));
    }

    public static Collection<HubPlayer> getHubPlayers() {
        return HUB_PLAYERS.values();
    }

    public static Collection<HubPlayer> getOnlineHubPlayers() {
        Collection<HubPlayer> hubPlayers = getHubPlayers();
        hubPlayers.removeIf(hubPlayer -> !hubPlayer.smartPlayer.isOnline());
        return hubPlayers;
    }

    public static void removeUnneededHubPlayers() {
        for (HubPlayer value : getHubPlayers()) {
            if (!SmartPlayer.getSmartPlayers().contains(value.smartPlayer)) {
                HUB_PLAYERS.remove(value.getUUID());
                RelayMCUtils.info("Removed HubPlayer %s for inactivity.", value.getName());
            }
        }
    }

    public static void onTick() {
        for (HubPlayer onlineHubPlayer : getOnlineHubPlayers()) {
            onlineHubPlayer.tick();
        }
    }

    public void tick() {
        if (Main.ticks % 20 == 0 && Main.isLimbo()) {
            this.updateScoreboard();
        }

        if (Main.ticks % 20 * 4 == 0) {
            if (this.isCreatingCustomMap) {
                this.renderCustomMapBorder();
            }
            if (this.isPreviewingMap) {
                this.smartPlayer.sendActionBar("%sMap Code: %s%s%s", GOLD, AQUA, UNDERLINE, this.previewMap.getId());
            }
        }

        if (this.hasNoMaps) {
            this.hasNoMaps = this.customMaps.isEmpty();
        }

        if (this.hasNoKits) {
            this.hasNoKits = this.customDuelKits.isEmpty();
        }

        if (Main.ticks % 30 == 0) {
            if (this.isVanished) {
                this.getPlayer().sendActionBar(WHITE + "You are " + BLUE + "VANISHED");
            } else if (ExtraListeners.isInArena(this.getPlayer())) {
                this.getPlayer().sendActionBar(AQUA + (Main.isDuelsLobby() ? "Kills: " : "Points: ") + WHITE + this.points);
            }

            if (this.inArena() && this.getInventory().contains(Material.TRIDENT) && (this.lobbyKit != null && this.lobbyKit != Kits.DUELS_LOBBY_RETIARIUS)) {
                this.getInventory().remove(Material.TRIDENT);
            }
        }

        if (Main.ticks % 30 == 15) {
            if (Main.isLimbo() && !this.getPlayer().getCanPickupItems()) {
                this.getPlayer().sendActionBar(AQUA + "Move to join");

            } else if (this.inArena()) {
                this.getPlayer().sendActionBar(YELLOW + "/spawn" + WHITE + " to return back to spawn");
            }
        }

        if (Main.isDuelsLobby() && this.inArena() && this.lobbyKit == Kits.DUELS_LOBBY_NINJA) {
            this.addPotionEffect(PotionEffectType.SPEED, 1, 1);
        }

        for (DefinedLobbyCosmetic activeCosmetic : this.activeLobbyCosmetics) {
            activeCosmetic.tick(this);
        }
        lastLocation = getLocation();

        if (this.isLastLeague(this.queueType)) {
            if (this.queueScoreboard != null) {
                this.queueScoreboard.update();
            }
            if (this.dynamicScoreboard != null) {
                this.dynamicScoreboard.update();
            }
            if (this.arenaScoreboard != null) {
                this.arenaScoreboard.update();
            }
        }

        if (this.beingSentToHell && Main.ticks % 5 == 0) {
            this.playSound(Sound.BLOCK_NOTE_BLOCK_SNARE);
        }

        if (this.isCreatingCustomKitCreative || this.isCreatingCustomMap || this.isCreatingCustomKit || this.isInQueue()) {
            this.updateLastMovementTime();
        }
        if (!Main.isLimbo()) {

            if (Main.ticks % 5 == 0 && this.lastMovementTime != 0 && Miscellaneous.enoughTimeHasPassed(this.lastMovementTime, 5, TimeUnit.MINUTES)) {
                if (Main.isLimbo() && !this.AFKAgain) {
/*
                this.afkagain = true;
                ServerCommunicator.sendToProxy(Command.OTHER, "afkagain", this.getUUID());
                this.sendErrorMessage("You have been removed from the queue due to being AFK.");
*/
                } else {
                    this.updateLastMovementTime();
                    this.sendErrorMessage("You have been sent to limbo for being AFK. Move to return to the lobby.");
                    this.sendToServer("afk");
                }
            }
        }
    }

    private boolean isInQueue() {
        return this.queueScoreboard != null;
    }

    public void getHistory() {
        if (Main.isLimbo()) return;
        Main.async(() -> {
            List<String> kits = Text.separateCSV((String) this.smartPlayer.getGamePlayerData(GamePlayerDataColumn.RECENT_DUELS_KITS));
            this.recentKits.clear();
            for (String kit : kits) {
                CustomDuelsKit customDuelsKit = CustomDuelsKit.getCustomDuelsKit(kit);
                if (customDuelsKit != null) {
                    this.recentKits.add(customDuelsKit);
                }
            }

            this.recentMaps.clear();
            for (String s : Text.separateCSV((String) this.smartPlayer.getGamePlayerData(GamePlayerDataColumn.RECENT_DUELS_MAPS))) {
                CustomMap customMap = CustomMap.getCustomMap(s);
                if (customMap != null) {
                    this.recentMaps.add(customMap);
                }
            }

            this.votedIDs.clear();
            this.votedIDs.addAll(Text.separateCSV((String) this.smartPlayer.getGamePlayerData(GamePlayerDataColumn.VOTED_IDS)));

            for (CustomDuelsKit customDuelsKit : Miscellaneous.getList(this.smartPlayer.getCustomDuelsKits())) {
                if (customDuelsKit.getPublicCustomMap() != null && !customDuelsKit.getPublicCustomMap().isEmpty()) {
                    CustomMap customMap = CustomMap.getCustomMap(customDuelsKit.getPublicCustomMap());
                    this.publicCustomMaps.put(customDuelsKit.getPublicCustomMap(), customMap);
                } else {
                    customDuelsKit.setPublicCustomMap(null);
                }
            }
        });
    }

    public void onJoin() {
        Main.schedule(() -> this.hasAFKMoved = false, 2);
        this.lastMovementTime = System.currentTimeMillis();
        this.limboQueuePosition = "Loading...";
        this.isCreatingCustomKit = false;
        this.points = 0;
        this.canRequeue = false;
        for (HubPlayer onlineHubPlayer : getOnlineHubPlayers()) {
            if (onlineHubPlayer.isHidingOthers && !(this.smartPlayer.isStaff() && !this.isAndHasNick())) {
                onlineHubPlayer.hidePlayer(this);
            }

            if (this.isVanished && !onlineHubPlayer.isStaff()) {
                onlineHubPlayer.hidePlayer(this);
            }

            if (onlineHubPlayer.isVanished && !this.isStaff()) {
                this.hidePlayer(onlineHubPlayer);
            }
        }

        this.getPlayer().setCanPickupItems(false);

        for (int i = 0; i < 3; i++) {
            this.previousLeaderboardStats.add(new LinkedHashMap<>());
            this.previousLeaderboardExtraStats.add(new LinkedHashMap<>());
        }

        if (Main.isAboutToRestart) {
            this.playSound(Sound.BLOCK_NOTE_BLOCK_SNARE);
            this.sendMessage(new ComponentBuilder(ComponentBuilder.RED_ORANGE).line().newLine().color(ComponentBuilder.RED).append("This lobby will be restarting in %s.", Text.getShortTextualFormattedTime(RelayMCUtils.getShouldStartRealizeTime() + 60 * 1000L - System.currentTimeMillis())).newLine().color(ComponentBuilder.RED_ORANGE).line());
        }

        if (Main.isLimbo()) {
            this.updateScoreboard();
            this.getPlayer().setInvisible(true);
            this.getPlayer().setCustomNameVisible(true);
        }
    }

    public BaseComponent getComponentFormattedName() {
        return this.smartPlayer.getComponentFormattedName();
    }

    public void broadcastLobbyJoinMessage() {
        ComponentBuilder message = new ComponentBuilder();

        message.append("%s> %s> %s> ", GOLD, YELLOW, AQUA);
        message.append(this.getComponentFormattedName()).color(ComponentBuilder.AQUA).append(" has joined the lobby!");
        message.append(" %s< %s< %s<", AQUA, YELLOW, GOLD);

        BaseComponent messageResult = message.getResult();
        for (HubPlayer onlineHubPlayer : getOnlineHubPlayers()) {
            onlineHubPlayer.sendMessage(messageResult);
        }
    }

    public void onDataRetrieve() {
        getHistory();
        this.updateExperienceBar();
        this.updateScoreboard();
        if (!this.isCreatingCustomMap) {
            Main.scheduleSyncDelayedTask(() -> this.smartPlayer.setAllowFlight(this.smartPlayer.isInGroupOrHigher(Group.TIERI)), 0.2);
        }

        this.getGameData();

        if (this.arenaStats == null || this.arenaStats.isEmpty()) {
            this.arenaStats = this.smartPlayer.getArenaStats();
        }

        if (!this.firstDataRetrieve && !Main.isLimbo()) {
            this.giveCosmetics();

            if (getExtraData().has("vanish")) {
                this.setVanished(getExtraData().getBoolean("vanish", false));
                if (this.isVanished) {
                    this.vanish();
                }
            }

            if (getExtraData().has("hideOthers")) {
                this.setHidingOthers(getExtraData().getBoolean("hideOthers", false));
            }

            this.queuePreferences = new QueuePreferences(getExtraData());

            if (getExtraData().has("lobbyKit")) {
                switch (getExtraData().getString("lobbyKit", "default")) {
                    case "axe" -> this.lobbyKit = Kits.DUELS_LOBBY_AXE;
                    case "ninja" -> this.lobbyKit = Kits.DUELS_LOBBY_NINJA;
                    case "retiarius" -> this.lobbyKit = Kits.DUELS_LOBBY_RETIARIUS;
                    case "archer" -> this.lobbyKit = Kits.DUELS_LOBBY_ARCHER;
                    default -> this.lobbyKit = Kits.DUELS_LOBBY_SWORD;
                }
            }

            if (!Main.isLimbo() && this.isInGroupOrHigher(Group.CHAMPION) && !this.isAndHasNick() && !this.isVanished) {
                this.broadcastLobbyJoinMessage();
            }
        }

        if (this.canRequeue) {

        }
        this.firstDataRetrieve = true;
    }

    private boolean inLimboQueue() {
        return !this.limboQueuePosition.equalsIgnoreCase("Loading...");
    }

    public void updateScoreboard() {
        if (!this.isOnline()) {
            return;
        }
        if (this.dynamicScoreboard == null) {
            if (Main.isLimbo()) {
                this.dynamicScoreboard = new DynamicScoreboard(GRAY + BOLD.toString() + "LIMBO");
                this.dynamicScoreboard.addPreLine();

                this.dynamicScoreboard.addDynamicScore(() -> {
                    if (this.inLimboQueue()) {
                        return new ComponentBuilder("%s%s/%s%s", GREEN, this.limboQueuePosition, WHITE, this.limboQueueSize);
                    }

                    return new ComponentBuilder().append(RED).bold().append("AFK");
                });
                this.dynamicScoreboard.addDynamicScore(() -> {
                    if (this.inLimboQueue()) {
                        return new ComponentBuilder().color(ComponentBuilder.AQUA).append("Queue Position: ");
                    }

                    return new ComponentBuilder().color(ComponentBuilder.AQUA).append("Status:");
                });
                this.dynamicScoreboard.addPostLine();
            } else {
                this.dynamicScoreboard = new DynamicScoreboard(GREEN + BOLD.toString() + Main.getScoreboardName());
                this.dynamicScoreboard.addPreLine();
                this.dynamicScoreboard.addDynamicScore(() -> new ComponentBuilder(RelayUtils.format("%sTokens: %s%s", GOLD, WHITE, Text.addCommasToNumber(this.smartPlayer.credits))));

                this.dynamicScoreboard.addDynamicScore(() -> new ComponentBuilder("%sLevel: %s%s [%s%s%s/%s]", AQUA, WHITE, Text.addCommasToNumber(this.getLevel()), AQUA, Text.addCommasToNumber(this.getXpAfterLevels(), true), WHITE, Text.addCommasToNumber(this.getNextLevelXp(), true)));
                this.dynamicScoreboard.addBlankSpace();
                this.dynamicScoreboard.addDynamicScore(() -> {
                    ComponentBuilder componentBuilder;
                    if (this.getHighestGroup() == Group.CHAMPION) {
                        componentBuilder = new ComponentBuilder(this.smartPlayer.getChampionTier() > 1000 ? ComponentBuilder.PURPLE : RelayUtils.getChampionTierColor(this.smartPlayer.getChampionTier()));
                    } else {
                        componentBuilder = new ComponentBuilder(this.getHighestGroup().getOther());
                    }
                    return componentBuilder.bold().append(this.getRealName()).append("!");
                });
                this.dynamicScoreboard.addScore("Welcome,");
                this.dynamicScoreboard.addPostLine();
            }
        } else {
            this.dynamicScoreboard.update();
        }
        if (this.queueScoreboard == null) {
            this.dynamicScoreboard.applyToPlayer(this.getPlayer());
        }
    }

    public void getGameData() {
        if (Main.isGameLobby()) {
            Main.info("Loading data for %s", this.getName());
            this.getLeaderboardStats();

            this.kitConfigurations.clear();
            String kitConfigurations = (String) this.smartPlayer.getGamePlayerData(GamePlayerDataColumn.KIT_CONFIGURATIONS);
            if (!kitConfigurations.equalsIgnoreCase("default")) {
                for (String s : Text.separateSSV(kitConfigurations)) {
                    Kit kit = Kit.fromJson(RelayUtils.parseJsonObject(s));
                    if (kit != null) {
                        this.kitConfigurations.add(kit);
                    } else {
                        Main.error("Couldn't process kit from json: \n%s", s);
                    }
                }
            }

            if (this.smartPlayer.getGamePlayerData(GamePlayerDataColumn.DUELS_CUSTOM_KITS).equals("")) {
                this.customDuelKits.clear();
            } else {
                for (CustomDuelsKit customDuelsKit : this.smartPlayer.getCustomDuelsKits()) {
                    if (!this.customDuelKits.contains(customDuelsKit)) {
                        this.customDuelKits.add(customDuelsKit);
                    }
                }

                List<String> ids = Text.separateCSV((String) this.smartPlayer.getGamePlayerData(GamePlayerDataColumn.DUELS_CUSTOM_KITS));

                this.customDuelKits.removeIf(customDuelsKit -> !ids.contains(customDuelsKit.getId()));
            }

            if (this.smartPlayer.getGamePlayerData(GamePlayerDataColumn.DUELS_CUSTOM_MAPS).equals("")) {
                this.customMaps.clear();
            } else {
                for (CustomMap customMap : this.smartPlayer.getCustomMaps()) {
                    if (!this.customMaps.contains(customMap)) {
                        this.customMaps.add(customMap);
                    }
                }

                List<String> ids = Text.separateCSV((String) this.smartPlayer.getGamePlayerData(GamePlayerDataColumn.DUELS_CUSTOM_MAPS));

                this.customMaps.removeIf(customMap -> !ids.contains(customMap.getId()));
            }
            if (this.getExtraData().has("selectedCustomKit")) {
                if (this.customDuelKits.isEmpty()) {
                    this.selectedCustomDuelsKit = null;
                }
                String selectedKit = this.getExtraData().getString("selectedCustomKit", "null");
                for (CustomDuelsKit customDuelsKit : this.customDuelKits) {
                    if (Text.comparablyContains(customDuelsKit.getId(), selectedKit)) {
                        this.selectedCustomDuelsKit = customDuelsKit;
                        break;
                    }
                }
            } else if (!customDuelKits.isEmpty()) {
                RelayMCUtils.warn("No kit selected, returning random");
                this.selectedCustomDuelsKit = Miscellaneous.getFirstEntry(customDuelKits);
            } else {
                RelayMCUtils.error("Attempted to get selected custom duels kit, no custom duels kits listed; %s", this.getRealName());
                this.selectedCustomDuelsKit = null;
            }

            Statistics statistics = this.smartPlayer.getStatistics(Main.getMainGame());
            if (statistics != null) {
                this.experience = statistics.getXp();
                this.eloMap = statistics.getEloMap();
                this.timeMap = statistics.getTimeMap();
                if (!this.isOnline()) {
                    Main.scheduleSyncDelayedTask(this::updateExperienceBar, 1);
                } else {
                    Main.synchronous(this::updateExperienceBar);
                }

                this.overallStatistics = statistics.getOverallData();
                this.specificStatistics = statistics.getSpecificData();
            }

            this.dynamicScoreboard.update();
        }
    }

    public void getLeaderboardStats() {
        List<String> leaderboardNames = Miscellaneous.getList("leaderboard", "leaderboard_weekly", "leaderboard_daily");

        this.previousLeaderboardStats.clear();
        this.previousLeaderboardExtraStats.clear();
        for (int i = 0; i < 3; i++) {
            this.previousLeaderboardStats.add(new LinkedHashMap<>());
            this.previousLeaderboardExtraStats.add(new LinkedHashMap<>());
        }
        Main.async(() -> {
            for (int i = 0; i < leaderboardNames.size(); i++) {
                String leaderboardName = leaderboardNames.get(i);
                try {
                    ResultSet resultSet = SmartDatabase.getAllFrom(leaderboardName, ProfileColumn.ID.getName(), this.getUUID()).get(5, TimeUnit.SECONDS);

                    if (resultSet.first()) {
                        for (GameAction gameAction : GameAction.getGameActions(Main.getMainGame())) {
                            if (gameAction.isLeaderboard() && !gameAction.isCalculated()) {
                                try {
                                    long value = resultSet.getLong(gameAction.getLeaderboardName(Main.getMainGame()));
                                    this.previousLeaderboardStats.get(i).put(gameAction, value);
                                } catch (Exception exception) {
                                    Main.warn("No column found: %s | %s | %s", leaderboardName, gameAction.getLeaderboardName(Main.getMainGame()), exception.getMessage());
                                }
                            }
                        }

                        Map<String, Number> extraStats = this.previousLeaderboardExtraStats.get(i);

                        extraStats.put("experience", resultSet.getLong(Main.getMainGame().specifyName("experience")));
                        extraStats.put("elo", resultSet.getLong(Main.getMainGame().specifyName("elo")));
                        extraStats.put("time_played", resultSet.getLong(Main.getMainGame().specifyName("time_played")));
                    }
                } catch (Exception exception) {
                    Main.error("An error occurred while trying to update the leaderboard statistics: %s | %s | %s", leaderboardName, this.getName(), exception.getMessage());
                }
            }
        });
    }

    public void addValueToOverallStatistic(SpecificGameAction specificGameAction, Long value) {
        GameAction overallGameAction = specificGameAction.getGameAction();
        if (!this.overallStatistics.containsKey(overallGameAction)) {
            this.overallStatistics.put(overallGameAction, value);
        } else {
            double current = this.overallStatistics.get(overallGameAction).doubleValue();
            this.overallStatistics.put(overallGameAction, current + value);
        }
    }

    public void onDisconnect() {
        this.smartPlayer.clear();
        this.connectTime = 0;
        this.smartPlayer.activeCosmetics.clear();
        for (DefinedLobbyCosmetic activeCosmetic : this.activeLobbyCosmetics) {
            this.smartPlayer.activeCosmetics.add(activeCosmetic.getOriginal());
        }
        this.updateProfile(ProfileColumn.ACTIVE_COSMETICS);
        this.disableAllCosmetics();
        this.arenaStats.forEach((s, number) -> this.smartPlayer.getArenaStats().put(s, number));
        this.smartPlayer.updateArenaStats();
        if (this.isCreatingOwnedCustomMap()) {
            this.saveCustomMap(true);
        } else if (this.isCreatingCustomMap) {
            this.leaveCustomMap();
        }
        this.pvpArenaHealth = -1F;

        this.arenaStats = new HashMap<>();

        this.cancelKit(false);
    }

    public void disableAllCosmetics() {
        while (!this.activeLobbyCosmetics.isEmpty()) {
            this.activeLobbyCosmetics.get(0).disable(this, false);
        }
    }

    public void giveCosmetics() {
        this.purchasedLobbyCosmetics.clear();
        this.activeLobbyCosmetics.clear();
        this.lobbyCosmetics.addAll(Cosmetics.getDefaultCosmeticItemsForPlayer(this));

        for (Cosmetic cosmeticName : this.smartPlayer.cosmetics) {
            DefinedLobbyCosmetic cosmetic = DefinedLobbyCosmetic.getCosmetic(cosmeticName);
            if (cosmetic != null) {
                Miscellaneous.addIfAbsent(this.purchasedLobbyCosmetics, cosmetic);
            }
        }

        for (DefinedLobbyCosmetic purchasedLobbyCosmetic : this.purchasedLobbyCosmetics) {
            Miscellaneous.addIfAbsent(this.lobbyCosmetics, purchasedLobbyCosmetic);
        }

        for (Cosmetic activeCosmetic : this.smartPlayer.activeCosmetics) {
            DefinedLobbyCosmetic cosmetic = DefinedLobbyCosmetic.getCosmetic(activeCosmetic);
            if (cosmetic != null && this.lobbyCosmetics.contains(cosmetic)) {
                cosmetic.enable(this, false);
            }
        }
    }

    public void sendMessage(String format, Object... data) {
        this.smartPlayer.sendMessage(format, data);
    }

    public void sendInfoMessage(String format, Object... data) {
        this.smartPlayer.sendInfoMessage(format, data);
    }

    public void sendErrorMessage(String format, Object... data) {
        this.smartPlayer.sendErrorMessage(format, data);
    }

    public void sendSuccessMessage(String format, Object... data) {
        this.smartPlayer.sendSuccessMessage(format, data);
    }

    public void sendUsageMessage(String format, Object... data) {
        this.smartPlayer.sendUsageMessage(format, data);
    }

    public Player getPlayer() {
        return this.smartPlayer.getPlayer();
    }

    public void sendToHubType(MainGame mainGame) {
        this.sendToServer(mainGame.getComparableName());
    }

    public void sendToMainHub() {
        if (Main.mainGame == null) {
            spawn(true);
        } else {
            this.sendToServer("main");
        }
    }

    public void sendToServer(String serverName) {
        this.smartPlayer.sendToServer(serverName);
    }

    public void queue(GameType gameType) {
        this.smartPlayer.queue(gameType);
    }

    public void queue(GameType gameType, QueueData queueData) {
        if (this.isInParty()) {
            queueData.setParty();
        }
        this.smartPlayer.queue(gameType, queueData);
    }

    public void partyQueue(GameType gameType) {
        this.smartPlayer.partyQueue(gameType);
    }

    public void sendChampionPurchaseMessage() {
        this.smartPlayer.sendChampionPurchaseMessage();
    }

    public void cancel(GameType gameType) {
        this.smartPlayer.cancel(gameType);
    }

    public Location getLocation() {
        return this.smartPlayer.getLocation();
    }

    public Location getLastLocation() {
        return lastLocation;
    }

    public void showSettingsGui() {
        AtomicReference<ChatFilter> chatFilterLevelShown = new AtomicReference<>(this.smartPlayer.chatFilter);
        AtomicReference<MessageFilter> messageFilterShown = new AtomicReference<>(this.smartPlayer.messageFilter);
        AtomicReference<FriendFilter> friendFilterShown = new AtomicReference<>(this.smartPlayer.friendFilter);
        AtomicReference<PartyFilter> partyFilterShown = new AtomicReference<>(this.smartPlayer.partyFilter);
        AtomicReference<DuelFilter> duelFilterShown = new AtomicReference<>(this.smartPlayer.duelFilter);

        SmartGUI settingsGUI = new SmartGUI("Settings", 1, false) {
            @Override
            public void onClose() {
                boolean shouldUpdate = false;

                smartPlayer.set(chatFilterLevelShown.get());

                smartPlayer.set(messageFilterShown.get());

                smartPlayer.set(friendFilterShown.get());

                smartPlayer.set(partyFilterShown.get());

                smartPlayer.set(duelFilterShown.get());
            }
        };

        ItemStack chatFilter = SmartGUIUtils.getItemStackForSettingName(this.smartPlayer.chatFilter.name());

        settingsGUI.add(0, chatFilter, (player1, rightClick, shift) -> {
            ChatFilter next = rightClick ? chatFilterLevelShown.get().getPrevious() : chatFilterLevelShown.get().getNext();
            chatFilterLevelShown.set(next);
            settingsGUI.setItem(0, ItemUtilities.edit(next.itemStack, ChatFilter.getName(), next.getDescriptionForItem()));
            playSuccessSoundVariation();
        }, ChatFilter.getName(), this.smartPlayer.chatFilter.getDescriptionForItem());

        ItemStack messageFilter = SmartGUIUtils.getItemStackForSettingName(this.smartPlayer.messageFilter.name());

        settingsGUI.add(1, messageFilter, (player1, rightClick, shift) -> {
            MessageFilter next = rightClick ? messageFilterShown.get().getPrevious() : messageFilterShown.get().getNext();
            messageFilterShown.set(next);
            settingsGUI.setItem(1, ItemUtilities.edit(next.itemStack, MessageFilter.getName(), next.getDescriptionForItem()));
            playSuccessSound();
        }, MessageFilter.getName(), this.smartPlayer.messageFilter.getDescriptionForItem());

        ItemStack friendFilter = SmartGUIUtils.getItemStackForSettingName(this.smartPlayer.friendFilter.name());

        settingsGUI.add(2, friendFilter, (player1, rightClick, shift) -> {
            FriendFilter next = rightClick ? friendFilterShown.get().getPrevious() : friendFilterShown.get().getNext();
            friendFilterShown.set(next);
            settingsGUI.setItem(2, ItemUtilities.edit(next.itemStack, FriendFilter.getName(), next.getDescriptionForItem()));
            playSuccessSoundVariation();
        }, FriendFilter.getName(), this.smartPlayer.friendFilter.getDescriptionForItem());

        ItemStack partyFilter = SmartGUIUtils.getItemStackForSettingName(this.smartPlayer.partyFilter.name());

        settingsGUI.add(3, partyFilter, (player1, rightClick, shift) -> {
            PartyFilter next = rightClick ? partyFilterShown.get().getPrevious() : partyFilterShown.get().getPrevious();
            partyFilterShown.set(next);
            settingsGUI.setItem(3, ItemUtilities.edit(next.itemStack, PartyFilter.getName(), next.getDescriptionForItem()));
            playSuccessSoundVariation();
        }, PartyFilter.getName(), this.smartPlayer.partyFilter.getDescriptionForItem());

        ItemStack duelFilter = SmartGUIUtils.getItemStackForSettingName(this.smartPlayer.duelFilter.name());

        settingsGUI.add(4, duelFilter, (player1, rightClick, shift) -> {
            DuelFilter next = rightClick ? duelFilterShown.get().getPrevious() : duelFilterShown.get().getNext();
            duelFilterShown.set(next);
            settingsGUI.setItem(4, ItemUtilities.edit(next.itemStack, DuelFilter.getName(), next.getDescriptionForItem()));
            playSuccessSoundVariation();
        }, DuelFilter.getName(), this.smartPlayer.duelFilter.getDescriptionForItem());

        this.openGui(settingsGUI);
    }

    public World getWorld() {
        return this.smartPlayer.getWorld();
    }

    public Vector getDirection() {
        return this.smartPlayer.getDirection();
    }

    public boolean isAdministrator() {
        return this.smartPlayer.isAdministrator();
    }

    public boolean lobbyHasGameCosmeticTypes() {
        return Main.getMainGame() != null && !Miscellaneous.filter(GameCosmeticType.values(), type -> !GameCosmetic.getGameCosmetics(type).isEmpty()).isEmpty();
    }

    public void openCosmeticsGui() {
        if (!this.lobbyHasGameCosmeticTypes()) {
            this.openLobbyCosmeticsGui();
            return;
        }
        SmartGUI smartGUI = new SmartGUI("Cosmetics", 1, false);
        smartGUI.add(3, Material.IRON_SWORD, (player, b, b2) -> this.openGameCosmeticsGui(), GREEN + Main.getMainGame().getName() + " Cosmetics");
        smartGUI.add(5, Material.PAPER, (player, b, b2) -> this.openLobbyCosmeticsGui(), BLUE + "Lobby Cosmetics");

/*
        smartGUI.add(40, Material.ARROW, (player, b, b2) -> {
            if (this.hasMenu()) {
                this.openMenu();
            } else {
                this.closeInventory();
            }
        }, this.hasMenu() ? RED + "Cancel" : RED + "Close");
*/

        smartGUI.fillEmptySpots();

        this.openGui(smartGUI);
    }

    public void openMenu() {
        this.openGui(SmartGUIUtils.MENU);
    }

    public void openLobbyCosmeticsGui() {
        SmartGUI smartGUI = new SmartGUI("Cosmetics", 4, false);
        List<CosmeticType> options = Miscellaneous.filter(CosmeticType.values(), type -> !Cosmetic.getCosmeticsOfType(type).isEmpty());

        if (options.size() % 2 == 0) {
            int startingIndex = 13 - options.size() / 2 + 1;
            for (int i = 0; i < options.size() - 1; i++) {
                CosmeticType cosmeticType = options.get(i);
                smartGUI.add(startingIndex + i, cosmeticType.getRepresentative(), (player, b, b2) -> this.openLobbyCosmeticsGui(cosmeticType), WHITE + cosmeticType.getPrettyName());
            }

            int i = options.size() - 1;
            CosmeticType cosmeticType = options.get(i);
            smartGUI.add(22, cosmeticType.getRepresentative(), (player, b, b2) -> this.openLobbyCosmeticsGui(cosmeticType), WHITE + cosmeticType.getPrettyName());
        } else {
            int startingIndex = 13 - options.size() / 2;
            for (int i = 0; i < options.size(); i++) {
                CosmeticType cosmeticType = options.get(i);
                smartGUI.add(startingIndex + i, cosmeticType.getRepresentative(), (player, b, b2) -> this.openLobbyCosmeticsGui(cosmeticType), WHITE + cosmeticType.getPrettyName());
            }
        }

        smartGUI.add(31, Material.BARRIER, (player, b, b2) -> {
            if (this.lobbyHasGameCosmeticTypes()) {
                this.openCosmeticsGui();
            } else {
                if (this.hasMenu()) {
                    this.openMenu();
                } else {
                    this.closeInventory();
                }
            }
        }, RED + "Cancel");

        smartGUI.fillEmptySpots();

        this.openGui(smartGUI);
    }

    public boolean hasMenu() {
        return Main.hasKits();
    }

    public void openLobbyCosmeticsGui(CosmeticType cosmeticType) {
        this.openLobbyCosmeticsGui(cosmeticType, 1);
    }

    public void openLobbyCosmeticsGui(CosmeticType cosmeticType, int page) {
        List<DefinedLobbyCosmetic> ofType = DefinedLobbyCosmetic.getCosmeticItems(cosmeticType);

        SmartGUI cosmeticsGUI = new SmartGUI(ChatColor.stripColor(cosmeticType.getPrettyName()), 5, false);

        int itemIndex = 3 * 7 * (page - 1);

        Map<DefinedLobbyCosmetic, Integer> cosmeticToIndex = new HashMap<>();

        for (int row = 1; row < 4 && itemIndex < ofType.size(); row++) {
            for (int i = 1; i < 8 && itemIndex < ofType.size(); i++) {
                DefinedLobbyCosmetic cosmetic = ofType.get(itemIndex++);
                final int currentIndex = row * 9 + i;
                cosmeticToIndex.put(cosmetic, currentIndex);
            }
        }

        for (DefinedLobbyCosmetic cosmetic : cosmeticToIndex.keySet()) {
            int index = cosmeticToIndex.get(cosmetic);
            cosmeticsGUI.add(index, cosmetic.getItemStack(this), (player, b, b2) -> {
                if (cosmetic.onClick(this)) {
                    this.playSuccessSound();
                } else {
                    if (cosmetic.isPurchasable()) {
                        this.showPurchaseGUI(cosmeticsGUI, cosmetic);
                    } else {
                        this.sendErrorMessage("This cosmetic cannot be purchased!");
                        this.playErrorSound();
                    }
                }
                for (DefinedLobbyCosmetic other : cosmeticToIndex.keySet()) {
                    cosmeticsGUI.setItem(cosmeticToIndex.get(other), other.getItemStack(this));
                }
            });
        }

        cosmeticsGUI.add(8, Material.PAPER, (player, b, b2) -> this.showStore(), BLUE + "Store", GRAY + "Don't have enough tokens?");

        if (page != 1) {
            cosmeticsGUI.add(4 * 9 + 3, Material.FEATHER, (player, b, b2) -> this.openLobbyCosmeticsGui(cosmeticType, page - 1), YELLOW + "Previous");
        }

        if (itemIndex != ofType.size()) {
            cosmeticsGUI.add(4 * 9 + 5, Material.EMERALD_BLOCK, (player, b, b2) -> this.openLobbyCosmeticsGui(cosmeticType, page + 1), YELLOW + "Next");
        }

        cosmeticsGUI.add(4 * 9 + 4, Material.BARRIER, (player, b, b2) -> this.openLobbyCosmeticsGui(), RED + "Close");

        int start = 10;
        int end = 17;
        for (int i = 0; i < 3; i++) {
            for (int j = start; j < end; j++) {
                if (cosmeticsGUI.getGui().getItem(j) == null) {
                    cosmeticsGUI.addImmutable(j, Material.WHITE_STAINED_GLASS_PANE, ChatColor.RESET.toString());
                }
            }
            start += 9;
            end += 9;
        }

        cosmeticsGUI.fillEmptySpots();

        this.openGui(cosmeticsGUI);
    }


    public void showPurchaseGUI(SmartGUI previous, DefinedLobbyCosmetic cosmetic) {
        SmartGUI gui = new SmartGUI("Purchase", 1, false);
        gui.add(3, this.canPurchaseCosmetic(cosmetic) ? Material.GREEN_TERRACOTTA : Material.BARRIER, (player, b, b2) -> this.tryPurchase(cosmetic, previous), GREEN + "Purchase " + cosmetic.getPrettyName(), GOLD.toString() + cosmetic.getCost() + " Tokens");
        gui.add(5, Material.RED_TERRACOTTA, (player, b, b2) -> this.openGui(previous), RED + "Return");
        this.openGui(gui);
    }

    public void tryPurchase(DefinedLobbyCosmetic cosmetic, SmartGUI previous) {
        if (this.purchasedLobbyCosmetics.contains(cosmetic)) {
            this.sendErrorMessage("You already own this cosmetic!");
            this.playErrorSound();
        } else if (this.smartPlayer.credits < cosmetic.getCost()) {
            int difference = (int) (cosmetic.getCost() - this.smartPlayer.credits);
            this.sendErrorMessage("You need %s%d%s more token%s to purchase this cosmetic!", YELLOW, difference, RED, difference == 1 ? "" : "s");
            this.playErrorSound();
        } else {
            this.purchasedLobbyCosmetics.add(cosmetic);
            Miscellaneous.addIfAbsent(this.lobbyCosmetics, cosmetic);
            this.smartPlayer.credits -= cosmetic.getCost();
            this.smartPlayer.cosmetics.add(cosmetic.getOriginal());
            this.updateProfile(ProfileColumn.CREDITS);
            this.updateProfile(ProfileColumn.COSMETICS);
            this.sendSuccessMessage("Purchased %s%s.", cosmetic.getPrettyName(), GREEN);
            cosmetic.enable(this, true);
            this.playSuccessSound();
            this.openLobbyCosmeticsGui(cosmetic.getOriginal().getType());
        }
    }

    public boolean canPurchaseCosmetic(DefinedLobbyCosmetic cosmetic) {
        return !(this.purchasedLobbyCosmetics.contains(cosmetic) || this.smartPlayer.credits < cosmetic.getCost());
    }

    public boolean hasLobbyCosmeticActive(DefinedLobbyCosmetic cosmetic) {
        return this.activeLobbyCosmetics.contains(cosmetic);
    }

    public boolean hasLobbyCosmeticUnlocked(DefinedLobbyCosmetic cosmetic) {
        return this.lobbyCosmetics.contains(cosmetic) || this.purchasedLobbyCosmetics.contains(cosmetic);
    }

    public void openGameCosmeticsGui() {
        SmartGUI smartGUI = new SmartGUI(Main.getMainGame().getName() + " Cosmetics", 5, false);
        List<GameCosmeticType> options = Miscellaneous.filter(GameCosmeticType.values(), type -> !GameCosmetic.getGameCosmetics(Main.getMainGame(), type).isEmpty());
        options.remove(GameCosmeticType.TRAIL);
        options.remove(GameCosmeticType.KILL_EFFECT);

        if (options.size() == 2) {
            boolean f = false;
            for (GameCosmeticType option : options) {
                smartGUI.add(f ? 14 : 12, option.getRepresentative(), (player, b, b2) -> this.openGameCosmeticsGui(option), WHITE + option.getPrettyName());
                f = true;
            }
        } else if (options.size() % 2 == 0) {
            int startingIndex = 13 - options.size() / 2 + 1;
            for (int i = 0; i < options.size() - 1; i++) {
                GameCosmeticType gameCosmeticType = options.get(i);
                smartGUI.add(startingIndex + i, gameCosmeticType.getRepresentative(), (player, b, b2) -> this.openGameCosmeticsGui(gameCosmeticType), WHITE + gameCosmeticType.getPrettyName());
            }

            int i = options.size() - 1;
            GameCosmeticType gameCosmeticType = options.get(i);
            smartGUI.add(22, gameCosmeticType.getRepresentative(), (player, b, b2) -> this.openGameCosmeticsGui(gameCosmeticType), WHITE + gameCosmeticType.getPrettyName());
        } else {
            int startingIndex = 13 - options.size() / 2;
            for (int i = 0; i < options.size(); i++) {
                GameCosmeticType gameCosmeticType = options.get(i);
                smartGUI.add(startingIndex + i, gameCosmeticType.getRepresentative(), (player, b, b2) -> this.openGameCosmeticsGui(gameCosmeticType), WHITE + gameCosmeticType.getPrettyName());
            }
        }

        smartGUI.add(40, Material.BARRIER, (player, b, b2) -> this.openCosmeticsGui(), RED + "Cancel");

        smartGUI.fillEmptySpots();

        this.openGui(smartGUI);
    }

    public void openGameCosmeticsGui(GameCosmeticType gameCosmeticType) {
        this.openGameCosmeticsGui(gameCosmeticType, 1);
    }

    public void openGameCosmeticsGui(GameCosmeticType gameCosmeticType, int page) {
        List<GameCosmetic> ofType = GameCosmetic.getGameCosmetics(Main.getMainGame(), gameCosmeticType);

        SmartGUI gameCosmeticsGUI = new SmartGUI(ChatColor.stripColor(gameCosmeticType.getPrettyName()), 5, false);

        int itemIndex = 3 * 7 * (page - 1);

        Map<GameCosmetic, Integer> gameCosmeticToIndex = new HashMap<>();

        for (int row = 1; row < 4 && itemIndex < ofType.size(); row++) {
            for (int i = 1; i < 8 && itemIndex < ofType.size(); i++) {
                GameCosmetic gameCosmetic = ofType.get(itemIndex++);
                final int currentIndex = row * 9 + i;
                gameCosmeticToIndex.put(gameCosmetic, currentIndex);
            }
        }

        for (GameCosmetic gameCosmetic : gameCosmeticToIndex.keySet()) {
            int index = gameCosmeticToIndex.get(gameCosmetic);
            gameCosmeticsGUI.add(index, gameCosmetic.getItemStack(this.hasGameCosmetic(gameCosmetic), this.hasGameCosmeticActive(gameCosmetic)), (player, b, b2) -> {
                if (!this.onClickGameCosmetic(gameCosmetic)) {
                    if (gameCosmetic.isPurchasable()) {
                        this.showPurchaseGUI(gameCosmeticsGUI, gameCosmetic);
                    } else {
                        this.sendErrorMessage("This cosmetic cannot be purchased!");
                        this.playErrorSound();
                    }
                }
                for (GameCosmetic other : gameCosmeticToIndex.keySet()) {
                    gameCosmeticsGUI.setItem(gameCosmeticToIndex.get(other), other.getItemStack(this.hasGameCosmetic(other), this.hasGameCosmeticActive(other)));
                }
            });
        }

        gameCosmeticsGUI.add(8, Material.PAPER, (player, b, b2) -> this.showStore(), BLUE + "Store", GRAY + "Don't have enough tokens?");

        if (page != 1) {
            gameCosmeticsGUI.add(4 * 9 + 3, Material.FEATHER, (player, b, b2) -> this.openGameCosmeticsGui(gameCosmeticType, page - 1), YELLOW + "Previous");
        }

        if (itemIndex != ofType.size()) {
            gameCosmeticsGUI.add(4 * 9 + 5, Material.EMERALD_BLOCK, (player, b, b2) -> this.openGameCosmeticsGui(gameCosmeticType, page + 1), YELLOW + "Next");
        }

        gameCosmeticsGUI.add(4 * 9 + 4, Material.BARRIER, (player, b, b2) -> this.openGameCosmeticsGui(), RED + "Close");

        int start = 10;
        int end = 17;
        for (int i = 0; i < 3; i++) {
            for (int j = start; j < end; j++) {
                if (gameCosmeticsGUI.getGui().getItem(j) == null) {
                    gameCosmeticsGUI.addImmutable(j, Material.WHITE_STAINED_GLASS_PANE, ChatColor.RESET.toString());
                }
            }
            start += 9;
            end += 9;
        }

        gameCosmeticsGUI.fillEmptySpots();

        this.openGui(gameCosmeticsGUI);
    }

    public boolean hasGameCosmetic(GameCosmetic gameCosmetic) {
        return this.getGameCosmetics().contains(gameCosmetic);
    }

    public boolean hasGameCosmeticActive(GameCosmetic gameCosmetic) {
        return this.getActiveGameCosmetics().contains(gameCosmetic);
    }

    public List<GameCosmetic> getGameCosmetics() {
        return this.smartPlayer.getGameCosmeticList(Main.getMainGame());
    }

    public List<GameCosmetic> getActiveGameCosmetics() {
        return this.smartPlayer.getActiveGameCosmeticList(Main.getMainGame());
    }

    public boolean onClickGameCosmetic(GameCosmetic gameCosmetic) {
        if (this.hasGameCosmetic(gameCosmetic)) {
            if (this.hasGameCosmeticActive(gameCosmetic)) {
                this.disableGameCosmetic(gameCosmetic);
            } else {
                this.enableGameCosmetic(gameCosmetic);
            }

            return true;
        } else {
            this.sendErrorMessage("You do not have this cosmetic!");
        }

        return false;
    }

    public void disableGameCosmetic(GameCosmetic gameCosmetic) {
        this.getActiveGameCosmetics().remove(gameCosmetic);
        this.playSuccessSound();
        this.sendInfoMessage("Disabled %s%s.", gameCosmetic.getPrettyName(), YELLOW);
        this.updateProfile(ProfileColumn.ACTIVE_GAME_COSMETICS);
    }

    public void updateProfile(ProfileColumn profileColumn) {
        this.smartPlayer.updateProfile(profileColumn);
        if (this.dynamicScoreboard != null) {
            this.dynamicScoreboard.update();
        }
    }

    public void enableGameCosmetic(GameCosmetic gameCosmetic) {
        this.getActiveGameCosmetics().removeIf(gameCosmetic1 -> gameCosmetic.isOfGame(Main.getMainGame()) && gameCosmetic1.getType() == gameCosmetic.getType());

        this.getActiveGameCosmetics().add(gameCosmetic);
        this.updateProfile(ProfileColumn.ACTIVE_GAME_COSMETICS);
        this.playSuccessSound();
        this.sendSuccessMessage("Enabled %s%s.", gameCosmetic.getPrettyName(), GREEN);
    }

    public void showStore() {
        this.closeInventory();
        ComponentBuilder componentBuilder = new ComponentBuilder();
        componentBuilder.gradientLine(ComponentBuilder.GREEN.darker(), ComponentBuilder.GREEN, ComponentBuilder.GREEN.darker()).newLine();
        componentBuilder.color(ComponentBuilder.BLUE).append("Store: ").link("https://store.relaymc.net").newLine();
        componentBuilder.gradientLine(ComponentBuilder.GREEN.darker(), ComponentBuilder.GREEN, ComponentBuilder.GREEN.darker());

        this.sendMessage(componentBuilder);

        this.playSuccessSoundVariation();
    }

    public void sendMessage(ComponentBuilder componentBuilder) {
        this.smartPlayer.sendMessage(componentBuilder);
    }

    public void sendMessage(BaseComponent baseComponent) {
        this.smartPlayer.sendMessage(baseComponent);
    }

    public void closeInventory() {
        this.getPlayer().closeInventory();
    }

    public void showPurchaseGUI(SmartGUI previous, GameCosmetic gameCosmetic) {
        SmartGUI gui = new SmartGUI("Purchase", 1, false);
        gui.add(3, this.canPurchaseCosmetic(gameCosmetic) ? Material.GREEN_TERRACOTTA : Material.BARRIER, (player, b, b2) -> this.tryPurchase(gameCosmetic, previous), GREEN + "Purchase " + gameCosmetic.getPrettyName(), GOLD.toString() + gameCosmetic.getCost() + " Tokens");
        gui.add(5, Material.RED_TERRACOTTA, (player, b, b2) -> this.openGui(previous), RED + "Return");
        this.openGui(gui);
    }

    public void tryPurchase(GameCosmetic gameCosmetic, SmartGUI previous) {
        if (this.hasGameCosmetic(gameCosmetic)) {
            this.sendErrorMessage("You already own this cosmetic!");
            this.playErrorSound();
        } else if (this.smartPlayer.credits < gameCosmetic.getCost()) {
            int difference = (int) (gameCosmetic.getCost() - this.smartPlayer.credits);
            this.sendErrorMessage("You need %s%d%s more token%s to purchase this cosmetic!", YELLOW, difference, RED, difference == 1 ? "" : "s");
            this.playErrorSound();
        } else {
            this.getGameCosmetics().add(gameCosmetic);
            this.smartPlayer.credits -= gameCosmetic.getCost();
            this.updateProfile(ProfileColumn.CREDITS);
            this.updateProfile(ProfileColumn.GAME_COSMETICS);
            this.sendSuccessMessage("Purchased %s%s.", gameCosmetic.getPrettyName(), GREEN);
            this.enableGameCosmetic(gameCosmetic);
            this.playSuccessSound();
            this.openGameCosmeticsGui(gameCosmetic.getType());
        }
    }

    public boolean canPurchaseCosmetic(GameCosmetic gameCosmetic) {
        return !(this.hasGameCosmetic(gameCosmetic) || this.smartPlayer.credits < gameCosmetic.getCost());
    }

    public void playSound(Sound sound) {
        this.smartPlayer.playSound(sound);
    }

    public void playSuccessSound() {
        this.smartPlayer.playSuccessSound();
    }

    public void playSuccessSoundVariation() {
        this.smartPlayer.playSuccessSoundVariation();
    }

    public void playErrorSound() {
        this.smartPlayer.playErrorSound();
    }

    public void playClickSound() {
        this.smartPlayer.playClickSound();
    }

    public void playClickSoundVariation() {
        this.smartPlayer.playClickSoundVariation();
    }

    public void openGui(SmartGUI smartGUI) {
        if (smartGUI == null) return;
        smartGUI.fillEmptySpots();
        this.smartPlayer.openGui(smartGUI);
    }

    public void giveSmartItem(SmartItem smartItem) {
        this.smartPlayer.giveItem(smartItem);
    }

    public void giveSmartItem(int index, SmartItem smartItem) {
        this.smartPlayer.giveItem(index, smartItem);
    }

    public PlayerInventory getInventory() {
        return this.smartPlayer.getInventory();
    }

    public Group getHighestGroup() {
        return this.smartPlayer.getHighestGroup();
    }

    public boolean isInGroupOrHigher(Group group) {
        return this.smartPlayer.isInGroupOrHigher(group);
    }

    public boolean hasRankOrHigher(Group group) {
        if (this.isAdministrator()) return true;

        return this.getHighestRank().isEqualOrHigherThan(group);
    }

    public EntityEquipment getEquipment() {
        return this.getPlayer().getEquipment();
    }

    public void setWalkSpeed(float walkSpeed) {
        this.getPlayer().setWalkSpeed(walkSpeed);
    }

    public Group getHighestRank() {
        if (this.isInGroup(Group.CHAMPION)) {
            return Group.CHAMPION;
        } else if (this.isInGroup(Group.TIERIII)) {
            return Group.TIERIII;
        } else if (this.isInGroup(Group.TIERII)) {
            return Group.TIERII;
        } else if (this.isInGroup(Group.TIERI)) {
            return Group.TIERI;
        }

        return Group.DEFAULT;
    }

    public boolean isInGroup(Group group) {
        return this.smartPlayer.isInGroup(group);
    }

    public void giveItem(int index, ItemStack itemStack) {
        this.getInventory().setItem(index, itemStack);
    }

    public boolean isGliding() {
        return this.getPlayer().isGliding();
    }

    public void boostElytra(ItemStack firework) {
        this.getPlayer().boostElytra(firework);
    }

    public void hidePlayers() {
        if (this.enoughSecondsHaveElapsed(Cooldown.HIDE_SHOW_PLAYERS)) {
            //this.giveSmartItem(6, SmartGUIUtils.SHOW_PLAYERS);
            this.hideOthers();
            this.canSeePlayers = false;
            this.sendSuccessMessage("Hiding players.");
            this.updateCooldown(Cooldown.HIDE_SHOW_PLAYERS);
            this.playSuccessSound();
        } else {
            this.showCooldownWarning(Cooldown.HIDE_SHOW_PLAYERS);
        }
    }

    public void showPlayers() {
        if (this.enoughSecondsHaveElapsed(Cooldown.HIDE_SHOW_PLAYERS)) {
            //this.giveSmartItem(6, SmartGUIUtils.HIDE_PLAYERS);
            this.showOthers();
            this.canSeePlayers = true;
            this.sendSuccessMessage("Showing players.");
            this.updateCooldown(Cooldown.HIDE_SHOW_PLAYERS);
            this.playSuccessSound();
        } else {
            this.showCooldownWarning(Cooldown.HIDE_SHOW_PLAYERS);
        }
    }

    public void updateCooldown(String codeName) {
        this.cooldowns.put(codeName, System.currentTimeMillis());
    }

    public void updateCooldown(Cooldown cooldown) {
        this.updateCooldown(cooldown.codeName);
    }

    public long getLastTime(String codeName) {
        return this.cooldowns.getOrDefault(codeName, 0L);
    }

    public long getLastTime(Cooldown cooldown) {
        return this.getLastTime(cooldown.codeName);
    }

    public boolean enoughSecondsHaveElapsed(String codeName, double seconds) {
        if (this.isAdministrator()) {
            return true;
        }

        long lastTime = this.getLastTime(codeName);

        return lastTime == 0 || System.currentTimeMillis() - lastTime >= seconds * 1000L;
    }

    public boolean enoughSecondsHaveElapsed(Cooldown cooldown) {
        if (this.isAdministrator()) {
            return true;
        }

        long lastTime = this.getLastTime(cooldown);

        return cooldown.enoughTimeHasPassed(lastTime);
    }

    public void showCooldownWarning(Cooldown cooldown) {
        long timeRemaining = cooldown.time - (System.currentTimeMillis() - this.getLastTime(cooldown));
        int secondsRemaining = (int) Math.ceil(timeRemaining / 1000D);

        this.sendErrorMessage("You must wait %ds to do this!", secondsRemaining);
    }

    public void showCooldownWarning(String cooldown, double seconds) {
        long timeRemaining = (long) (seconds * 1000L - (System.currentTimeMillis() - this.getLastTime(cooldown)));
        int secondsRemaining = (int) Math.ceil(timeRemaining / 1000D);

        this.sendErrorMessage("You must wait %ds to do this!", secondsRemaining);
    }

    public void vanish() {
        this.isVanished = true;
        this.hideFromOthers();

        this.sendSuccessMessage("You are now vanished.");
        this.getExtraData().setBoolean("vanish", true);
        this.smartPlayer.updateProfile(ProfileColumn.EXTRA);
    }

    public void hideFromOthers() {
        for (SmartPlayer player : SmartPlayer.getOnlineSmartPlayers()) {
            if (player != this.smartPlayer && !player.isStaff()) {
                player.hidePlayer(this.smartPlayer);
            }
        }
    }

    public void unVanish() {
        this.isVanished = false;
        this.smartPlayer.showForOthers();
        this.smartPlayer.sendActionBar("You are no longer %sVANISHED", RED);
        this.sendSuccessMessage("You are no longer vanished.");
        this.getExtraData().setBoolean("vanish", false);
        this.smartPlayer.updateProfile(ProfileColumn.EXTRA);
    }

    public void showOthers() {
        this.isHidingOthers = false;
        for (HubPlayer hubPlayer : getHubPlayers()) {
            if (!hubPlayer.isVanished) {
                this.showPlayer(hubPlayer);
            }
        }

        this.getExtraData().setBoolean("hidingOthers", false);
    }

    public ExtraData getExtraData() {
        return (ExtraData) this.smartPlayer.getProfileData(ProfileColumn.EXTRA);
    }

    public void setExtraData(String name, Object value) {
        this.getExtraData().setString(name, String.valueOf(value));
        this.updateProfile(ProfileColumn.EXTRA);
    }

    public void hideOthers() {
        this.isHidingOthers = true;
        for (HubPlayer hubPlayer : getHubPlayers()) {
            if (!(hubPlayer.isStaff() && !hubPlayer.isAndHasNick())) {
                this.hidePlayer(hubPlayer);
            }
        }

        this.getExtraData().setBoolean("hidingOthers", true);
    }

    public void hidePlayer(HubPlayer hubPlayer) {
        this.smartPlayer.hidePlayer(hubPlayer.smartPlayer);
    }

    public void showPlayer(HubPlayer hubPlayer) {
        this.smartPlayer.showPlayer(hubPlayer.smartPlayer);
    }

    public String getFormattedName() {
        return this.smartPlayer.getFormattedName();
    }

    public ChatColor getFormattedNameColor() {
        return this.smartPlayer.getFormattedNameColor();
    }

    public ChatColor getRealFormattedNameColor() {
        return this.smartPlayer.getRealFormattedNameColor();
    }

    public String getName() {
        return this.smartPlayer.getName();
    }

    public boolean isStaff() {
        return this.smartPlayer.isStaff();
    }

    public boolean isAndHasNick() {
        return this.smartPlayer.isAndHasNick();
    }

    public void givePvPItems() {
        PlayerInventory inventory = this.getInventory();
        inventory.clear();
        if (Main.isDuelsLobby()) {
            if (this.nextKit != null) {
                this.lobbyKit = this.nextKit;
                this.nextKit = null;
            }
            this.lobbyKit.giveItemsToPlayer(this.getPlayer(), true);
            this.giveSmartItem(8, SmartGUIUtils.KIT_SELECTOR);
            this.giveSmartItem(7, SPAWN);
        } else {
            inventory.setItem(0, ItemUtilities.unbreakable(Material.DIAMOND_AXE));
            inventory.setItem(1, ItemUtilities.unbreakable(Material.DIAMOND_SWORD));
            inventory.setHelmet(ItemUtilities.unbreakable(Material.DIAMOND_HELMET));
            inventory.setChestplate(ItemUtilities.unbreakable(Material.DIAMOND_CHESTPLATE));
            inventory.setLeggings(ItemUtilities.unbreakable(Material.DIAMOND_LEGGINGS));
            inventory.setBoots(ItemUtilities.unbreakable(Material.DIAMOND_BOOTS));
            inventory.setItemInOffHand(ItemUtilities.unbreakable(Material.SHIELD));
        }
    }

    public void onKillPlayer(HubPlayer killed) {
        if (Main.isDuelsLobby()) {
            this.getPlayer().setHealth(20F);
            this.points++;
            if (this.lobbyKit == Kits.DUELS_LOBBY_ARCHER) {
                this.getInventory().addItem(new ItemStack(Material.ARROW, 6));
            }
            this.arenaStats.put("kills", this.arenaStats.getOrDefault("kills", 0).intValue() + 1);
            this.arenaStats.put("killstreak", this.arenaStats.getOrDefault("killstreak", 0).intValue() + 1);
            if (this.arenaStats.get("killstreak").intValue() > this.arenaStats.getOrDefault("max_killstreak", 0).intValue()) {
                this.arenaStats.put("max_killstreak", this.arenaStats.get("killstreak").intValue());
            }
            this.arenaStats.put("kdr", this.arenaStats.get("kills").doubleValue() / (Math.max(0, this.arenaStats.get("deaths").doubleValue())));
        } else {
            int killedKilled = killed.points;

            int previousPoints = this.points;
            int gained;
            if (previousPoints < 10) {
                gained = Math.max(1, (int) (0.7 * killedKilled));
            } else {
                gained = (int) Math.max(1, 6 * Math.log(killedKilled));
            }

            this.points += gained;
            this.sendSuccessMessage("You gained %s%d point%s!", AQUA + BOLD.toString(), gained, gained == 1 ? "" : "s");

            if (this.points >= 1) {
                this.addPotionEffect(PotionEffectType.REGENERATION, 5, 1);
            }

            if (this.points >= 3) {
                this.addPotionEffect(PotionEffectType.ABSORPTION, 60, 1);
            }

            if (this.points >= 5) {
                this.addPotionEffect(PotionEffectType.INCREASE_DAMAGE, 10, 1);
            }

            if (this.points >= 7) {
                this.addPotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 60, 0);
            }

            if (this.points >= 10 && previousPoints < 10) {
                Main.broadcastMessage("%s%s is now a juggernaut!", this.getFormattedName(), AQUA + BOLD.toString());
            }

            if (this.points >= 10) {
                int degree = this.points / 10;
                this.addPotionEffect(PotionEffectType.ABSORPTION, 10, (int) (degree * 1.25));
                this.addPotionEffect(PotionEffectType.REGENERATION, 10, 1);
                this.addPotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 10, 1);
                this.addPotionEffect(PotionEffectType.SPEED, 10, 1);
                this.addPotionEffect(PotionEffectType.GLOWING, 300);
                this.addPotionEffect(PotionEffectType.HEALTH_BOOST, 10, (int) (degree / 2D));
                this.addPotionEffect(PotionEffectType.INCREASE_DAMAGE, 10, 1);
                this.addPotionEffect(PotionEffectType.FAST_DIGGING, 60, 1);
            }

            for (int i = 1; i < 10; i++) {
                if (this.points >= i * 10 && previousPoints < i * 10) {
                    this.doForStage(i);
                    break;
                }
            }
        }
        this.inCombat = false;
    }

    public void doForStage(int stage) {
        if (stage >= 1) this.addRandomEnchantmentToArmor(stage);
        if (stage >= 2) this.addRandomEnchantToWeapon(stage);
        if (stage >= 3) this.addPotionEffect(PotionEffectType.BLINDNESS, 600, stage);
        if (stage >= 4) this.addPotionEffect(PotionEffectType.SLOW, 600, stage);
        if (stage >= 5) this.addPotionEffect(PotionEffectType.CONFUSION, 600, stage);
        if (stage >= 6) this.addPotionEffect(PotionEffectType.WEAKNESS, 600, stage);
        if (stage >= 7) {
            this.addPotionEffect(PotionEffectType.WITHER, 600, stage);
            this.addPotionEffect(PotionEffectType.POISON, 600, stage);
        }
    }

    public void addPotionEffect(PotionEffectType potionEffectType, double duration, int amplifier) {
        PotionEffect potionEffect = new PotionEffect(potionEffectType, (int) (duration * 20), amplifier);
        this.getPlayer().addPotionEffect(potionEffect.withIcon(false).withAmbient(false).withParticles(false));
    }

    public void addPotionEffect(PotionEffectType potionEffectType, double duration) {
        this.addPotionEffect(potionEffectType, duration, 0);
    }

    public void addPotionEffect(PotionEffectType potionEffectType) {
        this.addPotionEffect(potionEffectType, 99999);
    }

    public ItemStack getRandomEquipment() {
        EntityEquipment entityEquipment = this.getEquipment();

        switch (Miscellaneous.getRandomIntBetweenRange(0, 3)) {
            case 0:
                return entityEquipment.getHelmet();
            case 1:
                return entityEquipment.getChestplate();
            case 2:
                return entityEquipment.getLeggings();
        }

        return entityEquipment.getBoots();
    }

    public void addRandomEnchantmentToArmor(int degree) {
        ItemUtilities.edit(this.getRandomEquipment(), itemMeta -> {
            Enchantment enchantment = getRandomArmorEnchantment();
            if (itemMeta.hasEnchant(enchantment)) {
                itemMeta.addEnchant(enchantment, itemMeta.getEnchantLevel(enchantment) + degree, true);
            } else {
                itemMeta.addEnchant(enchantment, Miscellaneous.getRandomIntBetweenRange(enchantment.getStartLevel(), enchantment.getMaxLevel()), true);
            }
        });
    }

    public void addRandomEnchantToWeapon(int degree) {
        ItemStack axe = null;

        for (ItemStack itemStack : this.getInventory()) {
            if (itemStack != null && itemStack.getType() == Material.DIAMOND_AXE) {
                axe = itemStack;
                break;
            }
        }

        if (axe == null) return;

        ItemUtilities.edit(axe, itemMeta -> {
            Enchantment enchantment = getRandomAxeEnchantment();
            if (itemMeta.hasEnchant(enchantment)) {
                itemMeta.addEnchant(enchantment, itemMeta.getEnchantLevel(enchantment) + degree, true);
            } else {
                itemMeta.addEnchant(enchantment, Miscellaneous.getRandomIntBetweenRange(enchantment.getStartLevel(), enchantment.getMaxLevel()), true);
            }
        });
    }

    public static Enchantment getRandomArmorEnchantment() {
        return Miscellaneous.getRandomEntry(Miscellaneous.getList(Enchantment.PROTECTION_ENVIRONMENTAL, Enchantment.THORNS));
    }

    public static Enchantment getRandomAxeEnchantment() {
        return Miscellaneous.getRandomEntry(Miscellaneous.getList(Enchantment.DAMAGE_ALL, Enchantment.KNOCKBACK, Enchantment.FIRE_ASPECT));
    }

    public static Enchantment getRandomBowEnchantment() {
        return Miscellaneous.getRandomEntry(Miscellaneous.getList(Enchantment.ARROW_DAMAGE, Enchantment.ARROW_FIRE, Enchantment.ARROW_KNOCKBACK));
    }

    public void showStatistics() {
        if (!Main.isGameLobby()) {
            this.playErrorSound();
            this.sendErrorMessage("Statistics for a game are only available in that game lobby!");
            return;
        }

        this.openStatisticsGui();
    }

    public void openStatisticsGui() {
        if (this.specificStatistics.isEmpty()) {
            this.sendErrorMessage("There are no statistics to display.");
        } else {
            this.getStatisticsGui().open(this.getPlayer());
        }
    }

    public SmartGUI getStatisticsGui() {
        List<GameType> gameTypes = Main.getMainGame().getGameTypes();
        gameTypes.removeIf(gameType -> gameType.isExplicitParty() || !gameType.isEnabled());
        SmartGUI smartGUI = new SmartGUI((Main.isGameLobby() ? Main.getMainGame().getName() : "Other") + " Statistics", 3 + gameTypes.size() / 9, false);

        if (Main.isDuelsLobby()) {
            gameTypes.add(MainGame.DUELS.getPartyGameType());
        }
        smartGUI.add(gameTypes.size() % 2 == 0 ? 13 : 22, ItemUtilities.enchanted(Material.BOOK), null, GOLD + "Overall", this.getOverallStatistics());

        int i = 13 - (gameTypes.size() / 2);
        for (GameType gameType : gameTypes) {
            String text = GOLD + (gameType.hasSuffix() ? gameType.getSuffix() : gameType.getFullPrettyName());

            List<ComponentBuilder> desc = this.getSpecificStatistics(gameType);

            if (gameType == MainGame.DUELS.getPartyGameType()) {
                text = GOLD + "Arena";
                List<ComponentBuilder> statisticsList = new ArrayList<>();
                final boolean[] darkAqua = {false};
                this.arenaStats.forEach((specificGameAction, number) -> {
                    boolean isRatio = specificGameAction.equalsIgnoreCase("kdr");
                    statisticsList.add(new ComponentBuilder((darkAqua[0] ? DARK_AQUA : AQUA) + ("kdr".equalsIgnoreCase(specificGameAction) ? "K/D" : Text.prettify(specificGameAction)) + ": " + WHITE + (isRatio ? shortenNumber(number) : Text.addCommasToNumber(number.longValue()))));
                    if (Text.comparablyContains(specificGameAction, "kdr", "streak")) {
                        statisticsList.add(new ComponentBuilder(""));
                    }
                    darkAqua[0] = !darkAqua[0];
                });

                desc = statisticsList;
            }
            if (gameTypes.size() % 2 == 0) {
                smartGUI.addImmutable(i % 9 > 3 ? i + 1 : i, ItemUtilities.editComponentBuilder(gameType.getRepresentative(), text, desc));
                i++;
            } else {
                smartGUI.addImmutable(i++, ItemUtilities.editComponentBuilder(gameType.getRepresentative(), text, desc));
            }
        }

        smartGUI.fillEmptySpots();

        return smartGUI;
    }

    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 final List<GameAction> NEW_LINE = Miscellaneous.getList(GameActions.GAMES_PLAYED, GameActions.WINSTREAK, GameActions.LOSSES, GameActions.DEATHS);

    public static boolean shouldNewLine(GameAction gameAction) {
        return NEW_LINE.contains(gameAction);
    }

    public static boolean shouldNewLine(SpecificGameAction specificGameAction) {
        return shouldNewLine(specificGameAction.getGameAction());
    }

    public List<String> getOverallStatistics() {
        List<String> overallStatistics = new ArrayList<>();
        overallStatistics.add(AQUA + "Time Played: " + WHITE + Text.getShortTextualFormattedTime(this.timeMap.get(null)));
        final boolean[] darkAqua = {true};
        this.overallStatistics.forEach((gameAction, value) -> {
            boolean isRatio = gameAction.isCalculated();
            overallStatistics.add((darkAqua[0] ? DARK_AQUA : AQUA) + gameAction.getPluralPrettyName() + ": " + WHITE + (isRatio ? Main.shortenNumber(value) : Text.addCommasToNumber(value.longValue())));
            if (shouldNewLine(gameAction)) {
                overallStatistics.add("");
            }
            darkAqua[0] = !darkAqua[0];
        });

        return overallStatistics;
    }

    public List<ComponentBuilder> getSpecificStatistics(GameType gameType) {
        List<ComponentBuilder> statistics = new ArrayList<>();
        boolean darkAqua = true;
        statistics.add(SmartPlayer.getEloComponent(this.eloMap.getOrDefault(gameType, 0L), true));
        //statistics.add(new ComponentBuilder(GOLD.toString()).append("Total Elo: ").color(WHITE).append(this.eloMap.getOrDefault(gameType, 0L)));
        statistics.add(new ComponentBuilder(ChatColor.AQUA + "Time Played: " + WHITE + Text.getShortTextualFormattedTime(this.timeMap.getOrDefault(gameType, 0L))));
        for (SpecificGameAction specificGameAction : this.specificStatistics.keySet()) {
            GameAction gameAction = specificGameAction.getGameAction();
            boolean isRatio = gameAction.isCalculated();
            if (this.specificStatistics.containsKey(specificGameAction) && specificGameAction.getGameType() == gameType) {
                Number value = this.specificStatistics.get(specificGameAction);
                statistics.add(new ComponentBuilder((darkAqua ? DARK_AQUA : AQUA) + gameAction.getPluralPrettyName() + ": " + WHITE + (isRatio ? Main.shortenNumber(value) : Text.addCommasToNumber(value.longValue()))));
                darkAqua = !darkAqua;
                if (shouldNewLine(specificGameAction)) {
                    statistics.add(new ComponentBuilder(""));
                }
            }

        }

        return statistics;
    }

    public UUID getUUID() {
        return this.smartPlayer.getUUID();
    }

    public void hideEntity(Entity entity) {
        this.smartPlayer.hideEntity(entity);
    }

    public void showEntity(Entity entity) {
        this.smartPlayer.showEntity(entity);
    }

    public void setEntityNameVisible(Entity entity, boolean visible) {
        this.smartPlayer.setEntityNameVisible(entity, visible);
    }

    public String getRealFormattedName() {
        return this.smartPlayer.getRealFormattedName();
    }

    public String getRealName() {
        return this.smartPlayer.getRealName();
    }

    public int getLevel() {
        return RelayUtils.calculateLevel(null, this.getXp());
    }

    public long getXp() {
        if (true/*!Main.isGameLobby()*/) {
            return this.smartPlayer.xp;
        }

        return this.experience;
    }

    public long getNextLevelXp() {
        return RelayUtils.getXpRequired(/*Main.getMainGame()*/null, this.getLevel());
    }

    public long getXpAfterLevels() {
        return RelayUtils.getXpAfterLevels(/*Main.getMainGame()*/null, this.getXp());
    }

    public double getNetworkLevelPercentage() {
        return this.getNetworkXpAfterLevels() / (double) this.getNextNetworkLevelXp();
    }

    public long getNetworkXpAfterLevels() {
        return RelayUtils.getXpAfterLevels(null, this.smartPlayer.xp);
    }

    public long getNextNetworkLevelXp() {
        return RelayUtils.getXpRequired(null, this.getNetworkLevel());
    }

    public int getNetworkLevel() {
        return RelayUtils.calculateLevel(null, this.smartPlayer.xp);
    }

    public void updateExperienceBar() {
        if (this.isOnline()) {
            Main.synchronous(() -> {

                Player player = this.getPlayer();
                player.setExp(0F);
                player.setLevel(this.getNetworkLevel());
                player.giveExp((int) (this.getNetworkLevelPercentage() * player.getExpToLevel()));
                if (this.queueScoreboard == null) {
                    this.displayDefaultScoreboard();
                }
            });
        }
    }

    public boolean isOnline() {
        return this.smartPlayer.isOnline();
    }

    public void displayDefaultScoreboard() {
        this.queueScoreboard = null;
        Main.synchronous(this::updateScoreboard);

        if (!this.isInQueue()) {
            this.getInventory().remove(SmartGUIUtils.LEAVE_QUEUE.getItemStack());
        }
    }

    public void spawn(boolean teleport) {
        if (teleport) {
            Location location = PlayerUtilities.getLocationNear(this.getWorld().getSpawnLocation(), Main.isLimbo() ? 7 : 3);
            location.setYaw(Main.yaw);
            this.teleport(location);
            this.getPlayer().setVelocity(new Vector(0, 0, 0));
        }

        if (this.isCreatingOwnedCustomMap()) {
            this.saveCustomMap(true);
        } else if (this.isCreatingCustomMap) {
            this.leaveCustomMap(true);
        }

        this.disableAllCosmetics();
        this.smartPlayer.clear();
        this.smartPlayer.setInvulnerable(true);
        this.getPlayer().setFireTicks(-1);
        this.smartPlayer.setAllowFlight(this.isInGroupOrHigher(Group.TIERI));
        this.updateExperienceBar();
        ExtraListeners.giveItems(this.getPlayer());
        if (this.isInQueue()) {
            this.giveSmartItem(LEAVE_QUEUE);
        }
        for (DefinedLobbyCosmetic activeLobbyCosmetic : this.activeLobbyCosmetics) {
            activeLobbyCosmetic.enable(this, false);
        }
        Main.scheduleSyncDelayedTask(() -> StatisticsLeaderboard.showLeaderboards(this), 1);
    }

    public void leaveCustomMap(boolean alreadySpawned) {
        HubPlayer owner = HubPlayer.getHubPlayer(this.customMapEdited.getOwner());
        if (owner != null) {
            owner.sendInfoMessage("%s%s has left your custom map.", this.getRealFormattedName(), YELLOW);
            owner.mapCollaborators.remove(this);
        }
        this.isCreatingCustomMap = false;
        if (!alreadySpawned) {
            sendSuccessMessage("Sent you back to spawn.");
            spawn(true);
        }
    }

    public void leaveCustomMap() {
        this.leaveCustomMap(false);
    }

    public void teleport(Location location) {
        this.smartPlayer.teleport(location);
    }

    public String getLevelPrefix(boolean color) {
        return RelayUtils.getLevelPrefix(null, this.isAndHasNick() ? this.nickLevel : this.getLevel(), color);
    }

    public String getLevelPrefix() {
        return this.getLevelPrefix(true);
    }

    public void sendChatMessage(HubPlayer hubPlayer, String message) {
        this.smartPlayer.sendChatMessage(hubPlayer.getChatName().append(WHITE + ": "), hubPlayer.smartPlayer, message);
    }

    public ComponentBuilder getChatName() {
        ComponentBuilder componentBuilder = new ComponentBuilder();
        //if (Main.isGameLobby()) {
        //componentBuilder.append(this.getLevelPrefix()).append(" ");
        NamePrefixCosmetic namePrefixCosmetic = this.getNamePrefixCosmetic();
        if (namePrefixCosmetic != null) {
            componentBuilder.appendComponent(namePrefixCosmetic.getPrefix()).append(" ");
        }
        //}

        return componentBuilder.append(this.smartPlayer.getComponentFormattedName());
    }

    public NamePrefixCosmetic getNamePrefixCosmetic() {
        return this.smartPlayer.getActiveGameCosmeticOfType(Main.getMainGame(), GameCosmeticType.NAME_PREFIX);
    }

    public void openDefaultKitListGui() {
        if (Main.getMainGame() == MainGame.DUELS) {
            //this.openDuelsKitGui();
            this.openDuelsCustomKitsListGui(1);
        } else {
            this.openDefaultKitListGui(1);
        }
    }

    public void openDefaultKitListGui(int page) {
        List<GameKit> gameKits = GameKit.getKitsForGame(Main.getMainGame());
        SmartGUI kitGUI = new SmartGUI("Kits", 5, false);

        int itemIndex = 3 * 7 * (page - 1);

        Map<GameKit, Integer> gameKitToIndex = new HashMap<>();

        for (int row = 1; row < 4 && itemIndex < gameKits.size(); row++) {
            for (int i = 1; i < 8 && itemIndex < gameKits.size(); i++) {
                GameKit gameKit = gameKits.get(itemIndex++);
                final int currentIndex = row * 9 + i;
                gameKitToIndex.put(gameKit, currentIndex);
            }
        }

        for (GameKit gameKit : gameKitToIndex.keySet()) {
            int index = gameKitToIndex.get(gameKit);
            kitGUI.add(index, gameKit.kit.representative.itemStack, (player, b, b2) -> this.openEditKitGui(gameKit, kitGUI), AQUA + gameKit.kit.name, DARK_GRAY + "Edit " + gameKit.kit.name + ".");
        }

        if (page != 1) {
            kitGUI.add(4 * 9 + 3, Material.FEATHER, (player, b, b2) -> this.openDefaultKitListGui(page - 1), YELLOW + "Previous");
        }

        if (itemIndex != gameKits.size()) {
            kitGUI.add(4 * 9 + 5, Material.EMERALD_BLOCK, (player, b, b2) -> this.openDefaultKitListGui(page + 1), YELLOW + "Next");
        }

        kitGUI.add(40, Material.BARRIER, (player, b, b2) -> {
            if (this.hasMenu()) {
                if (Main.getMainGame() == MainGame.DUELS) {
                    this.openDefaultKitListGui();
                } else {
                    this.openMenu();
                }
            } else {
                this.closeInventory();
            }
        }, this.hasMenu() ? RED + "Cancel" : RED + "Close");

        this.openGui(kitGUI);
    }

    public void openEditKitGui(GameKit gameKit, SmartGUI previousGui) {
        Kit toEdit = gameKit.kit;
        Kit match = Miscellaneous.match(this.kitConfigurations, kit -> kit.name.equalsIgnoreCase(toEdit.name));
        this.openEditKitGui(match == null ? toEdit : match, previousGui);
    }

    public void openEditKitGui(Kit kit, SmartGUI previousGui) {
        SmartGUI smartGUI = new SmartGUI("Edit " + kit.name, 6, false);

        for (int i = 45; i < 54; i++) {
            if (!(i == 45 || i == 49 || i == 53)) {
                smartGUI.add(i, Material.GRAY_STAINED_GLASS_PANE, (OnPlayerClick) null, "");
            }
        }

        smartGUI.add(45, Material.EMERALD_BLOCK, (player, b, b2) -> {
            List<KitItem> editedKitItems = new ArrayList<>();
            for (int i = 36; i < 45; i++) {
                ItemStack itemStack = smartGUI.getGui().getItem(i);
                if (itemStack != null) {
                    editedKitItems.add(new KitItem(itemStack, i - 36));
                }
            }

            for (int i = 0; i < 27; i++) {
                ItemStack itemStack = smartGUI.getGui().getItem(i);
                if (itemStack != null) {
                    int j;
                    if (i < 9) {
                        j = i + 27;
                    } else if (i < 18) {
                        j = i % 9 + 18;
                    } else {
                        j = i % 9 + 9;
                    }
                    editedKitItems.add(new KitItem(itemStack, j));
                }
            }

            for (KitItem item : kit.items) {
                if (item.slot == 40) {
                    editedKitItems.add(item);
                    break;
                }
            }
            Kit editedKit = new Kit(kit, editedKitItems);
            this.kitConfigurations.removeIf(kit1 -> kit1.name.equals(kit.name));
            this.kitConfigurations.add(editedKit);
            this.smartPlayer.editGamePlayerColumn(GamePlayerDataColumn.KIT_CONFIGURATIONS, Text.toSSV(this.kitConfigurations));
            this.playSuccessSound();
            this.sendSuccessMessage("Kit saved.");
            this.openGui(previousGui);
        }, GREEN + "Save");

        smartGUI.add(49, Material.NETHERITE_SCRAP, (player, b, b2) -> this.openEditKitGui(Kit.getKit(kit.name), previousGui), YELLOW + "Reset");

        smartGUI.add(53, Material.BARRIER, (player, b, b2) -> this.openGui(previousGui), RED + "Cancel");

        for (int i = 27; i < 36; i++) {
            smartGUI.addImmutable(i, Material.GREEN_STAINED_GLASS_PANE, GRAY + "↑ Inventory", GRAY + "↓ Hotbar");
        }

        int nextStart = 0;

        for (KitItem item : kit.items) {
            if (item.slot != 40) {
                if (item.slot < 9) {
                    if (item.slot < 0) {
                        smartGUI.add(item.slot + 36 + ++nextStart, item.itemStack);
                    } else {
                        smartGUI.add(item.slot + 36, item.itemStack);
                    }
                } else {
                    if (item.slot < 18) {
                        smartGUI.add(item.slot % 9 + 18, item.itemStack);
                    } else if (item.slot < 27) {
                        smartGUI.add(item.slot % 9 + 9, item.itemStack);
                    } else {
                        smartGUI.add(item.slot % 9, item.itemStack);
                    }
                }
            }
        }

        this.openGui(smartGUI);
    }

    public void openDuelsKitGui() {
        SmartGUI smartGUI = new SmartGUI("Kits", 5, false);

        smartGUI.add(12, Material.IRON_SWORD, (player, b, b2) -> this.openDefaultKitListGui(1), GREEN + "Default Kits");

        smartGUI.add(14, Material.NETHERITE_SWORD, (player, b, b2) -> this.openDuelsCustomKitsListGui(1), BLUE + "Custom Kits");

        smartGUI.add(40, Material.BARRIER, (player, b, b2) -> {
            if (this.hasMenu()) {
                this.openMenu();
            } else {
                this.closeInventory();
            }
        }, this.hasMenu() ? RED + "Cancel" : RED + "Close");

        this.smartPlayer.openGui(smartGUI);
    }

    public void openDuelsCustomKitsListGui() {
        this.openDuelsCustomKitsListGui(1);
    }

    public void openDuelsCustomKitsListGui(int page) {
        this.updateCustomKitList();
        SmartGUI kitGUI;
        if (this.customDuelKits.isEmpty()) {
            kitGUI = new SmartGUI("Kits", 3, false);
            kitGUI.add(13, Material.OAK_BUTTON, (player, b, b1) -> this.openCustomKitGui(null, b), GREEN + ChatColor.BOLD.toString() + "CREATE KIT", GRAY + ITALIC.toString() + "Select to create a custom kit!");
            kitGUI.fillEmptySpots();
        } else {
            kitGUI = new SmartGUI("Kits", 5, false);

            int itemIndex = 3 * 7 * (page - 1);

            int row = 1;
            Main.info("Selected kit: %s", this.selectedCustomDuelsKit != null ? this.selectedCustomDuelsKit.getId() : "null");
            for (int i = 0; i < 7; i++) {
                int j = i + 1;
                List<CustomDuelsKit> kits = Miscellaneous.filter(this.customDuelKits, customDuelsKit -> customDuelsKit.getOwner().equals(this.getUUID()) && customDuelsKit != this.getNonOwnedKit());
                if (i < kits.size()) {
                    CustomDuelsKit kit = kits.get(i);
                    kitGUI.add(j + 9, kit.getKit().representative.itemStack, (player, b, b2) -> this.openCustomKitGui(kit, b), AQUA + kit.getKit().name);

                    kitGUI.add(j + 18, this.selectedCustomDuelsKit == kit ? Material.LIME_DYE : Material.GRAY_DYE, (player, b, b1) -> {
                        if (this.selectedCustomDuelsKit == kit) {
                            this.playErrorSound();
                        } else {
                            if (this.isInQueue() && this.queueType == GameType.DUELS_CUSTOM) {
                                this.sendErrorMessage("You cannot change your selected kit while queued!");
                                this.playErrorSound();
                            } else {
                                this.selectedCustomDuelsKit = kit;
                                this.openDuelsCustomKitsListGui();
                                this.updateSelectedCustomDuelsKit();
                                this.playSuccessSound();
                            }
                        }
                    }, this.selectedCustomDuelsKit == kit ? GREEN + "Selected" : GRAY + "Click to Select");
                } else {
                    if (j < this.getMaxCustomKits() + 1) {
                        kitGUI.add(row * 9 + j, Material.OAK_BUTTON, (player, b, b1) -> this.openCustomKitGui(null, b), ChatColor.GREEN + ChatColor.BOLD.toString() + "CREATE KIT", GRAY + ITALIC.toString() + "Select to create a custom kit!");
                    } else {
                        kitGUI.add(row * 9 + j, Material.STONE_BUTTON, (player, b, b1) -> this.playErrorSound(), ChatColor.RED + ChatColor.BOLD.toString() + "CREATE KIT", GRAY + ITALIC.toString() + "Upgrade to Premium to unlock this slot.");
                    }
                    kitGUI.add(row * 9 + j + 9, Material.GRAY_DYE, (player, b, b1) -> this.playErrorSound(), GRAY + "You cannot select a kit you don't have.");
                }
            }

            CustomDuelsKit customDuelsKit = this.getNonOwnedKit();

            if (customDuelsKit != null) {
                boolean isSelected = this.selectedCustomDuelsKit == customDuelsKit;

                List<String> description = new ArrayList<>();
                description.add(GRAY + "Owner: " + SmartPlayer.getOrCreate(customDuelsKit.getOwner()).getRealFormattedName());
                if (isSelected) {
                    description.add(GREEN + "Selected");
                    description.add(DARK_GRAY + "Left-Click to remove.");
                } else {
                    description.add(DARK_GRAY + "Left-Click to select.");
                }
                description.add(DARK_GRAY + "Right-Click to preview.");

                kitGUI.add(39, isSelected ? ItemUtilities.enchanted(customDuelsKit.getKit().representative.itemStack) : new ItemStack(customDuelsKit.getKit().representative.itemStack), (player, b, b1) -> {
                    if (b) {
                        this.selectPublicCustomKit(customDuelsKit, true, false);
                    } else {
                        if (isSelected) {
                            this.customDuelKits.remove(customDuelsKit);
                            this.selectedCustomDuelsKit = Miscellaneous.getFirstEntry(this.customDuelKits);
                            this.updateSelectedCustomDuelsKit();
                        } else {
                            this.selectedCustomDuelsKit = customDuelsKit;
                            this.updateSelectedCustomDuelsKit();
                        }
                        this.openDuelsCustomKitsListGui();
                        this.playSuccessSound();
                    }
                }, AQUA + customDuelsKit.getKit().name, description);
            } else {
                kitGUI.add(39, Material.POLISHED_BLACKSTONE_BUTTON, (player, b, b1) -> enterCustomKitCode(), YELLOW + "Enter Code", GRAY + "Enter the code to a custom kit.");
            }
            kitGUI.add(40, Material.EMERALD, (player, b, b1) -> this.viewCustomKitHistory(), GREEN + "History", GRAY + "View your recently played kits.");
            kitGUI.add(41, Material.CLOCK, (player, b, b1) -> this.browsePublicKits(false), GOLD + "Browse Public Kits", GRAY + "Choose a public kit.");


/*
            if (page != 1) {
                kitGUI.add(3 * 9 + 3, Material.FEATHER, (player, b, b2) -> this.openDuelsCustomKitsListGui(page - 1), YELLOW + "Previous");
            }

            if (itemIndex != this.customDuelKits.size()) {
                kitGUI.add(3 * 9 + 5, Material.EMERALD_BLOCK, (player, b, b2) -> this.openDuelsCustomKitsListGui(page + 1), YELLOW + "Next");
            }
*/

            // kitGUI.add(3 * 9 + 4, Material.BARRIER, (player, b, b2) -> this.closeInventory(), RED + "Close");

/*
            kitGUI.add(3 * 9 + 8, Material.PAPER, (player, b, b2) -> {
                if (this.canCreateCustomDuelKit()) {
                    this.openCustomKitGui(null, b);
                } else {
                    this.playErrorSound();
                }
            }, BLUE + "New Custom Kit", !this.canCreateCustomDuelKit() ? RED + "You have reached the maximum kits for your rank! Upgrade to unlock more!" : WHITE + "Create a new kit.");
*/
        }

        kitGUI.fillEmptySpots();

        this.openGui(kitGUI);
    }

    public void browsePublicKits(boolean recent) {
        SmartGUI smartGUI = new SmartGUI("Public Kits", 6, false);

        List<CustomDuelsKit> orderedMaps = Main.getCustomKits(recent, timeFrame);

        for (int i = 0; i < orderedMaps.size(); i++) {
            CustomDuelsKit customDuelsKit = orderedMaps.get(i);
            int plays = customDuelsKit.getPlays(recent ? 0 : timeFrame);

            smartGUI.add(i, customDuelsKit.getRepresentative(), (player, b, b1) -> this.selectPublicCustomKit(customDuelsKit, true, true),
                    WHITE + customDuelsKit.getName(),
                    String.format("%sCreator: %s", GRAY, SmartPlayer.getOrCreate(customDuelsKit.getOwner()).getRealFormattedName()),
                    RelayUtils.format("%s%d Play%s", AQUA, plays, plays == 1 ? "" : "s"),
                    DARK_GRAY + "Click to preview");
        }

        smartGUI.add(45, Material.ARROW, (player, b, b1) -> this.openDuelsCustomKitsListGui(), RED + "Back");
        if (!recent) {
            smartGUI.add(52, Material.CLOCK, (p, b, b1) -> {
                this.cycleTimeFrame();
                this.browsePublicKits(recent);
                this.playClickSoundVariation();
            }, AQUA + this.getTimeFrame(), DARK_GRAY + "Click to cycle.");
        }
        smartGUI.add(53, Material.ANVIL, (player, b, b1) -> {
            this.playClickSoundVariation();
            this.browsePublicKits(!recent);
        }, GOLD + (recent ? "Recent" : "Popular"), DARK_GRAY + "Click to toggle.");
        smartGUI.fillEmptySpots();
        this.openGui(smartGUI);
    }

    private void cycleTimeFrame() {
        this.timeFrame++;
        if (this.timeFrame > 3) {
            this.timeFrame = 0;
        }
    }

    public String getTimeFrame() {
        return switch (this.timeFrame) {
            case 1 -> "Daily";
            case 2 -> "Weekly";
            case 3 -> "Monthly";
            default -> "All Time";
        };
    }

    public void viewCustomKitHistory() {
        SmartGUI smartGUI = new SmartGUI("History", 3, false);
        for (int i = 0; i < this.recentKits.size(); ) {
            CustomDuelsKit customDuelsKit = this.recentKits.get(i);
            if (customDuelsKit != null) {
                int plays = customDuelsKit.getPlays(0);
                smartGUI.add(i++, customDuelsKit.getRepresentative(), (player, b, b1) -> this.selectPublicCustomKit(customDuelsKit, true, false),
                        WHITE + customDuelsKit.getName(),
                        RelayUtils.format("%sCreator: %s", GRAY, SmartPlayer.getOrCreate(customDuelsKit.getOwner()).getRealFormattedName()),
                        RelayUtils.format("%s%d Play%s", AQUA, plays, plays == 1 ? "" : "s"),
                        DARK_GRAY + "Click to preview");
            }
        }

        smartGUI.add(22, Material.ARROW, (player, b, b1) -> this.openDuelsCustomKitsListGui(), RED + "Back");

        smartGUI.fillEmptySpots();

        this.openGui(smartGUI);
    }

    private synchronized void selectPublicCustomKit(CustomDuelsKit customDuelsKit, boolean rightClick, boolean browsing) {
        if (rightClick) {
            this.previewKit = customDuelsKit;

            this.openPreviewKitGUI(true, browsing);
        } else {
            if (this.customDuelKits.contains(customDuelsKit)) {
                this.playErrorSound();
                this.sendErrorMessage("You already have this kit!");
                return;
            }
            this.playSuccessSound();
            this.customDuelKits.remove(this.getNonOwnedKit());
            this.customDuelKits.add(customDuelsKit);
            this.updateCustomKitList();
            this.openDuelsCustomKitsListGui();
        }
    }

    private void enterCustomKitCode() {
        new TextGUI(getPlayer(), "Custom Code", "") {
            @Override
            public void process(ItemStack itemStack) {
                String s = itemStack.getItemMeta().getDisplayName();
                if (!s.isEmpty()) {
                    sendInfoMessage("Downloading map...");
                    Main.async(() -> {
                        CustomDuelsKit duelsKit = CustomDuelsKit.getCustomDuelsKit(s);
                        if (duelsKit != null) {
                            customDuelKits.add(duelsKit);
                            playSuccessSound();
                        } else {
                            playErrorSound();
                            sendErrorMessage("No kit by the code %s%s%s found.", YELLOW, s, RED);
                        }
                    });
                }

                openDuelsCustomKitsListGui();
            }

            @Override
            public void onClose() {
                openDuelsCustomKitsListGui();
            }
        }.openInventory();

    }

    public CustomDuelsKit getNonOwnedKit() {
        for (CustomDuelsKit customDuelKit : this.customDuelKits) {
            if (!customDuelKit.getOwner().equals(this.getUUID())) {
                return customDuelKit;
            }
        }

        if (this.customDuelKits.size() > this.getMaxCustomKits()) {
            return Miscellaneous.getLastEntry(this.customDuelKits);
        }

        return null;
    }

    public static ItemStack getCustomKitRepresentative(int i) {
        Material material;
        switch (i % 11) {
            case 0 -> material = Material.RED_BANNER;
            case 1 -> material = Material.ORANGE_BANNER;
            case 2 -> material = Material.YELLOW_BANNER;
            case 3 -> material = Material.LIME_BANNER;
            case 4 -> material = Material.GREEN_BANNER;
            case 5 -> material = Material.CYAN_BANNER;
            case 6 -> material = Material.LIGHT_BLUE_BANNER;
            case 7 -> material = Material.BLUE_BANNER;
            case 8 -> material = Material.PURPLE_BANNER;
            case 9 -> material = Material.MAGENTA_BANNER;
            case 10 -> material = Material.PINK_BANNER;
            default -> material = Material.WHITE_BANNER;
        }

        return new ItemStack(material);
    }

    public boolean canCreateCustomDuelKit() {
        return this.customDuelKits.size() < this.getMaxCustomKits();
    }

    public void openCustomKitGui(CustomDuelsKit original, boolean rightClick) {
        if (this.isInQueue() && this.queueType == GameType.DUELS_CUSTOM) {
            this.sendErrorMessage("You cannot change your selected kit while queued!");
            this.playErrorSound();
            return;
        }
        EditCustomKitGUI editKitGUI = new EditCustomKitGUI(original, this, this.customDuelKits.size());
        this.openGui(editKitGUI);
/*
            this.getInventory().clear();
            this.closeInventory();
            this.sendSuccessMessage("Open your inventory to begin editing your kit. This is now your selected kit.");
            this.smartPlayer.setGameMode(GameMode.CREATIVE);
            this.selectedCustomDuelsKit = original;
            this.isCreatingCustomKit = true;
            if (original != null) {
                original.getKit().giveItemsToPlayer(this.getPlayer(), true);
            }
 */
    }

    public void openDuelsSettingsGui(CustomDuelsKit kit) {
        GameSettings gameSettings = getDefaultCustomDuelsGameSettings();
        gameSettings.applyFromJson(kit.getGameSettings());

        SmartGUI smartGUI = gameSettings.getGUI(this.getHighestGroup(), false);

        smartGUI.add(0, Material.ARROW, (player, b, b1) -> {
            kit.updateGameSettings(gameSettings);
            this.sendSuccessMessage("Game settings updated.");
            this.saveCustomKit(kit, kit.getId());
            this.openDuelsCustomKitsListGui();
        }, RED + "Back");

        this.openGui(smartGUI);
    }

    public static HubGameSettings getDefaultCustomDuelsGameSettings() {
        HubGameSettings gameSettings = new HubGameSettings();

        gameSettings.addInteger(Group.DEFAULT, "time", Material.CLOCK, BLUE + "Round Duration", "How long each round lasts (minutes).", 20, 1, 60).addDefaultOptions(1, 5, 15, 30, 60);
        gameSettings.addInteger(Group.DEFAULT, "rounds_to_win", Material.GOLD_INGOT, GOLD + "Rounds To Win", "How many rounds are needed to win.", 2, 1, 10).addDefaultOptions(1, 2, 10);
        gameSettings.addBoolean(Group.DEFAULT, "break", Material.IRON_PICKAXE, RED + "Break Blocks", "Players can break blocks", true);
        gameSettings.addBoolean(Group.DEFAULT, "place", Material.COBBLESTONE, GREEN + "Place Blocks", "Players can place blocks.", true);
        gameSettings.addBoolean(Group.DEFAULT, "auto_tnt", Material.TNT, RED + "Auto TNT-Ignite", "TNT is ignited as it is placed.", false);
        gameSettings.addBoolean(Group.DEFAULT, "explosion_griefing", Material.CREEPER_SPAWN_EGG, RED + "Explosion Griefing", "Explosions destroy the terrain.", true);
        gameSettings.addBoolean(Group.DEFAULT, "explosion_damage", Material.TNT_MINECART, RED + "Explosion Damage", "Explosions deal damage.", true);
        gameSettings.addBoolean(Group.DEFAULT, "block_drops", Material.GRASS_BLOCK, ChatColor.GREEN + "Block Drops", "Blocks drop when broken.", true);
        gameSettings.addBoolean(Group.DEFAULT, "round_map_reset", Material.ITEM_FRAME, ChatColor.AQUA + "Round Map Reset", "The map is reset every round.", true);
        gameSettings.addInteger(Group.DEFAULT, "deathmatch_delay", Material.IRON_SWORD, DARK_RED + "Deathmatch Delay", "After this amount of time everyone will gain glowing and the border will close in (minutes).", 15, 0, 60).addDefaultOptions(0, 15, 60);
        gameSettings.addInteger(Group.DEFAULT, "deathmatch_border", Material.RED_STAINED_GLASS, DARK_RED + "Deathmatch Border Size", "The size of the deathmatch border.", 10, 0, 100).addDefaultOptions(0, 10, 100);
        gameSettings.addBoolean(Group.DEFAULT, "durability", Material.IRON_LEGGINGS, YELLOW + "Durability", "Items will take damage as they are used.", true);
        gameSettings.addBoolean(Group.DEFAULT, "crafting", Material.CRAFTING_TABLE, ChatColor.AQUA + "Crafting", "Players can craft.", true);
        gameSettings.addBoolean(Group.DEFAULT, "hunger", Material.COOKED_BEEF, ChatColor.GREEN + "Hunger", "Hunger.", true);
        gameSettings.addInteger(Group.DEFAULT, "health", Material.RED_TERRACOTTA, RED + "Health", "Amount of health players can have.", 20, 1, 200).addDefaultOptions(1, 10, 20, 50, 100, 200);
        gameSettings.addBoolean(Group.DEFAULT, "natural_regen", Material.GOLDEN_APPLE, ChatColor.GOLD + "Natural Regeneration", "Players regenerate health naturally.", true);
        gameSettings.addBoolean(Group.DEFAULT, "fall_damage", Material.IRON_BOOTS, ChatColor.GREEN + "Fall Damage", "Players take damage from falling.", true);
        gameSettings.addBoolean(Group.DEFAULT, "fly", Material.FEATHER, ChatColor.GOLD + "Flight", "Players can fly.", false);
        gameSettings.addCycle(Group.DEFAULT, "gamemode", Material.STONE_PICKAXE, ChatColor.BLUE + "Gamemode", "The gamemode players are in.", 0, "Survival", "Adventure");
        //gameSettings.addCycle(Group.DEFAULT, "weather", Material.SNOWBALL, LIGHT_PURPLE + "Weather", "The weather you play in.", 0, "Clear", "Rain", "Thunder");
        //gameSettings.addCycle(Group.DEFAULT, "world_time", Material.LANTERN, DARK_PURPLE + "Time", "The time you play at.", 1, "Dusk", "Morning", "Noon", "Evening", "Midnight");
        //gameSettings.addBoolean(Group.DEFAULT, "mob_drops", Material.BONE, ChatColor.YELLOW + "Mob Drops", "Mobs drop items.", true);
        gameSettings.addInteger(Group.DEFAULT, "max_build_height", Material.BARRIER, "Max Build Height", "The Maximum height players can build to.", 128, 1, 128).addDefaultOptions(0, 16, 32, 64, 128, 256);
        gameSettings.addBoolean(Group.DEFAULT, "arrow_pickup", Material.ARROW, "Arrow Pickup", "Players can pickup arrows.", true);
        gameSettings.addBoolean(Group.DEFAULT, "drop_items", Material.IRON_INGOT, "Drop Items", "Players drop items upon death.", false);
        gameSettings.addString(Group.DEFAULT, "effects", Material.POTION, LIGHT_PURPLE + "Effects", "Set the effects that will be active during the duel.", PotionEffectType.NIGHT_VISION.getName() + ":-1");

        return gameSettings;
    }

    public SmartPlayer getSmartPlayer() {
        return this.smartPlayer;
    }

    public List<DefinedLobbyCosmetic> getPurchasedLobbyCosmetics() {
        return this.purchasedLobbyCosmetics;
    }

    public List<DefinedLobbyCosmetic> getLobbyCosmetics() {
        return this.lobbyCosmetics;
    }

    public List<DefinedLobbyCosmetic> getActiveLobbyCosmetics() {
        return this.activeLobbyCosmetics;
    }

    public Map<String, Long> getCooldowns() {
        return this.cooldowns;
    }

    public Map<SpecificGameAction, Number> getSpecificStatistics() {
        return this.specificStatistics;
    }

    public List<Kit> getKitConfigurations() {
        return this.kitConfigurations;
    }

    public List<CustomDuelsKit> getCustomDuelKits() {
        return this.customDuelKits;
    }

    public EditCustomKitGUI getLastCustomKitGui() {
        return this.lastEditKitGui;
    }

    public boolean isVanished() {
        return this.isVanished;
    }

    public boolean isHidingOthers() {
        return this.isHidingOthers;
    }

    public int getPoints() {
        return this.points;
    }

    public double getPvpArenaHealth() {
        return this.pvpArenaHealth;
    }

    public void setLastCustomKitGui(EditCustomKitGUI lastEditKitGui) {
        this.lastEditKitGui = lastEditKitGui;
    }

    public void setVanished(boolean vanished) {
        isVanished = vanished;
    }

    public void setHidingOthers(boolean hidingOthers) {
        isHidingOthers = hidingOthers;
    }

    public void setPoints(int points) {
        this.points = points;
    }

    public void setPvpArenaHealth(double pvpArenaHealth) {
        this.pvpArenaHealth = pvpArenaHealth;
    }

    public void setXp(long xp) {
        this.experience = xp;
    }

    public League getLeague(GameType gameType) {
        return League.getLeague(this.eloMap.getOrDefault(gameType, 0L));
    }

    public Location getEyeLocation() {
        return this.smartPlayer.getEyeLocation();
    }

    public Entity rayTraceEntity(double blocks) {
        return this.smartPlayer.rayTraceEntity(blocks);
    }

    public Block rayTraceBlocks(double blocks) {
        return this.smartPlayer.rayTraceBlocks(blocks);
    }

    public double distance(Location location) {
        return this.getLocation().distance(location);
    }

    public void updateQueuePosition(int position, int size, int specificPosition, int specificSize, int typeSize,
                                    boolean serverAvailable) {
        this.queuePosition = position;
        this.queueSize = size;
        this.specificPosition = specificPosition;
        this.specificSize = specificSize;
        this.typeSize = typeSize;
        this.serverAvailable = serverAvailable;
        if (this.queueScoreboard != null) {
            this.queueScoreboard.update();
            if (this.getPlayer().getScoreboard() != this.queueScoreboard.scoreboard) {
                this.queueScoreboard.applyToPlayer(this.getPlayer());
            }
        }
    }

    public void updateQueueScoreboard(GameType gameType, int position, int size, int specificPosition,
                                      int specificSize, int typeSize, boolean fastQueue, boolean serverAvailable, QueueData queueData) {
        this.queuePosition = position;
        this.queueSize = size;
        this.specificPosition = specificPosition;
        this.specificSize = specificSize;
        this.queueType = gameType;
        this.typeSize = typeSize;
        this.serverAvailable = serverAvailable;
        this.queueData = queueData;
        if (this.queueScoreboard == null && this.queueType != null) {
            this.onEnterQueue();
            this.queueScoreboard = new DynamicScoreboard(GREEN + BOLD.toString() + gameType.getPrefix().toUpperCase());
            boolean ranked = gameType.isRankedType();
            this.queueScoreboard.addPreLine();
            if (this.isInParty()) {
                this.queueScoreboard.addDynamicScore(() -> new ComponentBuilder(DARK_AQUA.toString()).append("Party Size: ").append(this.isInParty() ? WHITE.toString() + this.getPartySize() : GRAY + "None"));
                this.queueScoreboard.addBlankSpace();
            }
            this.queueScoreboard.addDynamicScore(this::getPositionLine);
            this.queueScoreboard.addBlankSpace();
            if (ranked) {
                //this.queueScoreboard.addDynamicScore(this::getLeagueLine);
            } else {
                this.queueScoreboard.addScore("%s%s%s", LIGHT_PURPLE, BOLD, this.queueType.getDisplaySuffix().toUpperCase());
            }
            this.queueScoreboard.addScore(fastQueue ? GOLD + "Fast Queue" : AQUA + "Normal Queue");
            this.queueScoreboard.addPostLine();
            this.queueScoreboard.applyToPlayer(this.getPlayer());
        } else {
            this.updateQueuePosition(position, size, specificPosition, specificSize, typeSize, serverAvailable);
        }
    }

    public boolean isLastLeague(GameType g) {
        if (g == null) {
            return false;
        }
        return this.getLeague(g).getNext() == null;
    }

    public ComponentBuilder getLeagueLine(GameType g) {
        return SmartPlayer.getEloComponent(this.eloMap.getOrDefault(g, 0L), true);
    }

    public ComponentBuilder getPositionLine() {
        if (!this.serverAvailable) {
            if (this.specificPosition <= this.queueType.getTotalPlayerCount()) {
                return new ComponentBuilder("%sWaiting for open server", YELLOW);
            } else {
                return new ComponentBuilder("%sPosition: %s%d%s/%d", AQUA, GREEN, this.specificPosition, WHITE, this.specificSize);
            }
        }

        return new ComponentBuilder("%sWaiting for players", YELLOW);
    }

    public void setVelocity(Vector velocity) {
        this.getPlayer().setVelocity(velocity);
    }

    public boolean isSneaking() {
        return this.getPlayer().isSneaking();
    }

    public boolean isSprinting() {
        return this.getPlayer().isSprinting();
    }

    public Number getStatistic(int type, String statisticName) {
        GameAction gameAction = GameAction.getGameAction(statisticName);
        if (this.previousLeaderboardStats.size() < type + 1) {
            return 0;
        }

        if (gameAction != null) {
            return this.previousLeaderboardStats.get(type).getOrDefault(gameAction, 0L);
        }

        return this.previousLeaderboardExtraStats.get(type).getOrDefault(statisticName, 0L);
    }

    public void sendToHell() {
        this.beingSentToHell = true;
        this.sendErrorMessage("You are being sent to hell.");
        Main.scheduleSyncDelayedTask(() -> {
            for (int i = 0; i < 5; i++) {
                int finalI = i;
                Main.scheduleSyncDelayedTask(() -> {
                    double hellValue = this.getHellValue(finalI);
                    this.sendInfoMessage("Sending you to hell at %s%s%s.", RED, hellValue, YELLOW);
                    this.smartPlayer.crash(hellValue);
                    if (finalI == 4) {
                        this.beingSentToHell = false;
                    }
                }, i * 2);
            }
        }, 1);
    }

    private double getHellValue(int i) {
        switch (i) {
            case 0:
                return 0.05;
            case 1:
                return 0.1;
            case 2:
                return 0.25;
            case 3:
                return 0.5;
        }

        return 1;
    }

    public void openPartyModes() {
        SmartGUIUtils.PARTY_MODES.open(this.getPlayer());
    }

    public void showPlayerGui(HubPlayer clicked) {
        this.openGui(clicked.getClickGui(this.smartPlayer));
    }

    public SmartGUI getClickGui(SmartPlayer clicker) {
        SmartGUI smartGUI = new SmartGUI(this.getName(), 3, false);
        if (Main.isGameLobby()) {
            smartGUI.add(0, Material.PAPER, (player, b, b1) -> SmartPlayer.getSmartPlayer(player).openGui(this.smartPlayer.getStatisticsGui(Main.getMainGame())), AQUA + "View Statistics", GRAY + "View statistics for " + this.smartPlayer.getFormattedName() + GRAY + ".");
        }
        smartGUI.add(4, this.smartPlayer.getHead(), (player, b, b1) -> SmartPlayer.getSmartPlayer(player).openGui(this.smartPlayer.getProfileGui(true, SmartPlayer.getSmartPlayer(player))), this.smartPlayer.getFormattedName(), GRAY + "View profile for " + this.smartPlayer.getFormattedName() + GRAY + ".");
        smartGUI.add(21, Material.EMERALD, (player, b, b1) -> HubPlayer.getHubPlayer(player).inviteToParty(this), GOLD + "Invite To Party", GRAY + "Invite " + this.smartPlayer.getFormattedName() + GRAY + " to your party.");
        smartGUI.add(22, Material.IRON_SWORD, ((player, b, b1) -> HubPlayer.getHubPlayer(player).duelPlayer(this.getName())), AQUA + "Send Duel Request", GRAY + "Send " + this.smartPlayer.getFormattedName() + GRAY + " a duel request.");
        smartGUI.add(23, Material.ITEM_FRAME, (player, b, b1) -> HubPlayer.getHubPlayer(player).sendFriendRequest(this), GREEN + "Send Friend Request", GRAY + "Send a friend request to " + this.smartPlayer.getFormattedName() + GRAY + ".");

        smartGUI.fillEmptySpots();

        return this.smartPlayer.getProfileGui(clicker);
    }

    public void inviteToParty(HubPlayer hubPlayer) {
        ServerCommunicator.sendToProxy(Command.OTHER, "partyinvite", this.getUUID(), hubPlayer.getName());
    }

    public void sendFriendRequest(HubPlayer hubPlayer) {
        ServerCommunicator.sendToProxy(Command.OTHER, "friendrequest", this.getUUID(), hubPlayer.getName());
    }

    public void resetPvpArenaHealth() {
        this.setPvpArenaHealth(-1);
    }

    public void onArenaDamage() {
        this.lastArenaDamage = System.currentTimeMillis();
    }

    public void hubCommand() {
        long timeRemaining = 8000 - (System.currentTimeMillis() - this.lastArenaDamage);
        if (this.inArena() && timeRemaining > 0) {
            int secondsRemaining = (int) Math.ceil(timeRemaining / 1000D);
            this.sendErrorMessage("You must wait %ds to exit combat!", secondsRemaining);
        } else {
            ExtraListeners.spawnPlayer(this.getPlayer());
        }
    }

    public boolean inArena() {
        return ExtraListeners.isInArena(this.getLocation());
    }

    public void updatePartyInfo(PartyData partyData) {
        this.partyData = partyData;
    }

    public boolean isInParty() {
        return this.partyData != null;
    }

    public void leaveParty() {
        this.partyData = null;
    }

    public int getPartySize() {
        return this.isInParty() ? Miscellaneous.without(Miscellaneous.with(this.partyData.others, this.partyData.owner), this.getUUID()).size() + 1 : 1;
    }

    public void teleportToParkourCheckpoint() {
        if (this.hasParkour()) {
            this.getParkour().teleportToCheckpoint(this.smartPlayer);
        } else {
            this.sendErrorMessage("shouldn't be possible");
        }
    }

    public boolean hasParkour() {
        return this.smartPlayer.selectedParkour != null;
    }

    public Parkour getParkour() {
        return this.smartPlayer.selectedParkour;
    }

    public void resetParkour() {
        if (this.hasParkour()) {
            this.smartPlayer.restartParkour();
        } else {
            this.sendErrorMessage("what");
        }
    }

    public void exitParkour() {
        if (this.hasParkour()) {
            this.getParkour().exitParkour(this.smartPlayer);
            this.smartPlayer.sendActionBar("%sUse %s/spawn%s to return to spawn.", GREEN, YELLOW, GREEN);
        } else {
            this.sendErrorMessage("no");
        }
    }

    public void duelPlayer(String name, GameType gameType) {
        if (gameType == null) {
            ServerCommunicator.sendToProxy(Command.OTHER, "duelrequest", this.getUUID(), name);
        } else {
            this.selectDuelType(name, gameType);
        }
    }

    public void duelPlayer(String name) {
        this.duelPlayer(name, null);
    }

    public void showDuelPlayerSearch() {
        new TextGUI(this.smartPlayer, "Enter Player to Duel") {
            @Override
            public void process(ItemStack itemStack) {
                duelPlayer(itemStack.getItemMeta().getDisplayName());
                HubPlayer.this.closeInventory();
            }
        }.openInventory();
    }

    public void showDuelGui(String playerName) {
        SmartGUI menu = new SmartGUI("Duel Type", 4, true);
        List<GameType> types = Miscellaneous.getList(GameType.DUELS_CUSTOM, GameType.DUELS_AXE, GameType.DUELS_SWORD, GameType.DUELS_CRYSTAL, GameType.DUELS_NETHERITE_POT, GameType.DUELS_DIAMOND_POT, GameType.DUELS_UHC, GameType.DUELS_BOW, GameType.DUELS_SUMO, GameType.DUELS_INDEPENDENT_CUSTOM, GameType.DUELS_SMP, GameType.DUELS_OP);

        for (int i = 0; i < types.size(); i++) {
            GameType gameType = types.get(i);
            menu.add(9 + i/*13 - types.size() / 2 + i + (i >= 4 ? 1 : 0)*/, gameType.getRepresentative(), (player, b, b1) -> this.selectDuelType(playerName, gameType), AQUA + BOLD.toString() + gameType.getDisplaySuffix().toUpperCase());
        }

        menu.fillEmptySpots();

        this.openGui(menu);
    }

    private void selectDuelType(String playerName, GameType gameType) {
        ServerCommunicator.sendToProxy(Command.OTHER, "duelrequest", this.getUUID(), playerName, gameType);
    }

    public void nickname(String[] args) {
        ServerCommunicator.sendToProxy(Command.OTHER, "nickname", this.getUUID(), Text.toCSV(args));
    }

    public boolean isCreatingCustomKitCreative() {
        return this.isCreatingCustomKitCreative;
    }

    public void showCustomKitEnchant(ItemStack currentItem) {
        this.changeEnchantments(currentItem);
    }

    public void changeEnchantments(ItemStack itemStack) {
        List<Enchantment> applicableEnchantments = Miscellaneous.getList(Enchantment.values());

        applicableEnchantments.removeIf(enchantment -> !enchantment.canEnchantItem(itemStack));

        Miscellaneous.addIfAbsent(applicableEnchantments, Enchantment.DAMAGE_ALL);
        Miscellaneous.addIfAbsent(applicableEnchantments, Enchantment.KNOCKBACK);

        if (applicableEnchantments.isEmpty()) {
            this.playErrorSound();
            this.sendErrorMessage("This item cannot be enchanted.");
        } else {
            this.selectEnchantment(itemStack, applicableEnchantments);
        }
    }

    public void selectEnchantment(ItemStack itemStack, List<Enchantment> options) {
        SmartGUI smartGUI = new SmartGUI("Enchantments", 5, false);

        int itemIndex = 0;

        Map<Enchantment, Integer> kitToIndex = new HashMap<>();

        for (int row = 1; row < 4 && itemIndex < options.size(); row++) {
            for (int i = 1; i < 8 && itemIndex < options.size(); i++) {
                Enchantment enchantment = options.get(itemIndex++);
                final int currentIndex = row * 9 + i;
                kitToIndex.put(enchantment, currentIndex);
            }
        }

        for (Enchantment enchantment : kitToIndex.keySet()) {
            int index = kitToIndex.get(enchantment);
            smartGUI.add(index, ItemUtilities.edit(Material.ENCHANTED_BOOK, itemMeta -> itemMeta.addEnchant(enchantment, 1, true)), (player, b, b2) -> this.enchant(itemStack, enchantment), "");
        }

        smartGUI.add(39, Material.GRAY_STAINED_GLASS_PANE, (player, b, b2) -> this.removeEnchants(itemStack), YELLOW + "Clear Enchants");

        smartGUI.add(41, Material.BARRIER, (player, b, b2) -> this.continueEditingCustomKit(), RED + "Cancel");

        this.openGui(smartGUI);
    }

    private void continueEditingCustomKit() {
        this.reOpeningCustomKit = true;
        this.closeInventory();
        this.sendInfoMessage("Open your inventory to continue editing your kit.");
    }

    public void enchant(ItemStack itemStack, Enchantment enchantment) {
        int max = this.getMaxEnchantLevel(enchantment);

        this.getStringInput("Enchantment (1 - " + max + ")", "0", s -> {
            try {
                int i = Integer.parseInt(s);

                i = Math.max(1, Math.min(max, i));

                ItemStack changed = itemStack.clone();
                int finalI = i;
                ItemUtilities.edit(changed, itemMeta -> itemMeta.addEnchant(enchantment, finalI, true));

                this.updateItem(changed);
            } catch (Exception exception) {
                this.onInvalidInput();
            }

            this.customKitBack();
        });
    }

    private void customKitBack() {
        this.continueEditingCustomKit();
    }

    private void onInvalidInput() {
        this.playErrorSound();
        this.sendErrorMessage("Invalid input!");
    }

    private void updateItem(ItemStack changed) {
        this.getInventory().addItem(changed);
    }

    public int getMaxEnchantLevel(Enchantment enchantment) {
        if (this.isAdministrator()) {
            return 32767;
        }

        if (this.isStaff()) {
            return 16383;
        }

        if (this.isInGroupOrHigher(Group.YOUTUBE)) {
            return 255;
        }

        return enchantment.getMaxLevel();
    }

    public void removeEnchants(ItemStack itemStack) {
        SmartGUI smartGUI = new SmartGUI("Clear Enchants?", 5, false);

        if (itemStack.getItemMeta().getEnchants().isEmpty()) {
            this.playErrorSound();
            this.sendErrorMessage("No enchantments to clear.");
            return;
        }

        smartGUI.add(13, itemStack, null);

        smartGUI.add(30, Material.EMERALD_BLOCK, (player, b, b2) -> {
            ItemStack changed = itemStack.clone();
            ItemUtilities.edit(changed, itemMeta -> {
                for (Enchantment enchantment : itemMeta.getEnchants().keySet()) {
                    itemMeta.removeEnchant(enchantment);
                }
            });
            this.updateItem(changed);
        }, RED + "Confirm", YELLOW + "Remove all enchantments on item.");

        smartGUI.add(32, Material.BARRIER, (player, b, b2) -> this.customKitBack(), YELLOW + "Cancel");

        this.openGui(smartGUI);
    }

    public boolean reOpeningCustomKit() {
        return this.reOpeningCustomKit;
    }

    public void getStringInput(String title, String originalText, Consumer<String> process) {
        new TextGUI(getPlayer(), title, originalText) {
            @Override
            public void process(ItemStack itemStack) {
                process.accept(itemStack.getItemMeta().getDisplayName());
            }

            @Override
            public void onClose() {
                if (isCreatingCustomKitCreative) {
                    customKitBack();
                }
            }
        }.openInventory();
    }

    public void selectLobbyKit(Kit kit) {
        if (this.inCombat()) {
            this.playSuccessSound();
            this.sendSuccessMessage("You will receive your kit when you respawn.");
            this.nextKit = kit;
        } else {
            this.lobbyKit = kit;
            this.playSuccessSoundVariation();
            this.getInventory().clear();
            kit.giveItemsToPlayer(this.getPlayer(), true);
            this.sendSuccessMessage("Changed kit.");
            this.giveSmartItem(8, SmartGUIUtils.KIT_SELECTOR);
            this.giveSmartItem(7, SPAWN);
        }
        this.getExtraData().setString("kit", kit.name.toLowerCase());
        this.smartPlayer.updateProfile(ProfileColumn.EXTRA);
    }

    public void showLobbyKitSelect() {
        SmartGUI kitSelector = new SmartGUI("Kit Selector", 1);

        for (int i = 0; i < KitCommand.KITS.size(); i++) {
            Kit kit = KitCommand.KITS.get(i);
            kitSelector.add(i * 2, kit == this.lobbyKit ? ItemUtilities.enchanted(kit.representative.itemStack) : kit.representative.itemStack, (player, b, b1) -> HubPlayer.getHubPlayer(player).selectLobbyKit(kit), ChatColor.AQUA + ChatColor.BOLD.toString() + kit.name, Miscellaneous.processList(Miscellaneous.getList(kit.description.split(",")), s -> GRAY + s));
            kitSelector.fillEmptySpots();
        }

        this.openGui(kitSelector);
    }

    public boolean inCombat() {
        long timeRemaining = 8000 - (System.currentTimeMillis() - this.lastArenaDamage);
        return this.inArena() && timeRemaining > 0;
    }

    public void reQueue() {
        ServerCommunicator.sendToProxy(Command.OTHER, "requeue", this.getUUID());
    }

    public void enterArena() {
        for (Entity passenger : this.getPlayer().getPassengers()) {
            this.getPlayer().removePassenger(passenger);
        }
        this.sendInfoMessage("Use /spawn to leave the PVP Arena.");
        if (this.hasParkour()) {
            this.teleportToParkourCheckpoint();
        } else {
            this.setPoints(0);
            double health = this.getPvpArenaHealth() == -1 ? 20 : this.getPvpArenaHealth();
            this.getPlayer().setHealth(Math.max(0F, health));
            this.getPlayer().setSaturation(0F);
            this.getPlayer().setUnsaturatedRegenRate(160);
            this.getPlayer().setSaturatedRegenRate(160);
            this.disableAllCosmetics();
            this.givePvPItems();
            this.smartPlayer.setAllowFlight(false);
            this.smartPlayer.setInvulnerable(false);
            this.smartPlayer.setGameMode(GameMode.SURVIVAL);
            if (this.isHidingOthers()) {
                this.showOthers();
            }
            if (this.isVanished()) {
                this.unVanish();
            }
        }
    }

    public void showCustomDuelsQueueGui(boolean other) {
        SmartGUI smartGUI = new SmartGUI("Queue", 1, true);

        smartGUI.add(
                3,
                Material.DIAMOND_AXE,
                (player, rightClick, shift) -> HubPlayer.getHubPlayer(player).queue(GameType.DUELS_CUSTOM, new QueueData().attachData("other", other)),
                ChatColor.AQUA + ChatColor.BOLD.toString() + (other ? "ANOTHER'S" : "YOUR" + " KIT"),
                GRAY + "Play duels with " + (other ? "someone else's" : "your") + " custom kit.");
        smartGUI.add(
                5,
                Material.FEATHER,
                (player, rightClick, shift) -> HubPlayer.getHubPlayer(player).cancel(GameType.DUELS_CUSTOM),
                RED + "Leave Queue",
                GRAY + RelayUtils.format("Leave the %s queue.", GameType.DUELS_CUSTOM.getFullPrettyName()));

        this.openGui(smartGUI);
    }

    public synchronized void saveCustomKit(CustomDuelsKit kit, String originalID) {
        int i = this.customDuelKits.indexOf(Miscellaneous.match(this.customDuelKits, customDuelsKit -> Text.comparablyEquals(customDuelsKit.getId(), originalID)));

        if (i != -1) {
            this.customDuelKits.remove(i);
        }

        this.customDuelKits.removeIf(customDuelsKit -> Text.comparablyEquals(kit.getId(), customDuelsKit.getId()));
        this.smartPlayer.getCustomDuelsKits().removeIf(customDuelsKit -> Text.comparablyEquals(customDuelsKit.getId(), originalID));
        this.smartPlayer.getCustomDuelsKits().removeIf(customDuelsKit -> Text.comparablyEquals(kit.getId(), customDuelsKit.getId()));
        if (i != -1) {
            this.customDuelKits.add(Math.min(i, customDuelKits.size()), kit);
        } else {
            this.customDuelKits.add(kit);
        }
        CustomDuelsKit.insertIntoDatabase(kit);
        this.smartPlayer.getCustomDuelsKits().add(kit);
        this.selectedCustomDuelsKit = kit;
        this.updateSelectedCustomDuelsKit();

        this.updateCustomKitList();
    }

    public void updateSelectedCustomDuelsKit() {
        if (!Main.isDuelsLobby()) {
            return;
        }
        if (this.customDuelKits.isEmpty()) {
            this.selectedCustomDuelsKit = null;
        } else if (this.selectedCustomDuelsKit != null && !this.customDuelKits.contains(this.selectedCustomDuelsKit)) {
            this.selectedCustomDuelsKit = Miscellaneous.getFirstEntry(this.customDuelKits);
        }
        this.getExtraData().setString("selectedCustomKit", this.selectedCustomDuelsKit == null ? null : this.selectedCustomDuelsKit.getId());
        this.smartPlayer.updateProfile(ProfileColumn.EXTRA);
    }

    public static final List<String> BLACKLISTED_ITEMS = Miscellaneous.getList("boat", "redstone", "piston", "button", "pressureplate", "lever", "chest", "jukebox", "shulkerbox", "barrel", "stand", "dropper", "hopper", "dispenser");

    public Kit getInventoryAsKit() {
        List<KitItem> items = new ArrayList<>();
        Inventory inventory = this.getInventory();
        boolean blacklistedItem = false;
        for (int i = 0; i <= 40; i++) {
            if (i < 36 || i > 39) {
                ItemStack itemStack = inventory.getItem(i);
                if (itemStack != null && !itemStack.getType().isAir()) {
                    if (!blacklistedNBT(itemStack)) {
                        if (!Text.comparablyContains(itemStack.getType(), BLACKLISTED_ITEMS.toArray()) || Text.comparablyContains(itemStack.getType(), "chestplate")) {
                            items.add(new KitItem(itemStack, i));
                        } else {
                            blacklistedItem = true;
                        }
                    } else {
                        this.sendErrorMessage("Your item %s had an NBT tag and was removed.", Text.prettify(itemStack.getType()));
                    }
                }
            }
        }
        if (blacklistedItem) {
            this.sendErrorMessage("Your inventory contained one or more blacklisted items which were removed.");
        }
        ItemStack helmetItem = inventory.getItem(39);
        ItemStack chestplateItem = inventory.getItem(38);
        ItemStack leggingsItem = inventory.getItem(37);
        ItemStack bootsItem = inventory.getItem(36);

        KitItem helmet = helmetItem == null ? null : new KitItem(helmetItem);
        KitItem chestplate = chestplateItem == null ? null : new KitItem(chestplateItem);
        KitItem leggings = leggingsItem == null ? null : new KitItem(leggingsItem);
        KitItem boots = bootsItem == null ? null : new KitItem(bootsItem);

        return
                new Kit(UUID.randomUUID().toString(), "Custom Kit.", null, helmet, chestplate, leggings, boots, items.toArray(new KitItem[0]));
    }

    public void onHitTargetWithArrow() {
        if (Main.isDuelsLobby() && this.lobbyKit == Kits.DUELS_LOBBY_ARCHER && this.inArena()) {
            this.getInventory().addItem(new ItemStack(Material.ARROW));
        }
    }

    public void inventoryClose() {
        if (this.isCreatingCustomKitCreative) {
            this.returnToKitEditor();
        }
    }

    public void returnToKitEditor() {
        this.lastEditKitGui.setFromKit(this.getInventoryAsKit());
        this.isCreatingCustomKitCreative = false;
        spawn(false);
        this.smartPlayer.openGui(this.lastEditKitGui);
    }

    public void setCreatingCustomKitCreative(boolean b) {
        this.isCreatingCustomKitCreative = b;
    }

    public List<CustomMap> getCustomMaps() {
        return this.customMaps;
    }

    public void editCustomMap(CustomMap customMap, boolean newMap) {
        this.customMapEdited = customMap;
        this.teleportedToCustomMap = false;
        this.customMapCenter = getAvailableCenter(customMap);
        this.customMapBlocks = new ArrayList<>();
        this.customMapSpawnA = customMap.getSpawnA().clone();
        this.customMapSpawnB = customMap.getSpawnB().clone();
        this.customMapSpawnA.setWorld(this.getWorld());
        this.customMapSpawnB.setWorld(this.getWorld());
        this.customMapVisibility = customMap.isVisible();
        if (newMap) {
            customMap.setName("Map #" + (this.customMaps.size() + 1));
        }
        this.customMapName = customMap.getName();
        this.customMapRepresentative = customMap.getRepresentative();
        this.customMapSize = customMap.getSize();
        this.allCollaborators = customMap.getContributors();
        this.customMapEdited.setCreators(customMap.getCreators());
        this.customMapEdited.setContributors(customMap.getContributors());
        checkCustomMapCenter();
        World world = Main.getWorld();
        this.newCustomMap = newMap;
        if (this.newCustomMap) {
            if (Main.hasWallTemplate()) {
                this.applyTemplate(Main.getWallTemplate());
            } else {
                this.setFloor(Material.BEDROCK);
            }
        } else {
            for (CustomMap.Block block : customMap.getMap()) {
                Vector position = block.getPosition();
                int x = (int) (this.customMapCenter.getX() + position.getX());
                int y = (int) (this.customMapCenter.getY() + position.getY());
                if (y < 0) {
                    Main.error("(Loading Map) CustomMap Block is lower than 0 blocks: %s (%s)", this.customMapEdited.getId(), Text.shortenDouble(position.getY(), 2));
                }
                int z = (int) (this.customMapCenter.getZ() + position.getZ());
                Block worldBlock = world.getBlockAt(x, y, z);
                worldBlock.setType(block.material());
                worldBlock.setBlockData(block.blockData());
                this.customMapBlocks.add(worldBlock);
            }
        }

        this.sendSuccessMessage("Teleported you to your custom map.");

        this.closeInventory();
        this.smartPlayer.clear();
        this.teleportToCustomMap();
        this.smartPlayer.setGameMode(GameMode.CREATIVE);
        this.smartPlayer.setAllowFlight(true);
        this.getPlayer().setFlying(true);
        this.isCreatingCustomMap = true;

        this.giveSmartItem(8, CUSTOM_MAP_MENU);
    }

    public void teleportToCustomMap() {
        if (this.customMapCenter != null) {
            Location toTeleport = PlayerUtilities.getNearestTwoBlockSpaceAbove(this.customMapCenter);

            toTeleport.add(0, 3, 0);

            if (toTeleport.getY() >= 130) {
                toTeleport.setY(5);
            }
            Location location = this.customMapCenter.clone().add(customMapSpawnA.toVector());
            if (!this.inCustomMapBoundingBox(location)) {
                this.teleport(toTeleport);
            } else {
                this.teleport(location);
            }
            Main.synchronous(() -> this.teleportedToCustomMap = true);
            Main.scheduleSyncDelayedTask(() -> this.addPotionEffect(PotionEffectType.NIGHT_VISION), 1);
        }
    }

    public void inviteCollaborator() {
        this.getStringInput("Invite Collaborator", null, s -> {
            HubPlayer hubPlayer = getHubPlayer(SmartPlayer.getPlayerOrNick(s));
            if (hubPlayer != null) {
                this.inviteCollaborator(hubPlayer);
            } else {
                CommandUtilities.noPlayerFound(this.getPlayer(), s);
            }
            this.closeInventory();
        });
    }

    public void showCustomMapMenu() {
        SmartGUI smartGUI = new SmartGUI("Menu", 5, false);

        smartGUI.add(0, Material.LEAD, (p, b, b1) -> this.inviteCollaborator(), GOLD + "Invite Collaborator", GRAY + "Invite a player to help you edit this custom map.");

        smartGUI.add(11, Material.EMERALD_BLOCK, (player, b, b1) -> this.saveCustomMap(true), GREEN + "Save and Exit", GRAY + "Save your custom map and return to the lobby.");
        smartGUI.add(13, Material.NAME_TAG, (player, b, b1) -> this.editCustomMapLabel(false, false), YELLOW + "Label", GRAY + "Change the name and icon of this custom map.");
        smartGUI.add(15, Material.BOOK, (player, b, b1) -> this.shareCustomMap(this.customMapEdited), AQUA + "Share Map", GRAY + "Share your custom map with your friends!");

        smartGUI.add(29, Material.SLIME_BALL, (player, b, b1) -> this.showCustomMapSpawns(), YELLOW + "Spawnpoints", GRAY + "Set where players will spawn on the map.");
        smartGUI.add(30, Material.OAK_SIGN, (player, b, b1) -> this.setCustomMapSize(), GOLD + "Plot Size", GRAY + "Change custom map dimensions.");
        smartGUI.add(31, Material.ITEM_FRAME, (player, b, b1) -> this.chooseCustomMapTemplate(), DARK_AQUA + "Choose Template", GRAY + "Create a custom map from a template.");
        smartGUI.add(32, Material.WHITE_CARPET, (player, b, b1) -> {
            if (b && b1) {
                this.setFloor(Material.AIR);
            }
        }, YELLOW + "Floor", GRAY + "Drag and drop an item here to change the floor block.", GRAY + "Shift-right-click to set floor to air.");

        //smartGUI.add(33, Material.COMPARATOR, (player, b, b1) -> this.showCustomMapSettings(), RED + "Map Settings", GRAY + "Change settings.");
        smartGUI.add(33, Material.TNT, (player, b, b1) -> this.deleteMap(true), RED + "Delete Map", GRAY + "Delete this custom map.");
        smartGUI.fillEmptySpots();

        this.openGui(smartGUI);
    }

    private void shareCustomMap(CustomMap customMap) {
        if (this.enoughSecondsHaveElapsed(Cooldown.SHARE_MAP)) {
            this.updateCooldown(Cooldown.SHARE_MAP);
        } else {
            this.showCooldownWarning(Cooldown.SHARE_MAP);
            return;
        }
        ComponentBuilder componentBuilder = new ComponentBuilder();
        componentBuilder.color(ComponentBuilder.BLUE).line().newLine();
        String code = customMap.getId();
        componentBuilder.copy("/setmap " + code, GOLD + UNDERLINE.toString() + "/setmap " + code, "Click to copy.").newLine();
        componentBuilder.copy(code, GOLD + UNDERLINE.toString() + code, "Click to copy.").newLine();
        componentBuilder.color(ComponentBuilder.GREEN).append("This map has been shared publicly.").newLine();
        componentBuilder.color(ComponentBuilder.BLUE).line();
        this.sendMessage(componentBuilder);
        this.playSuccessSound();
        customMap.setVisible();
        if (customMap.getMap().isEmpty()) {
            this.sendInfoMessage("Your custom map is empty!");
        }
        CustomMap.insertIntoDatabase(customMap);
    }

    private void editCustomMapLabel(boolean autoSave, boolean fromMapList) {
        SmartGUI smartGUI = new SmartGUI("Label", 1, false);

        smartGUI.add(2, Material.NAME_TAG, (player, b, b2) -> this.renameMap(autoSave), AQUA + "Change Name", GRAY + this.customMapEdited.getName());
        smartGUI.add(4, Material.BARRIER, (player, b, b1) -> {
            if (autoSave) {
                if (fromMapList) {
                    this.openDuelsCustomMapsListGui();
                } else {
                    this.selectMap();
                }
            } else {
                this.showCustomMapMenu();
            }
        }, RED + "Back");
        if (autoSave) {
            smartGUI.add(6, this.customMapEdited.getRepresentative(), (player, b, b2) -> this.chooseMapIcon(true), RED + "Kit Icon", GRAY + "Browse items to change your representative.");
        } else {

            smartGUI.add(6, this.customMapEdited.getRepresentative(), (player, b, b2) -> this.showCustomMapMenu(), RED + "Kit Icon", GRAY + "Drop an item here to change your map representative.");
        }

        smartGUI.fillEmptySpots();

        this.openGui(smartGUI);
        this.playClickSound();
    }

    private void selectMap() {
        if (this.lastEditKitGui != null) {
            this.lastEditKitGui.selectMap();
        } else {
            this.closeInventory();
        }
    }

    private void showCustomMapSpawns() {
        SmartGUI spawns = new SmartGUI("Spawns", 3, false);

        spawns.add(12, Material.BLUE_TERRACOTTA, (player, b, b1) -> this.setCustomMapSpawn(this.getLocation(), false), AQUA + "Set Spawn A", GRAY + "Set the first spawn to where you are currently.");

        spawns.add(14, Material.RED_TERRACOTTA, (player, b, b1) -> this.setCustomMapSpawn(this.getLocation(), true), RED + "Set Spawn B", GRAY + "Set the second spawn to where you are currently.");

        spawns.add(0, Material.ARROW, (player, b, b1) -> this.showCustomMapMenu(), RED + "Cancel");

        spawns.fillEmptySpots();

        this.openGui(spawns);
    }

    private void chooseCustomMapTemplate() {
        SmartGUI presetGUI = new SmartGUI("Templates", this.isAdministrator() ? 6 : 3, false);

        List<String> templateNames = Miscellaneous.getList("walled", "island", "cave", "lake", "bedrock");

        for (int i = 0; i < 5; i++) {
            String name = templateNames.get(i) + "template";
            CustomMap template = Miscellaneous.match(Main.CUSTOM_MAP_TEMPLATES, customMap -> customMap.getId().equalsIgnoreCase(name));

            if (template != null) {
                presetGUI.add(11 + i, template.getRepresentative(), (player, b, b1) -> this.confirmTemplate(template), template.getName());
            }
        }

        if (this.isAdministrator()) {
            int index = 27;

            for (CustomMap template : Main.CUSTOM_MAP_TEMPLATES) {
                if (!Text.comparablyContains(template.getId(), "template")) {
                    presetGUI.add(index++, template.getRepresentative(), (player, b, b1) -> this.confirmTemplate(template), template.getName());
                }
            }
        }

        presetGUI.add(0, Material.ARROW, (player, b, b1) -> this.showCustomMapMenu(), RED + "Cancel");

        presetGUI.fillEmptySpots();
        this.openGui(presetGUI);
    }

    public void applyTemplate(CustomMap customMap) {
        this.closeInventory();
        for (Block customMapBlock : this.getCustomMapBlocks(this.customMapSize)) {
            customMapBlock.setType(Material.AIR);
        }
        this.sendSuccessMessage("Applied %s%s%s template to your custom map.", YELLOW, customMap.getName(), GREEN);
        this.playSuccessSound();
        this.customMapBlocks.clear();
        this.customMapSpawnA = customMap.getSpawnA();
        this.customMapSpawnB = customMap.getSpawnB();
        this.customMapSpawnA.setWorld(this.getWorld());
        this.customMapSpawnB.setWorld(this.getWorld());
        this.customMapRepresentative = customMap.getRepresentative();
        this.teleport(this.customMapCenter.clone().add(0, 5, 0).add(this.customMapSpawnA.toVector()));
        for (HubPlayer mapCollaborator : this.mapCollaborators) {
            mapCollaborator.teleport(this.customMapCenter.clone().add(0, 5, 0).add(this.customMapSpawnA.toVector()));
        }
        this.customMapSize = Math.max(this.customMapSize, customMap.getSize());
        checkCustomMapCenter();
        World world = Main.getWorld();
        for (CustomMap.Block block : customMap.getMap()) {
            Vector position = block.getPosition();
            int x = (int) (this.customMapCenter.getX() + position.getX());
            int y = (int) (this.customMapCenter.getY() + position.getY());
            int z = (int) (this.customMapCenter.getZ() + position.getZ());
            Block worldBlock = world.getBlockAt(x, y, z);
            worldBlock.setType(block.material());
            worldBlock.setBlockData(block.blockData());
            this.customMapBlocks.add(worldBlock);
        }
    }

    private void confirmTemplate(CustomMap template) {
        SmartGUI confirm = new SmartGUI("Confirm", 1, false);

        confirm.add(3, Material.GREEN_TERRACOTTA, (player, b, b1) -> this.applyTemplate(template), GREEN + "Confirm", GRAY + "Delete your current map and replace it with this template.");

        confirm.add(5, Material.RED_TERRACOTTA, (player, b, b1) -> this.chooseCustomMapTemplate(), RED + "Cancel", GRAY + "Cancel and return to menu.");

        confirm.fillEmptySpots();

        this.openGui(confirm);
    }

    private void guideInfo() {
    }

    public void sendSetSpawnMessage() {
        this.sendInfoMessage("Use /setspawn [id] to update your spawnpoints!");
    }

    public void setFloor(Material material) {
        World world = this.customMapCenter.getWorld();
        if (material == Material.BEDROCK || (material.isBlock() && ((material.isSolid() && material.isCollidable()) || material == Material.AIR) && !material.hasGravity() && !Text.comparablyContains(material, "door", "dripstone", "bed", "cact", "enchanting"))) {
            int startX = this.getCustomMapMin().getBlockX();
            int startZ = this.getCustomMapMin().getBlockZ();
            int endX = this.getCustomMapMax().getBlockX();
            int endZ = this.getCustomMapMax().getBlockZ();

            for (int x = startX; x < endX; x++) {
                for (int z = startZ; z < endZ; z++) {
                    Block block = world.getBlockAt(x + 1, CUSTOM_MAP_Y, z + 1);
                    if (!this.collidesWithCustomSpawn(block)) {
                        block.setType(material);
                    }
                    Miscellaneous.addIfAbsent(this.customMapBlocks, block);
                }
            }
            this.playSuccessSound();
            this.sendSuccessMessage("Updated floor.");
        } else {
            this.sendErrorMessage("That is not a valid block!");
            this.playErrorSound();
        }
    }

    public List<Block> getCustomMapBlocks() {
        return this.getCustomMapBlocks(this.customMapSize);
    }

    public List<Block> getCustomMapBlocks(int size) {
        List<Block> blocks = new ArrayList<>();
        World world = this.customMapCenter.getWorld();
        int startX = this.getCustomMapMin(size).getBlockX();
        int startZ = this.getCustomMapMin(size).getBlockZ();
        int endX = this.getCustomMapMax(size).getBlockX();
        int endZ = this.getCustomMapMax(size).getBlockZ();

        for (int x = startX; x < endX; x++) {
            for (int z = startZ; z < endZ; z++) {
                for (int y = CUSTOM_MAP_Y; y < MAX_CUSTOM_MAP_Y; y++) {
                    Block block = world.getBlockAt(x + 1, y, z + 1);
                    if (!block.getType().isAir()) {
                        blocks.add(block);
                    }
                }
            }
        }

        return blocks;
    }

    private void setCustomMapHeight() {
        this.getStringInput("Max Height", String.valueOf(this.customMapHeight), s -> {
            try {
                int val = Integer.parseInt(s);
                val = Math.max(1, Math.min(val, 100));
                this.customMapHeight = val;
                this.playSuccessSound();
            } catch (Exception e) {
                this.onInvalidInput();
            }
            this.closeInventory();
        });
    }

    private void deleteMap(boolean fromEditMap) {
        SmartGUI confirm = new SmartGUI("Confirm", 1, true);
        confirm.add(3, Material.GREEN_TERRACOTTA, ((player, b, b1) -> {
            this.isCreatingCustomMap = false;
            this.customMaps.removeIf(customMap -> customMap.getId().equals(this.customMapEdited.getId()));
            this.smartPlayer.getCustomMaps().removeIf(customMap -> customMap.getId().equals(this.customMapEdited.getId()));
            for (CustomDuelsKit customDuelKit : Miscellaneous.getList(this.customDuelKits)) {
                if (customDuelKit.getMaps().contains(this.customMapEdited.getId())) {
                    customDuelKit.getMaps().clear();
                    customDuelKit.getMaps().add(DuelMap.ARENA.getCodeName());
                    this.saveCustomKit(customDuelKit, customDuelKit.getId());
                }
            }
            this.updateCustomMapList();
            this.playSuccessSound();
            this.sendSuccessMessage("Custom map deleted.");
            if (fromEditMap) {
                this.spawn(true);
            } else {
                if (this.lastEditKitGui != null) {

                    this.openCustomKitGui(this.lastEditKitGui.customDuelsKit, false);
                }
            }
        }), RED + "Confirm", GRAY + "Delete this map permanently.");

        confirm.add(5, Material.RED_TERRACOTTA, ((player, b, b1) -> {
            if (fromEditMap) {
                this.closeInventory();
            } else {
                this.showCustomMapMenu();
            }
        }), YELLOW + "Cancel");

        hasNoMaps = this.customMaps.isEmpty();

        this.openGui(confirm);
    }

    private synchronized void updateCustomMapList() {
        if (!Main.isDuelsLobby() || !Miscellaneous.enoughSecondsHavePassed(this.smartPlayer.getConnectTime(), 5)) {
            return;
        }
        if (this.customMaps.isEmpty() && !hasNoMaps) {
            return;
        }

        this.smartPlayer.updateGamePlayerDataDatabase(GamePlayerDataColumn.DUELS_CUSTOM_MAPS, Text.toCSV(Miscellaneous.processList(this.customMaps, CustomMap::getId)));
    }

    public synchronized void updateCustomKitList() {
        if (!Main.isDuelsLobby() || !Miscellaneous.enoughSecondsHavePassed(this.smartPlayer.getConnectTime(), 5)) {
            return;
        }
        if (this.customDuelKits.isEmpty() && !hasNoKits) {
            return;
        }
        List<CustomDuelsKit> copy = new ArrayList<>();
        for (CustomDuelsKit customDuelKit : this.customDuelKits) {
            Miscellaneous.addIfAbsent(copy, customDuelKit);
        }
        this.customDuelKits.clear();
        this.customDuelKits.addAll(copy);
        this.smartPlayer.updateGamePlayerDataDatabase(GamePlayerDataColumn.DUELS_CUSTOM_KITS, Text.toCSV(Miscellaneous.processList(this.customDuelKits, CustomDuelsKit::getId)));
    }

    private void chooseMapIcon(boolean autoSave) {
        this.showCategorySelect(autoSave);
    }

    public void showCategorySelect(boolean autoSave) {
        SmartGUI smartGUI = new SmartGUI("Categories", 5, false);

        List<EditCustomKitGUI.ItemCategory> itemCategories = Miscellaneous.getList(EditCustomKitGUI.ItemCategory.values());

        smartGUI.add(31, Material.BARRIER, (player, b, b2) -> {
            if (autoSave) {
                this.editSocialSettings(this.customMapEdited, true);
            } else {
                this.showCustomMapMenu();
            }
        }, RED + "Cancel");

        if (itemCategories.size() % 2 == 0) {
            int startingIndex = 13 - itemCategories.size() / 2 + 1;
            for (int i = 0; i < itemCategories.size() - 1; i++) {
                EditCustomKitGUI.ItemCategory itemCategory = itemCategories.get(i);
                smartGUI.add(startingIndex + i, itemCategory.representative, (player, b, b2) -> this.showItemCategorySelect(itemCategory, autoSave), AQUA + itemCategory.name);
            }

            int i = itemCategories.size() - 1;
            EditCustomKitGUI.ItemCategory itemCategory = itemCategories.get(i);
            smartGUI.add(22, itemCategory.representative, (player, b, b2) -> this.showItemCategorySelect(itemCategory, autoSave), AQUA + itemCategory.name);
        } else {
            int startingIndex = 13 - itemCategories.size() / 2;
            for (int i = 0; i < itemCategories.size(); i++) {
                EditCustomKitGUI.ItemCategory itemCategory = itemCategories.get(i);
                smartGUI.add(startingIndex + i, itemCategory.representative, (player, b, b2) -> this.showItemCategorySelect(itemCategory, autoSave), AQUA + itemCategory.name);
            }
        }

        this.openGui(smartGUI);
    }

    public void showItemCategorySelect(EditCustomKitGUI.ItemCategory itemCategory, boolean autoSave) {
        if (itemCategory == EditCustomKitGUI.ItemCategory.POTION) {
            this.showPotionSelect(autoSave);
        } else {
            List<KitItem> options = getKitItemsForCategory(itemCategory);

            this.showItemSelect(options, itemCategory, autoSave);
        }
    }

    public void showItemSelect(List<KitItem> options, EditCustomKitGUI.ItemCategory itemCategory, boolean autoSave) {
        SmartGUI smartGUI = new SmartGUI("Select", 6, false);

        smartGUI.add(49, Material.BARRIER, (player, b, b2) -> this.showCategorySelect(autoSave), RED + "Cancel");

        for (int index = 0; index < options.size(); index++) {
            KitItem kitItem = options.get(index);
            Material material = kitItem.itemStack.getType();
            if (kitItem.itemStack.getType() != Material.AIR) {
                if (index < 54) {
                    smartGUI.add(index, kitItem.itemStack, (player, b, b2) -> this.setCustomMapRepresentative(kitItem.itemStack.getType(), autoSave));
                } else {
                    Main.warn("Invalid index! (i = %d, index = %d)!", index, index);
                }
            }
        }

        this.openGui(smartGUI);
    }

    public void setCustomMapRepresentative(Material type, boolean autoSave) {
        this.customMapRepresentative = type;
        this.customMapEdited.setRepresentative(type);
        this.playSuccessSound();
        this.sendSuccessMessage("Updated custom map representative.");
        if (autoSave) {
            this.saveCustomMap(false);
        } else {
            this.closeInventory();
        }
    }

    public void showPotionSelect(boolean autoSave) {
        List<KitItem.PotionKitItem> options = new ArrayList<>();

        for (PotionEffectType value : PotionEffectType.values()) {
            if (PotionType.getByEffect(value) != null) {
                options.add(new KitItem.PotionKitItem(value, Material.POTION, false, false, -1));
            }
        }

        SmartGUI smartGUI = new SmartGUI("Select", 6, false);

        smartGUI.add(40, Material.BARRIER, (player, b, b2) -> {
            if (autoSave) {
                this.editSocialSettings(this.customMapEdited, true);
            } else {
                this.showCustomMapMenu();
            }
        }, RED + "Cancel");

        int rowLength = 7;

        for (int i = 0; i < options.size(); i++) {
            // i = 7
            int row = i / rowLength; // 1
            int positionInRow = i % rowLength; // 0
            int remainingAfterLength = options.size() % rowLength; // 1
            int reduce = (i + 1) < (9 * options.size() / rowLength) ? rowLength / 2 : ((remainingAfterLength == 0 ? rowLength : remainingAfterLength) / 2);
            // 15 - 7 > 7 + 1 ? 7 / 2 : ((1 == 1 ? 7 : remainingAfterLength) / 2)
            int startingPositionInRow = 13 + (9 * row) - reduce;
            int index = startingPositionInRow + positionInRow;

            KitItem.PotionKitItem potionKitItem = options.get(i);
            if (index < 54) {
                PotionMeta potionMeta = (PotionMeta) potionKitItem.itemStack.getItemMeta();
                if (potionMeta.getCustomEffects().isEmpty()) {
                    smartGUI.add(index, potionKitItem.itemStack, (player, b, b2) -> this.showPotionSelect(((PotionMeta) potionKitItem.itemStack.getItemMeta()).getBasePotionData().getType().getEffectType(), autoSave));
                } else {
                    smartGUI.add(index, potionKitItem.itemStack, (player, b, b2) -> this.showPotionSelect(((PotionMeta) potionKitItem.itemStack.getItemMeta()).getCustomEffects().get(0).getType(), autoSave));
                }
            } else {
                Main.warn("Invalid index! (i = %d; index = %d)", i, index);
            }
        }

        this.openGui(smartGUI);
    }

    public void showPotionSelect(PotionEffectType type, boolean autoSave) {
        List<KitItem> options = new ArrayList<>();

        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 2; j++) {
                for (int k = 0; k < 2; k++) {
                    boolean extended = j == 1;
                    boolean upgraded = k == 1;
                    Material material = switch (i) {
                        case 0 -> Material.POTION;
                        case 1 -> Material.SPLASH_POTION;
                        default -> Material.LINGERING_POTION;
                    };
                    PotionType potionType = PotionType.getByEffect(type);
                    if (potionType != null) {
                        if (!((!potionType.isExtendable() && extended) || (!potionType.isUpgradeable() && upgraded) || (upgraded && extended))) {
                            options.add(new KitItem.PotionKitItem(type, material, extended, upgraded, -1));
                        }
                    } else {
                        Main.error("Potion Type is null for %s", type);
                    }
                }
            }
        }

        this.showItemSelect(options, EditCustomKitGUI.ItemCategory.POTION, autoSave);
    }

    public void showItemSelect(List<Material> options, boolean autoSave) {
        int rowsize = 9;
        int totalRows = (int) (Math.ceil(options.size() / (double) rowsize)) + 1;
        SmartGUI smartGUI = new SmartGUI("Select", totalRows, false);

        smartGUI.add((totalRows - 1) * 9 + 4, Material.BARRIER, (player, b, b2) -> this.showCustomMapMenu(), RED + "Cancel");

        for (int i = 0; i < options.size(); i++) {
            Material material = options.get(i);
            if (material != null) {
                smartGUI.add(i, material, ((player, b, b1) -> this.setCustomMapRepresentative(material, autoSave)));
            }
        }

        this.openGui(smartGUI);
    }

    public static List<KitItem> turnIntoKitItems(Material... materials) {
        return Miscellaneous.processList(Miscellaneous.getList(materials), KitItem::new);
    }

    private void renameMap(boolean autoSave) {
        this.getStringInput("Rename...", this.customMapName, s -> {
            if (s.isBlank() || s.length() > 32 || !SwearUtils.canSendMessageAtLevel(s, ChatFilter.HIGH)) {
                this.playErrorSound();
                this.sendErrorMessage("Invalid name!");

            } else {
                if (this.isInGroupOrHigher(Group.CHAMPION)) {
                    s = ChatColor.translateAlternateColorCodes('&', s);
                }
                this.customMapName = s;
                this.playSuccessSound();
                this.sendSuccessMessage("Your custom map has been renamed.");
                if (autoSave) {
                    this.saveCustomMap(false);
                } else {
                    this.closeInventory();
                }
            }
        });
    }

    private void getPlayerHead() {
        this.getStringInput("Player Head", "Enter player name...", s -> {
            if (s.length() > 2 && s.length() < 17) {
                ItemStack itemStack = new ItemStack(Material.PLAYER_HEAD);
                SkullMeta skullMeta = (SkullMeta) itemStack.getItemMeta();
                UUID uuid = RelayUtils.getUUID(s);
                if (uuid == null) {
                    this.playErrorSound();
                    this.closeInventory();
                    this.sendErrorMessage("Could not find a player by the name of %s%s%s.", YELLOW, s, RED);
                } else {
                    GameProfile gameProfile = new GameProfile(uuid, s);
                    String[] textures = RelayUtils.getTextureFromUUID(this.getUUID());
                    if (textures != null) {
                        gameProfile.getProperties().put("textures", new Property(textures[0], textures[1]));
                    }
                    OfflinePlayer offlinePlayer = ((CraftServer) Bukkit.getServer()).getOfflinePlayer(gameProfile);
                    skullMeta.setOwningPlayer(offlinePlayer);
                    itemStack.setItemMeta(skullMeta);
                    this.closeInventory();
                    this.getInventory().addItem(itemStack);
                    this.playSuccessSound();
                }
            } else {
                this.playErrorSound();
                this.closeInventory();
            }
        });
    }

    private synchronized void saveCustomMap(boolean fromMapEditor) {
        if (fromMapEditor) {
            this.customMapEdited.setMap(Miscellaneous.processList(this.getCustomMapBlocks(), block -> {
                Vector val = block.getLocation().toVector().subtract(getCustomMapSaveCenter());
                CustomMap.Block b = new CustomMap.Block((float) val.getX(), (float) val.getY(), (float) val.getZ(), block.getType(), block.getBlockData());
                if (b.getPosition().getY() < 0) {
                    Main.error("(Saving) Custommap block is lower than 0: %s | %s | (Block: %s Center: %s)",
                            Text.shortenDouble(b.getPosition().getY(), 2),
                            this.customMapEdited.getId(),
                            block.getLocation().toVector(),
                            getCustomMapSaveCenter());
                }
                return b;
            }));
            this.customMapEdited.setSpawnA(this.customMapSpawnA);
            this.customMapEdited.setSpawnB(this.customMapSpawnB);
            this.customMapEdited.setContributors(Miscellaneous.with(this.customMapEdited.getContributors(), this.allCollaborators));
            for (HubPlayer mapCollaborator : this.mapCollaborators) {
                mapCollaborator.isCreatingCustomMap = false;
                mapCollaborator.sendInfoMessage("The owner has finished editing their custom map. You have been sent back to spawn.");
                mapCollaborator.spawn(true);
            }
            this.mapCollaborators.clear();
            this.allCollaborators.clear();
        }
        this.customMapEdited.setRepresentative(this.customMapRepresentative);
        this.customMapEdited.setName(this.customMapName);
        // ignore publishing for now, just overwrite ig
        int index = this.customMaps.size();
        for (int i = 0; i < this.customMaps.size(); i++) {
            CustomMap customMap = this.customMaps.get(i);
            if (customMap.getId().equalsIgnoreCase(this.customMapEdited.getId())) {
                index = i;
                break;
            }
        }
        this.customMaps.removeIf(customMap -> customMap.getId().equalsIgnoreCase(this.customMapEdited.getId()));
        this.smartPlayer.getCustomMaps().removeIf(customMap -> customMap.getId().equalsIgnoreCase(this.customMapEdited.getId()));
        if (this.customMapEdited.isVisible()) {
            this.customMapEdited.getNewId();
            this.customMapEdited.hide();
        }
        this.customMaps.add(index, this.customMapEdited);
        this.smartPlayer.getCustomMaps().add(this.customMapEdited);
        this.updateCustomMapList();
        CustomMap.insertIntoDatabase(this.customMapEdited);
        if (customMapEdited.getMap().isEmpty()) {
            this.sendInfoMessage("Your custom map is empty!");
        }
        this.sendSuccessMessage("Custom map saved.");
        this.playSuccessSound();
        this.isCreatingCustomMap = false;
        for (Block customMapBlock : this.getCustomMapBlocks(this.customMapSize)) {
            customMapBlock.setType(Material.AIR);
        }
        CUSTOM_MAP_CENTERS.remove(this.customMapCenter);

        this.customMapBlocks.clear();
        if (fromMapEditor) {
            this.spawn(true);
            this.openGui(this.lastEditKitGui);
        }
    }

    private Vector getCustomMapSaveCenter() {
        return this.customMapCenter.toVector().setY(CUSTOM_MAP_Y);
    }

    public int getMaxCustomMapSize() {
        if (this.isAdministrator()) {
            return 500;
        }

        if (this.isInGroupOrHigher(Group.CHAMPION)) {
            return new int[]{35, 100, 125, 150, 175, 200, 225, 250, 275, 300}[Math.min(this.smartPlayer.getChampionTier(), 9)];
        }

        return 35;
    }

    public void setCustomMapSize() {
        if (this.isInGroupOrHigher(Group.CHAMPION)) {
            this.getStringInput("Map Size", String.valueOf(this.customMapSize), s -> {
                try {
                    int oldSize = this.customMapSize;
                    int val = Integer.parseInt(s);
                    val = Math.max(2, Math.min(val, this.getMaxCustomMapSize()));
                    if (oldSize > val) {
                        SmartGUI smartGUI = new SmartGUI("Confirm", 1, true);

                        int finalVal = val;
                        smartGUI.add(3, Material.GREEN_TERRACOTTA, (player, b, b1) -> {
                            this.customMapSize = finalVal;
                            for (HubPlayer mapCollaborator : this.mapCollaborators) {
                                mapCollaborator.customMapSize = this.customMapSize;
                                mapCollaborator.sendInfoMessage("The map size has been updated.");
                            }
                            this.playSuccessSound();
                            this.updateMap(oldSize);
                        }, RED + "Confirm", GRAY + "Blocks outside of your new size will be deleted.");

                        smartGUI.add(5, Material.RED_TERRACOTTA, YELLOW + "Cancel");

                        smartGUI.fillEmptySpots();
                        this.openGui(smartGUI);
                    } else {
                        this.customMapSize = val;
                        this.playSuccessSound();
                        this.updateMap(oldSize);
                    }
                } catch (Exception e) {
                    this.onInvalidInput();
                }
                this.closeInventory();
            });
        } else {
            this.sendChampionPurchaseMessage();
        }
    }

    private void updateMap(int oldSize) {
        List<Block> customMapBlocks = this.getCustomMapBlocks();
        for (Block customMapBlock : this.getCustomMapBlocks(oldSize + 2)) {
            if (!customMapBlocks.contains(customMapBlock)) {
                customMapBlock.setType(Material.AIR);
            }
        }
        boolean odd = this.customMapSize % 2 == 1;
        double halfSize = this.customMapSize / 4D;
        if (!this.customMapSpawnA.toVector().isInAABB(this.getCustomMapMin().toVector().setY(-50), this.getCustomMapMax().toVector().setY(320))) {
            this.customMapSpawnA = new Location(this.getWorld(), halfSize + (odd ? 0.75 : 0.5), CUSTOM_MAP_Y + 1, 0 + (odd ? 1 : 1));
            for (HubPlayer mapCollaborator : this.mapCollaborators) {
                mapCollaborator.customMapSpawnA = this.customMapSpawnA;
            }
            this.customMapSpawnA.setYaw(90);
        }
        if (!this.customMapSpawnB.toVector().isInAABB(this.getCustomMapMin().toVector().setY(-50), this.getCustomMapMax().toVector().setY(320))) {
            this.customMapSpawnB = new Location(this.getWorld(), -halfSize + (odd ? 0.75 : 0.5), CUSTOM_MAP_Y + 1, 0 + (odd ? 1 : 1));
            this.customMapSpawnB.setYaw(-90);
        }

        this.customMapBlocks.removeIf(block -> block.getType() == Material.AIR);
        //this.setFloor(this.customMapCenter.getBlock().getType());
        this.sendSuccessMessage("Your map size has been updated.");
        this.sendSetSpawnMessage();
    }

    public boolean inCustomMapBoundingBox(Location to) {
        if (this.customMapCenter != null) {
            Vector min = this.customMapCenter.toVector().subtract(new Vector(this.customMapSize / 2 + 20, 0, this.customMapSize / 2 + 20)).setY(CUSTOM_MAP_Y - 20);
            Vector max = this.customMapCenter.toVector().add(new Vector(this.customMapSize / 2 + 20, 0, this.customMapSize / 2 + 20)).setY(330);

            return to.toVector().isInAABB(min, max);
        }

        return true;
    }

    public Location getCustomMapMin(int customMapSize) {
        if (customMapSize % 2 == 1) {
            return this.customMapCenter.clone().subtract((double) customMapSize / 2, CUSTOM_MAP_Y, (double) customMapSize / 2);
        }

        return this.customMapCenter.clone().subtract((double) customMapSize / 2 + 0.5, CUSTOM_MAP_Y, (double) customMapSize / 2 + 0.5);
    }

    public Location getCustomMapMax(int customMapSize) {
        if (customMapSize % 2 == 1) {
            return this.customMapCenter.clone().add((double) customMapSize / 2, CUSTOM_MAP_Y, (double) customMapSize / 2);
        }

        return this.customMapCenter.clone().add((double) customMapSize / 2 - 0.5, CUSTOM_MAP_Y, (double) customMapSize / 2 - 0.5);
    }

    private static final int CUSTOM_MAP_Y = 0;

    private static final int MAX_CUSTOM_MAP_Y = CUSTOM_MAP_Y + 128;

    public Location getCustomMapMin() {
        return this.getCustomMapMin(this.customMapSize);
    }

    public Location getCustomMapMax() {
        return this.getCustomMapMax(this.customMapSize);
    }

    public boolean isCreatingCustomMap() {
        return this.isCreatingCustomMap;
    }

    public void onBreakBlock(BlockBreakEvent event) {
        if (!event.getBlock().getLocation().toVector().isInAABB(this.getCustomMapMin().add(1, 0, 1).toVector().setY(CUSTOM_MAP_Y), this.getCustomMapMax().toVector().setY(MAX_CUSTOM_MAP_Y))) {
            event.setCancelled(true);
            this.sendErrorMessage("You cannot break blocks outside of your map dimensions!");
            this.playErrorSound();
        } else if (this.collidesWithCustomSpawn(event.getBlock())) {
            event.setCancelled(true);
            this.sendErrorMessage("You cannot break blocks too close to your spawns!");
            this.playErrorSound();
        } else {
            if (this.isCreatingOwnedCustomMap()) {
                this.customMapBlocks.remove(event.getBlock());
            } else {
                this.getCollaborationOwner().customMapBlocks.remove(event.getBlock());
            }
        }
    }

    public boolean collidesWithCustomSpawn(Block block) {
        for (Location location : Miscellaneous.getList(this.customMapSpawnA, this.customMapSpawnB)) {
            for (int i = -1; i < 2; i++) {
                Block block1 = this.customMapCenter.clone().add(location.toVector()).add(0, i, 0).getBlock();
                if (block.equals(block1)) {
                    Main.warn("collieded with block");
                    return true;
                }
            }
        }

        return false;
    }

    public void renderCustomMapBorder() {
        Location center = this.customMapCenter.clone();
        double sizeX = this.customMapSize + 1;
        double sizeZ = this.customMapSize + 1;
        double height = MAX_CUSTOM_MAP_Y;
        int particleCountX = (int) (sizeX / 6);
        int particleCountZ = (int) (sizeZ / 6);
        int heightCount = (int) (height / 3);
        double increase = this.customMapSize % 2 == 1 ? 1 : 0.5;
        double xMin = center.getX() - sizeX / 2.0 + increase;
        double zMin = center.getZ() - sizeZ / 2.0 + increase;
        double xMax = center.getX() + sizeX / 2.0 + increase;
        double zMax = center.getZ() + sizeZ / 2.0 + increase;
        double yMin = CUSTOM_MAP_Y;
        double yStep = height / (double) heightCount;
        double xStep = sizeX / particleCountX;
        double zStep = sizeZ / particleCountZ;

        for (int j = 0; j < heightCount; j++) {
            double y = yMin + j * yStep;
            // Spawn particles along the top edge
            for (int i = 0; i < particleCountX; i++) {
                double x = xMin + i * xStep;
                spawnWorldParticle(new Location(center.getWorld(), x, y, zMin));
                spawnWorldParticle(new Location(center.getWorld(), x, y, zMax));
            }

            // Spawn particles along the side edges
            for (int i = 0; i < particleCountZ + 1; i++) {
                double z = zMin + i * zStep;
                spawnWorldParticle(new Location(center.getWorld(), xMin, y, z));
                spawnWorldParticle(new Location(center.getWorld(), xMax, y, z));
            }
        }

        // Spawn Particles along the top and bottom side
        for (int i = 0; i < particleCountX + 1; i++) {
            double x = xMin + i * xStep;
            for (int j = 0; j < particleCountZ + 1; j++) {
                double z = zMin + j * zStep;
                spawnWorldParticle(new Location(center.getWorld(), x, MAX_CUSTOM_MAP_Y, z));
                spawnWorldParticle(new Location(center.getWorld(), x, yMin, z));
            }
        }
    }

    private void spawnWorldParticle(Location location) {
        float speed = 1000 / 0.25F;
        long dif = (long) (location.getX() / 10 - location.getY() / 10);
        long l = System.currentTimeMillis() - dif;
        boolean chroma = (this.isStaff() && this.isSprinting());
        java.awt.Color chromaColor = java.awt.Color.getHSBColor((l % (int) speed) / speed, 0.8F, 0.8F);
        location.getWorld().spawnParticle(Particle.REDSTONE, location, 1, 0, 0, 0, 0, new Particle.DustOptions(Color.RED, 1));
    }

    public static boolean blacklistedNBT(ItemStack itemStack) {
        net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(itemStack);

        if (Text.comparablyEquals(itemStack.getType(), "chestplate", "leggings", "helmet", "boots")) {
            return false;
        }

        if (nms.t()) {
            NBTTagCompound nbtTagCompound = new NBTTagCompound();
            nms.b(nbtTagCompound);
            for (String s : nbtTagCompound.e()) {
                NBTBase nbtBase = nbtTagCompound.c(s);
                if (Text.comparablyEquals(s, "items")) {
                    return true;
                }
                if (Text.comparablyEquals(s, "entitytag")) {
                    if (nbtBase instanceof NBTTagString) {
                        Main.info("itemstack nbt thing (%s): %s", itemStack, nbtBase);
                        NBTTagString nbtTagString = ((NBTTagString) nbtBase).e();
                        if (Text.comparablyContains(nbtTagString.m_(), "dragon", "wither", "warden", "minecart", "boat", "shulker")) {
                            return true;
                        }
                    }
                }
                if (Text.comparablyEquals(itemStack.getType(), "shulker", "book")) {
                    return true;
                }
            }
        }
        return false;
    }

    public void onPlaceBlock(BlockPlaceEvent event) {
        ItemStack itemStack = event.getItemInHand();
        if (blacklistedNBT(itemStack) || Text.comparablyContains(itemStack.getType(), BLACKLISTED_ITEMS.toArray())) {
            this.sendErrorMessage("You cannot use this block.");
            this.playErrorSound();
            event.setCancelled(true);
            return;
        }
        if (this.isCreatingCustomMap) {
            if (!event.getBlock().getLocation().toVector().isInAABB(this.getCustomMapMin().add(1, 0, 1).toVector().setY(CUSTOM_MAP_Y), this.getCustomMapMax().toVector().setY(MAX_CUSTOM_MAP_Y - 1))) {
                event.setCancelled(true);
                this.sendErrorMessage("You cannot place blocks outside of your map dimensions!");
                this.playErrorSound();
            }
            if (this.collidesWithCustomSpawn(event.getBlock())) {
                event.setCancelled(true);
                this.sendErrorMessage("You cannot place blocks too close to your spawns!");
                this.playErrorSound();
            } else {
                if (this.isCreatingOwnedCustomMap()) {
                    this.customMapBlocks.add(event.getBlock());
                } else {
                    this.getCollaborationOwner().customMapBlocks.add(event.getBlock());
                }
            }
        }
    }

    public HubPlayer getCollaborationOwner() {
        return getHubPlayer(this.customMapEdited.getOwner());
    }

    public void setCustomMapSpawn(Location location, boolean spawnB) {
        Location original = location;
        location = location.getBlock().getLocation();
        this.closeInventory();
        Location difference = location.clone().subtract(this.customMapCenter.clone());
        difference.setPitch(0);
        difference.setYaw(original.getYaw());
        difference.set(difference.getX(), difference.getBlockY(), difference.getZ());
        if (this.getPlayer().isFlying() || !location.add(0, -0.5, 0).getBlock().getType().isCollidable()) {
            this.playErrorSound();
            this.sendErrorMessage("Invalid spawn! You must be on the ground and on a solid surface!");
            return;
        }
        if (spawnB) {
            this.customMapSpawnB = difference;
        } else {
            this.customMapSpawnA = difference;
            for (HubPlayer mapCollaborator : this.mapCollaborators) {
                mapCollaborator.customMapSpawnA = this.customMapSpawnA;
            }
        }
        Location asBlock = this.customMapCenter.clone().add(difference.toVector());
        Material type = asBlock.getBlock().getType();
        BlockData blockData = asBlock.getBlock().getBlockData();

        asBlock.getBlock().setType(Material.RED_STAINED_GLASS);

        Main.schedule(() -> {
            if (this.isCreatingCustomMap) {
                asBlock.getBlock().setType(type);
                asBlock.getBlock().setBlockData(blockData);
            }
        }, 1);

        this.sendSuccessMessage("Spawn %s set to %s%s %s %s [%s %s]%s.", spawnB ? "B" : "A", YELLOW, difference.getX(), difference.getY(), difference.getZ(), difference.getYaw(), difference.getPitch(), GREEN);
        this.playSuccessSound();
    }

    public void setCustomMapSpawn(Location location) {
        this.setCustomMapSpawn(location, this.customMapSpawnA != null);
    }

    public void onKilled() {
        this.setPoints(0);
        this.arenaStats.put("killstreak", 0);
        this.arenaStats.put("deaths", this.arenaStats.get("deaths").intValue() + 1);
        this.arenaStats.put("kdr", this.arenaStats.get("kills").doubleValue() / (Math.max(1, this.arenaStats.get("deaths").doubleValue())));
    }

    public void setCanRequeue() {
        this.canRequeue = true;
    }

    public boolean canRequeue() {
        return this.canRequeue;
    }

    public void togglePlayers() {
        if (this.canSeePlayers) {
            this.hidePlayers();
        } else {
            this.showPlayers();
        }
    }

    public void updateLastMovementTime() {
        this.lastMovementTime = System.currentTimeMillis();
        this.AFKAgain = false;
    }

    public void setLimboQueuePosition(String position, String size) {
        this.limboQueuePosition = position;
        this.limboQueueSize = size;
    }

    public void checkCustomMapCenter() {
        if (this.customMapSize % 2 == 1) {
            this.customMapCenter.set(this.customMapCenter.getBlockX(), CUSTOM_MAP_Y, this.customMapCenter.getBlockZ()).add(0.5, 0, 0.5);
        }
    }

    public void editSocialSettings(CustomMap customMap, boolean fromMapList) {
        this.customMapEdited = customMap;
        if (!fromMapList) {
            this.customMapEdited.setMap(Miscellaneous.processList(this.getCustomMapBlocks(), block -> {
                Vector val = block.getLocation().toVector().subtract(getCustomMapSaveCenter());
                return new CustomMap.Block((float) val.getX(), (float) val.getY(), (float) val.getZ(), block.getType(), block.getBlockData());
            }));
        }
        this.customMapCenter = getAvailableCenter(customMap);
        this.customMapBlocks = new ArrayList<>();
        this.customMapSpawnA = customMap.getSpawnA();
        this.customMapSpawnB = customMap.getSpawnB();
        this.customMapVisibility = customMap.isVisible();
        this.customMapName = customMap.getName();
        this.customMapRepresentative = customMap.getRepresentative();
        this.customMapSize = customMap.getSize();

        SmartGUI smartGUI = new SmartGUI("Map Settings", 1, false);

        smartGUI.add(0, Material.ARROW, (p, b, b1) -> {
            if (fromMapList) {
                this.openDuelsCustomMapsListGui();
            } else {
                this.selectMap();
            }
        }, RED + "Back");

        smartGUI.add(3, Material.WRITABLE_BOOK, (player, b, b1) -> this.shareCustomMap(this.customMapEdited), AQUA + "Share Map", GRAY + "Share your custom map.");
        smartGUI.add(4, Material.NAME_TAG, (player, b, b1) -> this.editCustomMapLabel(true, fromMapList), GREEN + "Map Label", GRAY + "Change the name and icon of this kit.");
        smartGUI.add(5, Material.TNT, (player, b, b1) -> this.deleteMap(false), RED + "Delete", GRAY + "Delete this map.");

        this.openGui(smartGUI);
    }

    public void openDuelsCustomMapsListGui() {
        final int start = this.isAdministrator() ? 9 : 11;
        SmartGUI mapGUI = new SmartGUI("Maps", this.isAdministrator() ? 5 : 4, false);
        if (this.customMaps.isEmpty()) {
            mapGUI.add(13, Material.OAK_BUTTON, (player, b, b1) -> this.createCustomMap(), GREEN + ChatColor.BOLD.toString() + "CREATE MAP", GRAY + ITALIC.toString() + "Select to create a custom map!");
        } else {
            for (int i = 0; i < (this.isAdministrator() ? 18 : 5); i++) {
                List<CustomMap> customMaps = Miscellaneous.filter(this.getCustomMaps(), customMap -> customMap.getOwner().equals(this.getUUID()));
                if (i < customMaps.size()) {
                    CustomMap customMap = customMaps.get(i);
                    mapGUI.add(start + i, customMap.getRepresentative(), (player, b, b1) -> {
                        if (b1) {
                            this.editSocialSettings(customMap, true);
                        } else {
                            this.editCustomMap(customMap, false);
                        }
                    }, AQUA + customMap.getName(), GRAY + "Left or right-click to edit.", GRAY + "Shift-click to edit settings.");
                } else {
                    if (i < this.getMaxCustomMaps()) {
                        mapGUI.add(start + i, Material.OAK_BUTTON, (((player, b, b1) -> this.createCustomMap())), GREEN + "Create Custom Map", GRAY + "Make your own map.");
                    } else {
                        mapGUI.add(start + i, Material.STONE_BUTTON, (player, b, b1) -> this.playErrorSound(), ChatColor.RED + ChatColor.BOLD.toString() + "CREATE MAP", GRAY + ITALIC.toString() + "Upgrade to Premium rank to unlock this slot.");
                    }
                }
            }
        }

        mapGUI.add(this.isAdministrator() ? 39 : 30, Material.EMERALD, (player, b, b1) -> this.showCustomMapHistory(null), GREEN + "History", GRAY + "View your recently played maps.");
        mapGUI.add(this.isAdministrator() ? 41 : 32, Material.CLOCK, (player, b, b1) -> this.browsePublicMaps(null, false), GOLD + "Public Maps", GRAY + "Browse public custom maps.");


        mapGUI.fillEmptySpots();
        this.openGui(mapGUI);
    }

    private void createCustomMap() {
        CustomMap customMap = new CustomMap(this.smartPlayer);
        this.editCustomMap(customMap, true);
    }

    public void onChat(String message) {
        if (this.smartPlayer.canSendChatMessage(message)) {
            for (HubPlayer onlineHubPlayer : HubPlayer.getOnlineHubPlayers()) {
                onlineHubPlayer.sendChatMessage(this, message);
            }
        }
    }

    public void onClickQueuedGame(QueueData queuedGame, GameType gameType, int playersNeeded, int ready) {
        if (queuedGame.getData("owner").equals(this.getUUID().toString())) {
            this.cancelQueue();
            this.playSuccessSoundVariation();
            return;
        }

        if (ready >= playersNeeded) {
            this.sendErrorMessage("This queue is full!");
            this.playErrorSound();
            return;
        }

        if (this.getPartySize() > 1) {
            this.sendErrorMessage("You cannot queue public matches in a party!");
            this.playErrorSound();
            return;
        }

        if (ready + this.getPartySize() > playersNeeded) {
            this.sendErrorMessage("Your party is too large!");
            this.playErrorSound();
            return;
        }

        this.queuedAgainst = UUID.fromString(queuedGame.getData("owner"));
        this.queue(gameType, new QueueData().attachData("with", queuedGame.getData("owner")).attachBoolean("another", true).attachData("lobby", RelayUtils.getName()));
    }

    public void onClickQueuedGame(QueueData queuedGame, boolean rightClick, CustomDuelsKit customDuelsKit, int playersNeeded, int ready) {
        SmartPlayer owner = SmartPlayer.getOrCreate(UUID.fromString(queuedGame.getData("owner")));
        if (owner == this.smartPlayer) {
            this.cancelQueue();
            return;
        }
        this.previewKit = customDuelsKit;
        if (rightClick) {
            String map = customDuelsKit.getMaps().get(0);
            CustomMap customMap = CustomMap.CODE_TO_MAP.get(map);
            if (customMap != null) {
                this.previewCenter = getAvailableCenter(customMap);
                if (customMap.getSize() % 2 == 1) {
                    previewCenter.add(0.5, 0, 0.5);
                }

                for (CustomMap.Block block : customMap.getMap()) {
                    Vector position = block.getPosition();
                    int x = (int) (previewCenter.getX() + position.getX());
                    int y = (int) (previewCenter.getY() + position.getY());
                    int z = (int) (previewCenter.getZ() + position.getZ());
                    Block worldBlock = getWorld().getBlockAt(x, y, z);
                    worldBlock.setType(block.material());
                    worldBlock.setBlockData(block.blockData());
                    this.previewMapBlocks.add(worldBlock);
                }

                this.previewMap = customMap;

                this.isPreviewingMap = true;
                Main.synchronous(() -> {
                    this.smartPlayer.setGameMode(GameMode.CREATIVE);
                    this.teleport(previewCenter.clone().add(customMap.getSpawnA().toVector()));
                    this.teleportedToPreviewMap = true;
                });

                this.smartPlayer.clear(true);
                this.smartPlayer.setAllowFlight(true);
                this.smartPlayer.setGameMode(GameMode.ADVENTURE);
                this.giveSmartItem(SmartGUIUtils.PREVIEW_KIT);
                this.giveSmartItem(SmartGUIUtils.ACCEPT_KIT);
                this.giveSmartItem(SmartGUIUtils.CANCEL_KIT);
                this.giveSmartItem(4, SmartGUIUtils.MAP_INFO);
            }

            this.previewQueueOwner = owner.getUUID();
            this.openPreviewKitGUI(false, false);

            return;
        }

        if (queuedGame.getData("owner").equals(this.getUUID().toString())) {
            this.sendErrorMessage("You cannot duel yourself!");
            this.playErrorSound();
            return;
        }

        if (ready >= playersNeeded) {
            this.sendErrorMessage("This queue is full!");
            this.playErrorSound();
            return;
        }

        if (this.getPartySize() > 1) {
            this.sendErrorMessage("You cannot queue public matches in a party!");
            this.playErrorSound();
            return;
        }

        if (ready + this.getPartySize() > playersNeeded) {
            this.sendErrorMessage("Your party is too large!");
            this.playErrorSound();
            return;
        }


        this.onClickQueuedGame(queuedGame, GameType.DUELS_CUSTOM, playersNeeded, ready);
    }

    private void openPreviewKitGUI(boolean library, boolean browsing) {
        this.previewCustomKitGUI = new PreviewCustomKitGUI(this.previewKit, this, library, browsing);

        this.openGui(this.previewCustomKitGUI);
    }

    public static void onCustomQueueRemoved(String ownerUUID) {
        for (HubPlayer onlineHubPlayer : getOnlineHubPlayers()) {
            if (ownerUUID.equals(String.valueOf(onlineHubPlayer.previewQueueOwner)) && onlineHubPlayer.isInQueue()) {
                onlineHubPlayer.kickFromPreview();
            }
        }

        onDuelsQueueRemoved(ownerUUID);
    }

    public static void onDuelsQueueRemoved(String ownerUUID) {
        for (HubPlayer onlineHubPlayer : getOnlineHubPlayers()) {
            if (ownerUUID.equals(String.valueOf(onlineHubPlayer.queuedAgainst))) {
                if (onlineHubPlayer.isInQueue()) {
                    onlineHubPlayer.sendErrorMessage("The player you were queued against is no longer queued.");
                    onlineHubPlayer.cancelQueue();
                }
                onlineHubPlayer.queuedAgainst = null;
            }
        }
    }

    private static final List<Location> CUSTOM_MAP_CENTERS = new ArrayList<>();

    private static Location getAvailableCenter(CustomMap customMap) {
        int rng = Miscellaneous.getRandomIntBetweenRange(2000, 4000);
        int rng2 = Miscellaneous.getRandomIntBetweenRange(2000, 4000);
        Location location = new Location(Main.getWorld(),
                Miscellaneous.getRandomTrueOrFalse() ? rng : -rng,
                CUSTOM_MAP_Y,
                Miscellaneous.getRandomTrueOrFalse() ? rng2 : -rng2,
                0, 0);

        if (Miscellaneous.match(CUSTOM_MAP_CENTERS, o -> o.distance(location) <= 150) == null) {
            CUSTOM_MAP_CENTERS.add(location);
            return location;
        }

        return getAvailableCenter(customMap);
    }

    public boolean inPreviewingMapBoundingBox(Location to) {
        if (this.previewCenter != null) {
            Vector min = this.previewCenter.toVector().subtract(new Vector(100 / 2 + 20, 0, 100 / 2 + 20)).setY(CUSTOM_MAP_Y - 20);
            Vector max = this.previewCenter.toVector().add(new Vector(100 / 2 + 20, 0, 100 / 2 + 20)).setY(330);

            return to.toVector().isInAABB(min, max);
        }

        return true;
    }

    public void teleportToPreviewMap() {
        if (this.previewCenter != null) {
            Location toTeleport = PlayerUtilities.getNearestTwoBlockSpaceAbove(this.previewCenter.clone().add(this.previewMap.getSpawnA().toVector()));

            toTeleport.add(0, 3, 0);

            if (toTeleport.getY() >= 130) {
                toTeleport.setY(5);
            }
            Location location = this.previewCenter.clone().add(this.previewMap.getSpawnA().toVector());
            if (!this.inCustomMapBoundingBox(location)) {
                this.teleport(toTeleport);
            } else {
                this.teleport(location);
            }
            this.sendSuccessMessage("Teleported you to map.");
            Main.synchronous(() -> {
                this.addPotionEffect(PotionEffectType.NIGHT_VISION);
                this.teleportedToPreviewMap = true;
            });
            Main.scheduleSyncDelayedTask(() -> this.addPotionEffect(PotionEffectType.NIGHT_VISION), 1);
        }
    }

    public void previewKit() {
        this.openPreviewKitGUI(false, false);
    }

    public void acceptKit() {
        if (this.previewQueueOwner.equals(this.getUUID())) {
            this.playErrorSound();
            this.sendErrorMessage("You cannot queue against yourself!");
            return;
        }

        if (this.previewQueueOwner != null) {
            this.cancelKit(false);
        }

        this.queuedAgainst = this.previewQueueOwner;
        this.queue(GameType.DUELS_CUSTOM, new QueueData().attachData("with", this.previewQueueOwner).attachBoolean("another", true));
    }

    public void cancelKit(boolean openGUi) {
        for (Block previewMapBlock : this.previewMapBlocks) {
            previewMapBlock.setType(Material.AIR);
        }

        this.previewMapBlocks.clear();
        this.isPreviewingMap = false;
        this.teleportedToPreviewMap = false;
        this.previewKit = null;
        this.previewCustomKitGUI = null;
        this.previewMap = null;

        this.spawn(true);
        if (openGUi) {
            this.openGui(CUSTOM_KITS_QUEUE);
        }
    }

    public void kickFromPreview() {
        this.sendErrorMessage("The kit you were previewing is no longer in the queue!");
        this.cancelKit(true);
    }

    public void spectate(String s) {
        ServerCommunicator.sendToProxy(Command.SPECTATE, this.getUUID(), s);
    }

    public void onEnterQueue() {
        this.giveSmartItem(SmartGUIUtils.LEAVE_QUEUE);
    }

    public void createCustomQueue(int i) {
        if (this.customDuelKits.isEmpty()) {
            this.openDuelsCustomKitsListGui(1);
            this.sendErrorMessage("You do not have any custom kits created!");
            this.playErrorSound();
        } else {
            QueueData queueData1 = new QueueData().attachData("owner", this.getUUID()).attachData("another", false).attachData("index", i).attachData("size", this.getPreferredQueueSize(GameType.DUELS_CUSTOM));
            this.queue(GameType.DUELS_CUSTOM, queueData1);
        }
    }

    public void cancelQueue() {
        ServerCommunicator.sendToProxy(Command.CANCEL, this.getUUID());
    }

    public void createQueue(int i, GameType gameType) {

        if (gameType == GameType.DUELS_CUSTOM) {
            this.createCustomQueue(i);
        } else {
            if (gameType.getMainGame() == MainGame.MANHUNT) {
                this.queue(gameType, new QueueData().attachData("owner", this.getUUID())
                        .attachData("index", i)
                        .attachData("size", this.getPreferredHunterCount() + this.getPreferredRunnerCount())
                        .attachData("runners", this.getPreferredRunnerCount())
                        .attachData("hunters", this.getPreferredHunterCount())
                        .attachData("release", this.getPreferredReleaseTime())
                        .attachData("dropRate", this.getPreferredDropRate())
                        .attachData("tradeRate", this.getPreferredTradeRate()));
            } else {
                this.queue(gameType, new QueueData().attachData("owner", this.getUUID())
                        .attachData("index", i)
                        .attachData("size", this.getPreferredQueueSize(gameType))
                        .attachData("rounds", this.getPreferredRounds(gameType))
                        .attachData("map", this.queuePreferences.getIntPreference(gameType, "map", 0)));
            }
        }
    }

    private int getPreferredDropRate() {
        return this.queuePreferences.getIntPreference(GameType.MANHUNT_1V1, "dropRate", 0);
    }

    public int maxQueueSize() {
        return this.isInGroupOrHigher(Group.CHAMPION) ? 20 : 4;
    }

    public void changePreferredSize(SmartGUI smartGUI, GameType gameType) {
        if (gameType.getMainGame() == MainGame.DUELS) {
            if (gameType == GameType.DUELS_CUSTOM) {
                this.getStringInput("Player Count", String.valueOf(this.getPreferredQueueSize(gameType)), s -> {
                    try {
                        int val = Integer.parseInt(s);
                        val = Math.max(2, Math.min(val, this.maxQueueSize()));
                        this.queuePreferences.setIntPreference(gameType, "queueSize", val);
                        this.onUpdatePreferences();
                        this.playSuccessSound();
                        this.openGui(smartGUI);
                    } catch (Exception e) {
                        this.onInvalidInput();
                    }
                });
            } else {
                SmartGUI settingsGUI = new SmartGUI("Public Settings", 3, false);

                settingsGUI.add(11, Material.ENDER_EYE, ((player, b, b1) -> this.getStringInput("Player Count", String.valueOf(this.getPreferredQueueSize(gameType)), s -> {
                    try {
                        int val = Integer.parseInt(s);
                        val = Math.max(2, Math.min(val, this.maxQueueSize()));
                        this.queuePreferences.setIntPreference(gameType, "queueSize", val);
                        this.onUpdatePreferences();
                        this.playSuccessSound();
                        this.changePreferredSize(smartGUI, gameType);
                    } catch (Exception e) {
                        this.onInvalidInput();
                    }
                })), GREEN + "Player Count: " + GRAY + this.getPreferredQueueSize(gameType));

                settingsGUI.add(13, Material.MAP, ((player, b, b1) -> {
                    SmartGUI mapGUI = new SmartGUI("Map", 2, false);
                    List<DuelMap> duelMaps = Main.getMaps(gameType);
                    int selected = this.queuePreferences.getIntPreference(gameType, "map", 0);
                    for (int i = 0; i < duelMaps.size(); i++) {
                        DuelMap duelMap = duelMaps.get(i);
                        int finalI = i;
                        mapGUI.add(i, i == selected ? ItemUtilities.enchanted(duelMap.getRepresentative()) : new ItemStack(duelMap.getRepresentative()), (player1, b2, b11) -> {
                            this.queuePreferences.setIntPreference(gameType, "map", finalI);
                            this.onUpdatePreferences();
                            this.playSuccessSound();
                            this.changePreferredSize(smartGUI, gameType);
                        }, AQUA + duelMap.getName(), (i == selected ? ChatColor.GREEN + "Selected" : GRAY + "Left-click to select."));
                    }
                    mapGUI.fillEmptySpots();
                    this.openGui(mapGUI);
                }), GOLD + "Map: " + GRAY + this.getPreferredMapName(gameType));

                settingsGUI.add(15, Material.CLOCK, ((player, b, b1) -> this.getStringInput("Rounds", String.valueOf(this.getPreferredRounds(gameType)), s -> {
                    try {
                        int val = Integer.parseInt(s);
                        val = Math.max(1, Math.min(val, 10));
                        this.queuePreferences.setIntPreference(gameType, "rounds", val);
                        this.onUpdatePreferences();
                        this.playSuccessSound();
                        this.changePreferredSize(smartGUI, gameType);
                    } catch (Exception e) {
                        this.onInvalidInput();
                    }
                })), GREEN + "Rounds: " + GRAY + this.getPreferredRounds(gameType));

                settingsGUI.add(0, Material.ARROW, (p, b, b1) -> this.openGui(smartGUI), RED + "Back");

                this.openGui(settingsGUI);
            }
        } else {
            SmartGUI settingsGUI = new SmartGUI("Public Settings", 5, false);

            settingsGUI.add(12, Material.ENDER_EYE, ((player, b, b1) -> {
                this.getStringInput("Runner Count", String.valueOf(this.getPreferredRunnerCount()), s -> {
                    try {
                        int val = Integer.parseInt(s);
                        val = Math.max(1, Math.min(val, this.getMaxRunnerCount()));
                        this.setPreferredRunnerCount(val);
                        this.playSuccessSound();
                        this.changePreferredSize(smartGUI, gameType);
                    } catch (Exception e) {
                        this.onInvalidInput();
                    }
                });
            }), GREEN + "Runner Count: " + GRAY + this.getPreferredRunnerCount());

            settingsGUI.add(14, Material.STONE_AXE, ((player, b, b1) -> {
                this.getStringInput("Hunter Count", String.valueOf(this.getPreferredHunterCount()), s -> {
                    try {
                        int val = Integer.parseInt(s);
                        val = Math.max(1, Math.min(val, this.getMaxHunterCount()));
                        this.setPreferredHunterCount(val);
                        this.playSuccessSound();
                        this.changePreferredSize(smartGUI, gameType);
                    } catch (Exception e) {
                        this.onInvalidInput();
                    }
                });
            }), RED + "Hunter Count: " + GRAY + this.getPreferredHunterCount());

            settingsGUI.add(29, Material.CLOCK, ((player, b, b1) -> {
                this.getStringInput("Hunter Release Time", String.valueOf(this.getPreferredReleaseTime()), s -> {
                    try {
                        int val = Integer.parseInt(s);
                        val = Math.max(0, Math.min(val, 60));
                        this.setPreferredReleaseTime(val);
                        this.playSuccessSound();
                        this.changePreferredSize(smartGUI, gameType);
                    } catch (Exception e) {
                        this.onInvalidInput();
                    }
                });
            }), GOLD + "Release Time", GRAY.toString() + this.getPreferredReleaseTime() + " seconds");

            settingsGUI.add(31, Material.BLAZE_ROD, (p, b, b1) -> {
                this.setPreferredDropRate(this.getPreferredDropRate() == 1 ? 0 : 1);
                this.playSuccessSound();
                this.setExtraData("dropRate", this.getPreferredDropRate());
                this.changePreferredSize(smartGUI, gameType);
                settingsGUI.setItem(31, Material.BLAZE_ROD, RED + "Blaze/Enderman Drop Rate", GRAY + (this.getPreferredDropRate() == 1 ? "Boosted" : "Normal"));
            }, RED + "Blaze/Enderman Drop Rate", GRAY + (this.getPreferredDropRate() == 1 ? "Boosted" : "Normal"));

            settingsGUI.add(33, Material.GOLD_INGOT, (p, b, b1) -> {
                this.setPreferredTradeRate(this.getPreferredTradeRate() + 1);
                if (this.getPreferredTradeRate() > 2) {
                    this.setPreferredTradeRate(0);
                }
                this.playSuccessSound();
                this.changePreferredSize(smartGUI, gameType);
                settingsGUI.setItem(33, Material.GOLD_INGOT, YELLOW + "Piglin Trade Rate", GRAY + (this.getPreferredTradeRate() == 0 ? "1.16" : (this.getPreferredTradeRate() == 1 ? "1.19" : "Boosted")));
            }, YELLOW + "Piglin Trade Rate", GRAY + (this.getPreferredTradeRate() == 0 ? "1.16" : (this.getPreferredTradeRate() == 1 ? "1.19" : "Boosted")));

            settingsGUI.add(0, Material.ARROW, (p, b, b1) -> this.openGui(smartGUI), RED + "Back");

            settingsGUI.fillEmptySpots();

            this.openGui(settingsGUI);
        }
    }

    private void setPreferredDropRate(int i) {
        this.queuePreferences.setIntPreference(GameType.MANHUNT_1V1, "dropRate", i);
        this.onUpdatePreferences();
    }

    private void onUpdatePreferences() {
        getExtraData().setJsonArray("preferences", this.queuePreferences.getAsJsonObject());
        this.smartPlayer.updateProfile(ProfileColumn.EXTRA);
    }

    private void setPreferredHunterCount(int val) {
        this.queuePreferences.setIntPreference(GameType.MANHUNT_1V1, "hunters", val);
        this.onUpdatePreferences();
    }

    private void setPreferredRunnerCount(int val) {
        this.queuePreferences.setIntPreference(GameType.MANHUNT_1V1, "runners", val);
        this.onUpdatePreferences();
    }

    private void setPreferredReleaseTime(int val) {
        this.queuePreferences.setIntPreference(GameType.MANHUNT_1V1, "releaseTime", val);
        this.onUpdatePreferences();
    }

    private int getPreferredReleaseTime() {
        return this.queuePreferences.getIntPreference(GameType.MANHUNT_1V1, "releaseTime", 15);
    }

    private void setPreferredTradeRate(int i) {
        this.queuePreferences.setIntPreference(GameType.MANHUNT_1V1, "tradeRate", i);
        this.onUpdatePreferences();
    }

    private int getPreferredTradeRate() {
        return this.queuePreferences.getIntPreference(GameType.MANHUNT_1V1, "tradeRate", 0);
    }

    private int getPreferredRounds(GameType gameType) {
        return this.queuePreferences.getIntPreference(gameType, "rounds", 2);
    }

    private String getPreferredMapName(GameType gameType) {
        int map = this.queuePreferences.getIntPreference(gameType, "map", 0);

        List<DuelMap> maps = Main.getMaps(gameType);

        return maps.get(Math.min(map, maps.size() - 1)).getName();
    }

    private int getPreferredQueueSize(GameType gameType) {
        return this.queuePreferences.getIntPreference(gameType, "queueSize", 2);
    }

    private int getMaxHunterCount() {
        return this.getMaxManhuntSize() - this.getPreferredRunnerCount();
    }

    private int getPreferredRunnerCount() {
        return this.queuePreferences.getIntPreference(GameType.MANHUNT_1V1, "runners", 1);
    }

    private int getMaxRunnerCount() {
        return this.getMaxManhuntSize() - this.getPreferredHunterCount();
    }

    private int getPreferredHunterCount() {
        return this.queuePreferences.getIntPreference(GameType.MANHUNT_1V1, "hunters", 1);
    }

    private int getMaxManhuntSize() {
        if (this.isAdministrator()) {
            return 64;
        }

        if (this.isInGroupOrHigher(Group.CHAMPION)) {
            return new int[]{4, 8, 10, 12, 13, 14, 15, 16}[Math.min(this.smartPlayer.getChampionTier(), 7)];
        }

        return 4;
    }

    public boolean isCreative() {
        return this.getPlayer().getGameMode() == GameMode.CREATIVE;
    }

    public void browsePublicMaps(EditCustomKitGUI editCustomKitGUI, boolean recent) {
        SmartGUI smartGUI = new SmartGUI("Public Maps", 6, false);

        List<CustomMap> orderedMaps = Main.getCustomMaps(recent, timeFrame);

        for (int i = 0; i < orderedMaps.size() && i < 45; i++) {
            CustomMap customMap = orderedMaps.get(i);
            int plays = customMap.getPlays(recent ? 0 : timeFrame);
            smartGUI.add(i, customMap.getRepresentative(), (player, b, b1) -> this.selectPublicCustomMap(editCustomKitGUI, customMap, true),
                    WHITE + customMap.getName(),
                    RelayUtils.format("%sSize: %s%dx%d", GRAY, YELLOW, customMap.getSize(), customMap.getSize()),
                    RelayUtils.format("%sBuilder: %s", GRAY, SmartPlayer.getOrCreate(customMap.getOwner()).getRealFormattedName()),
                    RelayUtils.format("%s%d Play%s", AQUA, plays, plays == 1 ? "" : "s"),
                    DARK_GRAY + "Click to preview");
        }

        smartGUI.add(45, Material.ARROW, (player, b, b1) -> {
            if (editCustomKitGUI != null) {

                editCustomKitGUI.selectMap();
            } else {
                this.openDuelsCustomMapsListGui();
            }
        }, RED + "Back");
        if (!recent) {
            smartGUI.add(52, Material.CLOCK, (p, b, b1) -> {
                this.cycleTimeFrame();
                this.browsePublicMaps(editCustomKitGUI, recent);
                this.playClickSoundVariation();
            }, AQUA + this.getTimeFrame(), DARK_GRAY + "Click to cycle.");
        }
        smartGUI.add(53, Material.ANVIL, (player, b, b1) -> {
            this.browsePublicMaps(editCustomKitGUI, !recent);
            this.playClickSoundVariation();
        }, GOLD + (recent ? "Recent" : "Popular"), DARK_GRAY + "Click to toggle.");
        smartGUI.fillEmptySpots();
        this.openGui(smartGUI);
    }

    public void selectPublicCustomMap(EditCustomKitGUI editCustomKitGUI, CustomMap customMap, boolean rightClick) {
        if (rightClick) {
            this.previewCenter = getAvailableCenter(customMap);
            if (customMap.getSize() % 2 == 1) {
                previewCenter.add(0.5, 0, 0.5);
            }

            for (CustomMap.Block block : customMap.getMap()) {
                Vector position = block.getPosition();
                int x = (int) (previewCenter.getX() + position.getX());
                int y = (int) (previewCenter.getY() + position.getY());
                int z = (int) (previewCenter.getZ() + position.getZ());
                Block worldBlock = getWorld().getBlockAt(x, y, z);
                worldBlock.setType(block.material());
                worldBlock.setBlockData(block.blockData());
                this.previewMapBlocks.add(worldBlock);
            }

            this.previewMap = customMap;

            this.isPreviewingMap = true;
            Main.synchronous(() -> {
                this.smartPlayer.setGameMode(GameMode.CREATIVE);
                this.teleport(previewCenter.clone().add(customMap.getSpawnA().toVector()));
                this.teleportedToPreviewMap = true;
            });

            this.smartPlayer.clear(true);
            this.smartPlayer.setAllowFlight(true);
            this.smartPlayer.setGameMode(GameMode.ADVENTURE);
            this.addPotionEffect(PotionEffectType.NIGHT_VISION);

            this.giveSmartItem(SmartGUIUtils.ACCEPT_MAP);
            this.giveSmartItem(SmartGUIUtils.ADD_TO_MAP_LIBRARY);
            this.giveSmartItem(SmartGUIUtils.CANCEL_MAP);
            this.giveSmartItem(4, SmartGUIUtils.MAP_INFO);
        } else {
            if (editCustomKitGUI == null) {
                if (this.selectedCustomDuelsKit != null) {
                    editCustomKitGUI = new EditCustomKitGUI(this.selectedCustomDuelsKit, this, this.customDuelKits.size());
                    this.lastEditKitGui = editCustomKitGUI;
                } else {
                    this.sendErrorMessage("You must have a selected custom kit first!");
                    this.playErrorSound();
                    return;
                }
            }
            editCustomKitGUI.customDuelsKit.getMaps().clear();
            editCustomKitGUI.customDuelsKit.getMaps().add(customMap.getId());
            editCustomKitGUI.publicCustomMap = customMap.getId();
            editCustomKitGUI.selectMap();
            this.playSuccessSound();
        }
    }

    public void showCustomMapHistory(EditCustomKitGUI editCustomKitGUI) {
        SmartGUI smartGUI = new SmartGUI("History", 3, false);
        for (int i = 0; i < this.recentMaps.size(); ) {
            CustomMap customMap = this.recentMaps.get(i);
            if (customMap != null) {
                int plays = customMap.getPlays(0);
                smartGUI.add(i++, customMap.getRepresentative(), (player, b, b1) -> this.selectPublicCustomMap(editCustomKitGUI, customMap, true),
                        WHITE + customMap.getName(),
                        RelayUtils.format("%sSize: %s%dx%d", GRAY, YELLOW, customMap.getSize(), customMap.getSize()),
                        RelayUtils.format("%sBuilder: %s", GRAY, SmartPlayer.getOrCreate(customMap.getOwner()).getRealFormattedName()),
                        RelayUtils.format("%s%d Play%s", AQUA, plays, plays == 1 ? "" : "s"),
                        DARK_GRAY + "Click to preview");
            }
        }

        smartGUI.add(22, Material.ARROW, (player, b, b1) -> {
            if (editCustomKitGUI != null) {

                editCustomKitGUI.selectMap();
            } else {
                this.openDuelsCustomMapsListGui();
            }
        }, RED + "Back");

        smartGUI.fillEmptySpots();

        this.openGui(smartGUI);
    }

    public CustomMap getNonOwnedCustomMap() {
        CustomMap customMap1 = Miscellaneous.match(this.customMaps, customMap -> !customMap.getOwner().equals(this.getUUID()));

        if (customMap1 != null) {
            return customMap1;
        }

        if (this.customMaps.size() > this.getMaxCustomMaps()) {
            return Miscellaneous.getLastEntry(this.customMaps);
        }

        return null;
    }

    public synchronized void acceptMap() {
        if (this.customMaps.contains(this.previewMap)) {
            this.playErrorSound();
            this.sendErrorMessage("You already have this map!");
            return;
        }
        if (this.selectedCustomDuelsKit != null) {
            if (this.lastEditKitGui == null) {
                this.lastEditKitGui = new EditCustomKitGUI(this.selectedCustomDuelsKit, this, this.customDuelKits.size());
            }
        } else {
            this.sendErrorMessage("You must select a custom kit first!");
            this.playErrorSound();
            return;
        }
        this.lastEditKitGui.publicCustomMap = this.previewMap.getId();
        this.lastEditKitGui.customMap = this.previewMap.getId();
        this.customMaps.remove((this.getNonOwnedCustomMap()));
        this.customMaps.add(this.previewMap);
        this.clearPreviewMap();
        this.playSuccessSound();
        this.spawn(true);
        this.lastEditKitGui.selectMap();
        //this.openDuelsCustomKitsListGui();
    }

    public void cancelMap() {
        this.playClickSound();
        this.clearPreviewMap();
        this.spawn(true);
        this.browsePublicMaps(this.lastEditKitGui, true);
    }

    public synchronized void addToMapLibrary() {
        this.customMaps.remove(this.previewMap);

        if (this.customMaps.size() < this.getMaxCustomMaps()) {
            CustomMap clone = new CustomMap(this.smartPlayer);
            clone.setMap(this.previewMap.getMap());
            clone.setCreators(Miscellaneous.with(this.previewMap.getCreators(), this.previewMap.getOwner()));
            clone.setContributors(this.previewMap.getContributors());
            clone.setSize(this.previewMap.getSize());
            clone.setSpawnA(this.previewMap.getSpawnA());
            clone.setSpawnB(this.previewMap.getSpawnB());
            clone.setRepresentative(this.previewMap.getRepresentative());
            clone.setName(Text.truncate(this.previewMap.getName() + " - Copy", 32));
            this.uploadNewCustomMap(clone);
            this.customMaps.add(clone);
            this.smartPlayer.getCustomMaps().add(clone);
            this.updateCustomMapList();
            this.playSuccessSound();
            this.spawn(true);
            this.openDuelsCustomMapsListGui();
            this.clearPreviewMap();
        } else {
            if (this.isInGroupOrHigher(Group.CHAMPION)) {
                this.sendErrorMessage("You do not have any more slots available!");
            } else {
                this.sendErrorMessage("You do not have any more slots available! Upgrade to Premium to bypass this restriction!");
            }
            this.playErrorSound();
        }
    }

    public void clearPreviewMap() {
        for (Block previewMapBlock : this.previewMapBlocks) {
            previewMapBlock.setType(Material.AIR);
        }

        this.previewMapBlocks.clear();
        this.isPreviewingMap = false;
        this.teleportedToPreviewMap = false;
        this.previewKit = null;
        this.previewCustomKitGUI = null;
        this.previewMap = null;
    }

    private void uploadNewCustomMap(CustomMap clone) {
        CustomMap.insertIntoDatabase(clone);
    }

    public int getMaxCustomMaps() {
        if (this.isAdministrator()) {
            return 18;
        }

        if (this.isInGroupOrHigher(Group.CHAMPION)) {
            return new int[]{1, 3, 3, 4, 4, 5}[Math.min(5, this.smartPlayer.getChampionTier())];
        }

        return 1;
    }

    public synchronized void addToKitLibrary() {
        this.customDuelKits.remove(this.previewKit);

        if (this.customDuelKits.size() < this.getMaxCustomKits()) {
            String id = CustomDuelsKit.generateNewId();

            CustomDuelsKit clone = new CustomDuelsKit(id, this.getUUID(), this.previewKit.getKit(), this.previewKit.getGameSettings(), this.previewKit.getMaps(), this.previewKit.getPublicCustomMap(), false, this.previewKit.getVotes(0), Miscellaneous.with(this.previewKit.getCreators(), this.previewKit.getOwner()), 0);
            clone.setName(Text.truncate(this.previewKit.getName() + " - Copy", 32));
            this.uploadNewCustomKit(clone);
            this.customDuelKits.add(clone);
            this.updateCustomKitList();
            this.playSuccessSound();
            this.openDuelsCustomKitsListGui();
        } else {
            if (this.isInGroupOrHigher(Group.CHAMPION)) {
                this.sendErrorMessage("You do not have any more slots available!");
            } else {
                this.sendErrorMessage("You do not have any more slots available! Upgrade to Premium to bypass this restriction!");
            }
            this.playErrorSound();
        }
    }

    private void uploadNewCustomKit(CustomDuelsKit clone) {
        CustomDuelsKit.insertIntoDatabase(clone);
    }

    private int getMaxCustomKits() {
        if (this.isAdministrator()) {
            return 18;
        }

        if (this.isInGroupOrHigher(Group.CHAMPION)) {
            return new int[]{3, 5, 5, 6, 6, 6, 7}[Math.min(this.smartPlayer.getChampionTier(), 6)];
        }

        return 3;
    }

    public synchronized void acceptPreviewKit() {
        if (this.customDuelKits.contains(this.previewKit)) {
            this.playErrorSound();
            this.sendErrorMessage("You already have this kit!");
            return;
        }
        this.customDuelKits.remove(this.getNonOwnedKit());
        this.customDuelKits.add(this.previewKit);
        this.selectedCustomDuelsKit = this.previewKit;
        this.sendSuccessMessage("Selected custom kit.");
        this.updateSelectedCustomDuelsKit();
        this.playSuccessSound();
        this.updateCustomKitList();
        this.openDuelsCustomKitsListGui();
    }

    public boolean shareCustomKit(CustomDuelsKit asKit, String originalId) {
        if (this.enoughSecondsHaveElapsed(Cooldown.SHARE_KIT)) {
            this.updateCooldown(Cooldown.SHARE_KIT);
        } else {
            this.showCooldownWarning(Cooldown.SHARE_KIT);
            return false;
        }
        asKit.setVisible();
        this.saveCustomKit(asKit, originalId);
        ComponentBuilder componentBuilder = new ComponentBuilder();
        componentBuilder.color(ComponentBuilder.BLUE).line().newLine();
        String code = asKit.getId();
        componentBuilder.copy("/setkit " + code, GOLD + UNDERLINE.toString() + "/setkit " + code, "Click to copy.").newLine();
        componentBuilder.copy(code, GOLD + UNDERLINE.toString() + code, "Click to copy.").newLine();
        componentBuilder.color(ComponentBuilder.GREEN).append("This kit has been shared publicly.").newLine();
        componentBuilder.color(ComponentBuilder.BLUE).line();
        this.playSuccessSound();
        this.sendMessage(componentBuilder);

        return true;
    }

    public void setMap(String arg) {
        CustomMap customMap = CustomMap.getCustomMap(arg);
        if (customMap == null || !customMap.isVisible()) {
            this.sendErrorMessage("No public custom map found by the code %s%s%s.", YELLOW, arg, RED);
        } else {
            if (this.selectedCustomDuelsKit == null) {
                this.sendErrorMessage("You must select a custom kit first!");
                this.playErrorSound();
            } else {
                if (this.selectedCustomDuelsKit.getOwner().equals(this.getUUID())) {
                    if (this.selectedCustomDuelsKit.isVisible()) {
                        CustomDuelsKit clone = this.selectedCustomDuelsKit.clone();
                        this.saveCustomKit(clone, this.selectedCustomDuelsKit.getId());
                    } else {
                        this.selectedCustomDuelsKit.setPublicCustomMap(customMap.getId());
                        this.saveCustomKit(this.selectedCustomDuelsKit, this.selectedCustomDuelsKit.getId());
                    }
                    this.playSuccessSound();
                    this.sendSuccessMessage("Changed your selected public map for your selected custom kit.");
                } else {
                    this.sendErrorMessage("You can only change the selected public for a map you own!");
                    this.playErrorSound();
                }
            }
        }
    }

    public void shareKit() {
        if (this.customDuelKits.isEmpty()) {
            this.openDuelsCustomKitsListGui();
            return;
        }

        SmartGUI kitGUI = new SmartGUI("Share Kit", 1, false);
        for (int i = 0; i < this.customDuelKits.size(); i++) {
            CustomDuelsKit kit = this.customDuelKits.get(i);
            if (kit.getOwner().equals(this.getUUID())) {
                kitGUI.add(i, kit.getRepresentative(), (p, b, b1) -> this.shareCustomKit(kit, kit.getId()), kit.getName(), GRAY + "Share this custom map.");
            }
        }

        this.openGui(kitGUI);
    }

    public void shareMap() {
        if (this.customMaps.isEmpty()) {
            this.openDuelsCustomMapsListGui();
            return;
        }

        SmartGUI mapGUI = new SmartGUI("Share Map", this.isAdministrator() ? 2 : 1, false);
        for (int i = 0; i < this.customMaps.size(); i++) {
            CustomMap customMap = this.customMaps.get(i);
            if (customMap.getOwner().equals(this.getUUID())) {
                mapGUI.add(i, customMap.getRepresentative(), (p, b, b1) -> this.shareCustomMap(customMap), customMap.getName(), GRAY + "Share this custom map.");
            }
        }

        mapGUI.fillEmptySpots();
        this.openGui(mapGUI);
    }

    public CustomMap getPublicCustomMap(String publicCustomMap) {
        return this.publicCustomMaps.get(publicCustomMap);
    }

    public void showMapInfo() {
        if (this.previewMap == null) {
            return;
        }

        SmartGUI smartGUI = new SmartGUI("Map Info", 2, false);
        SmartPlayer owner = SmartPlayer.getOrCreate(this.previewMap.getOwner());
        smartGUI.addImmutable(4, owner.getHead(), owner.getRealFormattedName(), GRAY + "Creator");

        List<String> contributors = new ArrayList<>();
        if (this.previewMap.getContributors().isEmpty()) {
            contributors.add(DARK_GRAY + " - None");
        } else {
            for (UUID contributor : this.previewMap.getContributors()) {
                contributors.add(DARK_GRAY + " - " + SmartPlayer.getOrCreate(contributor).getRealFormattedName());
            }
        }

        smartGUI.addImmutable(6, Material.PAPER, GOLD + "Contributors", contributors);

        List<String> creators = new ArrayList<>();
        if (this.previewMap.getCreators().isEmpty()) {
            creators.add(DARK_GRAY + " - None");
        } else {
            for (UUID creator : this.previewMap.getCreators()) {
                creators.add(DARK_GRAY + " - " + SmartPlayer.getOrCreate(creator).getRealFormattedName());
            }
        }

        smartGUI.addImmutable(2, Material.PAPER, GOLD + "Previous Creators", creators);

        smartGUI.addImmutable(13, this.previewMap.getRepresentative(), DARK_AQUA + "Stats",
                String.format("%sSize: %s%dx%d", YELLOW, WHITE, this.previewMap.getSize(), this.previewMap.getSize()),
                String.format("%sBlocks: %s%d", YELLOW, WHITE, this.previewMapBlocks.size()),
                String.format("%sPlays: %s%d", YELLOW, WHITE, this.previewMap.getPlays(0)),
                String.format("%sVotes: %s%d", YELLOW, WHITE, this.previewMap.getVotes(0)));

        smartGUI.fillEmptySpots();

        this.openGui(smartGUI);
    }

    public boolean isCreatingOwnedCustomMap() {
        return this.isCreatingCustomMap && this.customMapEdited.getOwner().equals(this.getUUID());
    }

    public boolean hasCollaborator(HubPlayer player) {
        return this.mapCollaborators.contains(player);
    }

    List<HubPlayer> mapCollaborators = new ArrayList<>();
    List<UUID> allCollaborators = new ArrayList<>();

    public void kickCollaborator(HubPlayer player) {
        player.sendErrorMessage("You have been kicked from map editing!");
        player.spawn(true);
        player.playErrorSound();
        this.sendSuccessMessage("Kicked %s%s from editing.", player.getFormattedName(), GREEN);
        this.mapCollaborators.remove(player);
    }

    public void collaborate(HubPlayer toJoin) {
        this.collaborationInvites.remove(toJoin);
        toJoin.mapCollaborators.add(this);
        toJoin.allCollaborators.add(this.getUUID());
        this.customMapEdited = toJoin.customMapEdited;
        this.customMapCenter = toJoin.customMapCenter;
        this.customMapSpawnA = toJoin.customMapSpawnA;
        this.customMapSpawnB = toJoin.customMapSpawnB;
        this.customMapSize = toJoin.customMapSize;
        this.closeInventory();
        this.smartPlayer.clear();
        this.teleportToCustomMap();
        this.smartPlayer.setGameMode(GameMode.CREATIVE);
        this.smartPlayer.setAllowFlight(true);
        this.getPlayer().setFlying(true);
        this.isCreatingCustomMap = true;
        this.teleportToCustomMap();

        this.playSuccessSound();
        this.sendSuccessMessage("You have joined editing on %s%s custom map.", toJoin.getFormattedName(), GREEN);
        toJoin.sendSuccessMessage("%s%s has joined editing your custom map.", this.getFormattedName(), GREEN);
        this.giveSmartItem(8, SPAWN);
    }

    public void inviteCollaborator(HubPlayer player) {
        player.inviteToCollaborate(this);
        this.sendSuccessMessage("Invite sent. They have 60 seconds to accept.");
    }

    public void inviteToCollaborate(HubPlayer inviter) {
        this.collaborationInvites.add(inviter);
        ComponentBuilder componentBuilder = new ComponentBuilder();
        componentBuilder.gradientLine(ComponentBuilder.GREEN, ComponentBuilder.YELLOW_GREEN, ComponentBuilder.GREEN).newLine();
        componentBuilder.color(GREEN).append(inviter.getComponentFormattedName()).append("%s has invited you to edit their custom map! ", GREEN);
        componentBuilder.command(GOLD + "Click here.", "/custommap join " + inviter.getName(), RelayUtils.format("%sClick here to accept %s%s collaboration request.", AQUA, inviter.getFormattedName(), AQUA));
        componentBuilder.color(YELLOW).append(" You have 60 seconds to accept.").newLine();
        componentBuilder.gradientLine(ComponentBuilder.GREEN, ComponentBuilder.YELLOW_GREEN, ComponentBuilder.GREEN);
        BaseComponent textComponent = componentBuilder.getResult();
        ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/custommap join " + inviter.getName());
        textComponent.setClickEvent(clickEvent);
        String hover = RelayUtils.format("%sClick here to accept %s%s collaboration request.", AQUA, inviter.getFormattedName(), AQUA);
        List<String> lines = Miscellaneous.getList(hover.split("\n"));
        HoverEvent hoverEvent = new HoverEvent(net.md_5.bungee.api.chat.HoverEvent.Action.SHOW_TEXT, Miscellaneous.processList(lines, net.md_5.bungee.api.chat.hover.content.Text::new));
        textComponent.setHoverEvent(hoverEvent);
        this.sendMessage(textComponent);
        Main.scheduleSyncDelayedTask(() -> {
            if (this.collaborationInvites.contains(this)) {
                this.collaborationInvites.remove(this);
                inviter.sendInfoMessage("The collaboration invite to %s%s has expired.", this.getRealFormattedName(), YELLOW);
                this.sendInfoMessage("The collaboration invite from %s%s has expired.", inviter.getFormattedName(), YELLOW);
            }
        }, 60);
    }

    public boolean isCollaboratingCustomMap() {
        return this.isCreatingCustomMap && !this.isCreatingOwnedCustomMap();
    }

    public void deleteKit(String originalId, String id) {
        getCustomDuelKits().removeIf(kit -> kit.getId().equals(id) || kit.getId().equalsIgnoreCase(originalId));
        getSmartPlayer().getCustomDuelsKits().removeIf(kit -> kit.getId().equals(id) || kit.getId().equalsIgnoreCase(originalId));
        closeInventory();
        hasNoKits = customDuelKits.isEmpty();
        playSuccessSound();
        updateCustomKitList();
        updateSelectedCustomDuelsKit();
    }

    public void onDailyJoin() {
        Main.synchronous(() -> {

            ItemStack book = new ItemStack(Material.WRITABLE_BOOK);
            BookMeta bookMeta = (BookMeta) book.getItemMeta();
            Component component = JSONComponentSerializer.json().deserialize("[\"\",{\"text\":\"Welcome to\",\"color\":\"#989898\"},{\"text\":\" Relay \",\"color\":\"#0066FF\"},{\"text\":\"Beta!\",\"color\":\"#FF0003\"},{\"text\":\"\\n\\n\",\"color\":\"reset\"},{\"text\":\"Some features are\",\"color\":\"black\"},{\"text\":\" STILL IN DEVELOPMENT\",\"bold\":true,\"color\":\"dark_green\"},{\"text\":\"\\nso you may experience bugs while playing.\\n\\n\",\"color\":\"reset\"},{\"text\":\"Report them in our discord\",\"underlined\":true,\"color\":\"#0054C5\",\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://discord.gg/zQ2pFMjd9R\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"Click to join the discord!\",\"color\":\"aqua\"}}},{\"text\":\" \",\"color\":\"reset\",\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://discord.gg/zQ2pFMjd9R\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"Click to join the discord!\",\"color\":\"aqua\"}}},{\"text\":\"to help us improve the network!\",\"color\":\"dark_gray\",\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://discord.gg/zQ2pFMjd9R\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"Click to join the discord!\",\"color\":\"aqua\"}}},{\"text\":\"\\n\\n\",\"color\":\"reset\"},{\"text\":\"        Have fun!\",\"italic\":true,\"color\":\"#F7B32B\"}]");

            Book book1 = bookMeta.title(Component.text("Daily Message")).pages(component).author(Component.text("Relay"));

            this.getPlayer().openBook(book1);
            Main.info("daily join");
        });
    }
}