From 99390f1f3559e76391046ef0059381beea02817b Mon Sep 17 00:00:00 2001 From: RaidMax Date: Thu, 26 Apr 2018 01:13:04 -0500 Subject: [PATCH] fixed issue with status response erroring when incorrect length view angle vector parse fail is now a handled exception change local host check to byte array to make it faster than comparing string kick command now requires moderator level or higher tempban now requires administrator level or higher hopefully fixed negative SPM bug pipelined the events and consolidated them to run through GameEventHandler uniform console colors --- Application/Application.csproj | 1 + Application/GameEventHandler.cs | 46 ++ Application/IO/GameLogEvent.cs | 72 +++ Application/IO/GameLogReader.cs | 49 ++ Application/Logger.cs | 11 +- Application/Main.cs | 23 +- Application/Manager.cs | 76 ++-- Application/RconParsers/IW4RConParser.cs | 2 + Application/Server.cs | 426 ++++++++---------- Plugins/Login/Plugin.cs | 8 +- Plugins/ProfanityDeterment/Plugin.cs | 5 +- Plugins/Stats/Helpers/StatManager.cs | 15 +- Plugins/Stats/Plugin.cs | 2 +- Plugins/Tests/Plugin.cs | 3 +- Plugins/Welcome/Plugin.cs | 4 +- SharedLibraryCore/Commands/NativeCommands.cs | 15 +- SharedLibraryCore/Event.cs | 10 +- SharedLibraryCore/File.cs | 8 +- SharedLibraryCore/Interfaces/IEventHandler.cs | 28 ++ SharedLibraryCore/Interfaces/IEventParser.cs | 1 + SharedLibraryCore/Interfaces/IManager.cs | 9 + SharedLibraryCore/Server.cs | 6 +- SharedLibraryCore/Utilities.cs | 1 - WebfrontCore/Controllers/BaseController.cs | 48 +- WebfrontCore/Controllers/ConsoleController.cs | 11 +- .../wwwroot/css/bootstrap-custom.scss | 1 - 26 files changed, 526 insertions(+), 355 deletions(-) create mode 100644 Application/GameEventHandler.cs create mode 100644 Application/IO/GameLogEvent.cs create mode 100644 Application/IO/GameLogReader.cs create mode 100644 SharedLibraryCore/Interfaces/IEventHandler.cs diff --git a/Application/Application.csproj b/Application/Application.csproj index 7522fd4d0..ca9bec2b6 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -19,6 +19,7 @@ IW4MAdmin Debug;Release;Prerelease + IW4MAdmin.Application diff --git a/Application/GameEventHandler.cs b/Application/GameEventHandler.cs new file mode 100644 index 000000000..a6f55b5b3 --- /dev/null +++ b/Application/GameEventHandler.cs @@ -0,0 +1,46 @@ +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin.Application +{ + class GameEventHandler : IEventHandler + { + private Queue EventQueue; + private IManager Manager; + + public GameEventHandler(IManager mgr) + { + EventQueue = new Queue(); + Manager = mgr; + } + + public void AddEvent(GameEvent gameEvent) + { +#if DEBUG + Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner}"); +#endif + EventQueue.Enqueue(gameEvent); +#if DEBUG + Manager.GetLogger().WriteDebug($"There are now {EventQueue.Count} events in queue"); +#endif + Manager.SetHasEvent(); + } + + public string[] GetEventOutput() + { + throw new NotImplementedException(); + } + + public GameEvent GetNextEvent() + { +#if DEBUG + Manager.GetLogger().WriteDebug("Getting next event to be processed"); +#endif + + return EventQueue.Count > 0 ? EventQueue.Dequeue() : null; + } + } +} diff --git a/Application/IO/GameLogEvent.cs b/Application/IO/GameLogEvent.cs new file mode 100644 index 000000000..56e96d408 --- /dev/null +++ b/Application/IO/GameLogEvent.cs @@ -0,0 +1,72 @@ +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace IW4MAdmin.Application.IO +{ + class GameLogEvent + { + FileSystemWatcher LogPathWatcher; + Server Server; + long PreviousFileSize; + GameLogReader Reader; + Timer RefreshInfoTimer; + string GameLogFile; + FileInfo Info; + + public GameLogEvent(Server server, string gameLogPath, string gameLogName) + { + GameLogFile = gameLogPath; + Reader = new GameLogReader(gameLogPath, server.EventParser); + Server = server; + RefreshInfoTimer = new Timer((sender) => + { + var newInfo = new FileInfo(GameLogFile); + if (newInfo.Length - Info?.Length > 0) + LogPathWatcher_Changed(this, new FileSystemEventArgs(WatcherChangeTypes.Changed, "", "")); + Info = newInfo; + + }, null, 0, 100); + LogPathWatcher = new FileSystemWatcher() + { + Path = gameLogPath.Replace(gameLogName, ""), + Filter = gameLogName, + NotifyFilter = (NotifyFilters)383, + InternalBufferSize = 4096 + }; + + LogPathWatcher.Changed += LogPathWatcher_Changed; + LogPathWatcher.EnableRaisingEvents = true; + } + + ~GameLogEvent() + { + LogPathWatcher.EnableRaisingEvents = false; + } + + private void LogPathWatcher_Changed(object sender, FileSystemEventArgs e) + { + // retrieve the new file size + long newFileSize = new FileInfo(GameLogFile).Length; + + if (PreviousFileSize == 0) + PreviousFileSize = newFileSize; + + long fileDiff = newFileSize - PreviousFileSize; + + if (fileDiff < 1) + return; + + var events = Reader.EventsFromLog(Server, fileDiff); + foreach (var ev in events) + Server.Manager.GetEventHandler().AddEvent(ev); + + PreviousFileSize = newFileSize; + } + } +} diff --git a/Application/IO/GameLogReader.cs b/Application/IO/GameLogReader.cs new file mode 100644 index 000000000..f6e60fae4 --- /dev/null +++ b/Application/IO/GameLogReader.cs @@ -0,0 +1,49 @@ +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace IW4MAdmin.Application.IO +{ + class GameLogReader + { + IEventParser Parser; + string LogFile; + + public GameLogReader(string logFile, IEventParser parser) + { + LogFile = logFile; + Parser = parser; + } + + public ICollection EventsFromLog(Server server, long fileSizeDiff) + { + // allocate the bytes for the new log lines + byte[] fileBytes = new byte[fileSizeDiff]; + + // open the file as a stream + using (var rd = new BinaryReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType)) + { + rd.BaseStream.Seek(rd.BaseStream.Length - fileSizeDiff - 1, SeekOrigin.Begin); + // the difference should be in the range of a int :P + rd.Read(fileBytes, 0, (int)fileSizeDiff); + } + + // convert to event line list + string[] logLines = Utilities.EncodingType.GetString(fileBytes).Replace("\r", "").Split('\n'); + + List events = new List(); + + // parse each line + foreach (string eventLine in logLines) + { + if (eventLine.Length > 0) + events.Add(Parser.GetEvent(server, eventLine)); + } + + return events; + } + } +} diff --git a/Application/Logger.cs b/Application/Logger.cs index 9f0f1c694..90a63f0f6 100644 --- a/Application/Logger.cs +++ b/Application/Logger.cs @@ -30,17 +30,8 @@ namespace IW4MAdmin.Application void Write(string msg, LogType type) { - string stringType; - - try - { - stringType = Utilities.CurrentLocalization.LocalizationSet[$"GLOBAL_{type.ToString().ToUpper()}"]; - } - - catch(KeyNotFoundException) - { + if (!Utilities.CurrentLocalization.LocalizationSet.TryGetValue($"GLOBAL_{type.ToString().ToUpper()}", out string stringType)) stringType = type.ToString(); - } string LogLine = $"[{DateTime.Now.ToString("HH:mm:ss")}] - {stringType}: {msg}"; lock (ThreadLock) diff --git a/Application/Main.cs b/Application/Main.cs index 082506c6d..cf96bbbc3 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -24,6 +24,7 @@ namespace IW4MAdmin.Application Localization.Configure.Initialize(); var loc = Utilities.CurrentLocalization.LocalizationSet; Console.OutputEncoding = Encoding.UTF8; + Console.ForegroundColor = ConsoleColor.Gray; Version = Assembly.GetExecutingAssembly().GetName().Version.Major + Assembly.GetExecutingAssembly().GetName().Version.Minor / 10.0f; Version = Math.Round(Version, 2); @@ -40,7 +41,7 @@ namespace IW4MAdmin.Application ServerManager = ApplicationManager.GetInstance(); - using (var db = new DatabaseContext(ServerManager.GetApplicationSettings().Configuration().ConnectionString)) + using (var db = new DatabaseContext(ServerManager.GetApplicationSettings().Configuration().ConnectionString)) new ContextSeed(db).Seed().Wait(); var api = API.Master.Endpoint.Get(); @@ -69,7 +70,7 @@ namespace IW4MAdmin.Application { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(loc["MANAGER_VERSION_FAIL"]); - Console.ForegroundColor = ConsoleColor.White; + Console.ForegroundColor = ConsoleColor.Gray; } #if !PRERELEASE @@ -78,7 +79,7 @@ namespace IW4MAdmin.Application Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]"); Console.WriteLine($"{loc["MANAGER_VERSION_CURRENT"]} [v{Version.ToString("0.0")}]"); - Console.ForegroundColor = ConsoleColor.White; + Console.ForegroundColor = ConsoleColor.Gray; } #else else if (version.CurrentVersionPrerelease > Version) @@ -86,14 +87,14 @@ namespace IW4MAdmin.Application Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine($"IW4MAdmin-Prerelease {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionPrerelease.ToString("0.0")}-pr]"); Console.WriteLine($"{loc["MANAGER_VERSION_CURRENT"]} [v{Version.ToString("0.0")}-pr]"); - Console.ForegroundColor = ConsoleColor.White; + Console.ForegroundColor = ConsoleColor.Gray; } #endif else { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(loc["MANAGER_VERSION_SUCCESS"]); - Console.ForegroundColor = ConsoleColor.White; + Console.ForegroundColor = ConsoleColor.Gray; } ServerManager.Init().Wait(); @@ -112,13 +113,13 @@ namespace IW4MAdmin.Application if (ServerManager.Servers.Count == 0) { - Console.WriteLine("No servers are currently being monitored"); + Console.WriteLine(loc["MANAGER_CONSOLE_NOSERV"]); continue; } Origin.CurrentServer = ServerManager.Servers[0]; GameEvent E = new GameEvent(GameEvent.EventType.Say, userInput, Origin, null, ServerManager.Servers[0]); - ServerManager.Servers[0].ExecuteEvent(E); + ServerManager.GetEventHandler().AddEvent(E); Console.Write('>'); } while (ServerManager.Running); @@ -128,10 +129,6 @@ namespace IW4MAdmin.Application { Task.Run(() => WebfrontCore.Program.Init(ServerManager)); } - - ServerManager.Start(); - ServerManager.Logger.WriteVerbose(loc["MANAGER_SHUTDOWN_SUCCESS"]); - } catch (Exception e) @@ -145,6 +142,10 @@ namespace IW4MAdmin.Application Console.WriteLine(loc["MANAGER_EXIT"]); Console.ReadKey(); } + + + ServerManager.Start(); + ServerManager.Logger.WriteVerbose(loc["MANAGER_SHUTDOWN_SUCCESS"]); } static void CheckDirectories() diff --git a/Application/Manager.cs b/Application/Manager.cs index 4be917e9b..9561cafe7 100644 --- a/Application/Manager.cs +++ b/Application/Manager.cs @@ -41,10 +41,13 @@ namespace IW4MAdmin.Application PenaltyService PenaltySvc; BaseConfigurationHandler ConfigHandler; EventApi Api; + GameEventHandler Handler; + ManualResetEventSlim OnEvent; + Timer StatusUpdateTimer; #if FTP_LOG const int UPDATE_FREQUENCY = 700; #else - const int UPDATE_FREQUENCY = 450; + const int UPDATE_FREQUENCY = 2000; #endif private ApplicationManager() @@ -63,6 +66,7 @@ namespace IW4MAdmin.Application ConfigHandler = new BaseConfigurationHandler("IW4MAdminSettings"); Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey); StartTime = DateTime.UtcNow; + OnEvent = new ManualResetEventSlim(); } private void OnCancelKey(object sender, ConsoleCancelEventArgs args) @@ -86,8 +90,18 @@ namespace IW4MAdmin.Application return Instance ?? (Instance = new ApplicationManager()); } + public void UpdateStatus(object state) + { + foreach (var server in Servers) + { + Task.Run(() => server.ProcessUpdatesAsync(new CancellationToken())); + } + } + public async Task Init() { + // setup the event handler after the class is initialized + Handler = new GameEventHandler(this); #region DATABASE var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted)) .Select(c => new @@ -248,13 +262,8 @@ namespace IW4MAdmin.Application } Logger.WriteVerbose($"{Utilities.CurrentLocalization.LocalizationSet["MANAGER_MONITORING_TEXT"]} {ServerInstance.Hostname}"); - - // this way we can keep track of execution time and see if problems arise. - var Status = new AsyncStatus(ServerInstance, UPDATE_FREQUENCY); - lock (TaskStatuses) - { - TaskStatuses.Add(Status); - } + // add the start event for this server + Handler.AddEvent(new GameEvent(GameEvent.EventType.Start, "Server started", null, null, ServerInstance)); } catch (ServerException e) @@ -273,6 +282,8 @@ namespace IW4MAdmin.Application } await Task.WhenAll(config.Servers.Select(c => Init(c)).ToArray()); + // start polling servers + StatusUpdateTimer = new Timer(UpdateStatus, null, 0, 10000); #endregion @@ -344,38 +355,32 @@ namespace IW4MAdmin.Application public void Start() { Task.Run(() => HeartBeatThread()); - while (Running || TaskStatuses.Count > 0) + GameEvent newEvent; + while (Running) { - for (int i = 0; i < TaskStatuses.Count; i++) + // wait for new event to be added + OnEvent.Wait(); + + // todo: sequencially or parallelize? + while ((newEvent = Handler.GetNextEvent()) != null) { - var Status = TaskStatuses[i]; - - // task is read to be rerun - if (Status.RequestedTask == null || Status.RequestedTask.Status == TaskStatus.RanToCompletion) + try { - // remove the task when we want to quit and last run has finished - if (!Running) - { - TaskStatuses.RemoveAt(i); - continue; - } - // normal operation - else - { - Status.Update(new Task(() => { return (Status.Dependant as Server).ProcessUpdatesAsync(Status.GetToken()).Result; })); - if (Status.RunAverage > 1000 + UPDATE_FREQUENCY && !(Status.Dependant as Server).Throttled) - Logger.WriteWarning($"Update task average execution is longer than desired for {(Status.Dependant as Server)} [{Status.RunAverage}ms]"); - } + Task.WaitAll(newEvent.Owner.ExecuteEvent(newEvent)); } - if (Status.RequestedTask.Status == TaskStatus.Faulted) + catch (Exception E) { - Logger.WriteWarning($"Update task for {(Status.Dependant as Server)} faulted, restarting"); - Status.Abort(); + Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationSet["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}"); + Logger.WriteDebug("Error Message: " + E.Message); + Logger.WriteDebug("Error Trace: " + E.StackTrace); } + // tell anyone waiting for the output that we're done + newEvent.OnProcessed.Set(); } - Thread.Sleep(UPDATE_FREQUENCY); + // signal that all events have been processed + OnEvent.Reset(); } #if !DEBUG foreach (var S in Servers) @@ -388,6 +393,9 @@ namespace IW4MAdmin.Application public void Stop() { Running = false; + + // trigger the event processing loop to end + SetHasEvent(); } public ILogger GetLogger() @@ -417,5 +425,11 @@ namespace IW4MAdmin.Application public IDictionary GetPrivilegedClients() => PrivilegedClients; public IEventApi GetEventApi() => Api; public bool ShutdownRequested() => !Running; + public IEventHandler GetEventHandler() => Handler; + + public void SetHasEvent() + { + OnEvent.Set(); + } } } diff --git a/Application/RconParsers/IW4RConParser.cs b/Application/RconParsers/IW4RConParser.cs index ca9caf0af..6cb5126d2 100644 --- a/Application/RconParsers/IW4RConParser.cs +++ b/Application/RconParsers/IW4RConParser.cs @@ -85,6 +85,8 @@ namespace Application.RconParsers if (Regex.Matches(responseLine, @" *^\d+", RegexOptions.IgnoreCase).Count > 0) { String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + if (playerInfo.Length < 4) + continue; int cID = -1; int Ping = -1; Int32.TryParse(playerInfo[2], out Ping); diff --git a/Application/Server.cs b/Application/Server.cs index 2e1b4fe87..0c9bdc6ed 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -18,6 +18,7 @@ using SharedLibraryCore.Exceptions; using Application.Misc; using Application.RconParsers; using IW4MAdmin.Application.EventParsers; +using IW4MAdmin.Application.IO; namespace IW4MAdmin { @@ -25,6 +26,8 @@ namespace IW4MAdmin { private CancellationToken cts; private static Dictionary loc = Utilities.CurrentLocalization.LocalizationSet; + private GameLogEvent LogEvent; + public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg) { } @@ -180,7 +183,8 @@ namespace IW4MAdmin Logger.WriteInfo($"Client {player} connecting..."); - await ExecuteEvent(new GameEvent(GameEvent.EventType.Connect, "", player, null, this)); + + Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Connect, "", player, null, this)); if (!Manager.GetApplicationSettings().Configuration().EnableClientVPNs && @@ -208,7 +212,7 @@ namespace IW4MAdmin Player Leaving = Players[cNum]; Logger.WriteInfo($"Client {Leaving} disconnecting..."); - await ExecuteEvent(new GameEvent(GameEvent.EventType.Disconnect, "", Leaving, null, this)); + Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Disconnect, "", Leaving, null, this)); Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds; Leaving.LastConnection = DateTime.UtcNow; @@ -255,16 +259,8 @@ namespace IW4MAdmin if (C.RequiresTarget || Args.Length > 0) { - int cNum = -1; - try - { - cNum = Convert.ToInt32(Args[0]); - } - - catch (FormatException) - { - - } + if (!Int32.TryParse(Args[0], out int cNum)) + cNum = -1; if (Args[0][0] == '@') // user specifying target by database ID { @@ -359,24 +355,27 @@ namespace IW4MAdmin public override async Task ExecuteEvent(GameEvent E) { - //if (Throttled) - // return; - + bool canExecuteCommand = true; await ProcessEvent(E); Manager.GetEventApi().OnServerEvent(this, E); foreach (IPlugin P in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) { -#if !DEBUG try -#endif { if (cts.IsCancellationRequested) break; await P.OnEventAsync(E, this); } -#if !DEBUG + + // this happens if a plugin (login) wants to stop commands from executing + catch (AuthorizationException e) + { + await E.Origin.Tell($"{loc["COMMAND_NOTAUTHORIZED"]} - {e.Message}"); + canExecuteCommand = false; + } + catch (Exception Except) { Logger.WriteError(String.Format("The plugin \"{0}\" generated an error. ( see log )", P.Name)); @@ -389,10 +388,168 @@ namespace IW4MAdmin } continue; } -#endif + } + + // hack: this prevents commands from getting executing that 'shouldn't' be + if (E.Type == GameEvent.EventType.Command && + E.Extra != null && + (canExecuteCommand || + E.Origin?.Level == Player.Permission.Console)) + { + await (((Command)E.Extra).ExecuteAsync(E)); + } + } + /// + /// Perform the server specific tasks when an event occurs + /// + /// + /// + override protected async Task ProcessEvent(GameEvent E) + { + if (E.Type == GameEvent.EventType.Connect) + { + // special case for IW5 when connect is from the log + if (E.Extra != null) + { + var logClient = (Player)E.Extra; + var client = (await this.GetStatusAsync()) + .Single(c => c.ClientNumber == logClient.ClientNumber && + c.Name == logClient.Name); + client.NetworkId = logClient.NetworkId; + + await AddPlayer(client); + + // hack: to prevent plugins from registering it as a real connect + E.Type = GameEvent.EventType.Unknown; + } + + else + { + ChatHistory.Add(new ChatInfo() + { + Name = E.Origin.Name, + 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)); + } + } + + else if (E.Type == GameEvent.EventType.Disconnect) + { + ChatHistory.Add(new ChatInfo() + { + Name = E.Origin.Name, + Message = "DISCONNECTED", + Time = DateTime.UtcNow + }); + } + + else if (E.Type == GameEvent.EventType.Script) + { + Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Kill, E.Data, E.Origin, E.Target, this)); + } + + if (E.Type == GameEvent.EventType.Say && E.Data.Length >= 2) + { + if (E.Data.Substring(0, 1) == "!" || + E.Data.Substring(0, 1) == "@" || + E.Origin.Level == Player.Permission.Console) + { + Command C = null; + + try + { + C = await ValidateCommand(E); + } + + catch (CommandException e) + { + Logger.WriteInfo(e.Message); + } + + if (C != null) + { + if (C.RequiresTarget && E.Target == null) + { + Logger.WriteWarning("Requested event (command) requiring target does not have a target!"); + } + + Manager.GetEventHandler().AddEvent(new GameEvent() + { + Type = GameEvent.EventType.Command, + Data = E.Data, + Origin = E.Origin, + Target = E.Target, + Owner = this, + Extra = C, + Remote = E.Remote, + Message = E.Message + }); + } + } + + else // Not a command + { + E.Data = E.Data.StripColors(); + + ChatHistory.Add(new ChatInfo() + { + Name = E.Origin.Name, + Message = E.Data, + Time = DateTime.UtcNow + }); + } + } + + if (E.Type == GameEvent.EventType.MapChange) + { + Logger.WriteInfo($"New map loaded - {ClientNum} active players"); + + // iw4 doesn't log the game info + if (E.Extra == null) + { + var dict = await this.GetInfoAsync(); + + Gametype = dict["gametype"].StripColors(); + Hostname = dict["hostname"].StripColors(); + + string mapname = dict["mapname"].StripColors(); + CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; + } + + else + { + var dict = (Dictionary)E.Extra; + Gametype = dict["g_gametype"].StripColors(); + Hostname = dict["sv_hostname"].StripColors(); + + string mapname = dict["mapname"].StripColors(); + CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; + } + } + + if (E.Type == GameEvent.EventType.MapEnd) + { + Logger.WriteInfo("Game ending..."); + } + + //todo: move + while (ChatHistory.Count > Math.Ceiling((double)ClientNum / 2)) + ChatHistory.RemoveAt(0); + + // the last client hasn't fully disconnected yet + // so there will still be at least 1 client left + if (ClientNum < 2) + ChatHistory.Clear(); + } + + async Task PollPlayersAsync() { var now = DateTime.Now; @@ -429,30 +586,16 @@ namespace IW4MAdmin return CurrentPlayers.Count; } - long l_size = -1; - String[] lines = new String[8]; - String[] oldLines = new String[8]; DateTime start = DateTime.Now; DateTime playerCountStart = DateTime.Now; DateTime lastCount = DateTime.Now; DateTime tickTime = DateTime.Now; - bool firstRun = true; - int count = 0; override public async Task ProcessUpdatesAsync(CancellationToken cts) { this.cts = cts; - //#if DEBUG == false try - //#endif { - // first start - if (firstRun) - { - await ExecuteEvent(new GameEvent(GameEvent.EventType.Start, "Server started", null, null, this)); - firstRun = false; - } - if ((DateTime.Now - LastPoll).TotalMinutes < 2 && ConnectionErrors >= 1) return true; @@ -517,44 +660,6 @@ namespace IW4MAdmin start = DateTime.Now; } - if (LogFile == null) - return true; - - if (l_size != LogFile.Length()) - { - lines = l_size != -1 ? await LogFile.Tail(12) : lines; - if (lines != oldLines) - { - l_size = LogFile.Length(); - int end = (lines.Length == oldLines.Length) ? lines.Length - 1 : Math.Abs((lines.Length - oldLines.Length)) - 1; - - for (count = 0; count < lines.Length; count++) - { - if (lines.Length < 1 && oldLines.Length < 1) - continue; - - if (lines[count] == oldLines[oldLines.Length - 1]) - continue; - - if (lines[count].Length < 10) // it's not a needed line - continue; - - else - { - GameEvent event_ = EventParser.GetEvent(this, lines[count]); - if (event_ != null) - { - if (event_.Origin == null) - continue; - - await ExecuteEvent(event_); - } - } - } - } - } - oldLines = lines; - l_size = LogFile.Length(); if (Manager.ShutdownRequested()) { foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) @@ -565,7 +670,6 @@ namespace IW4MAdmin } return true; } - //#if !DEBUG catch (NetworkException) { Logger.WriteError($"{loc["SERVER_ERROR_COMMUNICATION"]} {IP}:{Port}"); @@ -575,7 +679,6 @@ namespace IW4MAdmin catch (InvalidOperationException) { Logger.WriteWarning("Event could not parsed properly"); - Logger.WriteDebug($"Log Line: {lines[count]}"); return false; } @@ -586,7 +689,6 @@ namespace IW4MAdmin Logger.WriteDebug("Error Trace: " + E.StackTrace); return false; } - //#endif } public async Task Initialize() @@ -657,7 +759,7 @@ namespace IW4MAdmin CustomCallback = await ScriptLoaded(); string mainPath = EventParser.GetGameDir(); #if DEBUG - basepath.Value = @"\\192.168.88.253\Call of Duty 4"; + basepath.Value = @"\\192.168.88.253\mw2\"; #endif string logPath; if (GameName == Game.IW5) @@ -671,6 +773,7 @@ namespace IW4MAdmin $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{game.Value.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{logfile.Value}"; } + // hopefully fix wine drive name mangling if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -686,183 +789,18 @@ namespace IW4MAdmin } else { - LogFile = new IFile(logPath); - } + // LogFile = new IFile(logPath); + } + LogEvent = new GameLogEvent(this, logPath, logfile.Value); Logger.WriteInfo($"Log file is {logPath}"); #if DEBUG - // LogFile = new RemoteFile("https://raidmax.org/IW4MAdmin/getlog.php"); + // LogFile = new RemoteFile("https://raidmax.org/IW4MAdmin/getlog.php"); #else await Broadcast(loc["BROADCAST_ONLINE"]); #endif } - //Process any server event - override protected async Task ProcessEvent(GameEvent E) - { - if (E.Type == GameEvent.EventType.Connect) - { - // special case for IW5 when connect is from the log - if (E.Extra != null) - { - var logClient = (Player)E.Extra; - var client = (await this.GetStatusAsync()) - .Single(c => c.ClientNumber == logClient.ClientNumber && - c.Name == logClient.Name); - client.NetworkId = logClient.NetworkId; - - await AddPlayer(client); - - // hack: to prevent plugins from registering it as a real connect - E.Type = GameEvent.EventType.Unknown; - } - - else - { - ChatHistory.Add(new ChatInfo() - { - Name = E.Origin.Name, - 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)); - } - } - - else if (E.Type == GameEvent.EventType.Disconnect) - { - ChatHistory.Add(new ChatInfo() - { - Name = E.Origin.Name, - Message = "DISCONNECTED", - Time = DateTime.UtcNow - }); - } - - else if (E.Type == GameEvent.EventType.Script) - { - await ExecuteEvent(new GameEvent(GameEvent.EventType.Kill, E.Data, E.Origin, E.Target, this)); - } - - if (E.Type == GameEvent.EventType.Say && E.Data.Length >= 2) - { - if (E.Data.Substring(0, 1) == "!" || E.Data.Substring(0, 1) == "@" || E.Origin.Level == Player.Permission.Console) - { - Command C = null; - - try - { - C = await ValidateCommand(E); - } - - catch (CommandException e) - { - Logger.WriteInfo(e.Message); - } - - if (C != null) - { - if (C.RequiresTarget && E.Target == null) - { - Logger.WriteWarning("Requested event (command) requiring target does not have a target!"); - } - - try - { - if (!E.Remote && E.Origin.Level != Player.Permission.Console) - { - await ExecuteEvent(new GameEvent() - { - Type = GameEvent.EventType.Command, - Data = string.Empty, - Origin = E.Origin, - Target = E.Target, - Owner = this, - Extra = C, - Remote = E.Remote - }); - } - - await C.ExecuteAsync(E); - } - - catch (AuthorizationException e) - { - await E.Origin.Tell($"{loc["COMMAND_NOTAUTHORIZED"]} - {e.Message}"); - } - - catch (Exception Except) - { - Logger.WriteError(String.Format($"\"{0}\" {loc["SERVER_ERROR_COMMAND_LOG"]}", C.Name)); - Logger.WriteDebug(String.Format("Error Message: {0}", Except.Message)); - Logger.WriteDebug(String.Format("Error Trace: {0}", Except.StackTrace)); - await E.Origin.Tell($"^1{loc["SERVER_ERROR_COMMAND_INGAME"]}"); -#if DEBUG - await E.Origin.Tell(Except.Message); -#endif - } - } - } - - else // Not a command - { - E.Data = E.Data.StripColors(); - - ChatHistory.Add(new ChatInfo() - { - Name = E.Origin.Name, - Message = E.Data, - Time = DateTime.UtcNow - }); - } - } - - if (E.Type == GameEvent.EventType.MapChange) - { - Logger.WriteInfo($"New map loaded - {ClientNum} active players"); - - // iw4 doesn't log the game info - if (E.Extra == null) - { - var dict = await this.GetInfoAsync(); - - Gametype = dict["gametype"].StripColors(); - Hostname = dict["hostname"].StripColors(); - - string mapname = dict["mapname"].StripColors(); - CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; - } - - else - - { - var dict = (Dictionary)E.Extra; - Gametype = dict["g_gametype"].StripColors(); - Hostname = dict["sv_hostname"].StripColors(); - - string mapname = dict["mapname"].StripColors(); - CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; - } - - } - - if (E.Type == GameEvent.EventType.MapEnd) - { - Logger.WriteInfo("Game ending..."); - } - - //todo: move - while (ChatHistory.Count > Math.Ceiling((double)ClientNum / 2)) - ChatHistory.RemoveAt(0); - - // the last client hasn't fully disconnected yet - // so there will still be at least 1 client left - if (ClientNum < 2) - ChatHistory.Clear(); - } - public override async Task Warn(String Reason, Player Target, Player Origin) { // ensure player gets warned if command not performed on them in game diff --git a/Plugins/Login/Plugin.cs b/Plugins/Login/Plugin.cs index 70c9d4eae..2e276af48 100644 --- a/Plugins/Login/Plugin.cs +++ b/Plugins/Login/Plugin.cs @@ -70,12 +70,8 @@ namespace IW4MAdmin.Plugins.Login Config = cfg.Configuration(); } - public Task OnTickAsync(Server S) => Utilities.CompletedTask; + public Task OnTickAsync(Server S) => Task.CompletedTask; - public Task OnUnloadAsync() - { - AuthorizedClients.Clear(); - return Utilities.CompletedTask; - } + public Task OnUnloadAsync() => Task.CompletedTask; } } diff --git a/Plugins/ProfanityDeterment/Plugin.cs b/Plugins/ProfanityDeterment/Plugin.cs index 68ac19abd..03fbccae1 100644 --- a/Plugins/ProfanityDeterment/Plugin.cs +++ b/Plugins/ProfanityDeterment/Plugin.cs @@ -20,7 +20,6 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment BaseConfigurationHandler Settings; ConcurrentDictionary ProfanityCounts; IManager Manager; - Task CompletedTask = Task.FromResult(false); public async Task OnEventAsync(GameEvent E, Server S) { @@ -87,8 +86,8 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment Manager = manager; } - public Task OnTickAsync(Server S) => CompletedTask; + public Task OnTickAsync(Server S) => Task.CompletedTask; - public Task OnUnloadAsync() => CompletedTask; + public Task OnUnloadAsync() => Task.CompletedTask; } } diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index ccf17bf92..221d1dd12 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -187,7 +187,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // get individual client's stats var clientStats = playerStats[pl.ClientId]; // sync their score - clientStats.SessionScore = pl.Score; + clientStats.SessionScore += (pl.Score - clientStats.LastScore); // remove the client from the stats dictionary as they're leaving playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue); detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2); @@ -213,11 +213,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers 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) @@ -241,7 +243,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Damage = Int32.Parse(damage), HitLoc = ParseEnum.Get(hitLoc, typeof(IW4Info.HitLocation)), Weapon = ParseEnum.Get(weapon, typeof(IW4Info.WeaponName)), - ViewAngles = Vector3.Parse(viewAngles).FixIW4Angles(), + ViewAngles = vViewAngles, TimeOffset = Int64.Parse(offset), When = DateTime.UtcNow, IsKillstreakKill = isKillstreakKill[0] != '0', @@ -338,11 +340,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // update the total stats Servers[serverId].ServerStatistics.TotalKills += 1; - attackerStats.SessionScore = attacker.Score; - victimStats.SessionScore = victim.Score; + attackerStats.SessionScore += (attacker.Score - attackerStats.LastScore); + victimStats.SessionScore += (victim.Score - victimStats.LastScore); // calculate for the clients CalculateKill(attackerStats, victimStats); + // this should fix the negative SPM + attackerStats.LastScore = attacker.Score; + victimStats.LastScore = victim.Score; // show encouragement/discouragement string streakMessage = (attackerStats.ClientId != victimStats.ClientId) ? @@ -457,7 +462,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } clientStats.LastStatCalculation = DateTime.UtcNow; - clientStats.LastScore = clientStats.SessionScore; + //clientStats.LastScore = clientStats.SessionScore; return clientStats; } diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index 32d14be39..50c493919 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -253,7 +253,7 @@ namespace IW4MAdmin.Plugins.Stats Manager = new StatManager(manager); } - public Task OnTickAsync(Server S) => Utilities.CompletedTask; + public Task OnTickAsync(Server S) => Task.CompletedTask; public async Task OnUnloadAsync() { diff --git a/Plugins/Tests/Plugin.cs b/Plugins/Tests/Plugin.cs index a6210dd50..76b194c56 100644 --- a/Plugins/Tests/Plugin.cs +++ b/Plugins/Tests/Plugin.cs @@ -51,8 +51,9 @@ namespace IW4MAdmin.Plugins public Task OnLoadAsync(IManager manager) => Task.CompletedTask; - public async Task OnTickAsync(Server S) + public Task OnTickAsync(Server S) { + return Task.CompletedTask; /* if ((DateTime.Now - Interval).TotalSeconds > 1) { diff --git a/Plugins/Welcome/Plugin.cs b/Plugins/Welcome/Plugin.cs index 4197e19f9..449714f2b 100644 --- a/Plugins/Welcome/Plugin.cs +++ b/Plugins/Welcome/Plugin.cs @@ -72,9 +72,9 @@ namespace IW4MAdmin.Plugins.Welcome } } - public Task OnUnloadAsync() => Utilities.CompletedTask; + public Task OnUnloadAsync() => Task.CompletedTask; - public Task OnTickAsync(Server S) => Utilities.CompletedTask; + public Task OnTickAsync(Server S) => Task.CompletedTask; public async Task OnEventAsync(GameEvent E, Server S) { diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index dd1accd45..302e98591 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -32,7 +32,7 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent E) { - if ((await (E.Owner.Manager.GetClientService() as Services.ClientService).GetOwners()).Count == 0) + if ((await (E.Owner.Manager.GetClientService() as ClientService).GetOwners()).Count == 0) { E.Origin.Level = Player.Permission.Owner; await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationSet["COMMANDS_OWNER_SUCCESS"]); @@ -94,7 +94,7 @@ namespace SharedLibraryCore.Commands public class CKick : Command { public CKick() : - base("kick", Utilities.CurrentLocalization.LocalizationSet["COMMANDS_KICK_DESC"], "k", Player.Permission.Trusted, true, new CommandArgument[] + base("kick", Utilities.CurrentLocalization.LocalizationSet["COMMANDS_KICK_DESC"], "k", Player.Permission.Moderator, true, new CommandArgument[] { new CommandArgument() { @@ -113,7 +113,7 @@ namespace SharedLibraryCore.Commands { if (E.Origin.Level > E.Target.Level) { - await E.Owner.ExecuteEvent(new GameEvent(GameEvent.EventType.Kick, E.Data, E.Origin, E.Target, E.Owner)); + E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Kick, E.Data, E.Origin, E.Target, E.Owner)); await E.Target.Kick(E.Data, E.Origin); await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationSet["COMMANDS_KICK_SUCCESS"]}"); } @@ -144,7 +144,7 @@ namespace SharedLibraryCore.Commands public class CTempBan : Command { public CTempBan() : - base("tempban", Utilities.CurrentLocalization.LocalizationSet["COMMANDS_TEMPBAN_DESC"], "tb", Player.Permission.Moderator, true, new CommandArgument[] + base("tempban", Utilities.CurrentLocalization.LocalizationSet["COMMANDS_TEMPBAN_DESC"], "tb", Player.Permission.Administrator, true, new CommandArgument[] { new CommandArgument() { @@ -716,7 +716,7 @@ namespace SharedLibraryCore.Commands }; await E.Owner.Manager.GetPenaltyService().Create(newPenalty); - await E.Owner.ExecuteEvent(new GameEvent(GameEvent.EventType.Flag, E.Data, E.Origin, E.Target, E.Owner)); + E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Flag, E.Data, E.Origin, E.Target, E.Owner)); await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationSet["COMMANDS_FLAG_SUCCESS"]} ^5{E.Target.Name}"); } @@ -770,7 +770,7 @@ namespace SharedLibraryCore.Commands E.Owner.Reports.Add(new Report(E.Target, E.Origin, E.Data)); await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationSet["COMMANDS_REPORT_SUCCESS"]); - await E.Owner.ExecuteEvent(new GameEvent(GameEvent.EventType.Report, E.Data, E.Origin, E.Target, E.Owner)); + E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Report, E.Data, E.Origin, E.Target, E.Owner)); await E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data)); } } @@ -1023,7 +1023,8 @@ namespace SharedLibraryCore.Commands E.Origin.PasswordSalt = hashedPassword[1]; // update the password for the client in privileged - E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId] = E.Origin; + E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId].Password = hashedPassword[0]; + E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId].PasswordSalt = hashedPassword[1]; await E.Owner.Manager.GetClientService().Update(E.Origin); await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationSet["COMMANDS_PASSWORD_SUCCESS"]); diff --git a/SharedLibraryCore/Event.cs b/SharedLibraryCore/Event.cs index 16f5983fa..02d82034d 100644 --- a/SharedLibraryCore/Event.cs +++ b/SharedLibraryCore/Event.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; - +using System.Threading; using SharedLibraryCore.Objects; namespace SharedLibraryCore @@ -47,10 +47,13 @@ namespace SharedLibraryCore Origin = O; Target = T; Owner = S; + OnProcessed = new ManualResetEventSlim(); } - public GameEvent() { } - + public GameEvent() + { + OnProcessed = new ManualResetEventSlim(); + } public EventType Type; public string Data; // Data is usually the message sent by player @@ -60,5 +63,6 @@ namespace SharedLibraryCore public Server Owner; public Boolean Remote = false; public object Extra { get; set; } + public ManualResetEventSlim OnProcessed { get; private set; } } } diff --git a/SharedLibraryCore/File.cs b/SharedLibraryCore/File.cs index 31ffcb2a6..e555ace5d 100644 --- a/SharedLibraryCore/File.cs +++ b/SharedLibraryCore/File.cs @@ -5,6 +5,7 @@ using System.IO; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using System.Linq; namespace SharedLibraryCore { @@ -27,7 +28,12 @@ namespace SharedLibraryCore public override long Length() { Retrieve(); - return FileCache[0].Length; + return FileCache.Sum(l => l.Length); + } + + public override Task Tail(int lineCount) + { + return Task.FromResult(FileCache); } } diff --git a/SharedLibraryCore/Interfaces/IEventHandler.cs b/SharedLibraryCore/Interfaces/IEventHandler.cs new file mode 100644 index 000000000..7fe2efc5c --- /dev/null +++ b/SharedLibraryCore/Interfaces/IEventHandler.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Interfaces +{ + /// + /// This class handle games events (from log, manual events, etc) + /// + public interface IEventHandler + { + /// + /// Add a game event event to the queue to be processed + /// + /// Game event + void AddEvent(GameEvent gameEvent); + /// + /// Get the next event to be processed + /// + /// Game event that needs to be processed + GameEvent GetNextEvent(); + /// + /// If an event has output. Like executing a command wait until it's available + /// + /// List of output strings + string[] GetEventOutput(); + } +} diff --git a/SharedLibraryCore/Interfaces/IEventParser.cs b/SharedLibraryCore/Interfaces/IEventParser.cs index 968ad44a8..c96f33f1f 100644 --- a/SharedLibraryCore/Interfaces/IEventParser.cs +++ b/SharedLibraryCore/Interfaces/IEventParser.cs @@ -12,6 +12,7 @@ namespace SharedLibraryCore.Interfaces /// server the event occurred on /// single log line string /// + /// todo: make this integrate without needing the server GameEvent GetEvent(Server server, string logLine); /// /// Get game specific folder prefix for log files diff --git a/SharedLibraryCore/Interfaces/IManager.cs b/SharedLibraryCore/Interfaces/IManager.cs index af032a231..55eb0550f 100644 --- a/SharedLibraryCore/Interfaces/IManager.cs +++ b/SharedLibraryCore/Interfaces/IManager.cs @@ -23,6 +23,15 @@ namespace SharedLibraryCore.Interfaces PenaltyService GetPenaltyService(); IDictionary GetPrivilegedClients(); IEventApi GetEventApi(); + /// + /// Get the event handlers + /// + /// EventHandler for the manager + IEventHandler GetEventHandler(); + /// + /// Signal to the manager that event(s) needs to be processed + /// + void SetHasEvent(); bool ShutdownRequested(); } } diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index ede04b296..053b7b031 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -41,6 +41,7 @@ namespace SharedLibraryCore PlayerHistory = new Queue(); ChatHistory = new List(); NextMessage = 0; + OnEvent = new ManualResetEventSlim(); CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName; CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName; InitializeTokens(); @@ -132,7 +133,7 @@ namespace SharedLibraryCore await this.ExecuteCommandAsync(formattedMessage); #else Logger.WriteVerbose(Message.StripColors()); - await Utilities.CompletedTask; + await Task.CompletedTask; #endif } @@ -150,7 +151,7 @@ namespace SharedLibraryCore await this.ExecuteCommandAsync(formattedMessage); #else Logger.WriteVerbose($"{Target.ClientNumber}->{Message.StripColors()}"); - await Utilities.CompletedTask; + await Task.CompletedTask; #endif if (Target.Level == Player.Permission.Console) @@ -308,6 +309,7 @@ namespace SharedLibraryCore public RCon.Connection RemoteConnection { get; protected set; } public IRConParser RconParser { get; protected set; } public IEventParser EventParser { get; set; } + public ManualResetEventSlim OnEvent { get; private set; } // Internal protected string IP; diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 70e892631..38d294df3 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -16,7 +16,6 @@ namespace SharedLibraryCore public static class Utilities { public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar; - public static readonly Task CompletedTask = Task.FromResult(false); public static Encoding EncodingType; public static Localization.Layout CurrentLocalization; diff --git a/WebfrontCore/Controllers/BaseController.cs b/WebfrontCore/Controllers/BaseController.cs index 044b78e1a..a409ebd01 100644 --- a/WebfrontCore/Controllers/BaseController.cs +++ b/WebfrontCore/Controllers/BaseController.cs @@ -16,24 +16,37 @@ namespace WebfrontCore.Controllers protected IManager Manager; protected readonly DatabaseContext Context; protected bool Authorized { get; private set; } - protected EFClient User { get; private set; } + protected EFClient Client { get; private set; } + private static byte[] LocalHost = { 127, 0, 0, 1 }; + private static string DiscordLink; + + public BaseController() + { + var Manager = Program.Manager; + if (Manager.GetApplicationSettings().Configuration().EnableDiscordLink) + { + string inviteLink = Manager.GetApplicationSettings().Configuration().DiscordInviteCode; + if (inviteLink != null) + DiscordLink = inviteLink.Contains("https") ? inviteLink : $"https://discordapp.com/invite/{inviteLink}"; + else + DiscordLink = ""; + } + } public override void OnActionExecuting(ActionExecutingContext context) { - Manager = Program.Manager; - - User = User ?? new EFClient() + Client = Client ?? new EFClient() { ClientId = -1 }; - if (HttpContext.Connection.RemoteIpAddress.ToString() != "127.0.0.1") + if (!HttpContext.Connection.RemoteIpAddress.GetAddressBytes().SequenceEqual(LocalHost)) { try { - User.ClientId = Convert.ToInt32(base.User.Claims.First(c => c.Type == ClaimTypes.Sid).Value); - User.Level = (Player.Permission)Enum.Parse(typeof(Player.Permission), base.User.Claims.First(c => c.Type == ClaimTypes.Role).Value); - User.CurrentAlias = new EFAlias() { Name = base.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value }; + Client.ClientId = Convert.ToInt32(base.User.Claims.First(c => c.Type == ClaimTypes.Sid).Value); + Client.Level = (Player.Permission)Enum.Parse(typeof(Player.Permission), User.Claims.First(c => c.Type == ClaimTypes.Role).Value); + Client.CurrentAlias = new EFAlias() { Name = User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value }; } catch (InvalidOperationException) @@ -44,24 +57,17 @@ namespace WebfrontCore.Controllers else { - User.ClientId = 1; - User.Level = Player.Permission.Console; - User.CurrentAlias = new EFAlias() { Name = "IW4MAdmin" }; + Client.ClientId = 1; + Client.Level = Player.Permission.Console; + Client.CurrentAlias = new EFAlias() { Name = "IW4MAdmin" }; } - Authorized = User.ClientId >= 0; + Authorized = Client.ClientId >= 0; ViewBag.Authorized = Authorized; ViewBag.Url = Startup.Configuration["Web:Address"]; - ViewBag.User = User; + ViewBag.User = Client; + ViewBag.DiscordLink = DiscordLink; - if (Manager.GetApplicationSettings().Configuration().EnableDiscordLink) - { - string inviteLink = Manager.GetApplicationSettings().Configuration().DiscordInviteCode; - if (inviteLink != null) - ViewBag.DiscordLink = inviteLink.Contains("https") ? inviteLink : $"https://discordapp.com/invite/{inviteLink}"; - else - ViewBag.DiscordLink = ""; - } base.OnActionExecuting(context); } } diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index c21434665..6868da350 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -32,10 +32,10 @@ namespace WebfrontCore.Controllers var server = Manager.GetServers().First(s => s.GetHashCode() == serverId); var client = new Player() { - ClientId = User.ClientId, - Level = User.Level, + ClientId = Client.ClientId, + Level = Client.Level, CurrentServer = server, - CurrentAlias = new Alias() { Name = User.Name } + CurrentAlias = new Alias() { Name = Client.Name } }; var remoteEvent = new GameEvent() { @@ -46,8 +46,9 @@ namespace WebfrontCore.Controllers Remote = true }; - await server.ExecuteEvent(remoteEvent); - + Manager.GetEventHandler().AddEvent(remoteEvent); + // wait for the event to process + remoteEvent.OnProcessed.Wait(); var response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList(); // remove the added command response diff --git a/WebfrontCore/wwwroot/css/bootstrap-custom.scss b/WebfrontCore/wwwroot/css/bootstrap-custom.scss index 1613aebe9..3f3ad42a0 100644 --- a/WebfrontCore/wwwroot/css/bootstrap-custom.scss +++ b/WebfrontCore/wwwroot/css/bootstrap-custom.scss @@ -33,7 +33,6 @@ a.nav-link { } .server-history, -.server-header, .server-activity, #mobile_seperator, .border-bottom {