2021-03-22 12:09:25 -04:00
|
|
|
|
using SharedLibraryCore.Database.Models;
|
2018-11-05 22:01:29 -05:00
|
|
|
|
using SharedLibraryCore.Helpers;
|
2018-03-18 22:24:06 -04:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2019-07-27 18:46:48 -04:00
|
|
|
|
using System.Text.RegularExpressions;
|
2021-03-22 12:09:25 -04:00
|
|
|
|
using Data.Models;
|
|
|
|
|
using Data.Models.Client;
|
|
|
|
|
using Data.Models.Client.Stats;
|
2020-11-11 18:31:26 -05:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2021-03-22 12:09:25 -04:00
|
|
|
|
using SharedLibraryCore;
|
2023-02-11 22:01:28 -05:00
|
|
|
|
using Stats.Config;
|
2020-11-11 18:31:26 -05:00
|
|
|
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
2018-04-08 17:50:58 -04:00
|
|
|
|
namespace IW4MAdmin.Plugins.Stats.Cheat
|
2018-03-18 22:24:06 -04:00
|
|
|
|
{
|
2019-09-27 16:53:52 -04:00
|
|
|
|
public class Detection
|
2018-03-18 22:24:06 -04:00
|
|
|
|
{
|
2018-05-11 00:52:20 -04:00
|
|
|
|
public enum DetectionType
|
|
|
|
|
{
|
|
|
|
|
Bone,
|
|
|
|
|
Chest,
|
|
|
|
|
Offset,
|
2019-06-15 18:37:43 -04:00
|
|
|
|
Strain,
|
2019-09-09 18:40:04 -04:00
|
|
|
|
Recoil,
|
2020-09-30 18:15:47 -04:00
|
|
|
|
Snap,
|
|
|
|
|
Button
|
2018-05-11 00:52:20 -04:00
|
|
|
|
};
|
|
|
|
|
|
2018-06-05 17:31:36 -04:00
|
|
|
|
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
2019-09-09 18:40:04 -04:00
|
|
|
|
public const int MIN_HITS_TO_RUN_DETECTION = 5;
|
|
|
|
|
private const int MIN_ANGLE_COUNT = 5;
|
2018-06-05 17:31:36 -04:00
|
|
|
|
|
2019-06-24 17:56:47 -04:00
|
|
|
|
public List<EFClientKill> TrackedHits { get; set; }
|
2018-03-18 22:24:06 -04:00
|
|
|
|
int Kills;
|
2018-05-10 01:34:29 -04:00
|
|
|
|
int HitCount;
|
2019-06-12 11:27:15 -04:00
|
|
|
|
Dictionary<IW4Info.HitLocation, HitInfo> HitLocationCount;
|
2018-05-08 00:58:46 -04:00
|
|
|
|
double AngleDifferenceAverage;
|
2018-03-27 20:27:01 -04:00
|
|
|
|
EFClientStatistics ClientStats;
|
2023-02-11 22:01:28 -05:00
|
|
|
|
private readonly StatsConfiguration _statsConfiguration;
|
2018-05-05 16:36:26 -04:00
|
|
|
|
long LastOffset;
|
2021-06-29 16:02:01 -04:00
|
|
|
|
string LastWeapon;
|
2018-03-18 22:24:06 -04:00
|
|
|
|
ILogger Log;
|
2018-05-03 01:25:49 -04:00
|
|
|
|
Strain Strain;
|
2018-08-03 22:11:58 -04:00
|
|
|
|
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
2020-09-30 18:15:47 -04:00
|
|
|
|
private double mapAverageRecoilAmount;
|
2019-09-09 18:40:04 -04:00
|
|
|
|
private double sessionAverageSnapAmount;
|
|
|
|
|
private int sessionSnapHits;
|
|
|
|
|
private EFClientKill lastHit;
|
2019-09-10 18:24:32 -04:00
|
|
|
|
private int validRecoilHitCount;
|
2020-09-30 18:15:47 -04:00
|
|
|
|
private int validButtonHitCount;
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
2019-06-12 11:27:15 -04:00
|
|
|
|
private class HitInfo
|
|
|
|
|
{
|
|
|
|
|
public int Count { get; set; }
|
|
|
|
|
public double Offset { get; set; }
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-11 22:01:28 -05:00
|
|
|
|
public Detection(ILogger log, EFClientStatistics clientStats, StatsConfiguration statsConfiguration)
|
2018-03-18 22:24:06 -04:00
|
|
|
|
{
|
|
|
|
|
Log = log;
|
2019-06-12 11:27:15 -04:00
|
|
|
|
HitLocationCount = new Dictionary<IW4Info.HitLocation, HitInfo>();
|
2018-03-18 22:24:06 -04:00
|
|
|
|
foreach (var loc in Enum.GetValues(typeof(IW4Info.HitLocation)))
|
2018-11-05 22:01:29 -05:00
|
|
|
|
{
|
2019-06-12 11:27:15 -04:00
|
|
|
|
HitLocationCount.Add((IW4Info.HitLocation)loc, new HitInfo());
|
2018-11-05 22:01:29 -05:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-27 20:27:01 -04:00
|
|
|
|
ClientStats = clientStats;
|
2023-02-11 22:01:28 -05:00
|
|
|
|
_statsConfiguration = statsConfiguration;
|
2018-05-03 01:25:49 -04:00
|
|
|
|
Strain = new Strain();
|
2018-06-05 17:31:36 -04:00
|
|
|
|
Tracker = new ChangeTracking<EFACSnapshot>();
|
2019-06-24 17:56:47 -04:00
|
|
|
|
TrackedHits = new List<EFClientKill>();
|
2018-03-18 22:24:06 -04:00
|
|
|
|
}
|
2021-03-22 12:09:25 -04:00
|
|
|
|
|
|
|
|
|
private 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(preserveDirectionAngle(b.X, a.X),preserveDirectionAngle(b.Y, a.Y), 0),
|
|
|
|
|
new Vector3( preserveDirectionAngle(c.X, b.X), preserveDirectionAngle(c.Y, b.Y), 0)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var distance = new Vector3(Math.Abs(directions[1].X - directions[0].X),
|
|
|
|
|
Math.Abs(directions[1].Y - directions[0].Y), 0);
|
|
|
|
|
|
|
|
|
|
return Math.Sqrt((distance.X * distance.X) + (distance.Y * distance.Y));
|
|
|
|
|
}
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Analyze kill and see if performed by a cheater
|
|
|
|
|
/// </summary>
|
2018-10-28 21:47:56 -04:00
|
|
|
|
/// <param name="hit">kill performed by the player</param>
|
2018-03-18 22:24:06 -04:00
|
|
|
|
/// <returns>true if detection reached thresholds, false otherwise</returns>
|
2020-02-06 19:35:30 -05:00
|
|
|
|
public IEnumerable<DetectionPenaltyResult> ProcessHit(EFClientKill hit)
|
2018-03-18 22:24:06 -04:00
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
var results = new List<DetectionPenaltyResult>();
|
|
|
|
|
|
2021-03-22 12:09:25 -04:00
|
|
|
|
if ((hit.DeathType != (int)IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
|
|
|
|
|
hit.DeathType != (int)IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
|
|
|
|
|
hit.DeathType != (int)IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
|
|
|
|
|
hit.HitLoc == (int)IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 ||
|
2018-10-06 16:31:05 -04:00
|
|
|
|
// hack: prevents false positives
|
2021-06-29 16:02:01 -04:00
|
|
|
|
(LastWeapon != hit.WeaponReference && (hit.TimeOffset - LastOffset) == 50))
|
2018-11-05 22:01:29 -05:00
|
|
|
|
{
|
2020-02-06 19:35:30 -05:00
|
|
|
|
return new[] {new DetectionPenaltyResult()
|
2018-03-18 22:24:06 -04:00
|
|
|
|
{
|
2019-05-29 17:55:35 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Any,
|
2020-02-06 19:35:30 -05:00
|
|
|
|
}};
|
2018-11-05 22:01:29 -05:00
|
|
|
|
}
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
2021-06-29 16:02:01 -04:00
|
|
|
|
LastWeapon = hit.WeaponReference;
|
2018-10-28 21:47:56 -04:00
|
|
|
|
|
2021-03-22 12:09:25 -04:00
|
|
|
|
HitLocationCount[(IW4Info.HitLocation)hit.HitLoc].Count++;
|
2018-10-28 21:47:56 -04:00
|
|
|
|
HitCount++;
|
2018-05-03 01:25:49 -04:00
|
|
|
|
|
2020-02-06 19:35:30 -05:00
|
|
|
|
if (hit.IsKill)
|
2018-05-08 00:58:46 -04:00
|
|
|
|
{
|
|
|
|
|
Kills++;
|
|
|
|
|
}
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
2019-09-09 18:40:04 -04:00
|
|
|
|
#region SNAP
|
|
|
|
|
if (hit.AnglesList.Count == MIN_ANGLE_COUNT)
|
|
|
|
|
{
|
|
|
|
|
if (lastHit == null)
|
|
|
|
|
{
|
|
|
|
|
lastHit = hit;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 18:50:23 -04:00
|
|
|
|
bool areAnglesInvalid = hit.AnglesList[0].Equals(hit.AnglesList[1]) && hit.AnglesList[3].Equals(hit.AnglesList[4]);
|
|
|
|
|
|
|
|
|
|
if ((lastHit == hit ||
|
|
|
|
|
lastHit.VictimId != hit.VictimId ||
|
|
|
|
|
(hit.TimeOffset - lastHit.TimeOffset) >= 1000) &&
|
|
|
|
|
!areAnglesInvalid)
|
2019-09-09 18:40:04 -04:00
|
|
|
|
{
|
|
|
|
|
ClientStats.SnapHitCount++;
|
|
|
|
|
sessionSnapHits++;
|
2021-03-22 12:09:25 -04:00
|
|
|
|
var currentSnapDistance = SnapDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles);
|
2019-09-09 18:40:04 -04:00
|
|
|
|
double previousAverage = ClientStats.AverageSnapValue;
|
|
|
|
|
ClientStats.AverageSnapValue = (previousAverage * (ClientStats.SnapHitCount - 1) + currentSnapDistance) / ClientStats.SnapHitCount;
|
|
|
|
|
double previousSessionAverage = sessionAverageSnapAmount;
|
|
|
|
|
sessionAverageSnapAmount = (previousSessionAverage * (sessionSnapHits - 1) + currentSnapDistance) / sessionSnapHits;
|
|
|
|
|
lastHit = hit;
|
|
|
|
|
|
2019-09-27 16:53:52 -04:00
|
|
|
|
//var marginOfError = Thresholds.GetMarginOfError(sessionSnapHits);
|
|
|
|
|
//var marginOfErrorLifetime = Thresholds.GetMarginOfError(ClientStats.SnapHitCount);
|
|
|
|
|
|
2019-10-07 11:26:07 -04:00
|
|
|
|
if (sessionSnapHits >= Thresholds.MediumSampleMinKills &&
|
2019-09-27 16:53:52 -04:00
|
|
|
|
sessionAverageSnapAmount >= Thresholds.SnapFlagValue/* + marginOfError*/)
|
|
|
|
|
{
|
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
|
|
|
|
{
|
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Flag,
|
|
|
|
|
Value = sessionAverageSnapAmount,
|
2019-10-07 11:26:07 -04:00
|
|
|
|
HitCount = sessionSnapHits,
|
2019-09-27 16:53:52 -04:00
|
|
|
|
Type = DetectionType.Snap
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-07 11:26:07 -04:00
|
|
|
|
if (sessionSnapHits >= Thresholds.MediumSampleMinKills &&
|
2019-09-27 16:53:52 -04:00
|
|
|
|
sessionAverageSnapAmount >= Thresholds.SnapBanValue/* + marginOfError*/)
|
|
|
|
|
{
|
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
|
|
|
|
{
|
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
|
|
|
|
Value = sessionAverageSnapAmount,
|
2019-10-07 11:26:07 -04:00
|
|
|
|
HitCount = sessionSnapHits,
|
2019-09-27 16:53:52 -04:00
|
|
|
|
Type = DetectionType.Snap
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// lifetime
|
2019-10-07 11:26:07 -04:00
|
|
|
|
if (ClientStats.SnapHitCount >= Thresholds.MediumSampleMinKills * 2 &&
|
2019-09-27 16:53:52 -04:00
|
|
|
|
ClientStats.AverageSnapValue >= Thresholds.SnapFlagValue/* + marginOfErrorLifetime*/)
|
2019-09-09 18:40:04 -04:00
|
|
|
|
{
|
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
|
|
|
|
{
|
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Flag,
|
|
|
|
|
Value = sessionAverageSnapAmount,
|
|
|
|
|
HitCount = ClientStats.SnapHitCount,
|
|
|
|
|
Type = DetectionType.Snap
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-07 11:26:07 -04:00
|
|
|
|
if (ClientStats.SnapHitCount >= Thresholds.MediumSampleMinKills * 2 &&
|
2019-09-27 16:53:52 -04:00
|
|
|
|
ClientStats.AverageSnapValue >= Thresholds.SnapBanValue/* + marginOfErrorLifetime*/)
|
2019-09-09 18:40:04 -04:00
|
|
|
|
{
|
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
|
|
|
|
{
|
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
|
|
|
|
Value = sessionAverageSnapAmount,
|
|
|
|
|
HitCount = ClientStats.SnapHitCount,
|
|
|
|
|
Type = DetectionType.Snap
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-09-27 16:53:52 -04:00
|
|
|
|
|
2019-09-09 18:40:04 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
2018-05-04 00:22:10 -04:00
|
|
|
|
#region VIEWANGLES
|
2019-09-09 18:40:04 -04:00
|
|
|
|
int totalUsableAngleCount = hit.AnglesList.Count - 1;
|
|
|
|
|
int angleOffsetIndex = totalUsableAngleCount / 2;
|
|
|
|
|
if (hit.AnglesList.Count == 5)
|
2018-03-18 22:24:06 -04:00
|
|
|
|
{
|
2019-09-09 18:40:04 -04:00
|
|
|
|
double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[angleOffsetIndex - 1], hit.AnglesList[angleOffsetIndex + 1], hit.ViewAngles);
|
2018-05-03 01:25:49 -04:00
|
|
|
|
|
2018-05-08 00:58:46 -04:00
|
|
|
|
// LIFETIME
|
2018-03-27 20:27:01 -04:00
|
|
|
|
var hitLoc = ClientStats.HitLocations
|
2018-10-28 21:47:56 -04:00
|
|
|
|
.First(hl => hl.Location == hit.HitLoc);
|
2018-05-08 00:58:46 -04:00
|
|
|
|
|
2018-03-27 20:27:01 -04:00
|
|
|
|
float previousAverage = hitLoc.HitOffsetAverage;
|
2018-05-03 01:25:49 -04:00
|
|
|
|
double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount;
|
2018-03-27 20:27:01 -04:00
|
|
|
|
hitLoc.HitOffsetAverage = (float)newAverage;
|
2018-04-11 18:24:21 -04:00
|
|
|
|
|
2019-06-12 11:27:15 -04:00
|
|
|
|
int totalHits = ClientStats.HitLocations.Sum(_hit => _hit.HitCount);
|
|
|
|
|
var weightedLifetimeAverage = ClientStats.HitLocations.Where(_hit => _hit.HitCount > 0)
|
|
|
|
|
.Sum(_hit => _hit.HitOffsetAverage * _hit.HitCount) / totalHits;
|
|
|
|
|
|
|
|
|
|
if (weightedLifetimeAverage > Thresholds.MaxOffset(totalHits) &&
|
2018-05-16 00:57:37 -04:00
|
|
|
|
hitLoc.HitCount > 100)
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
2018-05-08 00:58:46 -04:00
|
|
|
|
{
|
2019-10-07 11:26:07 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
2018-05-11 00:52:20 -04:00
|
|
|
|
Value = hitLoc.HitOffsetAverage,
|
|
|
|
|
HitCount = hitLoc.HitCount,
|
2018-05-14 13:55:10 -04:00
|
|
|
|
Type = DetectionType.Offset
|
2019-06-15 18:37:43 -04:00
|
|
|
|
});
|
2018-04-11 18:24:21 -04:00
|
|
|
|
}
|
2018-05-03 01:25:49 -04:00
|
|
|
|
|
2018-05-08 00:58:46 -04:00
|
|
|
|
// SESSION
|
2021-03-22 12:09:25 -04:00
|
|
|
|
var sessionHitLoc = HitLocationCount[(IW4Info.HitLocation)hit.HitLoc];
|
2019-06-12 11:27:15 -04:00
|
|
|
|
sessionHitLoc.Offset = (sessionHitLoc.Offset * (sessionHitLoc.Count - 1) + realAgainstPredict) / sessionHitLoc.Count;
|
|
|
|
|
|
|
|
|
|
int totalSessionHits = HitLocationCount.Sum(_hit => _hit.Value.Count);
|
|
|
|
|
var weightedSessionAverage = HitLocationCount.Where(_hit => _hit.Value.Count > 0)
|
2019-06-13 20:10:08 -04:00
|
|
|
|
.Sum(_hit => _hit.Value.Offset * _hit.Value.Count) / totalSessionHits;
|
2018-05-04 00:22:10 -04:00
|
|
|
|
|
2019-06-24 12:01:34 -04:00
|
|
|
|
AngleDifferenceAverage = weightedSessionAverage;
|
|
|
|
|
|
2019-06-12 11:27:15 -04:00
|
|
|
|
if (weightedSessionAverage > Thresholds.MaxOffset(totalSessionHits) &&
|
2019-06-24 17:56:47 -04:00
|
|
|
|
totalSessionHits >= (Thresholds.MediumSampleMinKills * 2))
|
2018-05-04 00:22:10 -04:00
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
2018-05-04 00:22:10 -04:00
|
|
|
|
{
|
2019-10-07 11:26:07 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
2019-06-12 11:27:15 -04:00
|
|
|
|
Value = weightedSessionAverage,
|
2018-05-11 00:52:20 -04:00
|
|
|
|
HitCount = HitCount,
|
|
|
|
|
Type = DetectionType.Offset,
|
2021-03-22 12:09:25 -04:00
|
|
|
|
Location = (IW4Info.HitLocation)hitLoc.Location
|
2019-06-15 18:37:43 -04:00
|
|
|
|
});
|
2018-05-04 00:22:10 -04:00
|
|
|
|
}
|
2020-11-11 18:31:26 -05:00
|
|
|
|
|
|
|
|
|
Log.LogDebug("PredictVsReal={realAgainstPredict}", realAgainstPredict);
|
2018-05-03 01:25:49 -04:00
|
|
|
|
}
|
2019-06-24 17:56:47 -04:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region STRAIN
|
2019-09-27 16:53:52 -04:00
|
|
|
|
double currentStrain = Strain.GetStrain(hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, LastOffset == 0 ? 50 : (hit.TimeOffset - LastOffset)));
|
2020-11-11 18:31:26 -05:00
|
|
|
|
Log.LogDebug("Current Strain: {currentStrain}", currentStrain);
|
2018-10-28 21:47:56 -04:00
|
|
|
|
LastOffset = hit.TimeOffset;
|
2018-05-03 01:25:49 -04:00
|
|
|
|
|
|
|
|
|
if (currentStrain > ClientStats.MaxStrain)
|
|
|
|
|
{
|
|
|
|
|
ClientStats.MaxStrain = currentStrain;
|
2018-05-05 16:36:26 -04:00
|
|
|
|
}
|
2018-05-04 00:22:10 -04:00
|
|
|
|
|
2018-05-16 00:57:37 -04:00
|
|
|
|
// flag
|
2018-10-28 21:47:56 -04:00
|
|
|
|
if (currentStrain > Thresholds.MaxStrainFlag)
|
2018-05-16 00:57:37 -04:00
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
2018-05-16 00:57:37 -04:00
|
|
|
|
{
|
2019-05-29 17:55:35 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Flag,
|
2018-05-20 22:35:56 -04:00
|
|
|
|
Value = currentStrain,
|
2018-05-16 00:57:37 -04:00
|
|
|
|
HitCount = HitCount,
|
|
|
|
|
Type = DetectionType.Strain
|
2019-06-15 18:37:43 -04:00
|
|
|
|
});
|
2018-05-16 00:57:37 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ban
|
2018-07-05 22:04:34 -04:00
|
|
|
|
if (currentStrain > Thresholds.MaxStrainBan &&
|
2018-10-28 21:47:56 -04:00
|
|
|
|
HitCount >= 5)
|
2018-05-05 16:36:26 -04:00
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
2018-05-04 00:22:10 -04:00
|
|
|
|
{
|
2019-05-29 17:55:35 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
2018-05-20 22:35:56 -04:00
|
|
|
|
Value = currentStrain,
|
2018-05-11 00:52:20 -04:00
|
|
|
|
HitCount = HitCount,
|
|
|
|
|
Type = DetectionType.Strain
|
2019-06-15 18:37:43 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region RECOIL
|
2019-07-27 18:46:48 -04:00
|
|
|
|
float hitRecoilAverage = 0;
|
2020-09-30 18:15:47 -04:00
|
|
|
|
bool shouldIgnoreDetection = false;
|
|
|
|
|
try
|
|
|
|
|
{
|
2023-02-11 22:01:28 -05:00
|
|
|
|
shouldIgnoreDetection = _statsConfiguration.AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Recoil]
|
2021-06-29 16:02:01 -04:00
|
|
|
|
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
2020-09-30 18:15:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch (KeyNotFoundException)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!shouldIgnoreDetection)
|
2019-07-27 18:46:48 -04:00
|
|
|
|
{
|
2019-09-10 18:24:32 -04:00
|
|
|
|
validRecoilHitCount++;
|
2019-07-27 18:46:48 -04:00
|
|
|
|
hitRecoilAverage = (hit.AnglesList.Sum(_angle => _angle.Z) + hit.ViewAngles.Z) / (hit.AnglesList.Count + 1);
|
2020-09-30 18:15:47 -04:00
|
|
|
|
mapAverageRecoilAmount = (mapAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount;
|
2019-06-15 18:37:43 -04:00
|
|
|
|
|
2020-09-30 18:15:47 -04:00
|
|
|
|
if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && mapAverageRecoilAmount == 0)
|
2019-06-15 18:37:43 -04:00
|
|
|
|
{
|
2019-07-27 18:46:48 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
|
|
|
|
{
|
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
2020-09-30 18:15:47 -04:00
|
|
|
|
Value = mapAverageRecoilAmount,
|
2019-07-27 18:46:48 -04:00
|
|
|
|
HitCount = HitCount,
|
|
|
|
|
Type = DetectionType.Recoil
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-06-15 18:37:43 -04:00
|
|
|
|
}
|
2018-04-05 00:38:45 -04:00
|
|
|
|
#endregion
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
2020-09-30 18:15:47 -04:00
|
|
|
|
#region BUTTON
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
shouldIgnoreDetection = false;
|
2023-02-11 22:01:28 -05:00
|
|
|
|
shouldIgnoreDetection = _statsConfiguration.AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Button]
|
2021-06-29 16:02:01 -04:00
|
|
|
|
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
2020-09-30 18:15:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch (KeyNotFoundException)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!shouldIgnoreDetection)
|
|
|
|
|
{
|
|
|
|
|
validButtonHitCount++;
|
|
|
|
|
|
2020-10-02 17:45:55 -04:00
|
|
|
|
double lastDiff = hit.TimeOffset - hit.TimeSinceLastAttack;
|
|
|
|
|
if (validButtonHitCount > 0 && lastDiff <= 0)
|
2020-09-30 18:15:47 -04:00
|
|
|
|
{
|
2020-10-02 17:45:55 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
|
|
|
|
{
|
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
|
|
|
|
Value = lastDiff,
|
|
|
|
|
HitCount = HitCount,
|
|
|
|
|
Type = DetectionType.Button
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-09-30 18:15:47 -04:00
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
2018-04-05 00:38:45 -04:00
|
|
|
|
#region SESSION_RATIOS
|
|
|
|
|
if (Kills >= Thresholds.LowSampleMinKills)
|
|
|
|
|
{
|
2018-05-10 01:34:29 -04:00
|
|
|
|
double marginOfError = Thresholds.GetMarginOfError(HitCount);
|
2018-04-05 00:38:45 -04:00
|
|
|
|
// determine what the max headshot percentage can be for current number of kills
|
2018-05-10 01:34:29 -04:00
|
|
|
|
double lerpAmount = Math.Min(1.0, (HitCount - Thresholds.LowSampleMinKills) / (double)(/*Thresholds.HighSampleMinKills*/ 60 - Thresholds.LowSampleMinKills));
|
2018-04-05 00:38:45 -04:00
|
|
|
|
double maxHeadshotLerpValueForFlag = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(2.0), Thresholds.HeadshotRatioThresholdHighSample(2.0), lerpAmount) + marginOfError;
|
2018-06-02 00:48:10 -04:00
|
|
|
|
double maxHeadshotLerpValueForBan = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(3.5), Thresholds.HeadshotRatioThresholdHighSample(3.5), lerpAmount) + marginOfError;
|
2018-04-05 00:38:45 -04:00
|
|
|
|
// determine what the max bone percentage can be for current number of kills
|
|
|
|
|
double maxBoneRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(2.25), Thresholds.BoneRatioThresholdHighSample(2.25), lerpAmount) + marginOfError;
|
|
|
|
|
double maxBoneRatioLerpValueForBan = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(3.25), Thresholds.BoneRatioThresholdHighSample(3.25), lerpAmount) + marginOfError;
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
2018-04-05 00:38:45 -04:00
|
|
|
|
// calculate headshot ratio
|
2019-06-12 11:27:15 -04:00
|
|
|
|
double currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head].Count + HitLocationCount[IW4Info.HitLocation.helmet].Count + HitLocationCount[IW4Info.HitLocation.neck].Count) / (double)HitCount);
|
2018-04-09 23:33:42 -04:00
|
|
|
|
|
2018-04-05 00:38:45 -04:00
|
|
|
|
// calculate maximum bone
|
2019-06-12 11:27:15 -04:00
|
|
|
|
double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v.Count / (double)HitCount).Max());
|
2019-06-13 20:10:08 -04:00
|
|
|
|
var bone = HitLocationCount.FirstOrDefault(b => b.Value.Count == HitLocationCount.Values.Max(_hit => _hit.Count)).Key;
|
2018-04-09 23:33:42 -04:00
|
|
|
|
|
2018-04-05 00:38:45 -04:00
|
|
|
|
#region HEADSHOT_RATIO
|
|
|
|
|
// flag on headshot
|
|
|
|
|
if (currentHeadshotRatio > maxHeadshotLerpValueForFlag)
|
|
|
|
|
{
|
|
|
|
|
// ban on headshot
|
2018-09-02 23:09:25 -04:00
|
|
|
|
if (currentHeadshotRatio > maxHeadshotLerpValueForBan)
|
2018-04-05 00:38:45 -04:00
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
2018-04-05 00:38:45 -04:00
|
|
|
|
{
|
2019-05-29 17:55:35 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
2018-05-11 00:52:20 -04:00
|
|
|
|
Value = currentHeadshotRatio,
|
|
|
|
|
Location = IW4Info.HitLocation.head,
|
|
|
|
|
HitCount = HitCount,
|
|
|
|
|
Type = DetectionType.Bone
|
2019-06-15 18:37:43 -04:00
|
|
|
|
});
|
2018-04-05 00:38:45 -04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
2018-04-05 00:38:45 -04:00
|
|
|
|
{
|
2019-05-29 17:55:35 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Flag,
|
2018-05-11 00:52:20 -04:00
|
|
|
|
Value = currentHeadshotRatio,
|
|
|
|
|
Location = IW4Info.HitLocation.head,
|
|
|
|
|
HitCount = HitCount,
|
|
|
|
|
Type = DetectionType.Bone
|
2019-06-15 18:37:43 -04:00
|
|
|
|
});
|
2018-04-05 00:38:45 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
2018-03-26 00:51:25 -04:00
|
|
|
|
|
2018-04-05 00:38:45 -04:00
|
|
|
|
#region BONE_RATIO
|
|
|
|
|
// flag on bone ratio
|
|
|
|
|
else if (currentMaxBoneRatio > maxBoneRatioLerpValueForFlag)
|
|
|
|
|
{
|
|
|
|
|
// ban on bone ratio
|
|
|
|
|
if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
|
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
2018-04-05 00:38:45 -04:00
|
|
|
|
{
|
2019-05-29 17:55:35 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
2018-05-11 00:52:20 -04:00
|
|
|
|
Value = currentMaxBoneRatio,
|
|
|
|
|
Location = bone,
|
|
|
|
|
HitCount = HitCount,
|
|
|
|
|
Type = DetectionType.Bone
|
2019-06-15 18:37:43 -04:00
|
|
|
|
});
|
2018-04-05 00:38:45 -04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
2018-04-05 00:38:45 -04:00
|
|
|
|
{
|
2019-05-29 17:55:35 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Flag,
|
2018-05-11 00:52:20 -04:00
|
|
|
|
Value = currentMaxBoneRatio,
|
|
|
|
|
Location = bone,
|
|
|
|
|
HitCount = HitCount,
|
|
|
|
|
Type = DetectionType.Bone
|
2019-06-15 18:37:43 -04:00
|
|
|
|
});
|
2018-04-05 00:38:45 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
2018-04-05 00:38:45 -04:00
|
|
|
|
#region CHEST_ABDOMEN_RATIO_SESSION
|
2019-06-12 11:27:15 -04:00
|
|
|
|
int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper].Count;
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
2020-09-30 18:15:47 -04:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
shouldIgnoreDetection = false; // reset previous value
|
2023-02-11 22:01:28 -05:00
|
|
|
|
shouldIgnoreDetection = _statsConfiguration.AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Chest]
|
2021-06-29 16:02:01 -04:00
|
|
|
|
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
2020-09-30 18:15:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch (KeyNotFoundException)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (chestHits >= Thresholds.MediumSampleMinKills && !shouldIgnoreDetection)
|
2018-04-05 00:38:45 -04:00
|
|
|
|
{
|
2018-05-11 00:52:20 -04:00
|
|
|
|
double marginOfError = Thresholds.GetMarginOfError(chestHits);
|
|
|
|
|
double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
|
2018-04-05 00:38:45 -04:00
|
|
|
|
// 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;
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
2019-06-12 11:27:15 -04:00
|
|
|
|
double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper].Count / (double)HitLocationCount[IW4Info.HitLocation.torso_lower].Count;
|
2018-03-18 22:24:06 -04:00
|
|
|
|
|
2018-04-05 00:38:45 -04:00
|
|
|
|
if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
|
|
|
|
|
{
|
2018-03-26 00:51:25 -04:00
|
|
|
|
|
2019-07-27 18:46:48 -04:00
|
|
|
|
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills * 2)
|
2018-04-05 00:38:45 -04:00
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
2018-04-05 00:38:45 -04:00
|
|
|
|
{
|
2019-05-29 17:55:35 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
2018-05-11 00:52:20 -04:00
|
|
|
|
Value = currentChestAbdomenRatio,
|
|
|
|
|
Location = IW4Info.HitLocation.torso_upper,
|
|
|
|
|
Type = DetectionType.Chest,
|
|
|
|
|
HitCount = chestHits
|
2019-06-15 18:37:43 -04:00
|
|
|
|
});
|
2018-04-05 00:38:45 -04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-06-15 18:37:43 -04:00
|
|
|
|
results.Add(new DetectionPenaltyResult()
|
2018-04-05 00:38:45 -04:00
|
|
|
|
{
|
2019-05-29 17:55:35 -04:00
|
|
|
|
ClientPenalty = EFPenalty.PenaltyType.Flag,
|
2018-05-11 00:52:20 -04:00
|
|
|
|
Value = currentChestAbdomenRatio,
|
|
|
|
|
Location = IW4Info.HitLocation.torso_upper,
|
|
|
|
|
Type = DetectionType.Chest,
|
|
|
|
|
HitCount = chestHits
|
2019-06-15 18:37:43 -04:00
|
|
|
|
});
|
2018-04-05 00:38:45 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
#endregion
|
2018-05-20 22:35:56 -04:00
|
|
|
|
|
2019-09-09 18:40:04 -04:00
|
|
|
|
var snapshot = new EFACSnapshot()
|
2018-05-20 22:35:56 -04:00
|
|
|
|
{
|
2018-10-28 21:47:56 -04:00
|
|
|
|
When = hit.When,
|
2018-06-05 17:31:36 -04:00
|
|
|
|
ClientId = ClientStats.ClientId,
|
2021-06-29 16:02:01 -04:00
|
|
|
|
ServerId = ClientStats.ServerId,
|
2018-06-05 17:31:36 -04:00
|
|
|
|
SessionAngleOffset = AngleDifferenceAverage,
|
2019-06-15 18:37:43 -04:00
|
|
|
|
RecoilOffset = hitRecoilAverage,
|
2020-01-17 18:31:53 -05:00
|
|
|
|
CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalMinutes,
|
2018-06-05 17:31:36 -04:00
|
|
|
|
CurrentStrain = currentStrain,
|
2019-09-09 18:40:04 -04:00
|
|
|
|
CurrentViewAngle = new Vector3(hit.ViewAngles.X, hit.ViewAngles.Y, hit.ViewAngles.Z),
|
2018-06-05 17:31:36 -04:00
|
|
|
|
Hits = HitCount,
|
|
|
|
|
Kills = Kills,
|
|
|
|
|
Deaths = ClientStats.SessionDeaths,
|
2019-09-09 18:40:04 -04:00
|
|
|
|
//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),
|
2018-06-05 17:31:36 -04:00
|
|
|
|
EloRating = ClientStats.EloRating,
|
2018-10-28 21:47:56 -04:00
|
|
|
|
HitLocation = hit.HitLoc,
|
2019-09-09 18:40:04 -04:00
|
|
|
|
LastStrainAngle = new Vector3(Strain.LastAngle.X, Strain.LastAngle.Y, Strain.LastAngle.Z),
|
2018-06-05 17:31:36 -04:00
|
|
|
|
// this is in "meters"
|
2018-10-28 21:47:56 -04:00
|
|
|
|
Distance = hit.Distance,
|
2018-06-05 17:31:36 -04:00
|
|
|
|
SessionScore = ClientStats.SessionScore,
|
2018-10-28 21:47:56 -04:00
|
|
|
|
HitType = hit.DeathType,
|
2020-01-17 18:31:53 -05:00
|
|
|
|
SessionSPM = Math.Round(ClientStats.SessionSPM, 0),
|
2018-06-05 17:31:36 -04:00
|
|
|
|
StrainAngleBetween = Strain.LastDistance,
|
|
|
|
|
TimeSinceLastEvent = (int)Strain.LastDeltaTime,
|
2021-06-29 16:02:01 -04:00
|
|
|
|
WeaponReference = hit.WeaponReference,
|
2021-07-08 22:12:09 -04:00
|
|
|
|
HitLocationReference = hit.GetAdditionalProperty<string>("HitLocationReference"),
|
2019-09-27 16:53:52 -04:00
|
|
|
|
SessionSnapHits = sessionSnapHits,
|
|
|
|
|
SessionAverageSnapValue = sessionAverageSnapAmount
|
2019-09-09 18:40:04 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
snapshot.PredictedViewAngles = hit.AnglesList
|
|
|
|
|
.Select(_angle => new EFACSnapshotVector3()
|
|
|
|
|
{
|
|
|
|
|
Vector = _angle,
|
|
|
|
|
Snapshot = snapshot
|
|
|
|
|
})
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
Tracker.OnChange(snapshot);
|
2018-05-20 22:35:56 -04:00
|
|
|
|
|
2020-02-06 19:35:30 -05:00
|
|
|
|
return results;
|
2018-03-26 00:51:25 -04:00
|
|
|
|
}
|
2020-09-30 18:15:47 -04:00
|
|
|
|
|
|
|
|
|
public void OnMapChange()
|
|
|
|
|
{
|
|
|
|
|
mapAverageRecoilAmount = 0;
|
|
|
|
|
validRecoilHitCount = 0;
|
|
|
|
|
}
|
2018-03-18 22:24:06 -04:00
|
|
|
|
}
|
|
|
|
|
}
|