示例#1
0
        /// <summary>
        /// Process stats for kill event
        /// </summary>
        /// <returns></returns>
        public async Task AddScriptKill(Player attacker, Player victim, int serverId, string map, string hitLoc, string type,
                                        string damage, string weapon, string killOrigin, string deathOrigin)
        {
            await AddStandardKill(attacker, victim);

            if (victim == null)
            {
                Log.WriteError($"[AddScriptKill] Victim is null");
                return;
            }

            var statsSvc        = ContextThreads[serverId];
            var playerDetection = Servers[serverId].PlayerDetections[attacker.ClientNumber];

            var kill = new EFClientKill()
            {
                Active      = true,
                AttackerId  = attacker.ClientId,
                VictimId    = victim.ClientId,
                ServerId    = serverId,
                Map         = ParseEnum <IW4Info.MapName> .Get(map, typeof(IW4Info.MapName)),
                DeathOrigin = Vector3.Parse(deathOrigin),
                KillOrigin  = Vector3.Parse(killOrigin),
                DeathType   = ParseEnum <IW4Info.MeansOfDeath> .Get(type, typeof(IW4Info.MeansOfDeath)),
                Damage      = Int32.Parse(damage),
                HitLoc      = ParseEnum <IW4Info.HitLocation> .Get(hitLoc, typeof(IW4Info.HitLocation)),
                Weapon      = ParseEnum <IW4Info.WeaponName> .Get(weapon, typeof(IW4Info.WeaponName))
            };

            playerDetection.ProcessKill(kill);

            return;

            statsSvc.KillStatsSvc.Insert(kill);
            await statsSvc.KillStatsSvc.SaveChangesAsync();
        }
示例#2
0
        /// <summary>
        /// Process stats for kill event
        /// </summary>
        /// <returns></returns>
        public async Task AddScriptKill(Player attacker, Player victim, int serverId, string map, string hitLoc, string type,
                                        string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads)
        {
            var     statsSvc     = ContextThreads[serverId];
            Vector3 vDeathOrigin = null;
            Vector3 vKillOrigin  = null;

            try
            {
                vDeathOrigin = Vector3.Parse(deathOrigin);
                vKillOrigin  = Vector3.Parse(killOrigin);
            }

            catch (FormatException)
            {
                Log.WriteWarning("Could not parse kill or death origin vector");
                Log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin}");
                await AddStandardKill(attacker, victim);

                return;
            }

            var kill = new EFClientKill()
            {
                Active           = true,
                AttackerId       = attacker.ClientId,
                VictimId         = victim.ClientId,
                ServerId         = serverId,
                Map              = ParseEnum <IW4Info.MapName> .Get(map, typeof(IW4Info.MapName)),
                DeathOrigin      = vDeathOrigin,
                KillOrigin       = vKillOrigin,
                DeathType        = ParseEnum <IW4Info.MeansOfDeath> .Get(type, typeof(IW4Info.MeansOfDeath)),
                Damage           = Int32.Parse(damage),
                HitLoc           = ParseEnum <IW4Info.HitLocation> .Get(hitLoc, typeof(IW4Info.HitLocation)),
                Weapon           = ParseEnum <IW4Info.WeaponName> .Get(weapon, typeof(IW4Info.WeaponName)),
                ViewAngles       = Vector3.Parse(viewAngles).FixIW4Angles(),
                TimeOffset       = Int64.Parse(offset),
                When             = DateTime.UtcNow,
                IsKillstreakKill = isKillstreakKill[0] != '0',
                AdsPercent       = float.Parse(Ads)
            };

            if (kill.DeathType == IW4Info.MeansOfDeath.MOD_SUICIDE &&
                kill.Damage == 100000)
            {
                // suicide by switching teams so let's not count it against them
                return;
            }

            await AddStandardKill(attacker, victim);

            if (kill.IsKillstreakKill)
            {
                return;
            }

            var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId];
            var clientStats     = Servers[serverId].PlayerStats[attacker.ClientId];

            // increment their hit count
            if (kill.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
                kill.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET ||
                kill.DeathType == IW4Info.MeansOfDeath.MOD_HEAD_SHOT)
            {
                clientStats.HitLocations.Single(hl => hl.Location == kill.HitLoc).HitCount += 1;

                statsSvc.ClientStatSvc.Update(clientStats);
                await statsSvc.ClientStatSvc.SaveChangesAsync();
            }

            //statsSvc.KillStatsSvc.Insert(kill);
            //await statsSvc.KillStatsSvc.SaveChangesAsync();

            if (Plugin.Config.Configuration().EnableAntiCheat)
            {
                async Task executePenalty(Cheat.DetectionPenaltyResult penalty)
                {
                    switch (penalty.ClientPenalty)
                    {
                    case Penalty.PenaltyType.Ban:
                        await attacker.Ban("You appear to be cheating", new Player()
                        {
                            ClientId = 1
                        });

                        break;

                    case Penalty.PenaltyType.Flag:
                        if (attacker.Level != Player.Permission.User)
                        {
                            break;
                        }
                        var flagCmd = new CFlag();
                        await flagCmd.ExecuteAsync(new GameEvent(GameEvent.EventType.Flag, $"{(int)penalty.Bone}-{Math.Round(penalty.RatioAmount, 2).ToString()}@{penalty.KillCount}", new Player()
                        {
                            ClientId = 1,
                            Level = Player.Permission.Console,
                            ClientNumber = -1,
                            CurrentServer = attacker.CurrentServer
                        }, attacker, attacker.CurrentServer));

                        break;
                    }
                }

                await executePenalty(clientDetection.ProcessKill(kill));
                await executePenalty(clientDetection.ProcessTotalRatio(clientStats));
            }
        }
