small updates to stat handling

various little tweaks
This commit is contained in:
RaidMax 2019-09-09 17:37:57 -05:00
parent 58a73e581f
commit 198f596ab3
12 changed files with 248 additions and 227 deletions

View File

@ -72,6 +72,7 @@ namespace IW4MAdmin.Application.IO
foreach (var ev in events) foreach (var ev in events)
{ {
_server.Manager.GetEventHandler().AddEvent(ev); _server.Manager.GetEventHandler().AddEvent(ev);
await ev.WaitAsync(Utilities.DefaultCommandTimeout, ev.Owner.Manager.CancellationToken);
} }
previousFileSize = fileSize; previousFileSize = fileSize;

View File

@ -36,14 +36,15 @@ namespace IW4MAdmin.Application.IO
List<string> logLines = new List<string>(); List<string> logLines = new List<string>();
// open the file as a stream // open the file as a stream
using (var rd = new StreamReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType)) using (FileStream fs = new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{ {
char[] buff = new char[fileSizeDiff]; byte[] buff = new byte[fileSizeDiff];
rd.BaseStream.Seek(startPosition, SeekOrigin.Begin); fs.Seek(startPosition, SeekOrigin.Begin);
await rd.ReadAsync(buff, 0, (int)fileSizeDiff); await fs.ReadAsync(buff, 0, (int)fileSizeDiff, server.Manager.CancellationToken);
var stringBuilder = new StringBuilder(); var stringBuilder = new StringBuilder();
foreach (char c in buff) char[] charBuff = Utilities.EncodingType.GetChars(buff);
foreach (char c in charBuff)
{ {
if (c == '\n') if (c == '\n')
{ {

View File

@ -1,7 +1,6 @@
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;

View File

@ -1,4 +1,5 @@
using IW4MAdmin.Plugins.Stats.Models; using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
@ -22,7 +23,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
}; };
public ChangeTracking<EFACSnapshot> Tracker { get; private set; } public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
public const int MAX_TRACKED_HIT_COUNT = 10; public const int MIN_HITS_TO_RUN_DETECTION = 5;
private const int MIN_ANGLE_COUNT = 5;
public List<EFClientKill> TrackedHits { get; set; } public List<EFClientKill> TrackedHits { get; set; }
int Kills; int Kills;
@ -36,6 +38,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Strain Strain; Strain Strain;
readonly DateTime ConnectionTime = DateTime.UtcNow; readonly DateTime ConnectionTime = DateTime.UtcNow;
private double sessionAverageRecoilAmount; private double sessionAverageRecoilAmount;
private EFClientKill lastHit;
private class HitInfo private class HitInfo
{ {
@ -344,7 +347,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
#endregion #endregion
#endregion #endregion
Tracker.OnChange(new EFACSnapshot() var snapshot = new EFACSnapshot()
{ {
When = hit.When, When = hit.When,
ClientId = ClientStats.ClientId, ClientId = ClientStats.ClientId,
@ -372,7 +375,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
StrainAngleBetween = Strain.LastDistance, StrainAngleBetween = Strain.LastDistance,
TimeSinceLastEvent = (int)Strain.LastDeltaTime, TimeSinceLastEvent = (int)Strain.LastDeltaTime,
WeaponId = hit.Weapon WeaponId = hit.Weapon
}); };
Tracker.OnChange(snapshot);
return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ?? return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ??
results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ?? results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ??

View File

@ -44,25 +44,45 @@ namespace IW4MAdmin.Plugins.Stats.Commands
long serverId = StatManager.GetIdForServer(E.Owner); long serverId = StatManager.GetIdForServer(E.Owner);
using (var ctx = new DatabaseContext(disableTracking: true))
{
if (E.Target != null)
{
int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId)); if (E.Target != null)
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}"; {
int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Target)))
{
pStats = Plugin.Manager.GetClientStats(E.Target.ClientId, serverId);
} }
else else
{ {
int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId); using (var ctx = new DatabaseContext(true))
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}"; {
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId));
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync((c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId))); }
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
} }
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
}
else
{
int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Origin)))
{
pStats = Plugin.Manager.GetClientStats(E.Origin.ClientId, serverId);
}
else
{
using (var ctx = new DatabaseContext(true))
{
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId));
}
}
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
} }
if (E.Message.IsBroadcastCommand()) if (E.Message.IsBroadcastCommand())

