add snap metric to anticheat

update various small code bits
This commit is contained in:
RaidMax 2019-09-09 17:40:04 -05:00
parent 148d28eaca
commit c18be20899
27 changed files with 2768 additions and 269 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;
@ -18,11 +19,13 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Chest, Chest,
Offset, Offset,
Strain, Strain,
Recoil Recoil,
Snap
}; };
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 +39,9 @@ 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 double sessionAverageSnapAmount;
private int sessionSnapHits;
private EFClientKill lastHit;
private class HitInfo private class HitInfo
{ {
@ -91,10 +97,57 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Kills++; Kills++;
} }
#region VIEWANGLES #region SNAP
if (hit.AnglesList.Count >= 2) if (hit.AnglesList.Count == MIN_ANGLE_COUNT)
{ {
double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles); if (lastHit == null)
{
lastHit = hit;
}
if (lastHit == hit || lastHit.VictimId != hit.VictimId || (hit.TimeOffset - lastHit.TimeOffset) >= 1000)
{
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;
if (sessionSnapHits > Thresholds.LowSampleMinKills && sessionAverageSnapAmount > Thresholds.SnapFlagValue)
{
results.Add(new DetectionPenaltyResult()
{
ClientPenalty = EFPenalty.PenaltyType.Flag,
Value = sessionAverageSnapAmount,
HitCount = ClientStats.SnapHitCount,
Type = DetectionType.Snap
});
}
if (sessionSnapHits > Thresholds.LowSampleMinKills && sessionAverageSnapAmount > Thresholds.SnapBanValue)
{
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 // LIFETIME
var hitLoc = ClientStats.HitLocations var hitLoc = ClientStats.HitLocations
@ -119,7 +172,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
results.Add(new DetectionPenaltyResult() results.Add(new DetectionPenaltyResult()
{ {
ClientPenalty = EFPenalty.PenaltyType.Ban, ClientPenalty = EFPenalty.PenaltyType.Flag,
Value = hitLoc.HitOffsetAverage, Value = hitLoc.HitOffsetAverage,
HitCount = hitLoc.HitCount, HitCount = hitLoc.HitCount,
Type = DetectionType.Offset Type = DetectionType.Offset
@ -146,7 +199,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
results.Add(new DetectionPenaltyResult() results.Add(new DetectionPenaltyResult()
{ {
ClientPenalty = EFPenalty.PenaltyType.Ban, ClientPenalty = EFPenalty.PenaltyType.Flag,
Value = weightedSessionAverage, Value = weightedSessionAverage,
HitCount = HitCount, HitCount = HitCount,
Type = DetectionType.Offset, Type = DetectionType.Offset,
@ -344,7 +397,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,
@ -352,18 +405,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
RecoilOffset = hitRecoilAverage, RecoilOffset = hitRecoilAverage,
CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds, CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds,
CurrentStrain = currentStrain, CurrentStrain = currentStrain,
CurrentViewAngle = hit.ViewAngles, CurrentViewAngle = new Vector3(hit.ViewAngles.X, hit.ViewAngles.Y, hit.ViewAngles.Z),
Hits = HitCount, Hits = HitCount,
Kills = Kills, Kills = Kills,
Deaths = ClientStats.SessionDeaths, Deaths = ClientStats.SessionDeaths,
HitDestinationId = hit.DeathOrigin.Vector3Id, //todo[9.1.19]: why does this cause unique failure?
HitDestination = hit.DeathOrigin, HitDestination = new Vector3(hit.DeathOrigin.X, hit.DeathOrigin.Y, hit.DeathOrigin.Z),
HitOriginId = hit.KillOrigin.Vector3Id, HitOrigin = new Vector3(hit.KillOrigin.X, hit.KillOrigin.Y, hit.KillOrigin.Z),
HitOrigin = hit.KillOrigin,
EloRating = ClientStats.EloRating, EloRating = ClientStats.EloRating,
HitLocation = hit.HitLoc, HitLocation = hit.HitLoc,
LastStrainAngle = Strain.LastAngle, LastStrainAngle = new Vector3(Strain.LastAngle.X, Strain.LastAngle.Y, Strain.LastAngle.Z),
PredictedViewAngles = hit.AnglesList,
// this is in "meters" // this is in "meters"
Distance = hit.Distance, Distance = hit.Distance,
SessionScore = ClientStats.SessionScore, SessionScore = ClientStats.SessionScore,
@ -372,7 +423,17 @@ 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
}); };
snapshot.PredictedViewAngles = hit.AnglesList
.Select(_angle => new EFACSnapshotVector3()
{
Vector = _angle,
Snapshot = snapshot
})
.ToList();
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

@ -27,6 +27,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public const int HighSampleMinKills = 100; public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2; public const double KillTimeThreshold = 0.2;
public const int LowSampleMinKillsRecoil = 5; public const int LowSampleMinKillsRecoil = 5;
public const double SnapFlagValue = 6.12;
public const double SnapBanValue = 9.67;
public const double MaxStrainBan = 0.9; public const double MaxStrainBan = 0.9;

View File

@ -44,14 +44,24 @@ 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) if (E.Target != null)
{ {
int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId); int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}"; 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
{
using (var ctx = new DatabaseContext(true))
{
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.Target.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}";
} }
@ -60,9 +70,19 @@ namespace IW4MAdmin.Plugins.Stats.Commands
int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId); int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}"; 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.Origin.ClientId))); if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Origin)))
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}"; {
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

@ -117,6 +117,7 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
.Include(s => s.HitDestination) .Include(s => s.HitDestination)
.Include(s => s.CurrentViewAngle) .Include(s => s.CurrentViewAngle)
.Include(s => s.PredictedViewAngles) .Include(s => s.PredictedViewAngles)
.ThenInclude(_angles => _angles.Vector)
.OrderBy(s => s.When) .OrderBy(s => s.When)
.ThenBy(s => s.Hits); .ThenBy(s => s.Hits);

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()
@ -276,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);
@ -397,11 +390,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
_log.WriteDebug(ex.GetExceptionInfo()); _log.WriteDebug(ex.GetExceptionInfo());
} }
finally
{
OnProcessingSensitive.Release(1);
}
return null; return null;
} }
@ -467,7 +455,10 @@ 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 try
{ {
vDeathOrigin = Vector3.Parse(deathOrigin); vDeathOrigin = Vector3.Parse(deathOrigin);
@ -528,20 +519,23 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return; return;
} }
if (!isDamage)
{
await AddStandardKill(attacker, victim);
}
// incase the add player event get delayed // incase the add player event get delayed
if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId))
{ {
await AddPlayer(attacker); await AddPlayer(attacker);
} }
if (!isDamage)
{
await AddStandardKill(attacker, victim);
}
var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId]; var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId];
var clientStats = _servers[serverId].PlayerStats[attacker.ClientId]; var clientStats = _servers[serverId].PlayerStats[attacker.ClientId];
waiter = clientStats.ProcessingHit;
await waiter.WaitAsync();
// increment their hit count // increment their hit count
if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
hit.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || hit.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET ||
@ -550,82 +544,55 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
clientStats.HitLocations.First(hl => hl.Location == hit.HitLoc).HitCount += 1; clientStats.HitLocations.First(hl => hl.Location == hit.HitLoc).HitCount += 1;
} }
if (clientStats.SessionKills % Detection.MAX_TRACKED_HIT_COUNT == 0)
{
await OnProcessingPenalty.WaitAsync();
await SaveClientStats(clientStats);
OnProcessingPenalty.Release(1);
}
if (hit.IsKillstreakKill) if (hit.IsKillstreakKill)
{ {
return; 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)
{
OnProcessingPenalty.Release(1);
} }
finally
{
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,60 +657,18 @@ 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;
using (var ctx = new DatabaseContext(true))
{
while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot)) while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot))
{ {
if (change.HitOrigin.Vector3Id > 0)
{
change.HitOriginId = change.HitOrigin.Vector3Id;
ctx.Attach(change.HitOrigin);
}
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); ctx.Add(change);
} }
await ctx.SaveChangesAsync();
}
} }
public async Task AddStandardKill(EFClient attacker, EFClient victim) public async Task AddStandardKill(EFClient attacker, EFClient victim)
@ -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;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
namespace IW4MAdmin.Plugins.Stats.Models namespace IW4MAdmin.Plugins.Stats.Models
{ {
@ -47,6 +48,11 @@ namespace IW4MAdmin.Plugins.Stats.Models
public IW4Info.WeaponName WeaponId { get; set; } public IW4Info.WeaponName WeaponId { get; set; }
public IW4Info.HitLocation HitLocation { get; set; } public IW4Info.HitLocation HitLocation { get; set; }
public IW4Info.MeansOfDeath HitType { get; set; } public IW4Info.MeansOfDeath HitType { get; set; }
public virtual ICollection<Vector3> PredictedViewAngles { get; set; } public virtual ICollection<EFACSnapshotVector3> PredictedViewAngles { get; set; }
[NotMapped]
public string CapturedViewAngles => PredictedViewAngles?.Count > 0 ?
string.Join(", ", PredictedViewAngles.OrderBy(_angle => _angle.ACSnapshotVector3Id).Select(_angle => _angle.Vector.ToString())) :
"";
} }
} }

