Beispiel #1
0
        /// <summary>
        /// Analyze kill and see if performed by a cheater
        /// </summary>
        /// <param name="hit">kill performed by the player</param>
        /// <returns>true if detection reached thresholds, false otherwise</returns>
        public IEnumerable <DetectionPenaltyResult> ProcessHit(EFClientKill hit)
        {
            var results = new List <DetectionPenaltyResult>();

            if ((hit.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
                 hit.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
                 hit.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
                hit.HitLoc == IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 ||
                // hack: prevents false positives
                (LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50))
            {
                return(new[] { new DetectionPenaltyResult()
                               {
                                   ClientPenalty = EFPenalty.PenaltyType.Any,
                               } });
            }

            LastWeapon = hit.Weapon;

            HitLocationCount[hit.HitLoc].Count++;
            HitCount++;

            if (hit.IsKill)
            {
                Kills++;
            }

            #region SNAP
            if (hit.AnglesList.Count == MIN_ANGLE_COUNT)
            {
                if (lastHit == null)
                {
                    lastHit = hit;
                }

                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)
                {
                    ClientStats.SnapHitCount++;
                    sessionSnapHits++;
                    var    currentSnapDistance = Vector3.SnapDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles);
                    double previousAverage     = ClientStats.AverageSnapValue;
                    ClientStats.AverageSnapValue = (previousAverage * (ClientStats.SnapHitCount - 1) + currentSnapDistance) / ClientStats.SnapHitCount;
                    double previousSessionAverage = sessionAverageSnapAmount;
                    sessionAverageSnapAmount = (previousSessionAverage * (sessionSnapHits - 1) + currentSnapDistance) / sessionSnapHits;
                    lastHit = hit;

                    //var marginOfError = Thresholds.GetMarginOfError(sessionSnapHits);
                    //var marginOfErrorLifetime = Thresholds.GetMarginOfError(ClientStats.SnapHitCount);

                    if (sessionSnapHits >= Thresholds.MediumSampleMinKills &&
                        sessionAverageSnapAmount >= Thresholds.SnapFlagValue /* + marginOfError*/)
                    {
                        results.Add(new DetectionPenaltyResult()
                        {
                            ClientPenalty = EFPenalty.PenaltyType.Flag,
                            Value         = sessionAverageSnapAmount,
                            HitCount      = sessionSnapHits,
                            Type          = DetectionType.Snap
                        });
                    }

                    if (sessionSnapHits >= Thresholds.MediumSampleMinKills &&
                        sessionAverageSnapAmount >= Thresholds.SnapBanValue /* + marginOfError*/)
                    {
                        results.Add(new DetectionPenaltyResult()
                        {
                            ClientPenalty = EFPenalty.PenaltyType.Ban,
                            Value         = sessionAverageSnapAmount,
                            HitCount      = sessionSnapHits,
                            Type          = DetectionType.Snap
                        });
                    }

                    // lifetime
                    if (ClientStats.SnapHitCount >= Thresholds.MediumSampleMinKills * 2 &&
                        ClientStats.AverageSnapValue >= Thresholds.SnapFlagValue /* + marginOfErrorLifetime*/)
                    {
                        results.Add(new DetectionPenaltyResult()
                        {
                            ClientPenalty = EFPenalty.PenaltyType.Flag,
                            Value         = sessionAverageSnapAmount,
                            HitCount      = ClientStats.SnapHitCount,
                            Type          = DetectionType.Snap
                        });
                    }

                    if (ClientStats.SnapHitCount >= Thresholds.MediumSampleMinKills * 2 &&
                        ClientStats.AverageSnapValue >= Thresholds.SnapBanValue /* + marginOfErrorLifetime*/)
                    {
                        results.Add(new DetectionPenaltyResult()
                        {
                            ClientPenalty = EFPenalty.PenaltyType.Ban,
                            Value         = sessionAverageSnapAmount,
                            HitCount      = ClientStats.SnapHitCount,
                            Type          = DetectionType.Snap
                        });
                    }
                }
            }
            #endregion

            #region VIEWANGLES
            int totalUsableAngleCount = hit.AnglesList.Count - 1;
            int angleOffsetIndex      = totalUsableAngleCount / 2;
            if (hit.AnglesList.Count == 5)
            {
                double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[angleOffsetIndex - 1], hit.AnglesList[angleOffsetIndex + 1], hit.ViewAngles);

                // LIFETIME
                var hitLoc = ClientStats.HitLocations
                             .First(hl => hl.Location == hit.HitLoc);

                float  previousAverage = hitLoc.HitOffsetAverage;
                double newAverage      = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount;
                hitLoc.HitOffsetAverage = (float)newAverage;

                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) &&
                    hitLoc.HitCount > 100)
                {
                    results.Add(new DetectionPenaltyResult()
                    {
                        ClientPenalty = EFPenalty.PenaltyType.Ban,
                        Value         = hitLoc.HitOffsetAverage,
                        HitCount      = hitLoc.HitCount,
                        Type          = DetectionType.Offset
                    });
                }

                // SESSION
                var sessionHitLoc = HitLocationCount[hit.HitLoc];
                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)
                                             .Sum(_hit => _hit.Value.Offset * _hit.Value.Count) / totalSessionHits;

                AngleDifferenceAverage = weightedSessionAverage;

                if (weightedSessionAverage > Thresholds.MaxOffset(totalSessionHits) &&
                    totalSessionHits >= (Thresholds.MediumSampleMinKills * 2))
                {
                    Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
                    Log.WriteDebug($"Session Average = {weightedSessionAverage}");
                    Log.WriteDebug($"HitCount = {HitCount}");
                    Log.WriteDebug($"ID = {hit.AttackerId}");

                    results.Add(new DetectionPenaltyResult()
                    {
                        ClientPenalty = EFPenalty.PenaltyType.Ban,
                        Value         = weightedSessionAverage,
                        HitCount      = HitCount,
                        Type          = DetectionType.Offset,
                        Location      = hitLoc.Location
                    });
                }

