internal override void Handle(ClientSession session, JsonSendPmIncomingMessage message)
    {
        if (session.IsGuest)
        {
            return;
        }

        if (message.Message.Length > 0)
        {
            UserManager.TryGetUserDataByNameAsync(message.ReceiverUsername).ContinueWith((task) =>
            {
                PlayerUserData userData = task.Result;
                if (userData != null)
                {
                    PrivateMessageManager.SendTextPrivateMessageAsync(userData.Id, session.UserData.Id, message.Title, message.Message);
                }
                else
                {
                    session.SendPacket(new AlertOutgoingMessage("User was not found!"));
                }
            });
        }
        else
        {
            session.SendPacket(new AlertOutgoingMessage("What if you typed message before sending?"));
        }
    }
        public void OnCommand(ICommandExecutor executor, string label, ReadOnlySpan <string> args)
        {
            if (args.Length == 2)
            {
                PlayerUserData playerUserData = UserManager.TryGetUserDataByNameAsync(args[0]).Result;
                if (playerUserData != null)
                {
                    if (!uint.TryParse(args[1], out uint amount))
                    {
                        executor.SendMessage("The amount must be unsigned integer");

                        return;
                    }

                    if (PlatformRacing3Server.ClientManager.TryGetClientSessionByUserId(playerUserData.Id, out ClientSession session) && session.UserData != null)
                    {
                        session.UserData.GiveBonusExp(amount);
                    }
                    else
                    {
                        playerUserData.GiveBonusExp(amount);
                    }
                }
                else
                {
                    executor.SendMessage($"Unable to find user named {args[0]}");
                }
            }
            else
            {
                executor.SendMessage("Usage: /givebonusexp [user] [amount]");
            }
        }
        public async Task <object> IsLoggedInAsync([FromQuery] string id, [FromForm] string gameId)
        {
            if (id == "IsLoggedIn" && gameId == "f1c25e3bd3523110394b5659c68d8092") //Sparkworks
            {
                uint userId = this.HttpContext.IsAuthenicatedPr3User();
                if (userId > 0)
                {
                    PlayerUserData playerUserData = await UserManager.TryGetUserDataByIdAsync(userId);

                    if (playerUserData != null)
                    {
                        return(new IsLoggedInResponse(userId, playerUserData.Username));
                    }
                    else
                    {
                        return(new DataAccessErrorResponse("We were unable to load your user data"));
                    }
                }
                else
                {
                    return(new IsLoggedInResponse());
                }
            }

            return(this.BadRequest());
        }
        public async Task <IDataAccessDataResponse> GetResponse(HttpContext httpContext, XDocument xml)
        {
            uint userId = httpContext.IsAuthenicatedPr3User();

            if (userId > 0)
            {
                XElement data = xml.Element("Params");
                if (data != null)
                {
                    uint   levelId      = (uint?)data.Element("p_level_id") ?? throw new DataAccessProcedureMissingData();
                    uint   levelVersion = (uint?)data.Element("p_level_version") ?? throw new DataAccessProcedureMissingData();
                    string recordRun    = (string)data.Element("p_recorded_run") ?? throw new DataAccessProcedureMissingData();
                    int    finishTime   = (int?)data.Element("p_finish_time") ?? throw new DataAccessProcedureMissingData();

                    CampaignRun campaignRun = null;
                    using (MemoryStream compressedMemoryStream = new MemoryStream(Convert.FromBase64String(recordRun)))
                    {
                        using (InflaterInputStream inflater = new InflaterInputStream(compressedMemoryStream))
                        {
                            using (MemoryStream uncompressedMemoryStream = new MemoryStream())
                            {
                                inflater.CopyTo(uncompressedMemoryStream);

                                campaignRun = JsonConvert.DeserializeObject <CampaignRun>(Encoding.UTF8.GetString(uncompressedMemoryStream.ToArray()));
                            }
                        }
                    }

                    if (campaignRun != null)
                    {
                        PlayerUserData playerUserData = await UserManager.TryGetUserDataByIdAsync(userId);

                        if (campaignRun.Username != playerUserData.Username)
                        {
                            return(new DataAccessErrorResponse("Invalid username"));
                        }

                        await CampaignManager.SaveCampaignRun(userId, levelId, levelVersion, recordRun, finishTime);

                        return(new DataAccessSaveCampaignRun3Response());
                    }
                    else
                    {
                        throw new DataAccessProcedureMissingData();
                    }
                }
                else
                {
                    throw new DataAccessProcedureMissingData();
                }
            }
            else
            {
                return(new DataAccessErrorResponse("You are not logged in!"));
            }
        }
    [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]     //Dynamic get
    public async Task <ContentResult> GetAsync([FromQuery] string code)
    {
        uint userId = this.HttpContext.IsAuthenicatedPr3User();

        if (userId <= 0)
        {
            return(this.Content(@"Please login to the client in the browser! Or <a href=""https://pr3hub.com/login"">click here</a> to do so without the Flash client.", "text/html"));
        }

        if (string.IsNullOrWhiteSpace(code))
        {
            PlayerUserData playerUserData = await UserManager.TryGetUserDataByIdAsync(userId);

            return(this.Content($@"Are you sure that you want to link your {playerUserData.Username} account to your Discord account?

<a href=""https://discordapp.com/api/oauth2/authorize?response_type=code&client_id={Program.Config.DiscordClientId}&scope=identify"">Click here to proceed</a>", "text/html"));
        }

        DiscordTokenResponse tokenResponse = await RequestTokenAsync(code);

        if (!string.IsNullOrWhiteSpace(tokenResponse.Error))
        {
            return(this.Content($"Requesting discord token returned the following error: {tokenResponse.ErrorReason}"));
        }

        DiscordUserResponse user = await GetUser(tokenResponse);

        if (user.Code != null)
        {
            return(this.Content($"Requesting discord profile resulted to the following error: {user.Message}"));
        }

        uint linkedToAccount = await UserManager.HasDiscordLinkage(userId, user.Id);

        if (linkedToAccount == 0)
        {
            if (!await UserManager.TryInsertDiscordLinkage(userId, user.Id))
            {
                return(this.Content("Oops, something went wrong!"));
            }

            return(this.Content("All ok! :sunglasses:"));
        }
        else
        {
            if (linkedToAccount == userId)
            {
                return(this.Content("This account has already been linked to Discord account!"));
            }
            else
            {
                return(this.Content("This Discord account has already been linked to different account!"));
            }
        }
示例#6
0
        public async Task <string> PostAsync([FromForm] string email, [FromForm] string token, [FromForm] string password)
        {
            //Password check
            if (string.IsNullOrWhiteSpace(password))
            {
                return("Please enter password");
            }
            else if (password.Length < RegisterController.PASSWORD_MIN_LENGTH)
            {
                return($"Password must be at least {RegisterController.PASSWORD_MIN_LENGTH} chars long");
            }

            PlayerUserData player = await UserManager.TryGetUserDataByEmailAsync(email);

            if (player == null)
            {
                return("Invalid token");
            }

            string hashedToken;

            using (SHA512 sha = SHA512.Create())
            {
                hashedToken = Convert.ToBase64String(sha.ComputeHash(WebEncoders.Base64UrlDecode(token)));
            }

            uint userId = await UserManager.TryGetForgotPasswordToken(hashedToken);

            if (userId == 0 || userId != player.Id)
            {
                return("Invalid token");
            }

            if (!await UserManager.TryConsumePasswordToken(hashedToken, password, this.HttpContext.Connection.RemoteIpAddress))
            {
                return("Error, try again");
            }

            using (MailMessage mail = new())
            {
                mail.To.Add(email);
                mail.Subject = $"Platform Racing 3 - {player.Username} - Security Alert";
                mail.From    = new MailAddress(Program.Config.SmtpUser);
                mail.Body    = $"Your account {player.Username} password was reset! If you did not do this change your password immediately!";

                Program.SmtpClient.Send(mail);
            }

            return("Password has been reset! Start the grind :sunglasses:");
        }
示例#7
0
    public async Task <IDataAccessDataResponse> GetResponseAsync(HttpContext httpContext, XDocument xml)
    {
        uint userId = httpContext.IsAuthenicatedPr3User();

        if (userId > 0)
        {
            XElement data = xml.Element("Params");
            if (data != null)
            {
                uint   levelId      = (uint?)data.Element("p_level_id") ?? throw new DataAccessProcedureMissingData();
                uint   levelVersion = (uint?)data.Element("p_level_version") ?? throw new DataAccessProcedureMissingData();
                string recordRun    = (string)data.Element("p_recorded_run") ?? throw new DataAccessProcedureMissingData();
                int    finishTime   = (int?)data.Element("p_finish_time") ?? throw new DataAccessProcedureMissingData();

                CampaignRun campaignRun = CampaignRun.FromCompressed(recordRun);

                if (campaignRun != null)
                {
                    PlayerUserData playerUserData = await UserManager.TryGetUserDataByIdAsync(userId);

                    if (campaignRun.Username != playerUserData.Username)
                    {
                        return(new DataAccessErrorResponse("Invalid username"));
                    }

                    await CampaignManager.SaveCampaignRunAsync(userId, levelId, levelVersion, recordRun, finishTime);

                    return(new DataAccessSaveCampaignRun3Response());
                }
                else
                {
                    throw new DataAccessProcedureMissingData();
                }
            }
            else
            {
                throw new DataAccessProcedureMissingData();
            }
        }
        else
        {
            return(new DataAccessErrorResponse("You are not logged in!"));
        }
    }
示例#8
0
    public void OnCommand(ICommandExecutor executor, string label, ReadOnlySpan <string> args)
    {
        if (args.Length >= 2 && args.Length <= 3)
        {
            PlayerUserData playerUserData = UserManager.TryGetUserDataByNameAsync(args[0]).Result;
            if (playerUserData != null)
            {
                Hat hat;
                if (uint.TryParse(args[1], out uint hatId))
                {
                    hat = (Hat)hatId;
                }
                else if (!Enum.TryParse(args[1], ignoreCase: true, out hat))
                {
                    executor.SendMessage($"Unable to find part with name {args[1]}");
                }

                bool temp = false;
                if (args.Length >= 3)
                {
                    bool.TryParse(args[2], out temp);
                }

                if (this.clientManager.TryGetClientSessionByUserId(playerUserData.Id, out ClientSession session) && session.UserData != null)
                {
                    session.UserData.GiveHat(hat, temp);
                }
                else if (!temp)
                {
                    UserManager.GiveHat(playerUserData.Id, hat);
                }
            }
            else
            {
                executor.SendMessage($"Unable to find user named {args[0]}");
            }
        }
        else
        {
            executor.SendMessage("Usage: /givehat [user] [id/name] [temporaly(false)]");
        }
    }
示例#9
0
    public Task GetOnlinePlayersCount()
    {
        return(DoAsync());

        async Task DoAsync()
        {
            uint userId = await UserManager.HasDiscordLinkage(this.Context.User.Id);

            if (userId == 0)
            {
                await this.ReplyAsync("Well bruh, u ain't got ur Discord linkage");

                return;
            }

            PlayerUserData userData = await UserManager.TryGetUserDataByIdAsync(userId);

            EmbedBuilder embed = new();

            BuildHats(embed, userData);
            BuildParts(embed, userData);

            await this.ReplyAsync(message : this.Context.User.Mention, embed : embed.Build());
        public async Task <string> PostAsync([FromForm] string email)
        {
            PlayerUserData player = await UserManager.TryGetUserDataByEmailAsync(email);

            if (player != null)
            {
                if (!await UserManager.CanSendPasswordToken(player.Id))
                {
                    return("Please request new password reset token after half an hour!");
                }

                //Generate 512 bit random token
                byte[] token = new byte[512 / 8];

                using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
                {
                    rng.GetNonZeroBytes(token.AsSpan(start: 0, length: 32));
                }

                using (SHA256 sha = SHA256.Create())
                {
                    byte[] content = Encoding.UTF8.GetBytes(player.Id.ToString() + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + email);

                    if (!sha.TryComputeHash(content, token.AsSpan(start: 32, length: 32), out int bytesWritten) || bytesWritten != 32)
                    {
                        //Should not happen, right?

                        return("Something went wrong when trying to generate the token! Try again!");
                    }
                }

                byte[] tokenHash;

                //Generate hash of the token for database
                using (SHA512 sha = SHA512.Create())
                {
                    tokenHash = sha.ComputeHash(token);
                }

                bool result = await UserManager.TryCreateForgotPasswordToken(player.Id, Convert.ToBase64String(tokenHash), this.HttpContext.Connection.RemoteIpAddress);

                if (!result)
                {
                    return("Something went wrong when trying to create the token! Try again!");
                }

                using (MailMessage mail = new())
                {
                    mail.To.Add(email);
                    mail.Subject = $"Platform Racing 3 - {player.Username} - Password Reset";
                    mail.From    = new MailAddress(Program.Config.SmtpUser);
                    mail.Body    = $"You have requested password reset for your {player.Username} account! Use the following link to reset your password! https://pr3hub.com/resetpassword?email={email}&token={WebEncoders.Base64UrlEncode(token)}";

                    Program.SmtpClient.Send(mail);
                }

                return("We have sent you email!");
            }
            else
            {
                return("User was not found!");
            }
        }
    public void OnCommand(ICommandExecutor executor, string label, ReadOnlySpan <string> args)
    {
        if (args.Length >= 3 && args.Length <= 4)
        {
            PlayerUserData playerUserData = UserManager.TryGetUserDataByNameAsync(args[0]).Result;
            if (playerUserData != null)
            {
                Part part;
                if (uint.TryParse(args[2], out uint partId))
                {
                    part = (Part)partId;
                }
                else if (!Enum.TryParse(args[2], ignoreCase: true, out part))
                {
                    executor.SendMessage($"Unable to find part with name {args[2]}");
                }

                bool temp = false;
                if (args.Length >= 4)
                {
                    bool.TryParse(args[3], out temp);
                }

                switch (args[1])
                {
                case "head":
                {
                    if (this.clientManager.TryGetClientSessionByUserId(playerUserData.Id, out ClientSession session) && session.UserData != null)
                    {
                        session.UserData.GiveHead(part, temp);
                    }
                    else if (!temp)
                    {
                        UserManager.GiveHead(playerUserData.Id, part);
                    }
                }
                break;

                case "body":
                {
                    if (this.clientManager.TryGetClientSessionByUserId(playerUserData.Id, out ClientSession session) && session.UserData != null)
                    {
                        session.UserData.GiveBody(part, temp);
                    }
                    else if (!temp)
                    {
                        UserManager.GiveBody(playerUserData.Id, part);
                    }
                }
                break;

                case "feet":
                {
                    if (this.clientManager.TryGetClientSessionByUserId(playerUserData.Id, out ClientSession session) && session.UserData != null)
                    {
                        session.UserData.GiveFeet(part, temp);
                    }
                    else if (!temp)
                    {
                        UserManager.GiveFeet(playerUserData.Id, part);
                    }
                }
                break;

                case "set":
                {
                    if (this.clientManager.TryGetClientSessionByUserId(playerUserData.Id, out ClientSession session) && session.UserData != null)
                    {
                        session.UserData.GiveSet(part, temp);
                    }
                    else if (!temp)
                    {
                        UserManager.GiveSet(playerUserData.Id, part);
                    }
                }
                break;
                }
            }
            else
            {
                executor.SendMessage($"Unable to find user named {args[0]}");
            }
        }
        else
        {
            executor.SendMessage("Usage: /givepart [user] [head/body/feet/set] [id/name] [temporaly(false)]");
        }
    }
        public async Task <string> RegisterAsync([FromForm] string username, [FromForm] string password, [FromForm(Name = "retype_password")] string retypePassword, [FromForm] string email)
        {
            //Username check
            if (string.IsNullOrWhiteSpace(username))
            {
                return("Please enter username");
            }
            else if (username.Length < RegisterController.USERNAME_MIN_LENGTH)
            {
                return($"Username must be at least {RegisterController.USERNAME_MIN_LENGTH} char(s)");
            }
            else if (username.Length > RegisterController.USERNAME_MAX_LENGTH)
            {
                return($"Username can't be longer than {RegisterController.USERNAME_MAX_LENGTH} char(s)");
            }
            else if (!RegisterController.UsernameRegex.IsMatch(username))
            {
                return("Username may only have characters are a-Z, 0-9, _ and -");
            }

            //Password check
            if (string.IsNullOrWhiteSpace(password))
            {
                return("Please enter password");
            }
            else if (password.Length < RegisterController.PASSWORD_MIN_LENGTH)
            {
                return($"Password must be at least {RegisterController.PASSWORD_MIN_LENGTH} chars long");
            }

            //Retype password check, this should be in client
            if (string.IsNullOrWhiteSpace(retypePassword))
            {
                return("Please retype your password");
            }
            else if (password != retypePassword)
            {
                return("Passwords don't match");
            }

            //Email check
            if (string.IsNullOrWhiteSpace(email))
            {
                return("Please enter email address, this can be used to recover your account");
            }
            else if (email.Length > RegisterController.EMAIL_MAX_LENGTH)
            {
                return($"Email address can't be longer than {RegisterController.EMAIL_MAX_LENGTH} char(s)");
            }
            else
            {
                try
                {
                    new MailAddress(email); //TODO: Alternativly we could use regex
                }
                catch
                {
                    return("Please enter valid email address");
                }
            }

            Task <PlayerUserData> userByName  = UserManager.TryGetUserDataByNameAsync(username);
            Task <PlayerUserData> userByEmail = UserManager.TryGetUserDataByEmailAsync(email); //This speeds up things so lets do this?

            //Check for dublicated username
            if (await userByName != null)
            {
                return("Username has been taken already!");
            }

            //Check for dublicated email
            if (await userByEmail != null)
            {
                return("Email is linked to another user already!");
            }

            PlayerUserData userData = await UserManager.TryCreateNewUserAsync(username, password, email, this.HttpContext.Connection.RemoteIpAddress);

            if (userData == null)
            {
                return("Critical error while trying to create user! Please try again! If this error keeps happening, please report it to the staff team");
            }

            await this.HttpContext.LogOutPr3UserAsync();                 //Log out to make sure authenicate passes successfully

            await this.HttpContext.AuthenicatePr3UserAsync(userData.Id); //Log in the user automatically after register

            return(string.Empty);                                        //Everything is fine, empty requests represents that there was no issues, bad design
        }
示例#13
0
        [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] //Dynamic get
        public async Task <ContentResult> GetAsync([FromQuery] string code)
        {
            uint userId = this.HttpContext.IsAuthenicatedPr3User();

            if (userId > 0)
            {
                if (string.IsNullOrWhiteSpace(code))
                {
                    PlayerUserData playerUserData = await UserManager.TryGetUserDataByIdAsync(userId);

                    return(this.Content($@"Are you sure you want to link your {playerUserData.Username} account to your Discord account?

<a href=""https://discordapp.com/api/oauth2/authorize?response_type=code&client_id={Program.Config.DiscordClientId}&scope=identify"">Click here to proceed</a>", "text/html"));
                }
                else
                {
                    DiscordTokenResponse tokenResponse = await RequestTokenAsync();

                    if (!string.IsNullOrWhiteSpace(tokenResponse.Error))
                    {
                        return(this.Content($"Requesting discord token returned the following error: {tokenResponse.ErrorReason}"));
                    }

                    DiscordUserResponse user = await GetUser();

                    if (user.Code != null)
                    {
                        return(this.Content($"Requesting discord profile resulted to the following error: {user.Message}"));
                    }

                    uint linkedToAccount = await UserManager.HasDiscordLinkage(userId, user.Id);

                    if (linkedToAccount == 0)
                    {
                        if (!await UserManager.TryInsertDiscordLinkage(userId, user.Id))
                        {
                            return(this.Content("Oops, something went wrong!"));
                        }

                        return(this.Content("All ok! :sunglasses:"));
                    }
                    else
                    {
                        if (linkedToAccount == userId)
                        {
                            return(this.Content("This account has already been linked to Discord account!"));
                        }
                        else
                        {
                            return(this.Content("This Discord account has already been linked to different account!"));
                        }
                    }

                    async Task <DiscordUserResponse> GetUser()
                    {
                        using (HttpClient httpClient = new())
                        {
                            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(tokenResponse.TokenType, tokenResponse.AccessToken);

                            using (HttpResponseMessage message = await httpClient.GetAsync(LinkDiscordController.DISCORD_API_ME))
                            {
                                return(JsonConvert.DeserializeObject <DiscordUserResponse>(await message.Content.ReadAsStringAsync()));
                            }
                        }
                    }

                    async Task <DiscordTokenResponse> RequestTokenAsync()
                    {
                        IDictionary <string, string> values = new Dictionary <string, string>()
                        {
                            { "client_id", Program.Config.DiscordClientId },
                            { "client_secret", Program.Config.DiscordClientSecret },
                            { "grant_type", "authorization_code" },
                            { "code", code },
                            { "scope", "identify" },
                        };

                        using (HttpClient httpClient = new())
                        {
                            using (FormUrlEncodedContent content = new(values))
                            {
                                using (HttpResponseMessage message = await httpClient.PostAsync(LinkDiscordController.DISCORD_API_TOKEN, content))
                                {
                                    return(JsonConvert.DeserializeObject <DiscordTokenResponse>(await message.Content.ReadAsStringAsync()));
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                return(this.Content("Please login to the client!"));
            }
        }
        internal override void Handle(ClientSession session, JsonTokenLoginIncomingMessage message)
        {
            if (session.UpgradeClientStatus(ClientStatus.LoggedIn))
            {
                RedisConnection.GetDatabase().StringGetAsync($"logintoken:{message.LoginToken}").ContinueWith((task) =>
                {
                    if (task.IsCompletedSuccessfully)
                    {
                        RedisValue result = task.Result;
                        if (result.HasValue)
                        {
                            UserManager.TryGetUserDataByIdAsync((uint)result, false).ContinueWith((task_) =>
                            {
                                if (task_.IsCompletedSuccessfully)
                                {
                                    PlayerUserData result_ = task_.Result;
                                    if (result_ != null)
                                    {
                                        DatabaseConnection.NewAsyncConnection((dbConnection) => dbConnection.ReadDataAsync($"INSERT INTO base.users_logins(user_id, ip, data, type) VALUES({result_.Id}, {session.IPAddres}, {message.LoginToken}, 'server')").ContinueWith((task__) =>
                                        {
                                            if (task__.IsCompletedSuccessfully)
                                            {
                                                session.UserData = result_;
                                                session.SendPacket(new LoginSuccessOutgoingMessage(session.SocketId, result_.Id, result_.Username, result_.Permissions, result_.GetVars("*")));
                                                session.SendPacket(new FriendsAndIgnoredOutgoingMessage(result_.Friends, result_.Ignored));

                                                PlatformRacing3Server.ClientManager.Add(session);

                                                //Race condition
                                                if (PlatformRacing3Server.ServerManager.TryGetServer(PlatformRacing3Server.ServerConfig.ServerId, out ServerDetails server))
                                                {
                                                    result_.SetServer(server);
                                                }
                                            }
                                            else if (task__.IsFaulted)
                                            {
                                                TokenLoginIncomingMessage.Logger.Error($"Failed to insert login", task.Exception);

                                                session.SendPacket(new LoginErrorOutgoingMessage("Critical error"));
                                            }
                                        }));
                                    }
                                    else
                                    {
                                        session.SendPacket(new LoginErrorOutgoingMessage("User data was not found"));
                                    }
                                }
                                else if (task_.IsFaulted)
                                {
                                    session.SendPacket(new LoginErrorOutgoingMessage("Critical error while trying to load user data"));

                                    TokenLoginIncomingMessage.Logger.Error("Failed to load user data", task_.Exception);
                                }
                                else
                                {
                                    session.SendPacket(new LoginErrorOutgoingMessage("Failed to load user data"));
                                }
                            });
                        }
                        else
                        {
                            session.SendPacket(new LoginErrorOutgoingMessage("This login token is invalid"));
                        }
                    }
                    else if (task.IsFaulted)
                    {
                        session.SendPacket(new LoginErrorOutgoingMessage("Critical error while trying to retrieve login token information"));

                        TokenLoginIncomingMessage.Logger.Error("Failed to retieve login token", task.Exception);
                    }
                    else
                    {
                        session.SendPacket(new LoginErrorOutgoingMessage("Failed to retrieve login token information"));
                    }
                });
            }
        }
示例#15
0
        public Task GetOnlinePlayersCount()
        {
            return(DoAsync());

            async Task DoAsync()
            {
                uint userId = await UserManager.HasDiscordLinkage(this.Context.User.Id);

                if (userId == 0)
                {
                    await this.ReplyAsync("Well bruh, u ain't got ur Discord linkage");

                    return;
                }

                PlayerUserData userData = await UserManager.TryGetUserDataByIdAsync(userId);

                (int Count, int Max)hats = default;

                StringBuilder hatsWriter = new();

                foreach (Hat hat in Enum.GetValues(typeof(Hat)))
                {
                    if (hat == Hat.None || hat.IsStaffOnly())
                    {
                        continue;
                    }

                    hats.Max++;

                    hatsWriter.Append(hat.ToString());
                    hatsWriter.Append(" ");

                    if (userData.HasHat(hat))
                    {
                        hatsWriter.Append("✓");

                        hats.Count++;
                    }
                    else
                    {
                        hatsWriter.Append("✕");
                    }

                    hatsWriter.AppendLine();
                }

                (int Head, int Body, int Feet, int Max)parts        = default;
                (int Head, int Body, int Feet, int Max)tournieParts = default;

                (StringBuilder Head, StringBuilder Body, StringBuilder Feet)partWriters        = (new StringBuilder(), new StringBuilder(), new StringBuilder());
                (StringBuilder Head, StringBuilder Body, StringBuilder Feet)tourniePartWriters = (new StringBuilder(), new StringBuilder(), new StringBuilder());

                foreach (Part part in Enum.GetValues(typeof(Part)))
                {
                    if (part == Part.None || part.IsStaffOnly())
                    {
                        continue;
                    }

                    bool isTournie = part.IsTournamentPrize();

                    CountTo(ref !isTournie ? ref partWriters : ref tourniePartWriters, ref !isTournie ? ref parts : ref tournieParts);

                    void CountTo(ref (StringBuilder Head, StringBuilder Body, StringBuilder Feet) writers, ref (int Head, int Body, int Feet, int Max) partCounter)
                    {
                        partCounter.Max++;

                        AppendToAll(ref writers, part.ToString());
                        AppendToAll(ref writers, " ");

                        if (userData.HasHead(part))
                        {
                            writers.Head.Append("✓");

                            partCounter.Head++;
                        }
                        else
                        {
                            writers.Head.Append("✕");
                        }

                        if (userData.HasBody(part))
                        {
                            writers.Body.Append("✓");

                            partCounter.Body++;
                        }
                        else
                        {
                            writers.Body.Append("✕");
                        }

                        if (userData.HasFeet(part))
                        {
                            writers.Feet.Append("✓");

                            partCounter.Feet++;
                        }
                        else
                        {
                            writers.Feet.Append("✕");
                        }

                        BreakAll(ref writers);
                    }

                    void AppendToAll(ref (StringBuilder Head, StringBuilder Body, StringBuilder Feet) writers, string value)
                    {
                        writers.Head.Append(value);
                        writers.Body.Append(value);
                        writers.Feet.Append(value);
                    }

                    void BreakAll(ref (StringBuilder Head, StringBuilder Body, StringBuilder Feet) writers)
                    {
                        writers.Head.AppendLine();
                        writers.Body.AppendLine();
                        writers.Feet.AppendLine();
                    }
                }

                EmbedBuilder embed = new();

                embed.AddField(new EmbedFieldBuilder()
                {
                    Name  = $"Hats ({hats.Count}/{hats.Max})",
                    Value = hatsWriter.ToString()
                });

                embed.AddField(new EmbedFieldBuilder()
                {
                    Name  = $"Heads ({parts.Head}/{parts.Max})",
                    Value = partWriters.Head.ToString(),

                    IsInline = true
                });

                embed.AddField(new EmbedFieldBuilder()
                {
                    Name  = $"Bodies ({parts.Body}/{parts.Max})",
                    Value = partWriters.Body.ToString(),

                    IsInline = true
                });

                embed.AddField(new EmbedFieldBuilder()
                {
                    Name  = $"Feets ({parts.Feet}/{parts.Max})",
                    Value = partWriters.Feet.ToString(),

                    IsInline = true
                });

                embed.AddField(new EmbedFieldBuilder()
                {
                    Name  = $"Tournie Heads ({tournieParts.Head}/{tournieParts.Max})",
                    Value = tourniePartWriters.Head.ToString(),

                    IsInline = true
                });

                embed.AddField(new EmbedFieldBuilder()
                {
                    Name  = $"Tournie Bodies ({tournieParts.Body}/{tournieParts.Max})",
                    Value = tourniePartWriters.Body.ToString(),

                    IsInline = true
                });

                embed.AddField(new EmbedFieldBuilder()
                {
                    Name  = $"Tournie Feets ({tournieParts.Feet}/{tournieParts.Max})",
                    Value = tourniePartWriters.Feet.ToString(),

                    IsInline = true
                });

                await this.ReplyAsync(message : this.Context.User.Mention, embed : embed.Build());
            }
        }