1
0
mirror of https://github.com/RaidMax/IW4M-Admin.git synced 2025-06-07 21:58:06 -05:00

Using Code from EFPerformanceBucket for references

This commit is contained in:
Ayymoss 2024-09-07 18:41:18 +01:00
parent 79bd6ca8e1
commit 98e2be8623
No known key found for this signature in database
GPG Key ID: 6F64388D52A78E9E
17 changed files with 154 additions and 103 deletions

View File

@ -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;
}

View File

@ -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<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, (int?, DateTime?)> snapshotCache,
IDataValueCache<EFClient, (int, int)> serverStatsCache,
IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> clientHistoryCache, IDataValueCache<EFClientRankingHistory, int> rankedClientsCache, StatManager statManager)
IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> clientHistoryCache,
IDataValueCache<EFClientRankingHistory, int> rankedClientsCache, StatManager statManager)
{
_logger = logger;
_snapshotCache = snapshotCache;
@ -47,12 +48,13 @@ namespace IW4MAdmin.Application.Misc
MaxConcurrentClientsAsync(long? serverId = null, Reference.Game? gameCode = null, TimeSpan? overPeriod = null,
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,7 +117,8 @@ 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) =>
{
@ -131,7 +133,8 @@ namespace IW4MAdmin.Application.Misc
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<ClientHistoryInfo>();
return [];
}
}
public async Task<int> RankedClientsCountAsync(long? serverId = null, string performanceBucket = null, CancellationToken token = default)
public async Task<int> 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), new object[] { serverId, performanceBucket }, _cacheTimeSpan);
}, nameof(_rankedClientsCache), [serverId, performanceBucketCode], _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)
{

View File

@ -25,6 +25,7 @@ namespace Data.Context
#region STATS
public DbSet<EFPerformanceBucket> PerformanceBuckets { get; set; }
public DbSet<Models.Vector3> Vector3s { get; set; }
public DbSet<EFACSnapshotVector3> SnapshotVector3s { get; set; }
public DbSet<EFACSnapshot> ACSnapshots { get; set; }

View File

@ -4,12 +4,17 @@ namespace Data.Models.Client.Stats;
public class EFPerformanceBucket
{
[Key]
public int PerformanceBucketId { get; set; }
[Key] public int PerformanceBucketId { get; set; }
/// <summary>
/// Identifier for Bucket
/// </summary>
[MaxLength(256)]
public string BucketCode { get; set; }
public string Code { get; set; }
/// <summary>
/// Friendly name for Bucket
/// </summary>
[MaxLength(256)]
public string BucketName { get; set; }
public string Name { get; set; }
}

View File

@ -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<Task<EFClientHitStatistic>> 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<EFClientHitStatistic> GetOrAddClientHit(int clientId, long? serverId = null, string performanceBucket = null,
private async Task<EFClientHitStatistic> 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)
{

View File

@ -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<long, string>(s.ServerId, s.PerformanceBucket))
.Select(s => new Tuple<long, string>(s.ServerId, s.PerformanceBucket.Code))
.ToListAsync());
}
}

View File

@ -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);
}

View File

@ -7,6 +7,6 @@
/// </summary>
public int? ClientId { get; set; }
public string ServerEndpoint { get; set; }
public string PerformanceBucket { get; set; }
public string PerformanceBucketCode { get; set; }
}
}

View File

@ -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<EFClientStatistics>()
.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<EFClientRankingHistory>()
.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<ClientRankingInfo>
{
Results = [new ClientRankingInfo(currentRanking, totalRankedClients, performanceBucket)]
Results = [new ClientRankingInfo(currentRanking, totalRankedClients, performanceBucketCode)]
};
}
}

View File

@ -115,7 +115,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return 0;
}
private Expression<Func<EFClientRankingHistory, bool>> GetNewRankingFunc(TimeSpan oldestStat, TimeSpan minPlayTime, long? serverId = null, string performanceBucket = null)
private Expression<Func<EFClientRankingHistory, bool>> 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<List<TopStatsInfo>> GetNewTopStats(int start, int count, long? serverId = null, string performanceBucket = null)
public async Task<List<TopStatsInfo>> 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<EFClientRankingHistory>()
.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<EFClientRankingHistory>()
.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<PerformanceBucketConfiguration> 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<EFClientStatistics>()
.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<EFClientRankingHistory>()
.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<EFClientRankingHistory>()
.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<EFClientRankingHistory>()
.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
/// </summary>
/// <param name="attackerStats">Stats of the attacker</param>
/// <param name="victimStats">Stats of the victim</param>
/// <param name="attacker">Attacker</param>
/// <param name="victim">Victim</param>
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<Func<EFClient, EFClientStatistics, double>>("EloRatingFunction");
// Unused? New code. TODO: Check if needed?
//var victimEloRatingFunc =
// victim.GetAdditionalProperty<Func<EFClient, EFClientStatistics, double>>("EloRatingFunction");
victimStats.EloRating =
attackerEloRatingFunc?.Invoke(victim, victimStats) ?? victimStats.EloRating;

View File

@ -620,7 +620,7 @@ public class ZombieClientStateManager(
}
public async Task GetAdvancedStatsMetrics(Dictionary<int, List<EFMeta>> 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

View File

@ -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.

View File

@ -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()
{

View File

@ -45,9 +45,9 @@ namespace SharedLibraryCore.Interfaces
/// Retrieves the number of ranked clients for given server id
/// </summary>
/// <param name="serverId">ServerId to query on</param>
/// <param name="performanceBucket"></param>
/// <param name="performanceBucketCode"></param>
/// <param name="token">CancellationToken</param>
/// <returns></returns>
Task<int> RankedClientsCountAsync(long? serverId = null, string performanceBucket = null, CancellationToken token = default);
Task<int> RankedClientsCountAsync(long? serverId = null, string performanceBucketCode = null, CancellationToken token = default);
}
}

View File

@ -83,7 +83,7 @@ namespace SharedLibraryCore
RConConnectionFactory = rconConnectionFactory;
ServerLogger = logger;
DefaultSettings = serviceProvider.GetRequiredService<DefaultSettings>();
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<string[]> ExecuteCommandAsync(string command, CancellationToken token = default);
public abstract Task SetDvarAsync(string name, object value, CancellationToken token = default);

View File

@ -35,7 +35,7 @@ namespace WebfrontCore.Controllers
{
ClientId = id,
ServerEndpoint = serverId,
PerformanceBucket = performanceBucket
PerformanceBucketCode = performanceBucket
}))?.Results?.First();
if (hitInfo is null)

View File

@ -72,7 +72,7 @@ namespace WebfrontCore.Controllers.Client.Legacy
IPAddress = selectedServer.ListenAddress,
Port = selectedServer.ListenPort,
Game = selectedServer.GameCode,
PerformanceBucket = selectedServer.PerformanceBucket
PerformanceBucket = selectedServer.PerformanceCode
}));
}