示例#3
0
        /// <summary>
        /// Analyze kill and see if performed by a cheater
        /// </summary>
        /// <param name="kill">kill performed by the player</param>
        /// <returns>true if detection reached thresholds, false otherwise</returns>
        public bool ProcessKill(EFClientKill kill)
        {
            if (kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET)
            {
                return(false);
            }

            bool thresholdReached = false;

            HitLocationCount[kill.HitLoc]++;
            Kills++;
            AverageKillTime = (AverageKillTime + (DateTime.UtcNow - LastKill).TotalSeconds) / Kills;

            if (Kills > Thresholds.LowSampleMinKills)
            {
                double marginOfError = Thresholds.GetMarginOfError(Kills);
                // determine what the max headshot percentage can be for current number of kills
                double lerpAmount           = Math.Min(1.0, (Kills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
                double maxHeadshotLerpValue = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample, Thresholds.HeadshotRatioThresholdHighSample, lerpAmount);
                //  determine what the max bone percentage can be for current number of kills
                double maxBoneRatioLerpValue = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample, Thresholds.BoneRatioThresholdHighSample, lerpAmount);
                // calculate headshot ratio
                double headshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet]) / (double)Kills) - marginOfError;
                // calculate maximum bone
                double maximumBoneRatio = (HitLocationCount.Values.Select(v => v / (double)Kills).Max()) - marginOfError;

                if (headshotRatio > maxHeadshotLerpValue)
                {
                    AboveThresholdCount++;
                    Log.WriteDebug("**Maximum Headshot Ratio Reached**");
                    Log.WriteDebug($"ClientId: {kill.AttackerId}");
                    Log.WriteDebug($"**Kills: {Kills}");
                    Log.WriteDebug($"**Ratio {headshotRatio}");
                    Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValue}");
                    var sb = new StringBuilder();
                    foreach (var kvp in HitLocationCount)
                    {
                        sb.Append($"HitLocation: {kvp.Key}     Count: {kvp.Value}");
                    }
                    Log.WriteDebug(sb.ToString());
                    Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
                    thresholdReached = true;
                }

                else if (maximumBoneRatio > maxBoneRatioLerpValue)
                {
                    Log.WriteDebug("**Maximum Bone Ratio Reached**");
                    Log.WriteDebug($"ClientId: {kill.AttackerId}");
                    Log.WriteDebug($"**Kills: {Kills}");
                    Log.WriteDebug($"**Ratio {maximumBoneRatio}");
                    Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValue}");
                    var sb = new StringBuilder();
                    foreach (var kvp in HitLocationCount)
                    {
                        sb.Append($"HitLocation: {kvp.Key}     Count: {kvp.Value}");
                    }
                    Log.WriteDebug(sb.ToString());
                    thresholdReached = true;
                }
            }

            return(thresholdReached);
        }
示例#4
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);
        }
