Пример #1
0
        public async Task TankerTank(CommandContext ctx,
                                     [Description("The *gamer tag* or *PSN Name*. If it has spaces, enclose it on quotes.")] string gamerTag,
                                     [Description("The Tank name, as it appears in battles. If it has spaces, enclose it on quotes.")][RemainingText] string tankName)
        {
            // Yeah... it's a Player feature but it uses more calculations for tanks...
            if (!await CanExecute(ctx, Features.Players))
            {
                return;
            }

            if (string.IsNullOrWhiteSpace(gamerTag))
            {
                await ctx.RespondAsync($"Please specify the *Gamer Tag*, {ctx.User.Mention}. Something like `!w tankerTank \"{ctx.User.Username.RemoveDiacritics()}\" \"HWK 12\"`, for example.");

                return;
            }

            if (string.IsNullOrWhiteSpace(tankName))
            {
                await ctx.RespondAsync($"Please specify the *Tank Name*, {ctx.User.Mention}. Something like `!w tankerTank \"{ctx.User.Username.RemoveDiacritics()}\" \"HWK 12\"`, for example.");

                return;
            }

            await ctx.TriggerTypingAsync();

            Log.Debug($"Requesting {nameof(TankerTank)}({gamerTag}, {tankName})...");

            try
            {
                var provider = new DbProvider(_connectionString);

                var playerCommands = new PlayerCommands();
                var player         = await playerCommands.GetPlayer(ctx, gamerTag);

                if (player == null)
                {
                    Log.Debug($"Could not find player {gamerTag}");
                    await ctx.RespondAsync("I could not find a tanker " +
                                           $"with the Gamer Tag `{gamerTag}` on the Database, {ctx.User.Mention}. I may not track this player, or the Gamer Tag is wrong.");

                    return;
                }
                gamerTag = player.Name;

                var tank = FindTank(player.Plataform, tankName, out _);

                if (tank == null)
                {
                    await ctx.RespondAsync($"Can't find a tank with `{tankName}` on the name, {ctx.User.Mention}.");

                    return;
                }

                var tr = provider.GetTanksReferences(tank.Plataform, null, tank.TankId, false, false, false).FirstOrDefault();

                if (tr == null)
                {
                    await ctx.RespondAsync($"Sorry, there is no tank statistics for the `{tank.Name}`, {ctx.User.Mention}.");

                    return;
                }

                var hist = provider.GetPlayerHistoryByTank(player.Plataform, player.Id, tank.TankId).ToArray();
                if (!hist.Any())
                {
                    await ctx.RespondAsync($"Sorry, there is no tank statistics history for the `{tank.Name}` for the player `{gamerTag}`, {ctx.User.Mention}.");

                    return;
                }

                var wn8Expected = provider.GetWn8ExpectedValues(player.Plataform);

                foreach (var h in hist)
                {
                    h.Wn8 = wn8Expected.CalculateWn8(h);
                }

                TankPlayerStatistics[] head, tail;
                if (hist.Length > 20)
                {
                    head = hist.Take(15).ToArray();
                    tail = hist.Skip(hist.Length - 5).Take(5).ToArray();
                }
                else
                {
                    head = hist;
                    tail = null;
                }

                var sb = new StringBuilder();

                sb.AppendLine($"History of the {Formatter.MaskedUrl(tr.Name, new Uri(tr.Url))}, Tier {tr.Tier.ToRomanNumeral()}, {tr.Nation.GetDescription()}, {(tr.IsPremium ? "Premium" : "Regular")}, as played by {Formatter.MaskedUrl(gamerTag, new Uri(player.PlayerOverallUrl))}, {ctx.User.Mention}:");
                sb.AppendLine();
                sb.AppendLine("```");
                sb.AppendLine("Date       Battles Tot.Dmg   WN8");
                foreach (var t in head)
                {
                    sb.AppendLine($"{t.LastBattle:yyyy-MM-dd} {t.Battles.ToString("N0").PadLeft(7)} {t.TotalDamagePerBattle.ToString("N0").PadLeft(7)} {t.Wn8.ToString("N0").PadLeft(6)}");
                }
                if (tail != null)
                {
                    sb.AppendLine("...");
                    foreach (var t in tail)
                    {
                        sb.AppendLine($"{t.LastBattle:yyyy-MM-dd} {t.Battles.ToString("N0").PadLeft(7)} {t.TotalDamagePerBattle.ToString("N0").PadLeft(7)} {t.Wn8.ToString("N0").PadLeft(6)}");
                    }
                }
                sb.AppendLine("```");
                sb.AppendLine();

                var ptr = player.Performance.All[tr.TankId];

                sb.AppendLine($"**{player.Name}** current values:");
                sb.AppendLine($"Total Damage: {ptr.TotalDamagePerBattle:N0} ");
                sb.AppendLine($"Direct Damage: {ptr.DirectDamagePerBattle:N0} ");
                sb.AppendLine($"Assisted Damage: {ptr.DamageAssistedPerBattle:N0} ");
                sb.AppendLine($"WN8: {ptr.Wn8:N0}; Win Rate: {ptr.WinRate:P1}");
                sb.AppendLine($"Battles: {ptr.Battles:N0}; Hours Battling: {ptr.BattleLifeTime.TotalHours:N0}");
                sb.AppendLine($"Max Kills: {ptr.MaxFrags:N1}; Avg Kills: {ptr.KillsPerBattle:N1}");
                if (tr.LastMonth != null)
                {
                    sb.AppendLine();
                    sb.AppendLine($"**Global Recent** average among {tr.LastMonth.TotalPlayers:N0} players of the {tr.Name}:");
                    sb.AppendLine($"Total Damage: {tr.LastMonth.TotalDamage:N0} ");
                    sb.AppendLine($"Direct Damage: {tr.LastMonth.DamageDealt:N0} ");
                    sb.AppendLine($"Assisted Damage: {tr.LastMonth.DamageAssisted:N0} ");
                    sb.AppendLine($"WN8: {tr.LastMonth.AverageWn8:N0}; Win Rate: {tr.LastMonth.WinRatio:P1}");
                    sb.AppendLine($"Battles: {tr.LastMonth.BattlesPerPlayer:N0}; Hours Battling: {tr.LastMonth.TimePerPlayer.TotalHours:N0}");
                    sb.AppendLine($"Max Kills: {tr.LastMonth.MaxKills:N1}; Avg Kills: {tr.LastMonth.Kills:N1}");
                }

                var platformPrefix = tr.Plataform == Platform.PS ? "ps." : string.Empty;

                var color = ptr.Wn8.ToColor();

                var embed = new DiscordEmbedBuilder
                {
                    Title        = $"{gamerTag} history with the {tank.Name}",
                    Description  = sb.ToString(),
                    Color        = new DiscordColor(color.R, color.G, color.B),
                    ThumbnailUrl = tank.SmallImageUrl,
                    Url          = player.PlayerOverallUrl,
                    Author       = new DiscordEmbedBuilder.EmbedAuthor
                    {
                        Name = "WoTClans",
                        Url  = $"https://{platformPrefix}wotclans.com.br"
                    },
                    Footer = new DiscordEmbedBuilder.EmbedFooter
                    {
                        Text = $"Calculated at {player.Moment:yyyy-MM-dd HH:mm} UTC"
                    }
                };

                await ctx.RespondAsync("", embed : embed);
            }
            catch (Exception ex)
            {
                Log.Error($"{nameof(TankerTank)}({gamerTag})", ex);
                await ctx.RespondAsync($"Sorry, {ctx.User.Mention}. There was an error... the *Coder* will be notified of `{ex.Message}`.");
            }
        }
