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