View File

@ -0,0 +1,26 @@
using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Helpers;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
namespace IW4MAdmin.Plugins.Stats.Models
{
public class EFACSnapshotVector3
{
[Key]
public int ACSnapshotVector3Id { get; set; }
public int SnapshotId { get; set; }
[ForeignKey("SnapshotId")]
public EFACSnapshot Snapshot { get; set; }
public int Vector3Id { get; set; }
[ForeignKey("Vector3Id")]
public Vector3 Vector { get; set;}
}
}

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; }
@ -27,6 +38,8 @@ namespace IW4MAdmin.Plugins.Stats.Models
public double RollingWeightedKDR { get; set; } public double RollingWeightedKDR { get; set; }
public double VisionAverage { get; set; } public double VisionAverage { get; set; }
public double AverageRecoilOffset { get; set; } public double AverageRecoilOffset { get; set; }
public double AverageSnapValue { get; set; }
public int SnapHitCount { get; set; }
[NotMapped] [NotMapped]
public double Performance public double Performance
{ {
@ -96,12 +109,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;
} }
@ -245,6 +272,7 @@ namespace IW4MAdmin.Plugins.Stats
double chestAbdomenRatio = 0; double chestAbdomenRatio = 0;
double hitOffsetAverage = 0; double hitOffsetAverage = 0;
double averageRecoilAmount = 0; double averageRecoilAmount = 0;
double averageSnapValue = 0;
double maxStrain = clientStats.Count(c => c.MaxStrain > 0) == 0 ? 0 : clientStats.Max(cs => cs.MaxStrain); double maxStrain = clientStats.Count(c => c.MaxStrain > 0) == 0 ? 0 : clientStats.Max(cs => cs.MaxStrain);
if (clientStats.Where(cs => cs.HitLocations.Count > 0).FirstOrDefault() != null) if (clientStats.Where(cs => cs.HitLocations.Count > 0).FirstOrDefault() != null)
@ -268,6 +296,7 @@ namespace IW4MAdmin.Plugins.Stats
var validOffsets = clientStats.Where(c => c.HitLocations.Count(hl => hl.HitCount > 0) > 0).SelectMany(hl => hl.HitLocations); var validOffsets = clientStats.Where(c => c.HitLocations.Count(hl => hl.HitCount > 0) > 0).SelectMany(hl => hl.HitLocations);
hitOffsetAverage = validOffsets.Sum(o => o.HitCount * o.HitOffsetAverage) / (double)validOffsets.Sum(o => o.HitCount); hitOffsetAverage = validOffsets.Sum(o => o.HitCount * o.HitOffsetAverage) / (double)validOffsets.Sum(o => o.HitCount);
averageRecoilAmount = clientStats.Average(_stat => _stat.AverageRecoilOffset); averageRecoilAmount = clientStats.Average(_stat => _stat.AverageRecoilOffset);
averageSnapValue = clientStats.Any(_stats => _stats.AverageSnapValue > 0) ? clientStats.Where(_stats => _stats.AverageSnapValue > 0).Average(_stat => _stat.AverageSnapValue) : 0;
} }
return new List<ProfileMeta>() return new List<ProfileMeta>()
@ -343,6 +372,16 @@ namespace IW4MAdmin.Plugins.Stats
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"], Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"],
Sensitive = true Sensitive = true
}, },
new ProfileMeta()
{
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 8",
Value = Math.Round(averageSnapValue, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 6,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM8"],
Sensitive = true
}
}; };
} }

View File

