From 198f596ab34f6b2b211f06d17bc2422507cfcbd5 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Mon, 9 Sep 2019 17:37:57 -0500 Subject: [PATCH] small updates to stat handling various little tweaks --- Application/IO/GameLogEventDetection.cs | 1 + Application/IO/GameLogReader.cs | 13 +- Application/Misc/Logger.cs | 1 - Plugins/Stats/Cheat/Detection.cs | 11 +- Plugins/Stats/Commands/ViewStats.cs | 46 ++- Plugins/Stats/Helpers/ServerStats.cs | 3 + Plugins/Stats/Helpers/StatManager.cs | 317 +++++++++------------ Plugins/Stats/Models/EFClientStatistics.cs | 15 +- Plugins/Stats/Plugin.cs | 33 ++- SharedLibraryCore/Helpers/Vector3.cs | 8 +- SharedLibraryCore/RCon/Connection.cs | 23 +- SharedLibraryCore/SharedLibraryCore.csproj | 4 +- 12 files changed, 248 insertions(+), 227 deletions(-) diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index 3b89ccfb6..18a351d16 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -72,6 +72,7 @@ namespace IW4MAdmin.Application.IO foreach (var ev in events) { _server.Manager.GetEventHandler().AddEvent(ev); + await ev.WaitAsync(Utilities.DefaultCommandTimeout, ev.Owner.Manager.CancellationToken); } previousFileSize = fileSize; diff --git a/Application/IO/GameLogReader.cs b/Application/IO/GameLogReader.cs index 247a6804e..658e8ff2a 100644 --- a/Application/IO/GameLogReader.cs +++ b/Application/IO/GameLogReader.cs @@ -36,14 +36,15 @@ namespace IW4MAdmin.Application.IO List logLines = new List(); // open the file as a stream - using (var rd = new StreamReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType)) + using (FileStream fs = new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { - char[] buff = new char[fileSizeDiff]; - rd.BaseStream.Seek(startPosition, SeekOrigin.Begin); - await rd.ReadAsync(buff, 0, (int)fileSizeDiff); - + byte[] buff = new byte[fileSizeDiff]; + fs.Seek(startPosition, SeekOrigin.Begin); + await fs.ReadAsync(buff, 0, (int)fileSizeDiff, server.Manager.CancellationToken); var stringBuilder = new StringBuilder(); - foreach (char c in buff) + char[] charBuff = Utilities.EncodingType.GetChars(buff); + + foreach (char c in charBuff) { if (c == '\n') { diff --git a/Application/Misc/Logger.cs b/Application/Misc/Logger.cs index c10a90fc4..d19b1f0ec 100644 --- a/Application/Misc/Logger.cs +++ b/Application/Misc/Logger.cs @@ -1,7 +1,6 @@ using SharedLibraryCore; using SharedLibraryCore.Interfaces; using System; -using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; diff --git a/Plugins/Stats/Cheat/Detection.cs b/Plugins/Stats/Cheat/Detection.cs index 0155dded3..895ed6ff6 100644 --- a/Plugins/Stats/Cheat/Detection.cs +++ b/Plugins/Stats/Cheat/Detection.cs @@ -1,4 +1,5 @@ using IW4MAdmin.Plugins.Stats.Models; +using SharedLibraryCore; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; @@ -22,7 +23,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat }; public ChangeTracking Tracker { get; private set; } - public const int MAX_TRACKED_HIT_COUNT = 10; + public const int MIN_HITS_TO_RUN_DETECTION = 5; + private const int MIN_ANGLE_COUNT = 5; public List TrackedHits { get; set; } int Kills; @@ -36,6 +38,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat Strain Strain; readonly DateTime ConnectionTime = DateTime.UtcNow; private double sessionAverageRecoilAmount; + private EFClientKill lastHit; private class HitInfo { @@ -344,7 +347,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat #endregion #endregion - Tracker.OnChange(new EFACSnapshot() + var snapshot = new EFACSnapshot() { When = hit.When, ClientId = ClientStats.ClientId, @@ -372,7 +375,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat StrainAngleBetween = Strain.LastDistance, TimeSinceLastEvent = (int)Strain.LastDeltaTime, WeaponId = hit.Weapon - }); + }; + + Tracker.OnChange(snapshot); return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ?? results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ?? diff --git a/Plugins/Stats/Commands/ViewStats.cs b/Plugins/Stats/Commands/ViewStats.cs index 9bb51b59f..a13688e80 100644 --- a/Plugins/Stats/Commands/ViewStats.cs +++ b/Plugins/Stats/Commands/ViewStats.cs @@ -44,25 +44,45 @@ namespace IW4MAdmin.Plugins.Stats.Commands long serverId = StatManager.GetIdForServer(E.Owner); - using (var ctx = new DatabaseContext(disableTracking: true)) - { - if (E.Target != null) - { - int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId); - string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}"; - pStats = (await ctx.Set().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId)); - statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}"; + if (E.Target != null) + { + int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId); + string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}"; + + if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Target))) + { + pStats = Plugin.Manager.GetClientStats(E.Target.ClientId, serverId); } else { - int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId); - string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}"; - - pStats = (await ctx.Set().FirstAsync((c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId))); - statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}"; + using (var ctx = new DatabaseContext(true)) + { + pStats = (await ctx.Set().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId)); + } } + statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}"; + } + + else + { + int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId); + string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}"; + + if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Origin))) + { + pStats = Plugin.Manager.GetClientStats(E.Origin.ClientId, serverId); + } + + else + { + using (var ctx = new DatabaseContext(true)) + { + pStats = (await ctx.Set().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId)); + } + } + statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}"; } if (E.Message.IsBroadcastCommand()) diff --git a/Plugins/Stats/Helpers/ServerStats.cs b/Plugins/Stats/Helpers/ServerStats.cs index ac76e3776..515a1969e 100644 --- a/Plugins/Stats/Helpers/ServerStats.cs +++ b/Plugins/Stats/Helpers/ServerStats.cs @@ -2,6 +2,7 @@ using IW4MAdmin.Plugins.Stats.Models; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; namespace IW4MAdmin.Plugins.Stats.Helpers @@ -9,6 +10,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers class ServerStats { public ConcurrentDictionary PlayerStats { get; set; } public ConcurrentDictionary PlayerDetections { get; set; } + public IList HitCache { get; private set; } public EFServerStatistics ServerStatistics { get; private set; } public EFServer Server { get; private set; } public bool IsTeamBased { get; set; } @@ -17,6 +19,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { PlayerStats = new ConcurrentDictionary(); PlayerDetections = new ConcurrentDictionary(); + HitCache = new List(); ServerStatistics = st; Server = sv; } diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 2ab9f472b..4ffbe4e3f 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -19,20 +19,15 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { public class StatManager { + private const int MAX_CACHED_HITS = 100; 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() @@ -143,6 +138,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var iqStatsInfo = (from stat in context.Set() where clientIds.Contains(stat.ClientId) where stat.Kills > 0 || stat.Deaths > 0 + where serverId == null ? true : stat.ServerId == serverId group stat by stat.ClientId into s select new { @@ -150,7 +146,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Kills = s.Sum(c => c.Kills), Deaths = s.Sum(c => c.Deaths), KDR = s.Sum(c => (c.Kills / (double)(c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) / s.Sum(c => c.TimePlayed), - TotalTimePlayed = s.Sum(c => c.TimePlayed) + TotalTimePlayed = s.Sum(c => c.TimePlayed), }); #if DEBUG == true @@ -275,8 +271,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers /// EFClientStatistic of specified player public async Task AddPlayer(EFClient pl) { - await OnProcessingSensitive.WaitAsync(); - try { long serverId = GetIdForServer(pl.CurrentServer); @@ -396,11 +390,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers _log.WriteDebug(ex.GetExceptionInfo()); } - finally - { - OnProcessingSensitive.Release(1); - } - return null; } @@ -466,166 +455,144 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Vector3 vDeathOrigin = null; Vector3 vKillOrigin = null; Vector3 vViewAngles = null; + SemaphoreSlim waiter = null; try { - vDeathOrigin = Vector3.Parse(deathOrigin); - vKillOrigin = Vector3.Parse(killOrigin); - vViewAngles = Vector3.Parse(viewAngles).FixIW4Angles(); - } - - catch (FormatException) - { - _log.WriteWarning("Could not parse kill or death origin or viewangle vectors"); - _log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles}"); - await AddStandardKill(attacker, victim); - return; - } - - var snapshotAngles = new List(); - - try - { - foreach (string angle in snapAngles.Split(':', StringSplitOptions.RemoveEmptyEntries)) + try { - snapshotAngles.Add(Vector3.Parse(angle).FixIW4Angles()); + vDeathOrigin = Vector3.Parse(deathOrigin); + vKillOrigin = Vector3.Parse(killOrigin); + vViewAngles = Vector3.Parse(viewAngles).FixIW4Angles(); } - } - catch (FormatException) - { - _log.WriteWarning("Could not parse snapshot angles"); - return; - } + catch (FormatException) + { + _log.WriteWarning("Could not parse kill or death origin or viewangle vectors"); + _log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles}"); + await AddStandardKill(attacker, victim); + return; + } - var hit = new EFClientKill() - { - Active = true, - AttackerId = attacker.ClientId, - VictimId = victim.ClientId, - ServerId = serverId, - //Map = ParseEnum.Get(map, typeof(IW4Info.MapName)), - DeathOrigin = vDeathOrigin, - KillOrigin = vKillOrigin, - DeathType = ParseEnum.Get(type, typeof(IW4Info.MeansOfDeath)), - Damage = int.Parse(damage), - HitLoc = ParseEnum.Get(hitLoc, typeof(IW4Info.HitLocation)), - Weapon = ParseEnum.Get(weapon, typeof(IW4Info.WeaponName)), - ViewAngles = vViewAngles, - TimeOffset = long.Parse(offset), - When = time, - IsKillstreakKill = isKillstreakKill[0] != '0', - AdsPercent = float.Parse(Ads, System.Globalization.CultureInfo.InvariantCulture), - Fraction = double.Parse(fraction, System.Globalization.CultureInfo.InvariantCulture), - VisibilityPercentage = double.Parse(visibilityPercentage, System.Globalization.CultureInfo.InvariantCulture), - IsKill = !isDamage, - AnglesList = snapshotAngles - }; + var snapshotAngles = new List(); - if (hit.HitLoc == IW4Info.HitLocation.shield) - { - // we don't care about shield hits - return; - } + try + { + foreach (string angle in snapAngles.Split(':', StringSplitOptions.RemoveEmptyEntries)) + { + snapshotAngles.Add(Vector3.Parse(angle).FixIW4Angles()); + } + } - if (!isDamage) - { - await AddStandardKill(attacker, victim); - } + catch (FormatException) + { + _log.WriteWarning("Could not parse snapshot angles"); + return; + } - // incase the add player event get delayed - if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) - { - await AddPlayer(attacker); - } + var hit = new EFClientKill() + { + Active = true, + AttackerId = attacker.ClientId, + VictimId = victim.ClientId, + ServerId = serverId, + DeathOrigin = vDeathOrigin, + KillOrigin = vKillOrigin, + DeathType = ParseEnum.Get(type, typeof(IW4Info.MeansOfDeath)), + Damage = int.Parse(damage), + HitLoc = ParseEnum.Get(hitLoc, typeof(IW4Info.HitLocation)), + Weapon = ParseEnum.Get(weapon, typeof(IW4Info.WeaponName)), + ViewAngles = vViewAngles, + TimeOffset = long.Parse(offset), + When = time, + IsKillstreakKill = isKillstreakKill[0] != '0', + AdsPercent = float.Parse(Ads, System.Globalization.CultureInfo.InvariantCulture), + Fraction = double.Parse(fraction, System.Globalization.CultureInfo.InvariantCulture), + VisibilityPercentage = double.Parse(visibilityPercentage, System.Globalization.CultureInfo.InvariantCulture), + IsKill = !isDamage, + AnglesList = snapshotAngles + }; - var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId]; - var clientStats = _servers[serverId].PlayerStats[attacker.ClientId]; + if (hit.HitLoc == IW4Info.HitLocation.shield) + { + // we don't care about shield hits + return; + } - // increment their hit count - if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || - hit.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || - hit.DeathType == IW4Info.MeansOfDeath.MOD_HEAD_SHOT) - { - clientStats.HitLocations.First(hl => hl.Location == hit.HitLoc).HitCount += 1; - } + // incase the add player event get delayed + if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) + { + await AddPlayer(attacker); + } - if (clientStats.SessionKills % Detection.MAX_TRACKED_HIT_COUNT == 0) - { - await OnProcessingPenalty.WaitAsync(); - await SaveClientStats(clientStats); - OnProcessingPenalty.Release(1); - } + if (!isDamage) + { + await AddStandardKill(attacker, victim); + } - if (hit.IsKillstreakKill) - { - return; - } + var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId]; + var clientStats = _servers[serverId].PlayerStats[attacker.ClientId]; + + waiter = clientStats.ProcessingHit; + await waiter.WaitAsync(); + + // increment their hit count + if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || + hit.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || + hit.DeathType == IW4Info.MeansOfDeath.MOD_HEAD_SHOT) + { + clientStats.HitLocations.First(hl => hl.Location == hit.HitLoc).HitCount += 1; + } + + if (hit.IsKillstreakKill) + { + return; + } - try - { if (Plugin.Config.Configuration().StoreClientKills) { - await OnProcessingPenalty.WaitAsync(); - _hitCache.Add(hit); + var cache = _servers[serverId].HitCache; + cache.Add(hit); - if (_hitCache.Count > Detection.MAX_TRACKED_HIT_COUNT) + if (cache.Count > MAX_CACHED_HITS) { - - using (var ctx = new DatabaseContext()) - { - ctx.AddRange(_hitCache); - await ctx.SaveChangesAsync(); - } - - _hitCache.Clear(); + await SaveHitCache(serverId); } - OnProcessingPenalty.Release(1); } if (Plugin.Config.Configuration().EnableAntiCheat && !attacker.IsBot && attacker.ClientId != victim.ClientId) { DetectionPenaltyResult result = new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Any }; - await OnProcessingPenalty.WaitAsync(); + clientDetection.TrackedHits.Add(hit); -#if DEBUG - if (clientDetection.TrackedHits.Count > 0) -#else - if (clientDetection.TrackedHits.Count > Detection.MAX_TRACKED_HIT_COUNT) -#endif + if (clientDetection.TrackedHits.Count >= Detection.MIN_HITS_TO_RUN_DETECTION) { while (clientDetection.TrackedHits.Count > 0) { - + var oldestHit = clientDetection.TrackedHits + .OrderBy(_hits => _hits.TimeOffset) + .First(); - var oldestHit = clientDetection.TrackedHits.OrderBy(_hits => _hits.TimeOffset).First(); clientDetection.TrackedHits.Remove(oldestHit); result = clientDetection.ProcessHit(oldestHit, isDamage); +#if !DEBUG await ApplyPenalty(result, attacker); +#endif if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any) { - using (var ctx = new DatabaseContext()) - { - SaveTrackedSnapshots(clientDetection, ctx); - await ctx.SaveChangesAsync(); - } + await SaveTrackedSnapshots(clientDetection); if (result.ClientPenalty == EFPenalty.PenaltyType.Ban) { + // we don't care about any additional hits now that they're banned + clientDetection.TrackedHits.Clear(); break; } } } } - - else - { - clientDetection.TrackedHits.Add(hit); - } - - OnProcessingPenalty.Release(1); } } @@ -633,11 +600,22 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { _log.WriteError("Could not save hit or AC info"); _log.WriteDebug(ex.GetExceptionInfo()); + } - if (OnProcessingPenalty.CurrentCount == 0) - { - OnProcessingPenalty.Release(1); - } + finally + { + waiter?.Release(1); + } + } + + public async Task SaveHitCache(long serverId) + { + using (var ctx = new DatabaseContext(true)) + { + var server = _servers[serverId]; + ctx.AddRange(server.HitCache); + await ctx.SaveChangesAsync(); + server.HitCache.Clear(); } } @@ -679,59 +657,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } } - void SaveTrackedSnapshots(Detection clientDetection, DatabaseContext ctx) + async Task SaveTrackedSnapshots(Detection clientDetection) { - // todo: why does this cause duplicate primary key - _ = clientDetection.Tracker.GetNextChange(); EFACSnapshot change; - while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot)) + + using (var ctx = new DatabaseContext(true)) { - - if (change.HitOrigin.Vector3Id > 0) + while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot)) { - change.HitOriginId = change.HitOrigin.Vector3Id; - ctx.Attach(change.HitOrigin); + ctx.Add(change); } - - else if (change.HitOrigin.Vector3Id == 0) - { - ctx.Add(change.HitOrigin); - } - - if (change.HitDestination.Vector3Id > 0) - { - change.HitDestinationId = change.HitDestination.Vector3Id; - ctx.Attach(change.HitDestination); - } - - else if (change.HitDestination.Vector3Id == 0) - { - ctx.Add(change.HitOrigin); - } - - if (change.CurrentViewAngle.Vector3Id > 0) - { - change.CurrentViewAngleId = change.CurrentViewAngle.Vector3Id; - ctx.Attach(change.CurrentViewAngle); - } - - else if (change.CurrentViewAngle.Vector3Id == 0) - { - ctx.Add(change.HitOrigin); - } - - if (change.LastStrainAngle.Vector3Id > 0) - { - change.LastStrainAngleId = change.LastStrainAngle.Vector3Id; - ctx.Attach(change.LastStrainAngle); - } - - else if (change.LastStrainAngle.Vector3Id == 0) - { - ctx.Add(change.HitOrigin); - } - - ctx.Add(change); + await ctx.SaveChangesAsync(); } } @@ -815,11 +751,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } // update their performance -#if !DEBUG if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 2.5) -#else - if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 0.1) -#endif { attackerStats.LastStatHistoryUpdate = DateTime.UtcNow; await UpdateStatHistory(attacker, attackerStats); @@ -837,12 +769,10 @@ 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 (currentSessionTime < 60) { return; } - //#endif int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime; @@ -1240,9 +1170,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var serverStatsSet = ctx.Set(); serverStatsSet.Update(_servers[serverId].ServerStatistics); - - await ctx.SaveChangesAsync(); } + + foreach (var client in sv.GetClientsAsList()) + { + var stats = GetClientStats(client.ClientId, serverId); + if (stats != null) + { + await SaveClientStats(stats); + } + } + + await SaveHitCache(serverId); } public void SetTeamBased(long serverId, bool isTeamBased) diff --git a/Plugins/Stats/Models/EFClientStatistics.cs b/Plugins/Stats/Models/EFClientStatistics.cs index 7332702e6..45487c974 100644 --- a/Plugins/Stats/Models/EFClientStatistics.cs +++ b/Plugins/Stats/Models/EFClientStatistics.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using SharedLibraryCore.Database.Models; @@ -12,6 +13,16 @@ namespace IW4MAdmin.Plugins.Stats.Models { public class EFClientStatistics : SharedEntity { + public EFClientStatistics() + { + ProcessingHit = new SemaphoreSlim(1, 1); + } + + ~EFClientStatistics() + { + ProcessingHit.Dispose(); + } + public int ClientId { get; set; } [ForeignKey("ClientId")] public virtual EFClient Client { get; set; } @@ -96,12 +107,14 @@ namespace IW4MAdmin.Plugins.Stats.Models } } [NotMapped] - private List SessionScores = new List() { 0 }; + private readonly List SessionScores = new List() { 0 }; [NotMapped] public IW4Info.Team Team { get; set; } [NotMapped] public DateTime LastStatHistoryUpdate { get; set; } = DateTime.UtcNow; [NotMapped] public double SessionSPM { get; set; } + [NotMapped] + public SemaphoreSlim ProcessingHit { get; private set; } } } diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index 29c90e908..e77218b91 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -29,6 +29,10 @@ namespace IW4MAdmin.Plugins.Stats public static StatManager Manager { get; private set; } public static IManager ServerManager; public static BaseConfigurationHandler Config { get; private set; } +#if DEBUG + int scriptDamageCount; + int scriptKillCount; +#endif public async Task OnEventAsync(GameEvent E, Server S) { @@ -44,7 +48,6 @@ namespace IW4MAdmin.Plugins.Stats break; case GameEvent.EventType.Disconnect: await Manager.RemovePlayer(E.Origin); - await Manager.Sync(S); break; case GameEvent.EventType.Say: if (!string.IsNullOrEmpty(E.Data) && @@ -56,8 +59,10 @@ namespace IW4MAdmin.Plugins.Stats case GameEvent.EventType.MapChange: Manager.SetTeamBased(StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm"); Manager.ResetKillstreaks(StatManager.GetIdForServer(E.Owner)); + await Manager.Sync(E.Owner); break; case GameEvent.EventType.MapEnd: + await Manager.Sync(E.Owner); break; case GameEvent.EventType.JoinTeam: break; @@ -85,8 +90,17 @@ namespace IW4MAdmin.Plugins.Stats E.Origin = E.Target; } +#if DEBUG + scriptKillCount++; + S.Logger.WriteInfo($"Start ScriptKill {scriptKillCount}"); +#endif + 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]); + +#if DEBUG + S.Logger.WriteInfo($"End ScriptKill {scriptKillCount}"); +#endif } break; case GameEvent.EventType.Kill: @@ -123,8 +137,21 @@ namespace IW4MAdmin.Plugins.Stats E.Origin = E.Target; } +#if DEBUG + scriptDamageCount++; + S.Logger.WriteInfo($"Start ScriptDamage {scriptDamageCount}"); +#endif + 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]); + +#if DEBUG + S.Logger.WriteInfo($"End ScriptDamage {scriptDamageCount}"); +#endif + } + else + { + break; } break; } @@ -342,7 +369,7 @@ namespace IW4MAdmin.Plugins.Stats Order = 5, Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"], Sensitive = true - }, + } }; } @@ -483,7 +510,7 @@ namespace IW4MAdmin.Plugins.Stats /// private bool ShouldIgnoreEvent(EFClient origin, EFClient target) { - return ((origin?.NetworkId <= 1 && target?.NetworkId <= 1) || (origin?.ClientId <=1 && target?.ClientId <= 1)); + return ((origin?.NetworkId <= 1 && target?.NetworkId <= 1) || (origin?.ClientId <= 1 && target?.ClientId <= 1)); } /// diff --git a/SharedLibraryCore/Helpers/Vector3.cs b/SharedLibraryCore/Helpers/Vector3.cs index a0dae6eb8..f16981c72 100644 --- a/SharedLibraryCore/Helpers/Vector3.cs +++ b/SharedLibraryCore/Helpers/Vector3.cs @@ -13,7 +13,7 @@ namespace SharedLibraryCore.Helpers [Key] public int Vector3Id { get; set; } public float X { get; protected set; } - public float Y { get; protected set; } + public float Y { get; protected set; } public float Z { get; protected set; } // this is for EF and really should be somewhere else @@ -45,8 +45,8 @@ namespace SharedLibraryCore.Helpers string removeParenthesis = s.Substring(1, s.Length - 2); string[] eachPoint = removeParenthesis.Split(','); - return new Vector3(float.Parse(eachPoint[0], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture), - float.Parse(eachPoint[1], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture), + return new Vector3(float.Parse(eachPoint[0], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture), + float.Parse(eachPoint[1], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture), float.Parse(eachPoint[2], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture)); } @@ -57,7 +57,7 @@ namespace SharedLibraryCore.Helpers public static double AbsoluteDistance(Vector3 a, Vector3 b) { - double deltaX = Math.Abs(b.X -a.X); + double deltaX = Math.Abs(b.X - a.X); double deltaY = Math.Abs(b.Y - a.Y); // this 'fixes' the roll-over angles diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index a83a7c3fa..4987735ec 100644 --- a/SharedLibraryCore/RCon/Connection.cs +++ b/SharedLibraryCore/RCon/Connection.cs @@ -13,6 +13,13 @@ namespace SharedLibraryCore.RCon { class ConnectionState { + ~ConnectionState() + { + OnComplete.Dispose(); + OnSentData.Dispose(); + OnReceivedData.Dispose(); + } + public int ConnectionAttempts { get; set; } const int BufferSize = 4096; public readonly byte[] ReceiveBuffer = new byte[BufferSize]; @@ -81,7 +88,7 @@ namespace SharedLibraryCore.RCon bool waitForResponse = Config.WaitForResponse; string convertEncoding(string text) - { + { byte[] convertedBytes = Utilities.EncodingType.GetBytes(text); return defaultEncoding.GetString(convertedBytes); } @@ -123,7 +130,7 @@ namespace SharedLibraryCore.RCon catch (OverflowException) { connectionState.OnComplete.Release(1); - throw new NetworkException($"Invalid character expected when converting encodings - {parameters}"); + throw new NetworkException($"Invalid character encountered when converting encodings - {parameters}"); } byte[] response = null; @@ -152,7 +159,6 @@ namespace SharedLibraryCore.RCon throw new NetworkException("Expected response but got 0 bytes back"); } - connectionState.OnComplete.Release(1); connectionState.ConnectionAttempts = 0; } @@ -164,9 +170,16 @@ namespace SharedLibraryCore.RCon goto retrySend; } - connectionState.OnComplete.Release(1); throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"].FormatExt(Endpoint)); } + + finally + { + if (connectionState.OnComplete.CurrentCount == 0) + { + connectionState.OnComplete.Release(1); + } + } } string responseString = defaultEncoding.GetString(response, 0, response.Length) + '\n'; @@ -258,7 +271,7 @@ namespace SharedLibraryCore.RCon private void OnDataSent(object sender, SocketAsyncEventArgs e) { #if DEBUG == true - Log.WriteDebug($"Sent {e.Buffer.Length} bytes to {e.ConnectSocket.RemoteEndPoint.ToString()}"); + Log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}"); #endif ActiveQueries[this.Endpoint].OnSentData.Set(); } diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index a77fc081f..f416b1e93 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -46,8 +46,8 @@ - - + +