fix T6 reading

add WaW support
fix stats threading
This commit is contained in:
RaidMax 2018-05-10 23:52:20 -05:00
parent e964013700
commit 6e5501b32d
15 changed files with 246 additions and 151 deletions

View File

@ -13,7 +13,7 @@ namespace IW4MAdmin.Application.EventParsers
public virtual GameEvent GetEvent(Server server, string logLine) public virtual GameEvent GetEvent(Server server, string logLine)
{ {
string[] lineSplit = logLine.Split(';'); 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') if (cleanedEventLine[0] == 'K')
{ {

View File

@ -9,9 +9,9 @@ using SharedLibraryCore.Objects;
namespace IW4MAdmin.Application.EventParsers 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 cleanedEventLine = Regex.Replace(logLine, @"^ *[0-9]+:[0-9]+ *", "").Trim();
string[] lineSplit = cleanedEventLine.Split(';'); string[] lineSplit = cleanedEventLine.Split(';');
@ -104,8 +104,8 @@ namespace IW4MAdmin.Application.EventParsers
}, },
Owner = server Owner = server
}; };
} }*/
public string GetGameDir() => $"t6r{Path.DirectorySeparatorChar}data"; public override string GetGameDir() => $"t6r{Path.DirectorySeparatorChar}data";
} }
} }

View File

@ -139,7 +139,7 @@ namespace IW4MAdmin.Application
sensitiveEvent.OnProcessed.Set(); sensitiveEvent.OnProcessed.Set();
} }
await Task.Delay(5000); await Task.Delay(1000);
} }
} }

View File

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

View File