View File

@ -2,6 +2,7 @@
using IW4MAdmin.Plugins.Stats.Models; using IW4MAdmin.Plugins.Stats.Models;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace IW4MAdmin.Plugins.Stats.Helpers namespace IW4MAdmin.Plugins.Stats.Helpers
@ -9,6 +10,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
class ServerStats { class ServerStats {
public ConcurrentDictionary<int, EFClientStatistics> PlayerStats { get; set; } public ConcurrentDictionary<int, EFClientStatistics> PlayerStats { get; set; }
public ConcurrentDictionary<int, Detection> PlayerDetections { get; set; } public ConcurrentDictionary<int, Detection> PlayerDetections { get; set; }
public IList<EFClientKill> HitCache { get; private set; }
public EFServerStatistics ServerStatistics { get; private set; } public EFServerStatistics ServerStatistics { get; private set; }
public EFServer Server { get; private set; } public EFServer Server { get; private set; }
public bool IsTeamBased { get; set; } public bool IsTeamBased { get; set; }
@ -17,6 +19,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
PlayerStats = new ConcurrentDictionary<int, EFClientStatistics>(); PlayerStats = new ConcurrentDictionary<int, EFClientStatistics>();
PlayerDetections = new ConcurrentDictionary<int, Detection>(); PlayerDetections = new ConcurrentDictionary<int, Detection>();
HitCache = new List<EFClientKill>();
ServerStatistics = st; ServerStatistics = st;
Server = sv; Server = sv;
} }

View File

