fix issue with duplicate key on top stats page

This commit is contained in:
RaidMax 2022-07-22 10:28:26 -05:00
parent 507688a175
commit b27ae1517e

View File

@ -86,7 +86,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public async Task<int> GetClientOverallRanking(int clientId, long? serverId = null) public async Task<int> GetClientOverallRanking(int clientId, long? serverId = null)
{ {
await using var context = _contextFactory.CreateContext(enableTracking: false); await using var context = _contextFactory.CreateContext(enableTracking: false);
if (_config.EnableAdvancedMetrics) if (_config.EnableAdvancedMetrics)
{ {
var clientRanking = await context.Set<EFClientRankingHistory>() var clientRanking = await context.Set<EFClientRankingHistory>()
@ -117,7 +117,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return 0; return 0;
} }
public Expression<Func<EFClientRankingHistory, bool>> GetNewRankingFunc(int? clientId = null, long? serverId = null) public Expression<Func<EFClientRankingHistory, bool>> GetNewRankingFunc(int? clientId = null,
long? serverId = null)
{ {
return (ranking) => ranking.ServerId == serverId return (ranking) => ranking.ServerId == serverId
&& ranking.Client.Level != Data.Models.Client.EFClient.Permission.Banned && ranking.Client.Level != Data.Models.Client.EFClient.Permission.Banned
@ -162,7 +163,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.ToListAsync(); .ToListAsync();
var rankingsDict = new Dictionary<int, List<RankingSnapshot>>(); var rankingsDict = new Dictionary<int, List<RankingSnapshot>>();
foreach (var clientId in clientIdsList) foreach (var clientId in clientIdsList)
{ {
var eachRank = await context.Set<EFClientRankingHistory>() var eachRank = await context.Set<EFClientRankingHistory>()
@ -171,8 +172,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.OrderByDescending(ranking => ranking.CreatedDateTime) .OrderByDescending(ranking => ranking.CreatedDateTime)
.Select(ranking => new RankingSnapshot .Select(ranking => new RankingSnapshot
{ {
ClientId = ranking.ClientId, ClientId = ranking.ClientId,
Name = ranking.Client.CurrentAlias.Name, Name = ranking.Client.CurrentAlias.Name,
LastConnection = ranking.Client.LastConnection, LastConnection = ranking.Client.LastConnection,
PerformanceMetric = ranking.PerformanceMetric, PerformanceMetric = ranking.PerformanceMetric,
ZScore = ranking.ZScore, ZScore = ranking.ZScore,
@ -181,7 +182,16 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}) })
.Take(60) .Take(60)
.ToListAsync(); .ToListAsync();
rankingsDict.Add(clientId, eachRank);
if (rankingsDict.ContainsKey(clientId))
{
rankingsDict[clientId] = rankingsDict[clientId].Concat(eachRank).Distinct()
.OrderByDescending(ranking => ranking.CreatedDateTime).ToList();
}
else
{
rankingsDict.Add(clientId, eachRank);
}
} }
var statsInfo = await context.Set<EFClientStatistics>() var statsInfo = await context.Set<EFClientStatistics>()
@ -195,13 +205,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ClientId = s.Key, ClientId = s.Key,
Kills = s.Sum(c => c.Kills), Kills = s.Sum(c => c.Kills),
Deaths = s.Sum(c => c.Deaths), Deaths = s.Sum(c => c.Deaths),
KDR = s.Sum(c => (c.Kills / (double) (c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) / KDR = s.Sum(c => (c.Kills / (double)(c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) /
s.Sum(c => c.TimePlayed), s.Sum(c => c.TimePlayed),
TotalTimePlayed = s.Sum(c => c.TimePlayed), TotalTimePlayed = s.Sum(c => c.TimePlayed),
UpdatedAt = s.Max(c => c.UpdatedAt) UpdatedAt = s.Max(c => c.UpdatedAt)
}) })
.ToListAsync(); .ToListAsync();
var finished = statsInfo var finished = statsInfo
.OrderByDescending(stat => rankingsDict[stat.ClientId].First().PerformanceMetric) .OrderByDescending(stat => rankingsDict[stat.ClientId].First().PerformanceMetric)
.Select((s, index) => new TopStatsInfo .Select((s, index) => new TopStatsInfo
@ -239,7 +249,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
return await GetNewTopStats(start, count, serverId); return await GetNewTopStats(start, count, serverId);
} }
await using var context = _contextFactory.CreateContext(enableTracking: false); await using var context = _contextFactory.CreateContext(enableTracking: false);
// setup the query for the clients within the given rating range // setup the query for the clients within the given rating range
var iqClientRatings = (from rating in context.Set<EFRating>() var iqClientRatings = (from rating in context.Set<EFRating>()
@ -282,7 +292,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.Select(grp => new .Select(grp => new
{ {
grp.Key, grp.Key,
Ratings = grp.Select(r => new {r.Performance, r.Ranking, r.When}) Ratings = grp.Select(r => new { r.Performance, r.Ranking, r.When })
}); });
var iqStatsInfo = (from stat in context.Set<EFClientStatistics>() var iqStatsInfo = (from stat in context.Set<EFClientStatistics>()
@ -296,7 +306,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ClientId = s.Key, ClientId = s.Key,
Kills = s.Sum(c => c.Kills), Kills = s.Sum(c => c.Kills),
Deaths = s.Sum(c => c.Deaths), Deaths = s.Sum(c => c.Deaths),
KDR = s.Sum(c => (c.Kills / (double) (c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) / KDR = s.Sum(c => (c.Kills / (double)(c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) /
s.Sum(c => c.TimePlayed), s.Sum(c => c.TimePlayed),
TotalTimePlayed = s.Sum(c => c.TimePlayed), TotalTimePlayed = s.Sum(c => c.TimePlayed),
}); });
@ -394,7 +404,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Port = sv.Port, Port = sv.Port,
EndPoint = sv.ToString(), EndPoint = sv.ToString(),
ServerId = serverId, ServerId = serverId,
GameName = (Reference.Game?) sv.GameName, GameName = (Reference.Game?)sv.GameName,
HostName = sv.Hostname HostName = sv.Hostname
}; };
@ -404,9 +414,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
// we want to set the gamename up if it's never been set, or it changed // we want to set the gamename up if it's never been set, or it changed
else if (!server.GameName.HasValue || server.GameName.Value != (Reference.Game) sv.GameName) else if (!server.GameName.HasValue || server.GameName.Value != (Reference.Game)sv.GameName)
{ {
server.GameName = (Reference.Game) sv.GameName; server.GameName = (Reference.Game)sv.GameName;
ctx.Entry(server).Property(_prop => _prop.GameName).IsModified = true; ctx.Entry(server).Property(_prop => _prop.GameName).IsModified = true;
ctx.SaveChanges(); ctx.SaveChanges();
} }
@ -497,7 +507,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
Active = true, Active = true,
HitCount = 0, HitCount = 0,
Location = (int) hl Location = (int)hl
}).ToList() }).ToList()
}; };
@ -517,7 +527,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
Active = true, Active = true,
HitCount = 0, HitCount = 0,
Location = (int) hl Location = (int)hl
}) })
.ToList(); .ToList();
@ -549,9 +559,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
catch (DbUpdateException updateException) when ( catch (DbUpdateException updateException) when (
updateException.InnerException is PostgresException {SqlState: "23503"} updateException.InnerException is PostgresException { SqlState: "23503" }
|| updateException.InnerException is SqliteException {SqliteErrorCode: 787} || updateException.InnerException is SqliteException { SqliteErrorCode: 787 }
|| updateException.InnerException is MySqlException {SqlState: "23503"}) || updateException.InnerException is MySqlException { SqlState: "23503" })
{ {
_log.LogWarning("Trying to add {Client} to stats before they have been added to the database", _log.LogWarning("Trying to add {Client} to stats before they have been added to the database",
pl.ToString()); pl.ToString());
@ -672,9 +682,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ServerId = serverId, ServerId = serverId,
DeathOrigin = vDeathOrigin, DeathOrigin = vDeathOrigin,
KillOrigin = vKillOrigin, KillOrigin = vKillOrigin,
DeathType = (int) ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath)), DeathType = (int)ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath)),
Damage = int.Parse(damage), Damage = int.Parse(damage),
HitLoc = (int) ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)), HitLoc = (int)ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)),
WeaponReference = weapon, WeaponReference = weapon,
ViewAngles = vViewAngles, ViewAngles = vViewAngles,
TimeOffset = long.Parse(offset), TimeOffset = long.Parse(offset),
@ -688,21 +698,21 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
AnglesList = snapshotAngles, AnglesList = snapshotAngles,
IsAlive = isAlive == "1", IsAlive = isAlive == "1",
TimeSinceLastAttack = long.Parse(lastAttackTime), TimeSinceLastAttack = long.Parse(lastAttackTime),
GameName = (int) attacker.CurrentServer.GameName GameName = (int)attacker.CurrentServer.GameName
}; };
} }
catch (Exception ex) catch (Exception ex)
{ {
_log.LogError(ex, "Could not parse script hit data. Damage={Damage}, TimeOffset={Offset}, TimeSinceLastAttack={LastAttackTime}", _log.LogError(ex,
"Could not parse script hit data. Damage={Damage}, TimeOffset={Offset}, TimeSinceLastAttack={LastAttackTime}",
damage, offset, lastAttackTime); damage, offset, lastAttackTime);
return; return;
} }
hit.SetAdditionalProperty("HitLocationReference", hitLoc); hit.SetAdditionalProperty("HitLocationReference", hitLoc);
if (hit.HitLoc == (int) IW4Info.HitLocation.shield) if (hit.HitLoc == (int)IW4Info.HitLocation.shield)
{ {
// we don't care about shield hits // we don't care about shield hits
return; return;
@ -721,9 +731,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
await waiter.WaitAsync(Utilities.DefaultCommandTimeout, Plugin.ServerManager.CancellationToken); await waiter.WaitAsync(Utilities.DefaultCommandTimeout, Plugin.ServerManager.CancellationToken);
// increment their hit count // increment their hit count
if (hit.DeathType == (int) IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || if (hit.DeathType == (int)IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
hit.DeathType == (int) IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || hit.DeathType == (int)IW4Info.MeansOfDeath.MOD_RIFLE_BULLET ||
hit.DeathType == (int) IW4Info.MeansOfDeath.MOD_HEAD_SHOT) hit.DeathType == (int)IW4Info.MeansOfDeath.MOD_HEAD_SHOT)
{ {
clientStats.HitLocations.First(hl => hl.Location == hit.HitLoc).HitCount += 1; clientStats.HitLocations.First(hl => hl.Location == hit.HitLoc).HitCount += 1;
} }
@ -866,7 +876,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
catch (KeyNotFoundException) catch (KeyNotFoundException)
{ {
} }
try try
{ {
if (!gameDetectionTypes[server.GameName].Contains(detectionType)) if (!gameDetectionTypes[server.GameName].Contains(detectionType))
@ -898,7 +908,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
new EFPenalty() new EFPenalty()
{ {
AutomatedOffense = penalty.Type == Detection.DetectionType.Bone AutomatedOffense = penalty.Type == Detection.DetectionType.Bone
? $"{penalty.Type}-{(int) penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" ? $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}"
: $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}", : $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
} }
}; };
@ -915,7 +925,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
string flagReason = penalty.Type == Cheat.Detection.DetectionType.Bone string flagReason = penalty.Type == Cheat.Detection.DetectionType.Bone
? $"{penalty.Type}-{(int) penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" ? $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}"
: $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}"; : $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}";
penaltyClient.AdministeredPenalties = new List<EFPenalty>() penaltyClient.AdministeredPenalties = new List<EFPenalty>()
@ -954,19 +964,19 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// update the total stats // update the total stats
_servers[serverId].ServerStatistics.TotalKills += 1; _servers[serverId].ServerStatistics.TotalKills += 1;
if (attackerStats == null) if (attackerStats == null)
{ {
_log.LogWarning("Stats for {Client} are not yet initialized", attacker.ToString()); _log.LogWarning("Stats for {Client} are not yet initialized", attacker.ToString());
return; return;
} }
if (victimStats == null) if (victimStats == null)
{ {
_log.LogWarning("Stats for {Client} are not yet initialized", victim.ToString()); _log.LogWarning("Stats for {Client} are not yet initialized", victim.ToString());
return; return;
} }
// this happens when the round has changed // this happens when the round has changed
if (attackerStats.SessionScore == 0) if (attackerStats.SessionScore == 0)
{ {
@ -979,10 +989,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
var estimatedAttackerScore = attacker.CurrentServer.GameName != Server.Game.CSGO var estimatedAttackerScore = attacker.CurrentServer.GameName != Server.Game.CSGO
? attacker.Score ? attacker.Score
: attackerStats.SessionKills * 50; : attackerStats.SessionKills * 50;
var estimatedVictimScore = attacker.CurrentServer.GameName != Server.Game.CSGO var estimatedVictimScore = attacker.CurrentServer.GameName != Server.Game.CSGO
? victim.Score ? victim.Score
: victimStats.SessionKills * 50; : victimStats.SessionKills * 50;
attackerStats.SessionScore = estimatedAttackerScore; attackerStats.SessionScore = estimatedAttackerScore;
@ -1070,7 +1080,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <returns></returns> /// <returns></returns>
public async Task UpdateStatHistory(EFClient client, EFClientStatistics clientStats) public async Task UpdateStatHistory(EFClient client, EFClientStatistics clientStats)
{ {
int currentSessionTime = (int) (DateTime.UtcNow - client.LastConnection).TotalSeconds; int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds;
// don't update their stat history if they haven't played long // don't update their stat history if they haven't played long
if (currentSessionTime < 60) if (currentSessionTime < 60)
@ -1243,7 +1253,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
await using var context = _contextFactory.CreateContext(); await using var context = _contextFactory.CreateContext();
var minPlayTime = _config.TopPlayersMinPlayTime; var minPlayTime = _config.TopPlayersMinPlayTime;
var performances = await context.Set<EFClientStatistics>() var performances = await context.Set<EFClientStatistics>()
.AsNoTracking() .AsNoTracking()
.Where(stat => stat.ClientId == clientId) .Where(stat => stat.ClientId == clientId)
@ -1251,7 +1261,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.Where(stats => stats.UpdatedAt >= Extensions.FifteenDaysAgo()) .Where(stats => stats.UpdatedAt >= Extensions.FifteenDaysAgo())
.Where(stats => stats.TimePlayed >= minPlayTime) .Where(stats => stats.TimePlayed >= minPlayTime)
.ToListAsync(); .ToListAsync();
if (clientStats.TimePlayed >= minPlayTime) if (clientStats.TimePlayed >= minPlayTime)
{ {
clientStats.ZScore = await _serverDistributionCalculator.GetZScoreForServer(serverId, clientStats.ZScore = await _serverDistributionCalculator.GetZScoreForServer(serverId,
@ -1282,8 +1292,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (performances.Any(performance => performance.TimePlayed >= minPlayTime)) if (performances.Any(performance => performance.TimePlayed >= minPlayTime))
{ {
var aggregateZScore = performances.WeightValueByPlaytime(nameof(EFClientStatistics.ZScore), minPlayTime); var aggregateZScore =
performances.WeightValueByPlaytime(nameof(EFClientStatistics.ZScore), minPlayTime);
int? aggregateRanking = await context.Set<EFClientStatistics>() int? aggregateRanking = await context.Set<EFClientStatistics>()
.Where(stat => stat.ClientId != clientId) .Where(stat => stat.ClientId != clientId)
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(minPlayTime)) .Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(minPlayTime))
@ -1302,7 +1313,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
clientStats.Client?.ToString(), aggregateZScore); clientStats.Client?.ToString(), aggregateZScore);
return; return;
} }
var aggregateRankingSnapshot = new EFClientRankingHistory var aggregateRankingSnapshot = new EFClientRankingHistory
{ {
ClientId = clientId, ClientId = clientId,
@ -1325,7 +1336,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.Where(r => r.ClientId == clientId) .Where(r => r.ClientId == clientId)
.Where(r => r.ServerId == serverId) .Where(r => r.ServerId == serverId)
.CountAsync(); .CountAsync();
var mostRecent = await context.Set<EFClientRankingHistory>() var mostRecent = await context.Set<EFClientRankingHistory>()
.Where(r => r.ClientId == clientId) .Where(r => r.ClientId == clientId)
.Where(r => r.ServerId == serverId) .Where(r => r.ServerId == serverId)
@ -1346,7 +1357,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.Where(r => r.ServerId == serverId) .Where(r => r.ServerId == serverId)
.OrderBy(r => r.CreatedDateTime) .OrderBy(r => r.CreatedDateTime)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (lastRating is not null) if (lastRating is not null)
{ {
context.Remove(lastRating); context.Remove(lastRating);
@ -1359,7 +1370,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary> /// </summary>
/// <param name="attackerStats">Stats of the attacker</param> /// <param name="attackerStats">Stats of the attacker</param>
/// <param name="victimStats">Stats of the victim</param> /// <param name="victimStats">Stats of the victim</param>
public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats, public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats,
EFClient attacker, EFClient victim) EFClient attacker, EFClient victim)
{ {
bool suicide = attackerStats.ClientId == victimStats.ClientId; bool suicide = attackerStats.ClientId == victimStats.ClientId;
@ -1385,7 +1396,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// calculate elo // calculate elo
var attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - var attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) -
Math.Log(Math.Max(1, attackerStats.EloRating)); Math.Log(Math.Max(1, attackerStats.EloRating));
var winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E)); var winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E));
attackerStats.EloRating += 6.0 * (1 - winPercentage); attackerStats.EloRating += 6.0 * (1 - winPercentage);
@ -1395,8 +1406,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
victimStats.EloRating = Math.Max(0, Math.Round(victimStats.EloRating, 2)); victimStats.EloRating = Math.Max(0, Math.Round(victimStats.EloRating, 2));
// update after calculation // update after calculation
attackerStats.TimePlayed += (int) (DateTime.UtcNow - attackerStats.LastActive).TotalSeconds; attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds;
victimStats.TimePlayed += (int) (DateTime.UtcNow - victimStats.LastActive).TotalSeconds; victimStats.TimePlayed += (int)(DateTime.UtcNow - victimStats.LastActive).TotalSeconds;
attackerStats.LastActive = DateTime.UtcNow; attackerStats.LastActive = DateTime.UtcNow;
victimStats.LastActive = DateTime.UtcNow; victimStats.LastActive = DateTime.UtcNow;
} }
@ -1434,11 +1445,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var killSpm = scoreDifference / timeSinceLastCalc; var killSpm = scoreDifference / timeSinceLastCalc;
var spmMultiplier = 2.934 * var spmMultiplier = 2.934 *
Math.Pow( Math.Pow(
_servers[clientStats.ServerId] _servers[clientStats.ServerId]
.TeamCount((IW4Info.Team) clientStats.Team == IW4Info.Team.Allies .TeamCount((IW4Info.Team)clientStats.Team == IW4Info.Team.Allies
? IW4Info.Team.Axis ? IW4Info.Team.Axis
: IW4Info.Team.Allies), -0.454); : IW4Info.Team.Allies), -0.454);
killSpm *= Math.Max(1, spmMultiplier); killSpm *= Math.Max(1, spmMultiplier);
// update this for ac tracking // update this for ac tracking
@ -1455,8 +1466,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// calculate the weight of the new play time against last 10 hours of gameplay // calculate the weight of the new play time against last 10 hours of gameplay
int totalPlayTime = (clientStats.TimePlayed == 0) int totalPlayTime = (clientStats.TimePlayed == 0)
? (int) (DateTime.UtcNow - clientStats.LastActive).TotalSeconds ? (int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds
: clientStats.TimePlayed + (int) (DateTime.UtcNow - clientStats.LastActive).TotalSeconds; : clientStats.TimePlayed + (int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds;
double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalPlayTime / 60.0)); double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalPlayTime / 60.0));
@ -1476,7 +1487,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill)) if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill))
{ {
_log.LogWarning("clientStats SPM/Skill NaN {@killInfo}", _log.LogWarning("clientStats SPM/Skill NaN {@killInfo}",
new {killSPM = killSpm, KDRWeight, totalPlayTime, SPMAgainstPlayWeight, clientStats, scoreDifference}); new
{
killSPM = killSpm, KDRWeight, totalPlayTime, SPMAgainstPlayWeight, clientStats, scoreDifference
});
clientStats.SPM = 0; clientStats.SPM = 0;
clientStats.Skill = 0; clientStats.Skill = 0;
} }
@ -1517,11 +1531,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public void ResetKillstreaks(Server sv) public void ResetKillstreaks(Server sv)
{ {
foreach (var session in sv.GetClientsAsList() foreach (var session in sv.GetClientsAsList()
.Select(_client => new .Select(_client => new
{ {
stat = _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY), stat = _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY),
detection = _client.GetAdditionalProperty<Detection>(CLIENT_DETECTIONS_KEY) detection = _client.GetAdditionalProperty<Detection>(CLIENT_DETECTIONS_KEY)
})) }))
{ {
session.stat?.StartNewSession(); session.stat?.StartNewSession();
session.detection?.OnMapChange(); session.detection?.OnMapChange();
@ -1583,8 +1597,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
foreach (var stats in sv.GetClientsAsList() foreach (var stats in sv.GetClientsAsList()
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY)) .Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))
.Where(_stats => _stats != null)) .Where(_stats => _stats != null))
{ {
await SaveClientStats(stats); await SaveClientStats(stats);
} }