From 91078eec0f14588947d1de5c6dc53cd6eb63e320 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Fri, 23 Aug 2019 18:34:31 -0500 Subject: [PATCH] Update to some stat stuff to fix some latent issues --- Application/IO/GameLogEventDetection.cs | 1 - Application/IO/GameLogReaderHttp.cs | 2 +- Plugins/Stats/Commands/MostPlayed.cs | 2 +- Plugins/Stats/Commands/ResetStats.cs | 2 +- Plugins/Stats/Commands/TopStats.cs | 2 +- Plugins/Stats/Commands/ViewStats.cs | 2 +- Plugins/Stats/Controllers/StatsController.cs | 2 +- Plugins/Stats/Helpers/StatManager.cs | 186 +++++++++--------- Plugins/Stats/Plugin.cs | 20 +- .../ViewComponents/TopPlayersViewComponent.cs | 2 +- SharedLibraryCore/Database/DatabaseContext.cs | 5 +- 11 files changed, 107 insertions(+), 119 deletions(-) diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index e1e70884c..70c14df8e 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -70,7 +70,6 @@ namespace IW4MAdmin.Application.IO foreach (var ev in events) { _server.Manager.GetEventHandler().AddEvent(ev); - await ev.WaitAsync(Utilities.DefaultCommandTimeout, _server.Manager.CancellationToken); } previousFileSize = fileSize; diff --git a/Application/IO/GameLogReaderHttp.cs b/Application/IO/GameLogReaderHttp.cs index 846c9aaad..23fe5c9b3 100644 --- a/Application/IO/GameLogReaderHttp.cs +++ b/Application/IO/GameLogReaderHttp.cs @@ -31,7 +31,7 @@ namespace IW4MAdmin.Application.IO public long Length => -1; - public int UpdateInterval => 250; + public int UpdateInterval => 500; public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) { diff --git a/Plugins/Stats/Commands/MostPlayed.cs b/Plugins/Stats/Commands/MostPlayed.cs index ccb8872ce..605a259b8 100644 --- a/Plugins/Stats/Commands/MostPlayed.cs +++ b/Plugins/Stats/Commands/MostPlayed.cs @@ -16,7 +16,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands { public static async Task> GetMostPlayed(Server s) { - long serverId = await StatManager.GetIdForServer(s); + long serverId = StatManager.GetIdForServer(s); List mostPlayed = new List() { diff --git a/Plugins/Stats/Commands/ResetStats.cs b/Plugins/Stats/Commands/ResetStats.cs index 700713cf1..51df65fb8 100644 --- a/Plugins/Stats/Commands/ResetStats.cs +++ b/Plugins/Stats/Commands/ResetStats.cs @@ -17,7 +17,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands if (E.Origin.ClientNumber >= 0) { - long serverId = await Helpers.StatManager.GetIdForServer(E.Owner); + long serverId = Helpers.StatManager.GetIdForServer(E.Owner); EFClientStatistics clientStats; using (var ctx = new DatabaseContext(disableTracking: true)) diff --git a/Plugins/Stats/Commands/TopStats.cs b/Plugins/Stats/Commands/TopStats.cs index 0b79cf38e..b56e86c1e 100644 --- a/Plugins/Stats/Commands/TopStats.cs +++ b/Plugins/Stats/Commands/TopStats.cs @@ -17,7 +17,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands { public static async Task> GetTopStats(Server s) { - long serverId = await StatManager.GetIdForServer(s); + long serverId = StatManager.GetIdForServer(s); List topStatsText = new List() { $"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--" diff --git a/Plugins/Stats/Commands/ViewStats.cs b/Plugins/Stats/Commands/ViewStats.cs index aa42184aa..9bb51b59f 100644 --- a/Plugins/Stats/Commands/ViewStats.cs +++ b/Plugins/Stats/Commands/ViewStats.cs @@ -42,7 +42,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands } } - long serverId = await StatManager.GetIdForServer(E.Owner); + long serverId = StatManager.GetIdForServer(E.Owner); using (var ctx = new DatabaseContext(disableTracking: true)) { diff --git a/Plugins/Stats/Controllers/StatsController.cs b/Plugins/Stats/Controllers/StatsController.cs index 28554a4a5..57a778402 100644 --- a/Plugins/Stats/Controllers/StatsController.cs +++ b/Plugins/Stats/Controllers/StatsController.cs @@ -36,7 +36,7 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers if (server != null) { - serverId = await StatManager.GetIdForServer(server); + serverId = StatManager.GetIdForServer(server); } var results = await Plugin.Manager.GetTopStats(offset, count, serverId); diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index c22449786..b165651f2 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -21,17 +21,28 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { private readonly ConcurrentDictionary _servers; private readonly ILogger _log; + private static List serverModels; private readonly SemaphoreSlim OnProcessingPenalty; private readonly SemaphoreSlim OnProcessingSensitive; + private readonly List _hitCache; public StatManager(IManager mgr) { _servers = new ConcurrentDictionary(); + _hitCache = new List(); _log = mgr.GetLogger(0); OnProcessingPenalty = new SemaphoreSlim(1, 1); OnProcessingSensitive = new SemaphoreSlim(1, 1); } + private void SetupServerIds() + { + using (var ctx = new DatabaseContext(disableTracking: true)) + { + serverModels = ctx.Set().ToList(); + } + } + public EFClientStatistics GetClientStats(int clientId, long serverId) { return _servers[serverId].PlayerStats[clientId]; @@ -188,7 +199,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // insert the server if it does not exist try { - long serverId = GetIdForServer(sv).Result; + if (serverModels == null) + { + SetupServerIds(); + } + + long serverId = GetIdForServer(sv); EFServer server; using (var ctx = new DatabaseContext(disableTracking: true)) @@ -263,7 +279,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers try { - long serverId = await GetIdForServer(pl.CurrentServer); + long serverId = GetIdForServer(pl.CurrentServer); if (!_servers.ContainsKey(serverId)) { @@ -363,7 +379,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers clientStats.SessionScore = pl.Score; clientStats.LastScore = pl.Score; - if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(_log, clientStats))) + if (!detectionStats.TryAdd(pl.ClientId, new Detection(_log, clientStats))) { _log.WriteWarning("Could not add client to detection"); } @@ -397,7 +413,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { pl.CurrentServer.Logger.WriteInfo($"Removing {pl} from stats"); - long serverId = await GetIdForServer(pl.CurrentServer); + long serverId = GetIdForServer(pl.CurrentServer); var playerStats = _servers[serverId].PlayerStats; var detectionStats = _servers[serverId].PlayerDetections; var serverStats = _servers[serverId].ServerStatistics; @@ -406,8 +422,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { pl.CurrentServer.Logger.WriteWarning($"Client disconnecting not in stats {pl}"); // remove the client from the stats dictionary as they're leaving - playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue1); - detectionStats.TryRemove(pl.ClientId, out Detection removedValue2); + playerStats.TryRemove(pl.ClientId, out _); + detectionStats.TryRemove(pl.ClientId, out _); return; } @@ -415,8 +431,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var clientStats = playerStats[pl.ClientId]; // remove the client from the stats dictionary as they're leaving - playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3); - detectionStats.TryRemove(pl.ClientId, out Detection removedValue4); + playerStats.TryRemove(pl.ClientId, out _); + detectionStats.TryRemove(pl.ClientId, out _); // sync their stats before they leave clientStats = UpdateStats(clientStats); @@ -433,20 +449,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId) { - // todo: maybe do something with this - //string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$"; - //var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase); - - //if (match.Success) - //{ - // // this gives us what team the player is on - // var attackerStats = Servers[serverId].PlayerStats[attackerClientId]; - // var victimStats = Servers[serverId].PlayerStats[victimClientId]; - // IW4Info.Team victimTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[4].ToString(), true); - // IW4Info.Team attackerTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[7].ToString(), true); - // attackerStats.Team = attackerTeam; - // victimStats.Team = victimTeam; - //} } /// @@ -516,10 +518,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers AnglesList = snapshotAngles }; - if ((hit.DeathType == IW4Info.MeansOfDeath.MOD_SUICIDE && - hit.Damage == 100000) || hit.HitLoc == IW4Info.HitLocation.shield) + if (hit.HitLoc == IW4Info.HitLocation.shield) { - // suicide by switching teams so let's not count it against them + // we don't care about shield hits return; } @@ -550,74 +551,80 @@ namespace IW4MAdmin.Plugins.Stats.Helpers clientStats.HitLocations.Single(hl => hl.Location == hit.HitLoc).HitCount += 1; } - using (var ctx = new DatabaseContext(disableTracking: true)) - { - ctx.Set().Update(clientStats); - await ctx.SaveChangesAsync(); - } - using (var ctx = new DatabaseContext()) + try { - try + if (Plugin.Config.Configuration().StoreClientKills) { - if (Plugin.Config.Configuration().StoreClientKills) - { - ctx.Set().Add(hit); - } + _hitCache.Add(hit); - if (Plugin.Config.Configuration().EnableAntiCheat && !attacker.IsBot && attacker.ClientId != victim.ClientId) + if (_hitCache.Count > Detection.MAX_TRACKED_HIT_COUNT) { - DetectionPenaltyResult result = new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Any }; + + using (var ctx = new DatabaseContext()) + { + ctx.AddRange(_hitCache); + await ctx.SaveChangesAsync(); + } + + _hitCache.Clear(); + } + } + + + if (Plugin.Config.Configuration().EnableAntiCheat && !attacker.IsBot && attacker.ClientId != victim.ClientId) + { + DetectionPenaltyResult result = new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Any }; #if DEBUG - if (clientDetection.TrackedHits.Count > 0) + if (clientDetection.TrackedHits.Count > 0) #else if (clientDetection.TrackedHits.Count > Detection.MAX_TRACKED_HIT_COUNT) #endif + { + while (clientDetection.TrackedHits.Count > 0) { - while (clientDetection.TrackedHits.Count > 0) + await OnProcessingPenalty.WaitAsync(); + + var oldestHit = clientDetection.TrackedHits.OrderBy(_hits => _hits.TimeOffset).First(); + clientDetection.TrackedHits.Remove(oldestHit); + + result = clientDetection.ProcessHit(oldestHit, isDamage); + await ApplyPenalty(result, attacker); + + if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any) { - await OnProcessingPenalty.WaitAsync(); - - var oldestHit = clientDetection.TrackedHits.OrderBy(_hits => _hits.TimeOffset).First(); - clientDetection.TrackedHits.Remove(oldestHit); - - result = clientDetection.ProcessHit(oldestHit, isDamage); - await ApplyPenalty(result, attacker, ctx); - - if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any) + using (var ctx = new DatabaseContext()) { SaveTrackedSnapshots(clientDetection, ctx); - - if (result.ClientPenalty == EFPenalty.PenaltyType.Ban) - { - OnProcessingPenalty.Release(1); - break; - } + await ctx.SaveChangesAsync(); } - OnProcessingPenalty.Release(1); + if (result.ClientPenalty == EFPenalty.PenaltyType.Ban) + { + OnProcessingPenalty.Release(1); + break; + } } - } - else - { - clientDetection.TrackedHits.Add(hit); + OnProcessingPenalty.Release(1); } } - ctx.Set().UpdateRange(clientStats.HitLocations); - await ctx.SaveChangesAsync(); + else + { + clientDetection.TrackedHits.Add(hit); + } } + } - catch (Exception ex) - { - _log.WriteError("Could not save hit or AC info"); - _log.WriteDebug(ex.GetExceptionInfo()); - } + catch (Exception ex) + { + _log.WriteError("Could not save hit or AC info"); + _log.WriteDebug(ex.GetExceptionInfo()); } } - async Task ApplyPenalty(DetectionPenaltyResult penalty, EFClient attacker, DatabaseContext ctx) + async Task ApplyPenalty(DetectionPenaltyResult penalty, EFClient attacker) { var penaltyClient = Utilities.IW4MAdminClient(attacker.CurrentServer); switch (penalty.ClientPenalty) @@ -713,7 +720,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public async Task AddStandardKill(EFClient attacker, EFClient victim) { - long serverId = await GetIdForServer(attacker.CurrentServer); + long serverId = GetIdForServer(attacker.CurrentServer); EFClientStatistics attackerStats = null; if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) @@ -743,7 +750,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // update the total stats _servers[serverId].ServerStatistics.TotalKills += 1; - await Sync(attacker.CurrentServer); // this happens when the round has changed if (attackerStats.SessionScore == 0) @@ -801,16 +807,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers attackerStats.LastStatHistoryUpdate = DateTime.UtcNow; await UpdateStatHistory(attacker, attackerStats); } - - // todo: do we want to save this immediately? - using (var ctx = new DatabaseContext(disableTracking: true)) - { - var clientStatsSet = ctx.Set(); - - clientStatsSet.Attach(attackerStats).State = EntityState.Modified; - clientStatsSet.Attach(victimStats).State = EntityState.Modified; - await ctx.SaveChangesAsync(); - } } /// @@ -824,12 +820,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds; // don't update their stat history if they haven't played long -#if DEBUG == false + //#if DEBUG == false if (currentSessionTime < 60) { return; } -#endif + //#endif int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime; @@ -858,12 +854,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers #region INDIVIDUAL_SERVER_PERFORMANCE // get the client ranking for the current server - int individualClientRanking = await ctx.Set() - .Where(GetRankingFunc(clientStats.ServerId)) - // ignore themselves in the query - .Where(c => c.RatingHistory.ClientId != client.ClientId) - .Where(c => c.Performance > clientStats.Performance) - .CountAsync() + 1; + int individualClientRanking = await ctx.Set() + .Where(GetRankingFunc(clientStats.ServerId)) + // ignore themselves in the query + .Where(c => c.RatingHistory.ClientId != client.ClientId) + .Where(c => c.Performance > clientStats.Performance) + .CountAsync() + 1; // limit max history per server to 40 if (clientHistory.Ratings.Count(r => r.ServerId == clientStats.ServerId) >= 40) @@ -1218,7 +1214,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public async Task Sync(Server sv) { - long serverId = await GetIdForServer(sv); + long serverId = GetIdForServer(sv); using (var ctx = new DatabaseContext(disableTracking: true)) { @@ -1237,7 +1233,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers _servers[serverId].IsTeamBased = isTeamBased; } - public static async Task GetIdForServer(Server server) + public static long GetIdForServer(Server server) { if ($"{server.IP}:{server.Port.ToString()}" == "66.150.121.184:28965") { @@ -1248,13 +1244,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers id = id < 0 ? Math.Abs(id) : id; long? serverId; - // todo: cache this eventually, as it shouldn't change - using (var ctx = new DatabaseContext(disableTracking: true)) - { - serverId = (await ctx.Set().FirstOrDefaultAsync(_server => _server.ServerId == server.EndPoint || - _server.EndPoint == server.ToString() || - _server.ServerId == id))?.ServerId; - } + serverId = serverModels.FirstOrDefault(_server => _server.ServerId == server.EndPoint || + _server.EndPoint == server.ToString() || + _server.ServerId == id)?.ServerId; if (!serverId.HasValue) { diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index 897dd2d79..29c90e908 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -50,12 +50,12 @@ namespace IW4MAdmin.Plugins.Stats if (!string.IsNullOrEmpty(E.Data) && E.Origin.ClientId > 1) { - await Manager.AddMessageAsync(E.Origin.ClientId, await StatManager.GetIdForServer(E.Owner), E.Data); + await Manager.AddMessageAsync(E.Origin.ClientId, StatManager.GetIdForServer(E.Owner), E.Data); } break; case GameEvent.EventType.MapChange: - Manager.SetTeamBased(await StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm"); - Manager.ResetKillstreaks(await StatManager.GetIdForServer(E.Owner)); + Manager.SetTeamBased(StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm"); + Manager.ResetKillstreaks(StatManager.GetIdForServer(E.Owner)); break; case GameEvent.EventType.MapEnd: break; @@ -77,7 +77,7 @@ namespace IW4MAdmin.Plugins.Stats break; case GameEvent.EventType.ScriptKill: string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; - if (killInfo.Length >= 14 && !ShouldIgnoreEvent(E.Origin, E.Target)) + if (E.Owner.CustomCallback && killInfo.Length >= 14 && !ShouldIgnoreEvent(E.Origin, E.Target)) { // this treats "world" damage as self damage if (IsWorldDamage(E.Origin)) @@ -85,7 +85,7 @@ namespace IW4MAdmin.Plugins.Stats E.Origin = E.Target; } - await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8], + await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]); } break; @@ -100,10 +100,6 @@ namespace IW4MAdmin.Plugins.Stats await Manager.AddStandardKill(E.Origin, E.Target); } - else - { - throw new Exception(); - } break; case GameEvent.EventType.Damage: if (!E.Owner.CustomCallback && !ShouldIgnoreEvent(E.Origin, E.Target)) @@ -114,12 +110,12 @@ namespace IW4MAdmin.Plugins.Stats E.Origin = E.Target; } - Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, await StatManager.GetIdForServer(E.Owner)); + Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, StatManager.GetIdForServer(E.Owner)); } break; case GameEvent.EventType.ScriptDamage: killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; - if (killInfo.Length >= 14 && !ShouldIgnoreEvent(E.Origin, E.Target)) + if (E.Owner.CustomCallback && killInfo.Length >= 14 && !ShouldIgnoreEvent(E.Origin, E.Target)) { // this treats "world" damage as self damage if (IsWorldDamage(E.Origin)) @@ -127,7 +123,7 @@ namespace IW4MAdmin.Plugins.Stats E.Origin = E.Target; } - await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8], + await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]); } break; diff --git a/Plugins/Stats/ViewComponents/TopPlayersViewComponent.cs b/Plugins/Stats/ViewComponents/TopPlayersViewComponent.cs index 88e43bccb..ad00b0fb9 100644 --- a/Plugins/Stats/ViewComponents/TopPlayersViewComponent.cs +++ b/Plugins/Stats/ViewComponents/TopPlayersViewComponent.cs @@ -19,7 +19,7 @@ namespace Stats.ViewComponents if (server != null) { - serverId = await StatManager.GetIdForServer(server); + serverId = StatManager.GetIdForServer(server); } return View("_List", await Plugin.Manager.GetTopStats(offset, count, serverId)); diff --git a/SharedLibraryCore/Database/DatabaseContext.cs b/SharedLibraryCore/Database/DatabaseContext.cs index adbd0b04e..437789a3f 100644 --- a/SharedLibraryCore/Database/DatabaseContext.cs +++ b/SharedLibraryCore/Database/DatabaseContext.cs @@ -22,6 +22,7 @@ namespace SharedLibraryCore.Database public DbSet EFMeta { get; set; } public DbSet EFChangeHistory { get; set; } + [Obsolete] private static readonly ILoggerFactory _loggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((category, level) => level == LogLevel.Information, true) @@ -113,9 +114,9 @@ namespace SharedLibraryCore.Database #if DEBUG #pragma warning disable CS0612 // Type or member is obsolete - optionsBuilder.UseLoggerFactory(_loggerFactory) + // optionsBuilder.UseLoggerFactory(_loggerFactory) #pragma warning restore CS0612 // Type or member is obsolete - .EnableSensitiveDataLogging(); + // .EnableSensitiveDataLogging(); #endif }