IW4M-Admin/Plugins/Stats/Extensions.cs
2023-08-27 12:28:35 -05:00

131 lines
4.0 KiB
C#

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<EFClientStatistics> stats, string propertyName,
int minTimePlayed, Func<EFClientStatistics, bool> 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<double> 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;
}
}
}