using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Data.Models.Client.Stats; using Microsoft.EntityFrameworkCore.Internal; using SharedLibraryCore.Dtos; using Stats.Dtos; namespace IW4MAdmin.Plugins.Stats { public static class Extensions { private const int ZScoreRange = 3; private const int RankIconDivisions = 24; public class LogParams { public double Mean { get; set; } public double Sigma { get; set; } } public static DateTime FifteenDaysAgo() => DateTime.UtcNow.AddDays(-15); public static double? WeightValueByPlaytime(this IEnumerable stats, string propertyName, int minTimePlayed, Func validation = null) { if (!stats.Any()) { return null; } validation ??= (item) => item.Performance > 0 && item.TimePlayed >= minTimePlayed; var items = stats.Where(validation).ToList(); var performancePlayTime = items.Sum(s => s.TimePlayed); var propInfo = typeof(EFClientStatistics).GetProperty(propertyName); var weightedValues = items.Sum(item => (double?) propInfo?.GetValue(item) * (item.TimePlayed / (double) performancePlayTime)); return weightedValues.Equals(double.NaN) ? 0 : weightedValues ?? 0; } public static LogParams GenerateDistributionParameters(this IEnumerable values) { if (!values.Any()) { return new LogParams() { Mean = 0, Sigma = 0 }; } var ti = 0.0; var ti2 = 0.0; var n = 0L; foreach (var val in values) { var logVal = Math.Log(val); ti += logVal * logVal; ti2 += logVal; n++; if (n % 50 == 0) // this isn't ideal, but we want to reduce the amount of CPU usage that the // loops takes so people don't complain { Thread.Sleep(1); } } var mean = ti2 / n; ti2 *= ti2; var bottom = n == 1 ? 1 : n * (n - 1); var sigma = Math.Sqrt(((n * ti) - ti2) / bottom); return new LogParams() { Sigma = sigma, Mean = mean }; } public static double? GetRatingForZScore(this double? zScore, double maxZScore) { const int ratingScalar = 1000; if (!zScore.HasValue) { return null; } // we just want everything positive so we can go from 0-max var adjustedZScore = zScore < -ZScoreRange ? 0 : zScore + ZScoreRange; return adjustedZScore / (maxZScore + ZScoreRange) * ratingScalar; } public static int RankIconIndexForZScore(this double? zScore) { if (zScore == null) { return 0; } const double divisionIncrement = (ZScoreRange * 2) / (double) RankIconDivisions; var rank = 1; for (var i = rank; i <= RankIconDivisions; i++) { var bottom = Math.Round(-ZScoreRange + (i - 1) * divisionIncrement, 5); var top = Math.Round(-ZScoreRange + i * divisionIncrement, 5); if (zScore > bottom && zScore <= top) { return rank; } if (i == 1 && zScore < bottom // catch all for really bad players // catch all for very good players || i == RankIconDivisions && zScore > top) { return i; } rank++; } return 0; } } }