#if DEBUG
                Log.WriteDebug($"PredictVsReal={realAgainstPredict}");
#endif
            }
            #endregion

            #region STRAIN
            double currentStrain = Strain.GetStrain(hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, LastOffset == 0 ? 50 : (hit.TimeOffset - LastOffset)));
#if DEBUG == true
            Log.WriteDebug($"Current Strain: {currentStrain}");
#endif
            LastOffset = hit.TimeOffset;

            if (currentStrain > ClientStats.MaxStrain)
            {
                ClientStats.MaxStrain = currentStrain;
            }

            // flag
            if (currentStrain > Thresholds.MaxStrainFlag)
            {
                results.Add(new DetectionPenaltyResult()
                {
                    ClientPenalty = EFPenalty.PenaltyType.Flag,
                    Value         = currentStrain,
                    HitCount      = HitCount,
                    Type          = DetectionType.Strain
                });
            }

            // ban
            if (currentStrain > Thresholds.MaxStrainBan &&
                HitCount >= 5)
            {
                results.Add(new DetectionPenaltyResult()
                {
                    ClientPenalty = EFPenalty.PenaltyType.Ban,
                    Value         = currentStrain,
                    HitCount      = HitCount,
                    Type          = DetectionType.Strain
                });
            }
            #endregion

            #region RECOIL
            float hitRecoilAverage      = 0;
            bool  shouldIgnoreDetection = false;
            try
            {
                shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Recoil]
                                        .Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
            }

            catch (KeyNotFoundException)
            {
            }

            if (!shouldIgnoreDetection)
            {
                validRecoilHitCount++;
                hitRecoilAverage       = (hit.AnglesList.Sum(_angle => _angle.Z) + hit.ViewAngles.Z) / (hit.AnglesList.Count + 1);
                mapAverageRecoilAmount = (mapAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount;

                if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && mapAverageRecoilAmount == 0)
                {
                    results.Add(new DetectionPenaltyResult()
                    {
                        ClientPenalty = EFPenalty.PenaltyType.Ban,
                        Value         = mapAverageRecoilAmount,
                        HitCount      = HitCount,
                        Type          = DetectionType.Recoil
                    });
                }
            }
            #endregion

            #region BUTTON
            try
            {
                shouldIgnoreDetection = false;
                shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Button]
                                        .Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
            }

            catch (KeyNotFoundException)
            {
            }

            if (!shouldIgnoreDetection)
            {
                validButtonHitCount++;

                double lastDiff = hit.TimeOffset - hit.TimeSinceLastAttack;
                if (validButtonHitCount > 0 && lastDiff <= 0)
                {
                    results.Add(new DetectionPenaltyResult()
                    {
                        ClientPenalty = EFPenalty.PenaltyType.Ban,
                        Value         = lastDiff,
                        HitCount      = HitCount,
                        Type          = DetectionType.Button
                    });
                }
            }
            #endregion

            #region SESSION_RATIOS
            if (Kills >= Thresholds.LowSampleMinKills)
            {
                double marginOfError = Thresholds.GetMarginOfError(HitCount);
                // determine what the max headshot percentage can be for current number of kills
                double lerpAmount = Math.Min(1.0, (HitCount - Thresholds.LowSampleMinKills) / (double)(/*Thresholds.HighSampleMinKills*/ 60 - Thresholds.LowSampleMinKills));
                double maxHeadshotLerpValueForFlag = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(2.0), Thresholds.HeadshotRatioThresholdHighSample(2.0), lerpAmount) + marginOfError;
                double maxHeadshotLerpValueForBan  = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(3.5), Thresholds.HeadshotRatioThresholdHighSample(3.5), lerpAmount) + marginOfError;
                //  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;

                // calculate headshot ratio
                double currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head].Count + HitLocationCount[IW4Info.HitLocation.helmet].Count + HitLocationCount[IW4Info.HitLocation.neck].Count) / (double)HitCount);

                // calculate maximum bone
                double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v.Count / (double)HitCount).Max());
                var    bone = HitLocationCount.FirstOrDefault(b => b.Value.Count == HitLocationCount.Values.Max(_hit => _hit.Count)).Key;

                #region HEADSHOT_RATIO
                // flag on headshot
                if (currentHeadshotRatio > maxHeadshotLerpValueForFlag)
                {
                    // ban on headshot
                    if (currentHeadshotRatio > maxHeadshotLerpValueForBan)
                    {
                        results.Add(new DetectionPenaltyResult()
                        {
                            ClientPenalty = EFPenalty.PenaltyType.Ban,
                            Value         = currentHeadshotRatio,
                            Location      = IW4Info.HitLocation.head,
                            HitCount      = HitCount,
                            Type          = DetectionType.Bone
                        });
                    }
                    else
                    {
                        results.Add(new DetectionPenaltyResult()
                        {
                            ClientPenalty = EFPenalty.PenaltyType.Flag,
                            Value         = currentHeadshotRatio,
                            Location      = IW4Info.HitLocation.head,
                            HitCount      = HitCount,
                            Type          = DetectionType.Bone
                        });
                    }
                }
                #endregion

                #region BONE_RATIO
                // flag on bone ratio
                else if (currentMaxBoneRatio > maxBoneRatioLerpValueForFlag)
                {
                    // ban on bone ratio
                    if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
                    {
                        results.Add(new DetectionPenaltyResult()
                        {
                            ClientPenalty = EFPenalty.PenaltyType.Ban,
                            Value         = currentMaxBoneRatio,
                            Location      = bone,
                            HitCount      = HitCount,
                            Type          = DetectionType.Bone
                        });
                    }
                    else
                    {
                        results.Add(new DetectionPenaltyResult()
                        {
                            ClientPenalty = EFPenalty.PenaltyType.Flag,
                            Value         = currentMaxBoneRatio,
                            Location      = bone,
                            HitCount      = HitCount,
                            Type          = DetectionType.Bone
                        });
                    }
                }
                #endregion
            }

            #region CHEST_ABDOMEN_RATIO_SESSION
            int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper].Count;

            try
            {
                shouldIgnoreDetection = false; // reset previous value
                shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Chest]
                                        .Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
            }

            catch (KeyNotFoundException)
            {
            }

            if (chestHits >= Thresholds.MediumSampleMinKills && !shouldIgnoreDetection)
            {
                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;

                double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper].Count / (double)HitLocationCount[IW4Info.HitLocation.torso_lower].Count;

                if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
                {
                    if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills * 2)
                    {
                        results.Add(new DetectionPenaltyResult()
                        {
                            ClientPenalty = EFPenalty.PenaltyType.Ban,
                            Value         = currentChestAbdomenRatio,
                            Location      = IW4Info.HitLocation.torso_upper,
                            Type          = DetectionType.Chest,
                            HitCount      = chestHits
                        });
                    }
                    else
                    {
                        results.Add(new DetectionPenaltyResult()
                        {
                            ClientPenalty = EFPenalty.PenaltyType.Flag,
                            Value         = currentChestAbdomenRatio,
                            Location      = IW4Info.HitLocation.torso_upper,
                            Type          = DetectionType.Chest,
                            HitCount      = chestHits
                        });
                    }
                }
            }
            #endregion
            #endregion

            var snapshot = new EFACSnapshot()
            {
                When                 = hit.When,
                ClientId             = ClientStats.ClientId,
                SessionAngleOffset   = AngleDifferenceAverage,
                RecoilOffset         = hitRecoilAverage,
                CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalMinutes,
                CurrentStrain        = currentStrain,
                CurrentViewAngle     = new Vector3(hit.ViewAngles.X, hit.ViewAngles.Y, hit.ViewAngles.Z),
                Hits                 = HitCount,
                Kills                = Kills,
                Deaths               = ClientStats.SessionDeaths,
                //todo[9.1.19]: why does this cause unique failure?
                HitDestination  = new Vector3(hit.DeathOrigin.X, hit.DeathOrigin.Y, hit.DeathOrigin.Z),
                HitOrigin       = new Vector3(hit.KillOrigin.X, hit.KillOrigin.Y, hit.KillOrigin.Z),
                EloRating       = ClientStats.EloRating,
                HitLocation     = hit.HitLoc,
                LastStrainAngle = new Vector3(Strain.LastAngle.X, Strain.LastAngle.Y, Strain.LastAngle.Z),
                // this is in "meters"
                Distance                = hit.Distance,
                SessionScore            = ClientStats.SessionScore,
                HitType                 = hit.DeathType,
                SessionSPM              = Math.Round(ClientStats.SessionSPM, 0),
                StrainAngleBetween      = Strain.LastDistance,
                TimeSinceLastEvent      = (int)Strain.LastDeltaTime,
                WeaponId                = hit.Weapon,
                SessionSnapHits         = sessionSnapHits,
                SessionAverageSnapValue = sessionAverageSnapAmount
            };

            snapshot.PredictedViewAngles = hit.AnglesList
                                           .Select(_angle => new EFACSnapshotVector3()
            {
                Vector   = _angle,
                Snapshot = snapshot
            })
                                           .ToList();

            Tracker.OnChange(snapshot);

            return(results);
        }
