adjust detection thresholds for recoil and offset

make sure we don't keep adding penalties after first
add "Other" penalty for future plugin use
This commit is contained in:
RaidMax 2019-06-24 16:56:47 -05:00
parent 253c7c8721
commit 8119ff9f83
4 changed files with 41 additions and 184 deletions

View File

@ -21,9 +21,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
}; };
public ChangeTracking<EFACSnapshot> Tracker { get; private set; } public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
public const int QUEUE_COUNT = 10; public const int MAX_TRACKED_HIT_COUNT = 10;
public List<EFClientKill> QueuedHits { get; set; } public List<EFClientKill> TrackedHits { get; set; }
int Kills; int Kills;
int HitCount; int HitCount;
Dictionary<IW4Info.HitLocation, HitInfo> HitLocationCount; Dictionary<IW4Info.HitLocation, HitInfo> HitLocationCount;
@ -54,7 +54,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
ClientStats = clientStats; ClientStats = clientStats;
Strain = new Strain(); Strain = new Strain();
Tracker = new ChangeTracking<EFACSnapshot>(); Tracker = new ChangeTracking<EFACSnapshot>();
QueuedHits = new List<EFClientKill>(); TrackedHits = new List<EFClientKill>();
} }
/// <summary> /// <summary>
@ -136,7 +136,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
AngleDifferenceAverage = weightedSessionAverage; AngleDifferenceAverage = weightedSessionAverage;
if (weightedSessionAverage > Thresholds.MaxOffset(totalSessionHits) && if (weightedSessionAverage > Thresholds.MaxOffset(totalSessionHits) &&
totalSessionHits > 40) totalSessionHits >= (Thresholds.MediumSampleMinKills * 2))
{ {
Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***"); Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
Log.WriteDebug($"Session Average = {weightedSessionAverage}"); Log.WriteDebug($"Session Average = {weightedSessionAverage}");
@ -157,8 +157,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Log.WriteDebug($"PredictVsReal={realAgainstPredict}"); Log.WriteDebug($"PredictVsReal={realAgainstPredict}");
#endif #endif
} }
#endregion
double currentStrain = Strain.GetStrain(isDamage, hit.Damage, hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, hit.TimeOffset - LastOffset));
#region STRAIN
double currentStrain = Strain.GetStrain(hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, hit.TimeOffset - LastOffset));
#if DEBUG == true #if DEBUG == true
Log.WriteDebug($"Current Strain: {currentStrain}"); Log.WriteDebug($"Current Strain: {currentStrain}");
#endif #endif
@ -213,7 +216,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
}); });
} }
if (ClientStats.AverageRecoilOffset == 0 && HitCount > Thresholds.HighSampleMinKills) if (ClientStats.AverageRecoilOffset == 0 && HitCount >= Thresholds.LowSampleMinKills)
{ {
results.Add(new DetectionPenaltyResult() results.Add(new DetectionPenaltyResult()
{ {
@ -251,19 +254,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
// ban on headshot // ban on headshot
if (currentHeadshotRatio > maxHeadshotLerpValueForBan) if (currentHeadshotRatio > maxHeadshotLerpValueForBan)
{ {
//Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
//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());
results.Add(new DetectionPenaltyResult() results.Add(new DetectionPenaltyResult()
{ {
ClientPenalty = EFPenalty.PenaltyType.Ban, ClientPenalty = EFPenalty.PenaltyType.Ban,
@ -275,19 +265,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
else else
{ {
//Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
//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());
results.Add(new DetectionPenaltyResult() results.Add(new DetectionPenaltyResult()
{ {
ClientPenalty = EFPenalty.PenaltyType.Flag, ClientPenalty = EFPenalty.PenaltyType.Flag,
@ -307,19 +284,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
// ban on bone ratio // ban on bone ratio
if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan) if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
{ {
//Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**");
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
//Log.WriteDebug($"**HitCount: {HitCount}");
//Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
//Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}");
//var sb = new StringBuilder();
//foreach (var kvp in HitLocationCount)
//{
// sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
//}
//Log.WriteDebug(sb.ToString());
results.Add(new DetectionPenaltyResult() results.Add(new DetectionPenaltyResult()
{ {
ClientPenalty = EFPenalty.PenaltyType.Ban, ClientPenalty = EFPenalty.PenaltyType.Ban,
@ -331,19 +295,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
else else
{ {
//Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
//Log.WriteDebug($"**HitCount: {HitCount}");
//Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
//Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}");
//var sb = new StringBuilder();
//foreach (var kvp in HitLocationCount)
//{
// sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
//}
//Log.WriteDebug(sb.ToString());
results.Add(new DetectionPenaltyResult() results.Add(new DetectionPenaltyResult()
{ {
ClientPenalty = EFPenalty.PenaltyType.Flag, ClientPenalty = EFPenalty.PenaltyType.Flag,
@ -375,16 +326,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills * 2) if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills * 2)
{ {
//Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
//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());
results.Add(new DetectionPenaltyResult() results.Add(new DetectionPenaltyResult()
{ {
ClientPenalty = EFPenalty.PenaltyType.Ban, ClientPenalty = EFPenalty.PenaltyType.Ban,
@ -396,16 +337,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
else else
{ {
//Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
//Log.WriteDebug($"**Chest Hits: {chestHits}");
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
//Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
//var sb = new StringBuilder();
//foreach (var kvp in HitLocationCount)
// sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
//Log.WriteDebug(sb.ToString());
results.Add(new DetectionPenaltyResult() results.Add(new DetectionPenaltyResult()
{ {
ClientPenalty = EFPenalty.PenaltyType.Flag, ClientPenalty = EFPenalty.PenaltyType.Flag,
@ -457,77 +388,5 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
ClientPenalty = EFPenalty.PenaltyType.Any, ClientPenalty = EFPenalty.PenaltyType.Any,
}; };
} }
public DetectionPenaltyResult ProcessTotalRatio(EFClientStatistics stats)
{
int totalChestHits = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.torso_upper).HitCount;
var results = new List<DetectionPenaltyResult>();
if (totalChestHits >= Thresholds.MediumSampleMinKills * 2)
{
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 = totalChestHits /
stats.HitLocations.Single(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount;
if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
{
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan)
{
//Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
//Log.WriteDebug($"ClientId: {stats.ClientId}");
//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());
results.Add(new DetectionPenaltyResult()
{
ClientPenalty = EFPenalty.PenaltyType.Ban,
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 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());
results.Add(new DetectionPenaltyResult()
{
ClientPenalty = EFPenalty.PenaltyType.Flag,
Value = currentChestAbdomenRatio,
Location = IW4Info.HitLocation.torso_upper,
HitCount = totalChestHits,
Type = DetectionType.Chest
});
}
}
}
return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ??
results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ??
new DetectionPenaltyResult()
{
ClientPenalty = EFPenalty.PenaltyType.Any,
};
}
} }
} }

View File

@ -14,7 +14,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public Vector3 LastAngle { get; private set; } public Vector3 LastAngle { get; private set; }
public double LastDeltaTime { get; private set; } public double LastDeltaTime { get; private set; }
public double GetStrain(bool isDamage, int damage, double killDistance, Vector3 newAngle, double deltaTime) public double GetStrain(double killDistance, Vector3 newAngle, double deltaTime)
{ {
if (LastAngle == null) if (LastAngle == null)
LastAngle = newAngle; LastAngle = newAngle;

View File

@ -502,11 +502,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
DeathOrigin = vDeathOrigin, DeathOrigin = vDeathOrigin,
KillOrigin = vKillOrigin, KillOrigin = vKillOrigin,
DeathType = ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath)), DeathType = ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath)),
Damage = Int32.Parse(damage), Damage = int.Parse(damage),
HitLoc = ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)), HitLoc = ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)),
Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName)), Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName)),
ViewAngles = vViewAngles, ViewAngles = vViewAngles,
TimeOffset = Int64.Parse(offset), TimeOffset = long.Parse(offset),
When = time, When = time,
IsKillstreakKill = isKillstreakKill[0] != '0', IsKillstreakKill = isKillstreakKill[0] != '0',
AdsPercent = float.Parse(Ads, System.Globalization.CultureInfo.InvariantCulture), AdsPercent = float.Parse(Ads, System.Globalization.CultureInfo.InvariantCulture),
@ -542,12 +542,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId]; var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId];
var clientStats = _servers[serverId].PlayerStats[attacker.ClientId]; var clientStats = _servers[serverId].PlayerStats[attacker.ClientId];
using (var ctx = new DatabaseContext(disableTracking: true))
{
ctx.Set<EFClientStatistics>().Update(clientStats);
await ctx.SaveChangesAsync();
}
// increment their hit count // increment their hit count
if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
hit.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || hit.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET ||
@ -556,10 +550,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
clientStats.HitLocations.Single(hl => hl.Location == hit.HitLoc).HitCount += 1; clientStats.HitLocations.Single(hl => hl.Location == hit.HitLoc).HitCount += 1;
} }
using (var ctx = new DatabaseContext(disableTracking: true))
{
ctx.Set<EFClientStatistics>().Update(clientStats);
await ctx.SaveChangesAsync();
}
using (var ctx = new DatabaseContext()) using (var ctx = new DatabaseContext())
{ {
await OnProcessingPenalty.WaitAsync();
try try
{ {
if (Plugin.Config.Configuration().StoreClientKills) if (Plugin.Config.Configuration().StoreClientKills)
@ -573,40 +571,41 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
#if DEBUG #if DEBUG
if (clientDetection.QueuedHits.Count > 0) if (clientDetection.QueuedHits.Count > 0)
#else #else
if (clientDetection.QueuedHits.Count > Detection.QUEUE_COUNT) if (clientDetection.TrackedHits.Count > Detection.MAX_TRACKED_HIT_COUNT)
#endif #endif
{ {
while (clientDetection.QueuedHits.Count > 0) while (clientDetection.TrackedHits.Count > 0)
{ {
clientDetection.QueuedHits = clientDetection.QueuedHits.OrderBy(_hits => _hits.TimeOffset).ToList(); await OnProcessingPenalty.WaitAsync();
var oldestHit = clientDetection.QueuedHits.First();
clientDetection.QueuedHits.RemoveAt(0); var oldestHit = clientDetection.TrackedHits.OrderBy(_hits => _hits.TimeOffset).First();
clientDetection.TrackedHits.Remove(oldestHit);
result = clientDetection.ProcessHit(oldestHit, isDamage); result = clientDetection.ProcessHit(oldestHit, isDamage);
await ApplyPenalty(result, attacker, ctx); await ApplyPenalty(result, attacker, ctx);
if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any) if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any)
{ {
SaveTrackedSnapshots(clientDetection, ctx); SaveTrackedSnapshots(clientDetection, ctx);
if (result.ClientPenalty == EFPenalty.PenaltyType.Ban)
{
OnProcessingPenalty.Release(1);
break;
}
} }
}
result = clientDetection.ProcessTotalRatio(clientStats); OnProcessingPenalty.Release(1);
await ApplyPenalty(result , attacker, ctx);
if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any)
{
SaveTrackedSnapshots(clientDetection, ctx);
} }
} }
else else
{ {
clientDetection.QueuedHits.Add(hit); clientDetection.TrackedHits.Add(hit);
} }
} }
ctx.Set<EFHitLocationCount>().UpdateRange(clientStats.HitLocations); ctx.Set<EFHitLocationCount>().UpdateRange(clientStats.HitLocations);
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
@ -615,8 +614,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
_log.WriteError("Could not save hit or AC info"); _log.WriteError("Could not save hit or AC info");
_log.WriteDebug(ex.GetExceptionInfo()); _log.WriteDebug(ex.GetExceptionInfo());
} }
OnProcessingPenalty.Release(1);
} }
} }
@ -859,7 +856,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ctx.Add(clientHistory); ctx.Add(clientHistory);
} }
#region INDIVIDUAL_SERVER_PERFORMANCE #region INDIVIDUAL_SERVER_PERFORMANCE
// get the client ranking for the current server // get the client ranking for the current server
int individualClientRanking = await ctx.Set<EFRating>() int individualClientRanking = await ctx.Set<EFRating>()
.Where(GetRankingFunc(clientStats.ServerId)) .Where(GetRankingFunc(clientStats.ServerId))
@ -910,8 +907,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// add new rating for current server // add new rating for current server
ctx.Add(newServerRating); ctx.Add(newServerRating);
#endregion #endregion
#region OVERALL_RATING #region OVERALL_RATING
// select all performance & time played for current client // select all performance & time played for current client
var iqClientStats = from stats in ctx.Set<EFClientStatistics>() var iqClientStats = from stats in ctx.Set<EFClientStatistics>()
where stats.ClientId == client.ClientId where stats.ClientId == client.ClientId
@ -986,7 +983,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}; };
ctx.Add(averageRating); ctx.Add(averageRating);
#endregion #endregion
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
@ -1021,7 +1018,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// calulate elo // calulate elo
if (_servers[attackerStats.ServerId].PlayerStats.Count > 1) if (_servers[attackerStats.ServerId].PlayerStats.Count > 1)
{ {
#region DEPRECATED #region DEPRECATED
/* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats /* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != attackerStats.ClientId) .Where(cs => cs.Value.ClientId != attackerStats.ClientId)
.Where(cs => .Where(cs =>
@ -1045,7 +1042,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ? double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ?
validVictimLobbyRatings.Average(cs => cs.Value.EloRating) : validVictimLobbyRatings.Average(cs => cs.Value.EloRating) :
victimStats.EloRating;*/ victimStats.EloRating;*/
#endregion #endregion
double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating)); double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating));
double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E)); double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E));

View File

@ -15,7 +15,8 @@ namespace SharedLibraryCore.Database.Models
Ban, Ban,
Unban, Unban,
Any, Any,
Unflag Unflag,
Other = 100
} }
} }
} }