Пример #2
0
        public async Task RandTank(CommandContext ctx,
                                   [Description("The minimum tier of the tanks")] int minTier = 5,
                                   [Description("The maximum tier of the tanks")] int maxTier = 10,
                                   [Description("Nation of the tank, or *any*. Multiple values can be sent using *;* as separators")] string nationFilter = "any",
                                   [Description("Type of the tank, or *any*. Multiple values can be sent using *;* as separators")] string typeFilter     = "any",
                                   [Description("*Premium*, *regular*, or *any*")] string premiumFilter = "any",
                                   [Description("The *gamer tag* or *PSN Name*, so it only returns tanks that the player have (or had)")] string gamerTag = null,
                                   [Description("If *true* then a tank that the given player **hadn't** will be picked")] bool notOnPlayer = false)
        {
            // Yeah... it's a Player feature but it uses more calculations for tanks...
            if (!await CanExecute(ctx, Features.Tanks))
            {
                return;
            }

            if (minTier < 1)
            {
                await ctx.RespondAsync("The minimum tier is 1.");

                return;
            }

            if (minTier > 10)
            {
                await ctx.RespondAsync("The maximum tier is 1.");

                return;
            }

            if (maxTier < 1)
            {
                await ctx.RespondAsync("The minimum tier is 1.");

                return;
            }

            if (maxTier > 10)
            {
                await ctx.RespondAsync("The maximum tier is 1.");

                return;
            }

            if (maxTier < minTier)
            {
                var temp = maxTier;
                maxTier = minTier;
                minTier = temp;
            }

            await ctx.TriggerTypingAsync();

            var cfg = GuildConfiguration.FromGuild(ctx.Guild);

            Log.Debug($"Requesting {nameof(RandTank)}({minTier}, {maxTier}, {nationFilter}, {typeFilter}, {premiumFilter}, {gamerTag ?? string.Empty}, {notOnPlayer})...");

            try
            {
                var provider = new DbProvider(_connectionString);

                var allTanks = provider.EnumTanks(cfg.Plataform).Where(t => (t.Tier >= minTier) && (t.Tier <= maxTier)).ToList();

                if (!string.IsNullOrWhiteSpace(nationFilter) && !nationFilter.EqualsCiAi("any"))
                {
                    var filtersText = nationFilter.Split(new[] { ',', ';', '-', ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    var filters     = new HashSet <Nation>();
                    foreach (var filterText in filtersText)
                    {
                        if (Enum.TryParse <Nation>(filterText, true, out var nation))
                        {
                            filters.Add(nation);
                        }
                        else
                        {
                            await ctx.RespondAsync(
                                $"Sorry, {ctx.User.Mention}, the nation `{filterText}` is not a valid nation. Valid nations are: {string.Join(", ", NationExtensions.GetGameNations().Select(n => $"`{n.ToString().ToLowerInvariant()}`"))}.");

                            return;
                        }
                    }

                    allTanks = allTanks.Where(t => filters.Contains(t.Nation)).ToList();
                }

                if (!string.IsNullOrWhiteSpace(typeFilter) && !typeFilter.EqualsCiAi("any"))
                {
                    var filtersText = typeFilter.Split(new[] { ',', ';', '-', ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    var filters     = new HashSet <TankType>();
                    foreach (var filterText in filtersText)
                    {
                        if (Enum.TryParse <TankType>(filterText, true, out var tankType))
                        {
                            filters.Add(tankType);
                        }
                        else
                        {
                            await ctx.RespondAsync(
                                $"Sorry, {ctx.User.Mention}, the tank type `{filterText}` is not a valid type. Valid tank types are: {string.Join(", ", TankTypeExtensions.GetGameTankTypes().Select(n => $"`{n.ToString().ToLowerInvariant()}`"))}.");

                            return;
                        }
                    }

                    allTanks = allTanks.Where(t => filters.Contains(t.Type)).ToList();
                }

                if (!string.IsNullOrWhiteSpace(premiumFilter) && !premiumFilter.EqualsCiAi("any"))
                {
                    if (premiumFilter.EqualsCiAi("regular"))
                    {
                        allTanks = allTanks.Where(t => !t.IsPremium).ToList();
                    }
                    else if (premiumFilter.EqualsCiAi("premium"))
                    {
                        allTanks = allTanks.Where(t => t.IsPremium).ToList();
                    }
                    else
                    {
                        await ctx.RespondAsync(
                            $"Sorry, {ctx.User.Mention}, the premium filter `{premiumFilter}` is not a valid value. Valid values are: `Regular`, `Premium` or `Any`.");

                        return;
                    }
                }

                if (!string.IsNullOrWhiteSpace(gamerTag))
                {
                    var playerCommands = new PlayerCommands();
                    var player         = await playerCommands.GetPlayer(ctx, gamerTag);

                    if (player == null)
                    {
                        Log.Debug($"Could not find player {gamerTag}");
                        await ctx.RespondAsync("I could not find a tanker " +
                                               $"with the Gamer Tag `{gamerTag}` on the Database, {ctx.User.Mention}. I may not track this player, or the Gamer Tag is wrong.");

                        return;
                    }
                    gamerTag = player.Name;

                    var playerTanks = player.Performance.All.Select(kv => kv.Key).ToHashSet();

                    allTanks = notOnPlayer
                        ? allTanks.Where(t => !playerTanks.Contains(t.TankId)).ToList()
                        : allTanks.Where(t => playerTanks.Contains(t.TankId)).ToList();
                }

                if (allTanks.Count <= 0)
                {
                    await ctx.RespondAsync(
                        $"Sorry, {ctx.User.Mention}, there are no tanks with the given criteria.");

                    return;
                }

                var number = Rand.Next(0, allTanks.Count);

                var tank = allTanks[number];

                var platformPrefix = tank.Plataform == Platform.PS ? "ps." : string.Empty;

                var sb = new StringBuilder();

                sb.AppendLine($"The random tank is the `{tank.Name}`, Tier {tank.Tier.ToRomanNumeral()}, {tank.Nation.GetDescription()}, {(tank.IsPremium ? "Premium" : "Regular")}, {ctx.User.Mention}!");

                var embed = new DiscordEmbedBuilder
                {
                    Title        = $"{tank.Name} is the random Tank",
                    Description  = sb.ToString(),
                    Color        = DiscordColor.Gray,
                    ThumbnailUrl = tank.SmallImageUrl,
                    Url          = $"https://{platformPrefix}wotclans.com.br/Tanks/{tank.TankId}",
                    Author       = new DiscordEmbedBuilder.EmbedAuthor
                    {
                        Name = "WoTClans",
                        Url  = $"https://{platformPrefix}wotclans.com.br"
                    },
                    Footer = new DiscordEmbedBuilder.EmbedFooter
                    {
                        Text = $"Picked at {DateTime.UtcNow:yyyy-MM-dd HH:mm}."
                    }
                };

                await ctx.RespondAsync("", embed : embed);
            }
            catch (Exception ex)
            {
                Log.Error($"Error on {nameof(RandTank)}({minTier}, {maxTier}, {nationFilter}, {typeFilter}, {premiumFilter}, {gamerTag ?? string.Empty}, {notOnPlayer})", ex);
                await ctx.RespondAsync($"Sorry, {ctx.User.Mention}. There was an error... the *Coder* will be notified of `{ex.Message}`.");
            }
        }
Пример #3
0
        public async Task ClanTopOnTank(CommandContext ctx,
                                        [Description("The clan **tag**")] string clanTag,
                                        [Description("The Tank name, as it appears in battles. If it has spaces, enclose it on quotes.")][RemainingText] string tankName)
        {
            if (!await CanExecute(ctx, Features.Clans))
            {
                return;
            }

            await ctx.TriggerTypingAsync();

            if (string.IsNullOrWhiteSpace(clanTag))
            {
                await ctx.RespondAsync($"You must send a clan tag as parameter, {ctx.User.Mention}.");

                return;
            }

            Log.Debug($"Requesting {nameof(ClanTopOnTank)}({clanTag}, {tankName})...");

            var cfg      = GuildConfiguration.FromGuild(ctx.Guild);
            var platform = GetPlatform(clanTag, cfg.Plataform, out clanTag);

            if (!ClanTagRegex.IsMatch(clanTag))
            {
                await ctx.RespondAsync($"You must send a **valid** clan **tag** as parameter, {ctx.User.Mention}.");

                return;
            }

            var provider = new DbProvider(_connectionString);

            var clan = provider.GetClan(platform, clanTag);

            if (clan == null)
            {
                platform = platform == Platform.PS ? Platform.XBOX : Platform.PS;

                clan = provider.GetClan(platform, clanTag);
                if (clan == null)
                {
                    await ctx.RespondAsync(
                        $"Can't find on a clan with tag `[{clanTag}]`, {ctx.User.Mention}. Maybe my site doesn't track it yet... or you have the wrong clan tag.");

                    return;
                }
            }

            if (!clan.Enabled)
            {
                await ctx.RespondAsync(
                    $"Data collection for the `[{clan.ClanTag}]` is disabled, {ctx.User.Mention}. Maybe the clan went too small, or inactive.");

                return;
            }

            var tankCommands = new TankCommands();
            var tank         = tankCommands.FindTank(platform, tankName, out _);

            if (tank == null)
            {
                await ctx.RespondAsync($"Can't find a tank with `{tankName}` on the name, {ctx.User.Mention}.");

                return;
            }

            var tr = provider.GetTanksReferences(tank.Plataform, null, tank.TankId, false, false, false).FirstOrDefault();

            if (tr == null)
            {
                await ctx.RespondAsync($"Sorry, there is no tank statistics for the `{tank.Name}`, {ctx.User.Mention}.");

                return;
            }

            if (tr.Tier < 5)
            {
                await ctx.RespondAsync($"Sorry, this command is meant to be used only with tanks Tier 5 and above, {ctx.User.Mention}.");

                return;
            }

            var players = provider.GetClanPlayerIdsOnTank(platform, clan.ClanId, tr.TankId).ToList();

            if (players.Count <= 0)
            {
                await ctx.RespondAsync($"No players from the `[{clan.ClanTag}]` has battles on the `{tank.Name}`, {ctx.User.Mention}, as far as the database is up to date.");

                return;
            }

            var waitMsg = await ctx.RespondAsync($"Please wait as data for {players.Count} tankers is being retrieved, {ctx.User.Mention}...");

            var playerCommands = new PlayerCommands();

            var fullPlayers = new ConcurrentBag <Player>();
            var tasks       = players.Select(async p =>
            {
                var player = await playerCommands.GetPlayer(ctx, ((p.Plataform == Platform.XBOX) ? "x." : "ps.") + p.Name);
                if (player == null)
                {
                    await ctx.RespondAsync($"Sorry, could not get updated information for player `{p.Name}`, {ctx.User.Mention}.");
                    return;
                }

                fullPlayers.Add(player);
            });
            await Task.WhenAll(tasks);

            await waitMsg.DeleteAsync();

            var sb = new StringBuilder();

            var maxNameLength = fullPlayers.Max(p => p.Name.Length);

            //sb.AppendLine($"Here `[{clan.ClanTag}]` top players on the `{tank.Name}`, {ctx.User.Mention}:");
            //sb.AppendLine();
            sb.AppendLine("```");
            sb.AppendLine($"{platform.TagName().PadRight(maxNameLength)} {"Days".PadLeft(5)} {"Battles".PadLeft(7)} {"WN8".PadLeft(6)}");
            foreach (var p in fullPlayers.OrderByDescending(p => p.Performance.All[tank.TankId].Wn8).Take(25))
            {
                var tp = p.Performance.All[tank.TankId];
                sb.AppendLine($"{(p.Name ?? string.Empty).PadRight(maxNameLength)} {(DateTime.UtcNow - tp.LastBattle).TotalDays.ToString("N0").PadLeft(5)} {tp.Battles.ToString("N0").PadLeft(7)} {tp.Wn8.ToString("N0").PadLeft(6)}");
            }
            sb.AppendLine("```");
            sb.AppendLine();
            sb.AppendLine("This command is a **Premium** feature on the bot. For now it's free to use this command, but be advised that on the near future access will be restricted to Premium subscribers.");

            var color          = clan.Top15Wn8.ToColor();
            var platformPrefix = clan.Plataform == Platform.PS ? "ps." : string.Empty;

            var embed = new DiscordEmbedBuilder
            {
                Title        = $"`{clan.ClanTag}` top players on the `{tank.Name}`",
                Description  = sb.ToString(),
                Color        = new DiscordColor(color.R, color.G, color.B),
                ThumbnailUrl = tank.SmallImageUrl,
                Url          = $"https://{platformPrefix}wotclans.com.br/Clan/{clan.ClanTag}",
                Author       = new DiscordEmbedBuilder.EmbedAuthor
                {
                    Name = "WoTClans",
                    Url  = $"https://{platformPrefix}wotclans.com.br"
                },
                Footer = new DiscordEmbedBuilder.EmbedFooter
                {
                    Text = $"Calculated at {DateTime.UtcNow:yyyy-MM-dd HH:mm} UTC"
                }
            };

            await ctx.RespondAsync("", embed : embed);
        }
Пример #4
0
        public async Task TankerTank(CommandContext ctx,
                                     [Description("The *gamer tag* or *PSN Name*. If it has spaces, enclose it on quotes.")] string gamerTag,
                                     [Description("The Tank name, as it appears in battles. If it has spaces, enclose it on quotes.")] string tankName,
                                     [Description("Shows additional info explaining the calculated WN8")] bool explainWn8 = false)
        {
            // Yeah... it's a Player feature but it uses more calculations for tanks...
            if (!await CanExecute(ctx, Features.Players))
            {
                return;
            }

            if (string.IsNullOrWhiteSpace(gamerTag))
            {
                await ctx.RespondAsync($"Please specify the *Gamer Tag*, {ctx.User.Mention}. Something like `!w tankerTank \"{ctx.User.Username.RemoveDiacritics()}\" \"HWK 12\"`, for example.");

                return;
            }

            if (string.IsNullOrWhiteSpace(tankName))
            {
                await ctx.RespondAsync($"Please specify the *Tank Name*, {ctx.User.Mention}. Something like `!w tankerTank \"{ctx.User.Username.RemoveDiacritics()}\" \"HWK 12\"`, for example.");

                return;
            }

            await ctx.TriggerTypingAsync();

            Log.Debug($"Requesting {nameof(TankerTank)}({gamerTag}, {tankName}, {explainWn8})...");

            try
            {
                var provider = new DbProvider(_connectionString);

                var playerCommands = new PlayerCommands();
                var player         = await playerCommands.GetPlayer(ctx, gamerTag);

                if (player == null)
                {
                    Log.Debug($"Could not find player {gamerTag}");
                    await ctx.RespondAsync("I could not find a tanker " +
                                           $"with the Gamer Tag `{gamerTag}` on the Database, {ctx.User.Mention}. I may not track this player, or the Gamer Tag is wrong.");

                    return;
                }
                gamerTag = player.Name;

                var tank = FindTank(tankName, out _);

                if (tank == null)
                {
                    await ctx.RespondAsync($"Can't find a tank with `{tankName}` on the name, {ctx.User.Mention}.");

                    return;
                }

                var tr = provider.GetTanksReferences(null, tank.TankId, includeMoe: false, includeHistogram: false, includeLeaders: false).FirstOrDefault();

                if (tr == null)
                {
                    await ctx.RespondAsync($"Sorry, there is no tank statistics for the `{tank.Name}`, {ctx.User.Mention}.");

                    return;
                }

                var hist = provider.GetPlayerHistoryByTank(player.Platform, player.Id, tank.TankId).ToArray();
                if (!hist.Any())
                {
                    await ctx.RespondAsync($"Sorry, there is no tank statistics history for the `{tank.Name}` for the player `{gamerTag}`, {ctx.User.Mention}.");

                    return;
                }

                var wn8Expected = provider.GetWn8ExpectedValues();

                foreach (var h in hist)
                {
                    h.Wn8 = wn8Expected.CalculateWn8(h);
                }

                TankPlayerStatistics[] head, tail;
                if (hist.Length > 20)
                {
                    head = hist.Take(15).ToArray();
                    tail = hist.Skip(hist.Length - 5).Take(5).ToArray();
                }
                else
                {
                    head = hist;
                    tail = null;
                }

                var sb = new StringBuilder();

                sb.AppendLine($"History of the {Formatter.MaskedUrl(tr.Name, new Uri(tr.Url))}, Tier {tr.Tier.ToRomanNumeral()}, {tr.Nation.GetDescription()}, {(tr.IsPremium ? "Premium" : "Regular")}, as played by {Formatter.MaskedUrl(gamerTag, new Uri(player.PlayerOverallUrl))}, {ctx.User.Mention}:");
                sb.AppendLine();
                sb.AppendLine("```");
                sb.AppendLine("Date       Battles Tot.Dmg   WN8");
                foreach (var t in head)
                {
                    sb.AppendLine($"{t.LastBattle:yyyy-MM-dd} {t.Battles,7:N0} {t.TotalDamagePerBattle,7:N0} {t.Wn8,6:N0}");
                }
                if (tail != null)
                {
                    sb.AppendLine("...");
                    foreach (var t in tail)
                    {
                        sb.AppendLine($"{t.LastBattle:yyyy-MM-dd} {t.Battles,7:N0} {t.TotalDamagePerBattle,7:N0} {t.Wn8,6:N0}");
                    }
                }
                sb.AppendLine("```");
                sb.AppendLine();

                var ptr = player.Performance.All[tr.TankId];

                sb.AppendLine($"**{player.Name}** current values:");
                sb.AppendLine($"Total Damage: {ptr.TotalDamagePerBattle:N0} ");
                sb.AppendLine($"Direct Damage: {ptr.DirectDamagePerBattle:N0} ");
                sb.AppendLine($"Assisted Damage: {ptr.DamageAssistedPerBattle:N0} ");
                sb.AppendLine($"WN8: {ptr.Wn8:N0}; Win Rate: {ptr.WinRate:P1}");
                sb.AppendLine($"Battles: {ptr.Battles:N0}; Hours Battling: {ptr.BattleLifeTime.TotalHours:N0}");
                sb.AppendLine($"Max Kills: {ptr.MaxFrags:N1}; Avg Kills: {ptr.KillsPerBattle:N1}");
                if (tr.LastMonth != null)
                {
                    sb.AppendLine();
                    sb.AppendLine($"**Global Recent** average among {tr.LastMonth.TotalPlayers:N0} players of the {tr.Name}:");
                    sb.AppendLine($"Total Damage: {tr.LastMonth.TotalDamage:N0} ");
                    sb.AppendLine($"Direct Damage: {tr.LastMonth.DamageDealt:N0} ");
                    sb.AppendLine($"Assisted Damage: {tr.LastMonth.DamageAssisted:N0} ");
                    sb.AppendLine($"WN8: {tr.LastMonth.AverageWn8:N0}; Win Rate: {tr.LastMonth.WinRatio:P1}");
                    sb.AppendLine($"Battles: {tr.LastMonth.BattlesPerPlayer:N0}; Hours Battling: {tr.LastMonth.TimePerPlayer.TotalHours:N0}");
                    sb.AppendLine($"Max Kills: {tr.LastMonth.MaxKills:N1}; Avg Kills: {tr.LastMonth.Kills:N1}");
                }

                var color = ptr.Wn8.ToColor();

                var embed = new DiscordEmbedBuilder
                {
                    Title        = $"{gamerTag} history with the {tank.Name}",
                    Description  = sb.ToString(),
                    Color        = new DiscordColor(color.R, color.G, color.B),
                    ThumbnailUrl = tank.SmallImageUrl,
                    Url          = player.PlayerOverallUrl,
                    Author       = new DiscordEmbedBuilder.EmbedAuthor
                    {
                        Name = "WoTClans",
                        Url  = "https://wotclans.com.br"
                    },
                    Footer = new DiscordEmbedBuilder.EmbedFooter
                    {
                        Text = $"Calculated {player.Moment.Humanize()}"
                    }
                };

                await ctx.RespondAsync("", embed : embed);

                if (!explainWn8)
                {
                    return;
                }

                sb.Clear();
                sb.AppendLine($"Overall WN8 Explanation for the {Formatter.MaskedUrl(tr.Name, new Uri(tr.Url))}, Tier {tr.Tier.ToRomanNumeral()}, {tr.Nation.GetDescription()}, {(tr.IsPremium ? "Premium" : "Regular")}, as played by {Formatter.MaskedUrl(gamerTag, new Uri(player.PlayerOverallUrl))}, {ctx.User.Mention}:");
                sb.AppendLine();

                var e = wn8Expected[tank.TankId];
                sb.AppendLine("**Expected Values**");
                sb.AppendLine($"eDmg  = {e.Damage}");
                sb.AppendLine($"eFrag = {e.Frag}");
                sb.AppendLine($"eSpot = {e.Spot}");
                sb.AppendLine($"eWin  = {e.WinRate}");
                sb.AppendLine($"eDef  = {e.Def}");
                sb.AppendLine();

                sb.AppendLine("**Player Values**");
                sb.AppendLine($"pDmg  = {ptr.DirectDamagePerBattle}");
                sb.AppendLine($"pFrag = {ptr.KillsPerBattle}");
                sb.AppendLine($"pSpot = {ptr.SpotsPerBattle}");
                sb.AppendLine($"pWin  = {ptr.WinRate}");
                sb.AppendLine($"pDef  = {ptr.DroppedCapturePointsPerBattle}");
                sb.AppendLine();

                var rDmg  = ptr.DirectDamagePerBattle / e.Damage;
                var rFrag = ptr.KillsPerBattle / e.Frag;
                var rSpot = ptr.SpotsPerBattle / e.Spot;
                var rWin  = ptr.WinRate / e.WinRate;
                var rDef  = ptr.DroppedCapturePointsPerBattle / e.Def;

                sb.AppendLine("**Relative Values**");
                sb.AppendLine($"rDmg  = pDmg/eDmg   = {rDmg}");
                sb.AppendLine($"rFrag = pFrag/eFrag = {rFrag}");
                sb.AppendLine($"rSpot = pSpot/eSpot = {rSpot}");
                sb.AppendLine($"rWin  = pWin/eWin   = {rWin}");
                sb.AppendLine($"rDef  = pDef/eDef   = {rDef}");
                sb.AppendLine();

                var nDmg  = Math.Max(0, (rDmg - 0.22) / (1.0 - 0.22));
                var nWin  = Math.Max(0, (rWin - 0.71) / (1.0 - 0.71));
                var nFrag = Math.Max(0, Math.Min(nDmg + 0.2, (rFrag - 0.12) / (1 - 0.12)));
                var nSpot = Math.Max(0, Math.Min(nDmg + 0.1, (rSpot - 0.38) / (1 - 0.38)));
                var nDef  = Math.Max(0, Math.Min(nDmg + 0.1, (rDef - 0.10) / (1 - 0.10)));

                sb.AppendLine("**Normalized Values**");
                sb.AppendLine($"nDmg  = Max(0, (rDmg - 0.22) / (1.0 - 0.22)) = {nDmg}");
                sb.AppendLine($"nFrag = Max(0, Min(nDmg + 0.2, (rFrag - 0.12) / (1 - 0.12))) = {nFrag}");
                sb.AppendLine($"nSpot = Max(0, Min(nDmg + 0.1, (rSpot - 0.38) / (1 - 0.38))) = {nSpot}");
                sb.AppendLine($"nWin  = Max(0, (rWin - 0.71) / (1.0 - 0.71)) = {nWin}");
                sb.AppendLine($"nDef  = Max(0, Min(nDmg + 0.1, (rDef - 0.10) / (1 - 0.10))) = {nDef}");
                sb.AppendLine();

                var dmgPart  = 980.0 * nDmg;
                var winPart  = 145.0 * Math.Min(1.8, nWin);
                var fragPart = 210.0 * nDmg * nFrag;
                var spotPart = 155.0 * nFrag * nSpot;
                var defPart  = 75.0 * nFrag * nDef;

                sb.AppendLine("**WN8 Parts**");
                sb.AppendLine($"dmgPart = 980 * nDmg = {dmgPart}");
                sb.AppendLine($"fragPart = 210 * nDmg * nFrag = {fragPart}");
                sb.AppendLine($"spotPart = 155 * nFrag * nSpot = {spotPart}");
                sb.AppendLine($"winPart  = 145 * Min(1.8, nWin) = {winPart}");
                sb.AppendLine($"defPart  = 75 * nFrag * nDef = {defPart}");
                sb.AppendLine();

                var wn8 = dmgPart + winPart + fragPart + spotPart + defPart;
                sb.AppendLine($"**WN8** = dmgPart + fragPart + spotPart + winPart + defPart = **{wn8}**");


                embed = new DiscordEmbedBuilder
                {
                    Title        = $"{gamerTag} WN8 with the {tank.Name}",
                    Description  = sb.ToString(),
                    Color        = new DiscordColor(color.R, color.G, color.B),
                    ThumbnailUrl = tank.SmallImageUrl,
                    Url          = player.PlayerOverallUrl,
                    Author       = new DiscordEmbedBuilder.EmbedAuthor
                    {
                        Name = "WoTClans",
                        Url  = "https://wotclans.com.br"
                    },
                    Footer = new DiscordEmbedBuilder.EmbedFooter
                    {
                        Text = $"Calculated {player.Moment.Humanize()}"
                    }
                };

                await ctx.RespondAsync("", embed : embed);
            }
            catch (Exception ex)
            {
                Log.Error($"{nameof(TankerTank)}({gamerTag})", ex);
                await ctx.RespondAsync($"Sorry, {ctx.User.Mention}. There was an error... the *Coder* will be notified of `{ex.Message}`.");
            }
        }