diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index 35db2f72..5683669b 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -912,9 +912,9 @@ namespace IW4MAdmin context.Entry(gameServer).Property(property => property.HostName).IsModified = true; } - if (gameServer.PerformanceBucket != PerformanceBucket) + if (gameServer.PerformanceBucket.Code != PerformanceCode) { - gameServer.PerformanceBucket = PerformanceBucket; + gameServer.PerformanceBucket.Code = PerformanceCode; context.Entry(gameServer).Property(property => property.PerformanceBucket).IsModified = true; } diff --git a/Application/Misc/ServerDataViewer.cs b/Application/Misc/ServerDataViewer.cs index 4ee92242..9cfee621 100644 --- a/Application/Misc/ServerDataViewer.cs +++ b/Application/Misc/ServerDataViewer.cs @@ -29,11 +29,12 @@ namespace IW4MAdmin.Application.Misc private readonly StatManager _statManager; private readonly TimeSpan? _cacheTimeSpan = - Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?) TimeSpan.FromMinutes(10); + Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?)TimeSpan.FromMinutes(10); public ServerDataViewer(ILogger logger, IDataValueCache snapshotCache, IDataValueCache serverStatsCache, - IDataValueCache> clientHistoryCache, IDataValueCache rankedClientsCache, StatManager statManager) + IDataValueCache> clientHistoryCache, + IDataValueCache rankedClientsCache, StatManager statManager) { _logger = logger; _snapshotCache = snapshotCache; @@ -43,16 +44,17 @@ namespace IW4MAdmin.Application.Misc _statManager = statManager; } - public async Task<(int?, DateTime?)> + public async Task<(int?, DateTime?)> MaxConcurrentClientsAsync(long? serverId = null, Reference.Game? gameCode = null, TimeSpan? overPeriod = null, - CancellationToken token = default) + CancellationToken token = default) { - _snapshotCache.SetCacheItem(async (snapshots, ids, cancellationToken) => + _snapshotCache.SetCacheItem(async (snapshots, idsList, cancellationToken) => { Reference.Game? game = null; long? id = null; - if (ids.Any()) + var ids = idsList.ToList(); + if (ids.Count is not 0) { game = (Reference.Game?)ids.First(); id = (long?)ids.Last(); @@ -102,12 +104,11 @@ namespace IW4MAdmin.Application.Misc _logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients); return (maxClients, maxClientsTime); - }, nameof(MaxConcurrentClientsAsync), new object[] { gameCode, serverId }, _cacheTimeSpan, true); + }, nameof(MaxConcurrentClientsAsync), [gameCode, serverId], _cacheTimeSpan, true); try { - return await _snapshotCache.GetCacheItem(nameof(MaxConcurrentClientsAsync), - new object[] { gameCode, serverId }, token); + return await _snapshotCache.GetCacheItem(nameof(MaxConcurrentClientsAsync), [gameCode, serverId], token); } catch (Exception ex) { @@ -116,22 +117,24 @@ namespace IW4MAdmin.Application.Misc } } - public async Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, Reference.Game? gameCode = null, CancellationToken token = default) + public async Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, Reference.Game? gameCode = null, + CancellationToken token = default) { _serverStatsCache.SetCacheItem(async (set, ids, cancellationToken) => { Reference.Game? game = null; - + if (ids.Any()) { game = (Reference.Game?)ids.First(); } - + var count = await set.CountAsync(item => game == null || item.GameName == game, cancellationToken); var startOfPeriod = DateTime.UtcNow.AddHours(-overPeriod?.TotalHours ?? -24); - var recentCount = await set.CountAsync(client => (game == null || client.GameName == game) && client.LastConnection >= startOfPeriod, + var recentCount = await set.CountAsync( + client => (game == null || client.GameName == game) && client.LastConnection >= startOfPeriod, cancellationToken); return (count, recentCount); @@ -173,7 +176,10 @@ namespace IW4MAdmin.Application.Misc { ServerId = byServer.Key, ClientCounts = byServer.Select(snapshot => new ClientCountSnapshot - { Time = snapshot.CapturedAt, ClientCount = snapshot.ClientCount, ConnectionInterrupted = snapshot.ConnectionInterrupted ?? false, Map = snapshot.MapName}).ToList() + { + Time = snapshot.CapturedAt, ClientCount = snapshot.ClientCount, + ConnectionInterrupted = snapshot.ConnectionInterrupted ?? false, Map = snapshot.MapName + }).ToList() }).ToList(); }, nameof(_clientHistoryCache), TimeSpan.MaxValue); @@ -184,35 +190,36 @@ namespace IW4MAdmin.Application.Misc catch (Exception ex) { _logger.LogError(ex, "Could not retrieve data for {Name}", nameof(ClientHistoryAsync)); - return Enumerable.Empty(); + return []; } } - public async Task RankedClientsCountAsync(long? serverId = null, string performanceBucket = null, CancellationToken token = default) + public async Task RankedClientsCountAsync(long? serverId = null, string performanceBucketCode = null, + CancellationToken token = default) { - _rankedClientsCache.SetCacheItem((set, ids, cancellationToken) => + _rankedClientsCache.SetCacheItem((set, idsList, cancellationToken) => { long? id = null; string bucket = null; - - if (ids.Any()) + + var ids = idsList.ToList(); + if (ids.Count is not 0) { id = (long?)ids.First(); } - if (ids.Count() == 2) + if (ids.Count is 2) { bucket = (string)ids.Last(); } return _statManager.GetBucketConfig(serverId) .ContinueWith(result => _statManager.GetTotalRankedPlayers(id, bucket), cancellationToken).Result; + }, nameof(_rankedClientsCache), [serverId, performanceBucketCode], _cacheTimeSpan); - }, nameof(_rankedClientsCache), new object[] { serverId, performanceBucket }, _cacheTimeSpan); - try { - return await _rankedClientsCache.GetCacheItem(nameof(_rankedClientsCache), new object[] { serverId, performanceBucket }, token); + return await _rankedClientsCache.GetCacheItem(nameof(_rankedClientsCache), [serverId, performanceBucketCode], token); } catch (Exception ex) { diff --git a/Data/Context/DatabaseContext.cs b/Data/Context/DatabaseContext.cs index ae60d6e1..45a617b9 100644 --- a/Data/Context/DatabaseContext.cs +++ b/Data/Context/DatabaseContext.cs @@ -25,6 +25,7 @@ namespace Data.Context #region STATS + public DbSet PerformanceBuckets { get; set; } public DbSet Vector3s { get; set; } public DbSet SnapshotVector3s { get; set; } public DbSet ACSnapshots { get; set; } diff --git a/Data/Models/Client/Stats/EFPerformanceBucket.cs b/Data/Models/Client/Stats/EFPerformanceBucket.cs index fecef7dd..b01763f3 100644 --- a/Data/Models/Client/Stats/EFPerformanceBucket.cs +++ b/Data/Models/Client/Stats/EFPerformanceBucket.cs @@ -4,12 +4,17 @@ namespace Data.Models.Client.Stats; public class EFPerformanceBucket { - [Key] - public int PerformanceBucketId { get; set; } - + [Key] public int PerformanceBucketId { get; set; } + + /// + /// Identifier for Bucket + /// [MaxLength(256)] - public string BucketCode { get; set; } - + public string Code { get; set; } + + /// + /// Friendly name for Bucket + /// [MaxLength(256)] - public string BucketName { get; set; } + public string Name { get; set; } } diff --git a/Plugins/Stats/Client/HitCalculator.cs b/Plugins/Stats/Client/HitCalculator.cs index 11b0f4d8..ea2fda7e 100644 --- a/Plugins/Stats/Client/HitCalculator.cs +++ b/Plugins/Stats/Client/HitCalculator.cs @@ -290,16 +290,16 @@ public class HitCalculator : IClientStatisticCalculator var matchingLocation = await GetOrAddHitLocation(hitInfo.Location, hitInfo.Game); var meansOfDeath = await GetOrAddMeansOfDeath(hitInfo.MeansOfDeath, hitInfo.Game); - var baseTasks = new[] - { + List> baseTasks = + [ // just the client - GetOrAddClientHit(hitInfo.EntityId, null), + GetOrAddClientHit(hitInfo.EntityId), // client and server GetOrAddClientHit(hitInfo.EntityId, serverId), // just the location - GetOrAddClientHit(hitInfo.EntityId, null, matchingLocation.HitLocationId), + GetOrAddClientHit(hitInfo.EntityId, hitLocationId: matchingLocation.HitLocationId), // location and server - GetOrAddClientHit(hitInfo.EntityId, serverId, matchingLocation.HitLocationId), + GetOrAddClientHit(hitInfo.EntityId, serverId, hitLocationId: matchingLocation.HitLocationId), // per weapon GetOrAddClientHit(hitInfo.EntityId, null, null, weapon.WeaponId), // per weapon and server @@ -309,7 +309,7 @@ public class HitCalculator : IClientStatisticCalculator // means of death per server aggregate GetOrAddClientHit(hitInfo.EntityId, serverId, meansOfDeathId: meansOfDeath.MeansOfDeathId) - }; + ]; var allTasks = baseTasks.AsEnumerable(); @@ -413,7 +413,7 @@ public class HitCalculator : IClientStatisticCalculator } } - private async Task GetOrAddClientHit(int clientId, long? serverId = null, string performanceBucket = null, + private async Task GetOrAddClientHit(int clientId, long? serverId = null, string performanceBucketCode = null, int? hitLocationId = null, int? weaponId = null, int? attachmentComboId = null, int? meansOfDeathId = null) { @@ -425,7 +425,7 @@ public class HitCalculator : IClientStatisticCalculator && hit.WeaponId == weaponId && hit.WeaponAttachmentComboId == attachmentComboId && hit.MeansOfDeathId == meansOfDeathId - && (performanceBucket is not null && performanceBucket == hit.Server.PerformanceBucket || (performanceBucket is null && hit.ServerId == serverId))); + && (performanceBucketCode is not null && performanceBucketCode == hit.Server.PerformanceBucket.Code || (performanceBucketCode is null && hit.ServerId == serverId))); if (hitStat != null) { diff --git a/Plugins/Stats/Client/ServerDistributionCalculator.cs b/Plugins/Stats/Client/ServerDistributionCalculator.cs index b3f51663..debc14f6 100644 --- a/Plugins/Stats/Client/ServerDistributionCalculator.cs +++ b/Plugins/Stats/Client/ServerDistributionCalculator.cs @@ -63,7 +63,7 @@ namespace Stats.Client { var bucketConfig = _configuration.PerformanceBuckets.FirstOrDefault(bucket => - bucket.Name == performanceBucket) ?? new PerformanceBucketConfiguration(); + bucket.Code == performanceBucket) ?? new PerformanceBucketConfiguration(); var oldestPerf = DateTime.UtcNow - bucketConfig.RankingExpiration; var performances = await iqPerformances.Where(s => s.ServerId == serverId) @@ -76,42 +76,43 @@ namespace Stats.Client distributions.Add(serverId.ToString(), distributionParams); } - foreach (var performanceBucketGroup in _appConfig.Servers.Select(server => server.PerformanceBucket).Distinct()) + foreach (var performanceBucket in _appConfig.Servers.Select(server => server.PerformanceBucketCode).Distinct()) { - var performanceBucket = performanceBucketGroup ?? "null"; + // TODO: ? + var performanceBucketCode = performanceBucket ?? "null"; var bucketConfig = _configuration.PerformanceBuckets.FirstOrDefault(bucket => - bucket.Name == performanceBucket) ?? new PerformanceBucketConfiguration(); + bucket.Code == performanceBucketCode) ?? new PerformanceBucketConfiguration(); var oldestPerf = DateTime.UtcNow - bucketConfig.RankingExpiration; var performances = await iqPerformances - .Where(perf => perf.Server.PerformanceBucket == performanceBucket) + .Where(perf => perf.Server.PerformanceBucket.Code == performanceBucketCode) .Where(perf => perf.TimePlayed >= bucketConfig.ClientMinPlayTime.TotalSeconds) .Where(perf => perf.UpdatedAt >= oldestPerf) .Where(perf => perf.Skill < 999999) .Select(s => s.EloRating * 1 / 3.0 + s.Skill * 2 / 3.0) .ToListAsync(token); var distributionParams = performances.GenerateDistributionParameters(); - distributions.Add(performanceBucket, distributionParams); + distributions.Add(performanceBucketCode, distributionParams); } return distributions; }, DistributionCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(1) : TimeSpan.FromHours(1)); - foreach (var performanceBucket in _appConfig.Servers.Select(s => s.PerformanceBucket).Distinct()) + foreach (var performanceBucket in _appConfig.Servers.Select(s => s.PerformanceBucketCode).Distinct()) { _maxZScoreCache.SetCacheItem(async (set, ids, token) => { var validPlayTime = _configuration.TopPlayersMinPlayTime; var oldestStat = DateTime.UtcNow - Extensions.FifteenDaysAgo(); - var perfBucket = (string)ids.FirstOrDefault(); + var localPerformanceBucket = (string)ids.FirstOrDefault(); - if (!string.IsNullOrEmpty(perfBucket)) + if (!string.IsNullOrEmpty(localPerformanceBucket)) { var bucketConfig = _configuration.PerformanceBuckets.FirstOrDefault(cfg => - cfg.Name == perfBucket) ?? new PerformanceBucketConfiguration(); + cfg.Code == localPerformanceBucket) ?? new PerformanceBucketConfiguration(); validPlayTime = (int)bucketConfig.ClientMinPlayTime.TotalSeconds; oldestStat = bucketConfig.RankingExpiration; @@ -121,7 +122,7 @@ namespace Stats.Client .Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(validPlayTime, oldestStat)) .Where(s => s.Skill > 0) .Where(s => s.EloRating >= 0) - .Where(stat => perfBucket == stat.Server.PerformanceBucket) + .Where(stat => localPerformanceBucket == stat.Server.PerformanceBucket.Code) .GroupBy(stat => stat.ClientId) .Select(group => group.Sum(stat => stat.ZScore * stat.TimePlayed) / group.Sum(stat => stat.TimePlayed)) @@ -170,7 +171,7 @@ namespace Stats.Client await using var context = _contextFactory.CreateContext(false); _serverIds.AddRange(await context.Servers .Where(s => s.EndPoint != null && s.HostName != null) - .Select(s => new Tuple(s.ServerId, s.PerformanceBucket)) + .Select(s => new Tuple(s.ServerId, s.PerformanceBucket.Code)) .ToListAsync()); } } diff --git a/Plugins/Stats/Config/PerformanceBucketConfiguration.cs b/Plugins/Stats/Config/PerformanceBucketConfiguration.cs index a7bc2f69..b9f533ce 100644 --- a/Plugins/Stats/Config/PerformanceBucketConfiguration.cs +++ b/Plugins/Stats/Config/PerformanceBucketConfiguration.cs @@ -5,7 +5,8 @@ namespace Stats.Config; public class PerformanceBucketConfiguration { - public string Name { get; set; } + public string FriendlyNameRENAME { get; set; } + public string Code { get; set; } public TimeSpan ClientMinPlayTime { get; set; } = Utilities.IsDevelopment ? TimeSpan.FromMinutes(1) : TimeSpan.FromHours(3); public TimeSpan RankingExpiration { get; set; } = TimeSpan.FromDays(15); } diff --git a/Plugins/Stats/Dtos/StatsInfoRequest.cs b/Plugins/Stats/Dtos/StatsInfoRequest.cs index 2e6fd77d..5f65424a 100644 --- a/Plugins/Stats/Dtos/StatsInfoRequest.cs +++ b/Plugins/Stats/Dtos/StatsInfoRequest.cs @@ -7,6 +7,6 @@ /// public int? ClientId { get; set; } public string ServerEndpoint { get; set; } - public string PerformanceBucket { get; set; } + public string PerformanceBucketCode { get; set; } } } diff --git a/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs b/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs index ad403a7f..864c4614 100644 --- a/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs +++ b/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs @@ -66,8 +66,8 @@ namespace Stats.Helpers .ThenInclude(attachment => attachment.Attachment3) .Where(stat => stat.ClientId == query.ClientId); - iqHitStats = !string.IsNullOrEmpty(query.PerformanceBucket) - ? iqHitStats.Where(stat => stat.Server.PerformanceBucket == query.PerformanceBucket) + iqHitStats = !string.IsNullOrEmpty(query.PerformanceBucketCode) + ? iqHitStats.Where(stat => stat.Server.PerformanceBucket.Code == query.PerformanceBucketCode) : iqHitStats.Where(stat => stat.ServerId == serverId); var hitStats = await iqHitStats.ToListAsync(); @@ -76,7 +76,7 @@ namespace Stats.Helpers .Where(r => r.ClientId == clientInfo.ClientId) .Where(r => r.ServerId == serverId) .Where(r => r.Ranking != null) - .Where(r => r.PerformanceBucket == query.PerformanceBucket) + .Where(r => r.PerformanceBucket.Code == query.PerformanceBucketCode) .OrderByDescending(r => r.CreatedDateTime) .Take(250) .ToListAsync(); @@ -85,7 +85,7 @@ namespace Stats.Helpers { ClientId = query.ClientId, ServerEndpoint = query.ServerEndpoint, - PerformanceBucket = query.PerformanceBucket + PerformanceBucketCode = query.PerformanceBucketCode })).Results.First(); var mostRecentRanking = ratings.FirstOrDefault(ranking => ranking.Newest); @@ -95,7 +95,7 @@ namespace Stats.Helpers var legacyStats = await context.Set() .Where(stat => stat.ClientId == query.ClientId) .Where(stat => serverId == null || stat.ServerId == serverId) - .Where(stat => stat.Server.PerformanceBucket == query.PerformanceBucket) + .Where(stat => stat.Server.PerformanceBucket.Code == query.PerformanceBucketCode) .ToListAsync(); var bucketConfig = await statManager.GetBucketConfig(serverId); @@ -126,12 +126,12 @@ namespace Stats.Helpers { Name = server.Hostname, IPAddress = server.ListenAddress, Port = server.ListenPort, Game = (Reference.Game)server.GameName, - PerformanceBucket = server.PerformanceBucket + PerformanceBucket = server.PerformanceCode }) .Where(server => server.Game == clientInfo.GameName) .ToList(), Aggregate = hitStats.FirstOrDefault(hit => - hit.HitLocationId == null && (string.IsNullOrEmpty(query.PerformanceBucket) || hit.ServerId == serverId) && hit.WeaponId == null && + hit.HitLocationId == null && (string.IsNullOrEmpty(query.PerformanceBucketCode) || hit.ServerId == serverId) && hit.WeaponId == null && hit.MeansOfDeathId == null), ByHitLocation = hitStats .Where(hit => hit.HitLocationId != null) @@ -186,9 +186,9 @@ namespace Stats.Helpers var currentRanking = 0; int totalRankedClients; - string performanceBucket; + string performanceBucketCode; - if (string.IsNullOrEmpty(query.PerformanceBucket) && serverId is null) + if (string.IsNullOrEmpty(query.PerformanceBucketCode) && serverId is null) { var maxPerformance = await context.Set() .Where(r => r.ClientId == query.ClientId) @@ -204,27 +204,27 @@ namespace Stats.Helpers { currentRanking = 0; totalRankedClients = 0; - performanceBucket = null; + performanceBucketCode = null; } else { currentRanking = - await statManager.GetClientOverallRanking(query.ClientId!.Value, null, maxPerformance.Key); - totalRankedClients = await serverDataViewer.RankedClientsCountAsync(null, maxPerformance.Key); - performanceBucket = maxPerformance.Key; + await statManager.GetClientOverallRanking(query.ClientId!.Value, null, maxPerformance.Key.Code); + totalRankedClients = await serverDataViewer.RankedClientsCountAsync(null, maxPerformance.Key.Code); + performanceBucketCode = maxPerformance.Key.Code; } } else { - performanceBucket = query.PerformanceBucket; + performanceBucketCode = query.PerformanceBucketCode; currentRanking = - await statManager.GetClientOverallRanking(query.ClientId!.Value, serverId, performanceBucket); - totalRankedClients = await serverDataViewer.RankedClientsCountAsync(serverId, performanceBucket); + await statManager.GetClientOverallRanking(query.ClientId!.Value, serverId, performanceBucketCode); + totalRankedClients = await serverDataViewer.RankedClientsCountAsync(serverId, performanceBucketCode); } return new ResourceQueryHelperResult { - Results = [new ClientRankingInfo(currentRanking, totalRankedClients, performanceBucket)] + Results = [new ClientRankingInfo(currentRanking, totalRankedClients, performanceBucketCode)] }; } } diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index bdff09d9..b61f687a 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -115,7 +115,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers return 0; } - private Expression> GetNewRankingFunc(TimeSpan oldestStat, TimeSpan minPlayTime, long? serverId = null, string performanceBucket = null) + private Expression> GetNewRankingFunc(TimeSpan oldestStat, TimeSpan minPlayTime, long? serverId = null, string performanceBucketCode = null) { var oldestDate = DateTime.UtcNow - oldestStat; return ranking => ranking.ServerId == serverId @@ -124,7 +124,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers && ranking.ZScore != null && ranking.PerformanceMetric != null && ranking.Newest - && ranking.PerformanceBucket == performanceBucket + && ranking.PerformanceBucket.Code == performanceBucketCode && ranking.Client.TotalConnectionTime >= (int)minPlayTime.TotalSeconds; } @@ -150,13 +150,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public DateTime CreatedDateTime { get; set; } } - public async Task> GetNewTopStats(int start, int count, long? serverId = null, string performanceBucket = null) + public async Task> GetNewTopStats(int start, int count, long? serverId = null, string performanceBucketCode = null) { var bucketConfig = await GetBucketConfig(serverId); await using var context = _contextFactory.CreateContext(false); var clientIdsList = await context.Set() - .Where(GetNewRankingFunc(bucketConfig.RankingExpiration, bucketConfig.ClientMinPlayTime, serverId: serverId, performanceBucket)) + .Where(GetNewRankingFunc(bucketConfig.RankingExpiration, bucketConfig.ClientMinPlayTime, serverId: serverId, performanceBucketCode)) .OrderByDescending(ranking => ranking.PerformanceMetric) .Select(ranking => ranking.ClientId) .Skip(start) @@ -170,7 +170,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var eachRank = await context.Set() .Where(ranking => ranking.ClientId == clientId) .Where(ranking => ranking.ServerId == serverId) - .Where(ranking => ranking.PerformanceBucket == performanceBucket) + .Where(ranking => ranking.PerformanceBucket.Code == performanceBucketCode) .OrderByDescending(ranking => ranking.CreatedDateTime) .Select(ranking => new RankingSnapshot { @@ -278,14 +278,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers foreach (var customMetricFunc in Plugin.ServerManager.CustomStatsMetrics) { await customMetricFunc(finished.ToDictionary(kvp => kvp.ClientId, kvp => kvp.Metrics), serverId, - performanceBucket, true); + performanceBucketCode, true); } return finished; } public async Task GetBucketConfig(long? serverId = null, - string bucketName = null) + string performanceBucketCode = null) { var defaultConfig = new PerformanceBucketConfiguration { @@ -293,26 +293,26 @@ namespace IW4MAdmin.Plugins.Stats.Helpers RankingExpiration = DateTime.UtcNow - Extensions.FifteenDaysAgo() }; - if (serverId is null && bucketName is null) + if (serverId is null && performanceBucketCode is null) { return defaultConfig; } - if (bucketName is not null) + if (performanceBucketCode is not null) { - return _config.PerformanceBuckets.FirstOrDefault(bucket => bucket.Name == bucketName) ?? + return _config.PerformanceBuckets.FirstOrDefault(bucket => bucket.Code == performanceBucketCode) ?? defaultConfig; } var performanceBucket = - (await _serverCache.FirstAsync(server => server.Id == serverId)).PerformanceBucket; + (await _serverCache.FirstAsync(server => server.Id == serverId))?.PerformanceBucket?.Code; if (string.IsNullOrEmpty(performanceBucket)) { return defaultConfig; } - return _config.PerformanceBuckets.FirstOrDefault(bucket => bucket.Name == performanceBucket) ?? + return _config.PerformanceBuckets.FirstOrDefault(bucket => bucket.Code == performanceBucket) ?? defaultConfig; } @@ -1268,7 +1268,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers .Include(stat => stat.Server) .Where(stat => stat.ClientId == clientId) .Where(stat => stat.ServerId != serverId) // ignore the one we're currently tracking - .Where(stat => stat.Server.PerformanceBucket == bucketConfig.Name) + .Where(stat => stat.Server.PerformanceBucket.Code == bucketConfig.Code) .Where(stats => stats.UpdatedAt >= oldestStateDate) .Where(stats => stats.TimePlayed >= (int)bucketConfig.ClientMinPlayTime.TotalSeconds) .ToListAsync(); @@ -1293,7 +1293,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var aggregateRanking = await context.Set() .Where(stat => stat.ClientId != clientId) - .Where(stat => bucketConfig.Name == stat.Server.PerformanceBucket) + .Where(stat => bucketConfig.Code == stat.Server.PerformanceBucket.Code) .Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc((int)bucketConfig.ClientMinPlayTime.TotalSeconds, bucketConfig.RankingExpiration)) .GroupBy(stat => stat.ClientId) .Where(group => @@ -1302,7 +1302,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers .Select(c => c.Key) .CountAsync(); - var newPerformanceMetric = await _serverDistributionCalculator.GetRatingForZScore(aggregateZScore, bucketConfig.Name); + var newPerformanceMetric = await _serverDistributionCalculator.GetRatingForZScore(aggregateZScore, bucketConfig.Code); if (newPerformanceMetric == null) { @@ -1311,19 +1311,23 @@ namespace IW4MAdmin.Plugins.Stats.Helpers return; } + // TODO: CACHE THE EFPERFORMANCEBUCKET SO WE DON'T NEED TO LOOK IT UP - THERE WILL BE A REALISTIC LIMIT TO HOW MANY BUCKETS SHOULD BE LIVE + var performanceBucketId = (await context.PerformanceBuckets.FirstOrDefaultAsync(x => x.Code == bucketConfig.Code)) + ?.PerformanceBucketId; + var aggregateRankingSnapshot = new EFClientRankingHistory { ClientId = clientId, ZScore = aggregateZScore, Ranking = aggregateRanking, PerformanceMetric = newPerformanceMetric, - PerformanceBucket = bucketConfig.Name, + PerformanceBucketId = performanceBucketId, Newest = true, }; context.Add(aggregateRankingSnapshot); - await PruneOldRankings(context, clientId, performanceBucket: bucketConfig.Name); + await PruneOldRankings(context, clientId, performanceBucketCode: bucketConfig.Code); await context.SaveChangesAsync(); } @@ -1354,18 +1358,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers await context.SaveChangesAsync(); } - private async Task PruneOldRankings(DatabaseContext context, int clientId, long? serverId = null, string performanceBucket = null) + private async Task PruneOldRankings(DatabaseContext context, int clientId, long? serverId = null, string performanceBucketCode = null) { var totalRankingEntries = await context.Set() .Where(r => r.ClientId == clientId) .Where(r => r.ServerId == serverId) - .Where(r => r.PerformanceBucket == performanceBucket) + .Where(r => r.PerformanceBucket.Code == performanceBucketCode) .CountAsync(); var mostRecent = await context.Set() .Where(r => r.ClientId == clientId) .Where(r => r.ServerId == serverId) - .Where(r => r.PerformanceBucket == performanceBucket) + .Where(r => r.PerformanceBucket.Code == performanceBucketCode) .FirstOrDefaultAsync(r => r.Newest); if (mostRecent != null) @@ -1381,7 +1385,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var lastRating = await context.Set() .Where(r => r.ClientId == clientId) .Where(r => r.ServerId == serverId) - .Where(r => r.PerformanceBucket == performanceBucket) + .Where(r => r.PerformanceBucket.Code == performanceBucketCode) .OrderBy(r => r.CreatedDateTime) .FirstOrDefaultAsync(); @@ -1397,6 +1401,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers /// /// Stats of the attacker /// Stats of the victim + /// Attacker + /// Victim public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats, EFClient attacker, EFClient victim) { @@ -1438,8 +1444,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers attackerStats.EloRating = attackerEloRatingFunc?.Invoke(attacker, attackerStats) ?? attackerStats.EloRating; - var victimEloRatingFunc = - victim.GetAdditionalProperty>("EloRatingFunction"); + // Unused? New code. TODO: Check if needed? + //var victimEloRatingFunc = + // victim.GetAdditionalProperty>("EloRatingFunction"); victimStats.EloRating = attackerEloRatingFunc?.Invoke(victim, victimStats) ?? victimStats.EloRating; diff --git a/Plugins/ZombieStats/States/ZombieClientStateManager.cs b/Plugins/ZombieStats/States/ZombieClientStateManager.cs index 8ece539a..3745d915 100644 --- a/Plugins/ZombieStats/States/ZombieClientStateManager.cs +++ b/Plugins/ZombieStats/States/ZombieClientStateManager.cs @@ -620,7 +620,7 @@ public class ZombieClientStateManager( } public async Task GetAdvancedStatsMetrics(Dictionary> meta, long? serverId, - string performanceBucket, + string performanceBucketCode, bool isTopStats) { if (isTopStats || !meta.Any()) @@ -634,8 +634,8 @@ public class ZombieClientStateManager( var iqStats = context.ZombieClientStatAggregates .Where(stat => stat.ClientId == clientId); - iqStats = !string.IsNullOrEmpty(performanceBucket) - ? iqStats.Where(stat => stat.Server.PerformanceBucket == performanceBucket) + iqStats = !string.IsNullOrEmpty(performanceBucketCode) + ? iqStats.Where(stat => stat.Server.PerformanceBucket.Code == performanceBucketCode) : iqStats.Where(stat => stat.ServerId == serverId); var stats = await iqStats.Select(stat => new diff --git a/Plugins/ZombieStats/TODO.md b/Plugins/ZombieStats/TODO.md new file mode 100644 index 00000000..89f176bc --- /dev/null +++ b/Plugins/ZombieStats/TODO.md @@ -0,0 +1,29 @@ + +### Server reference PerformanceBucket + +EFPerformanceBucket adding as DbSet to DatabaseContext + +Create migration for EFClientStat + + +### Leaderboard logic + +Using individual stats - but groups them by the bucket + +Determines what the performance is by all the stats in that bucket / per server and for global stats. (BUCKET, SERVER, GLOBAL) +- Isn't properly calculating, when it goes to try and find where a person ranked in a performance bucket, it was returning null(?) so they never got a rank +- Potentially resolved by using EFPerformanceBucketId instead of arbitrary string + + +If new buckets are added to config, check on start and add to DB (PerformanceBucketConfgiuration) +- Needs to update to use the code +- CODE LOC `EnsureServerAdded()` + +LOG OnPlayerRoundDataGameEvent ZOMBIES `CID - 848637` + +### 2024-09-05 +- Fixed references +- Added DbSet +- MIGRATION NOT CREATED +- StatManager Line 1313 needs checking. + diff --git a/SharedLibraryCore/Configuration/ServerConfiguration.cs b/SharedLibraryCore/Configuration/ServerConfiguration.cs index e3ad7fb8..b2e7c1ba 100644 --- a/SharedLibraryCore/Configuration/ServerConfiguration.cs +++ b/SharedLibraryCore/Configuration/ServerConfiguration.cs @@ -47,7 +47,7 @@ namespace SharedLibraryCore.Configuration [LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_CUSTOM_HOSTNAME")] [ConfigurationOptional] public string CustomHostname { get; set; } - public string PerformanceBucket { get; set; } + public string PerformanceBucketCode { get; set; } public IBaseConfiguration Generate() { diff --git a/SharedLibraryCore/Interfaces/IServerDataViewer.cs b/SharedLibraryCore/Interfaces/IServerDataViewer.cs index cfe0d30e..0457781c 100644 --- a/SharedLibraryCore/Interfaces/IServerDataViewer.cs +++ b/SharedLibraryCore/Interfaces/IServerDataViewer.cs @@ -45,9 +45,9 @@ namespace SharedLibraryCore.Interfaces /// Retrieves the number of ranked clients for given server id /// /// ServerId to query on - /// + /// /// CancellationToken /// - Task RankedClientsCountAsync(long? serverId = null, string performanceBucket = null, CancellationToken token = default); + Task RankedClientsCountAsync(long? serverId = null, string performanceBucketCode = null, CancellationToken token = default); } } diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index e6837d1b..f611ed85 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -83,7 +83,7 @@ namespace SharedLibraryCore RConConnectionFactory = rconConnectionFactory; ServerLogger = logger; DefaultSettings = serviceProvider.GetRequiredService(); - PerformanceBucket = ServerConfig.PerformanceBucket; + PerformanceCode = ServerConfig.PerformanceBucketCode; InitializeTokens(); InitializeAutoMessages(); } @@ -164,7 +164,7 @@ namespace SharedLibraryCore public bool IsInitialized { get; set; } public int Port { get; protected set; } public int ListenPort => Port; - public string PerformanceBucket { get; init; } + public string PerformanceCode { get; init; } public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty); public abstract Task ExecuteCommandAsync(string command, CancellationToken token = default); public abstract Task SetDvarAsync(string name, object value, CancellationToken token = default); diff --git a/WebfrontCore/Controllers/Client/ClientStatisticsController.cs b/WebfrontCore/Controllers/Client/ClientStatisticsController.cs index 30b286ad..05e04778 100644 --- a/WebfrontCore/Controllers/Client/ClientStatisticsController.cs +++ b/WebfrontCore/Controllers/Client/ClientStatisticsController.cs @@ -35,7 +35,7 @@ namespace WebfrontCore.Controllers { ClientId = id, ServerEndpoint = serverId, - PerformanceBucket = performanceBucket + PerformanceBucketCode = performanceBucket }))?.Results?.First(); if (hitInfo is null) diff --git a/WebfrontCore/Controllers/Client/Legacy/StatsController.cs b/WebfrontCore/Controllers/Client/Legacy/StatsController.cs index a376435c..344a10eb 100644 --- a/WebfrontCore/Controllers/Client/Legacy/StatsController.cs +++ b/WebfrontCore/Controllers/Client/Legacy/StatsController.cs @@ -72,7 +72,7 @@ namespace WebfrontCore.Controllers.Client.Legacy IPAddress = selectedServer.ListenAddress, Port = selectedServer.ListenPort, Game = selectedServer.GameCode, - PerformanceBucket = selectedServer.PerformanceBucket + PerformanceBucket = selectedServer.PerformanceCode })); }