@using SharedLibraryCore.Configuration
@using Data.Models.Client.Stats
@using Stats.Helpers
@using Data.Models.Client
@using Data.Models.Client.Stats.Reference
@using Humanizer
@using Humanizer.Localisation
@using IW4MAdmin.Plugins.Stats
@model Stats.Dtos.AdvancedStatsInfo
@{
ViewBag.Title = "Advanced Client Statistics";
ViewBag.Description = Model.ClientName.StripColors();
const int maxItems = 5;
const string headshotKey = "MOD_HEAD_SHOT";
const string headshotKey2 = "headshot";
const string meleeKey = "MOD_MELEE";
var suicideKeys = new[] {"MOD_SUICIDE", "MOD_FALLING"};
// if they've not copied default settings config this could be null
var config = (GameStringConfiguration) ViewBag.Config ?? new GameStringConfiguration();
var headerClass = Model.Level == EFClient.Permission.Banned ? "bg-danger" : "bg-primary";
var textClass = Model.Level == EFClient.Permission.Banned ? "text-danger" : "text-primary";
var borderBottomClass = Model.Level == EFClient.Permission.Banned ? "border-bottom-danger border-top-danger" : "border-bottom border-top";
var borderClass = Model.Level == EFClient.Permission.Banned ? "border-danger" : "border-primary";
var buttonClass = Model.Level == EFClient.Permission.Banned ? "btn-danger" : "btn-primary";
string GetWeaponNameForHit(EFClientHitStatistic stat)
{
if (stat == null)
{
return null;
}
var rebuiltName = stat.RebuildWeaponName();
var name = config.GetStringForGame(rebuiltName, stat.Weapon?.Game);
return !rebuiltName.Equals(name, StringComparison.InvariantCultureIgnoreCase)
? name
: config.GetStringForGame(stat.Weapon.Name, stat.Weapon.Game);
}
string GetWeaponAttachmentName(EFWeaponAttachmentCombo attachment)
{
if (attachment == null)
{
return null;
}
var attachmentText = string.Join('+', new[]
{
config.GetStringForGame(attachment.Attachment1.Name, attachment.Attachment1.Game),
config.GetStringForGame(attachment.Attachment2?.Name, attachment.Attachment2?.Game),
config.GetStringForGame(attachment.Attachment3?.Name, attachment.Attachment3?.Game)
}.Where(attach => !string.IsNullOrWhiteSpace(attach)));
return attachmentText;
}
var weapons = Model.ByWeapon
.Where(hit => hit.DamageInflicted > 0 || (hit.DamageInflicted == 0 && hit.HitCount > 0))
.GroupBy(hit => new {hit.WeaponId})
.Select(group =>
{
var withoutAttachments = group.FirstOrDefault(hit => hit.WeaponAttachmentComboId == null);
var mostUsedAttachment = group.Except(new[] {withoutAttachments})
.OrderByDescending(g => g.DamageInflicted)
.GroupBy(g => g.WeaponAttachmentComboId)
.FirstOrDefault()
?.FirstOrDefault();
if (withoutAttachments == null || mostUsedAttachment == null)
{
return withoutAttachments;
}
withoutAttachments.WeaponAttachmentComboId = mostUsedAttachment.WeaponAttachmentComboId;
withoutAttachments.WeaponAttachmentCombo = mostUsedAttachment.WeaponAttachmentCombo;
return withoutAttachments;
})
.Where(hit => hit != null)
.OrderByDescending(hit => hit.KillCount)
.ToList();
var allPerServer = Model.All.Where(hit => hit.ServerId == Model.ServerId).ToList();
// if the serverId is supplied we want all the entries with serverID but nothing else
var aggregate = Model.ServerId == null
? Model.Aggregate
: allPerServer.Where(hit => hit.WeaponId == null)
.Where(hit => hit.HitLocation == null)
.Where(hit => hit.ServerId == Model.ServerId)
.Where(hit => hit.WeaponAttachmentComboId == null)
.FirstOrDefault(hit => hit.MeansOfDeathId == null);
var filteredHitLocations = Model.ByHitLocation
.Where(hit => hit.HitCount > 0)
.Where(hit => hit.HitLocation.Name != "none")
.Where(hit => hit.HitLocation.Name != "neck")
.Where(hit => hit.ServerId == Model.ServerId)
.OrderByDescending(hit => hit.HitCount)
.ThenBy(hit => hit.HitLocationId)
.ToList();
var uniqueWeapons = allPerServer.Any()
? Model.ByWeapon.Where(hit => hit.ServerId == Model.ServerId)
.Where(weapon => weapon.DamageInflicted > 0)
.GroupBy(weapon => weapon.WeaponId)
.Count()
: (int?) null; // want to default to -- in ui instead of 0
var activeTime = weapons.Any()
? TimeSpan.FromSeconds(weapons.Sum(weapon => weapon.UsageSeconds ?? 0))
: (TimeSpan?) null; // want to default to -- in ui instead of 0
var kdr = aggregate == null
? null
: Math.Round(aggregate.KillCount / (float) aggregate.DeathCount, 2).ToString(Utilities.CurrentLocalization.Culture);
var serverLegacyStat = Model.LegacyStats
.FirstOrDefault(stat => stat.ServerId == Model.ServerId);
// legacy stats section
var performance = Model.Performance;
var skill = Model.ServerId != null ? serverLegacyStat?.Skill.ToNumericalString() : Model.LegacyStats.WeightValueByPlaytime(nameof(EFClientStatistics.Skill), 0).ToNumericalString();
var elo = Model.ServerId != null ? serverLegacyStat?.EloRating.ToNumericalString() : Model.LegacyStats.WeightValueByPlaytime(nameof(EFClientStatistics.EloRating), 0).ToNumericalString();
var spm = Model.ServerId != null ? serverLegacyStat?.SPM.ToNumericalString() : Model.LegacyStats.WeightValueByPlaytime(nameof(EFClientStatistics.SPM), 0).ToNumericalString();
var performanceHistory = Model.Ratings
.Select(rating => rating.PerformanceMetric);
if (performance != null)
{
performanceHistory = performanceHistory.Append(performance.Value);
}
var score = allPerServer.Any()
? allPerServer.Sum(stat => stat.Score)
: null;
var headShots = allPerServer.Any()
? allPerServer.Where(hit => hit.MeansOfDeath?.Name == headshotKey || hit.HitLocation?.Name == headshotKey2).Sum(hit => hit.HitCount)
: (int?) null; // want to default to -- in ui instead of 0
var meleeKills = allPerServer.Any()
? allPerServer.Where(hit => hit.MeansOfDeath?.Name == meleeKey).Sum(hit => hit.KillCount)
: (int?) null;
var suicides = allPerServer.Any()
? allPerServer.Where(hit => suicideKeys.Contains(hit.MeansOfDeath?.Name ?? "")).Sum(hit => hit.KillCount)
: (int?) null;
var statCards = new[]
{
new
{
Name = (ViewBag.Localization["PLUGINS_STATS_TEXT_KILLS"] as string).Titleize(),
Value = aggregate?.KillCount.ToNumericalString()
},
new
{
Name = (ViewBag.Localization["PLUGINS_STATS_TEXT_DEATHS"] as string).Titleize(),
Value = aggregate?.DeathCount.ToNumericalString()
},
new
{
Name = (ViewBag.Localization["PLUGINS_STATS_TEXT_KDR"] as string).Titleize(),
Value = kdr
},
new
{
Name = (ViewBag.Localization["WEBFRONT_ADV_STATS_SCORE"] as string).Titleize(),
Value = score.ToNumericalString()
},
new
{
Name = (ViewBag.Localization["WEBFRONT_ADV_STATS_ZSCORE"] as string),
Value = Model.ZScore.ToNumericalString(2)
},
new
{
Name = (ViewBag.Localization["PLUGINS_STATS_TEXT_SKILL"] as string).ToLower().Titleize(),
Value = skill
},
new
{
Name = (ViewBag.Localization["WEBFRONT_ADV_STATS_ELO"] as string).Titleize(),
Value = elo
},
new
{
Name = (ViewBag.Localization["PLUGINS_STATS_META_SPM"] as string).Titleize(),
Value = spm
},
new
{
Name = ViewBag.Localization["WEBFRONT_ADV_STATS_TOTAL_DAMAGE"] as string,
Value = aggregate?.DamageInflicted.ToNumericalString()
},
new
{
Name = ViewBag.Localization["WEBFRONT_ADV_STATS_SUICIDES"] as string,
Value = suicides.ToNumericalString()
},
new
{
Name = ViewBag.Localization["WEBFRONT_ADV_STATS_HEADSHOTS"] as string,
Value = headShots.ToNumericalString()
},
new
{
Name = ViewBag.Localization["WEBFRONT_ADV_STATS_MELEES"] as string,
Value = meleeKills.ToNumericalString()
},
new
{
Name = ViewBag.Localization["WEBFRONT_ADV_STATS_FAV_WEAP"] as string,
Value = GetWeaponNameForHit(weapons.FirstOrDefault())
},
new
{
Name = ViewBag.Localization["WEBFRONT_ADV_STATS_FAV_ATTACHMENTS"] as string,
Value = GetWeaponAttachmentName(weapons.FirstOrDefault()?.WeaponAttachmentCombo)
},
new
{
Name = ViewBag.Localization["WEBFRONT_ADV_STATS_TOTAL_WEAPONS_USED"] as string,
Value = uniqueWeapons.ToNumericalString()
},
new
{
Name = ViewBag.Localization["WEBFRONT_ADV_STATS_TOTAL_ACTIVE_TIME"] as string,
Value = activeTime?.HumanizeForCurrentCulture()
}
};
}
@foreach (var card in statCards)
{
@if (string.IsNullOrWhiteSpace(card.Value))
{
—
}
else
{
@card.Value
}
@card.Name
}
@foreach (var weaponHit in weapons.Take(maxItems))
{
@GetWeaponNameForHit(weaponHit) |
@{ var attachments = GetWeaponAttachmentName(weaponHit.WeaponAttachmentCombo); }
@if (string.IsNullOrWhiteSpace(attachments))
{
— |
}
else
{
@attachments |
}
@weaponHit.KillCount.ToNumericalString() |
@weaponHit.HitCount.ToNumericalString() |
@weaponHit.DamageInflicted.ToNumericalString() |
@TimeSpan.FromSeconds(weaponHit.UsageSeconds ?? 0).HumanizeForCurrentCulture(minUnit: TimeUnit.Second) |
}
@foreach (var weaponHit in weapons.Skip(maxItems))
{
@GetWeaponNameForHit(weaponHit) |
@{ var attachments = GetWeaponAttachmentName(weaponHit.WeaponAttachmentCombo); }
@if (string.IsNullOrWhiteSpace(attachments))
{
— |
}
else
{
@attachments |
}
@weaponHit.KillCount.ToNumericalString() |
@weaponHit.HitCount.ToNumericalString() |
@weaponHit.DamageInflicted.ToNumericalString() |
@TimeSpan.FromSeconds(weaponHit.UsageSeconds ?? 0).HumanizeForCurrentCulture() |
}
@{
var totalHits = filteredHitLocations.Sum(hit => hit.HitCount);
}
@foreach (var hitLocation in filteredHitLocations.Take(8))
{
@config.GetStringForGame(hitLocation.HitLocation.Name, hitLocation.HitLocation.Game) |
@hitLocation.HitCount |
@Math.Round((hitLocation.HitCount / (float) totalHits) * 100.0).ToString(Utilities.CurrentLocalization.Culture)% |
@hitLocation.DamageInflicted.ToNumericalString() |
}
@foreach (var hitLocation in filteredHitLocations.Skip(8))
{
@config.GetStringForGame(hitLocation.HitLocation.Name, hitLocation.HitLocation.Game) |
@hitLocation.HitCount |
@Math.Round((hitLocation.HitCount / (float) totalHits) * 100.0).ToString(Utilities.CurrentLocalization.Culture)% |
@hitLocation.DamageInflicted.ToNumericalString() |
}
@{
var projection = filteredHitLocations.Select(loc => new
{
name = loc.HitLocation.Name,
// we want to count head and neck as the same
percentage = (loc.HitLocation.Name == "head"
? filteredHitLocations.FirstOrDefault(c => c.HitLocation.Name == "neck")?.HitCount ?? 0 + loc.HitCount
: loc.HitCount) / (float) totalHits
}).ToList();
var maxPercentage = projection.Any() ? projection.Max(p => p.percentage) : 0;
}
@section scripts
{
}