Beispiel #2
0
        /// <summary>
        /// Analyze kill and see if performed by a cheater
        /// </summary>
        /// <param name="hit">kill performed by the player</param>
        /// <returns>true if detection reached thresholds, false otherwise</returns>
        public DetectionPenaltyResult ProcessHit(EFClientKill hit, bool isDamage)
        {
            if ((hit.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
                 hit.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
                 hit.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
                hit.HitLoc == IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 ||
                // hack: prevents false positives
                (LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50))
            {
                return(new DetectionPenaltyResult()
                {
                    ClientPenalty = Penalty.PenaltyType.Any,
                });
            }

            DetectionPenaltyResult result = null;

            LastWeapon = hit.Weapon;

            HitLocationCount[hit.HitLoc]++;
            HitCount++;

            if (!isDamage)
            {
                Kills++;
            }

            #region VIEWANGLES
            if (hit.AnglesList.Count >= 2)
            {
                double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles);

                // LIFETIME
                var hitLoc = ClientStats.HitLocations
                             .First(hl => hl.Location == hit.HitLoc);

                float  previousAverage = hitLoc.HitOffsetAverage;
                double newAverage      = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount;
                hitLoc.HitOffsetAverage = (float)newAverage;

                if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset(hitLoc.HitCount) &&
                    hitLoc.HitCount > 100)
                {
                    //Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
                    //Log.WriteDebug($"Lifetime Average = {newAverage}");
                    //Log.WriteDebug($"Bone = {hitLoc.Location}");
                    //Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
                    //Log.WriteDebug($"ID = {hit.AttackerId}");

                    result = new DetectionPenaltyResult()
                    {
                        ClientPenalty = Penalty.PenaltyType.Ban,
                        Value         = hitLoc.HitOffsetAverage,
                        HitCount      = hitLoc.HitCount,
                        Type          = DetectionType.Offset
                    };
                }

                // SESSION
                double sessAverage = (AngleDifferenceAverage * (HitCount - 1) + realAgainstPredict) / HitCount;
                AngleDifferenceAverage = sessAverage;

                if (sessAverage > Thresholds.MaxOffset(HitCount) &&
                    HitCount > 30)
                {
                    //Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
                    //Log.WriteDebug($"Session Average = {sessAverage}");
                    //Log.WriteDebug($"HitCount = {HitCount}");
                    //Log.WriteDebug($"ID = {hit.AttackerId}");

                    result = new DetectionPenaltyResult()
                    {
                        ClientPenalty = Penalty.PenaltyType.Ban,
                        Value         = sessAverage,
                        HitCount      = HitCount,
                        Type          = DetectionType.Offset,
                        Location      = hitLoc.Location
                    };
                }

#if DEBUG
                Log.WriteDebug($"PredictVsReal={realAgainstPredict}");
#endif
            }

            double currentStrain = Strain.GetStrain(isDamage, hit.Damage, hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, hit.TimeOffset - LastOffset));