@ -19,20 +19,15 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
public class StatManager public class StatManager
{ {
private const int MAX_CACHED_HITS = 100;
private readonly ConcurrentDictionary<long, ServerStats> _servers; private readonly ConcurrentDictionary<long, ServerStats> _servers;
private readonly ILogger _log; private readonly ILogger _log;
private static List<EFServer> serverModels; private static List<EFServer> serverModels;
private readonly SemaphoreSlim OnProcessingPenalty;
private readonly SemaphoreSlim OnProcessingSensitive;
private readonly List<EFClientKill> _hitCache;
public StatManager(IManager mgr) public StatManager(IManager mgr)
{ {
_servers = new ConcurrentDictionary<long, ServerStats>(); _servers = new ConcurrentDictionary<long, ServerStats>();
_hitCache = new List<EFClientKill>();
_log = mgr.GetLogger(0); _log = mgr.GetLogger(0);
OnProcessingPenalty = new SemaphoreSlim(1, 1);
OnProcessingSensitive = new SemaphoreSlim(1, 1);
} }
private void SetupServerIds() private void SetupServerIds()
@ -143,6 +138,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var iqStatsInfo = (from stat in context.Set<EFClientStatistics>() var iqStatsInfo = (from stat in context.Set<EFClientStatistics>()
where clientIds.Contains(stat.ClientId) where clientIds.Contains(stat.ClientId)
where stat.Kills > 0 || stat.Deaths > 0 where stat.Kills > 0 || stat.Deaths > 0
where serverId == null ? true : stat.ServerId == serverId
group stat by stat.ClientId into s group stat by stat.ClientId into s
select new select new
{ {
@ -150,7 +146,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Kills = s.Sum(c => c.Kills), Kills = s.Sum(c => c.Kills),
Deaths = s.Sum(c => c.Deaths), Deaths = s.Sum(c => c.Deaths),
KDR = s.Sum(c => (c.Kills / (double)(c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) / s.Sum(c => c.TimePlayed), KDR = s.Sum(c => (c.Kills / (double)(c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) / s.Sum(c => c.TimePlayed),
TotalTimePlayed = s.Sum(c => c.TimePlayed) TotalTimePlayed = s.Sum(c => c.TimePlayed),
}); });
#if DEBUG == true #if DEBUG == true
@ -275,8 +271,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <returns>EFClientStatistic of specified player</returns> /// <returns>EFClientStatistic of specified player</returns>
public async Task<EFClientStatistics> AddPlayer(EFClient pl) public async Task<EFClientStatistics> AddPlayer(EFClient pl)
{ {
await OnProcessingSensitive.WaitAsync();
try try
{ {
long serverId = GetIdForServer(pl.CurrentServer); long serverId = GetIdForServer(pl.CurrentServer);
@ -396,11 +390,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
_log.WriteDebug(ex.GetExceptionInfo()); _log.WriteDebug(ex.GetExceptionInfo());
} }
finally
{
OnProcessingSensitive.Release(1);
}
return null; return null;
} }
@ -466,166 +455,144 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Vector3 vDeathOrigin = null; Vector3 vDeathOrigin = null;
Vector3 vKillOrigin = null; Vector3 vKillOrigin = null;
Vector3 vViewAngles = null; Vector3 vViewAngles = null;
SemaphoreSlim waiter = null;
try try
{ {
vDeathOrigin = Vector3.Parse(deathOrigin); try
vKillOrigin = Vector3.Parse(killOrigin);
vViewAngles = Vector3.Parse(viewAngles).FixIW4Angles();
}
catch (FormatException)
{
_log.WriteWarning("Could not parse kill or death origin or viewangle vectors");
_log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles}");
await AddStandardKill(attacker, victim);
return;
}
var snapshotAngles = new List<Vector3>();
try
{
foreach (string angle in snapAngles.Split(':', StringSplitOptions.RemoveEmptyEntries))
{ {
snapshotAngles.Add(Vector3.Parse(angle).FixIW4Angles()); vDeathOrigin = Vector3.Parse(deathOrigin);
vKillOrigin = Vector3.Parse(killOrigin);
vViewAngles = Vector3.Parse(viewAngles).FixIW4Angles();
} }
}
catch (FormatException) catch (FormatException)
{ {
_log.WriteWarning("Could not parse snapshot angles"); _log.WriteWarning("Could not parse kill or death origin or viewangle vectors");
return; _log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles}");
} await AddStandardKill(attacker, victim);
return;
}
var hit = new EFClientKill() var snapshotAngles = new List<Vector3>();
{
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 = int.Parse(damage),
HitLoc = ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)),
Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName)),
ViewAngles = vViewAngles,
TimeOffset = long.Parse(offset),
When = time,
IsKillstreakKill = isKillstreakKill[0] != '0',
AdsPercent = float.Parse(Ads, System.Globalization.CultureInfo.InvariantCulture),
Fraction = double.Parse(fraction, System.Globalization.CultureInfo.InvariantCulture),
VisibilityPercentage = double.Parse(visibilityPercentage, System.Globalization.CultureInfo.InvariantCulture),
IsKill = !isDamage,
AnglesList = snapshotAngles
};
if (hit.HitLoc == IW4Info.HitLocation.shield) try
{ {
// we don't care about shield hits foreach (string angle in snapAngles.Split(':', StringSplitOptions.RemoveEmptyEntries))
return; {
} snapshotAngles.Add(Vector3.Parse(angle).FixIW4Angles());
}
}
if (!isDamage) catch (FormatException)
{ {
await AddStandardKill(attacker, victim); _log.WriteWarning("Could not parse snapshot angles");
} return;
}
// incase the add player event get delayed var hit = new EFClientKill()
if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) {
{ Active = true,
await AddPlayer(attacker); AttackerId = attacker.ClientId,
} VictimId = victim.ClientId,
ServerId = serverId,
DeathOrigin = vDeathOrigin,
KillOrigin = vKillOrigin,
DeathType = ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath)),
Damage = int.Parse(damage),
HitLoc = ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)),
Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName)),
ViewAngles = vViewAngles,
TimeOffset = long.Parse(offset),
When = time,
IsKillstreakKill = isKillstreakKill[0] != '0',
AdsPercent = float.Parse(Ads, System.Globalization.CultureInfo.InvariantCulture),
Fraction = double.Parse(fraction, System.Globalization.CultureInfo.InvariantCulture),
VisibilityPercentage = double.Parse(visibilityPercentage, System.Globalization.CultureInfo.InvariantCulture),
IsKill = !isDamage,
AnglesList = snapshotAngles
};
var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId]; if (hit.HitLoc == IW4Info.HitLocation.shield)
var clientStats = _servers[serverId].PlayerStats[attacker.ClientId]; {
// we don't care about shield hits
return;
}
// increment their hit count // incase the add player event get delayed
if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId))
hit.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || {
hit.DeathType == IW4Info.MeansOfDeath.MOD_HEAD_SHOT) await AddPlayer(attacker);
{ }
clientStats.HitLocations.First(hl => hl.Location == hit.HitLoc).HitCount += 1;
}
if (clientStats.SessionKills % Detection.MAX_TRACKED_HIT_COUNT == 0) if (!isDamage)
{ {
await OnProcessingPenalty.WaitAsync(); await AddStandardKill(attacker, victim);
await SaveClientStats(clientStats); }
OnProcessingPenalty.Release(1);
}
if (hit.IsKillstreakKill) var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId];
{ var clientStats = _servers[serverId].PlayerStats[attacker.ClientId];
return;
} waiter = clientStats.ProcessingHit;
await waiter.WaitAsync();
// increment their hit count
if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
hit.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET ||
hit.DeathType == IW4Info.MeansOfDeath.MOD_HEAD_SHOT)
{
clientStats.HitLocations.First(hl => hl.Location == hit.HitLoc).HitCount += 1;
}
if (hit.IsKillstreakKill)
{
return;
}
try
{
if (Plugin.Config.Configuration().StoreClientKills) if (Plugin.Config.Configuration().StoreClientKills)
{ {
await OnProcessingPenalty.WaitAsync(); var cache = _servers[serverId].HitCache;
_hitCache.Add(hit); cache.Add(hit);
if (_hitCache.Count > Detection.MAX_TRACKED_HIT_COUNT) if (cache.Count > MAX_CACHED_HITS)
{ {
await SaveHitCache(serverId);
using (var ctx = new DatabaseContext())
{
ctx.AddRange(_hitCache);
await ctx.SaveChangesAsync();
}
_hitCache.Clear();
} }
OnProcessingPenalty.Release(1);
} }
if (Plugin.Config.Configuration().EnableAntiCheat && !attacker.IsBot && attacker.ClientId != victim.ClientId) if (Plugin.Config.Configuration().EnableAntiCheat && !attacker.IsBot && attacker.ClientId != victim.ClientId)
{ {
DetectionPenaltyResult result = new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Any }; DetectionPenaltyResult result = new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Any };
await OnProcessingPenalty.WaitAsync(); clientDetection.TrackedHits.Add(hit);
#if DEBUG if (clientDetection.TrackedHits.Count >= Detection.MIN_HITS_TO_RUN_DETECTION)
if (clientDetection.TrackedHits.Count > 0)
#else
if (clientDetection.TrackedHits.Count > Detection.MAX_TRACKED_HIT_COUNT)
#endif
{ {
while (clientDetection.TrackedHits.Count > 0) while (clientDetection.TrackedHits.Count > 0)
{ {
var oldestHit = clientDetection.TrackedHits
.OrderBy(_hits => _hits.TimeOffset)
.First();
var oldestHit = clientDetection.TrackedHits.OrderBy(_hits => _hits.TimeOffset).First();
clientDetection.TrackedHits.Remove(oldestHit); clientDetection.TrackedHits.Remove(oldestHit);
result = clientDetection.ProcessHit(oldestHit, isDamage); result = clientDetection.ProcessHit(oldestHit, isDamage);
#if !DEBUG
await ApplyPenalty(result, attacker); await ApplyPenalty(result, attacker);
#endif
if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any) if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any)
{ {
using (var ctx = new DatabaseContext()) await SaveTrackedSnapshots(clientDetection);
{
SaveTrackedSnapshots(clientDetection, ctx);
await ctx.SaveChangesAsync();
}
if (result.ClientPenalty == EFPenalty.PenaltyType.Ban) if (result.ClientPenalty == EFPenalty.PenaltyType.Ban)
{ {
// we don't care about any additional hits now that they're banned
clientDetection.TrackedHits.Clear();
break; break;
} }
} }
} }
} }
else
{
clientDetection.TrackedHits.Add(hit);
}
OnProcessingPenalty.Release(1);
} }
} }
@ -633,11 +600,22 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
_log.WriteError("Could not save hit or AC info"); _log.WriteError("Could not save hit or AC info");
_log.WriteDebug(ex.GetExceptionInfo()); _log.WriteDebug(ex.GetExceptionInfo());
}
if (OnProcessingPenalty.CurrentCount == 0) finally
{ {
OnProcessingPenalty.Release(1); waiter?.Release(1);
} }
}
public async Task SaveHitCache(long serverId)
{
using (var ctx = new DatabaseContext(true))
{
var server = _servers[serverId];
ctx.AddRange(server.HitCache);
await ctx.SaveChangesAsync();
server.HitCache.Clear();
} }
} }
@ -679,59 +657,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
} }
void SaveTrackedSnapshots(Detection clientDetection, DatabaseContext ctx) async Task SaveTrackedSnapshots(Detection clientDetection)
{ {
// todo: why does this cause duplicate primary key
_ = clientDetection.Tracker.GetNextChange();
EFACSnapshot change; EFACSnapshot change;
while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot))
using (var ctx = new DatabaseContext(true))
{ {
while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot))
if (change.HitOrigin.Vector3Id > 0)
{ {
change.HitOriginId = change.HitOrigin.Vector3Id; ctx.Add(change);
ctx.Attach(change.HitOrigin);
} }
await ctx.SaveChangesAsync();
else if (change.HitOrigin.Vector3Id == 0)
{
ctx.Add(change.HitOrigin);
}
if (change.HitDestination.Vector3Id > 0)
{
change.HitDestinationId = change.HitDestination.Vector3Id;
ctx.Attach(change.HitDestination);
}
else if (change.HitDestination.Vector3Id == 0)
{
ctx.Add(change.HitOrigin);
}
if (change.CurrentViewAngle.Vector3Id > 0)
{
change.CurrentViewAngleId = change.CurrentViewAngle.Vector3Id;
ctx.Attach(change.CurrentViewAngle);
}
else if (change.CurrentViewAngle.Vector3Id == 0)
{
ctx.Add(change.HitOrigin);
}
if (change.LastStrainAngle.Vector3Id > 0)
{
change.LastStrainAngleId = change.LastStrainAngle.Vector3Id;
ctx.Attach(change.LastStrainAngle);
}
else if (change.LastStrainAngle.Vector3Id == 0)
{
ctx.Add(change.HitOrigin);
}
ctx.Add(change);
} }
} }
@ -815,11 +751,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
// update their performance // update their performance
#if !DEBUG
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 2.5) if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 2.5)
#else
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 0.1)
#endif
{ {
attackerStats.LastStatHistoryUpdate = DateTime.UtcNow; attackerStats.LastStatHistoryUpdate = DateTime.UtcNow;
await UpdateStatHistory(attacker, attackerStats); await UpdateStatHistory(attacker, attackerStats);
@ -837,12 +769,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds; int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds;
// don't update their stat history if they haven't played long // don't update their stat history if they haven't played long
//#if DEBUG == false
if (currentSessionTime < 60) if (currentSessionTime < 60)
{ {
return; return;
} }
//#endif
int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime; int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime;
@ -1240,9 +1170,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var serverStatsSet = ctx.Set<EFServerStatistics>(); var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStatsSet.Update(_servers[serverId].ServerStatistics); serverStatsSet.Update(_servers[serverId].ServerStatistics);
await ctx.SaveChangesAsync();
} }
foreach (var client in sv.GetClientsAsList())
{
var stats = GetClientStats(client.ClientId, serverId);
if (stats != null)
{
await SaveClientStats(stats);
}
}
await SaveHitCache(serverId);
} }
public void SetTeamBased(long serverId, bool isTeamBased) public void SetTeamBased(long serverId, bool isTeamBased)

