1
0
mirror of https://github.com/RaidMax/IW4M-Admin.git synced 2025-06-28 08:00:14 -05:00

kick clients with default name or an inuse name

fixed regular expression not being escaped when matching names
fixed reset stats
fixed duplicate kills
This commit is contained in:
RaidMax
2018-02-26 22:24:19 -06:00
parent 0496aa7f21
commit 2a6878351b
14 changed files with 190 additions and 76 deletions

View File

@ -0,0 +1,91 @@
using SharedLibrary.Interfaces;
using StatsPlugin.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StatsPlugin.Cheat
{
class Detection
{
int Kills;
int AboveThresholdCount;
double AverageKillTime;
Dictionary<IW4Info.HitLocation, int> HitLocationCount;
DateTime LastKill;
ILogger Log;
public Detection(ILogger log)
{
Log = log;
HitLocationCount = new Dictionary<IW4Info.HitLocation, int>();
foreach (var loc in Enum.GetValues(typeof(IW4Info.HitLocation)))
HitLocationCount.Add((IW4Info.HitLocation)loc, 0);
LastKill = DateTime.UtcNow;
}
/// <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;
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StatsPlugin.Cheat
{
class Thresholds
{
private const double Deviations = 3.33;
public const double HeadshotRatioThresholdLowSample = HeadshotRatioStandardDeviationLowSample * Deviations + HeadshotRatioMean;
public const double HeadshotRatioThresholdHighSample = HeadshotRatioStandardDeviationHighSample * Deviations + HeadshotRatioMean;
public const double HeadshotRatioStandardDeviationLowSample = 0.1769994181;
public const double HeadshotRatioStandardDeviationHighSample = 0.03924263235;
//public const double HeadshotRatioMean = 0.09587712258;
public const double HeadshotRatioMean = 0.222;
public const double BoneRatioThresholdLowSample = BoneRatioStandardDeviationLowSample * Deviations + BoneRatioMean;
public const double BoneRatioThresholdHighSample = BoneRatioStandardDeviationHighSample * Deviations + BoneRatioMean;
public const double BoneRatioStandardDeviationLowSample = 0.1324612879;
public const double BoneRatioStandardDeviationHighSample = 0.0515753935;
public const double BoneRatioMean = 0.3982907516;
public const int LowSampleMinKills = 15;
public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2;
public static double GetMarginOfError(int numKills) => 1.645 /(2 * Math.Sqrt(numKills));
public static double Lerp(double v1, double v2, double amount)
{
return v1 + (v2 - v1) * amount;
}
}
}

View File

@ -19,7 +19,7 @@ namespace StatsPlugin.Commands
if (E.Origin.ClientNumber >= 0)
{
var svc = new SharedLibrary.Services.GenericRepository<EFClientStatistics>();
int serverId = E.Origin.GetHashCode();
int serverId = E.Owner.GetHashCode();
var stats = svc.Find(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId).First();
stats.Deaths = 0;

View File

@ -1,4 +1,5 @@
using SharedLibrary;
using StatsPlugin.Cheat;
using StatsPlugin.Models;
using System;
using System.Collections.Generic;
@ -8,15 +9,16 @@ using System.Threading.Tasks;
namespace StatsPlugin.Helpers
{
public class ServerStats
{
class ServerStats {
public Dictionary<int, EFClientStatistics> PlayerStats { get; set; }
public Dictionary<int, Detection> PlayerDetections { get; set; }
public EFServerStatistics ServerStatistics { get; private set; }
public EFServer Server { get; private set; }
public ServerStats(EFServer sv, EFServerStatistics st)
{
PlayerStats = new Dictionary<int, EFClientStatistics>();
PlayerDetections = new Dictionary<int, Detection>();
ServerStatistics = st;
Server = sv;
}

View File

@ -123,6 +123,16 @@ namespace StatsPlugin.Helpers
}
playerStats.Add(pl.ClientNumber, clientStats);
}
var detectionStats = Servers[serverId].PlayerDetections;
lock (detectionStats)
{
if (detectionStats.ContainsKey(pl.ClientNumber))
detectionStats.Remove(pl.ClientNumber);
detectionStats.Add(pl.ClientNumber, new Cheat.Detection(Log));
}
return clientStats;
}
@ -135,6 +145,7 @@ namespace StatsPlugin.Helpers
{
int serverId = pl.CurrentServer.GetHashCode();
var playerStats = Servers[serverId].PlayerStats;
var detectionStats = Servers[serverId].PlayerDetections;
var serverStats = Servers[serverId].ServerStatistics;
var statsSvc = ContextThreads[serverId];
@ -143,6 +154,8 @@ namespace StatsPlugin.Helpers
// remove the client from the stats dictionary as they're leaving
lock (playerStats)
playerStats.Remove(pl.ClientNumber);
lock (detectionStats)
detectionStats.Remove(pl.ClientNumber);
// sync their stats before they leave
UpdateStats(clientStats);
@ -163,8 +176,9 @@ namespace StatsPlugin.Helpers
{
await AddStandardKill(attacker, victim);
return;
var statsSvc = ContextThreads[serverId];
var playerDetection = Servers[serverId].PlayerDetections[attacker.ClientNumber];
var kill = new EFClientKill()
{
Active = true,
@ -180,6 +194,10 @@ namespace StatsPlugin.Helpers
Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName))
};
playerDetection.ProcessKill(kill);
return;
statsSvc.KillStatsSvc.Insert(kill);
await statsSvc.KillStatsSvc.SaveChangesAsync();
}

View File

@ -41,7 +41,7 @@ namespace StatsPlugin
await Manager.RemovePlayer(E.Origin);
break;
case Event.GType.Say:
if (E.Data != string.Empty && E.Data.Trim().Length > 0 && E.Data[0] != '!')
if (E.Data != string.Empty && E.Data.Trim().Length > 0 && E.Data.Trim()[0] != '!')
await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data);
break;
case Event.GType.MapChange:
@ -69,9 +69,9 @@ namespace StatsPlugin
break;
case Event.GType.Kill:
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 9 && killInfo[0].Contains("ScriptKill"))
if (killInfo.Length >= 9 && killInfo[0].Contains("ScriptKill") && E.Owner.CustomCallback)
await Manager.AddScriptKill(E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4]);
else
else if (!E.Owner.CustomCallback)
await Manager.AddStandardKill(E.Origin, E.Target);
break;
case Event.GType.Death:

View File

@ -118,6 +118,8 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Cheat\Detection.cs" />
<Compile Include="Cheat\Thresholds.cs" />
<Compile Include="Commands\ResetStats.cs" />
<Compile Include="Commands\TopStats.cs" />
<Compile Include="Commands\ViewStats.cs" />