@ -23,7 +23,7 @@ namespace Application.RconParsers
TempBan = "tempbanclient {0} \"{1}\"" 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<string[]> ExecuteCommandAsync(Connection connection, string command) public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{ {
@ -72,7 +72,7 @@ namespace Application.RconParsers
return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0; return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0;
} }
public CommandPrefix GetCommandPrefixes() => Prefixes; public virtual CommandPrefix GetCommandPrefixes() => Prefixes;
private List<Player> ClientsFromStatus(string[] Status) private List<Player> ClientsFromStatus(string[] Status)
{ {

View File

@ -723,7 +723,7 @@ namespace IW4MAdmin
public async Task Initialize() public async Task Initialize()
{ {
RconParser = ServerConfig.UseT6MParser ? (IRConParser)new T6MRConParser() : new IW4RConParser(); RconParser = ServerConfig.UseT6MParser ? (IRConParser)new T6MRConParser() : new IW3RConParser();
if (ServerConfig.UseIW5MParser) if (ServerConfig.UseIW5MParser)
RconParser = new IW5MRConParser(); RconParser = new IW5MRConParser();

View File

@ -6,17 +6,23 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
namespace IW4MAdmin.Plugins.Stats.Cheat namespace IW4MAdmin.Plugins.Stats.Cheat
{ {
class Detection class Detection
{ {
public enum DetectionType
{
Bone,
Chest,
Offset,
Strain
};
int Kills; int Kills;
int HitCount; int HitCount;
int AboveThresholdCount;
double AverageKillTime;
Dictionary<IW4Info.HitLocation, int> HitLocationCount; Dictionary<IW4Info.HitLocation, int> HitLocationCount;
ChangeTracking Tracker;
double AngleDifferenceAverage; double AngleDifferenceAverage;
EFClientStatistics ClientStats; EFClientStatistics ClientStats;
DateTime LastHit; DateTime LastHit;
@ -32,31 +38,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
HitLocationCount.Add((IW4Info.HitLocation)loc, 0); HitLocationCount.Add((IW4Info.HitLocation)loc, 0);
ClientStats = clientStats; ClientStats = clientStats;
Strain = new Strain(); Strain = new Strain();
} Tracker = new ChangeTracking();
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<IW4Info.MeansOfDeath>.Get(match.Groups[12].Value, typeof(IW4Info.MeansOfDeath));
var hitLocation = ParseEnum<IW4Info.HitLocation>.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;
}
}
} }
/// <summary> /// <summary>
@ -111,8 +93,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = hitLoc.HitOffsetAverage, Value = hitLoc.HitOffsetAverage,
KillCount = hitLoc.HitCount, HitCount = hitLoc.HitCount,
}; };
} }
@ -124,15 +106,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
{ {
Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***"); Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
Log.WriteDebug($"Session Average = {sessAverage}"); Log.WriteDebug($"Session Average = {sessAverage}");
// Log.WriteDebug($"Bone = {hitLoc.Location}");
Log.WriteDebug($"HitCount = {HitCount}"); Log.WriteDebug($"HitCount = {HitCount}");
Log.WriteDebug($"ID = {kill.AttackerId}"); Log.WriteDebug($"ID = {kill.AttackerId}");
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = sessAverage, Value = sessAverage,
KillCount = HitCount, HitCount = HitCount,
Type = DetectionType.Offset,
Location = hitLoc.Location
}; };
} }
@ -140,10 +123,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Log.WriteDebug($"PredictVsReal={realAgainstPredict}"); Log.WriteDebug($"PredictVsReal={realAgainstPredict}");
#endif #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; LastOffset = kill.TimeOffset;
if (currentStrain > ClientStats.MaxStrain) if (currentStrain > ClientStats.MaxStrain)
@ -151,14 +132,20 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
ClientStats.MaxStrain = currentStrain; ClientStats.MaxStrain = currentStrain;
} }
if (currentStrain > Thresholds.MaxStrain) if (currentStrain > Thresholds.MaxStrainFlag)
{ {
Log.WriteDebug("*** Reached Max Strain ***"); Tracker.OnChange(Strain);
Log.WriteDebug($"Strain = {currentStrain}");
Log.WriteDebug($"Angles = {kill.ViewAngles} {kill.AnglesList[0]} {kill.AnglesList[1]}"); foreach (string change in Tracker.GetChanges())
Log.WriteDebug($"Time = {diff}"); {
Log.WriteDebug($"HitCount = {HitCount}"); Log.WriteDebug(change);
Log.WriteDebug($"ID = {kill.AttackerId}"); }
Log.WriteDebug(ClientStats.RoundScore.ToString());
}
else
{
Tracker.ClearChanges();
} }
if (Strain.TimesReachedMaxStrain >= 3) if (Strain.TimesReachedMaxStrain >= 3)
@ -166,8 +153,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = ClientStats.MaxStrain, Value = ClientStats.MaxStrain,
KillCount = HitCount, HitCount = HitCount,
Type = DetectionType.Strain
}; };
} }
@ -203,46 +191,44 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
// ban on headshot // ban on headshot
if (currentHeadshotRatio > maxHeadshotLerpValueForFlag) if (currentHeadshotRatio > maxHeadshotLerpValueForFlag)
{ {
AboveThresholdCount++;
Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**"); Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Ban, ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentHeadshotRatio, Value = currentHeadshotRatio,
Bone = IW4Info.HitLocation.head, Location = IW4Info.HitLocation.head,
KillCount = Kills HitCount = HitCount,
Type = DetectionType.Bone
}; };
} }
else else
{ {
AboveThresholdCount++;
Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**"); Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentHeadshotRatio, Value = currentHeadshotRatio,
Bone = IW4Info.HitLocation.head, Location = IW4Info.HitLocation.head,
KillCount = Kills HitCount = HitCount,
Type = DetectionType.Bone
}; };
} }
} }
@ -257,7 +243,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
{ {
Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**"); Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}");
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -268,16 +254,17 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Ban, ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentMaxBoneRatio, Value = currentMaxBoneRatio,
Bone = bone, Location = bone,
KillCount = Kills HitCount = HitCount,
Type = DetectionType.Bone
}; };
} }
else else
{ {
Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**"); Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -288,9 +275,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentMaxBoneRatio, Value = currentMaxBoneRatio,
Bone = bone, Location = bone,
KillCount = Kills HitCount = HitCount,
Type = DetectionType.Bone
}; };
} }
} }
@ -298,12 +286,12 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
#region CHEST_ABDOMEN_RATIO_SESSION #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 marginOfError = Thresholds.GetMarginOfError(chestHits);
double lerpAmount = Math.Min(1.0, (chestKills - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
// determine max acceptable ratio of chest to abdomen kills // determine max acceptable ratio of chest to abdomen kills
double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3), Thresholds.ChestAbdomenRatioThresholdHighSample(3), lerpAmount) + marginOfError; double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3), Thresholds.ChestAbdomenRatioThresholdHighSample(3), lerpAmount) + marginOfError;
double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(4), Thresholds.ChestAbdomenRatioThresholdHighSample(4), 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 > 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("**Maximum Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Chest Kills: {chestKills}"); Log.WriteDebug($"**Chest Hits: {chestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Ban, ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentChestAbdomenRatio, Value = currentChestAbdomenRatio,
Bone = 0, Location = IW4Info.HitLocation.torso_upper,
KillCount = chestKills Type = DetectionType.Chest,
HitCount = chestHits
}; };
} }
else else
{ {
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**"); Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Chest Kills: {chestKills}"); Log.WriteDebug($"**Chest Hits: {chestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -350,9 +338,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentChestAbdomenRatio, Value = currentChestAbdomenRatio,
Bone = 0, Location = IW4Info.HitLocation.torso_upper,
KillCount = chestKills Type = DetectionType.Chest,
HitCount = chestHits
}; };
} }
} }
@ -362,23 +351,22 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Any, ClientPenalty = Penalty.PenaltyType.Any,
RatioAmount = 0
}; };
} }
public DetectionPenaltyResult ProcessTotalRatio(EFClientStatistics stats) 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 marginOfError = Thresholds.GetMarginOfError(totalChestHits);
double lerpAmount = Math.Min(1.0, (totalChestKills - 60) / 250.0); double lerpAmount = Math.Min(1.0, (totalChestHits - 60) / 250.0);
// determine max acceptable ratio of chest to abdomen kills // determine max acceptable ratio of chest to abdomen kills
double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), Thresholds.ChestAbdomenRatioThresholdHighSample(2.0), lerpAmount) + marginOfError; 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 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; stats.HitLocations.Single(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount;
if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag) if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
@ -388,42 +376,42 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
{ {
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**"); Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {stats.ClientId}"); Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Kills: {totalChestKills}"); Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var location in stats.HitLocations) foreach (var location in stats.HitLocations)
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n"); sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Ban, ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentChestAbdomenRatio, Value = currentChestAbdomenRatio,
Bone = IW4Info.HitLocation.torso_upper, Location = IW4Info.HitLocation.torso_upper,
KillCount = totalChestKills HitCount = totalChestHits,
Type = DetectionType.Chest
}; };
} }
else else
{ {
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**"); Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {stats.ClientId}"); Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Kills: {totalChestKills}"); Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var location in stats.HitLocations) foreach (var location in stats.HitLocations)
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n"); sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentChestAbdomenRatio, Value = currentChestAbdomenRatio,
Bone = IW4Info.HitLocation.torso_upper, Location = IW4Info.HitLocation.torso_upper,
KillCount = totalChestKills HitCount = totalChestHits,
Type = DetectionType.Chest
}; };
} }
} }
@ -431,7 +419,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
Bone = IW4Info.HitLocation.none,
ClientPenalty = Penalty.PenaltyType.Any ClientPenalty = Penalty.PenaltyType.Any
}; };
} }

