diff --git a/Application/Application.csproj b/Application/Application.csproj index 45dca7973..9d9c84940 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -23,8 +23,8 @@ - - + + diff --git a/Application/GameEventHandler.cs b/Application/GameEventHandler.cs index cebbd5926..0a4c00ec3 100644 --- a/Application/GameEventHandler.cs +++ b/Application/GameEventHandler.cs @@ -24,6 +24,16 @@ namespace IW4MAdmin.Application public void AddEvent(GameEvent gameEvent) { + ((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent)); + if (gameEvent.Type == GameEvent.EventType.Connect) + { + if (!gameEvent.OnProcessed.Wait(30 * 1000)) + { + Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]"); + } + } + + return; #if DEBUG Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner} with id {gameEvent.Id}"); #endif @@ -48,16 +58,16 @@ namespace IW4MAdmin.Application // event occurs if (gameEvent.Id == Interlocked.Read(ref NextEventId)) { -//#if DEBUG == true -// Manager.GetLogger().WriteDebug($"sent event with id {gameEvent.Id} to be processed"); -// IsProcessingEvent.Wait(); -//#else -// if (GameEvent.IsEventTimeSensitive(gameEvent) && -// !IsProcessingEvent.Wait(30 * 1000)) -// { -// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]"); -// } -//#endif + //#if DEBUG == true + // Manager.GetLogger().WriteDebug($"sent event with id {gameEvent.Id} to be processed"); + // IsProcessingEvent.Wait(); + //#else + // if (GameEvent.IsEventTimeSensitive(gameEvent) && + // !IsProcessingEvent.Wait(30 * 1000)) + // { + // Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]"); + // } + //#endif ((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent)); //if (GameEvent.IsEventTimeSensitive(gameEvent)) diff --git a/Application/Server.cs b/Application/Server.cs index 620c5678b..0fb40c348 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -341,26 +341,14 @@ namespace IW4MAdmin /// override protected async Task ProcessEvent(GameEvent E) { - - if (E.Type == GameEvent.EventType.StatusUpdate) - { - // this event gets called before they're full connected - if (E.Origin != null) - { - //var existingClient = Players[E.Origin.ClientNumber] ?? E.Origin; - //existingClient.Ping = E.Origin.Ping; - //existingClient.Score = E.Origin.Score; - } - } - - else if (E.Type == GameEvent.EventType.Connect) + if (E.Type == GameEvent.EventType.Connect) { E.Origin.State = Player.ClientState.Authenticated; // add them to the server if (!await AddPlayer(E.Origin)) { E.Origin.State = Player.ClientState.Connecting; - throw new ServerException("client didn't pass authorization, so we are discontinuing event"); + throw new ServerException("client didn't pass authentication, so we are discontinuing event"); } // hack: makes the event propgate with the correct info E.Origin = Players[E.Origin.ClientNumber]; diff --git a/Plugins/Stats/Helpers/Extensions.cs b/Plugins/Stats/Helpers/Extensions.cs index e771607ae..12b860d51 100644 --- a/Plugins/Stats/Helpers/Extensions.cs +++ b/Plugins/Stats/Helpers/Extensions.cs @@ -25,7 +25,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public static double[] AngleStuff(Vector3 a, Vector3 b) { - double deltaX = 180.0 -Math.Abs(Math.Abs(a.X - b.X) - 180.0); + double deltaX = 180.0 - Math.Abs(Math.Abs(a.X - b.X) - 180.0); double deltaY = 180.0 - Math.Abs(Math.Abs(a.Y - b.Y) - 180.0); return new[] { deltaX, deltaY }; diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 029341686..4db734941 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -28,7 +28,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers private ILogger Log; private IManager Manager; - static readonly object LockObj = new object(); + + private readonly SemaphoreSlim OnProcessingPenalty; public StatManager(IManager mgr) { @@ -36,6 +37,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers ContextThreads = new ConcurrentDictionary(); Log = mgr.GetLogger(); Manager = mgr; + OnProcessingPenalty = new SemaphoreSlim(1, 1); } public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId]; @@ -44,7 +46,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15); return (r) => r.ServerId == serverId && - r.RatingHistory.Client.LastConnection > fifteenDaysAgo && + r.When > fifteenDaysAgo && r.RatingHistory.Client.Level != Player.Permission.Banned && r.Newest && r.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime; @@ -90,26 +92,49 @@ namespace IW4MAdmin.Plugins.Stats.Helpers .Where(GetRankingFunc()) select new { - Ratings = rating.RatingHistory.Ratings.Where(r => r.ServerId == null), rating.RatingHistory.ClientId, rating.RatingHistory.Client.CurrentAlias.Name, rating.RatingHistory.Client.LastConnection, rating.Performance, - }).OrderByDescending(c => c.Performance) + }) + .OrderByDescending(c => c.Performance) .Skip(start) .Take(count); #if DEBUG == true - var clientRatingsSql = iqClientRatings.ToSql(); + var clientRatingsSql = iqClientRatings.ToSql(); #endif // materialized list var clientRatings = await iqClientRatings.ToListAsync(); - // get all the client ids that + // get all the unique client ids that are in the top stats var clientIds = clientRatings .GroupBy(r => r.ClientId) .Select(r => r.First().ClientId) .ToList(); + var iqRatingInfo = from rating in context.Set() + where clientIds.Contains(rating.RatingHistory.ClientId) + where rating.ServerId == null + select new + { + rating.Ranking, + rating.Performance, + rating.RatingHistory.ClientId, + rating.When + }; + +#if DEBUG == true + var ratingQuery = iqRatingInfo.ToSql(); +#endif + + var ratingInfo = (await iqRatingInfo.ToListAsync()) + .GroupBy(r => r.ClientId) + .Select(grp => new + { + grp.Key, + Ratings = grp.Select(r => new { r.Performance, r.Ranking, r.When }) + }); + var iqStatsInfo = (from stat in context.Set() where clientIds.Contains(stat.ClientId) group stat by stat.ClientId into s @@ -121,366 +146,370 @@ namespace IW4MAdmin.Plugins.Stats.Helpers KDR = s.Sum(c => (c.Kills / (double)(c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) / s.Sum(c => c.TimePlayed), TotalTimePlayed = s.Sum(c => c.TimePlayed) }); + #if DEBUG == true - var statsInfoSql = iqStatsInfo.ToSql(); + var statsInfoSql = iqStatsInfo.ToSql(); #endif - var topPlayers = await iqStatsInfo.ToListAsync(); + var topPlayers = await iqStatsInfo.ToListAsync(); - var clientRatingsDict = clientRatings.ToDictionary(r => r.ClientId); - var finished = topPlayers.Select(s => new TopStatsInfo() - { - ClientId = s.ClientId, - Deaths = s.Deaths, - Kills = s.Kills, - KDR = Math.Round(s.KDR, 2), - LastSeen = Utilities.GetTimePassed(clientRatingsDict[s.ClientId].LastConnection, false), - Name = clientRatingsDict[s.ClientId].Name, - Performance = Math.Round(clientRatingsDict[s.ClientId].Performance, 2), - RatingChange = clientRatingsDict[s.ClientId].Ratings.First().Ranking - clientRatingsDict[s.ClientId].Ratings.Last().Ranking, - PerformanceHistory = clientRatingsDict[s.ClientId].Ratings.Count() > 1 ? - clientRatingsDict[s.ClientId].Ratings.Select(r => r.Performance).ToList() : - new List() { clientRatingsDict[s.ClientId].Performance, clientRatingsDict[s.ClientId].Performance }, - TimePlayed = Math.Round(s.TotalTimePlayed / 3600.0, 1).ToString("#,##0"), - }) - .OrderByDescending(r => r.Performance) - .ToList(); + var clientRatingsDict = clientRatings.ToDictionary(r => r.ClientId); + var finished = topPlayers.Select(s => new TopStatsInfo() + { + ClientId = s.ClientId, + Deaths = s.Deaths, + Kills = s.Kills, + KDR = Math.Round(s.KDR, 2), + LastSeen = Utilities.GetTimePassed(clientRatingsDict[s.ClientId].LastConnection, false), + Name = clientRatingsDict[s.ClientId].Name, + Performance = Math.Round(clientRatingsDict[s.ClientId].Performance, 2), + RatingChange = ratingInfo.First(r => r.Key == s.ClientId).Ratings.First().Ranking - ratingInfo.First(r => r.Key == s.ClientId).Ratings.Last().Ranking, + PerformanceHistory = ratingInfo.First(r => r.Key == s.ClientId).Ratings.Count() > 1 ? + ratingInfo.First(r => r.Key == s.ClientId).Ratings.OrderBy(r => r.When).Select(r => r.Performance).ToList() : + new List() { clientRatingsDict[s.ClientId].Performance, clientRatingsDict[s.ClientId].Performance }, + TimePlayed = Math.Round(s.TotalTimePlayed / 3600.0, 1).ToString("#,##0"), + }) + .OrderByDescending(r => r.Performance) + .ToList(); - // set the ranking numerically - int i = start + 1; - foreach (var stat in finished) - { - stat.Ranking = i; - i++; - } - - return finished; + // set the ranking numerically + int i = start + 1; + foreach (var stat in finished) + { + stat.Ranking = i; + i++; } + + return finished; } + } - /// - /// Add a server to the StatManager server pool - /// - /// - public void AddServer(Server sv) + /// + /// Add a server to the StatManager server pool + /// + /// + public void AddServer(Server sv) + { + try { - try - { - int serverId = sv.GetHashCode(); - var statsSvc = new ThreadSafeStatsService(); - ContextThreads.TryAdd(serverId, statsSvc); + int serverId = sv.GetHashCode(); + var statsSvc = new ThreadSafeStatsService(); + ContextThreads.TryAdd(serverId, statsSvc); - // get the server from the database if it exists, otherwise create and insert a new one - var server = statsSvc.ServerSvc.Find(c => c.ServerId == serverId).FirstOrDefault(); - if (server == null) - { - server = new EFServer() - { - Port = sv.GetPort(), - Active = true, - ServerId = serverId - }; - - statsSvc.ServerSvc.Insert(server); - } - - // this doesn't need to be async as it's during initialization - statsSvc.ServerSvc.SaveChanges(); - // check to see if the stats have ever been initialized - InitializeServerStats(sv); - statsSvc.ServerStatsSvc.SaveChanges(); - - var serverStats = statsSvc.ServerStatsSvc.Find(c => c.ServerId == serverId).FirstOrDefault(); - Servers.TryAdd(serverId, new ServerStats(server, serverStats) - { - IsTeamBased = sv.Gametype != "dm" - }); - } - - catch (Exception e) - { - Log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}"); - } - } - - /// - /// Add Player to the player stats - /// - /// Player to add/retrieve stats for - /// EFClientStatistic of specified player - public async Task AddPlayer(Player pl) - { - int serverId = pl.CurrentServer.GetHashCode(); - - if (!Servers.ContainsKey(serverId)) - { - Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found"); - return null; - } - - var playerStats = Servers[serverId].PlayerStats; - var statsSvc = ContextThreads[serverId]; - var detectionStats = Servers[serverId].PlayerDetections; - - if (playerStats.ContainsKey(pl.ClientId)) - { - Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}"); - return null; - } - - // get the client's stats from the database if it exists, otherwise create and attach a new one - // if this fails we want to throw an exception - var clientStatsSvc = statsSvc.ClientStatSvc; - var clientStats = clientStatsSvc.Find(c => c.ClientId == pl.ClientId && c.ServerId == serverId).FirstOrDefault(); - - if (clientStats == null) - { - clientStats = new EFClientStatistics() + // get the server from the database if it exists, otherwise create and insert a new one + var server = statsSvc.ServerSvc.Find(c => c.ServerId == serverId).FirstOrDefault(); + if (server == null) + { + server = new EFServer() { + Port = sv.GetPort(), Active = true, - ClientId = pl.ClientId, - Deaths = 0, - Kills = 0, - ServerId = serverId, - Skill = 0.0, - SPM = 0.0, - EloRating = 200.0, - HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType().Select(hl => new EFHitLocationCount() - { - Active = true, - HitCount = 0, - Location = hl - }).ToList() + ServerId = serverId }; - // insert if they've not been added - clientStats = clientStatsSvc.Insert(clientStats); - await clientStatsSvc.SaveChangesAsync(); + statsSvc.ServerSvc.Insert(server); } - // migration for previous existing stats - if (clientStats.HitLocations.Count == 0) + // this doesn't need to be async as it's during initialization + statsSvc.ServerSvc.SaveChanges(); + // check to see if the stats have ever been initialized + InitializeServerStats(sv); + statsSvc.ServerStatsSvc.SaveChanges(); + + var serverStats = statsSvc.ServerStatsSvc.Find(c => c.ServerId == serverId).FirstOrDefault(); + Servers.TryAdd(serverId, new ServerStats(server, serverStats) { - clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType().Select(hl => new EFHitLocationCount() + IsTeamBased = sv.Gametype != "dm" + }); + } + + catch (Exception e) + { + Log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}"); + } + } + + /// + /// Add Player to the player stats + /// + /// Player to add/retrieve stats for + /// EFClientStatistic of specified player + public async Task AddPlayer(Player pl) + { + int serverId = pl.CurrentServer.GetHashCode(); + + if (!Servers.ContainsKey(serverId)) + { + Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found"); + return null; + } + + var playerStats = Servers[serverId].PlayerStats; + var statsSvc = ContextThreads[serverId]; + var detectionStats = Servers[serverId].PlayerDetections; + + if (playerStats.ContainsKey(pl.ClientId)) + { + Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}"); + return null; + } + + // get the client's stats from the database if it exists, otherwise create and attach a new one + // if this fails we want to throw an exception + var clientStatsSvc = statsSvc.ClientStatSvc; + var clientStats = clientStatsSvc.Find(c => c.ClientId == pl.ClientId && c.ServerId == serverId).FirstOrDefault(); + + if (clientStats == null) + { + clientStats = new EFClientStatistics() + { + Active = true, + ClientId = pl.ClientId, + Deaths = 0, + Kills = 0, + ServerId = serverId, + Skill = 0.0, + SPM = 0.0, + EloRating = 200.0, + HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType().Select(hl => new EFHitLocationCount() { Active = true, HitCount = 0, Location = hl - }) - .ToList(); - //await statsSvc.ClientStatSvc.SaveChangesAsync(); - } - - // for stats before rating - if (clientStats.EloRating == 0.0) - { - clientStats.EloRating = clientStats.Skill; - } - - if (clientStats.RollingWeightedKDR == 0) - { - clientStats.RollingWeightedKDR = clientStats.KDR; - } - - // set these on connecting - clientStats.LastActive = DateTime.UtcNow; - clientStats.LastStatCalculation = DateTime.UtcNow; - clientStats.SessionScore = pl.Score; - clientStats.LastScore = pl.Score; - - Log.WriteInfo($"Adding {pl} to stats"); - - if (!playerStats.TryAdd(pl.ClientId, clientStats)) - Log.WriteDebug($"Could not add client to stats {pl}"); - - if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats))) - Log.WriteDebug("Could not add client to detection"); - - return clientStats; - } - - /// - /// Perform stat updates for disconnecting client - /// - /// Disconnecting client - /// - public async Task RemovePlayer(Player pl) - { - Log.WriteInfo($"Removing {pl} from stats"); - - int serverId = pl.CurrentServer.GetHashCode(); - var playerStats = Servers[serverId].PlayerStats; - var detectionStats = Servers[serverId].PlayerDetections; - var serverStats = Servers[serverId].ServerStatistics; - var statsSvc = ContextThreads[serverId]; - - if (!playerStats.ContainsKey(pl.ClientId)) - { - Log.WriteWarning($"Client disconnecting not in stats {pl}"); - // remove the client from the stats dictionary as they're leaving - playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue1); - detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2); - return; - } - - // get individual client's stats - var clientStats = playerStats[pl.ClientId]; - -#if DEBUG == true - await UpdateStatHistory(pl, clientStats); -#endif - - // remove the client from the stats dictionary as they're leaving - playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3); - detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4); - - // sync their stats before they leave - var clientStatsSvc = statsSvc.ClientStatSvc; - clientStats = UpdateStats(clientStats); - clientStatsSvc.Update(clientStats); - await clientStatsSvc.SaveChangesAsync(); - - // increment the total play time - serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds; - } - - public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, int serverId) - { - string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$"; - var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase); - - if (match.Success) - { - // this gives us what time the player is on - var attackerStats = Servers[serverId].PlayerStats[attackerClientId]; - var victimStats = Servers[serverId].PlayerStats[victimClientId]; - IW4Info.Team victimTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[4].ToString()); - IW4Info.Team attackerTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[7].ToString()); - attackerStats.Team = attackerTeam; - victimStats.Team = victimTeam; - } - } - - /// - /// Process stats for kill event - /// - /// - public async Task AddScriptHit(bool isDamage, DateTime time, Player attacker, Player victim, int serverId, string map, string hitLoc, string type, - string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads, - string fraction, string visibilityPercentage, string snapAngles) - { - var statsSvc = ContextThreads[serverId]; - Vector3 vDeathOrigin = null; - Vector3 vKillOrigin = null; - Vector3 vViewAngles = null; - - try - { - vDeathOrigin = Vector3.Parse(deathOrigin); - vKillOrigin = Vector3.Parse(killOrigin); - vViewAngles = Vector3.Parse(viewAngles).FixIW4Angles(); - } - - catch (FormatException) - { - Log.WriteWarning("Could not parse kill or death origin or viewangle vectors"); - Log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles}"); - await AddStandardKill(attacker, victim); - return; - } - - var snapshotAngles = new List(); - - try - { - foreach (string angle in snapAngles.Split(':', StringSplitOptions.RemoveEmptyEntries)) - { - snapshotAngles.Add(Vector3.Parse(angle).FixIW4Angles()); - } - } - - catch (FormatException) - { - Log.WriteWarning("Could not parse snapshot angles"); - return; - } - - var hit = new EFClientKill() - { - Active = true, - AttackerId = attacker.ClientId, - VictimId = victim.ClientId, - ServerId = serverId, - Map = ParseEnum.Get(map, typeof(IW4Info.MapName)), - DeathOrigin = vDeathOrigin, - KillOrigin = vKillOrigin, - DeathType = ParseEnum.Get(type, typeof(IW4Info.MeansOfDeath)), - Damage = Int32.Parse(damage), - HitLoc = ParseEnum.Get(hitLoc, typeof(IW4Info.HitLocation)), - Weapon = ParseEnum.Get(weapon, typeof(IW4Info.WeaponName)), - ViewAngles = vViewAngles, - TimeOffset = Int64.Parse(offset), - When = time, - IsKillstreakKill = isKillstreakKill[0] != '0', - AdsPercent = float.Parse(Ads), - Fraction = double.Parse(fraction), - VisibilityPercentage = double.Parse(visibilityPercentage), - IsKill = !isDamage, - AnglesList = snapshotAngles + }).ToList() }; - if (hit.DeathType == IW4Info.MeansOfDeath.MOD_SUICIDE && - hit.Damage == 100000) + // insert if they've not been added + clientStats = clientStatsSvc.Insert(clientStats); + await clientStatsSvc.SaveChangesAsync(); + } + + // migration for previous existing stats + if (clientStats.HitLocations.Count == 0) + { + clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType().Select(hl => new EFHitLocationCount() { - // suicide by switching teams so let's not count it against them - return; + Active = true, + HitCount = 0, + Location = hl + }) + .ToList(); + //await statsSvc.ClientStatSvc.SaveChangesAsync(); + } + + // for stats before rating + if (clientStats.EloRating == 0.0) + { + clientStats.EloRating = clientStats.Skill; + } + + if (clientStats.RollingWeightedKDR == 0) + { + clientStats.RollingWeightedKDR = clientStats.KDR; + } + + // set these on connecting + clientStats.LastActive = DateTime.UtcNow; + clientStats.LastStatCalculation = DateTime.UtcNow; + clientStats.SessionScore = pl.Score; + clientStats.LastScore = pl.Score; + + Log.WriteInfo($"Adding {pl} to stats"); + + if (!playerStats.TryAdd(pl.ClientId, clientStats)) + Log.WriteDebug($"Could not add client to stats {pl}"); + + if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats))) + Log.WriteDebug("Could not add client to detection"); + + return clientStats; + } + + /// + /// Perform stat updates for disconnecting client + /// + /// Disconnecting client + /// + public async Task RemovePlayer(Player pl) + { + Log.WriteInfo($"Removing {pl} from stats"); + + int serverId = pl.CurrentServer.GetHashCode(); + var playerStats = Servers[serverId].PlayerStats; + var detectionStats = Servers[serverId].PlayerDetections; + var serverStats = Servers[serverId].ServerStatistics; + var statsSvc = ContextThreads[serverId]; + + if (!playerStats.ContainsKey(pl.ClientId)) + { + Log.WriteWarning($"Client disconnecting not in stats {pl}"); + // remove the client from the stats dictionary as they're leaving + playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue1); + detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2); + return; + } + + // get individual client's stats + var clientStats = playerStats[pl.ClientId]; + +#if DEBUG == true + await UpdateStatHistory(pl, clientStats); +#endif + + // remove the client from the stats dictionary as they're leaving + playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3); + detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4); + + // sync their stats before they leave + var clientStatsSvc = statsSvc.ClientStatSvc; + clientStats = UpdateStats(clientStats); + clientStatsSvc.Update(clientStats); + await clientStatsSvc.SaveChangesAsync(); + + // increment the total play time + serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds; + } + + public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, int serverId) + { + string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$"; + var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase); + + if (match.Success) + { + // this gives us what time the player is on + var attackerStats = Servers[serverId].PlayerStats[attackerClientId]; + var victimStats = Servers[serverId].PlayerStats[victimClientId]; + IW4Info.Team victimTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[4].ToString()); + IW4Info.Team attackerTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[7].ToString()); + attackerStats.Team = attackerTeam; + victimStats.Team = victimTeam; + } + } + + /// + /// Process stats for kill event + /// + /// + public async Task AddScriptHit(bool isDamage, DateTime time, Player attacker, Player victim, int serverId, string map, string hitLoc, string type, + string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads, + string fraction, string visibilityPercentage, string snapAngles) + { + var statsSvc = ContextThreads[serverId]; + Vector3 vDeathOrigin = null; + Vector3 vKillOrigin = null; + Vector3 vViewAngles = null; + + try + { + vDeathOrigin = Vector3.Parse(deathOrigin); + vKillOrigin = Vector3.Parse(killOrigin); + vViewAngles = Vector3.Parse(viewAngles).FixIW4Angles(); + } + + catch (FormatException) + { + Log.WriteWarning("Could not parse kill or death origin or viewangle vectors"); + Log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles}"); + await AddStandardKill(attacker, victim); + return; + } + + var snapshotAngles = new List(); + + try + { + foreach (string angle in snapAngles.Split(':', StringSplitOptions.RemoveEmptyEntries)) + { + snapshotAngles.Add(Vector3.Parse(angle).FixIW4Angles()); } + } - if (!isDamage) + catch (FormatException) + { + Log.WriteWarning("Could not parse snapshot angles"); + return; + } + + var hit = new EFClientKill() + { + Active = true, + AttackerId = attacker.ClientId, + VictimId = victim.ClientId, + ServerId = serverId, + Map = ParseEnum.Get(map, typeof(IW4Info.MapName)), + DeathOrigin = vDeathOrigin, + KillOrigin = vKillOrigin, + DeathType = ParseEnum.Get(type, typeof(IW4Info.MeansOfDeath)), + Damage = Int32.Parse(damage), + HitLoc = ParseEnum.Get(hitLoc, typeof(IW4Info.HitLocation)), + Weapon = ParseEnum.Get(weapon, typeof(IW4Info.WeaponName)), + ViewAngles = vViewAngles, + TimeOffset = Int64.Parse(offset), + When = time, + IsKillstreakKill = isKillstreakKill[0] != '0', + AdsPercent = float.Parse(Ads), + Fraction = double.Parse(fraction), + VisibilityPercentage = double.Parse(visibilityPercentage), + IsKill = !isDamage, + AnglesList = snapshotAngles + }; + + if (hit.DeathType == IW4Info.MeansOfDeath.MOD_SUICIDE && + hit.Damage == 100000) + { + // suicide by switching teams so let's not count it against them + return; + } + + if (!isDamage) + { + await AddStandardKill(attacker, victim); + } + + if (hit.IsKillstreakKill) + { + return; + } + + var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId]; + var clientStats = Servers[serverId].PlayerStats[attacker.ClientId]; + var clientStatsSvc = statsSvc.ClientStatSvc; + clientStatsSvc.Update(clientStats); + + // increment their hit count + if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || + hit.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || + hit.DeathType == IW4Info.MeansOfDeath.MOD_HEAD_SHOT) + { + clientStats.HitLocations.Single(hl => hl.Location == hit.HitLoc).HitCount += 1; + } + + if (Plugin.Config.Configuration().EnableAntiCheat) + { + async Task executePenalty(Cheat.DetectionPenaltyResult penalty) { - await AddStandardKill(attacker, victim); - } - - if (hit.IsKillstreakKill) - { - return; - } - - var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId]; - var clientStats = Servers[serverId].PlayerStats[attacker.ClientId]; - var clientStatsSvc = statsSvc.ClientStatSvc; - clientStatsSvc.Update(clientStats); - - // increment their hit count - if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || - hit.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || - hit.DeathType == IW4Info.MeansOfDeath.MOD_HEAD_SHOT) - { - clientStats.HitLocations.Single(hl => hl.Location == hit.HitLoc).HitCount += 1; - } - - if (Plugin.Config.Configuration().EnableAntiCheat) - { - async Task executePenalty(Cheat.DetectionPenaltyResult penalty) + async Task saveLog() { - async Task saveLog() + using (var ctx = new DatabaseContext(false)) { - using (var ctx = new DatabaseContext()) + // todo: why does this cause duplicate primary key + foreach (var change in clientDetection.Tracker.GetChanges().Distinct()) { - // todo: why does this cause duplicate primary key - foreach (var change in clientDetection.Tracker.GetChanges().Distinct()) - { - ctx.Add(change); - } + ctx.Add(change); await ctx.SaveChangesAsync(); } } + } + await OnProcessingPenalty.WaitAsync(); + + try + { switch (penalty.ClientPenalty) { case Penalty.PenaltyType.Ban: if (attacker.Level == Player.Permission.Banned) break; - await saveLog(); await attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], new Player() { ClientId = 1, @@ -494,11 +523,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } } }); + await saveLog(); break; case Penalty.PenaltyType.Flag: if (attacker.Level != Player.Permission.User) break; - await saveLog(); var e = new GameEvent() { Data = penalty.Type == Cheat.Detection.DetectionType.Bone ? @@ -515,129 +544,137 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Owner = attacker.CurrentServer, Type = GameEvent.EventType.Flag }; + await saveLog(); // because we created an event it must be processed by the manager // even if it didn't really do anything Manager.GetEventHandler().AddEvent(e); await new CFlag().ExecuteAsync(e); break; } + OnProcessingPenalty.Release(); + } + catch + { + OnProcessingPenalty.Release(); } - await executePenalty(clientDetection.ProcessKill(hit, isDamage)); - await executePenalty(clientDetection.ProcessTotalRatio(clientStats)); - - await clientStatsSvc.SaveChangesAsync(); } - using (var ctx = new DatabaseContext()) - { - ctx.Set().Add(hit); - await ctx.SaveChangesAsync(); - } - } + await executePenalty(clientDetection.ProcessKill(hit, isDamage)); + await executePenalty(clientDetection.ProcessTotalRatio(clientStats)); - public async Task AddStandardKill(Player attacker, Player victim) - { - int serverId = attacker.CurrentServer.GetHashCode(); - EFClientStatistics attackerStats = null; - try - { - attackerStats = Servers[serverId].PlayerStats[attacker.ClientId]; - } - - catch (KeyNotFoundException) - { - // happens when the client has disconnected before the last status update - Log.WriteWarning($"[Stats::AddStandardKill] kill attacker ClientId is invalid {attacker.ClientId}-{attacker}"); - return; - } - - EFClientStatistics victimStats = null; - try - { - victimStats = Servers[serverId].PlayerStats[victim.ClientId]; - } - - catch (KeyNotFoundException) - { - Log.WriteWarning($"[Stats::AddStandardKill] kill victim ClientId is invalid {victim.ClientId}-{victim}"); - return; - } - -#if DEBUG - Log.WriteDebug("Calculating standard kill"); -#endif - - // update the total stats - Servers[serverId].ServerStatistics.TotalKills += 1; - - // this happens when the round has changed - if (attackerStats.SessionScore == 0) - attackerStats.LastScore = 0; - - if (victimStats.SessionScore == 0) - victimStats.LastScore = 0; - - attackerStats.SessionScore = attacker.Score; - victimStats.SessionScore = victim.Score; - - // calculate for the clients - CalculateKill(attackerStats, victimStats); - // this should fix the negative SPM - // updates their last score after being calculated - attackerStats.LastScore = attacker.Score; - victimStats.LastScore = victim.Score; - - // show encouragement/discouragement - string streakMessage = (attackerStats.ClientId != victimStats.ClientId) ? - StreakMessage.MessageOnStreak(attackerStats.KillStreak, attackerStats.DeathStreak) : - StreakMessage.MessageOnStreak(-1, -1); - - if (streakMessage != string.Empty) - await attacker.Tell(streakMessage); - - // fixme: why? - if (double.IsNaN(victimStats.SPM) || double.IsNaN(victimStats.Skill)) - { - Log.WriteDebug($"[StatManager::AddStandardKill] victim SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); - victimStats.SPM = 0.0; - victimStats.Skill = 0.0; - } - - if (double.IsNaN(attackerStats.SPM) || double.IsNaN(attackerStats.Skill)) - { - Log.WriteDebug($"[StatManager::AddStandardKill] attacker SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); - attackerStats.SPM = 0.0; - attackerStats.Skill = 0.0; - } - - // update their performance -#if !DEBUG - if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 2.5) -#endif - { - attackerStats.LastStatHistoryUpdate = DateTime.UtcNow; - await UpdateStatHistory(attacker, attackerStats); - } - - // todo: do we want to save this immediately? - var clientStatsSvc = ContextThreads[serverId].ClientStatSvc; - clientStatsSvc.Update(attackerStats); - clientStatsSvc.Update(victimStats); await clientStatsSvc.SaveChangesAsync(); } - /// - /// Update the invidual and average stat history for a client - /// - /// client to update - /// stats of client that is being updated - /// - private async Task UpdateStatHistory(Player client, EFClientStatistics clientStats) + using (var ctx = new DatabaseContext()) { - int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds; + ctx.Set().Add(hit); + await ctx.SaveChangesAsync(); + } + } - // don't update their stat history if they haven't played long + public async Task AddStandardKill(Player attacker, Player victim) + { + int serverId = attacker.CurrentServer.GetHashCode(); + EFClientStatistics attackerStats = null; + try + { + attackerStats = Servers[serverId].PlayerStats[attacker.ClientId]; + } + + catch (KeyNotFoundException) + { + // happens when the client has disconnected before the last status update + Log.WriteWarning($"[Stats::AddStandardKill] kill attacker ClientId is invalid {attacker.ClientId}-{attacker}"); + return; + } + + EFClientStatistics victimStats = null; + try + { + victimStats = Servers[serverId].PlayerStats[victim.ClientId]; + } + + catch (KeyNotFoundException) + { + Log.WriteWarning($"[Stats::AddStandardKill] kill victim ClientId is invalid {victim.ClientId}-{victim}"); + return; + } + +#if DEBUG + Log.WriteDebug("Calculating standard kill"); +#endif + + // update the total stats + Servers[serverId].ServerStatistics.TotalKills += 1; + + // this happens when the round has changed + if (attackerStats.SessionScore == 0) + attackerStats.LastScore = 0; + + if (victimStats.SessionScore == 0) + victimStats.LastScore = 0; + + attackerStats.SessionScore = attacker.Score; + victimStats.SessionScore = victim.Score; + + // calculate for the clients + CalculateKill(attackerStats, victimStats); + // this should fix the negative SPM + // updates their last score after being calculated + attackerStats.LastScore = attacker.Score; + victimStats.LastScore = victim.Score; + + // show encouragement/discouragement + string streakMessage = (attackerStats.ClientId != victimStats.ClientId) ? + StreakMessage.MessageOnStreak(attackerStats.KillStreak, attackerStats.DeathStreak) : + StreakMessage.MessageOnStreak(-1, -1); + + if (streakMessage != string.Empty) + await attacker.Tell(streakMessage); + + // fixme: why? + if (double.IsNaN(victimStats.SPM) || double.IsNaN(victimStats.Skill)) + { + Log.WriteDebug($"[StatManager::AddStandardKill] victim SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); + victimStats.SPM = 0.0; + victimStats.Skill = 0.0; + } + + if (double.IsNaN(attackerStats.SPM) || double.IsNaN(attackerStats.Skill)) + { + Log.WriteDebug($"[StatManager::AddStandardKill] attacker SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); + attackerStats.SPM = 0.0; + attackerStats.Skill = 0.0; + } + + // update their performance +#if !DEBUG + if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 2.5) +#endif + { + attackerStats.LastStatHistoryUpdate = DateTime.UtcNow; + await UpdateStatHistory(attacker, attackerStats); + } + + // todo: do we want to save this immediately? + var clientStatsSvc = ContextThreads[serverId].ClientStatSvc; + clientStatsSvc.Update(attackerStats); + clientStatsSvc.Update(victimStats); + await clientStatsSvc.SaveChangesAsync(); + } + + /// + /// Update the invidual and average stat history for a client + /// + /// client to update + /// stats of client that is being updated + /// + private async Task UpdateStatHistory(Player client, EFClientStatistics clientStats) + { + int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds; + + // don't update their stat history if they haven't played long #if DEBUG == false if (currentSessionTime < 60) { @@ -645,419 +682,419 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } #endif - int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime; + int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime; - using (var ctx = new DatabaseContext()) + using (var ctx = new DatabaseContext()) + { + // select the rating history for client + var iqHistoryLink = from history in ctx.Set() + .Include(h => h.Ratings) + where history.ClientId == client.ClientId + select history; + + // get the client ratings + var clientHistory = await iqHistoryLink + .FirstOrDefaultAsync() ?? new EFClientRatingHistory() + { + Active = true, + ClientId = client.ClientId, + Ratings = new List() + }; + + // it's the first time they've played + if (clientHistory.RatingHistoryId == 0) { - // select the rating history for client - var iqHistoryLink = from history in ctx.Set() - .Include(h => h.Ratings) - where history.ClientId == client.ClientId - select history; + ctx.Add(clientHistory); + // Log.WriteDebug($"adding first time client history {client.ClientId}"); + await ctx.SaveChangesAsync(); + } - // get the client ratings - var clientHistory = await iqHistoryLink - .FirstOrDefaultAsync() ?? new EFClientRatingHistory() - { - Active = true, - ClientId = client.ClientId, - Ratings = new List() - }; + else + { + //ctx.Update(clientHistory); + } - // it's the first time they've played - if (clientHistory.RatingHistoryId == 0) - { - ctx.Add(clientHistory); - // Log.WriteDebug($"adding first time client history {client.ClientId}"); - await ctx.SaveChangesAsync(); - } + #region INDIVIDUAL_SERVER_PERFORMANCE + // get the client ranking for the current server + int individualClientRanking = await ctx.Set() + .Where(GetRankingFunc(clientStats.ServerId)) + // ignore themselves in the query + .Where(c => c.RatingHistory.ClientId != client.ClientId) + .Where(c => c.Performance > clientStats.Performance) + .CountAsync() + 1; - else - { - //ctx.Update(clientHistory); - } - - #region INDIVIDUAL_SERVER_PERFORMANCE - // get the client ranking for the current server - int individualClientRanking = await ctx.Set() - .Where(GetRankingFunc(clientStats.ServerId)) - // ignore themselves in the query - .Where(c => c.RatingHistory.ClientId != client.ClientId) - .Where(c => c.Performance > clientStats.Performance) - .CountAsync() + 1; - - // limit max history per server to 40 - if (clientHistory.Ratings.Count(r => r.ServerId == clientStats.ServerId) >= 40) - { - // select the oldest one - var ratingToRemove = clientHistory.Ratings - .Where(r => r.ServerId == clientStats.ServerId) - .OrderBy(r => r.When) - .First(); - - ctx.Remove(ratingToRemove); - //Log.WriteDebug($"remove oldest rating {client.ClientId}"); - await ctx.SaveChangesAsync(); - } - - // set the previous newest to false - var ratingToUnsetNewest = clientHistory.Ratings + // limit max history per server to 40 + if (clientHistory.Ratings.Count(r => r.ServerId == clientStats.ServerId) >= 40) + { + // select the oldest one + var ratingToRemove = clientHistory.Ratings .Where(r => r.ServerId == clientStats.ServerId) - .OrderByDescending(r => r.When) - .FirstOrDefault(); + .OrderBy(r => r.When) + .First(); - if (ratingToUnsetNewest != null) - { - if (ratingToUnsetNewest.Newest) - { - ctx.Update(ratingToUnsetNewest); - ctx.Entry(ratingToUnsetNewest).Property(r => r.Newest).IsModified = true; - ratingToUnsetNewest.Newest = false; - //Log.WriteDebug($"unsetting previous newest flag {client.ClientId}"); - await ctx.SaveChangesAsync(); - } - } - - var newServerRating = new EFRating() - { - Performance = clientStats.Performance, - Ranking = individualClientRanking, - Active = true, - Newest = true, - ServerId = clientStats.ServerId, - RatingHistoryId = clientHistory.RatingHistoryId, - ActivityAmount = currentServerTotalPlaytime, - }; - - // add new rating for current server - ctx.Add(newServerRating); - - //Log.WriteDebug($"adding new server rating {client.ClientId}"); + ctx.Remove(ratingToRemove); + //Log.WriteDebug($"remove oldest rating {client.ClientId}"); await ctx.SaveChangesAsync(); + } - #endregion - #region OVERALL_RATING - // select all performance & time played for current client - var iqClientStats = from stats in ctx.Set() - where stats.ClientId == client.ClientId - where stats.ServerId != clientStats.ServerId - select new - { - stats.Performance, - stats.TimePlayed - }; + // set the previous newest to false + var ratingToUnsetNewest = clientHistory.Ratings + .Where(r => r.ServerId == clientStats.ServerId) + .OrderByDescending(r => r.When) + .FirstOrDefault(); - var clientStatsList = await iqClientStats.ToListAsync(); - - // add the current server's so we don't have to pull it frmo the database - clientStatsList.Add(new + if (ratingToUnsetNewest != null) + { + if (ratingToUnsetNewest.Newest) { - clientStats.Performance, - TimePlayed = currentServerTotalPlaytime - }); - - // weight the overall performance based on play time - double performanceAverage = clientStatsList.Sum(p => (p.Performance * p.TimePlayed)) / clientStatsList.Sum(p => p.TimePlayed); - - // shouldn't happen but just in case the sum of time played is 0 - if (double.IsNaN(performanceAverage)) - { - performanceAverage = clientStatsList.Average(p => p.Performance); - } - - int overallClientRanking = await ctx.Set() - .Where(GetRankingFunc()) - .Where(r => r.RatingHistory.ClientId != client.ClientId) - .Where(r => r.Performance > performanceAverage) - .CountAsync() + 1; - - // limit max average history to 40 - if (clientHistory.Ratings.Count(r => r.ServerId == null) >= 40) - { - var ratingToRemove = clientHistory.Ratings - .Where(r => r.ServerId == null) - .OrderBy(r => r.When) - .First(); - - ctx.Remove(ratingToRemove); - //Log.WriteDebug($"remove oldest overall rating {client.ClientId}"); + ctx.Update(ratingToUnsetNewest); + ctx.Entry(ratingToUnsetNewest).Property(r => r.Newest).IsModified = true; + ratingToUnsetNewest.Newest = false; + //Log.WriteDebug($"unsetting previous newest flag {client.ClientId}"); await ctx.SaveChangesAsync(); } + } - // set the previous average newest to false - ratingToUnsetNewest = clientHistory.Ratings + var newServerRating = new EFRating() + { + Performance = clientStats.Performance, + Ranking = individualClientRanking, + Active = true, + Newest = true, + ServerId = clientStats.ServerId, + RatingHistoryId = clientHistory.RatingHistoryId, + ActivityAmount = currentServerTotalPlaytime, + }; + + // add new rating for current server + ctx.Add(newServerRating); + + //Log.WriteDebug($"adding new server rating {client.ClientId}"); + await ctx.SaveChangesAsync(); + + #endregion + #region OVERALL_RATING + // select all performance & time played for current client + var iqClientStats = from stats in ctx.Set() + where stats.ClientId == client.ClientId + where stats.ServerId != clientStats.ServerId + select new + { + stats.Performance, + stats.TimePlayed + }; + + var clientStatsList = await iqClientStats.ToListAsync(); + + // add the current server's so we don't have to pull it frmo the database + clientStatsList.Add(new + { + clientStats.Performance, + TimePlayed = currentServerTotalPlaytime + }); + + // weight the overall performance based on play time + double performanceAverage = clientStatsList.Sum(p => (p.Performance * p.TimePlayed)) / clientStatsList.Sum(p => p.TimePlayed); + + // shouldn't happen but just in case the sum of time played is 0 + if (double.IsNaN(performanceAverage)) + { + performanceAverage = clientStatsList.Average(p => p.Performance); + } + + int overallClientRanking = await ctx.Set() + .Where(GetRankingFunc()) + .Where(r => r.RatingHistory.ClientId != client.ClientId) + .Where(r => r.Performance > performanceAverage) + .CountAsync() + 1; + + // limit max average history to 40 + if (clientHistory.Ratings.Count(r => r.ServerId == null) >= 40) + { + var ratingToRemove = clientHistory.Ratings .Where(r => r.ServerId == null) - .OrderByDescending(r => r.When) - .FirstOrDefault(); + .OrderBy(r => r.When) + .First(); - if (ratingToUnsetNewest != null) - { - if (ratingToUnsetNewest.Newest) - { - ctx.Update(ratingToUnsetNewest); - ctx.Entry(ratingToUnsetNewest).Property(r => r.Newest).IsModified = true; - ratingToUnsetNewest.Newest = false; - //Log.WriteDebug($"unsetting overall newest rating {client.ClientId}"); - await ctx.SaveChangesAsync(); - } - } - - // add new average rating - var averageRating = new EFRating() - { - Active = true, - Newest = true, - Performance = performanceAverage, - Ranking = overallClientRanking, - ServerId = null, - RatingHistoryId = clientHistory.RatingHistoryId, - ActivityAmount = clientStatsList.Sum(s => s.TimePlayed) - }; - - ctx.Add(averageRating); - #endregion - //Log.WriteDebug($"adding new average rating {client.ClientId}"); + ctx.Remove(ratingToRemove); + //Log.WriteDebug($"remove oldest overall rating {client.ClientId}"); await ctx.SaveChangesAsync(); } + + // set the previous average newest to false + ratingToUnsetNewest = clientHistory.Ratings + .Where(r => r.ServerId == null) + .OrderByDescending(r => r.When) + .FirstOrDefault(); + + if (ratingToUnsetNewest != null) + { + if (ratingToUnsetNewest.Newest) + { + ctx.Update(ratingToUnsetNewest); + ctx.Entry(ratingToUnsetNewest).Property(r => r.Newest).IsModified = true; + ratingToUnsetNewest.Newest = false; + //Log.WriteDebug($"unsetting overall newest rating {client.ClientId}"); + await ctx.SaveChangesAsync(); + } + } + + // add new average rating + var averageRating = new EFRating() + { + Active = true, + Newest = true, + Performance = performanceAverage, + Ranking = overallClientRanking, + ServerId = null, + RatingHistoryId = clientHistory.RatingHistoryId, + ActivityAmount = clientStatsList.Sum(s => s.TimePlayed) + }; + + ctx.Add(averageRating); + #endregion + //Log.WriteDebug($"adding new average rating {client.ClientId}"); + await ctx.SaveChangesAsync(); + } + } + + /// + /// Performs the incrementation of kills and deaths for client statistics + /// + /// Stats of the attacker + /// Stats of the victim + public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats) + { + bool suicide = attackerStats.ClientId == victimStats.ClientId; + + // only update their kills if they didn't kill themselves + if (!suicide) + { + attackerStats.Kills += 1; + attackerStats.SessionKills += 1; + attackerStats.KillStreak += 1; + attackerStats.DeathStreak = 0; } - /// - /// Performs the incrementation of kills and deaths for client statistics - /// - /// Stats of the attacker - /// Stats of the victim - public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats) + victimStats.Deaths += 1; + victimStats.SessionDeaths += 1; + victimStats.DeathStreak += 1; + victimStats.KillStreak = 0; + + // process the attacker's stats after the kills + attackerStats = UpdateStats(attackerStats); + + // calulate elo + if (Servers[attackerStats.ServerId].PlayerStats.Count > 1) { - bool suicide = attackerStats.ClientId == victimStats.ClientId; + /* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats + .Where(cs => cs.Value.ClientId != attackerStats.ClientId) + .Where(cs => + Servers[attackerStats.ServerId].IsTeamBased ? + cs.Value.Team != attackerStats.Team : + cs.Value.Team != IW4Info.Team.Spectator) + .Where(cs => cs.Value.Team != IW4Info.Team.Spectator); - // only update their kills if they didn't kill themselves - if (!suicide) - { - attackerStats.Kills += 1; - attackerStats.SessionKills += 1; - attackerStats.KillStreak += 1; - attackerStats.DeathStreak = 0; - } + double attackerLobbyRating = validAttackerLobbyRatings.Count() > 0 ? + validAttackerLobbyRatings.Average(cs => cs.Value.EloRating) : + attackerStats.EloRating; - victimStats.Deaths += 1; - victimStats.SessionDeaths += 1; - victimStats.DeathStreak += 1; - victimStats.KillStreak = 0; + var validVictimLobbyRatings = Servers[victimStats.ServerId].PlayerStats + .Where(cs => cs.Value.ClientId != victimStats.ClientId) + .Where(cs => + Servers[attackerStats.ServerId].IsTeamBased ? + cs.Value.Team != victimStats.Team : + cs.Value.Team != IW4Info.Team.Spectator) + .Where(cs => cs.Value.Team != IW4Info.Team.Spectator); - // process the attacker's stats after the kills - attackerStats = UpdateStats(attackerStats); + double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ? + validVictimLobbyRatings.Average(cs => cs.Value.EloRating) : + victimStats.EloRating;*/ - // calulate elo - if (Servers[attackerStats.ServerId].PlayerStats.Count > 1) - { - /* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats - .Where(cs => cs.Value.ClientId != attackerStats.ClientId) - .Where(cs => - Servers[attackerStats.ServerId].IsTeamBased ? - cs.Value.Team != attackerStats.Team : - cs.Value.Team != IW4Info.Team.Spectator) - .Where(cs => cs.Value.Team != IW4Info.Team.Spectator); + double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating)); + double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E)); - double attackerLobbyRating = validAttackerLobbyRatings.Count() > 0 ? - validAttackerLobbyRatings.Average(cs => cs.Value.EloRating) : - attackerStats.EloRating; + // double victimEloDifference = Math.Log(Math.Max(1, attackerStats.EloRating)) - Math.Log(Math.Max(1, victimStats.EloRating)); + // double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference/ Math.E)); - var validVictimLobbyRatings = Servers[victimStats.ServerId].PlayerStats - .Where(cs => cs.Value.ClientId != victimStats.ClientId) - .Where(cs => - Servers[attackerStats.ServerId].IsTeamBased ? - cs.Value.Team != victimStats.Team : - cs.Value.Team != IW4Info.Team.Spectator) - .Where(cs => cs.Value.Team != IW4Info.Team.Spectator); + attackerStats.EloRating += 6.0 * (1 - winPercentage); + victimStats.EloRating -= 6.0 * (1 - winPercentage); - double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ? - validVictimLobbyRatings.Average(cs => cs.Value.EloRating) : - victimStats.EloRating;*/ - - double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating)); - double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E)); - - // double victimEloDifference = Math.Log(Math.Max(1, attackerStats.EloRating)) - Math.Log(Math.Max(1, victimStats.EloRating)); - // double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference/ Math.E)); - - attackerStats.EloRating += 6.0 * (1 - winPercentage); - victimStats.EloRating -= 6.0 * (1 - winPercentage); - - attackerStats.EloRating = Math.Max(0, Math.Round(attackerStats.EloRating, 2)); - victimStats.EloRating = Math.Max(0, Math.Round(victimStats.EloRating, 2)); - } - - // update after calculation - attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds; - victimStats.TimePlayed += (int)(DateTime.UtcNow - victimStats.LastActive).TotalSeconds; - attackerStats.LastActive = DateTime.UtcNow; - victimStats.LastActive = DateTime.UtcNow; + attackerStats.EloRating = Math.Max(0, Math.Round(attackerStats.EloRating, 2)); + victimStats.EloRating = Math.Max(0, Math.Round(victimStats.EloRating, 2)); } - /// - /// Update the client stats (skill etc) - /// - /// Client statistics - /// - private EFClientStatistics UpdateStats(EFClientStatistics clientStats) + // update after calculation + attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds; + victimStats.TimePlayed += (int)(DateTime.UtcNow - victimStats.LastActive).TotalSeconds; + attackerStats.LastActive = DateTime.UtcNow; + victimStats.LastActive = DateTime.UtcNow; + } + + /// + /// Update the client stats (skill etc) + /// + /// Client statistics + /// + private EFClientStatistics UpdateStats(EFClientStatistics clientStats) + { + // prevent NaN or inactive time lowering SPM + if ((DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0 < 0.01 || + (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0 > 3 || + clientStats.SessionScore == 0) { - // prevent NaN or inactive time lowering SPM - if ((DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0 < 0.01 || - (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0 > 3 || - clientStats.SessionScore == 0) - { - // prevents idle time counting - clientStats.LastStatCalculation = DateTime.UtcNow; - return clientStats; - } - - double timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0; - double timeSinceLastActive = (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0; - - int scoreDifference = 0; - // this means they've been tking or suicide and is the only time they can have a negative SPM - if (clientStats.RoundScore < 0) - { - scoreDifference = clientStats.RoundScore + clientStats.LastScore; - } - - else if (clientStats.RoundScore > 0 && clientStats.LastScore < clientStats.RoundScore) - { - scoreDifference = clientStats.RoundScore - clientStats.LastScore; - } - - double killSPM = scoreDifference / timeSinceLastCalc; - double spmMultiplier = 2.934 * Math.Pow(Servers[clientStats.ServerId].TeamCount(clientStats.Team == IW4Info.Team.Allies ? IW4Info.Team.Axis : IW4Info.Team.Allies), -0.454); - killSPM *= Math.Max(1, spmMultiplier); - - // update this for ac tracking - clientStats.SessionSPM = killSPM; - - // calculate how much the KDR should weigh - // 1.637 is a Eddie-Generated number that weights the KDR nicely - double currentKDR = clientStats.SessionDeaths == 0 ? clientStats.SessionKills : clientStats.SessionKills / clientStats.SessionDeaths; - double alpha = Math.Sqrt(2) / Math.Min(600, Math.Max(clientStats.Kills + clientStats.Deaths, 1)); - clientStats.RollingWeightedKDR = (alpha * currentKDR) + (1.0 - alpha) * clientStats.KDR; - double KDRWeight = Math.Round(Math.Pow(clientStats.RollingWeightedKDR, 1.637 / Math.E), 3); - - // calculate the weight of the new play time against last 10 hours of gameplay - int totalPlayTime = (clientStats.TimePlayed == 0) ? - (int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds : - clientStats.TimePlayed + (int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds; - - double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalPlayTime / 60.0)); - - // calculate the new weight against average times the weight against play time - clientStats.SPM = (killSPM * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight)); - - if (clientStats.SPM < 0) - { - Log.WriteWarning("[StatManager:UpdateStats] clientStats SPM < 0"); - Log.WriteDebug($"{scoreDifference}-{clientStats.RoundScore} - {clientStats.LastScore} - {clientStats.SessionScore}"); - clientStats.SPM = 0; - } - - clientStats.SPM = Math.Round(clientStats.SPM, 3); - clientStats.Skill = Math.Round((clientStats.SPM * KDRWeight), 3); - - // fixme: how does this happen? - if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill)) - { - Log.WriteWarning("[StatManager::UpdateStats] clientStats SPM/Skill NaN"); - Log.WriteDebug($"{killSPM}-{KDRWeight}-{totalPlayTime}-{SPMAgainstPlayWeight}-{clientStats.SPM}-{clientStats.Skill}-{scoreDifference}"); - clientStats.SPM = 0; - clientStats.Skill = 0; - } - + // prevents idle time counting clientStats.LastStatCalculation = DateTime.UtcNow; - //clientStats.LastScore = clientStats.SessionScore; - return clientStats; } - public void InitializeServerStats(Server sv) + double timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0; + double timeSinceLastActive = (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0; + + int scoreDifference = 0; + // this means they've been tking or suicide and is the only time they can have a negative SPM + if (clientStats.RoundScore < 0) { - int serverId = sv.GetHashCode(); - var statsSvc = ContextThreads[serverId]; - - var serverStats = statsSvc.ServerStatsSvc.Find(s => s.ServerId == serverId).FirstOrDefault(); - if (serverStats == null) - { - Log.WriteDebug($"Initializing server stats for {sv}"); - // server stats have never been generated before - serverStats = new EFServerStatistics() - { - Active = true, - ServerId = serverId, - TotalKills = 0, - TotalPlayTime = 0, - }; - - var ieClientStats = statsSvc.ClientStatSvc.Find(cs => cs.ServerId == serverId); - - // set these incase we've imported settings - serverStats.TotalKills = ieClientStats.Sum(cs => cs.Kills); - serverStats.TotalPlayTime = Manager.GetClientService().GetTotalPlayTime().Result; - - statsSvc.ServerStatsSvc.Insert(serverStats); - } + scoreDifference = clientStats.RoundScore + clientStats.LastScore; } - public void ResetKillstreaks(int serverId) + else if (clientStats.RoundScore > 0 && clientStats.LastScore < clientStats.RoundScore) { - var serverStats = Servers[serverId]; - foreach (var stat in serverStats.PlayerStats.Values) - stat.StartNewSession(); + scoreDifference = clientStats.RoundScore - clientStats.LastScore; } - public void ResetStats(int clientId, int serverId) + double killSPM = scoreDifference / timeSinceLastCalc; + double spmMultiplier = 2.934 * Math.Pow(Servers[clientStats.ServerId].TeamCount(clientStats.Team == IW4Info.Team.Allies ? IW4Info.Team.Axis : IW4Info.Team.Allies), -0.454); + killSPM *= Math.Max(1, spmMultiplier); + + // update this for ac tracking + clientStats.SessionSPM = killSPM; + + // calculate how much the KDR should weigh + // 1.637 is a Eddie-Generated number that weights the KDR nicely + double currentKDR = clientStats.SessionDeaths == 0 ? clientStats.SessionKills : clientStats.SessionKills / clientStats.SessionDeaths; + double alpha = Math.Sqrt(2) / Math.Min(600, Math.Max(clientStats.Kills + clientStats.Deaths, 1)); + clientStats.RollingWeightedKDR = (alpha * currentKDR) + (1.0 - alpha) * clientStats.KDR; + double KDRWeight = Math.Round(Math.Pow(clientStats.RollingWeightedKDR, 1.637 / Math.E), 3); + + // calculate the weight of the new play time against last 10 hours of gameplay + int totalPlayTime = (clientStats.TimePlayed == 0) ? + (int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds : + clientStats.TimePlayed + (int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds; + + double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalPlayTime / 60.0)); + + // calculate the new weight against average times the weight against play time + clientStats.SPM = (killSPM * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight)); + + if (clientStats.SPM < 0) { - var stats = Servers[serverId].PlayerStats[clientId]; - stats.Kills = 0; - stats.Deaths = 0; - stats.SPM = 0; - stats.Skill = 0; - stats.TimePlayed = 0; - stats.EloRating = 200; + Log.WriteWarning("[StatManager:UpdateStats] clientStats SPM < 0"); + Log.WriteDebug($"{scoreDifference}-{clientStats.RoundScore} - {clientStats.LastScore} - {clientStats.SessionScore}"); + clientStats.SPM = 0; } - public async Task AddMessageAsync(int clientId, int serverId, string message) - { - // the web users can have no account - if (clientId < 1) - return; + clientStats.SPM = Math.Round(clientStats.SPM, 3); + clientStats.Skill = Math.Round((clientStats.SPM * KDRWeight), 3); - var messageSvc = ContextThreads[serverId].MessageSvc; - messageSvc.Insert(new EFClientMessage() + // fixme: how does this happen? + if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill)) + { + Log.WriteWarning("[StatManager::UpdateStats] clientStats SPM/Skill NaN"); + Log.WriteDebug($"{killSPM}-{KDRWeight}-{totalPlayTime}-{SPMAgainstPlayWeight}-{clientStats.SPM}-{clientStats.Skill}-{scoreDifference}"); + clientStats.SPM = 0; + clientStats.Skill = 0; + } + + clientStats.LastStatCalculation = DateTime.UtcNow; + //clientStats.LastScore = clientStats.SessionScore; + + return clientStats; + } + + public void InitializeServerStats(Server sv) + { + int serverId = sv.GetHashCode(); + var statsSvc = ContextThreads[serverId]; + + var serverStats = statsSvc.ServerStatsSvc.Find(s => s.ServerId == serverId).FirstOrDefault(); + if (serverStats == null) + { + Log.WriteDebug($"Initializing server stats for {sv}"); + // server stats have never been generated before + serverStats = new EFServerStatistics() { Active = true, - ClientId = clientId, - Message = message, ServerId = serverId, - TimeSent = DateTime.UtcNow - }); - await messageSvc.SaveChangesAsync(); - } + TotalKills = 0, + TotalPlayTime = 0, + }; - public async Task Sync(Server sv) - { - int serverId = sv.GetHashCode(); - var statsSvc = ContextThreads[serverId]; + var ieClientStats = statsSvc.ClientStatSvc.Find(cs => cs.ServerId == serverId); - // Log.WriteDebug("Syncing stats contexts"); - await statsSvc.ServerStatsSvc.SaveChangesAsync(); - //await statsSvc.ClientStatSvc.SaveChangesAsync(); - await statsSvc.KillStatsSvc.SaveChangesAsync(); - await statsSvc.ServerSvc.SaveChangesAsync(); + // set these incase we've imported settings + serverStats.TotalKills = ieClientStats.Sum(cs => cs.Kills); + serverStats.TotalPlayTime = Manager.GetClientService().GetTotalPlayTime().Result; - statsSvc = null; - // this should prevent the gunk for having a long lasting context. - ContextThreads[serverId] = new ThreadSafeStatsService(); - } - - public void SetTeamBased(int serverId, bool isTeamBased) - { - Servers[serverId].IsTeamBased = isTeamBased; + statsSvc.ServerStatsSvc.Insert(serverStats); } } + + public void ResetKillstreaks(int serverId) + { + var serverStats = Servers[serverId]; + foreach (var stat in serverStats.PlayerStats.Values) + stat.StartNewSession(); + } + + public void ResetStats(int clientId, int serverId) + { + var stats = Servers[serverId].PlayerStats[clientId]; + stats.Kills = 0; + stats.Deaths = 0; + stats.SPM = 0; + stats.Skill = 0; + stats.TimePlayed = 0; + stats.EloRating = 200; + } + + public async Task AddMessageAsync(int clientId, int serverId, string message) + { + // the web users can have no account + if (clientId < 1) + return; + + var messageSvc = ContextThreads[serverId].MessageSvc; + messageSvc.Insert(new EFClientMessage() + { + Active = true, + ClientId = clientId, + Message = message, + ServerId = serverId, + TimeSent = DateTime.UtcNow + }); + await messageSvc.SaveChangesAsync(); + } + + public async Task Sync(Server sv) + { + int serverId = sv.GetHashCode(); + var statsSvc = ContextThreads[serverId]; + + // Log.WriteDebug("Syncing stats contexts"); + await statsSvc.ServerStatsSvc.SaveChangesAsync(); + //await statsSvc.ClientStatSvc.SaveChangesAsync(); + await statsSvc.KillStatsSvc.SaveChangesAsync(); + await statsSvc.ServerSvc.SaveChangesAsync(); + + statsSvc = null; + // this should prevent the gunk for having a long lasting context. + ContextThreads[serverId] = new ThreadSafeStatsService(); + } + + public void SetTeamBased(int serverId, bool isTeamBased) + { + Servers[serverId].IsTeamBased = isTeamBased; + } +} } diff --git a/Plugins/Stats/Models/ModelConfiguration.cs b/Plugins/Stats/Models/ModelConfiguration.cs index 45fbdae18..360e7faef 100644 --- a/Plugins/Stats/Models/ModelConfiguration.cs +++ b/Plugins/Stats/Models/ModelConfiguration.cs @@ -21,6 +21,15 @@ namespace Stats.Models .Property(c => c.ServerId) .HasColumnName("EFClientStatistics_ServerId"); + builder.Entity() + .HasIndex(p => p.Performance); + + builder.Entity() + .HasIndex(p => p.Ranking); + + builder.Entity() + .HasIndex(p => p.When); + // force pluralization builder.Entity().ToTable("EFClientKills"); builder.Entity().ToTable("EFClientMessages"); diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index 66008365f..7951080c7 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -653,7 +653,7 @@ namespace SharedLibraryCore.Commands // they're not going by another alias string msg = P.Name.ToLower().Contains(E.Data.ToLower()) ? $"[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor(P.Level, localizedLevel)}^7] - {P.IPAddressString} | last seen {Utilities.GetTimePassed(P.LastConnection)}" : - $"({P.AliasLink.Children.First(a => a.Name.ToLower().Contains(E.Data.ToLower())).Name})->[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor(P.Level, localizedLevel)}^7] - {P.IPAddressString} | last seen {Utilities.GetTimePassed(P.LastConnection)}"; + $"({P.AliasLink.Children.FirstOrDefault(a => a.Name.ToLower().Contains(E.Data.ToLower()))?.Name})->[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor(P.Level, localizedLevel)}^7] - {P.IPAddressString} | last seen {Utilities.GetTimePassed(P.LastConnection)}"; await E.Origin.Tell(msg); await Task.Delay(FloodProtectionInterval); } diff --git a/SharedLibraryCore/Database/DatabaseContext.cs b/SharedLibraryCore/Database/DatabaseContext.cs index f0003ad24..9b1809263 100644 --- a/SharedLibraryCore/Database/DatabaseContext.cs +++ b/SharedLibraryCore/Database/DatabaseContext.cs @@ -99,6 +99,8 @@ namespace SharedLibraryCore.Database modelBuilder.Entity(ent => { ent.HasIndex(a => a.IPAddress); + ent.Property(a => a.Name).HasMaxLength(24); + ent.HasIndex(a => a.Name); }); // force full name for database conversion diff --git a/SharedLibraryCore/Database/Models/EFAlias.cs b/SharedLibraryCore/Database/Models/EFAlias.cs index 74e75cfc8..97f75890d 100644 --- a/SharedLibraryCore/Database/Models/EFAlias.cs +++ b/SharedLibraryCore/Database/Models/EFAlias.cs @@ -13,6 +13,7 @@ namespace SharedLibraryCore.Database.Models [ForeignKey("LinkId")] public virtual EFAliasLink Link { get; set; } [Required] + [MaxLength(24)] public string Name { get; set; } [Required] public int IPAddress { get; set; } diff --git a/SharedLibraryCore/Helpers/Vector3.cs b/SharedLibraryCore/Helpers/Vector3.cs index 0c7a24c4d..b7850ecdd 100644 --- a/SharedLibraryCore/Helpers/Vector3.cs +++ b/SharedLibraryCore/Helpers/Vector3.cs @@ -35,13 +35,13 @@ namespace SharedLibraryCore.Helpers public static Vector3 Parse(string s) { - bool valid = Regex.Match(s, @"\(-?[0-9]+.?[0-9]*,\ -?[0-9]+.?[0-9]*,\ -?[0-9]+.?[0-9]*\)").Success; + bool valid = Regex.Match(s, @"\((-?[0-9]+\.?[0-9]*|-?[0-9]+\.?[0-9]*e-[0-9]+),\ (-?[0-9]+\.?[0-9]*|-?[0-9]+\.?[0-9]*e-[0-9]+),\ (-?[0-9]+\.?[0-9]*|-?[0-9]+\.?[0-9]*e-[0-9]+)\)").Success; if (!valid) throw new FormatException("Vector3 is not in correct format"); string removeParenthesis = s.Substring(1, s.Length - 2); string[] eachPoint = removeParenthesis.Split(','); - return new Vector3(float.Parse(eachPoint[0]), float.Parse(eachPoint[1]), float.Parse(eachPoint[2])); + return new Vector3(float.Parse(eachPoint[0], System.Globalization.NumberStyles.Any), float.Parse(eachPoint[1], System.Globalization.NumberStyles.Any), float.Parse(eachPoint[2], System.Globalization.NumberStyles.Any)); } public static double Distance(Vector3 a, Vector3 b) diff --git a/SharedLibraryCore/Migrations/20180910221749_AddRatingIndexes.Designer.cs b/SharedLibraryCore/Migrations/20180910221749_AddRatingIndexes.Designer.cs new file mode 100644 index 000000000..231f84763 --- /dev/null +++ b/SharedLibraryCore/Migrations/20180910221749_AddRatingIndexes.Designer.cs @@ -0,0 +1,677 @@ +// +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("20180910221749_AddRatingIndexes")] + partial class AddRatingIndexes + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("CurrentSessionLength"); + + b.Property("CurrentStrain"); + + b.Property("CurrentViewAngleVector3Id"); + + b.Property("Deaths"); + + b.Property("Distance"); + + b.Property("EloRating"); + + b.Property("HitDestinationVector3Id"); + + b.Property("HitLocation"); + + b.Property("HitOriginVector3Id"); + + b.Property("HitType"); + + b.Property("Hits"); + + b.Property("Kills"); + + b.Property("LastStrainAngleVector3Id"); + + b.Property("SessionAngleOffset"); + + b.Property("SessionSPM"); + + b.Property("SessionScore"); + + b.Property("StrainAngleBetween"); + + b.Property("TimeSinceLastEvent"); + + b.Property("WeaponId"); + + b.Property("When"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleVector3Id"); + + b.HasIndex("HitDestinationVector3Id"); + + b.HasIndex("HitOriginVector3Id"); + + b.HasIndex("LastStrainAngleVector3Id"); + + b.ToTable("EFACSnapshot"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AttackerId"); + + b.Property("Damage"); + + b.Property("DeathOriginVector3Id"); + + b.Property("DeathType"); + + b.Property("Fraction"); + + b.Property("HitLoc"); + + b.Property("IsKill"); + + b.Property("KillOriginVector3Id"); + + b.Property("Map"); + + b.Property("ServerId"); + + b.Property("VictimId"); + + b.Property("ViewAnglesVector3Id"); + + b.Property("VisibilityPercentage"); + + b.Property("Weapon"); + + b.Property("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("MessageId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("TimeSent"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientMessages"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.Property("ClientId"); + + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Deaths"); + + b.Property("EloRating"); + + b.Property("Kills"); + + b.Property("MaxStrain"); + + b.Property("RollingWeightedKDR"); + + b.Property("SPM"); + + b.Property("Skill"); + + b.Property("TimePlayed"); + + b.Property("VisionAverage"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatistics"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId") + .HasColumnName("EFClientStatistics_ClientId"); + + b.Property("HitCount"); + + b.Property("HitOffsetAverage"); + + b.Property("Location"); + + b.Property("MaxAngleDistance"); + + b.Property("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("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ActivityAmount"); + + b.Property("Newest"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.Property("RatingHistoryId"); + + b.Property("ServerId"); + + b.Property("When"); + + b.HasKey("RatingId"); + + b.HasIndex("Performance"); + + b.HasIndex("Ranking"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("When"); + + b.ToTable("EFRating"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => + { + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Port"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ServerId"); + + b.Property("TotalKills"); + + b.Property("TotalPlayTime"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("DateAdded"); + + b.Property("IPAddress"); + + b.Property("LinkId"); + + b.Property("Name") + .IsRequired(); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.ToTable("EFAlias"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Comment") + .HasMaxLength(128); + + b.Property("OriginEntityId"); + + b.Property("TargetEntityId"); + + b.Property("TimeChanged"); + + b.Property("TypeOfChange"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AliasLinkId"); + + b.Property("Connections"); + + b.Property("CurrentAliasId"); + + b.Property("FirstConnection"); + + b.Property("LastConnection"); + + b.Property("Level"); + + b.Property("Masked"); + + b.Property("NetworkId"); + + b.Property("Password"); + + b.Property("PasswordSalt"); + + b.Property("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("MetaId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Extra"); + + b.Property("Key") + .IsRequired(); + + b.Property("Updated"); + + b.Property("Value") + .IsRequired(); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AutomatedOffense"); + + b.Property("Expires"); + + b.Property("LinkId"); + + b.Property("OffenderId"); + + b.Property("Offense") + .IsRequired(); + + b.Property("PunisherId"); + + b.Property("Type"); + + b.Property("When"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties"); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd(); + + b.Property("EFACSnapshotSnapshotId"); + + b.Property("X"); + + b.Property("Y"); + + b.Property("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("CurrentViewAngleVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleVector3Id"); + }); + + 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 + } + } +} diff --git a/SharedLibraryCore/Migrations/20180910221749_AddRatingIndexes.cs b/SharedLibraryCore/Migrations/20180910221749_AddRatingIndexes.cs new file mode 100644 index 000000000..5a4d62f0d --- /dev/null +++ b/SharedLibraryCore/Migrations/20180910221749_AddRatingIndexes.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace SharedLibraryCore.Migrations +{ + public partial class AddRatingIndexes : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_EFRating_Performance", + table: "EFRating", + column: "Performance"); + + migrationBuilder.CreateIndex( + name: "IX_EFRating_Ranking", + table: "EFRating", + column: "Ranking"); + + migrationBuilder.CreateIndex( + name: "IX_EFRating_When", + table: "EFRating", + column: "When"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_EFRating_Performance", + table: "EFRating"); + + migrationBuilder.DropIndex( + name: "IX_EFRating_Ranking", + table: "EFRating"); + + migrationBuilder.DropIndex( + name: "IX_EFRating_When", + table: "EFRating"); + } + } +} diff --git a/SharedLibraryCore/Migrations/20180911184224_AddEFAliasNameIndex.Designer.cs b/SharedLibraryCore/Migrations/20180911184224_AddEFAliasNameIndex.Designer.cs new file mode 100644 index 000000000..68ad1472e --- /dev/null +++ b/SharedLibraryCore/Migrations/20180911184224_AddEFAliasNameIndex.Designer.cs @@ -0,0 +1,679 @@ +// +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("20180911184224_AddEFAliasNameIndex")] + partial class AddEFAliasNameIndex + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("CurrentSessionLength"); + + b.Property("CurrentStrain"); + + b.Property("CurrentViewAngleVector3Id"); + + b.Property("Deaths"); + + b.Property("Distance"); + + b.Property("EloRating"); + + b.Property("HitDestinationVector3Id"); + + b.Property("HitLocation"); + + b.Property("HitOriginVector3Id"); + + b.Property("HitType"); + + b.Property("Hits"); + + b.Property("Kills"); + + b.Property("LastStrainAngleVector3Id"); + + b.Property("SessionAngleOffset"); + + b.Property("SessionSPM"); + + b.Property("SessionScore"); + + b.Property("StrainAngleBetween"); + + b.Property("TimeSinceLastEvent"); + + b.Property("WeaponId"); + + b.Property("When"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleVector3Id"); + + b.HasIndex("HitDestinationVector3Id"); + + b.HasIndex("HitOriginVector3Id"); + + b.HasIndex("LastStrainAngleVector3Id"); + + b.ToTable("EFACSnapshot"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AttackerId"); + + b.Property("Damage"); + + b.Property("DeathOriginVector3Id"); + + b.Property("DeathType"); + + b.Property("Fraction"); + + b.Property("HitLoc"); + + b.Property("IsKill"); + + b.Property("KillOriginVector3Id"); + + b.Property("Map"); + + b.Property("ServerId"); + + b.Property("VictimId"); + + b.Property("ViewAnglesVector3Id"); + + b.Property("VisibilityPercentage"); + + b.Property("Weapon"); + + b.Property("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("MessageId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("TimeSent"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientMessages"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.Property("ClientId"); + + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Deaths"); + + b.Property("EloRating"); + + b.Property("Kills"); + + b.Property("MaxStrain"); + + b.Property("RollingWeightedKDR"); + + b.Property("SPM"); + + b.Property("Skill"); + + b.Property("TimePlayed"); + + b.Property("VisionAverage"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatistics"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId") + .HasColumnName("EFClientStatistics_ClientId"); + + b.Property("HitCount"); + + b.Property("HitOffsetAverage"); + + b.Property("Location"); + + b.Property("MaxAngleDistance"); + + b.Property("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("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ActivityAmount"); + + b.Property("Newest"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.Property("RatingHistoryId"); + + b.Property("ServerId"); + + b.Property("When"); + + b.HasKey("RatingId"); + + b.HasIndex("Performance"); + + b.HasIndex("Ranking"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("When"); + + b.ToTable("EFRating"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => + { + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Port"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ServerId"); + + b.Property("TotalKills"); + + b.Property("TotalPlayTime"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("DateAdded"); + + b.Property("IPAddress"); + + b.Property("LinkId"); + + b.Property("Name") + .IsRequired(); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.ToTable("EFAlias"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Comment") + .HasMaxLength(128); + + b.Property("OriginEntityId"); + + b.Property("TargetEntityId"); + + b.Property("TimeChanged"); + + b.Property("TypeOfChange"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AliasLinkId"); + + b.Property("Connections"); + + b.Property("CurrentAliasId"); + + b.Property("FirstConnection"); + + b.Property("LastConnection"); + + b.Property("Level"); + + b.Property("Masked"); + + b.Property("NetworkId"); + + b.Property("Password"); + + b.Property("PasswordSalt"); + + b.Property("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("MetaId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Extra"); + + b.Property("Key") + .IsRequired(); + + b.Property("Updated"); + + b.Property("Value") + .IsRequired(); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AutomatedOffense"); + + b.Property("Expires"); + + b.Property("LinkId"); + + b.Property("OffenderId"); + + b.Property("Offense") + .IsRequired(); + + b.Property("PunisherId"); + + b.Property("Type"); + + b.Property("When"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties"); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd(); + + b.Property("EFACSnapshotSnapshotId"); + + b.Property("X"); + + b.Property("Y"); + + b.Property("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("CurrentViewAngleVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleVector3Id"); + }); + + 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 + } + } +} diff --git a/SharedLibraryCore/Migrations/20180911184224_AddEFAliasNameIndex.cs b/SharedLibraryCore/Migrations/20180911184224_AddEFAliasNameIndex.cs new file mode 100644 index 000000000..fa1ab6c77 --- /dev/null +++ b/SharedLibraryCore/Migrations/20180911184224_AddEFAliasNameIndex.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace SharedLibraryCore.Migrations +{ + public partial class AddEFAliasNameIndex : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_EFAlias_Name", + table: "EFAlias", + column: "Name"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_EFAlias_Name", + table: "EFAlias"); + } + } +} diff --git a/SharedLibraryCore/Migrations/20180911190418_AddEFAliasNameMaxLength.Designer.cs b/SharedLibraryCore/Migrations/20180911190418_AddEFAliasNameMaxLength.Designer.cs new file mode 100644 index 000000000..81dafef70 --- /dev/null +++ b/SharedLibraryCore/Migrations/20180911190418_AddEFAliasNameMaxLength.Designer.cs @@ -0,0 +1,680 @@ +// +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("20180911190418_AddEFAliasNameMaxLength")] + partial class AddEFAliasNameMaxLength + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("CurrentSessionLength"); + + b.Property("CurrentStrain"); + + b.Property("CurrentViewAngleVector3Id"); + + b.Property("Deaths"); + + b.Property("Distance"); + + b.Property("EloRating"); + + b.Property("HitDestinationVector3Id"); + + b.Property("HitLocation"); + + b.Property("HitOriginVector3Id"); + + b.Property("HitType"); + + b.Property("Hits"); + + b.Property("Kills"); + + b.Property("LastStrainAngleVector3Id"); + + b.Property("SessionAngleOffset"); + + b.Property("SessionSPM"); + + b.Property("SessionScore"); + + b.Property("StrainAngleBetween"); + + b.Property("TimeSinceLastEvent"); + + b.Property("WeaponId"); + + b.Property("When"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleVector3Id"); + + b.HasIndex("HitDestinationVector3Id"); + + b.HasIndex("HitOriginVector3Id"); + + b.HasIndex("LastStrainAngleVector3Id"); + + b.ToTable("EFACSnapshot"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AttackerId"); + + b.Property("Damage"); + + b.Property("DeathOriginVector3Id"); + + b.Property("DeathType"); + + b.Property("Fraction"); + + b.Property("HitLoc"); + + b.Property("IsKill"); + + b.Property("KillOriginVector3Id"); + + b.Property("Map"); + + b.Property("ServerId"); + + b.Property("VictimId"); + + b.Property("ViewAnglesVector3Id"); + + b.Property("VisibilityPercentage"); + + b.Property("Weapon"); + + b.Property("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("MessageId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("TimeSent"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientMessages"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.Property("ClientId"); + + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Deaths"); + + b.Property("EloRating"); + + b.Property("Kills"); + + b.Property("MaxStrain"); + + b.Property("RollingWeightedKDR"); + + b.Property("SPM"); + + b.Property("Skill"); + + b.Property("TimePlayed"); + + b.Property("VisionAverage"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatistics"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId") + .HasColumnName("EFClientStatistics_ClientId"); + + b.Property("HitCount"); + + b.Property("HitOffsetAverage"); + + b.Property("Location"); + + b.Property("MaxAngleDistance"); + + b.Property("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("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ActivityAmount"); + + b.Property("Newest"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.Property("RatingHistoryId"); + + b.Property("ServerId"); + + b.Property("When"); + + b.HasKey("RatingId"); + + b.HasIndex("Performance"); + + b.HasIndex("Ranking"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("When"); + + b.ToTable("EFRating"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => + { + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Port"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ServerId"); + + b.Property("TotalKills"); + + b.Property("TotalPlayTime"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("DateAdded"); + + b.Property("IPAddress"); + + b.Property("LinkId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.ToTable("EFAlias"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Comment") + .HasMaxLength(128); + + b.Property("OriginEntityId"); + + b.Property("TargetEntityId"); + + b.Property("TimeChanged"); + + b.Property("TypeOfChange"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AliasLinkId"); + + b.Property("Connections"); + + b.Property("CurrentAliasId"); + + b.Property("FirstConnection"); + + b.Property("LastConnection"); + + b.Property("Level"); + + b.Property("Masked"); + + b.Property("NetworkId"); + + b.Property("Password"); + + b.Property("PasswordSalt"); + + b.Property("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("MetaId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Extra"); + + b.Property("Key") + .IsRequired(); + + b.Property("Updated"); + + b.Property("Value") + .IsRequired(); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AutomatedOffense"); + + b.Property("Expires"); + + b.Property("LinkId"); + + b.Property("OffenderId"); + + b.Property("Offense") + .IsRequired(); + + b.Property("PunisherId"); + + b.Property("Type"); + + b.Property("When"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties"); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd(); + + b.Property("EFACSnapshotSnapshotId"); + + b.Property("X"); + + b.Property("Y"); + + b.Property("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("CurrentViewAngleVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleVector3Id"); + }); + + 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 + } + } +} diff --git a/SharedLibraryCore/Migrations/20180911190418_AddEFAliasNameMaxLength.cs b/SharedLibraryCore/Migrations/20180911190418_AddEFAliasNameMaxLength.cs new file mode 100644 index 000000000..41d807db7 --- /dev/null +++ b/SharedLibraryCore/Migrations/20180911190418_AddEFAliasNameMaxLength.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace SharedLibraryCore.Migrations +{ + public partial class AddEFAliasNameMaxLength : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/SharedLibraryCore/Migrations/20180911190823_AddEFAliasNameMaxLength24.Designer.cs b/SharedLibraryCore/Migrations/20180911190823_AddEFAliasNameMaxLength24.Designer.cs new file mode 100644 index 000000000..63ee9b549 --- /dev/null +++ b/SharedLibraryCore/Migrations/20180911190823_AddEFAliasNameMaxLength24.Designer.cs @@ -0,0 +1,680 @@ +// +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("20180911190823_AddEFAliasNameMaxLength24")] + partial class AddEFAliasNameMaxLength24 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("CurrentSessionLength"); + + b.Property("CurrentStrain"); + + b.Property("CurrentViewAngleVector3Id"); + + b.Property("Deaths"); + + b.Property("Distance"); + + b.Property("EloRating"); + + b.Property("HitDestinationVector3Id"); + + b.Property("HitLocation"); + + b.Property("HitOriginVector3Id"); + + b.Property("HitType"); + + b.Property("Hits"); + + b.Property("Kills"); + + b.Property("LastStrainAngleVector3Id"); + + b.Property("SessionAngleOffset"); + + b.Property("SessionSPM"); + + b.Property("SessionScore"); + + b.Property("StrainAngleBetween"); + + b.Property("TimeSinceLastEvent"); + + b.Property("WeaponId"); + + b.Property("When"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleVector3Id"); + + b.HasIndex("HitDestinationVector3Id"); + + b.HasIndex("HitOriginVector3Id"); + + b.HasIndex("LastStrainAngleVector3Id"); + + b.ToTable("EFACSnapshot"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AttackerId"); + + b.Property("Damage"); + + b.Property("DeathOriginVector3Id"); + + b.Property("DeathType"); + + b.Property("Fraction"); + + b.Property("HitLoc"); + + b.Property("IsKill"); + + b.Property("KillOriginVector3Id"); + + b.Property("Map"); + + b.Property("ServerId"); + + b.Property("VictimId"); + + b.Property("ViewAnglesVector3Id"); + + b.Property("VisibilityPercentage"); + + b.Property("Weapon"); + + b.Property("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("MessageId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("TimeSent"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientMessages"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.Property("ClientId"); + + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Deaths"); + + b.Property("EloRating"); + + b.Property("Kills"); + + b.Property("MaxStrain"); + + b.Property("RollingWeightedKDR"); + + b.Property("SPM"); + + b.Property("Skill"); + + b.Property("TimePlayed"); + + b.Property("VisionAverage"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatistics"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId") + .HasColumnName("EFClientStatistics_ClientId"); + + b.Property("HitCount"); + + b.Property("HitOffsetAverage"); + + b.Property("Location"); + + b.Property("MaxAngleDistance"); + + b.Property("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("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ActivityAmount"); + + b.Property("Newest"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.Property("RatingHistoryId"); + + b.Property("ServerId"); + + b.Property("When"); + + b.HasKey("RatingId"); + + b.HasIndex("Performance"); + + b.HasIndex("Ranking"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("When"); + + b.ToTable("EFRating"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => + { + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Port"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ServerId"); + + b.Property("TotalKills"); + + b.Property("TotalPlayTime"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("DateAdded"); + + b.Property("IPAddress"); + + b.Property("LinkId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.ToTable("EFAlias"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Comment") + .HasMaxLength(128); + + b.Property("OriginEntityId"); + + b.Property("TargetEntityId"); + + b.Property("TimeChanged"); + + b.Property("TypeOfChange"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AliasLinkId"); + + b.Property("Connections"); + + b.Property("CurrentAliasId"); + + b.Property("FirstConnection"); + + b.Property("LastConnection"); + + b.Property("Level"); + + b.Property("Masked"); + + b.Property("NetworkId"); + + b.Property("Password"); + + b.Property("PasswordSalt"); + + b.Property("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("MetaId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Extra"); + + b.Property("Key") + .IsRequired(); + + b.Property("Updated"); + + b.Property("Value") + .IsRequired(); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AutomatedOffense"); + + b.Property("Expires"); + + b.Property("LinkId"); + + b.Property("OffenderId"); + + b.Property("Offense") + .IsRequired(); + + b.Property("PunisherId"); + + b.Property("Type"); + + b.Property("When"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties"); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd(); + + b.Property("EFACSnapshotSnapshotId"); + + b.Property("X"); + + b.Property("Y"); + + b.Property("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("CurrentViewAngleVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleVector3Id"); + }); + + 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 + } + } +} diff --git a/SharedLibraryCore/Migrations/20180911190823_AddEFAliasNameMaxLength24.cs b/SharedLibraryCore/Migrations/20180911190823_AddEFAliasNameMaxLength24.cs new file mode 100644 index 000000000..c38024983 --- /dev/null +++ b/SharedLibraryCore/Migrations/20180911190823_AddEFAliasNameMaxLength24.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace SharedLibraryCore.Migrations +{ + public partial class AddEFAliasNameMaxLength24 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs index 9102988f1..d1f923a98 100644 --- a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs +++ b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs @@ -260,10 +260,16 @@ namespace SharedLibraryCore.Migrations b.HasKey("RatingId"); + b.HasIndex("Performance"); + + b.HasIndex("Ranking"); + b.HasIndex("RatingHistoryId"); b.HasIndex("ServerId"); + b.HasIndex("When"); + b.ToTable("EFRating"); }); @@ -314,7 +320,8 @@ namespace SharedLibraryCore.Migrations b.Property("LinkId"); b.Property("Name") - .IsRequired(); + .IsRequired() + .HasMaxLength(24); b.HasKey("AliasId"); @@ -322,6 +329,8 @@ namespace SharedLibraryCore.Migrations b.HasIndex("LinkId"); + b.HasIndex("Name"); + b.ToTable("EFAlias"); }); diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index 710a11f35..c476bf9f4 100644 --- a/SharedLibraryCore/RCon/Connection.cs +++ b/SharedLibraryCore/RCon/Connection.cs @@ -171,7 +171,7 @@ namespace SharedLibraryCore.RCon break; } - using (var socketConnection = new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp)) + using (var socketConnection = new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp) { ExclusiveAddressUse = true } ) { socketConnection.BeginConnect(Endpoint, new AsyncCallback(OnConnectedCallback), socketConnection); diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index 06b93fd01..a5726f384 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -164,7 +164,7 @@ namespace SharedLibraryCore.Services .Include(c => c.AliasLink) .Include(c => c.CurrentAlias) .Single(e => e.ClientId == entity.ClientId); - + // if their level has been changed if (entity.Level != client.Level) { @@ -264,29 +264,28 @@ namespace SharedLibraryCore.Services if (name.Length < 3) return new List(); - using (var context = new DatabaseContext()) + name = name.ToLower(); + + using (var context = new DatabaseContext(true)) { - context.ChangeTracker.AutoDetectChangesEnabled = false; - context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - int asIP = name.ConvertToIP(); - // hack: so IW4MAdmin doesn't show up in search results - asIP = asIP == 0 ? int.MinValue : asIP; + // hack: so IW4MAdmin and bots don't show up in search results + asIP = asIP == 0 ? int.MaxValue : asIP; - var iqClients = (from alias in context.Aliases - .AsNoTracking() - where alias.Name.ToLower() - .Contains(name.ToLower()) || - alias.IPAddress == asIP - join link in context.AliasLinks - on alias.LinkId equals link.AliasLinkId - join client in context.Clients - .AsNoTracking() - on alias.LinkId equals client.AliasLinkId - select client) - .Distinct() - .Include(c => c.CurrentAlias) - .Include(c => c.AliasLink.Children); + var iqLinkIds = (from alias in context.Aliases + where asIP != int.MaxValue ? alias.IPAddress == asIP : alias.Name.ToLower().Contains(name) + select alias.LinkId); + + var linkIds = iqLinkIds.ToList(); + + var iqClients = context.Clients + .Where(c => linkIds.Contains(c.AliasLinkId)) + .Include(c => c.CurrentAlias) + .Include(c => c.AliasLink.Children); + +#if DEBUG == true + var iqClientsSql = iqClients.ToSql(); +#endif return await iqClients.ToListAsync(); } @@ -294,7 +293,7 @@ namespace SharedLibraryCore.Services public async Task> GetClientByIP(int ipAddress) { - using (var context = new DatabaseContext()) + using (var context = new DatabaseContext(true)) { var iqClients = (from alias in context.Aliases .AsNoTracking() diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 97d8034ec..9ee978f7d 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -496,7 +496,14 @@ namespace SharedLibraryCore public static async Task> GetInfoAsync(this Server server) { - var response = await server.RemoteConnection.SendQueryAsync(RCon.StaticHelpers.QueryType.GET_INFO); + string[] response = new string[0]; + for (int i = 0; i < 4; i++) + { + response = await server.RemoteConnection.SendQueryAsync(RCon.StaticHelpers.QueryType.GET_INFO); + if (response.Length == 2) + break; + await Task.Delay(RCon.StaticHelpers.FloodProtectionInterval); + } return response.FirstOrDefault(r => r[0] == '\\')?.DictionaryFromKeyValue(); } diff --git a/WebfrontCore/Controllers/ClientController.cs b/WebfrontCore/Controllers/ClientController.cs index adeeb32bf..0824c5475 100644 --- a/WebfrontCore/Controllers/ClientController.cs +++ b/WebfrontCore/Controllers/ClientController.cs @@ -149,6 +149,7 @@ namespace WebfrontCore.Controllers { var clients = (await Manager.GetClientService().GetClientByName(clientName)) .OrderByDescending(c => c.LastConnection); + var clientsDto = clients.Select(c => new PlayerInfo() { Name = c.Name, diff --git a/_customcallbacks.gsc b/_customcallbacks.gsc index 3f719013e..c6d3ac409 100644 --- a/_customcallbacks.gsc +++ b/_customcallbacks.gsc @@ -20,18 +20,16 @@ init() level.playerTags[4] = "j_shoulder_ri"; level.playerTags[5] = "j_shoulder_le"; level.playerTags[6] = "j_elbow_ri"; - level.playerTags[7] = "j_spineupper"; - level.playerTags[8] = "j_spineupper"; - level.playerTags[9] = "j_elbow_le"; - level.playerTags[10] = "j_wrist_ri"; - level.playerTags[11] = "j_wrist_le"; - level.playerTags[12] = "j_hip_ri"; - level.playerTags[13] = "j_hip_le"; - level.playerTags[14] = "j_knee_ri"; - level.playerTags[15] = "j_knee_le"; - level.playerTags[16] = "j_ankle_ri"; - level.playerTags[17] = "j_ankle_le"; - level.playerTags[18] = "j_helmet"; + level.playerTags[7] = "j_elbow_le"; + level.playerTags[8] = "j_wrist_ri"; + level.playerTags[9] = "j_wrist_le"; + level.playerTags[10] = "j_hip_ri"; + level.playerTags[11] = "j_hip_le"; + level.playerTags[12] = "j_knee_ri"; + level.playerTags[13] = "j_knee_le"; + level.playerTags[14] = "j_ankle_ri"; + level.playerTags[15] = "j_ankle_le"; + level.playerTags[16] = "j_helmet"; } @@ -44,6 +42,58 @@ onPlayerConnect(player) } } +visibilityMultiplierForBone(bone) +{ + multiplier = 0; + + switch (bone) + { + case "none": + break; + case "j_helmet": + case "j_head": + multiplier = 0.0216; + break; + case "j_neck": + multiplier = 0.0236; + break; + case "j_spineupper": + multiplier = 0.1977; + break; + case "j_spinelower": + multiplier = 0.2202; + break; + case "j_shoulder_ri": + case "j_shoulder_le": + multiplier = 0.0344; + break; + case "j_elbow_ri": + case "j_elbow_le": + multiplier = 0.03394; + break; + case "j_wrist_ri": + case "j_wrist_le": + multiplier = 0.01296; + break; + case "j_hip_ri": + case "j_hip_le": + multiplier = 0.0860; + break; + case "j_knee_ri": + case "j_knee_le": + multiplier = 0.0782; + break; + case "j_ankle_ri": + case "j_ankle_le": + multiplier = 0.0118; + break; + case "gun": + break; + } + + return multiplier; +} + hitLocationToBone(hitloc) { switch(hitloc) @@ -135,16 +185,16 @@ waitForAdditionalAngles(logString) runVisibilityCheck(attacker, victim) { start = attacker getTagOrigin("tag_eye"); - traceSucceedCount = 0; + traceVisibilityAmount = 0; - for (i = 0; i < 19; i++) + for (i = 0; i < 17; i++) { if (sightTracePassed(start, victim getTagOrigin(level.playerTags[i]), false, attacker)) { - traceSucceedCount += 1; + traceVisibilityAmount += 1.0 * visibilityMultiplierForBone(level.playerTags[i]); } } - return traceSucceedCount / 20; + return traceVisibilityAmount; } vectorScale(vector, scale) @@ -170,7 +220,6 @@ Process_Hit(type, attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon) trace = bulletTrace(start, end, true, _attacker); playerVisibilityPercentage = runVisibilityCheck(_attacker, victim); - logLine = "Script" + type + ";" + _attacker.guid + ";" + victim.guid + ";" + _attacker GetTagOrigin("tag_eye") + ";" + location + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + ";" + _attacker getPlayerAngles() + ";" + gettime() + ";" + isKillstreakKill + ";" + _attacker playerADS() + ";" + trace["fraction"] + ";" + playerVisibilityPercentage; attacker thread waitForAdditionalAngles(logLine); }