From 699c19cd4b895280aeb9a88bf1cc3447bf1c9d06 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Mon, 14 May 2018 12:55:10 -0500 Subject: [PATCH] adding Cod4 support (for steam GUID is truncated to 16 characters) exit properly whoops add all linked accounts to drop down consolidate linked admin accounts to the most recently seen one limited some waits to 5s to hopefully prevent a rare thread lock --- Application/Application.csproj | 4 +- Application/EventParsers/IW4EventParser.cs | 17 +++-- Application/IO/GameLogReader.cs | 5 +- Application/Main.cs | 5 +- Application/Manager.cs | 4 +- Application/RconParsers/IW4RConParser.cs | 2 +- Application/Server.cs | 51 ++++++++----- Plugins/Stats/Cheat/Detection.cs | 11 +-- Plugins/Stats/Cheat/Thresholds.cs | 4 +- Plugins/Stats/Helpers/ServerStats.cs | 1 + Plugins/Stats/Helpers/StatManager.cs | 35 +++++---- .../Stats/Helpers/ThreadSafeStatsService.cs | 10 ++- SharedLibraryCore/Commands/NativeCommands.cs | 6 +- SharedLibraryCore/Database/Models/EFClient.cs | 2 + SharedLibraryCore/Dtos/PlayerInfo.cs | 1 + SharedLibraryCore/Dtos/ServerInfo.cs | 2 +- SharedLibraryCore/RCon/Connection.cs | 25 +----- SharedLibraryCore/Services/ClientService.cs | 76 +++++++++++++------ .../Services/GenericRepository.cs | 4 +- SharedLibraryCore/Services/PenaltyService.cs | 3 +- SharedLibraryCore/Utilities.cs | 1 + WebfrontCore/Controllers/BaseController.cs | 2 +- WebfrontCore/Controllers/ClientController.cs | 9 ++- WebfrontCore/Controllers/HomeController.cs | 6 ++ WebfrontCore/Controllers/ServerController.cs | 6 +- .../ViewComponents/ServerListViewComponent.cs | 4 +- .../Views/Client/Profile/Index.cshtml | 11 ++- .../Views/Server/_ClientActivity.cshtml | 6 +- WebfrontCore/Views/Server/_Server.cshtml | 1 - WebfrontCore/WebfrontCore.csproj | 2 +- 30 files changed, 184 insertions(+), 132 deletions(-) diff --git a/Application/Application.csproj b/Application/Application.csproj index 370817d2..960042f2 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -27,9 +27,9 @@ - + diff --git a/Application/EventParsers/IW4EventParser.cs b/Application/EventParsers/IW4EventParser.cs index e13f8158..f527c103 100644 --- a/Application/EventParsers/IW4EventParser.cs +++ b/Application/EventParsers/IW4EventParser.cs @@ -82,14 +82,17 @@ namespace IW4MAdmin.Application.EventParsers if (cleanedEventLine[0] == 'D') { - return new GameEvent() + if (Regex.Match(cleanedEventLine, @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$").Success) { - Type = GameEvent.EventType.Damage, - Data = Regex.Replace(logLine, @"[0-9]+:[0-9]+\ ", "").Trim(), - Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong()), - Target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()), - Owner = server - }; + return new GameEvent() + { + Type = GameEvent.EventType.Damage, + Data = cleanedEventLine, + Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong()), + Target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()), + Owner = server + }; + } } if (cleanedEventLine.Contains("ExitLevel")) diff --git a/Application/IO/GameLogReader.cs b/Application/IO/GameLogReader.cs index a2e62fc9..3a8c10f4 100644 --- a/Application/IO/GameLogReader.cs +++ b/Application/IO/GameLogReader.cs @@ -49,8 +49,11 @@ namespace IW4MAdmin.Application.IO events.Add(Parser.GetEvent(server, eventLine)); } - catch (Exception) + catch (Exception e) { + Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line"); + Program.ServerManager.GetLogger().WriteDebug(e.Message); + Program.ServerManager.GetLogger().WriteDebug(eventLine); } } } diff --git a/Application/Main.cs b/Application/Main.cs index 38eab43c..77fa06db 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -137,7 +137,7 @@ namespace IW4MAdmin.Application }; ServerManager.GetEventHandler().AddEvent(E); - E.OnProcessed.Wait(); + E.OnProcessed.Wait(5000); } Console.Write('>'); @@ -155,6 +155,7 @@ namespace IW4MAdmin.Application Console.WriteLine($"Exception: {e.Message}"); Console.WriteLine(loc["MANAGER_EXIT"]); Console.ReadKey(); + return; } if (ServerManager.GetApplicationSettings().Configuration().EnableWebFront) @@ -171,7 +172,7 @@ namespace IW4MAdmin.Application private static void OnCancelKey(object sender, ConsoleCancelEventArgs e) { ServerManager.Stop(); - OnShutdownComplete.Wait(); + OnShutdownComplete.Wait(5000); } static void CheckDirectories() diff --git a/Application/Manager.cs b/Application/Manager.cs index 0894a222..9f65137a 100644 --- a/Application/Manager.cs +++ b/Application/Manager.cs @@ -341,7 +341,7 @@ namespace IW4MAdmin.Application { try { - Heartbeat.Send(this, true).Wait(); + Heartbeat.Send(this, true).Wait(5000); heartbeatState.Connected = true; } @@ -356,7 +356,7 @@ namespace IW4MAdmin.Application { try { - Heartbeat.Send(this).Wait(); + Heartbeat.Send(this).Wait(5000); } catch (System.Net.Http.HttpRequestException e) { diff --git a/Application/RconParsers/IW4RConParser.cs b/Application/RconParsers/IW4RConParser.cs index 8e395a9e..5791449c 100644 --- a/Application/RconParsers/IW4RConParser.cs +++ b/Application/RconParsers/IW4RConParser.cs @@ -23,7 +23,7 @@ namespace Application.RconParsers TempBan = "tempbanclient {0} \"{1}\"" }; - private static string StatusRegex = @"^( *[0-9]+) +-*([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|bot[0-9]+|(?:[0-9]+)) +(.{0,20}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$"; + private static string StatusRegex = @"^( *[0-9]+) +-*([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|(?:[a-z]|[0-9]){32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$"; public async Task ExecuteCommandAsync(Connection connection, string command) { diff --git a/Application/Server.cs b/Application/Server.cs index 8b86bed7..31c84fd2 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -51,7 +51,6 @@ namespace IW4MAdmin override public async Task AddPlayer(Player polledPlayer) { - if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) || polledPlayer.Ping < 1 || polledPlayer.ClientNumber < 0) @@ -451,12 +450,16 @@ namespace IW4MAdmin { if (E.Type == GameEvent.EventType.Connect) { - ChatHistory.Add(new ChatInfo() + // this may be a fix for a hard to reproduce null exception error + lock (ChatHistory) { - Name = E.Origin.Name, - Message = "CONNECTED", - Time = DateTime.UtcNow - }); + ChatHistory.Add(new ChatInfo() + { + Name = E.Origin?.Name ?? "ERROR!", + Message = "CONNECTED", + Time = DateTime.UtcNow + }); + } if (E.Origin.Level > Player.Permission.Moderator) await E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count)); @@ -479,24 +482,35 @@ namespace IW4MAdmin else if (E.Type == GameEvent.EventType.Disconnect) { - ChatHistory.Add(new ChatInfo() + // this may be a fix for a hard to reproduce null exception error + lock (ChatHistory) { - Name = E.Origin.Name, - Message = "DISCONNECTED", - Time = DateTime.UtcNow - }); + ChatHistory.Add(new ChatInfo() + { + Name = E.Origin?.Name ?? "ERROR!", + Message = "DISCONNECTED", + Time = DateTime.UtcNow + }); + } } - if (E.Type == GameEvent.EventType.Say && E.Data?.Length >= 2) + if (E.Type == GameEvent.EventType.Say) { E.Data = E.Data.StripColors(); - ChatHistory.Add(new ChatInfo() + if (E.Data.Length > 0) { - Name = E.Origin.Name, - Message = E.Data, - Time = DateTime.UtcNow - }); + // this may be a fix for a hard to reproduce null exception error + lock (ChatHistory) + { + ChatHistory.Add(new ChatInfo() + { + Name = E.Origin?.Name ?? "ERROR!", + Message = E.Data, + Time = DateTime.UtcNow + }); + } + } } if (E.Type == GameEvent.EventType.MapChange) @@ -731,7 +745,10 @@ namespace IW4MAdmin GameName = Utilities.GetGame(version.Value); if (GameName == Game.IW4) + { EventParser = new IW4EventParser(); + RconParser = new IW4RConParser(); + } else if (GameName == Game.IW5) EventParser = new IW5EventParser(); else if (GameName == Game.T5M) diff --git a/Plugins/Stats/Cheat/Detection.cs b/Plugins/Stats/Cheat/Detection.cs index 23982901..03ec4763 100644 --- a/Plugins/Stats/Cheat/Detection.cs +++ b/Plugins/Stats/Cheat/Detection.cs @@ -81,8 +81,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount; hitLoc.HitOffsetAverage = (float)newAverage; - - if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset) + if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset && + hitLoc.HitCount > 15) { Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***"); Log.WriteDebug($"Lifetime Average = {newAverage}"); @@ -92,9 +92,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat return new DetectionPenaltyResult() { - ClientPenalty = Penalty.PenaltyType.Flag, + ClientPenalty = Penalty.PenaltyType.Ban, Value = hitLoc.HitOffsetAverage, HitCount = hitLoc.HitCount, + Type = DetectionType.Offset }; } @@ -102,7 +103,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat double sessAverage = (AngleDifferenceAverage * (HitCount - 1) + realAgainstPredict) / HitCount; AngleDifferenceAverage = sessAverage; - if (sessAverage > Thresholds.MaxOffset) + if (sessAverage > Thresholds.MaxOffset && + HitCount > 15) { Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***"); Log.WriteDebug($"Session Average = {sessAverage}"); @@ -124,7 +126,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat #endif } var currentStrain = Strain.GetStrain(kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset)); - LastOffset = kill.TimeOffset; if (currentStrain > ClientStats.MaxStrain) diff --git a/Plugins/Stats/Cheat/Thresholds.cs b/Plugins/Stats/Cheat/Thresholds.cs index bf8789e2..5b032b68 100644 --- a/Plugins/Stats/Cheat/Thresholds.cs +++ b/Plugins/Stats/Cheat/Thresholds.cs @@ -28,8 +28,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat public const double KillTimeThreshold = 0.2; public const double MaxStrainBan = 0.4399; - public const double MaxOffset = 4.789; - public const double MaxStrainFlag = 0.2; + public const double MaxOffset = 1.2; + public const double MaxStrainFlag = 1; public static double GetMarginOfError(int numKills) => 1.6455 / Math.Sqrt(numKills); diff --git a/Plugins/Stats/Helpers/ServerStats.cs b/Plugins/Stats/Helpers/ServerStats.cs index 5480d01b..2fcfd324 100644 --- a/Plugins/Stats/Helpers/ServerStats.cs +++ b/Plugins/Stats/Helpers/ServerStats.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using SharedLibraryCore.Services; namespace IW4MAdmin.Plugins.Stats.Helpers { diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 3d3ed9b0..0916d44d 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -104,7 +104,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // 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 clientStats = statsSvc.ClientStatSvc.Find(c => c.ClientId == pl.ClientId && c.ServerId == serverId).FirstOrDefault(); + var clientStatsSvc = statsSvc.ClientStatSvc; + var clientStats = clientStatsSvc.Find(c => c.ClientId == pl.ClientId && c.ServerId == serverId).FirstOrDefault(); if (clientStats == null) { @@ -127,7 +128,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers }; // insert if they've not been added - var clientStatsSvc = statsSvc.ClientStatSvc; clientStats = clientStatsSvc.Insert(clientStats); await clientStatsSvc.SaveChangesAsync(); } @@ -193,10 +193,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4); // sync their stats before they leave - // clientStats = UpdateStats(clientStats); - // var clientStatsSvc = statsSvc.ClientStatSvc; - // clientStatsSvc.Update(clientStats); - // await clientStatsSvc.SaveChangesAsync(); + 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; @@ -305,6 +305,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers 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 (kill.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || @@ -313,7 +315,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { clientStats.HitLocations.Single(hl => hl.Location == kill.HitLoc).HitCount += 1; - statsSvc.ClientStatSvc.Update(clientStats); + //statsSvc.ClientStatSvc.Update(clientStats); // await statsSvc.ClientStatSvc.SaveChangesAsync(); } @@ -345,7 +347,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { Data = penalty.Type == Cheat.Detection.DetectionType.Bone ? $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" : - $"{penalty.Type} -{Math.Round(penalty.Value, 2)}@{penalty.HitCount}", + $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}", Origin = new Player() { ClientId = 1, @@ -365,10 +367,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers await executePenalty(clientDetection.ProcessKill(kill, isDamage)); await executePenalty(clientDetection.ProcessTotalRatio(clientStats)); -#if DEBUG - statsSvc.ClientStatSvc.Update(clientStats); - await statsSvc.ClientStatSvc.SaveChangesAsync(); -#endif + await clientStatsSvc.SaveChangesAsync(); } } @@ -448,10 +447,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } // todo: do we want to save this immediately? - var statsSvc = ContextThreads[serverId].ClientStatSvc; - statsSvc.Update(attackerStats); - statsSvc.Update(victimStats); - await statsSvc.SaveChangesAsync(); + var clientStatsSvc = ContextThreads[serverId].ClientStatSvc; + clientStatsSvc.Update(attackerStats); + clientStatsSvc.Update(victimStats); + await clientStatsSvc.SaveChangesAsync(); } /// @@ -478,7 +477,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers victimStats.KillStreak = 0; // process the attacker's stats after the kills - //attackerStats = UpdateStats(attackerStats); + attackerStats = UpdateStats(attackerStats); // update after calculation attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds; @@ -630,7 +629,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Log.WriteDebug("Syncing stats contexts"); await statsSvc.ServerStatsSvc.SaveChangesAsync(); - await statsSvc.ClientStatSvc.SaveChangesAsync(); + //await statsSvc.ClientStatSvc.SaveChangesAsync(); await statsSvc.KillStatsSvc.SaveChangesAsync(); await statsSvc.ServerSvc.SaveChangesAsync(); diff --git a/Plugins/Stats/Helpers/ThreadSafeStatsService.cs b/Plugins/Stats/Helpers/ThreadSafeStatsService.cs index 22c93dab..7a3bea7b 100644 --- a/Plugins/Stats/Helpers/ThreadSafeStatsService.cs +++ b/Plugins/Stats/Helpers/ThreadSafeStatsService.cs @@ -20,7 +20,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public GenericRepository ServerSvc { get; private set; } public GenericRepository KillStatsSvc { get; private set; } public GenericRepository ServerStatsSvc { get; private set; } - public GenericRepository MessageSvc { get; private set; } + public GenericRepository MessageSvc + { + get + { + return new GenericRepository(); + } + } public ThreadSafeStatsService() { @@ -28,7 +34,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers ServerSvc = new GenericRepository(); KillStatsSvc = new GenericRepository(); ServerStatsSvc = new GenericRepository(); - MessageSvc = new GenericRepository(); + //MessageSvc = new GenericRepository(); } } } diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index 3735046f..ce7fab39 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -387,7 +387,7 @@ namespace SharedLibraryCore.Commands await E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE"]} [^5{E.Origin.Name}^7]"); else await E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE"]); - Task.Delay(5000).Wait(); + await Task.Delay(5000); await E.Owner.ExecuteCommandAsync("map_rotate"); } } @@ -553,14 +553,14 @@ namespace SharedLibraryCore.Commands if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap) { await E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_SUCCESS"]} ^5{m.Alias}"); - Task.Delay(5000).Wait(); + await Task.Delay(5000); await E.Owner.LoadMap(m.Name); return; } } await E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_UKN"]} ^5{newMap}"); - Task.Delay(5000).Wait(); + await Task.Delay(5000); await E.Owner.LoadMap(newMap); } } diff --git a/SharedLibraryCore/Database/Models/EFClient.cs b/SharedLibraryCore/Database/Models/EFClient.cs index 620e2f6f..249ed1f8 100644 --- a/SharedLibraryCore/Database/Models/EFClient.cs +++ b/SharedLibraryCore/Database/Models/EFClient.cs @@ -50,6 +50,8 @@ namespace SharedLibraryCore.Database.Models [NotMapped] public string IPAddressString => new System.Net.IPAddress(BitConverter.GetBytes(IPAddress)).ToString(); + [NotMapped] + public virtual IDictionary LinkedAccounts { get; set; } public virtual ICollection ReceivedPenalties { get; set; } public virtual ICollection AdministeredPenalties { get; set; } diff --git a/SharedLibraryCore/Dtos/PlayerInfo.cs b/SharedLibraryCore/Dtos/PlayerInfo.cs index 2eb37d61..8ecdcb1d 100644 --- a/SharedLibraryCore/Dtos/PlayerInfo.cs +++ b/SharedLibraryCore/Dtos/PlayerInfo.cs @@ -24,5 +24,6 @@ namespace SharedLibraryCore.Dtos public List Meta { get; set; } public bool Online { get; set; } public string TimeOnline { get; set; } + public IDictionary LinkedAccounts { get; set; } } } diff --git a/SharedLibraryCore/Dtos/ServerInfo.cs b/SharedLibraryCore/Dtos/ServerInfo.cs index f2c363a6..dd27a685 100644 --- a/SharedLibraryCore/Dtos/ServerInfo.cs +++ b/SharedLibraryCore/Dtos/ServerInfo.cs @@ -14,7 +14,7 @@ namespace SharedLibraryCore.Dtos public string GameType { get; set; } public int ClientCount { get; set; } public int MaxClients { get; set; } - public ChatInfo[] ChatHistory { get; set; } + public List ChatHistory { get; set; } public List Players { get; set; } public Helpers.PlayerHistory[] PlayerHistory { get; set; } public int ID { get; set; } diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index 2f75ce75..0d44dba8 100644 --- a/SharedLibraryCore/RCon/Connection.cs +++ b/SharedLibraryCore/RCon/Connection.cs @@ -33,25 +33,6 @@ namespace SharedLibraryCore.RCon } } - class ResponseEvent - { - public int Id { get; set; } - public string[] Response { get; set; } - public Task Awaiter - { - get - { - return Task.Run(() => FinishedEvent.Wait()); - } - } - private ManualResetEventSlim FinishedEvent; - - public ResponseEvent() - { - FinishedEvent = new ManualResetEventSlim(); - } - } - public class Connection { public IPEndPoint Endpoint { get; private set; } @@ -110,7 +91,7 @@ namespace SharedLibraryCore.RCon OnSent.Set(); } - catch (SocketException) + catch (Exception) { } } @@ -167,9 +148,9 @@ namespace SharedLibraryCore.RCon public async Task SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", bool waitForResponse = true) { // will this really prevent flooding? - if ((DateTime.Now - LastQuery).TotalMilliseconds < 250) + if ((DateTime.Now - LastQuery).TotalMilliseconds < 350) { - await Task.Delay(250); + await Task.Delay(350); } LastQuery = DateTime.Now; diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index 78bfdcb6..ae413167 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -107,11 +107,33 @@ namespace SharedLibraryCore.Services { using (var context = new DatabaseContext()) { - return await context.Clients - .AsNoTracking() - .Include(c => c.CurrentAlias) - .Include(c => c.AliasLink.Children) - .SingleOrDefaultAsync(e => e.ClientId == entityID); + context.ChangeTracker.AutoDetectChangesEnabled = false; + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + + var iqClient = from client in context.Clients + .Include(c => c.CurrentAlias) + .Include(c => c.AliasLink.Children) + where client.ClientId == entityID + select new + { + Client = client, + LinkedAccounts = (from linkedClient in context.Clients + where client.AliasLinkId == linkedClient.AliasLinkId + select new + { + linkedClient.ClientId, + linkedClient.NetworkId + }) + }; + var foundClient = await iqClient.FirstOrDefaultAsync(); + + foundClient.Client.LinkedAccounts = new Dictionary(); + // todo: find out the best way to do this + // I'm doing this here because I don't know the best way to have multiple awaits in the query + foreach (var linked in foundClient.LinkedAccounts) + foundClient.Client.LinkedAccounts.Add(linked.ClientId, linked.NetworkId); + + return foundClient.Client; } } @@ -216,11 +238,15 @@ namespace SharedLibraryCore.Services { using (var context = new DatabaseContext()) { - return await context.Clients - .AsNoTracking() - .Include(c => c.CurrentAlias) - .Where(c => c.Level >= Player.Permission.Trusted) - .ToListAsync(); + context.ChangeTracker.AutoDetectChangesEnabled = false; + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + + var iqClients = from client in context.Clients + .Include(c => c.CurrentAlias) + where client.Level >= Player.Permission.Trusted + select client; + + return await iqClients.ToListAsync(); } } @@ -233,14 +259,14 @@ namespace SharedLibraryCore.Services { var iqClients = (from alias in context.Aliases .AsNoTracking() - where alias.Name.ToLower() - .Contains(name.ToLower()) - 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) + where alias.Name.ToLower() + .Contains(name.ToLower()) + 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); @@ -255,13 +281,13 @@ namespace SharedLibraryCore.Services { var iqClients = (from alias in context.Aliases .AsNoTracking() - where alias.IPAddress == ipAddress - 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) + where alias.IPAddress == ipAddress + 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); diff --git a/SharedLibraryCore/Services/GenericRepository.cs b/SharedLibraryCore/Services/GenericRepository.cs index 4d5ee404..edae6577 100644 --- a/SharedLibraryCore/Services/GenericRepository.cs +++ b/SharedLibraryCore/Services/GenericRepository.cs @@ -12,7 +12,7 @@ namespace SharedLibraryCore.Services // https://stackoverflow.com/questions/43677906/crud-operations-with-entityframework-using-generic-type public class GenericRepository where TEntity : class { - private dynamic _context; + private DatabaseContext _context; private DbSet _dbSet; protected DbContext Context @@ -101,7 +101,6 @@ namespace SharedLibraryCore.Services dbSet.Attach(entity); dbSet.Remove(entity); - } public virtual void Delete(TEntity entity) @@ -119,7 +118,6 @@ namespace SharedLibraryCore.Services T entity = dbSet.Find(id); dbSet.Attach(entity); dbSet.Remove(entity); - } public virtual void Delete(object id) diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index 011fc12d..f20cc845 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -208,7 +208,8 @@ namespace SharedLibraryCore.Services Offense = penalty.Offense, Type = penalty.Type.ToString() }, - When = penalty.When + When = penalty.When, + Sensitive = penalty.Type == Penalty.PenaltyType.Flag }; // fixme: is this good and fast? var list = await iqPenalties.ToListAsync(); diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 978c7649..735160ea 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -182,6 +182,7 @@ namespace SharedLibraryCore public static long ConvertLong(this string str) { + str = str.Substring(0, Math.Min(str.Length, 16)); if (Int64.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long id)) return id; var bot = Regex.Match(str, @"bot[0-9]+").Value; diff --git a/WebfrontCore/Controllers/BaseController.cs b/WebfrontCore/Controllers/BaseController.cs index 035d26f0..3cc9f55f 100644 --- a/WebfrontCore/Controllers/BaseController.cs +++ b/WebfrontCore/Controllers/BaseController.cs @@ -71,7 +71,7 @@ namespace WebfrontCore.Controllers catch (System.Collections.Generic.KeyNotFoundException) { // force the "banned" client to be signed out - HttpContext.SignOutAsync().Wait(); + HttpContext.SignOutAsync().Wait(5000); } } diff --git a/WebfrontCore/Controllers/ClientController.cs b/WebfrontCore/Controllers/ClientController.cs index 055bc9b1..6dcad4e4 100644 --- a/WebfrontCore/Controllers/ClientController.cs +++ b/WebfrontCore/Controllers/ClientController.cs @@ -41,7 +41,8 @@ namespace WebfrontCore.Controllers .OrderBy(i => i) .ToList(), Online = Manager.GetActiveClients().FirstOrDefault(c => c.ClientId == client.ClientId) != null, - TimeOnline = (DateTime.UtcNow - client.LastConnection).TimeSpanText() + TimeOnline = (DateTime.UtcNow - client.LastConnection).TimeSpanText(), + LinkedAccounts = client.LinkedAccounts }; var meta = await MetaService.GetMeta(client.ClientId); @@ -54,7 +55,7 @@ namespace WebfrontCore.Controllers clientDto.Meta.Add(new ProfileMeta() { Key = Localization["WEBFRONT_CLIENT_META_MASKED"], - Value = client.Masked ? Localization["WEBFRONT_CLIENT_META_TRUE"]: Localization["WEBFRONT_CLIENT_META_FALSE"], + Value = client.Masked ? Localization["WEBFRONT_CLIENT_META_TRUE"] : Localization["WEBFRONT_CLIENT_META_FALSE"], Sensitive = true, When = DateTime.MinValue }); @@ -94,7 +95,9 @@ namespace WebfrontCore.Controllers { var admins = (await Manager.GetClientService().GetPrivilegedClients()) .Where(a => a.Active) - .OrderByDescending(a => a.Level); + .OrderByDescending(a => a.Level).ThenByDescending(a => a.LastConnection) + .GroupBy(a => a.AliasLinkId).Select(a => a.First()); + var adminsDict = new Dictionary>(); foreach (var admin in admins) diff --git a/WebfrontCore/Controllers/HomeController.cs b/WebfrontCore/Controllers/HomeController.cs index 30b2000c..88bb7993 100644 --- a/WebfrontCore/Controllers/HomeController.cs +++ b/WebfrontCore/Controllers/HomeController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc; using SharedLibraryCore.Dtos; @@ -21,6 +22,11 @@ namespace WebfrontCore.Controllers public IActionResult Error() { + var exceptionFeature = HttpContext.Features.Get(); + Manager.GetLogger().WriteError($"[Webfront] {exceptionFeature.Error.Message}"); + Manager.GetLogger().WriteDebug(exceptionFeature.Path); + Manager.GetLogger().WriteDebug(exceptionFeature.Error.StackTrace); + ViewBag.Description = Localization["WEBFRONT_ERROR_DESC"]; ViewBag.Title = Localization["WEBFRONT_ERROR_TITLE"]; return View(); diff --git a/WebfrontCore/Controllers/ServerController.cs b/WebfrontCore/Controllers/ServerController.cs index 123122fe..9e94ed6c 100644 --- a/WebfrontCore/Controllers/ServerController.cs +++ b/WebfrontCore/Controllers/ServerController.cs @@ -13,7 +13,6 @@ namespace WebfrontCore.Controllers [ResponseCache(NoStore = true, Duration = 0)] public IActionResult ClientActivity(int id) { - var s = Manager.GetServers().FirstOrDefault(s2 => s2.GetHashCode() == id); if (s == null) return View("Error", "Invalid server!"); @@ -27,14 +26,15 @@ namespace WebfrontCore.Controllers ClientCount = s.ClientNum, MaxClients = s.MaxClients, GameType = s.Gametype, - Players = s.Players.Where(p => p != null).Select(p => new PlayerInfo + Players = s.GetPlayersAsList() + .Select(p => new PlayerInfo { Name = p.Name, ClientId = p.ClientId, Level = p.Level.ToString(), LevelInt = (int)p.Level }).ToList(), - ChatHistory = s.ChatHistory.OrderBy(c => c.Time).Take((int)Math.Ceiling(s.ClientNum / 2.0)).ToArray(), + ChatHistory = s.ChatHistory, PlayerHistory = s.PlayerHistory.ToArray(), }; return PartialView("_ClientActivity", serverInfo); diff --git a/WebfrontCore/ViewComponents/ServerListViewComponent.cs b/WebfrontCore/ViewComponents/ServerListViewComponent.cs index 409f8b97..a5cea8cf 100644 --- a/WebfrontCore/ViewComponents/ServerListViewComponent.cs +++ b/WebfrontCore/ViewComponents/ServerListViewComponent.cs @@ -19,7 +19,7 @@ namespace WebfrontCore.ViewComponents MaxClients = s.MaxClients, GameType = s.Gametype, PlayerHistory = s.PlayerHistory.ToArray(), - Players = s.Players.Where(p => p != null) + Players = s.GetPlayersAsList() .Select(p => new PlayerInfo() { Name = p.Name, @@ -27,7 +27,7 @@ namespace WebfrontCore.ViewComponents Level = p.Level.ToString(), LevelInt = (int)p.Level }).ToList(), - ChatHistory = s.ChatHistory.ToArray(), + ChatHistory = s.ChatHistory, Online = !s.Throttled }).ToList(); return View("_List", serverInfo); diff --git a/WebfrontCore/Views/Client/Profile/Index.cshtml b/WebfrontCore/Views/Client/Profile/Index.cshtml index 9bee6c78..0c884154 100644 --- a/WebfrontCore/Views/Client/Profile/Index.cshtml +++ b/WebfrontCore/Views/Client/Profile/Index.cshtml @@ -31,13 +31,13 @@
@if (Model.LevelInt < (int)ViewBag.User.Level && - (SharedLibraryCore.Objects.Player.Permission)Model.LevelInt != SharedLibraryCore.Objects.Player.Permission.Banned) + (SharedLibraryCore.Objects.Player.Permission)Model.LevelInt != SharedLibraryCore.Objects.Player.Permission.Banned) { } @if (Model.LevelInt < (int)ViewBag.User.Level && - (SharedLibraryCore.Objects.Player.Permission)Model.LevelInt == SharedLibraryCore.Objects.Player.Permission.Banned) +(SharedLibraryCore.Objects.Player.Permission)Model.LevelInt == SharedLibraryCore.Objects.Player.Permission.Banned) { } @@ -45,10 +45,13 @@
@{ - @Model.NetworkId.ToString("X")
+ foreach (var linked in Model.LinkedAccounts) + { + @Html.ActionLink(linked.Value.ToString("X"), "ProfileAsync", "Client", new { id = linked.Key}, new { @class = "link-inverse" })
+ } foreach (string alias in Model.Aliases) { - @alias
+ @alias
} if (ViewBag.Authorized) diff --git a/WebfrontCore/Views/Server/_ClientActivity.cshtml b/WebfrontCore/Views/Server/_ClientActivity.cshtml index 464d9284..5b2f33e3 100644 --- a/WebfrontCore/Views/Server/_ClientActivity.cshtml +++ b/WebfrontCore/Views/Server/_ClientActivity.cshtml @@ -6,7 +6,7 @@ }
@{ - for (int i = 0; i < Model.ChatHistory.Length; i++) + for (int i = 0; i < Model.ChatHistory.Count; i++) { string message = @Model.ChatHistory[i].Message; if (Model.ChatHistory[i].Message == "CONNECTED") @@ -47,13 +47,13 @@
-@if (Model.ChatHistory.Length > 0) +@if (Model.ChatHistory.Count > 0) {
}
@{ - for (int i = 0; i < Model.ChatHistory.Length; i++) + for (int i = 0; i < Model.ChatHistory.Count; i++) { string message = @Model.ChatHistory[i].Message; if (Model.ChatHistory[i].Message == "CONNECTED") diff --git a/WebfrontCore/Views/Server/_Server.cshtml b/WebfrontCore/Views/Server/_Server.cshtml index 1a2ea435..209e2743 100644 --- a/WebfrontCore/Views/Server/_Server.cshtml +++ b/WebfrontCore/Views/Server/_Server.cshtml @@ -3,7 +3,6 @@ @{ Layout = null; - ViewBag.Description += Model.Name + ", "; }
diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index c0077afa..1960e294 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -2,7 +2,7 @@ netcoreapp2.0 - + false true 2.6 RaidMax.IW4MAdmin.WebfrontCore