View File

@ -9,9 +9,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
{ {
class DetectionPenaltyResult class DetectionPenaltyResult
{ {
public Detection.DetectionType Type { get; set; }
public Penalty.PenaltyType ClientPenalty { get; set; } public Penalty.PenaltyType ClientPenalty { get; set; }
public double RatioAmount { get; set; } public double Value { get; set; }
public IW4Info.HitLocation Bone { get; set; } public IW4Info.HitLocation Location { get; set; }
public int KillCount { get; set; } public int HitCount { get; set; }
} }
} }

View File

@ -1,15 +1,18 @@
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace IW4MAdmin.Plugins.Stats.Cheat namespace IW4MAdmin.Plugins.Stats.Cheat
{ {
class Strain class Strain : ITrackable
{ {
private static double StrainDecayBase = 0.15; private static double StrainDecayBase = 0.15;
private double CurrentStrain; private double CurrentStrain;
private Vector3 LastAngle; private Vector3 LastAngle;
private double LastDeltaTime;
private double LastDistance;
public int TimesReachedMaxStrain { get; private set; } public int TimesReachedMaxStrain { get; private set; }
@ -18,10 +21,13 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (LastAngle == null) if (LastAngle == null)
LastAngle = newAngle; LastAngle = newAngle;
LastDeltaTime = deltaTime;
double decayFactor = GetDecay(deltaTime); double decayFactor = GetDecay(deltaTime);
CurrentStrain *= decayFactor; CurrentStrain *= decayFactor;
double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle); double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle);
LastDistance = distance[0] + distance[1];
// this happens on first kill // this happens on first kill
if ((distance[0] == 0 && distance[1] == 0) || 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; double newStrain = Math.Pow(distance[0] + distance[1], 0.99) / deltaTime;
CurrentStrain += newStrain; CurrentStrain += newStrain;
if (CurrentStrain > Thresholds.MaxStrain) if (CurrentStrain > Thresholds.MaxStrainFlag)
TimesReachedMaxStrain++; TimesReachedMaxStrain++;
LastAngle = newAngle; LastAngle = newAngle;
return CurrentStrain; 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); private double GetDecay(double deltaTime) => Math.Pow(StrainDecayBase, deltaTime / 1000.0);
} }
} }

