From c18be2089925b88a60cb8d46d795ec805e45589a Mon Sep 17 00:00:00 2001 From: RaidMax Date: Mon, 9 Sep 2019 17:40:04 -0500 Subject: [PATCH] add snap metric to anticheat update various small code bits --- Application/IO/GameLogEventDetection.cs | 1 + Application/IO/GameLogReader.cs | 13 +- Application/Misc/Logger.cs | 1 - Plugins/Stats/Cheat/Detection.cs | 93 ++- Plugins/Stats/Cheat/Thresholds.cs | 2 + Plugins/Stats/Commands/ViewStats.cs | 46 +- Plugins/Stats/Controllers/StatsController.cs | 1 + Plugins/Stats/Helpers/ServerStats.cs | 3 + Plugins/Stats/Helpers/StatManager.cs | 313 +++----- Plugins/Stats/Models/EFACSnapshot.cs | 8 +- Plugins/Stats/Models/EFACSnapshotVector3.cs | 26 + Plugins/Stats/Models/EFClientStatistics.cs | 17 +- Plugins/Stats/Plugin.cs | 43 +- .../StatsWeb/Views/Stats/_PenaltyInfo.cshtml | 4 +- SharedLibraryCore/Helpers/Vector3.cs | 45 +- ...AvgSnapValueToClientStatistics.Designer.cs | 706 +++++++++++++++++ ...31210503_AvgSnapValueToClientStatistics.cs | 23 + ...SnapHitCountToClientStatistics.Designer.cs | 708 +++++++++++++++++ ...80209_AddSnapHitCountToClientStatistics.cs | 23 + ...unctionTableForSnapshotVector3.Designer.cs | 728 ++++++++++++++++++ ...3620_UseJunctionTableForSnapshotVector3.cs | 89 +++ .../DatabaseContextModelSnapshot.cs | 46 +- SharedLibraryCore/RCon/Connection.cs | 21 +- SharedLibraryCore/SharedLibraryCore.csproj | 4 +- .../Controllers/DynamicFileController.cs | 3 +- _customcallbacks.gsc | 69 +- version.txt | 1 + 27 files changed, 2768 insertions(+), 269 deletions(-) create mode 100644 Plugins/Stats/Models/EFACSnapshotVector3.cs create mode 100644 SharedLibraryCore/Migrations/20190831210503_AvgSnapValueToClientStatistics.Designer.cs create mode 100644 SharedLibraryCore/Migrations/20190831210503_AvgSnapValueToClientStatistics.cs create mode 100644 SharedLibraryCore/Migrations/20190901180209_AddSnapHitCountToClientStatistics.Designer.cs create mode 100644 SharedLibraryCore/Migrations/20190901180209_AddSnapHitCountToClientStatistics.cs create mode 100644 SharedLibraryCore/Migrations/20190901223620_UseJunctionTableForSnapshotVector3.Designer.cs create mode 100644 SharedLibraryCore/Migrations/20190901223620_UseJunctionTableForSnapshotVector3.cs 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 123c0bb2d..88644e7d6 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..5ec29c81a 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; @@ -18,11 +19,13 @@ namespace IW4MAdmin.Plugins.Stats.Cheat Chest, Offset, Strain, - Recoil + Recoil, + Snap }; 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 +39,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat Strain Strain; readonly DateTime ConnectionTime = DateTime.UtcNow; private double sessionAverageRecoilAmount; + private double sessionAverageSnapAmount; + private int sessionSnapHits; + private EFClientKill lastHit; private class HitInfo { @@ -91,10 +97,57 @@ namespace IW4MAdmin.Plugins.Stats.Cheat Kills++; } - #region VIEWANGLES - if (hit.AnglesList.Count >= 2) + #region SNAP + if (hit.AnglesList.Count == MIN_ANGLE_COUNT) { - double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles); + if (lastHit == null) + { + lastHit = hit; + } + + if (lastHit == hit || lastHit.VictimId != hit.VictimId || (hit.TimeOffset - lastHit.TimeOffset) >= 1000) + { + ClientStats.SnapHitCount++; + sessionSnapHits++; + var currentSnapDistance = Vector3.SnapDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles); + double previousAverage = ClientStats.AverageSnapValue; + ClientStats.AverageSnapValue = (previousAverage * (ClientStats.SnapHitCount - 1) + currentSnapDistance) / ClientStats.SnapHitCount; + double previousSessionAverage = sessionAverageSnapAmount; + sessionAverageSnapAmount = (previousSessionAverage * (sessionSnapHits - 1) + currentSnapDistance) / sessionSnapHits; + lastHit = hit; + + if (sessionSnapHits > Thresholds.LowSampleMinKills && sessionAverageSnapAmount > Thresholds.SnapFlagValue) + { + results.Add(new DetectionPenaltyResult() + { + ClientPenalty = EFPenalty.PenaltyType.Flag, + Value = sessionAverageSnapAmount, + HitCount = ClientStats.SnapHitCount, + Type = DetectionType.Snap + }); + } + + if (sessionSnapHits > Thresholds.LowSampleMinKills && sessionAverageSnapAmount > Thresholds.SnapBanValue) + { + results.Add(new DetectionPenaltyResult() + { + ClientPenalty = EFPenalty.PenaltyType.Ban, + Value = sessionAverageSnapAmount, + HitCount = ClientStats.SnapHitCount, + Type = DetectionType.Snap + }); + } + } + } + #endregion + + #region VIEWANGLES + int totalUsableAngleCount = hit.AnglesList.Count - 1; + int angleOffsetIndex = totalUsableAngleCount / 2; + if (hit.AnglesList.Count == 5) + { + + double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[angleOffsetIndex - 1], hit.AnglesList[angleOffsetIndex + 1], hit.ViewAngles); // LIFETIME var hitLoc = ClientStats.HitLocations @@ -119,7 +172,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat results.Add(new DetectionPenaltyResult() { - ClientPenalty = EFPenalty.PenaltyType.Ban, + ClientPenalty = EFPenalty.PenaltyType.Flag, Value = hitLoc.HitOffsetAverage, HitCount = hitLoc.HitCount, Type = DetectionType.Offset @@ -146,7 +199,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat results.Add(new DetectionPenaltyResult() { - ClientPenalty = EFPenalty.PenaltyType.Ban, + ClientPenalty = EFPenalty.PenaltyType.Flag, Value = weightedSessionAverage, HitCount = HitCount, Type = DetectionType.Offset, @@ -344,7 +397,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat #endregion #endregion - Tracker.OnChange(new EFACSnapshot() + var snapshot = new EFACSnapshot() { When = hit.When, ClientId = ClientStats.ClientId, @@ -352,18 +405,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat RecoilOffset = hitRecoilAverage, CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds, CurrentStrain = currentStrain, - CurrentViewAngle = hit.ViewAngles, + CurrentViewAngle = new Vector3(hit.ViewAngles.X, hit.ViewAngles.Y, hit.ViewAngles.Z), Hits = HitCount, Kills = Kills, Deaths = ClientStats.SessionDeaths, - HitDestinationId = hit.DeathOrigin.Vector3Id, - HitDestination = hit.DeathOrigin, - HitOriginId = hit.KillOrigin.Vector3Id, - HitOrigin = hit.KillOrigin, + //todo[9.1.19]: why does this cause unique failure? + HitDestination = new Vector3(hit.DeathOrigin.X, hit.DeathOrigin.Y, hit.DeathOrigin.Z), + HitOrigin = new Vector3(hit.KillOrigin.X, hit.KillOrigin.Y, hit.KillOrigin.Z), EloRating = ClientStats.EloRating, HitLocation = hit.HitLoc, - LastStrainAngle = Strain.LastAngle, - PredictedViewAngles = hit.AnglesList, + LastStrainAngle = new Vector3(Strain.LastAngle.X, Strain.LastAngle.Y, Strain.LastAngle.Z), // this is in "meters" Distance = hit.Distance, SessionScore = ClientStats.SessionScore, @@ -372,7 +423,17 @@ namespace IW4MAdmin.Plugins.Stats.Cheat StrainAngleBetween = Strain.LastDistance, TimeSinceLastEvent = (int)Strain.LastDeltaTime, WeaponId = hit.Weapon - }); + }; + + snapshot.PredictedViewAngles = hit.AnglesList + .Select(_angle => new EFACSnapshotVector3() + { + Vector = _angle, + Snapshot = snapshot + }) + .ToList(); + + 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/Cheat/Thresholds.cs b/Plugins/Stats/Cheat/Thresholds.cs index b85a11697..db520fbab 100644 --- a/Plugins/Stats/Cheat/Thresholds.cs +++ b/Plugins/Stats/Cheat/Thresholds.cs @@ -27,6 +27,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat public const int HighSampleMinKills = 100; public const double KillTimeThreshold = 0.2; public const int LowSampleMinKillsRecoil = 5; + public const double SnapFlagValue = 6.12; + public const double SnapBanValue = 9.67; public const double MaxStrainBan = 0.9; 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/Controllers/StatsController.cs b/Plugins/Stats/Controllers/StatsController.cs index b79b8e590..f195a40eb 100644 --- a/Plugins/Stats/Controllers/StatsController.cs +++ b/Plugins/Stats/Controllers/StatsController.cs @@ -117,6 +117,7 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers .Include(s => s.HitDestination) .Include(s => s.CurrentViewAngle) .Include(s => s.PredictedViewAngles) + .ThenInclude(_angles => _angles.Vector) .OrderBy(s => s.When) .ThenBy(s => s.Hits); 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 135cd2170..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() @@ -276,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); @@ -397,11 +390,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers _log.WriteDebug(ex.GetExceptionInfo()); } - finally - { - OnProcessingSensitive.Release(1); - } - return null; } @@ -467,165 +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, - 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/EFACSnapshot.cs b/Plugins/Stats/Models/EFACSnapshot.cs index df0082181..51984ee0e 100644 --- a/Plugins/Stats/Models/EFACSnapshot.cs +++ b/Plugins/Stats/Models/EFACSnapshot.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; namespace IW4MAdmin.Plugins.Stats.Models { @@ -47,6 +48,11 @@ namespace IW4MAdmin.Plugins.Stats.Models public IW4Info.WeaponName WeaponId { get; set; } public IW4Info.HitLocation HitLocation { get; set; } public IW4Info.MeansOfDeath HitType { get; set; } - public virtual ICollection PredictedViewAngles { get; set; } + public virtual ICollection PredictedViewAngles { get; set; } + + [NotMapped] + public string CapturedViewAngles => PredictedViewAngles?.Count > 0 ? + string.Join(", ", PredictedViewAngles.OrderBy(_angle => _angle.ACSnapshotVector3Id).Select(_angle => _angle.Vector.ToString())) : + ""; } } diff --git a/Plugins/Stats/Models/EFACSnapshotVector3.cs b/Plugins/Stats/Models/EFACSnapshotVector3.cs new file mode 100644 index 000000000..2e1215cfa --- /dev/null +++ b/Plugins/Stats/Models/EFACSnapshotVector3.cs @@ -0,0 +1,26 @@ +using IW4MAdmin.Plugins.Stats.Models; +using SharedLibraryCore.Helpers; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace IW4MAdmin.Plugins.Stats.Models +{ + public class EFACSnapshotVector3 + { + [Key] + public int ACSnapshotVector3Id { get; set; } + + public int SnapshotId { get; set; } + + [ForeignKey("SnapshotId")] + public EFACSnapshot Snapshot { get; set; } + + public int Vector3Id { get; set; } + + [ForeignKey("Vector3Id")] + public Vector3 Vector { get; set;} + } +} diff --git a/Plugins/Stats/Models/EFClientStatistics.cs b/Plugins/Stats/Models/EFClientStatistics.cs index 7332702e6..7c0e841c1 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; } @@ -27,6 +38,8 @@ namespace IW4MAdmin.Plugins.Stats.Models public double RollingWeightedKDR { get; set; } public double VisionAverage { get; set; } public double AverageRecoilOffset { get; set; } + public double AverageSnapValue { get; set; } + public int SnapHitCount { get; set; } [NotMapped] public double Performance { @@ -96,12 +109,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..13ed2283a 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; } @@ -245,6 +272,7 @@ namespace IW4MAdmin.Plugins.Stats double chestAbdomenRatio = 0; double hitOffsetAverage = 0; double averageRecoilAmount = 0; + double averageSnapValue = 0; double maxStrain = clientStats.Count(c => c.MaxStrain > 0) == 0 ? 0 : clientStats.Max(cs => cs.MaxStrain); if (clientStats.Where(cs => cs.HitLocations.Count > 0).FirstOrDefault() != null) @@ -268,6 +296,7 @@ namespace IW4MAdmin.Plugins.Stats var validOffsets = clientStats.Where(c => c.HitLocations.Count(hl => hl.HitCount > 0) > 0).SelectMany(hl => hl.HitLocations); hitOffsetAverage = validOffsets.Sum(o => o.HitCount * o.HitOffsetAverage) / (double)validOffsets.Sum(o => o.HitCount); averageRecoilAmount = clientStats.Average(_stat => _stat.AverageRecoilOffset); + averageSnapValue = clientStats.Any(_stats => _stats.AverageSnapValue > 0) ? clientStats.Where(_stats => _stats.AverageSnapValue > 0).Average(_stat => _stat.AverageSnapValue) : 0; } return new List() @@ -343,6 +372,16 @@ namespace IW4MAdmin.Plugins.Stats Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"], Sensitive = true }, + new ProfileMeta() + { + Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 8", + Value = Math.Round(averageSnapValue, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), + Type = ProfileMeta.MetaType.Information, + Column = 2, + Order = 6, + Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM8"], + Sensitive = true + } }; } @@ -483,7 +522,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/Plugins/Web/StatsWeb/Views/Stats/_PenaltyInfo.cshtml b/Plugins/Web/StatsWeb/Views/Stats/_PenaltyInfo.cshtml index 32bd8e82f..41902480e 100644 --- a/Plugins/Web/StatsWeb/Views/Stats/_PenaltyInfo.cshtml +++ b/Plugins/Web/StatsWeb/Views/Stats/_PenaltyInfo.cshtml @@ -13,11 +13,11 @@ @if (prop.GetValue(snapshot) is System.Collections.Generic.HashSet) { - @prop.Name + @*@prop.Name foreach (var v in (System.Collections.Generic.HashSet)prop.GetValue(snapshot)) { @v.ToString(),
- } + }*@ } else { diff --git a/SharedLibraryCore/Helpers/Vector3.cs b/SharedLibraryCore/Helpers/Vector3.cs index a0dae6eb8..08c7fb7f0 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 @@ -67,6 +67,43 @@ namespace SharedLibraryCore.Helpers return Math.Sqrt((dx * dx) + (dy * dy)); } + public static double SnapDistance(Vector3 a, Vector3 b, Vector3 c) + { + a = a.FixIW4Angles(); + b = b.FixIW4Angles(); + c = c.FixIW4Angles(); + + float preserveDirectionAngle(float a1, float b1) + { + float difference = b1 - a1; + while (difference < -180) difference += 360; + while (difference > 180) difference -= 360; + return difference; + } + + var directions = new[] + { + new Vector3() + { + X = preserveDirectionAngle(b.X, a.X), + Y = preserveDirectionAngle(b.Y, a.Y) + }, + new Vector3() + { + X = preserveDirectionAngle(c.X, b.X), + Y = preserveDirectionAngle(c.Y, b.Y) + } + }; + + var distance = new Vector3 + { + X = Math.Abs(directions[1].X - directions[0].X), + Y = Math.Abs(directions[1].Y - directions[0].Y) + }; + + return Math.Sqrt((distance.X * distance.X) + (distance.Y * distance.Y)); + } + public static double ViewAngleDistance(Vector3 a, Vector3 b, Vector3 c) { double dabX = Math.Abs(a.X - b.X); diff --git a/SharedLibraryCore/Migrations/20190831210503_AvgSnapValueToClientStatistics.Designer.cs b/SharedLibraryCore/Migrations/20190831210503_AvgSnapValueToClientStatistics.Designer.cs new file mode 100644 index 000000000..a100afbb4 --- /dev/null +++ b/SharedLibraryCore/Migrations/20190831210503_AvgSnapValueToClientStatistics.Designer.cs @@ -0,0 +1,706 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SharedLibraryCore.Database; + +namespace SharedLibraryCore.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20190831210503_AvgSnapValueToClientStatistics")] + partial class AvgSnapValueToClientStatistics + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("CurrentSessionLength"); + + b.Property("CurrentStrain"); + + b.Property("CurrentViewAngleId"); + + b.Property("Deaths"); + + b.Property("Distance"); + + b.Property("EloRating"); + + b.Property("HitDestinationId"); + + b.Property("HitLocation"); + + b.Property("HitOriginId"); + + b.Property("HitType"); + + b.Property("Hits"); + + b.Property("Kills"); + + b.Property("LastStrainAngleId"); + + b.Property("RecoilOffset"); + + b.Property("SessionAngleOffset"); + + b.Property("SessionSPM"); + + b.Property("SessionScore"); + + b.Property("StrainAngleBetween"); + + b.Property("TimeSinceLastEvent"); + + b.Property("WeaponId"); + + b.Property("When"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleId"); + + b.HasIndex("HitDestinationId"); + + b.HasIndex("HitOriginId"); + + b.HasIndex("LastStrainAngleId"); + + b.ToTable("EFACSnapshot"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AttackerId"); + + b.Property("Damage"); + + b.Property("DeathOriginVector3Id"); + + b.Property("DeathType"); + + b.Property("Fraction"); + + b.Property("HitLoc"); + + b.Property("IsKill"); + + b.Property("KillOriginVector3Id"); + + b.Property("Map"); + + b.Property("ServerId"); + + b.Property("VictimId"); + + b.Property("ViewAnglesVector3Id"); + + b.Property("VisibilityPercentage"); + + b.Property("Weapon"); + + b.Property("When"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("TimeSent"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("TimeSent"); + + b.ToTable("EFClientMessages"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.Property("ClientId"); + + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("AverageRecoilOffset"); + + b.Property("AverageSnapValue"); + + b.Property("Deaths"); + + b.Property("EloRating"); + + b.Property("Kills"); + + b.Property("MaxStrain"); + + b.Property("RollingWeightedKDR"); + + b.Property("SPM"); + + b.Property("Skill"); + + b.Property("TimePlayed"); + + b.Property("VisionAverage"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatistics"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId") + .HasColumnName("EFClientStatistics_ClientId"); + + b.Property("HitCount"); + + b.Property("HitOffsetAverage"); + + b.Property("Location"); + + b.Property("MaxAngleDistance"); + + b.Property("ServerId") + .HasColumnName("EFClientStatistics_ServerId"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ClientId", "ServerId"); + + b.ToTable("EFHitLocationCounts"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ActivityAmount"); + + b.Property("Newest"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.Property("RatingHistoryId"); + + b.Property("ServerId"); + + b.Property("When"); + + b.HasKey("RatingId"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("Performance", "Ranking", "When"); + + b.ToTable("EFRating"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => + { + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("EndPoint"); + + b.Property("GameName"); + + b.Property("Port"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ServerId"); + + b.Property("TotalKills"); + + b.Property("TotalPlayTime"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("DateAdded"); + + b.Property("IPAddress"); + + b.Property("LinkId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24); + + b.Property("SearchableName") + .HasMaxLength(24); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.HasIndex("SearchableName"); + + b.ToTable("EFAlias"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Comment") + .HasMaxLength(128); + + b.Property("CurrentValue"); + + b.Property("OriginEntityId"); + + b.Property("PreviousValue"); + + b.Property("TargetEntityId"); + + b.Property("TimeChanged"); + + b.Property("TypeOfChange"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AliasLinkId"); + + b.Property("Connections"); + + b.Property("CurrentAliasId"); + + b.Property("FirstConnection"); + + b.Property("LastConnection"); + + b.Property("Level"); + + b.Property("Masked"); + + b.Property("NetworkId"); + + b.Property("Password"); + + b.Property("PasswordSalt"); + + b.Property("TotalConnectionTime"); + + b.HasKey("ClientId"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("NetworkId") + .IsUnique(); + + b.ToTable("EFClients"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b => + { + b.Property("MetaId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Extra"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(32); + + b.Property("Updated"); + + b.Property("Value") + .IsRequired(); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.HasIndex("Key"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AutomatedOffense"); + + b.Property("Expires"); + + b.Property("IsEvadedOffense"); + + b.Property("LinkId"); + + b.Property("OffenderId"); + + b.Property("Offense") + .IsRequired(); + + b.Property("PunisherId"); + + b.Property("Type"); + + b.Property("When"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties"); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd(); + + b.Property("EFACSnapshotSnapshotId"); + + b.Property("X"); + + b.Property("Y"); + + b.Property("Z"); + + b.HasKey("Vector3Id"); + + b.HasIndex("EFACSnapshotSnapshotId"); + + b.ToTable("Vector3"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle") + .WithMany() + .HasForeignKey("CurrentViewAngleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics") + .WithMany("HitLocations") + .HasForeignKey("ClientId", "ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory") + .WithMany("Ratings") + .HasForeignKey("RatingHistoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany("Meta") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("EFACSnapshotSnapshotId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SharedLibraryCore/Migrations/20190831210503_AvgSnapValueToClientStatistics.cs b/SharedLibraryCore/Migrations/20190831210503_AvgSnapValueToClientStatistics.cs new file mode 100644 index 000000000..e8e405204 --- /dev/null +++ b/SharedLibraryCore/Migrations/20190831210503_AvgSnapValueToClientStatistics.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace SharedLibraryCore.Migrations +{ + public partial class AvgSnapValueToClientStatistics : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AverageSnapValue", + table: "EFClientStatistics", + nullable: false, + defaultValue: 0.0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AverageSnapValue", + table: "EFClientStatistics"); + } + } +} diff --git a/SharedLibraryCore/Migrations/20190901180209_AddSnapHitCountToClientStatistics.Designer.cs b/SharedLibraryCore/Migrations/20190901180209_AddSnapHitCountToClientStatistics.Designer.cs new file mode 100644 index 000000000..a9ac29def --- /dev/null +++ b/SharedLibraryCore/Migrations/20190901180209_AddSnapHitCountToClientStatistics.Designer.cs @@ -0,0 +1,708 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SharedLibraryCore.Database; + +namespace SharedLibraryCore.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20190901180209_AddSnapHitCountToClientStatistics")] + partial class AddSnapHitCountToClientStatistics + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("CurrentSessionLength"); + + b.Property("CurrentStrain"); + + b.Property("CurrentViewAngleId"); + + b.Property("Deaths"); + + b.Property("Distance"); + + b.Property("EloRating"); + + b.Property("HitDestinationId"); + + b.Property("HitLocation"); + + b.Property("HitOriginId"); + + b.Property("HitType"); + + b.Property("Hits"); + + b.Property("Kills"); + + b.Property("LastStrainAngleId"); + + b.Property("RecoilOffset"); + + b.Property("SessionAngleOffset"); + + b.Property("SessionSPM"); + + b.Property("SessionScore"); + + b.Property("StrainAngleBetween"); + + b.Property("TimeSinceLastEvent"); + + b.Property("WeaponId"); + + b.Property("When"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleId"); + + b.HasIndex("HitDestinationId"); + + b.HasIndex("HitOriginId"); + + b.HasIndex("LastStrainAngleId"); + + b.ToTable("EFACSnapshot"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AttackerId"); + + b.Property("Damage"); + + b.Property("DeathOriginVector3Id"); + + b.Property("DeathType"); + + b.Property("Fraction"); + + b.Property("HitLoc"); + + b.Property("IsKill"); + + b.Property("KillOriginVector3Id"); + + b.Property("Map"); + + b.Property("ServerId"); + + b.Property("VictimId"); + + b.Property("ViewAnglesVector3Id"); + + b.Property("VisibilityPercentage"); + + b.Property("Weapon"); + + b.Property("When"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("TimeSent"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("TimeSent"); + + b.ToTable("EFClientMessages"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.Property("ClientId"); + + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("AverageRecoilOffset"); + + b.Property("AverageSnapValue"); + + b.Property("Deaths"); + + b.Property("EloRating"); + + b.Property("Kills"); + + b.Property("MaxStrain"); + + b.Property("RollingWeightedKDR"); + + b.Property("SPM"); + + b.Property("Skill"); + + b.Property("SnapHitCount"); + + b.Property("TimePlayed"); + + b.Property("VisionAverage"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatistics"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId") + .HasColumnName("EFClientStatistics_ClientId"); + + b.Property("HitCount"); + + b.Property("HitOffsetAverage"); + + b.Property("Location"); + + b.Property("MaxAngleDistance"); + + b.Property("ServerId") + .HasColumnName("EFClientStatistics_ServerId"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ClientId", "ServerId"); + + b.ToTable("EFHitLocationCounts"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ActivityAmount"); + + b.Property("Newest"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.Property("RatingHistoryId"); + + b.Property("ServerId"); + + b.Property("When"); + + b.HasKey("RatingId"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("Performance", "Ranking", "When"); + + b.ToTable("EFRating"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => + { + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("EndPoint"); + + b.Property("GameName"); + + b.Property("Port"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ServerId"); + + b.Property("TotalKills"); + + b.Property("TotalPlayTime"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("DateAdded"); + + b.Property("IPAddress"); + + b.Property("LinkId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24); + + b.Property("SearchableName") + .HasMaxLength(24); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.HasIndex("SearchableName"); + + b.ToTable("EFAlias"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Comment") + .HasMaxLength(128); + + b.Property("CurrentValue"); + + b.Property("OriginEntityId"); + + b.Property("PreviousValue"); + + b.Property("TargetEntityId"); + + b.Property("TimeChanged"); + + b.Property("TypeOfChange"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AliasLinkId"); + + b.Property("Connections"); + + b.Property("CurrentAliasId"); + + b.Property("FirstConnection"); + + b.Property("LastConnection"); + + b.Property("Level"); + + b.Property("Masked"); + + b.Property("NetworkId"); + + b.Property("Password"); + + b.Property("PasswordSalt"); + + b.Property("TotalConnectionTime"); + + b.HasKey("ClientId"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("NetworkId") + .IsUnique(); + + b.ToTable("EFClients"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b => + { + b.Property("MetaId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Extra"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(32); + + b.Property("Updated"); + + b.Property("Value") + .IsRequired(); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.HasIndex("Key"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AutomatedOffense"); + + b.Property("Expires"); + + b.Property("IsEvadedOffense"); + + b.Property("LinkId"); + + b.Property("OffenderId"); + + b.Property("Offense") + .IsRequired(); + + b.Property("PunisherId"); + + b.Property("Type"); + + b.Property("When"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties"); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd(); + + b.Property("EFACSnapshotSnapshotId"); + + b.Property("X"); + + b.Property("Y"); + + b.Property("Z"); + + b.HasKey("Vector3Id"); + + b.HasIndex("EFACSnapshotSnapshotId"); + + b.ToTable("Vector3"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle") + .WithMany() + .HasForeignKey("CurrentViewAngleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics") + .WithMany("HitLocations") + .HasForeignKey("ClientId", "ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory") + .WithMany("Ratings") + .HasForeignKey("RatingHistoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany("Meta") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("EFACSnapshotSnapshotId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SharedLibraryCore/Migrations/20190901180209_AddSnapHitCountToClientStatistics.cs b/SharedLibraryCore/Migrations/20190901180209_AddSnapHitCountToClientStatistics.cs new file mode 100644 index 000000000..017c92304 --- /dev/null +++ b/SharedLibraryCore/Migrations/20190901180209_AddSnapHitCountToClientStatistics.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace SharedLibraryCore.Migrations +{ + public partial class AddSnapHitCountToClientStatistics : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SnapHitCount", + table: "EFClientStatistics", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SnapHitCount", + table: "EFClientStatistics"); + } + } +} diff --git a/SharedLibraryCore/Migrations/20190901223620_UseJunctionTableForSnapshotVector3.Designer.cs b/SharedLibraryCore/Migrations/20190901223620_UseJunctionTableForSnapshotVector3.Designer.cs new file mode 100644 index 000000000..c2e00ad9c --- /dev/null +++ b/SharedLibraryCore/Migrations/20190901223620_UseJunctionTableForSnapshotVector3.Designer.cs @@ -0,0 +1,728 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SharedLibraryCore.Database; + +namespace SharedLibraryCore.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20190901223620_UseJunctionTableForSnapshotVector3")] + partial class UseJunctionTableForSnapshotVector3 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("CurrentSessionLength"); + + b.Property("CurrentStrain"); + + b.Property("CurrentViewAngleId"); + + b.Property("Deaths"); + + b.Property("Distance"); + + b.Property("EloRating"); + + b.Property("HitDestinationId"); + + b.Property("HitLocation"); + + b.Property("HitOriginId"); + + b.Property("HitType"); + + b.Property("Hits"); + + b.Property("Kills"); + + b.Property("LastStrainAngleId"); + + b.Property("RecoilOffset"); + + b.Property("SessionAngleOffset"); + + b.Property("SessionSPM"); + + b.Property("SessionScore"); + + b.Property("StrainAngleBetween"); + + b.Property("TimeSinceLastEvent"); + + b.Property("WeaponId"); + + b.Property("When"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleId"); + + b.HasIndex("HitDestinationId"); + + b.HasIndex("HitOriginId"); + + b.HasIndex("LastStrainAngleId"); + + b.ToTable("EFACSnapshot"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b => + { + b.Property("ACSnapshotVector3Id") + .ValueGeneratedOnAdd(); + + b.Property("SnapshotId"); + + b.Property("Vector3Id"); + + b.HasKey("ACSnapshotVector3Id"); + + b.HasIndex("SnapshotId"); + + b.HasIndex("Vector3Id"); + + b.ToTable("EFACSnapshotVector3"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AttackerId"); + + b.Property("Damage"); + + b.Property("DeathOriginVector3Id"); + + b.Property("DeathType"); + + b.Property("Fraction"); + + b.Property("HitLoc"); + + b.Property("IsKill"); + + b.Property("KillOriginVector3Id"); + + b.Property("Map"); + + b.Property("ServerId"); + + b.Property("VictimId"); + + b.Property("ViewAnglesVector3Id"); + + b.Property("VisibilityPercentage"); + + b.Property("Weapon"); + + b.Property("When"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("TimeSent"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("TimeSent"); + + b.ToTable("EFClientMessages"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.Property("ClientId"); + + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("AverageRecoilOffset"); + + b.Property("AverageSnapValue"); + + b.Property("Deaths"); + + b.Property("EloRating"); + + b.Property("Kills"); + + b.Property("MaxStrain"); + + b.Property("RollingWeightedKDR"); + + b.Property("SPM"); + + b.Property("Skill"); + + b.Property("SnapHitCount"); + + b.Property("TimePlayed"); + + b.Property("VisionAverage"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatistics"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId") + .HasColumnName("EFClientStatistics_ClientId"); + + b.Property("HitCount"); + + b.Property("HitOffsetAverage"); + + b.Property("Location"); + + b.Property("MaxAngleDistance"); + + b.Property("ServerId") + .HasColumnName("EFClientStatistics_ServerId"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ClientId", "ServerId"); + + b.ToTable("EFHitLocationCounts"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ActivityAmount"); + + b.Property("Newest"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.Property("RatingHistoryId"); + + b.Property("ServerId"); + + b.Property("When"); + + b.HasKey("RatingId"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("Performance", "Ranking", "When"); + + b.ToTable("EFRating"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => + { + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("EndPoint"); + + b.Property("GameName"); + + b.Property("Port"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ServerId"); + + b.Property("TotalKills"); + + b.Property("TotalPlayTime"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("DateAdded"); + + b.Property("IPAddress"); + + b.Property("LinkId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24); + + b.Property("SearchableName") + .HasMaxLength(24); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.HasIndex("SearchableName"); + + b.ToTable("EFAlias"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Comment") + .HasMaxLength(128); + + b.Property("CurrentValue"); + + b.Property("OriginEntityId"); + + b.Property("PreviousValue"); + + b.Property("TargetEntityId"); + + b.Property("TimeChanged"); + + b.Property("TypeOfChange"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AliasLinkId"); + + b.Property("Connections"); + + b.Property("CurrentAliasId"); + + b.Property("FirstConnection"); + + b.Property("LastConnection"); + + b.Property("Level"); + + b.Property("Masked"); + + b.Property("NetworkId"); + + b.Property("Password"); + + b.Property("PasswordSalt"); + + b.Property("TotalConnectionTime"); + + b.HasKey("ClientId"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("NetworkId") + .IsUnique(); + + b.ToTable("EFClients"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b => + { + b.Property("MetaId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Extra"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(32); + + b.Property("Updated"); + + b.Property("Value") + .IsRequired(); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.HasIndex("Key"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AutomatedOffense"); + + b.Property("Expires"); + + b.Property("IsEvadedOffense"); + + b.Property("LinkId"); + + b.Property("OffenderId"); + + b.Property("Offense") + .IsRequired(); + + b.Property("PunisherId"); + + b.Property("Type"); + + b.Property("When"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties"); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd(); + + b.Property("X"); + + b.Property("Y"); + + b.Property("Z"); + + b.HasKey("Vector3Id"); + + b.ToTable("Vector3"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle") + .WithMany() + .HasForeignKey("CurrentViewAngleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", "Snapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("SnapshotId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "Vector") + .WithMany() + .HasForeignKey("Vector3Id") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics") + .WithMany("HitLocations") + .HasForeignKey("ClientId", "ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory") + .WithMany("Ratings") + .HasForeignKey("RatingHistoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany("Meta") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SharedLibraryCore/Migrations/20190901223620_UseJunctionTableForSnapshotVector3.cs b/SharedLibraryCore/Migrations/20190901223620_UseJunctionTableForSnapshotVector3.cs new file mode 100644 index 000000000..9d36fde1a --- /dev/null +++ b/SharedLibraryCore/Migrations/20190901223620_UseJunctionTableForSnapshotVector3.cs @@ -0,0 +1,89 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace SharedLibraryCore.Migrations +{ + public partial class UseJunctionTableForSnapshotVector3 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder.ActiveProvider != "Microsoft.EntityFrameworkCore.Sqlite") + { + migrationBuilder.DropForeignKey( + name: "FK_Vector3_EFACSnapshot_EFACSnapshotSnapshotId", + table: "Vector3"); + + migrationBuilder.DropIndex( + name: "IX_Vector3_EFACSnapshotSnapshotId", + table: "Vector3"); + + migrationBuilder.DropColumn( + name: "EFACSnapshotSnapshotId", + table: "Vector3"); + } + + migrationBuilder.CreateTable( + name: "EFACSnapshotVector3", + columns: table => new + { + ACSnapshotVector3Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + SnapshotId = table.Column(nullable: false), + Vector3Id = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EFACSnapshotVector3", x => x.ACSnapshotVector3Id); + table.ForeignKey( + name: "FK_EFACSnapshotVector3_EFACSnapshot_SnapshotId", + column: x => x.SnapshotId, + principalTable: "EFACSnapshot", + principalColumn: "SnapshotId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_EFACSnapshotVector3_Vector3_Vector3Id", + column: x => x.Vector3Id, + principalTable: "Vector3", + principalColumn: "Vector3Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_EFACSnapshotVector3_SnapshotId", + table: "EFACSnapshotVector3", + column: "SnapshotId"); + + migrationBuilder.CreateIndex( + name: "IX_EFACSnapshotVector3_Vector3Id", + table: "EFACSnapshotVector3", + column: "Vector3Id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "EFACSnapshotVector3"); + + migrationBuilder.AddColumn( + name: "EFACSnapshotSnapshotId", + table: "Vector3", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Vector3_EFACSnapshotSnapshotId", + table: "Vector3", + column: "EFACSnapshotSnapshotId"); + + migrationBuilder.AddForeignKey( + name: "FK_Vector3_EFACSnapshot_EFACSnapshotSnapshotId", + table: "Vector3", + column: "EFACSnapshotSnapshotId", + principalTable: "EFACSnapshot", + principalColumn: "SnapshotId", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs index 97423ea7a..b8c16d005 100644 --- a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs +++ b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs @@ -82,6 +82,24 @@ namespace SharedLibraryCore.Migrations b.ToTable("EFACSnapshot"); }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b => + { + b.Property("ACSnapshotVector3Id") + .ValueGeneratedOnAdd(); + + b.Property("SnapshotId"); + + b.Property("Vector3Id"); + + b.HasKey("ACSnapshotVector3Id"); + + b.HasIndex("SnapshotId"); + + b.HasIndex("Vector3Id"); + + b.ToTable("EFACSnapshotVector3"); + }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => { b.Property("KillId") @@ -188,6 +206,8 @@ namespace SharedLibraryCore.Migrations b.Property("AverageRecoilOffset"); + b.Property("AverageSnapValue"); + b.Property("Deaths"); b.Property("EloRating"); @@ -202,6 +222,8 @@ namespace SharedLibraryCore.Migrations b.Property("Skill"); + b.Property("SnapHitCount"); + b.Property("TimePlayed"); b.Property("VisionAverage"); @@ -498,8 +520,6 @@ namespace SharedLibraryCore.Migrations b.Property("Vector3Id") .ValueGeneratedOnAdd(); - b.Property("EFACSnapshotSnapshotId"); - b.Property("X"); b.Property("Y"); @@ -508,8 +528,6 @@ namespace SharedLibraryCore.Migrations b.HasKey("Vector3Id"); - b.HasIndex("EFACSnapshotSnapshotId"); - b.ToTable("Vector3"); }); @@ -541,6 +559,19 @@ namespace SharedLibraryCore.Migrations .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", "Snapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("SnapshotId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "Vector") + .WithMany() + .HasForeignKey("Vector3Id") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => { b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker") @@ -689,13 +720,6 @@ namespace SharedLibraryCore.Migrations .HasForeignKey("PunisherId") .OnDelete(DeleteBehavior.Restrict); }); - - modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => - { - b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot") - .WithMany("PredictedViewAngles") - .HasForeignKey("EFACSnapshotSnapshotId"); - }); #pragma warning restore 612, 618 } } diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index a83a7c3fa..5d234aa81 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); } @@ -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 d8a700ebb..dd0980538 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -29,6 +29,8 @@ + + @@ -46,7 +48,7 @@ - + diff --git a/WebfrontCore/Controllers/DynamicFileController.cs b/WebfrontCore/Controllers/DynamicFileController.cs index 1cf5b5896..50f65bd22 100644 --- a/WebfrontCore/Controllers/DynamicFileController.cs +++ b/WebfrontCore/Controllers/DynamicFileController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; @@ -24,7 +25,7 @@ namespace WebfrontCore.Controllers if (!_fileCache.ContainsKey(fileName)) { - string path = $"wwwroot\\css\\{fileName}"; + string path = $"wwwroot{Path.DirectorySeparatorChar}css{Path.DirectorySeparatorChar}{fileName}"; string data = await System.IO.File.ReadAllTextAsync(path); data = await Manager.MiddlewareActionHandler.Execute(data, "custom_css_accent"); _fileCache.Add(fileName, data); diff --git a/_customcallbacks.gsc b/_customcallbacks.gsc index 475a9d6aa..b00cdf587 100644 --- a/_customcallbacks.gsc +++ b/_customcallbacks.gsc @@ -6,8 +6,8 @@ init() { SetDvarIfUninitialized( "sv_customcallbacks", true ); SetDvarIfUninitialized( "sv_framewaittime", 0.05 ); - SetDvarIfUninitialized( "sv_additionalwaittime", 0.05 ); - SetDvarIfUninitialized( "sv_maxstoredframes", 4 ); + SetDvarIfUninitialized( "sv_additionalwaittime", 0.1 ); + SetDvarIfUninitialized( "sv_maxstoredframes", 12 ); SetDvarIfUninitialized( "sv_printradarupdates", false ); SetDvarIfUninitialized( "sv_printradar_updateinterval", 500 ); SetDvarIfUninitialized( "sv_iw4madmin_url", "http://127.0.0.1:1624" ); @@ -110,6 +110,11 @@ waitForFrameThread() self.currentAnglePosition = 0; self.anglePositions = []; + + for (i = 0; i < getDvarInt( "sv_maxstoredframes" ); i++) + { + self.anglePositions[i] = self getPlayerAngles(); + } for( ;; ) { @@ -119,34 +124,55 @@ waitForFrameThread() } } -waitForAdditionalAngles( logString ) +waitForAdditionalAngles( logString, beforeFrameCount, afterFrameCount ) { - wait( getDvarFloat( "sv_additionalwaittime" ) ); + currentIndex = self.currentAnglePosition; + wait( 0.05 * afterFrameCount ); self.angleSnapshot = []; - for( i = 0; i < getDvarInt( "sv_maxstoredframes" ); i++ ) + for( j = 0; j < self.anglePositions.size; j++ ) { - self.angleSnapshot[i] = self.anglePositions[i]; + self.angleSnapshot[j] = self.anglePositions[j]; } - currentPos = self.currentAnglePosition; anglesStr = ""; - - i = 0; - - if ( currentPos < getDvarInt( "sv_maxstoredframes" ) - 1 ) + collectedFrames = 0; + i = currentIndex - beforeFrameCount; + + while (collectedFrames < beforeFrameCount) { - i = currentPos + 1; + fixedIndex = i; + if (i < 0) + { + fixedIndex = self.angleSnapshot.size - abs(i); + } + anglesStr += self.angleSnapshot[fixedIndex] + ":"; + collectedFrames++; + i++; } - - while( i != currentPos ) + + if (i == currentIndex) { anglesStr += self.angleSnapshot[i] + ":"; - i = (i + 1) % getDvarInt( "sv_maxstoredframes" ); + i++; } - - logPrint( logString + ";" + anglesStr + "\n" ); + + collectedFrames = 0; + + while (collectedFrames < afterFrameCount) + { + fixedIndex = i; + if (i > self.angleSnapshot.size - 1) + { + fixedIndex = i % self.angleSnapshot.size; + } + anglesStr += self.angleSnapshot[fixedIndex] + ":"; + collectedFrames++; + i++; + } + + logPrint(logString + ";" + anglesStr + "\n" ); } vectorScale( vector, scale ) @@ -156,6 +182,11 @@ vectorScale( vector, scale ) Process_Hit( type, attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon ) { + if (sMeansOfDeath == "MOD_FALLING" || !isPlayer(attacker)) + { + return; + } + victim = self; _attacker = attacker; @@ -172,8 +203,8 @@ Process_Hit( type, attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon ) location = victim GetTagOrigin( hitLocationToBone( sHitLoc ) ); isKillstreakKill = !isPlayer( attacker ) || isKillstreakWeapon( sWeapon ); - logLine = "Script" + type + ";" + _attacker.guid + ";" + victim.guid + ";" + _attacker GetTagOrigin("tag_eye") + ";" + location + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + ";" + _attacker getPlayerAngles() + ";" + gettime() + ";" + isKillstreakKill + ";" + _attacker playerADS() + ";0;0;"; - attacker thread waitForAdditionalAngles( logLine ); + logLine = "Script" + type + ";" + _attacker.guid + ";" + victim.guid + ";" + _attacker GetTagOrigin("tag_eye") + ";" + location + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + ";" + _attacker getPlayerAngles() + ";" + gettime() + ";" + isKillstreakKill + ";" + _attacker playerADS() + ";0;0"; + attacker thread waitForAdditionalAngles( logLine, 2, 2 ); } Callback_PlayerDamage( eInflictor, attacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime ) diff --git a/version.txt b/version.txt index 62a76413d..4c8cd6c10 100644 --- a/version.txt +++ b/version.txt @@ -6,6 +6,7 @@ Version 2.4: -added ability to customize accent color and branding on webfront -added flag button to client profile -hid flagged status of users on webfront unless logged in +-added snap anticheat metric Version 2.3: -added configuration option to ignore bots