View File

@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
@ -12,6 +13,16 @@ namespace IW4MAdmin.Plugins.Stats.Models
{ {
public class EFClientStatistics : SharedEntity public class EFClientStatistics : SharedEntity
{ {
public EFClientStatistics()
{
ProcessingHit = new SemaphoreSlim(1, 1);
}
~EFClientStatistics()
{
ProcessingHit.Dispose();
}
public int ClientId { get; set; } public int ClientId { get; set; }
[ForeignKey("ClientId")] [ForeignKey("ClientId")]
public virtual EFClient Client { get; set; } public virtual EFClient Client { get; set; }
@ -96,12 +107,14 @@ namespace IW4MAdmin.Plugins.Stats.Models
} }
} }
[NotMapped] [NotMapped]
private List<int> SessionScores = new List<int>() { 0 }; private readonly List<int> SessionScores = new List<int>() { 0 };
[NotMapped] [NotMapped]
public IW4Info.Team Team { get; set; } public IW4Info.Team Team { get; set; }
[NotMapped] [NotMapped]
public DateTime LastStatHistoryUpdate { get; set; } = DateTime.UtcNow; public DateTime LastStatHistoryUpdate { get; set; } = DateTime.UtcNow;
[NotMapped] [NotMapped]
public double SessionSPM { get; set; } public double SessionSPM { get; set; }
[NotMapped]
public SemaphoreSlim ProcessingHit { get; private set; }
} }
} }