View File

@ -27,8 +27,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public const int HighSampleMinKills = 100; public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2; 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 MaxOffset = 4.789;
public const double MaxStrainFlag = 0.2;
public static double GetMarginOfError(int numKills) => 1.6455 / Math.Sqrt(numKills); public static double GetMarginOfError(int numKills) => 1.6455 / Math.Sqrt(numKills);

View File

@ -126,13 +126,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.ToList() .ToList()
}; };
clientStats = statsSvc.ClientStatSvc.Insert(clientStats); // insert if they've not been added
await statsSvc.ClientStatSvc.SaveChangesAsync(); var clientStatsSvc = statsSvc.ClientStatSvc;
} clientStats = clientStatsSvc.Insert(clientStats);
await clientStatsSvc.SaveChangesAsync();
else
{
statsSvc.ClientStatSvc.Update(clientStats);
} }
// migration for previous existing stats // migration for previous existing stats
@ -161,9 +158,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats))) if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats)))
Log.WriteDebug("Could not add client to detection"); Log.WriteDebug("Could not add client to detection");
/*
await statsSvc.ClientStatSvc.SaveChangesAsync();*/
return clientStats; return clientStats;
} }
@ -193,30 +187,39 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// get individual client's stats // get individual client's stats
var clientStats = playerStats[pl.ClientId]; var clientStats = playerStats[pl.ClientId];
/*// sync their score
clientStats.SessionScore += pl.Score;*/
// remove the client from the stats dictionary as they're leaving // remove the client from the stats dictionary as they're leaving
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3); playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3);
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4); detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4);
// sync their stats before they leave // 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 // increment the total play time
serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds; serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds;
//await statsSvc.ServerStatsSvc.SaveChangesAsync();
} }
public void AddDamageEvent(string eventLine, int clientId, int serverId) public void AddDamageEvent(string eventLine, int clientId, int serverId)
{ {
if (Plugin.Config.Configuration().EnableAntiCheat) /* 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 clientDetection = Servers[serverId].PlayerDetections[clientId]; var match = Regex.Match(damageLine, regex, RegexOptions.IgnoreCase);
clientDetection.ProcessScriptDamage(eventLine);
} if (match.Success)
{
var meansOfDeath = ParseEnum<IW4Info.MeansOfDeath>.Get(match.Groups[12].Value, typeof(IW4Info.MeansOfDeath));
var hitLocation = ParseEnum<IW4Info.HitLocation>.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;
}
}*/
} }
/// <summary> /// <summary>
@ -310,7 +313,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
clientStats.HitLocations.Single(hl => hl.Location == kill.HitLoc).HitCount += 1; clientStats.HitLocations.Single(hl => hl.Location == kill.HitLoc).HitCount += 1;
//statsSvc.ClientStatSvc.Update(clientStats); statsSvc.ClientStatSvc.Update(clientStats);
// await statsSvc.ClientStatSvc.SaveChangesAsync(); // await statsSvc.ClientStatSvc.SaveChangesAsync();
} }
@ -330,19 +333,31 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
switch (penalty.ClientPenalty) switch (penalty.ClientPenalty)
{ {
case Penalty.PenaltyType.Ban: 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; break;
case Penalty.PenaltyType.Flag: case Penalty.PenaltyType.Flag:
if (attacker.Level != Player.Permission.User) if (attacker.Level != Player.Permission.User)
break; break;
var flagCmd = new CFlag(); var e = new GameEvent()
await flagCmd.ExecuteAsync(new GameEvent(GameEvent.EventType.Flag, $"{(int)penalty.Bone}-{Math.Round(penalty.RatioAmount, 2).ToString()}@{penalty.KillCount}", new Player()
{ {
ClientId = 1, Data = penalty.Type == Cheat.Detection.DetectionType.Bone ?
Level = Player.Permission.Console, $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
ClientNumber = -1, $"{penalty.Type} -{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
CurrentServer = attacker.CurrentServer Origin = new Player()
}, attacker, attacker.CurrentServer)); {
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; break;
} }
} }
@ -433,10 +448,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
// todo: do we want to save this immediately? // todo: do we want to save this immediately?
var statsSvc = ContextThreads[serverId]; var statsSvc = ContextThreads[serverId].ClientStatSvc;
statsSvc.ClientStatSvc.Update(attackerStats); statsSvc.Update(attackerStats);
statsSvc.ClientStatSvc.Update(victimStats); statsSvc.Update(victimStats);
//await statsSvc.ClientStatSvc.SaveChangesAsync(); await statsSvc.SaveChangesAsync();
} }
/// <summary> /// <summary>
@ -463,7 +478,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
victimStats.KillStreak = 0; victimStats.KillStreak = 0;
// process the attacker's stats after the kills // process the attacker's stats after the kills
attackerStats = UpdateStats(attackerStats); //attackerStats = UpdateStats(attackerStats);
// update after calculation // update after calculation
attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds; attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds;