示例#5
0
        /// <summary>
        /// Analyze kill and see if performed by a cheater
        /// </summary>
        /// <param name="kill">kill performed by the player</param>
        /// <returns>true if detection reached thresholds, false otherwise</returns>
        public DetectionPenaltyResult ProcessKill(EFClientKill kill)
        {
            if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
                 kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
                 kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
                kill.HitLoc == IW4Info.HitLocation.none)
            {
                return new DetectionPenaltyResult()
                       {
                           ClientPenalty = Penalty.PenaltyType.Any,
                           RatioAmount   = 0
                       }
            }
            ;

            HitLocationCount[kill.HitLoc]++;
            Kills++;
            AverageKillTime = (AverageKillTime + (DateTime.UtcNow - LastKill).TotalSeconds) / Kills;

            #region VIEWANGLES
            double distance         = Vector3.Distance(kill.KillOrigin, kill.DeathOrigin);
            double x                = kill.KillOrigin.X + distance * Math.Cos(kill.ViewAngles.X.ToRadians()) * Math.Cos(kill.ViewAngles.Y.ToRadians());
            double y                = kill.KillOrigin.Y + (distance * Math.Sin(kill.ViewAngles.X.ToRadians()) * Math.Cos(kill.ViewAngles.Y.ToRadians()));
            double z                = kill.KillOrigin.Z + distance * Math.Sin((360.0f - kill.ViewAngles.Y).ToRadians());
            var    trueVector       = Vector3.Subtract(kill.KillOrigin, kill.DeathOrigin);
            var    calculatedVector = Vector3.Subtract(kill.KillOrigin, new Vector3((float)x, (float)y, (float)z));
            double angle            = trueVector.AngleBetween(calculatedVector);

            if (kill.AdsPercent > 0.5 && kill.Distance > 3)
            {
                var hitLoc = ClientStats.HitLocations
                             .First(hl => hl.Location == kill.HitLoc);

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

                if (double.IsNaN(hitLoc.HitOffsetAverage))
                {
                    Log.WriteWarning("[Detection::ProcessKill] HitOffsetAvgerage NaN");
                    Log.WriteDebug($"{previousAverage}-{hitLoc.HitCount}-{hitLoc}-{newAverage}");
                    hitLoc.HitOffsetAverage = 0f;
                }
            }

            #endregion

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

                // calculate maximum bone
                double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v / (double)Kills).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 > maxHeadshotLerpValueForFlag)
                    {
                        AboveThresholdCount++;
                        Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
                        Log.WriteDebug($"ClientId: {kill.AttackerId}");
                        Log.WriteDebug($"**Kills: {Kills}");
                        Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
                        Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
                        var sb = new StringBuilder();
                        foreach (var kvp in HitLocationCount)
                        {
                            sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
                        }
                        Log.WriteDebug(sb.ToString());
                        Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");

                        return(new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Ban,
                            RatioAmount = currentHeadshotRatio,
                            Bone = IW4Info.HitLocation.head,
                            KillCount = Kills
                        });
                    }
                    else
                    {
                        AboveThresholdCount++;
                        Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
                        Log.WriteDebug($"ClientId: {kill.AttackerId}");
                        Log.WriteDebug($"**Kills: {Kills}");
                        Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
                        Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
                        var sb = new StringBuilder();
                        foreach (var kvp in HitLocationCount)
                        {
                            sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
                        }
                        Log.WriteDebug(sb.ToString());
                        Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");

                        return(new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Flag,
                            RatioAmount = currentHeadshotRatio,
                            Bone = IW4Info.HitLocation.head,
                            KillCount = Kills
                        });
                    }
                }
                #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: {kill.AttackerId}");
                        Log.WriteDebug($"**Kills: {Kills}");
                        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());

                        return(new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Ban,
                            RatioAmount = currentMaxBoneRatio,
                            Bone = bone,
                            KillCount = Kills
                        });
                    }
                    else
                    {
                        Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
                        Log.WriteDebug($"ClientId: {kill.AttackerId}");
                        Log.WriteDebug($"**Kills: {Kills}");
                        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());

                        return(new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Flag,
                            RatioAmount = currentMaxBoneRatio,
                            Bone = bone,
                            KillCount = Kills
                        });
                    }
                }
                #endregion
            }

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

            if (chestKills >= Thresholds.MediumSampleMinKills)
            {
                double marginOfError = Thresholds.GetMarginOfError(chestKills);
                double lerpAmount    = Math.Min(1.0, (chestKills - 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 && chestKills >= Thresholds.MediumSampleMinKills + 30)
                    {
                        Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
                        Log.WriteDebug($"ClientId: {kill.AttackerId}");
                        Log.WriteDebug($"**Chest Kills: {chestKills}");
                        Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
                        Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
                        var sb = new StringBuilder();
                        foreach (var kvp in HitLocationCount)
                        {
                            sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
                        }
                        Log.WriteDebug(sb.ToString());
                        //  Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");

                        return(new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Ban,
                            RatioAmount = currentChestAbdomenRatio,
                            Bone = 0,
                            KillCount = chestKills
                        });
                    }
                    else
                    {
                        Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
                        Log.WriteDebug($"ClientId: {kill.AttackerId}");
                        Log.WriteDebug($"**Chest Kills: {chestKills}");
                        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());
                        // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");

                        return(new DetectionPenaltyResult()
                        {
                            ClientPenalty = Penalty.PenaltyType.Flag,
                            RatioAmount = currentChestAbdomenRatio,
                            Bone = 0,
                            KillCount = chestKills
                        });
                    }
                }
            }
            #endregion
            #endregion
            return(new DetectionPenaltyResult()
            {
                ClientPenalty = Penalty.PenaltyType.Any,
                RatioAmount = 0
            });
        }
示例#6
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,
            });
        }