@ -13,11 +13,11 @@
<!-- this is another ugly hack--> <!-- this is another ugly hack-->
@if (prop.GetValue(snapshot) is System.Collections.Generic.HashSet<SharedLibraryCore.Helpers.Vector3>) @if (prop.GetValue(snapshot) is System.Collections.Generic.HashSet<SharedLibraryCore.Helpers.Vector3>)
{ {
<span class="text-white">@prop.Name </span> @*<span class="text-white">@prop.Name </span>
foreach (var v in (System.Collections.Generic.HashSet<SharedLibraryCore.Helpers.Vector3>)prop.GetValue(snapshot)) foreach (var v in (System.Collections.Generic.HashSet<SharedLibraryCore.Helpers.Vector3>)prop.GetValue(snapshot))
{ {
<span>@v.ToString(),</span><br /> <span>@v.ToString(),</span><br />
} }*@
} }
else else
{ {

View File

@ -67,6 +67,43 @@ namespace SharedLibraryCore.Helpers
return Math.Sqrt((dx * dx) + (dy * dy)); return Math.Sqrt((dx * dx) + (dy * dy));
} }
public static double SnapDistance(Vector3 a, Vector3 b, Vector3 c)
{
a = a.FixIW4Angles();
b = b.FixIW4Angles();
c = c.FixIW4Angles();
float preserveDirectionAngle(float a1, float b1)
{
float difference = b1 - a1;
while (difference < -180) difference += 360;
while (difference > 180) difference -= 360;
return difference;
}
var directions = new[]
{
new Vector3()
{
X = preserveDirectionAngle(b.X, a.X),
Y = preserveDirectionAngle(b.Y, a.Y)
},
new Vector3()
{
X = preserveDirectionAngle(c.X, b.X),
Y = preserveDirectionAngle(c.Y, b.Y)
}
};
var distance = new Vector3
{
X = Math.Abs(directions[1].X - directions[0].X),
Y = Math.Abs(directions[1].Y - directions[0].Y)
};
return Math.Sqrt((distance.X * distance.X) + (distance.Y * distance.Y));
}
public static double ViewAngleDistance(Vector3 a, Vector3 b, Vector3 c) public static double ViewAngleDistance(Vector3 a, Vector3 b, Vector3 c)
{ {
double dabX = Math.Abs(a.X - b.X); double dabX = Math.Abs(a.X - b.X);

View File

@ -0,0 +1,706 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20190831210503_AvgSnapValueToClientStatistics")]
partial class AvgSnapValueToClientStatistics
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<int>("CurrentSessionLength");
b.Property<double>("CurrentStrain");
b.Property<int>("CurrentViewAngleId");
b.Property<int>("Deaths");
b.Property<double>("Distance");
b.Property<double>("EloRating");
b.Property<int>("HitDestinationId");
b.Property<int>("HitLocation");
b.Property<int>("HitOriginId");
b.Property<int>("HitType");
b.Property<int>("Hits");
b.Property<int>("Kills");
b.Property<int>("LastStrainAngleId");
b.Property<double>("RecoilOffset");
b.Property<double>("SessionAngleOffset");
b.Property<double>("SessionSPM");
b.Property<int>("SessionScore");
b.Property<double>("StrainAngleBetween");
b.Property<int>("TimeSinceLastEvent");
b.Property<int>("WeaponId");
b.Property<DateTime>("When");
b.HasKey("SnapshotId");
b.HasIndex("ClientId");
b.HasIndex("CurrentViewAngleId");
b.HasIndex("HitDestinationId");
b.HasIndex("HitOriginId");
b.HasIndex("LastStrainAngleId");
b.ToTable("EFACSnapshot");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.Property<long>("KillId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AttackerId");
b.Property<int>("Damage");
b.Property<int?>("DeathOriginVector3Id");
b.Property<int>("DeathType");
b.Property<double>("Fraction");
b.Property<int>("HitLoc");
b.Property<bool>("IsKill");
b.Property<int?>("KillOriginVector3Id");
b.Property<int>("Map");
b.Property<long>("ServerId");
b.Property<int>("VictimId");
b.Property<int?>("ViewAnglesVector3Id");
b.Property<double>("VisibilityPercentage");
b.Property<int>("Weapon");
b.Property<DateTime>("When");
b.HasKey("KillId");
b.HasIndex("AttackerId");
b.HasIndex("DeathOriginVector3Id");
b.HasIndex("KillOriginVector3Id");
b.HasIndex("ServerId");
b.HasIndex("VictimId");
b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.Property<long>("MessageId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<string>("Message");
b.Property<long>("ServerId");
b.Property<DateTime>("TimeSent");
b.HasKey("MessageId");
b.HasIndex("ClientId");
b.HasIndex("ServerId");
b.HasIndex("TimeSent");
b.ToTable("EFClientMessages");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.HasKey("RatingHistoryId");
b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.Property<int>("ClientId");
b.Property<long>("ServerId");
b.Property<bool>("Active");
b.Property<double>("AverageRecoilOffset");
b.Property<double>("AverageSnapValue");
b.Property<int>("Deaths");
b.Property<double>("EloRating");
b.Property<int>("Kills");
b.Property<double>("MaxStrain");
b.Property<double>("RollingWeightedKDR");
b.Property<double>("SPM");
b.Property<double>("Skill");
b.Property<int>("TimePlayed");
b.Property<double>("VisionAverage");
b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId");
b.ToTable("EFClientStatistics");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId")
.HasColumnName("EFClientStatistics_ClientId");
b.Property<int>("HitCount");
b.Property<float>("HitOffsetAverage");
b.Property<int>("Location");
b.Property<float>("MaxAngleDistance");
b.Property<long>("ServerId")
.HasColumnName("EFClientStatistics_ServerId");
b.HasKey("HitLocationCountId");
b.HasIndex("ServerId");
b.HasIndex("ClientId", "ServerId");
b.ToTable("EFHitLocationCounts");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.Property<int>("RatingId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ActivityAmount");
b.Property<bool>("Newest");
b.Property<double>("Performance");
b.Property<int>("Ranking");
b.Property<int>("RatingHistoryId");
b.Property<long?>("ServerId");
b.Property<DateTime>("When");
b.HasKey("RatingId");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.HasIndex("Performance", "Ranking", "When");
b.ToTable("EFRating");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{
b.Property<long>("ServerId");
b.Property<bool>("Active");
b.Property<string>("EndPoint");
b.Property<int?>("GameName");
b.Property<int>("Port");
b.HasKey("ServerId");
b.ToTable("EFServers");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.Property<int>("StatisticId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<long>("ServerId");
b.Property<long>("TotalKills");
b.Property<long>("TotalPlayTime");
b.HasKey("StatisticId");
b.HasIndex("ServerId");
b.ToTable("EFServerStatistics");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.Property<int>("AliasId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<DateTime>("DateAdded");
b.Property<int?>("IPAddress");
b.Property<int>("LinkId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24);
b.Property<string>("SearchableName")
.HasMaxLength(24);
b.HasKey("AliasId");
b.HasIndex("IPAddress");
b.HasIndex("LinkId");
b.HasIndex("Name");
b.HasIndex("SearchableName");
b.ToTable("EFAlias");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
{
b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
{
b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("Comment")
.HasMaxLength(128);
b.Property<string>("CurrentValue");
b.Property<int>("OriginEntityId");
b.Property<string>("PreviousValue");
b.Property<int>("TargetEntityId");
b.Property<DateTime>("TimeChanged");
b.Property<int>("TypeOfChange");
b.HasKey("ChangeHistoryId");
b.ToTable("EFChangeHistory");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.Property<int>("ClientId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AliasLinkId");
b.Property<int>("Connections");
b.Property<int>("CurrentAliasId");
b.Property<DateTime>("FirstConnection");
b.Property<DateTime>("LastConnection");
b.Property<int>("Level");
b.Property<bool>("Masked");
b.Property<long>("NetworkId");
b.Property<string>("Password");
b.Property<string>("PasswordSalt");
b.Property<int>("TotalConnectionTime");
b.HasKey("ClientId");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.ToTable("EFClients");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.Property<int>("MetaId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<DateTime>("Created");
b.Property<string>("Extra");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(32);
b.Property<DateTime>("Updated");
b.Property<string>("Value")
.IsRequired();
b.HasKey("MetaId");
b.HasIndex("ClientId");
b.HasIndex("Key");
b.ToTable("EFMeta");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("AutomatedOffense");
b.Property<DateTime?>("Expires");
b.Property<bool>("IsEvadedOffense");
b.Property<int>("LinkId");
b.Property<int>("OffenderId");
b.Property<string>("Offense")
.IsRequired();
b.Property<int>("PunisherId");
b.Property<int>("Type");
b.Property<DateTime>("When");
b.HasKey("PenaltyId");
b.HasIndex("LinkId");
b.HasIndex("OffenderId");
b.HasIndex("PunisherId");
b.ToTable("EFPenalties");
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd();
b.Property<int?>("EFACSnapshotSnapshotId");
b.Property<float>("X");
b.Property<float>("Y");
b.Property<float>("Z");
b.HasKey("Vector3Id");
b.HasIndex("EFACSnapshotSnapshotId");
b.ToTable("Vector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
.WithMany()
.HasForeignKey("CurrentViewAngleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
.WithMany()
.HasForeignKey("HitDestinationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
.WithMany()
.HasForeignKey("HitOriginId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
.WithMany()
.HasForeignKey("LastStrainAngleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
.WithMany()
.HasForeignKey("AttackerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
.WithMany()
.HasForeignKey("DeathOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
.WithMany()
.HasForeignKey("KillOriginVector3Id");
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
.WithMany()
.HasForeignKey("VictimId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
.WithMany()
.HasForeignKey("ViewAnglesVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics")
.WithMany("HitLocations")
.HasForeignKey("ClientId", "ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
.WithMany("Ratings")
.HasForeignKey("RatingHistoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("Children")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
.WithMany()
.HasForeignKey("AliasLinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
.WithMany()
.HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany("Meta")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
.WithMany("ReceivedPenalties")
.HasForeignKey("OffenderId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
.WithMany("AdministeredPenalties")
.HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("EFACSnapshotSnapshotId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AvgSnapValueToClientStatistics : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<double>(
name: "AverageSnapValue",
table: "EFClientStatistics",
nullable: false,
defaultValue: 0.0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AverageSnapValue",
table: "EFClientStatistics");
}
}
}

View File

@ -0,0 +1,708 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20190901180209_AddSnapHitCountToClientStatistics")]
partial class AddSnapHitCountToClientStatistics
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<int>("CurrentSessionLength");
b.Property<double>("CurrentStrain");
b.Property<int>("CurrentViewAngleId");
b.Property<int>("Deaths");
b.Property<double>("Distance");
b.Property<double>("EloRating");
b.Property<int>("HitDestinationId");
b.Property<int>("HitLocation");
b.Property<int>("HitOriginId");
b.Property<int>("HitType");
b.Property<int>("Hits");
b.Property<int>("Kills");
b.Property<int>("LastStrainAngleId");
b.Property<double>("RecoilOffset");
b.Property<double>("SessionAngleOffset");
b.Property<double>("SessionSPM");
b.Property<int>("SessionScore");
b.Property<double>("StrainAngleBetween");
b.Property<int>("TimeSinceLastEvent");
b.Property<int>("WeaponId");
b.Property<DateTime>("When");
b.HasKey("SnapshotId");
b.HasIndex("ClientId");
b.HasIndex("CurrentViewAngleId");
b.HasIndex("HitDestinationId");
b.HasIndex("HitOriginId");
b.HasIndex("LastStrainAngleId");
b.ToTable("EFACSnapshot");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.Property<long>("KillId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AttackerId");
b.Property<int>("Damage");
b.Property<int?>("DeathOriginVector3Id");
b.Property<int>("DeathType");
b.Property<double>("Fraction");
b.Property<int>("HitLoc");
b.Property<bool>("IsKill");
b.Property<int?>("KillOriginVector3Id");
b.Property<int>("Map");
b.Property<long>("ServerId");
b.Property<int>("VictimId");
b.Property<int?>("ViewAnglesVector3Id");
b.Property<double>("VisibilityPercentage");
b.Property<int>("Weapon");
b.Property<DateTime>("When");
b.HasKey("KillId");
b.HasIndex("AttackerId");
b.HasIndex("DeathOriginVector3Id");
b.HasIndex("KillOriginVector3Id");
b.HasIndex("ServerId");
b.HasIndex("VictimId");
b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.Property<long>("MessageId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<string>("Message");
b.Property<long>("ServerId");
b.Property<DateTime>("TimeSent");
b.HasKey("MessageId");
b.HasIndex("ClientId");
b.HasIndex("ServerId");
b.HasIndex("TimeSent");
b.ToTable("EFClientMessages");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.HasKey("RatingHistoryId");
b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.Property<int>("ClientId");
b.Property<long>("ServerId");
b.Property<bool>("Active");
b.Property<double>("AverageRecoilOffset");
b.Property<double>("AverageSnapValue");
b.Property<int>("Deaths");
b.Property<double>("EloRating");
b.Property<int>("Kills");
b.Property<double>("MaxStrain");
b.Property<double>("RollingWeightedKDR");
b.Property<double>("SPM");
b.Property<double>("Skill");
b.Property<int>("SnapHitCount");
b.Property<int>("TimePlayed");
b.Property<double>("VisionAverage");
b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId");
b.ToTable("EFClientStatistics");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId")
.HasColumnName("EFClientStatistics_ClientId");
b.Property<int>("HitCount");
b.Property<float>("HitOffsetAverage");
b.Property<int>("Location");
b.Property<float>("MaxAngleDistance");
b.Property<long>("ServerId")
.HasColumnName("EFClientStatistics_ServerId");
b.HasKey("HitLocationCountId");
b.HasIndex("ServerId");
b.HasIndex("ClientId", "ServerId");
b.ToTable("EFHitLocationCounts");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.Property<int>("RatingId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ActivityAmount");
b.Property<bool>("Newest");
b.Property<double>("Performance");
b.Property<int>("Ranking");
b.Property<int>("RatingHistoryId");
b.Property<long?>("ServerId");
b.Property<DateTime>("When");
b.HasKey("RatingId");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.HasIndex("Performance", "Ranking", "When");
b.ToTable("EFRating");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{
b.Property<long>("ServerId");
b.Property<bool>("Active");
b.Property<string>("EndPoint");
b.Property<int?>("GameName");
b.Property<int>("Port");
b.HasKey("ServerId");
b.ToTable("EFServers");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.Property<int>("StatisticId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<long>("ServerId");
b.Property<long>("TotalKills");
b.Property<long>("TotalPlayTime");
b.HasKey("StatisticId");
b.HasIndex("ServerId");
b.ToTable("EFServerStatistics");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.Property<int>("AliasId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<DateTime>("DateAdded");
b.Property<int?>("IPAddress");
b.Property<int>("LinkId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24);
b.Property<string>("SearchableName")
.HasMaxLength(24);
b.HasKey("AliasId");
b.HasIndex("IPAddress");
b.HasIndex("LinkId");
b.HasIndex("Name");
b.HasIndex("SearchableName");
b.ToTable("EFAlias");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
{
b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
{
b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("Comment")
.HasMaxLength(128);
b.Property<string>("CurrentValue");
b.Property<int>("OriginEntityId");
b.Property<string>("PreviousValue");
b.Property<int>("TargetEntityId");
b.Property<DateTime>("TimeChanged");
b.Property<int>("TypeOfChange");
b.HasKey("ChangeHistoryId");
b.ToTable("EFChangeHistory");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.Property<int>("ClientId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AliasLinkId");
b.Property<int>("Connections");
b.Property<int>("CurrentAliasId");
b.Property<DateTime>("FirstConnection");
b.Property<DateTime>("LastConnection");
b.Property<int>("Level");
b.Property<bool>("Masked");
b.Property<long>("NetworkId");
b.Property<string>("Password");
b.Property<string>("PasswordSalt");
b.Property<int>("TotalConnectionTime");
b.HasKey("ClientId");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.ToTable("EFClients");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.Property<int>("MetaId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<DateTime>("Created");
b.Property<string>("Extra");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(32);
b.Property<DateTime>("Updated");
b.Property<string>("Value")
.IsRequired();
b.HasKey("MetaId");
b.HasIndex("ClientId");
b.HasIndex("Key");
b.ToTable("EFMeta");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("AutomatedOffense");
b.Property<DateTime?>("Expires");
b.Property<bool>("IsEvadedOffense");
b.Property<int>("LinkId");
b.Property<int>("OffenderId");
b.Property<string>("Offense")
.IsRequired();
b.Property<int>("PunisherId");
b.Property<int>("Type");
b.Property<DateTime>("When");
b.HasKey("PenaltyId");
b.HasIndex("LinkId");
b.HasIndex("OffenderId");
b.HasIndex("PunisherId");
b.ToTable("EFPenalties");
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd();
b.Property<int?>("EFACSnapshotSnapshotId");
b.Property<float>("X");
b.Property<float>("Y");
b.Property<float>("Z");
b.HasKey("Vector3Id");
b.HasIndex("EFACSnapshotSnapshotId");
b.ToTable("Vector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
.WithMany()
.HasForeignKey("CurrentViewAngleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
.WithMany()
.HasForeignKey("HitDestinationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
.WithMany()
.HasForeignKey("HitOriginId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
.WithMany()
.HasForeignKey("LastStrainAngleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
.WithMany()
.HasForeignKey("AttackerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
.WithMany()
.HasForeignKey("DeathOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
.WithMany()
.HasForeignKey("KillOriginVector3Id");
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
.WithMany()
.HasForeignKey("VictimId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
.WithMany()
.HasForeignKey("ViewAnglesVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics")
.WithMany("HitLocations")
.HasForeignKey("ClientId", "ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
.WithMany("Ratings")
.HasForeignKey("RatingHistoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("Children")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
.WithMany()
.HasForeignKey("AliasLinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
.WithMany()
.HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany("Meta")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
.WithMany("ReceivedPenalties")
.HasForeignKey("OffenderId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
.WithMany("AdministeredPenalties")
.HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("EFACSnapshotSnapshotId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AddSnapHitCountToClientStatistics : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "SnapHitCount",
table: "EFClientStatistics",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SnapHitCount",
table: "EFClientStatistics");
}
}
}

View File

@ -0,0 +1,728 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20190901223620_UseJunctionTableForSnapshotVector3")]
partial class UseJunctionTableForSnapshotVector3
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<int>("CurrentSessionLength");
b.Property<double>("CurrentStrain");
b.Property<int>("CurrentViewAngleId");
b.Property<int>("Deaths");
b.Property<double>("Distance");
b.Property<double>("EloRating");
b.Property<int>("HitDestinationId");
b.Property<int>("HitLocation");
b.Property<int>("HitOriginId");
b.Property<int>("HitType");
b.Property<int>("Hits");
b.Property<int>("Kills");
b.Property<int>("LastStrainAngleId");
b.Property<double>("RecoilOffset");
b.Property<double>("SessionAngleOffset");
b.Property<double>("SessionSPM");
b.Property<int>("SessionScore");
b.Property<double>("StrainAngleBetween");
b.Property<int>("TimeSinceLastEvent");
b.Property<int>("WeaponId");
b.Property<DateTime>("When");
b.HasKey("SnapshotId");
b.HasIndex("ClientId");
b.HasIndex("CurrentViewAngleId");
b.HasIndex("HitDestinationId");
b.HasIndex("HitOriginId");
b.HasIndex("LastStrainAngleId");
b.ToTable("EFACSnapshot");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
{
b.Property<int>("ACSnapshotVector3Id")
.ValueGeneratedOnAdd();
b.Property<int>("SnapshotId");
b.Property<int>("Vector3Id");
b.HasKey("ACSnapshotVector3Id");
b.HasIndex("SnapshotId");
b.HasIndex("Vector3Id");
b.ToTable("EFACSnapshotVector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.Property<long>("KillId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AttackerId");
b.Property<int>("Damage");
b.Property<int?>("DeathOriginVector3Id");
b.Property<int>("DeathType");
b.Property<double>("Fraction");
b.Property<int>("HitLoc");
b.Property<bool>("IsKill");
b.Property<int?>("KillOriginVector3Id");
b.Property<int>("Map");
b.Property<long>("ServerId");
b.Property<int>("VictimId");
b.Property<int?>("ViewAnglesVector3Id");
b.Property<double>("VisibilityPercentage");
b.Property<int>("Weapon");
b.Property<DateTime>("When");
b.HasKey("KillId");
b.HasIndex("AttackerId");
b.HasIndex("DeathOriginVector3Id");
b.HasIndex("KillOriginVector3Id");
b.HasIndex("ServerId");
b.HasIndex("VictimId");
b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.Property<long>("MessageId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<string>("Message");
b.Property<long>("ServerId");
b.Property<DateTime>("TimeSent");
b.HasKey("MessageId");
b.HasIndex("ClientId");
b.HasIndex("ServerId");
b.HasIndex("TimeSent");
b.ToTable("EFClientMessages");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.HasKey("RatingHistoryId");
b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.Property<int>("ClientId");
b.Property<long>("ServerId");
b.Property<bool>("Active");
b.Property<double>("AverageRecoilOffset");
b.Property<double>("AverageSnapValue");
b.Property<int>("Deaths");
b.Property<double>("EloRating");
b.Property<int>("Kills");
b.Property<double>("MaxStrain");
b.Property<double>("RollingWeightedKDR");
b.Property<double>("SPM");
b.Property<double>("Skill");
b.Property<int>("SnapHitCount");
b.Property<int>("TimePlayed");
b.Property<double>("VisionAverage");
b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId");
b.ToTable("EFClientStatistics");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId")
.HasColumnName("EFClientStatistics_ClientId");
b.Property<int>("HitCount");
b.Property<float>("HitOffsetAverage");
b.Property<int>("Location");
b.Property<float>("MaxAngleDistance");
b.Property<long>("ServerId")
.HasColumnName("EFClientStatistics_ServerId");
b.HasKey("HitLocationCountId");
b.HasIndex("ServerId");
b.HasIndex("ClientId", "ServerId");
b.ToTable("EFHitLocationCounts");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.Property<int>("RatingId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ActivityAmount");
b.Property<bool>("Newest");
b.Property<double>("Performance");
b.Property<int>("Ranking");
b.Property<int>("RatingHistoryId");
b.Property<long?>("ServerId");
b.Property<DateTime>("When");
b.HasKey("RatingId");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.HasIndex("Performance", "Ranking", "When");
b.ToTable("EFRating");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{
b.Property<long>("ServerId");
b.Property<bool>("Active");
b.Property<string>("EndPoint");
b.Property<int?>("GameName");
b.Property<int>("Port");
b.HasKey("ServerId");
b.ToTable("EFServers");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.Property<int>("StatisticId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<long>("ServerId");
b.Property<long>("TotalKills");
b.Property<long>("TotalPlayTime");
b.HasKey("StatisticId");
b.HasIndex("ServerId");
b.ToTable("EFServerStatistics");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.Property<int>("AliasId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<DateTime>("DateAdded");
b.Property<int?>("IPAddress");
b.Property<int>("LinkId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24);
b.Property<string>("SearchableName")
.HasMaxLength(24);
b.HasKey("AliasId");
b.HasIndex("IPAddress");
b.HasIndex("LinkId");
b.HasIndex("Name");
b.HasIndex("SearchableName");
b.ToTable("EFAlias");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
{
b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
{
b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("Comment")
.HasMaxLength(128);
b.Property<string>("CurrentValue");
b.Property<int>("OriginEntityId");
b.Property<string>("PreviousValue");
b.Property<int>("TargetEntityId");
b.Property<DateTime>("TimeChanged");
b.Property<int>("TypeOfChange");
b.HasKey("ChangeHistoryId");
b.ToTable("EFChangeHistory");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.Property<int>("ClientId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AliasLinkId");
b.Property<int>("Connections");
b.Property<int>("CurrentAliasId");
b.Property<DateTime>("FirstConnection");
b.Property<DateTime>("LastConnection");
b.Property<int>("Level");
b.Property<bool>("Masked");
b.Property<long>("NetworkId");
b.Property<string>("Password");
b.Property<string>("PasswordSalt");
b.Property<int>("TotalConnectionTime");
b.HasKey("ClientId");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.ToTable("EFClients");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.Property<int>("MetaId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<DateTime>("Created");
b.Property<string>("Extra");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(32);
b.Property<DateTime>("Updated");
b.Property<string>("Value")
.IsRequired();
b.HasKey("MetaId");
b.HasIndex("ClientId");
b.HasIndex("Key");
b.ToTable("EFMeta");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("AutomatedOffense");
b.Property<DateTime?>("Expires");
b.Property<bool>("IsEvadedOffense");
b.Property<int>("LinkId");
b.Property<int>("OffenderId");
b.Property<string>("Offense")
.IsRequired();
b.Property<int>("PunisherId");
b.Property<int>("Type");
b.Property<DateTime>("When");
b.HasKey("PenaltyId");
b.HasIndex("LinkId");
b.HasIndex("OffenderId");
b.HasIndex("PunisherId");
b.ToTable("EFPenalties");
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd();
b.Property<float>("X");
b.Property<float>("Y");
b.Property<float>("Z");
b.HasKey("Vector3Id");
b.ToTable("Vector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
.WithMany()
.HasForeignKey("CurrentViewAngleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
.WithMany()
.HasForeignKey("HitDestinationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
.WithMany()
.HasForeignKey("HitOriginId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
.WithMany()
.HasForeignKey("LastStrainAngleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", "Snapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("SnapshotId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "Vector")
.WithMany()
.HasForeignKey("Vector3Id")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
.WithMany()
.HasForeignKey("AttackerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
.WithMany()
.HasForeignKey("DeathOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
.WithMany()
.HasForeignKey("KillOriginVector3Id");
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
.WithMany()
.HasForeignKey("VictimId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
.WithMany()
.HasForeignKey("ViewAnglesVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics")
.WithMany("HitLocations")
.HasForeignKey("ClientId", "ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
.WithMany("Ratings")
.HasForeignKey("RatingHistoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("Children")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
.WithMany()
.HasForeignKey("AliasLinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
.WithMany()
.HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany("Meta")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
.WithMany("ReceivedPenalties")
.HasForeignKey("OffenderId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
.WithMany("AdministeredPenalties")
.HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,89 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace SharedLibraryCore.Migrations
{
public partial class UseJunctionTableForSnapshotVector3 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.ActiveProvider != "Microsoft.EntityFrameworkCore.Sqlite")
{
migrationBuilder.DropForeignKey(
name: "FK_Vector3_EFACSnapshot_EFACSnapshotSnapshotId",
table: "Vector3");
migrationBuilder.DropIndex(
name: "IX_Vector3_EFACSnapshotSnapshotId",
table: "Vector3");
migrationBuilder.DropColumn(
name: "EFACSnapshotSnapshotId",
table: "Vector3");
}
migrationBuilder.CreateTable(
name: "EFACSnapshotVector3",
columns: table => new
{
ACSnapshotVector3Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
SnapshotId = table.Column<int>(nullable: false),
Vector3Id = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EFACSnapshotVector3", x => x.ACSnapshotVector3Id);
table.ForeignKey(
name: "FK_EFACSnapshotVector3_EFACSnapshot_SnapshotId",
column: x => x.SnapshotId,
principalTable: "EFACSnapshot",
principalColumn: "SnapshotId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_EFACSnapshotVector3_Vector3_Vector3Id",
column: x => x.Vector3Id,
principalTable: "Vector3",
principalColumn: "Vector3Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_EFACSnapshotVector3_SnapshotId",
table: "EFACSnapshotVector3",
column: "SnapshotId");
migrationBuilder.CreateIndex(
name: "IX_EFACSnapshotVector3_Vector3Id",
table: "EFACSnapshotVector3",
column: "Vector3Id");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EFACSnapshotVector3");
migrationBuilder.AddColumn<int>(
name: "EFACSnapshotSnapshotId",
table: "Vector3",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Vector3_EFACSnapshotSnapshotId",
table: "Vector3",
column: "EFACSnapshotSnapshotId");
migrationBuilder.AddForeignKey(
name: "FK_Vector3_EFACSnapshot_EFACSnapshotSnapshotId",
table: "Vector3",
column: "EFACSnapshotSnapshotId",
principalTable: "EFACSnapshot",
principalColumn: "SnapshotId",
onDelete: ReferentialAction.Restrict);
}
}
}

View File

@ -82,6 +82,24 @@ namespace SharedLibraryCore.Migrations
b.ToTable("EFACSnapshot"); b.ToTable("EFACSnapshot");
}); });
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
{
b.Property<int>("ACSnapshotVector3Id")
.ValueGeneratedOnAdd();
b.Property<int>("SnapshotId");
b.Property<int>("Vector3Id");
b.HasKey("ACSnapshotVector3Id");
b.HasIndex("SnapshotId");
b.HasIndex("Vector3Id");
b.ToTable("EFACSnapshotVector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{ {
b.Property<long>("KillId") b.Property<long>("KillId")
@ -188,6 +206,8 @@ namespace SharedLibraryCore.Migrations
b.Property<double>("AverageRecoilOffset"); b.Property<double>("AverageRecoilOffset");
b.Property<double>("AverageSnapValue");
b.Property<int>("Deaths"); b.Property<int>("Deaths");
b.Property<double>("EloRating"); b.Property<double>("EloRating");
@ -202,6 +222,8 @@ namespace SharedLibraryCore.Migrations
b.Property<double>("Skill"); b.Property<double>("Skill");
b.Property<int>("SnapHitCount");
b.Property<int>("TimePlayed"); b.Property<int>("TimePlayed");
b.Property<double>("VisionAverage"); b.Property<double>("VisionAverage");
@ -498,8 +520,6 @@ namespace SharedLibraryCore.Migrations
b.Property<int>("Vector3Id") b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
b.Property<int?>("EFACSnapshotSnapshotId");
b.Property<float>("X"); b.Property<float>("X");
b.Property<float>("Y"); b.Property<float>("Y");
@ -508,8 +528,6 @@ namespace SharedLibraryCore.Migrations
b.HasKey("Vector3Id"); b.HasKey("Vector3Id");
b.HasIndex("EFACSnapshotSnapshotId");
b.ToTable("Vector3"); b.ToTable("Vector3");
}); });
@ -541,6 +559,19 @@ namespace SharedLibraryCore.Migrations
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
}); });
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", "Snapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("SnapshotId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "Vector")
.WithMany()
.HasForeignKey("Vector3Id")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{ {
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker") b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
@ -689,13 +720,6 @@ namespace SharedLibraryCore.Migrations
.HasForeignKey("PunisherId") .HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict);
}); });
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("EFACSnapshotSnapshotId");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

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];
@ -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

@ -29,6 +29,8 @@
<Compile Remove="Migrations\20181127143920_AddEndpointToEFServerUpdateServerIdType.cs" /> <Compile Remove="Migrations\20181127143920_AddEndpointToEFServerUpdateServerIdType.cs" />
<Compile Remove="Migrations\20190222234606_AddIndexToEFMeta-KeyAndClientId.cs" /> <Compile Remove="Migrations\20190222234606_AddIndexToEFMeta-KeyAndClientId.cs" />
<Compile Remove="Migrations\20190223012312_SetMaxLengthForMetaKey.cs" /> <Compile Remove="Migrations\20190223012312_SetMaxLengthForMetaKey.cs" />
<Compile Remove="Migrations\20190907222702_AddMomentViewAnglesToAcSnapshot.cs" />
<Compile Remove="Migrations\20190907222702_AddMomentViewAnglesToAcSnapshot.Designer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -46,7 +48,7 @@
<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.7" /> <PackageReference Include="Npgsql" Version="4.0.9" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.2.4" /> <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" />

View File

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -24,7 +25,7 @@ namespace WebfrontCore.Controllers
if (!_fileCache.ContainsKey(fileName)) if (!_fileCache.ContainsKey(fileName))
{ {
string path = $"wwwroot\\css\\{fileName}"; string path = $"wwwroot{Path.DirectorySeparatorChar}css{Path.DirectorySeparatorChar}{fileName}";
string data = await System.IO.File.ReadAllTextAsync(path); string data = await System.IO.File.ReadAllTextAsync(path);
data = await Manager.MiddlewareActionHandler.Execute(data, "custom_css_accent"); data = await Manager.MiddlewareActionHandler.Execute(data, "custom_css_accent");
_fileCache.Add(fileName, data); _fileCache.Add(fileName, data);

View File

@ -6,8 +6,8 @@ init()
{ {
SetDvarIfUninitialized( "sv_customcallbacks", true ); SetDvarIfUninitialized( "sv_customcallbacks", true );
SetDvarIfUninitialized( "sv_framewaittime", 0.05 ); SetDvarIfUninitialized( "sv_framewaittime", 0.05 );
SetDvarIfUninitialized( "sv_additionalwaittime", 0.05 ); SetDvarIfUninitialized( "sv_additionalwaittime", 0.1 );
SetDvarIfUninitialized( "sv_maxstoredframes", 4 ); SetDvarIfUninitialized( "sv_maxstoredframes", 12 );
SetDvarIfUninitialized( "sv_printradarupdates", false ); SetDvarIfUninitialized( "sv_printradarupdates", false );
SetDvarIfUninitialized( "sv_printradar_updateinterval", 500 ); SetDvarIfUninitialized( "sv_printradar_updateinterval", 500 );
SetDvarIfUninitialized( "sv_iw4madmin_url", "http://127.0.0.1:1624" ); SetDvarIfUninitialized( "sv_iw4madmin_url", "http://127.0.0.1:1624" );
@ -111,6 +111,11 @@ waitForFrameThread()
self.currentAnglePosition = 0; self.currentAnglePosition = 0;
self.anglePositions = []; self.anglePositions = [];
for (i = 0; i < getDvarInt( "sv_maxstoredframes" ); i++)
{
self.anglePositions[i] = self getPlayerAngles();
}
for( ;; ) for( ;; )
{ {
self.anglePositions[self.currentAnglePosition] = self getPlayerAngles(); self.anglePositions[self.currentAnglePosition] = self getPlayerAngles();
@ -119,31 +124,52 @@ waitForFrameThread()
} }
} }
waitForAdditionalAngles( logString ) waitForAdditionalAngles( logString, beforeFrameCount, afterFrameCount )
{ {
wait( getDvarFloat( "sv_additionalwaittime" ) ); currentIndex = self.currentAnglePosition;
wait( 0.05 * afterFrameCount );
self.angleSnapshot = []; self.angleSnapshot = [];
for( i = 0; i < getDvarInt( "sv_maxstoredframes" ); i++ ) for( j = 0; j < self.anglePositions.size; j++ )
{ {
self.angleSnapshot[i] = self.anglePositions[i]; self.angleSnapshot[j] = self.anglePositions[j];
} }
currentPos = self.currentAnglePosition;
anglesStr = ""; anglesStr = "";
collectedFrames = 0;
i = currentIndex - beforeFrameCount;
i = 0; while (collectedFrames < beforeFrameCount)
if ( currentPos < getDvarInt( "sv_maxstoredframes" ) - 1 )
{ {
i = currentPos + 1; fixedIndex = i;
if (i < 0)
{
fixedIndex = self.angleSnapshot.size - abs(i);
}
anglesStr += self.angleSnapshot[fixedIndex] + ":";
collectedFrames++;
i++;
} }
while( i != currentPos ) if (i == currentIndex)
{ {
anglesStr += self.angleSnapshot[i] + ":"; anglesStr += self.angleSnapshot[i] + ":";
i = (i + 1) % getDvarInt( "sv_maxstoredframes" ); i++;
}
collectedFrames = 0;
while (collectedFrames < afterFrameCount)
{
fixedIndex = i;
if (i > self.angleSnapshot.size - 1)
{
fixedIndex = i % self.angleSnapshot.size;
}
anglesStr += self.angleSnapshot[fixedIndex] + ":";
collectedFrames++;
i++;
} }
logPrint(logString + ";" + anglesStr + "\n" ); logPrint(logString + ";" + anglesStr + "\n" );
@ -156,6 +182,11 @@ vectorScale( vector, scale )
Process_Hit( type, attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon ) Process_Hit( type, attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon )
{ {
if (sMeansOfDeath == "MOD_FALLING" || !isPlayer(attacker))
{
return;
}
victim = self; victim = self;
_attacker = attacker; _attacker = attacker;
@ -172,8 +203,8 @@ Process_Hit( type, attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon )
location = victim GetTagOrigin( hitLocationToBone( sHitLoc ) ); location = victim GetTagOrigin( hitLocationToBone( sHitLoc ) );
isKillstreakKill = !isPlayer( attacker ) || isKillstreakWeapon( sWeapon ); isKillstreakKill = !isPlayer( attacker ) || isKillstreakWeapon( sWeapon );
logLine = "Script" + type + ";" + _attacker.guid + ";" + victim.guid + ";" + _attacker GetTagOrigin("tag_eye") + ";" + location + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + ";" + _attacker getPlayerAngles() + ";" + gettime() + ";" + isKillstreakKill + ";" + _attacker playerADS() + ";0;0;"; logLine = "Script" + type + ";" + _attacker.guid + ";" + victim.guid + ";" + _attacker GetTagOrigin("tag_eye") + ";" + location + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + ";" + _attacker getPlayerAngles() + ";" + gettime() + ";" + isKillstreakKill + ";" + _attacker playerADS() + ";0;0";
attacker thread waitForAdditionalAngles( logLine ); attacker thread waitForAdditionalAngles( logLine, 2, 2 );
} }
Callback_PlayerDamage( eInflictor, attacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime ) Callback_PlayerDamage( eInflictor, attacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime )

View File

@ -6,6 +6,7 @@ Version 2.4:
-added ability to customize accent color and branding on webfront -added ability to customize accent color and branding on webfront
-added flag button to client profile -added flag button to client profile
-hid flagged status of users on webfront unless logged in -hid flagged status of users on webfront unless logged in
-added snap anticheat metric
Version 2.3: Version 2.3:
-added configuration option to ignore bots -added configuration option to ignore bots