View File

@ -29,6 +29,10 @@ namespace IW4MAdmin.Plugins.Stats
public static StatManager Manager { get; private set; } public static StatManager Manager { get; private set; }
public static IManager ServerManager; public static IManager ServerManager;
public static BaseConfigurationHandler<StatsConfiguration> Config { get; private set; } public static BaseConfigurationHandler<StatsConfiguration> Config { get; private set; }
#if DEBUG
int scriptDamageCount;
int scriptKillCount;
#endif
public async Task OnEventAsync(GameEvent E, Server S) public async Task OnEventAsync(GameEvent E, Server S)
{ {
@ -44,7 +48,6 @@ namespace IW4MAdmin.Plugins.Stats
break; break;
case GameEvent.EventType.Disconnect: case GameEvent.EventType.Disconnect:
await Manager.RemovePlayer(E.Origin); await Manager.RemovePlayer(E.Origin);
await Manager.Sync(S);
break; break;
case GameEvent.EventType.Say: case GameEvent.EventType.Say:
if (!string.IsNullOrEmpty(E.Data) && if (!string.IsNullOrEmpty(E.Data) &&
@ -56,8 +59,10 @@ namespace IW4MAdmin.Plugins.Stats
case GameEvent.EventType.MapChange: case GameEvent.EventType.MapChange:
Manager.SetTeamBased(StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm"); Manager.SetTeamBased(StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm");
Manager.ResetKillstreaks(StatManager.GetIdForServer(E.Owner)); Manager.ResetKillstreaks(StatManager.GetIdForServer(E.Owner));
await Manager.Sync(E.Owner);
break; break;
case GameEvent.EventType.MapEnd: case GameEvent.EventType.MapEnd:
await Manager.Sync(E.Owner);
break; break;
case GameEvent.EventType.JoinTeam: case GameEvent.EventType.JoinTeam:
break; break;
@ -85,8 +90,17 @@ namespace IW4MAdmin.Plugins.Stats
E.Origin = E.Target; E.Origin = E.Target;
} }
#if DEBUG
scriptKillCount++;
S.Logger.WriteInfo($"Start ScriptKill {scriptKillCount}");
#endif
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8], await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]); killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
#if DEBUG
S.Logger.WriteInfo($"End ScriptKill {scriptKillCount}");
#endif
} }
break; break;
case GameEvent.EventType.Kill: case GameEvent.EventType.Kill:
@ -123,8 +137,21 @@ namespace IW4MAdmin.Plugins.Stats
E.Origin = E.Target; E.Origin = E.Target;
} }
#if DEBUG
scriptDamageCount++;
S.Logger.WriteInfo($"Start ScriptDamage {scriptDamageCount}");
#endif
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8], await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]); killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
#if DEBUG
S.Logger.WriteInfo($"End ScriptDamage {scriptDamageCount}");
#endif
}
else
{
break;
} }
break; break;
} }
@ -342,7 +369,7 @@ namespace IW4MAdmin.Plugins.Stats
Order = 5, Order = 5,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"], Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"],
Sensitive = true Sensitive = true
}, }
}; };
} }
@ -483,7 +510,7 @@ namespace IW4MAdmin.Plugins.Stats
/// <returns></returns> /// <returns></returns>
private bool ShouldIgnoreEvent(EFClient origin, EFClient target) private bool ShouldIgnoreEvent(EFClient origin, EFClient target)
{ {
return ((origin?.NetworkId <= 1 && target?.NetworkId <= 1) || (origin?.ClientId <=1 && target?.ClientId <= 1)); return ((origin?.NetworkId <= 1 && target?.NetworkId <= 1) || (origin?.ClientId <= 1 && target?.ClientId <= 1));
} }
/// <summary> /// <summary>

