anticheat tweaks
- reset recoil state on map change - refactor config - remove m21 from chest detection - allow ignored client ids
This commit is contained in:
parent
7f11921757
commit
1f1f4de67a
@ -20,7 +20,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
Offset,
|
||||
Strain,
|
||||
Recoil,
|
||||
Snap
|
||||
Snap,
|
||||
Button
|
||||
};
|
||||
|
||||
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
||||
@ -38,11 +39,12 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
ILogger Log;
|
||||
Strain Strain;
|
||||
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
||||
private double sessionAverageRecoilAmount;
|
||||
private double mapAverageRecoilAmount;
|
||||
private double sessionAverageSnapAmount;
|
||||
private int sessionSnapHits;
|
||||
private EFClientKill lastHit;
|
||||
private int validRecoilHitCount;
|
||||
private int validButtonHitCount;
|
||||
|
||||
private class HitInfo
|
||||
{
|
||||
@ -282,18 +284,30 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
#region RECOIL
|
||||
float hitRecoilAverage = 0;
|
||||
if (!Plugin.Config.Configuration().RecoilessWeapons.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex)))
|
||||
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);
|
||||
sessionAverageRecoilAmount = (sessionAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount;
|
||||
mapAverageRecoilAmount = (mapAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount;
|
||||
|
||||
if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && sessionAverageRecoilAmount == 0)
|
||||
if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && mapAverageRecoilAmount == 0)
|
||||
{
|
||||
results.Add(new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
||||
Value = sessionAverageRecoilAmount,
|
||||
Value = mapAverageRecoilAmount,
|
||||
HitCount = HitCount,
|
||||
Type = DetectionType.Recoil
|
||||
});
|
||||
@ -301,6 +315,37 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
}
|
||||
#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)
|
||||
{
|
||||
@ -384,7 +429,19 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
#region CHEST_ABDOMEN_RATIO_SESSION
|
||||
int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper].Count;
|
||||
|
||||
if (chestHits >= Thresholds.MediumSampleMinKills)
|
||||
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));
|
||||
@ -466,5 +523,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public void OnMapChange()
|
||||
{
|
||||
mapAverageRecoilAmount = 0;
|
||||
validRecoilHitCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
24
Plugins/Stats/Config/AnticheatConfiguration.cs
Normal file
24
Plugins/Stats/Config/AnticheatConfiguration.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||
using static SharedLibraryCore.Server;
|
||||
|
||||
namespace Stats.Config
|
||||
{
|
||||
public class AnticheatConfiguration
|
||||
{
|
||||
public bool Enable { get; set; }
|
||||
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; } = new Dictionary<long, DetectionType[]>();
|
||||
public IList<long> IgnoredClientIds { get; set; } = new List<long>();
|
||||
public IDictionary<Game, IDictionary<DetectionType, string[]>> IgnoredDetectionSpecification{ get; set; } = new Dictionary<Game, IDictionary<DetectionType, string[]>>
|
||||
{
|
||||
{
|
||||
Game.IW4, new Dictionary<DetectionType, string[]>
|
||||
{
|
||||
{ DetectionType.Chest, new[] { "m21.+" } },
|
||||
{ DetectionType.Recoil, new[] { "ranger.*_mp", "model1887.*_mp", ".+shotgun.*_mp" } },
|
||||
{ DetectionType.Button, new[] { ".*akimbo.*" } }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using Stats.Config;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||
|
||||
@ -8,21 +9,41 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
||||
{
|
||||
public class StatsConfiguration : IBaseConfiguration
|
||||
{
|
||||
public bool EnableAntiCheat { get; set; }
|
||||
[Obsolete]
|
||||
public bool? EnableAntiCheat { get; set; }
|
||||
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }
|
||||
public List<StreakMessageConfiguration> DeathstreakMessages { get; set; }
|
||||
public List<string> RecoilessWeapons { get; set; }
|
||||
public int TopPlayersMinPlayTime { get; set; }
|
||||
public bool StoreClientKills { get; set; }
|
||||
public int MostKillsMaxInactivityDays { get; set; } = 30;
|
||||
public int MostKillsClientLimit { get; set; } = 5;
|
||||
public IDictionary<DetectionType, DistributionConfiguration> DetectionDistributions { get; set; }
|
||||
[Obsolete]
|
||||
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; }
|
||||
public AnticheatConfiguration AnticheatConfiguration { get; set; } = new AnticheatConfiguration();
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
public void ApplyMigration()
|
||||
{
|
||||
if (ServerDetectionTypes != null)
|
||||
{
|
||||
AnticheatConfiguration.ServerDetectionTypes = ServerDetectionTypes;
|
||||
}
|
||||
|
||||
ServerDetectionTypes = null;
|
||||
|
||||
if (EnableAntiCheat != null)
|
||||
{
|
||||
AnticheatConfiguration.Enable = EnableAntiCheat.Value;
|
||||
}
|
||||
|
||||
EnableAntiCheat = null;
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public string Name() => "StatsPluginSettings";
|
||||
public IBaseConfiguration Generate()
|
||||
{
|
||||
EnableAntiCheat = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);
|
||||
AnticheatConfiguration.Enable = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);
|
||||
KillstreakMessages = new List<StreakMessageConfiguration>()
|
||||
{
|
||||
new StreakMessageConfiguration(){
|
||||
@ -57,16 +78,9 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
||||
},
|
||||
};
|
||||
|
||||
RecoilessWeapons = new List<string>()
|
||||
{
|
||||
"ranger.*_mp",
|
||||
"model1887.*_mp",
|
||||
".+shotgun.*_mp"
|
||||
};
|
||||
|
||||
TopPlayersMinPlayTime = 3600 * 3;
|
||||
StoreClientKills = false;
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -481,7 +481,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
IsKill = !isDamage,
|
||||
AnglesList = snapshotAngles,
|
||||
IsAlive = isAlive == "1",
|
||||
TimeSinceLastAttack = long.Parse(lastAttackTime)
|
||||
TimeSinceLastAttack = long.Parse(lastAttackTime),
|
||||
GameName = attacker.CurrentServer.GameName
|
||||
};
|
||||
|
||||
if (hit.HitLoc == IW4Info.HitLocation.shield)
|
||||
@ -539,7 +540,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
if (Plugin.Config.Configuration().EnableAntiCheat && !attacker.IsBot && attacker.ClientId != victim.ClientId)
|
||||
if (Plugin.Config.Configuration().AnticheatConfiguration.Enable && !attacker.IsBot && attacker.ClientId != victim.ClientId)
|
||||
{
|
||||
clientDetection.TrackedHits.Add(hit);
|
||||
|
||||
@ -555,10 +556,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
if (oldestHit.IsAlive)
|
||||
{
|
||||
var result = DeterminePenaltyResult(clientDetection.ProcessHit(oldestHit), attacker.CurrentServer.EndPoint);
|
||||
#if !DEBUG
|
||||
await ApplyPenalty(result, attacker);
|
||||
#endif
|
||||
var result = DeterminePenaltyResult(clientDetection.ProcessHit(oldestHit), attacker);
|
||||
|
||||
if (Utilities.IsDevelopment)
|
||||
{
|
||||
await ApplyPenalty(result, attacker);
|
||||
}
|
||||
|
||||
if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any)
|
||||
{
|
||||
@ -594,10 +597,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private DetectionPenaltyResult DeterminePenaltyResult(IEnumerable<DetectionPenaltyResult> results, long serverId)
|
||||
private DetectionPenaltyResult DeterminePenaltyResult(IEnumerable<DetectionPenaltyResult> results, EFClient client)
|
||||
{
|
||||
// allow disabling of certain detection types
|
||||
results = results.Where(_result => ShouldUseDetection(serverId, _result.Type));
|
||||
results = results.Where(_result => ShouldUseDetection(client.CurrentServer, _result.Type, client.ClientId));
|
||||
return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ??
|
||||
results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ??
|
||||
new DetectionPenaltyResult()
|
||||
@ -617,21 +620,24 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldUseDetection(long serverId, DetectionType detectionType)
|
||||
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
|
||||
{
|
||||
var detectionTypes = Plugin.Config.Configuration().ServerDetectionTypes;
|
||||
bool shouldRun = true;
|
||||
var detectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.ServerDetectionTypes;
|
||||
var ignoredClients = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredClientIds;
|
||||
|
||||
if (detectionTypes == null)
|
||||
try
|
||||
{
|
||||
return true;
|
||||
shouldRun &= !detectionTypes[server.EndPoint].Contains(detectionType);
|
||||
}
|
||||
|
||||
if (!detectionTypes.ContainsKey(serverId))
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return detectionTypes[serverId].Contains(detectionType);
|
||||
shouldRun &= !ignoredClients.Any(_clientId => _clientId == clientId);
|
||||
return shouldRun;
|
||||
}
|
||||
|
||||
async Task ApplyPenalty(DetectionPenaltyResult penalty, EFClient attacker)
|
||||
@ -1139,10 +1145,15 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
public void ResetKillstreaks(Server sv)
|
||||
{
|
||||
foreach (var stat in sv.GetClientsAsList()
|
||||
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY)))
|
||||
foreach (var session in sv.GetClientsAsList()
|
||||
.Select(_client => new
|
||||
{
|
||||
stat = _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY),
|
||||
detection = _client.GetAdditionalProperty<Detection>(CLIENT_DETECTIONS_KEY)
|
||||
}))
|
||||
{
|
||||
stat?.StartNewSession();
|
||||
session.stat?.StartNewSession();
|
||||
session.detection?.OnMapChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Collections.Generic;
|
||||
using static SharedLibraryCore.Server;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Models
|
||||
{
|
||||
@ -44,6 +45,8 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
public float AdsPercent { get; set; }
|
||||
[NotMapped]
|
||||
public List<Vector3> AnglesList { get; set; }
|
||||
[NotMapped]
|
||||
public Game GameName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the attacker was alive after last captured angle
|
||||
|
@ -182,8 +182,9 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
if (Config.Configuration() == null)
|
||||
{
|
||||
Config.Set((StatsConfiguration)new StatsConfiguration().Generate());
|
||||
await Config.Save();
|
||||
}
|
||||
Config.Configuration().ApplyMigration();
|
||||
await Config.Save();
|
||||
|
||||
// register the topstats page
|
||||
// todo:generate the URL/Location instead of hardcoding
|
||||
@ -405,7 +406,7 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
return (await _chatQueryHelper.QueryResource(query)).Results;
|
||||
}
|
||||
|
||||
if (Config.Configuration().EnableAntiCheat)
|
||||
if (Config.Configuration().AnticheatConfiguration.Enable)
|
||||
{
|
||||
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, getAnticheatInfo);
|
||||
}
|
||||
@ -496,6 +497,6 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <returns></returns>
|
||||
private bool ShouldOverrideAnticheatSetting(Server s) => Config.Configuration().EnableAntiCheat && s.GameName == Server.Game.IW5;
|
||||
private bool ShouldOverrideAnticheatSetting(Server s) => Config.Configuration().AnticheatConfiguration.Enable && s.GameName == Server.Game.IW5;
|
||||
}
|
||||
}
|
||||
|
@ -772,8 +772,6 @@ namespace SharedLibraryCore.Commands
|
||||
/// </summary>
|
||||
public class ListAdminsCommand : Command
|
||||
{
|
||||
private readonly CommandConfiguration _config;
|
||||
|
||||
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||
{
|
||||
Name = "admins";
|
||||
@ -781,8 +779,6 @@ namespace SharedLibraryCore.Commands
|
||||
Alias = "a";
|
||||
Permission = Permission.User;
|
||||
RequiresTarget = false;
|
||||
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public static string OnlineAdmins(Server S, ITranslationLookup lookup)
|
||||
@ -901,8 +897,6 @@ namespace SharedLibraryCore.Commands
|
||||
/// </summary>
|
||||
public class ListRulesCommands : Command
|
||||
{
|
||||
private readonly CommandConfiguration _config;
|
||||
|
||||
public ListRulesCommands(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||
{
|
||||
Name = "rules";
|
||||
@ -910,8 +904,6 @@ namespace SharedLibraryCore.Commands
|
||||
Alias = "r";
|
||||
Permission = Permission.User;
|
||||
RequiresTarget = false;
|
||||
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
|
Loading…
Reference in New Issue
Block a user