From 6e5501b32d964ae90e7c07b3cc6633f83b4f7031 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Thu, 10 May 2018 23:52:20 -0500 Subject: [PATCH] fix T6 reading add WaW support fix stats threading --- Application/EventParsers/IW4EventParser.cs | 2 +- Application/EventParsers/T6MEventParser.cs | 8 +- Application/Manager.cs | 2 +- Application/RconParsers/IW3RConParser.cs | 22 +++ Application/RconParsers/IW4RConParser.cs | 4 +- Application/Server.cs | 2 +- Plugins/Stats/Cheat/Detection.cs | 181 ++++++++---------- Plugins/Stats/Cheat/DetectionPenaltyResult.cs | 7 +- Plugins/Stats/Cheat/Strain.cs | 17 +- Plugins/Stats/Cheat/Thresholds.cs | 3 +- Plugins/Stats/Helpers/StatManager.cs | 85 ++++---- .../Stats/Helpers/ThreadSafeStatsService.cs | 10 +- SharedLibraryCore/Helpers/ChangeTracking.cs | 41 ++++ SharedLibraryCore/Interfaces/ITrackable.cs | 11 ++ SharedLibraryCore/Utilities.cs | 2 +- 15 files changed, 246 insertions(+), 151 deletions(-) create mode 100644 Application/RconParsers/IW3RConParser.cs create mode 100644 SharedLibraryCore/Helpers/ChangeTracking.cs create mode 100644 SharedLibraryCore/Interfaces/ITrackable.cs diff --git a/Application/EventParsers/IW4EventParser.cs b/Application/EventParsers/IW4EventParser.cs index 19f1a1f5c..e13f81580 100644 --- a/Application/EventParsers/IW4EventParser.cs +++ b/Application/EventParsers/IW4EventParser.cs @@ -13,7 +13,7 @@ namespace IW4MAdmin.Application.EventParsers public virtual GameEvent GetEvent(Server server, string logLine) { string[] lineSplit = logLine.Split(';'); - string cleanedEventLine = Regex.Replace(lineSplit[0], @"[0-9]+:[0-9]+\ ", "").Trim(); + string cleanedEventLine = Regex.Replace(lineSplit[0], @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim(); if (cleanedEventLine[0] == 'K') { diff --git a/Application/EventParsers/T6MEventParser.cs b/Application/EventParsers/T6MEventParser.cs index 1ebf7c815..901a2ce4b 100644 --- a/Application/EventParsers/T6MEventParser.cs +++ b/Application/EventParsers/T6MEventParser.cs @@ -9,9 +9,9 @@ using SharedLibraryCore.Objects; namespace IW4MAdmin.Application.EventParsers { - class T6MEventParser : IEventParser + class T6MEventParser : IW4EventParser { - public GameEvent GetEvent(Server server, string logLine) + /*public GameEvent GetEvent(Server server, string logLine) { string cleanedEventLine = Regex.Replace(logLine, @"^ *[0-9]+:[0-9]+ *", "").Trim(); string[] lineSplit = cleanedEventLine.Split(';'); @@ -104,8 +104,8 @@ namespace IW4MAdmin.Application.EventParsers }, Owner = server }; - } + }*/ - public string GetGameDir() => $"t6r{Path.DirectorySeparatorChar}data"; + public override string GetGameDir() => $"t6r{Path.DirectorySeparatorChar}data"; } } diff --git a/Application/Manager.cs b/Application/Manager.cs index 59428fff1..0894a222a 100644 --- a/Application/Manager.cs +++ b/Application/Manager.cs @@ -139,7 +139,7 @@ namespace IW4MAdmin.Application sensitiveEvent.OnProcessed.Set(); } - await Task.Delay(5000); + await Task.Delay(1000); } } diff --git a/Application/RconParsers/IW3RConParser.cs b/Application/RconParsers/IW3RConParser.cs new file mode 100644 index 000000000..2d12e9a59 --- /dev/null +++ b/Application/RconParsers/IW3RConParser.cs @@ -0,0 +1,22 @@ +using Application.RconParsers; +using SharedLibraryCore.RCon; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Application.RconParsers +{ + class IW3RConParser : IW4RConParser + { + private static CommandPrefix Prefixes = new CommandPrefix() + { + Tell = "tell {0} {1}", + Say = "say {0}", + Kick = "clientkick {0} \"{1}\"", + Ban = "clientkick {0} \"{1}\"", + TempBan = "tempbanclient {0} \"{1}\"" + }; + + public override CommandPrefix GetCommandPrefixes() => Prefixes; + } +} diff --git a/Application/RconParsers/IW4RConParser.cs b/Application/RconParsers/IW4RConParser.cs index a3b05ba9a..8e395a9ed 100644 --- a/Application/RconParsers/IW4RConParser.cs +++ b/Application/RconParsers/IW4RConParser.cs @@ -23,7 +23,7 @@ namespace Application.RconParsers TempBan = "tempbanclient {0} \"{1}\"" }; - private static string StatusRegex = @"^( *[0-9]+) +-*([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|bot[0-9]+|(?:[0-9]+)) +(.{0,20}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}) +(-*[0-9]+) +([0-9]+) *$"; + private static string StatusRegex = @"^( *[0-9]+) +-*([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|bot[0-9]+|(?:[0-9]+)) +(.{0,20}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$"; public async Task ExecuteCommandAsync(Connection connection, string command) { @@ -72,7 +72,7 @@ namespace Application.RconParsers return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0; } - public CommandPrefix GetCommandPrefixes() => Prefixes; + public virtual CommandPrefix GetCommandPrefixes() => Prefixes; private List ClientsFromStatus(string[] Status) { diff --git a/Application/Server.cs b/Application/Server.cs index 92db6da77..8b86bed7f 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -723,7 +723,7 @@ namespace IW4MAdmin public async Task Initialize() { - RconParser = ServerConfig.UseT6MParser ? (IRConParser)new T6MRConParser() : new IW4RConParser(); + RconParser = ServerConfig.UseT6MParser ? (IRConParser)new T6MRConParser() : new IW3RConParser(); if (ServerConfig.UseIW5MParser) RconParser = new IW5MRConParser(); diff --git a/Plugins/Stats/Cheat/Detection.cs b/Plugins/Stats/Cheat/Detection.cs index b48c6bb45..239829010 100644 --- a/Plugins/Stats/Cheat/Detection.cs +++ b/Plugins/Stats/Cheat/Detection.cs @@ -6,17 +6,23 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Text.RegularExpressions; namespace IW4MAdmin.Plugins.Stats.Cheat { class Detection { + public enum DetectionType + { + Bone, + Chest, + Offset, + Strain + }; + int Kills; int HitCount; - int AboveThresholdCount; - double AverageKillTime; Dictionary HitLocationCount; + ChangeTracking Tracker; double AngleDifferenceAverage; EFClientStatistics ClientStats; DateTime LastHit; @@ -32,31 +38,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat HitLocationCount.Add((IW4Info.HitLocation)loc, 0); ClientStats = clientStats; Strain = new Strain(); - } - - public void ProcessScriptDamage(string damageLine) - { - - } - - public void ProcessDamage(string damageLine) - { - string regex = @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; - - var match = Regex.Match(damageLine, regex, RegexOptions.IgnoreCase); - - if (match.Success) - { - var meansOfDeath = ParseEnum.Get(match.Groups[12].Value, typeof(IW4Info.MeansOfDeath)); - var hitLocation = ParseEnum.Get(match.Groups[13].Value, typeof(IW4Info.HitLocation)); - - if (meansOfDeath == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || - meansOfDeath == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || - meansOfDeath == IW4Info.MeansOfDeath.MOD_HEAD_SHOT) - { - ClientStats.HitLocations.First(hl => hl.Location == hitLocation).HitCount += 1; - } - } + Tracker = new ChangeTracking(); } /// @@ -111,8 +93,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, - RatioAmount = hitLoc.HitOffsetAverage, - KillCount = hitLoc.HitCount, + Value = hitLoc.HitOffsetAverage, + HitCount = hitLoc.HitCount, }; } @@ -124,15 +106,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat { Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***"); Log.WriteDebug($"Session Average = {sessAverage}"); - // Log.WriteDebug($"Bone = {hitLoc.Location}"); Log.WriteDebug($"HitCount = {HitCount}"); Log.WriteDebug($"ID = {kill.AttackerId}"); return new DetectionPenaltyResult() { - ClientPenalty = Penalty.PenaltyType.Flag, - RatioAmount = sessAverage, - KillCount = HitCount, + ClientPenalty = Penalty.PenaltyType.Ban, + Value = sessAverage, + HitCount = HitCount, + Type = DetectionType.Offset, + Location = hitLoc.Location }; } @@ -140,10 +123,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat Log.WriteDebug($"PredictVsReal={realAgainstPredict}"); #endif } + var currentStrain = Strain.GetStrain(kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset)); - double diff = Math.Max(50, kill.TimeOffset - LastOffset); - var currentStrain = Strain.GetStrain(kill.ViewAngles, diff); - //LastHit = kill.When; LastOffset = kill.TimeOffset; if (currentStrain > ClientStats.MaxStrain) @@ -151,14 +132,20 @@ namespace IW4MAdmin.Plugins.Stats.Cheat ClientStats.MaxStrain = currentStrain; } - if (currentStrain > Thresholds.MaxStrain) + if (currentStrain > Thresholds.MaxStrainFlag) { - Log.WriteDebug("*** Reached Max Strain ***"); - Log.WriteDebug($"Strain = {currentStrain}"); - Log.WriteDebug($"Angles = {kill.ViewAngles} {kill.AnglesList[0]} {kill.AnglesList[1]}"); - Log.WriteDebug($"Time = {diff}"); - Log.WriteDebug($"HitCount = {HitCount}"); - Log.WriteDebug($"ID = {kill.AttackerId}"); + Tracker.OnChange(Strain); + + foreach (string change in Tracker.GetChanges()) + { + Log.WriteDebug(change); + } + Log.WriteDebug(ClientStats.RoundScore.ToString()); + } + + else + { + Tracker.ClearChanges(); } if (Strain.TimesReachedMaxStrain >= 3) @@ -166,8 +153,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, - RatioAmount = ClientStats.MaxStrain, - KillCount = HitCount, + Value = ClientStats.MaxStrain, + HitCount = HitCount, + Type = DetectionType.Strain }; } @@ -203,46 +191,44 @@ namespace IW4MAdmin.Plugins.Stats.Cheat // ban on headshot if (currentHeadshotRatio > maxHeadshotLerpValueForFlag) { - AboveThresholdCount++; Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); - Log.WriteDebug($"**Kills: {Kills}"); + Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); Log.WriteDebug(sb.ToString()); - Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, - RatioAmount = currentHeadshotRatio, - Bone = IW4Info.HitLocation.head, - KillCount = Kills + Value = currentHeadshotRatio, + Location = IW4Info.HitLocation.head, + HitCount = HitCount, + Type = DetectionType.Bone }; } else { - AboveThresholdCount++; Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); - Log.WriteDebug($"**Kills: {Kills}"); + Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); Log.WriteDebug(sb.ToString()); - Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, - RatioAmount = currentHeadshotRatio, - Bone = IW4Info.HitLocation.head, - KillCount = Kills + Value = currentHeadshotRatio, + Location = IW4Info.HitLocation.head, + HitCount = HitCount, + Type = DetectionType.Bone }; } } @@ -257,7 +243,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat { Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); - Log.WriteDebug($"**Kills: {Kills}"); + Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}"); var sb = new StringBuilder(); @@ -268,16 +254,17 @@ namespace IW4MAdmin.Plugins.Stats.Cheat return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, - RatioAmount = currentMaxBoneRatio, - Bone = bone, - KillCount = Kills + Value = currentMaxBoneRatio, + Location = bone, + HitCount = HitCount, + Type = DetectionType.Bone }; } else { Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); - Log.WriteDebug($"**Kills: {Kills}"); + Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}"); var sb = new StringBuilder(); @@ -288,9 +275,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, - RatioAmount = currentMaxBoneRatio, - Bone = bone, - KillCount = Kills + Value = currentMaxBoneRatio, + Location = bone, + HitCount = HitCount, + Type = DetectionType.Bone }; } } @@ -298,12 +286,12 @@ namespace IW4MAdmin.Plugins.Stats.Cheat } #region CHEST_ABDOMEN_RATIO_SESSION - int chestKills = HitLocationCount[IW4Info.HitLocation.torso_upper]; + int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper]; - if (chestKills >= Thresholds.MediumSampleMinKills) + if (chestHits >= Thresholds.MediumSampleMinKills) { - double marginOfError = Thresholds.GetMarginOfError(chestKills); - double lerpAmount = Math.Min(1.0, (chestKills - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); + double marginOfError = Thresholds.GetMarginOfError(chestHits); + double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); // determine max acceptable ratio of chest to abdomen kills double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3), Thresholds.ChestAbdomenRatioThresholdHighSample(3), lerpAmount) + marginOfError; double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(4), Thresholds.ChestAbdomenRatioThresholdHighSample(4), lerpAmount) + marginOfError; @@ -313,32 +301,32 @@ namespace IW4MAdmin.Plugins.Stats.Cheat if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag) { - if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestKills >= Thresholds.MediumSampleMinKills + 30) + if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills + 30) { Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); - Log.WriteDebug($"**Chest Kills: {chestKills}"); + Log.WriteDebug($"**Chest Hits: {chestHits}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); Log.WriteDebug(sb.ToString()); - // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, - RatioAmount = currentChestAbdomenRatio, - Bone = 0, - KillCount = chestKills + Value = currentChestAbdomenRatio, + Location = IW4Info.HitLocation.torso_upper, + Type = DetectionType.Chest, + HitCount = chestHits }; } else { Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); - Log.WriteDebug($"**Chest Kills: {chestKills}"); + Log.WriteDebug($"**Chest Hits: {chestHits}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); var sb = new StringBuilder(); @@ -350,9 +338,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, - RatioAmount = currentChestAbdomenRatio, - Bone = 0, - KillCount = chestKills + Value = currentChestAbdomenRatio, + Location = IW4Info.HitLocation.torso_upper, + Type = DetectionType.Chest, + HitCount = chestHits }; } } @@ -362,23 +351,22 @@ namespace IW4MAdmin.Plugins.Stats.Cheat return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Any, - RatioAmount = 0 }; } public DetectionPenaltyResult ProcessTotalRatio(EFClientStatistics stats) { - int totalChestKills = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.torso_upper).HitCount; + int totalChestHits = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.torso_upper).HitCount; - if (totalChestKills >= 60) + if (totalChestHits >= 60) { - double marginOfError = Thresholds.GetMarginOfError(totalChestKills); - double lerpAmount = Math.Min(1.0, (totalChestKills - 60) / 250.0); + double marginOfError = Thresholds.GetMarginOfError(totalChestHits); + double lerpAmount = Math.Min(1.0, (totalChestHits - 60) / 250.0); // determine max acceptable ratio of chest to abdomen kills double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), Thresholds.ChestAbdomenRatioThresholdHighSample(2.0), lerpAmount) + marginOfError; double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(4.0), Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), lerpAmount) + marginOfError; - double currentChestAbdomenRatio = totalChestKills / + double currentChestAbdomenRatio = totalChestHits / stats.HitLocations.Single(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount; if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag) @@ -388,42 +376,42 @@ namespace IW4MAdmin.Plugins.Stats.Cheat { Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**"); Log.WriteDebug($"ClientId: {stats.ClientId}"); - Log.WriteDebug($"**Total Chest Kills: {totalChestKills}"); + Log.WriteDebug($"**Total Chest Hits: {totalChestHits}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); var sb = new StringBuilder(); foreach (var location in stats.HitLocations) sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n"); Log.WriteDebug(sb.ToString()); - // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, - RatioAmount = currentChestAbdomenRatio, - Bone = IW4Info.HitLocation.torso_upper, - KillCount = totalChestKills + Value = currentChestAbdomenRatio, + Location = IW4Info.HitLocation.torso_upper, + HitCount = totalChestHits, + Type = DetectionType.Chest }; } else { Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**"); Log.WriteDebug($"ClientId: {stats.ClientId}"); - Log.WriteDebug($"**Total Chest Kills: {totalChestKills}"); + Log.WriteDebug($"**Total Chest Hits: {totalChestHits}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var location in stats.HitLocations) sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n"); Log.WriteDebug(sb.ToString()); - // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, - RatioAmount = currentChestAbdomenRatio, - Bone = IW4Info.HitLocation.torso_upper, - KillCount = totalChestKills + Value = currentChestAbdomenRatio, + Location = IW4Info.HitLocation.torso_upper, + HitCount = totalChestHits, + Type = DetectionType.Chest }; } } @@ -431,7 +419,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat return new DetectionPenaltyResult() { - Bone = IW4Info.HitLocation.none, ClientPenalty = Penalty.PenaltyType.Any }; } diff --git a/Plugins/Stats/Cheat/DetectionPenaltyResult.cs b/Plugins/Stats/Cheat/DetectionPenaltyResult.cs index 635af3033..23d4922b7 100644 --- a/Plugins/Stats/Cheat/DetectionPenaltyResult.cs +++ b/Plugins/Stats/Cheat/DetectionPenaltyResult.cs @@ -9,9 +9,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat { class DetectionPenaltyResult { + public Detection.DetectionType Type { get; set; } public Penalty.PenaltyType ClientPenalty { get; set; } - public double RatioAmount { get; set; } - public IW4Info.HitLocation Bone { get; set; } - public int KillCount { get; set; } + public double Value { get; set; } + public IW4Info.HitLocation Location { get; set; } + public int HitCount { get; set; } } } diff --git a/Plugins/Stats/Cheat/Strain.cs b/Plugins/Stats/Cheat/Strain.cs index 22b8f577c..f9c5b7662 100644 --- a/Plugins/Stats/Cheat/Strain.cs +++ b/Plugins/Stats/Cheat/Strain.cs @@ -1,15 +1,18 @@ using SharedLibraryCore.Helpers; +using SharedLibraryCore.Interfaces; using System; using System.Collections.Generic; using System.Text; namespace IW4MAdmin.Plugins.Stats.Cheat { - class Strain + class Strain : ITrackable { private static double StrainDecayBase = 0.15; private double CurrentStrain; private Vector3 LastAngle; + private double LastDeltaTime; + private double LastDistance; public int TimesReachedMaxStrain { get; private set; } @@ -18,10 +21,13 @@ namespace IW4MAdmin.Plugins.Stats.Cheat if (LastAngle == null) LastAngle = newAngle; + LastDeltaTime = deltaTime; + double decayFactor = GetDecay(deltaTime); CurrentStrain *= decayFactor; double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle); + LastDistance = distance[0] + distance[1]; // this happens on first kill if ((distance[0] == 0 && distance[1] == 0) || @@ -34,13 +40,18 @@ namespace IW4MAdmin.Plugins.Stats.Cheat double newStrain = Math.Pow(distance[0] + distance[1], 0.99) / deltaTime; CurrentStrain += newStrain; - if (CurrentStrain > Thresholds.MaxStrain) + if (CurrentStrain > Thresholds.MaxStrainFlag) TimesReachedMaxStrain++; LastAngle = newAngle; return CurrentStrain; } - + + public string GetTrackableValue() + { + return $"Strain - {CurrentStrain}, Angle - {LastAngle}, Delta Time - {LastDeltaTime}, Distance - {LastDistance}"; + } + private double GetDecay(double deltaTime) => Math.Pow(StrainDecayBase, deltaTime / 1000.0); } } \ No newline at end of file diff --git a/Plugins/Stats/Cheat/Thresholds.cs b/Plugins/Stats/Cheat/Thresholds.cs index 4a80dcf91..bf8789e25 100644 --- a/Plugins/Stats/Cheat/Thresholds.cs +++ b/Plugins/Stats/Cheat/Thresholds.cs @@ -27,8 +27,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat public const int HighSampleMinKills = 100; public const double KillTimeThreshold = 0.2; - public const double MaxStrain = 0.4399; + public const double MaxStrainBan = 0.4399; public const double MaxOffset = 4.789; + public const double MaxStrainFlag = 0.2; public static double GetMarginOfError(int numKills) => 1.6455 / Math.Sqrt(numKills); diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 79ac2c1aa..3d3ed9b02 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -126,13 +126,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers .ToList() }; - clientStats = statsSvc.ClientStatSvc.Insert(clientStats); - await statsSvc.ClientStatSvc.SaveChangesAsync(); - } - - else - { - statsSvc.ClientStatSvc.Update(clientStats); + // insert if they've not been added + var clientStatsSvc = statsSvc.ClientStatSvc; + clientStats = clientStatsSvc.Insert(clientStats); + await clientStatsSvc.SaveChangesAsync(); } // migration for previous existing stats @@ -161,9 +158,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats))) Log.WriteDebug("Could not add client to detection"); - /* - await statsSvc.ClientStatSvc.SaveChangesAsync();*/ - return clientStats; } @@ -193,30 +187,39 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // get individual client's stats var clientStats = playerStats[pl.ClientId]; - /*// sync their score - clientStats.SessionScore += pl.Score;*/ // remove the client from the stats dictionary as they're leaving playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3); detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4); // sync their stats before they leave - //clientStats = UpdateStats(clientStats); + // clientStats = UpdateStats(clientStats); + // var clientStatsSvc = statsSvc.ClientStatSvc; + // clientStatsSvc.Update(clientStats); + // await clientStatsSvc.SaveChangesAsync(); - statsSvc.ClientStatSvc.Update(clientStats); - await statsSvc.ClientStatSvc.SaveChangesAsync(); // increment the total play time serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds; - //await statsSvc.ServerStatsSvc.SaveChangesAsync(); } public void AddDamageEvent(string eventLine, int clientId, int serverId) { - if (Plugin.Config.Configuration().EnableAntiCheat) - { - var clientDetection = Servers[serverId].PlayerDetections[clientId]; - clientDetection.ProcessScriptDamage(eventLine); - } + /* string regex = @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; + + var match = Regex.Match(damageLine, regex, RegexOptions.IgnoreCase); + + if (match.Success) + { + var meansOfDeath = ParseEnum.Get(match.Groups[12].Value, typeof(IW4Info.MeansOfDeath)); + var hitLocation = ParseEnum.Get(match.Groups[13].Value, typeof(IW4Info.HitLocation)); + + if (meansOfDeath == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || + meansOfDeath == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || + meansOfDeath == IW4Info.MeansOfDeath.MOD_HEAD_SHOT) + { + ClientStats.HitLocations.First(hl => hl.Location == hitLocation).HitCount += 1; + } + }*/ } /// @@ -310,7 +313,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { clientStats.HitLocations.Single(hl => hl.Location == kill.HitLoc).HitCount += 1; - //statsSvc.ClientStatSvc.Update(clientStats); + statsSvc.ClientStatSvc.Update(clientStats); // await statsSvc.ClientStatSvc.SaveChangesAsync(); } @@ -330,19 +333,31 @@ namespace IW4MAdmin.Plugins.Stats.Helpers switch (penalty.ClientPenalty) { case Penalty.PenaltyType.Ban: - await attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], new Player() { ClientId = 1 }); + await attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], new Player() + { + ClientId = 1 + }); break; case Penalty.PenaltyType.Flag: if (attacker.Level != Player.Permission.User) break; - var flagCmd = new CFlag(); - await flagCmd.ExecuteAsync(new GameEvent(GameEvent.EventType.Flag, $"{(int)penalty.Bone}-{Math.Round(penalty.RatioAmount, 2).ToString()}@{penalty.KillCount}", new Player() + var e = new GameEvent() { - ClientId = 1, - Level = Player.Permission.Console, - ClientNumber = -1, - CurrentServer = attacker.CurrentServer - }, attacker, attacker.CurrentServer)); + Data = penalty.Type == Cheat.Detection.DetectionType.Bone ? + $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" : + $"{penalty.Type} -{Math.Round(penalty.Value, 2)}@{penalty.HitCount}", + Origin = new Player() + { + ClientId = 1, + Level = Player.Permission.Console, + ClientNumber = -1, + CurrentServer = attacker.CurrentServer + }, + Target = attacker, + Owner = attacker.CurrentServer, + Type = GameEvent.EventType.Flag + }; + await new CFlag().ExecuteAsync(e); break; } } @@ -433,10 +448,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } // todo: do we want to save this immediately? - var statsSvc = ContextThreads[serverId]; - statsSvc.ClientStatSvc.Update(attackerStats); - statsSvc.ClientStatSvc.Update(victimStats); - //await statsSvc.ClientStatSvc.SaveChangesAsync(); + var statsSvc = ContextThreads[serverId].ClientStatSvc; + statsSvc.Update(attackerStats); + statsSvc.Update(victimStats); + await statsSvc.SaveChangesAsync(); } /// @@ -463,7 +478,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers victimStats.KillStreak = 0; // process the attacker's stats after the kills - attackerStats = UpdateStats(attackerStats); + //attackerStats = UpdateStats(attackerStats); // update after calculation attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds; diff --git a/Plugins/Stats/Helpers/ThreadSafeStatsService.cs b/Plugins/Stats/Helpers/ThreadSafeStatsService.cs index 2c4b9f8e8..22c93dab5 100644 --- a/Plugins/Stats/Helpers/ThreadSafeStatsService.cs +++ b/Plugins/Stats/Helpers/ThreadSafeStatsService.cs @@ -10,7 +10,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { public class ThreadSafeStatsService { - public GenericRepository ClientStatSvc { get; private set; } + public GenericRepository ClientStatSvc + { + get + { + return new GenericRepository(); + } + } public GenericRepository ServerSvc { get; private set; } public GenericRepository KillStatsSvc { get; private set; } public GenericRepository ServerStatsSvc { get; private set; } @@ -18,7 +24,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public ThreadSafeStatsService() { - ClientStatSvc = new GenericRepository(); + //ClientStatSvc = new GenericRepository(); ServerSvc = new GenericRepository(); KillStatsSvc = new GenericRepository(); ServerStatsSvc = new GenericRepository(); diff --git a/SharedLibraryCore/Helpers/ChangeTracking.cs b/SharedLibraryCore/Helpers/ChangeTracking.cs new file mode 100644 index 000000000..c626c42d2 --- /dev/null +++ b/SharedLibraryCore/Helpers/ChangeTracking.cs @@ -0,0 +1,41 @@ +using SharedLibraryCore.Interfaces; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Helpers +{ + public class ChangeTracking + { + List Values; + + public ChangeTracking() + { + Values = new List(); + } + + public void OnChange(ITrackable value) + { + Values.Add(value.GetTrackableValue()); + } + + public void ClearChanges() + { + Values.Clear(); + } + + public string[] GetChanges() + { + List values = new List(); + + int number = 1; + foreach (string change in Values) + { + values.Add($"{number} {change}"); + number++; + } + + return values.ToArray(); + } + } +} diff --git a/SharedLibraryCore/Interfaces/ITrackable.cs b/SharedLibraryCore/Interfaces/ITrackable.cs new file mode 100644 index 000000000..b36b1d520 --- /dev/null +++ b/SharedLibraryCore/Interfaces/ITrackable.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Interfaces +{ + public interface ITrackable + { + string GetTrackableValue(); + } +} diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index d8b5b8e5b..978c7649d 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -242,7 +242,7 @@ namespace SharedLibraryCore return Game.IW4; if (gameName.Contains("CoD4")) return Game.IW3; - if (gameName.Contains("WaW")) + if (gameName.Contains("COD_WaW")) return Game.T4; if (gameName.Contains("COD_T5_S")) return Game.T5;