View File

@ -13,7 +13,7 @@ namespace SharedLibraryCore.Helpers
[Key] [Key]
public int Vector3Id { get; set; } public int Vector3Id { get; set; }
public float X { get; protected set; } public float X { get; protected set; }
public float Y { get; protected set; } public float Y { get; protected set; }
public float Z { get; protected set; } public float Z { get; protected set; }
// this is for EF and really should be somewhere else // this is for EF and really should be somewhere else
@ -57,7 +57,7 @@ namespace SharedLibraryCore.Helpers
public static double AbsoluteDistance(Vector3 a, Vector3 b) public static double AbsoluteDistance(Vector3 a, Vector3 b)
{ {
double deltaX = Math.Abs(b.X -a.X); double deltaX = Math.Abs(b.X - a.X);
double deltaY = Math.Abs(b.Y - a.Y); double deltaY = Math.Abs(b.Y - a.Y);
// this 'fixes' the roll-over angles // this 'fixes' the roll-over angles

View File

@ -13,6 +13,13 @@ namespace SharedLibraryCore.RCon
{ {
class ConnectionState class ConnectionState
{ {
~ConnectionState()
{
OnComplete.Dispose();
OnSentData.Dispose();
OnReceivedData.Dispose();
}
public int ConnectionAttempts { get; set; } public int ConnectionAttempts { get; set; }
const int BufferSize = 4096; const int BufferSize = 4096;
public readonly byte[] ReceiveBuffer = new byte[BufferSize]; public readonly byte[] ReceiveBuffer = new byte[BufferSize];
@ -123,7 +130,7 @@ namespace SharedLibraryCore.RCon
catch (OverflowException) catch (OverflowException)
{ {
connectionState.OnComplete.Release(1); connectionState.OnComplete.Release(1);
throw new NetworkException($"Invalid character expected when converting encodings - {parameters}"); throw new NetworkException($"Invalid character encountered when converting encodings - {parameters}");
} }
byte[] response = null; byte[] response = null;
@ -152,7 +159,6 @@ namespace SharedLibraryCore.RCon
throw new NetworkException("Expected response but got 0 bytes back"); throw new NetworkException("Expected response but got 0 bytes back");
} }
connectionState.OnComplete.Release(1);
connectionState.ConnectionAttempts = 0; connectionState.ConnectionAttempts = 0;
} }
@ -164,9 +170,16 @@ namespace SharedLibraryCore.RCon
goto retrySend; goto retrySend;
} }
connectionState.OnComplete.Release(1);
throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"].FormatExt(Endpoint)); throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"].FormatExt(Endpoint));
} }
finally
{
if (connectionState.OnComplete.CurrentCount == 0)
{
connectionState.OnComplete.Release(1);
}
}
} }
string responseString = defaultEncoding.GetString(response, 0, response.Length) + '\n'; string responseString = defaultEncoding.GetString(response, 0, response.Length) + '\n';
@ -258,7 +271,7 @@ namespace SharedLibraryCore.RCon
private void OnDataSent(object sender, SocketAsyncEventArgs e) private void OnDataSent(object sender, SocketAsyncEventArgs e)
{ {
#if DEBUG == true #if DEBUG == true
Log.WriteDebug($"Sent {e.Buffer.Length} bytes to {e.ConnectSocket.RemoteEndPoint.ToString()}"); Log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
#endif #endif
ActiveQueries[this.Endpoint].OnSentData.Set(); ActiveQueries[this.Endpoint].OnSentData.Set();
} }

View File

@ -46,8 +46,8 @@
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Npgsql" Version="4.0.6" /> <PackageReference Include="Npgsql" Version="4.0.9" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.2.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.2.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.2.0" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.2.0" />
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" /> <PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
</ItemGroup> </ItemGroup>