View File

@ -10,7 +10,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
public class ThreadSafeStatsService public class ThreadSafeStatsService
{ {
public GenericRepository<EFClientStatistics> ClientStatSvc { get; private set; } public GenericRepository<EFClientStatistics> ClientStatSvc
{
get
{
return new GenericRepository<EFClientStatistics>();
}
}
public GenericRepository<EFServer> ServerSvc { get; private set; } public GenericRepository<EFServer> ServerSvc { get; private set; }
public GenericRepository<EFClientKill> KillStatsSvc { get; private set; } public GenericRepository<EFClientKill> KillStatsSvc { get; private set; }
public GenericRepository<EFServerStatistics> ServerStatsSvc { get; private set; } public GenericRepository<EFServerStatistics> ServerStatsSvc { get; private set; }
@ -18,7 +24,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public ThreadSafeStatsService() public ThreadSafeStatsService()
{ {
ClientStatSvc = new GenericRepository<EFClientStatistics>(); //ClientStatSvc = new GenericRepository<EFClientStatistics>();
ServerSvc = new GenericRepository<EFServer>(); ServerSvc = new GenericRepository<EFServer>();
KillStatsSvc = new GenericRepository<EFClientKill>(); KillStatsSvc = new GenericRepository<EFClientKill>();
ServerStatsSvc = new GenericRepository<EFServerStatistics>(); ServerStatsSvc = new GenericRepository<EFServerStatistics>();

View File

@ -0,0 +1,41 @@
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Helpers
{
public class ChangeTracking
{
List<string> Values;
public ChangeTracking()
{
Values = new List<string>();
}
public void OnChange(ITrackable value)
{
Values.Add(value.GetTrackableValue());
}
public void ClearChanges()
{
Values.Clear();
}
public string[] GetChanges()
{
List<string> values = new List<string>();
int number = 1;
foreach (string change in Values)
{
values.Add($"{number} {change}");
number++;
}
return values.ToArray();
}
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Interfaces
{
public interface ITrackable
{
string GetTrackableValue();
}
}

View File

@ -242,7 +242,7 @@ namespace SharedLibraryCore
return Game.IW4; return Game.IW4;
if (gameName.Contains("CoD4")) if (gameName.Contains("CoD4"))
return Game.IW3; return Game.IW3;
if (gameName.Contains("WaW")) if (gameName.Contains("COD_WaW"))
return Game.T4; return Game.T4;
if (gameName.Contains("COD_T5_S")) if (gameName.Contains("COD_T5_S"))
return Game.T5; return Game.T5;