#if DEBUG == true
            Log.WriteDebug($"Current Strain: {currentStrain}");
#endif
            LastOffset = hit.TimeOffset;

            if (currentStrain > ClientStats.MaxStrain)
            {
                ClientStats.MaxStrain = currentStrain;
            }

            // flag
            if (currentStrain > Thresholds.MaxStrainFlag)
            {
                result = new DetectionPenaltyResult()
                {
                    ClientPenalty = Penalty.PenaltyType.Flag,
                    Value         = currentStrain,
                    HitCount      = HitCount,
                    Type          = DetectionType.Strain
                };
            }

            // ban
            if (currentStrain > Thresholds.MaxStrainBan &&
                HitCount >= 5)
            {
                result = new DetectionPenaltyResult()
                {
                    ClientPenalty = Penalty.PenaltyType.Ban,
                    Value         = currentStrain,
                    HitCount      = HitCount,
                    Type          = DetectionType.Strain
                };
            }
            #endregion

            #region SESSION_RATIOS
            if (Kills >= Thresholds.LowSampleMinKills)
            {
                double marginOfError = Thresholds.GetMarginOfError(HitCount);
                // determine what the max headshot percentage can be for current number of kills
                double lerpAmount = Math.Min(1.0, (HitCount - Thresholds.LowSampleMinKills) / (double)(/*Thresholds.HighSampleMinKills*/ 60 - Thresholds.LowSampleMinKills));
                double maxHeadshotLerpValueForFlag = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(2.0), Thresholds.HeadshotRatioThresholdHighSample(2.0), lerpAmount) + marginOfError;
                double maxHeadshotLerpValueForBan  = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(3.5), Thresholds.HeadshotRatioThresholdHighSample(3.5), lerpAmount) + marginOfError;
                //  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;

                // calculate headshot ratio
                double currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet] + HitLocationCount[IW4Info.HitLocation.neck]) / (double)HitCount);

                // calculate maximum bone
                double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v / (double)HitCount).Max());
                var    bone = HitLocationCount.FirstOrDefault(b => b.Value == HitLocationCount.Values.Max()).Key;

                #region HEADSHOT_RATIO
                // flag on headshot
                if (currentHeadshotRatio > maxHeadshotLerpValueForFlag)
                {
                    // ban on headshot
                    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());

                        result = new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Ban,
                            Value         = currentHeadshotRatio,
                            Location      = IW4Info.HitLocation.head,
                            HitCount      = HitCount,
                            Type          = DetectionType.Bone
                        };
                    }
                    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());

                        result = new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Flag,
                            Value         = currentHeadshotRatio,
                            Location      = IW4Info.HitLocation.head,
                            HitCount      = HitCount,
                            Type          = DetectionType.Bone
                        };
                    }
                }
                #endregion

                #region BONE_RATIO
                // flag on bone ratio
                else if (currentMaxBoneRatio > maxBoneRatioLerpValueForFlag)
                {
                    // ban on bone ratio
                    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());

                        result = new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Ban,
                            Value         = currentMaxBoneRatio,
                            Location      = bone,
                            HitCount      = HitCount,
                            Type          = DetectionType.Bone
                        };
                    }
                    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());

                        result = new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Flag,
                            Value         = currentMaxBoneRatio,
                            Location      = bone,
                            HitCount      = HitCount,
                            Type          = DetectionType.Bone
                        };
                    }
                }
                #endregion
            }

            #region CHEST_ABDOMEN_RATIO_SESSION
            int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper];

            if (chestHits >= Thresholds.MediumSampleMinKills)
            {
                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;

                double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper] / (double)HitLocationCount[IW4Info.HitLocation.torso_lower];

                if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
                {
                    if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills + 30)
                    {
                        //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());

                        result = new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Ban,
                            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: {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());

                        result = new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Flag,
                            Value         = currentChestAbdomenRatio,
                            Location      = IW4Info.HitLocation.torso_upper,
                            Type          = DetectionType.Chest,
                            HitCount      = chestHits
                        };
                    }
                }
            }
            #endregion
            #endregion

            Tracker.OnChange(new EFACSnapshot()
            {
                When                 = hit.When,
                ClientId             = ClientStats.ClientId,
                SessionAngleOffset   = AngleDifferenceAverage,
                CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds,
                CurrentStrain        = currentStrain,
                CurrentViewAngle     = hit.ViewAngles,
                Hits                 = HitCount,
                Kills                = Kills,
                Deaths               = ClientStats.SessionDeaths,
                HitDestinationId     = hit.DeathOrigin.Vector3Id,
                HitDestination       = hit.DeathOrigin,
                HitOriginId          = hit.KillOrigin.Vector3Id,
                HitOrigin            = hit.KillOrigin,
                EloRating            = ClientStats.EloRating,
                HitLocation          = hit.HitLoc,
                LastStrainAngle      = Strain.LastAngle,
                PredictedViewAngles  = hit.AnglesList,
                // this is in "meters"
                Distance           = hit.Distance,
                SessionScore       = ClientStats.SessionScore,
                HitType            = hit.DeathType,
                SessionSPM         = ClientStats.SessionSPM,
                StrainAngleBetween = Strain.LastDistance,
                TimeSinceLastEvent = (int)Strain.LastDeltaTime,
                WeaponId           = hit.Weapon
            });

            return(result ?? new DetectionPenaltyResult()
            {
                ClientPenalty = Penalty.PenaltyType.Any,
            });
        }