diff --git a/Application/Application.csproj b/Application/Application.csproj index c4d3a54df..b12c87b08 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -6,7 +6,7 @@ 2.2.2 false RaidMax.IW4MAdmin.Application - 2.2.7.1 + 2.2.7.2 RaidMax Forever None IW4MAdmin @@ -32,8 +32,8 @@ true true - 2.2.7.1 - 2.2.7.1 + 2.2.7.2 + 2.2.7.2 7.1 @@ -47,12 +47,6 @@ - - - Always - - - diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 723b5c034..2175e01f6 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -15,6 +15,7 @@ using SharedLibraryCore.Interfaces; using SharedLibraryCore.Objects; using SharedLibraryCore.Services; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -26,7 +27,7 @@ namespace IW4MAdmin.Application { public class ApplicationManager : IManager { - private List _servers; + private ConcurrentBag _servers; public List Servers => _servers.OrderByDescending(s => s.ClientNum).ToList(); public ILogger Logger => GetLogger(0); public bool Running { get; private set; } @@ -41,8 +42,9 @@ namespace IW4MAdmin.Application public IList AdditionalRConParsers { get; } public IList AdditionalEventParsers { get; } public ITokenAuthentication TokenAuthenticator { get; } + public CancellationToken CancellationToken => _tokenSource.Token; public string ExternalIPAddress { get; private set; } - + public bool IsRestartRequested { get; private set; } static ApplicationManager Instance; readonly List TaskStatuses; List Commands; @@ -52,16 +54,16 @@ namespace IW4MAdmin.Application readonly PenaltyService PenaltySvc; public BaseConfigurationHandler ConfigHandler; GameEventHandler Handler; - ManualResetEventSlim OnQuit; readonly IPageList PageList; readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1); readonly Dictionary Loggers = new Dictionary(); private readonly MetaService _metaService; private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0); + private readonly CancellationTokenSource _tokenSource; private ApplicationManager() { - _servers = new List(); + _servers = new ConcurrentBag(); Commands = new List(); TaskStatuses = new List(); MessageTokens = new List(); @@ -70,7 +72,6 @@ namespace IW4MAdmin.Application PenaltySvc = new PenaltyService(); ConfigHandler = new BaseConfigurationHandler("IW4MAdminSettings"); StartTime = DateTime.UtcNow; - OnQuit = new ManualResetEventSlim(); PageList = new PageList(); AdditionalEventParsers = new List(); AdditionalRConParsers = new List(); @@ -78,6 +79,7 @@ namespace IW4MAdmin.Application OnServerEvent += EventApi.OnGameEvent; TokenAuthenticator = new TokenAuthentication(); _metaService = new MetaService(); + _tokenSource = new CancellationTokenSource(); } private async void OnGameEvent(object sender, GameEventArgs args) @@ -155,12 +157,12 @@ namespace IW4MAdmin.Application return Instance ?? (Instance = new ApplicationManager()); } - public async Task UpdateServerStates(CancellationToken token) + public async Task UpdateServerStates() { // store the server hash code and task for it var runningUpdateTasks = new Dictionary(); - while (Running) + while (!_tokenSource.IsCancellationRequested) { // select the server ids that have completed the update task var serverTasksToRemove = runningUpdateTasks @@ -191,10 +193,11 @@ namespace IW4MAdmin.Application { try { - await server.ProcessUpdatesAsync(token); + await server.ProcessUpdatesAsync(_tokenSource.Token); + if (server.Throttled) { - await Task.Delay((int)_throttleTimeout.TotalMilliseconds); + await Task.Delay((int)_throttleTimeout.TotalMilliseconds, _tokenSource.Token); } } @@ -218,7 +221,7 @@ namespace IW4MAdmin.Application #endif try { - await Task.Delay(ConfigHandler.Configuration().RConPollRate, token); + await Task.Delay(ConfigHandler.Configuration().RConPollRate, _tokenSource.Token); } // if a cancellation is received, we want to return immediately catch { break; } @@ -341,7 +344,7 @@ namespace IW4MAdmin.Application GetApplicationSettings().Configuration()?.DatabaseProvider)) { await new ContextSeed(db).Seed(); - } + } #endregion #region COMMANDS @@ -351,6 +354,7 @@ namespace IW4MAdmin.Application } Commands.Add(new CQuit()); + Commands.Add(new CRestart()); Commands.Add(new CKick()); Commands.Add(new CSay()); Commands.Add(new CTempBan()); @@ -517,7 +521,12 @@ namespace IW4MAdmin.Application MetaService.AddRuntimeMeta(getPenaltyMeta); #endregion - #region INIT + await InitializeServers(); + } + + private async Task InitializeServers() + { + var config = ConfigHandler.Configuration(); int successServers = 0; Exception lastException = null; @@ -531,10 +540,7 @@ namespace IW4MAdmin.Application var ServerInstance = new IW4MServer(this, Conf); await ServerInstance.Initialize(); - lock (_servers) - { - _servers.Add(ServerInstance); - } + _servers.Add(ServerInstance); Logger.WriteVerbose(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(ServerInstance.Hostname)); // add the start event for this server @@ -577,25 +583,25 @@ namespace IW4MAdmin.Application throw lastException; } } - #endregion } - private async Task SendHeartbeat(object state) - { - var heartbeatState = (HeartbeatState)state; - while (Running) + private async Task SendHeartbeat() + { + bool connected = false; + + while (!_tokenSource.IsCancellationRequested) { - if (!heartbeatState.Connected) + if (!connected) { try { await Heartbeat.Send(this, true); - heartbeatState.Connected = true; + connected = true; } catch (Exception e) { - heartbeatState.Connected = false; + connected = false; Logger.WriteWarning($"Could not connect to heartbeat server - {e.Message}"); } } @@ -621,7 +627,7 @@ namespace IW4MAdmin.Application { if (((RestEase.ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized) { - heartbeatState.Connected = false; + connected = false; } } } @@ -631,9 +637,10 @@ namespace IW4MAdmin.Application Logger.WriteWarning($"Could not send heartbeat - {e.Message}"); if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized) { - heartbeatState.Connected = false; + connected = false; } } + catch (Exception e) { Logger.WriteWarning($"Could not send heartbeat - {e.Message}"); @@ -643,31 +650,32 @@ namespace IW4MAdmin.Application try { - await Task.Delay(30000, heartbeatState.Token); + await Task.Delay(30000, _tokenSource.Token); } catch { break; } } } - public void Start() + public Task Start() { - var tokenSource = new CancellationTokenSource(); - // this needs to be run seperately from the main thread - _ = Task.Run(() => SendHeartbeat(new HeartbeatState() { Token = tokenSource.Token })); - _ = Task.Run(() => UpdateServerStates(tokenSource.Token)); - - while (Running) + return Task.WhenAll(new[] { - OnQuit.Wait(); - tokenSource.Cancel(); - OnQuit.Reset(); - } + SendHeartbeat(), + UpdateServerStates() + }); } public void Stop() { + _tokenSource.Cancel(); Running = false; - OnQuit.Set(); + Instance = null; + } + + public void Restart() + { + IsRestartRequested = true; + Stop(); } public ILogger GetLogger(long serverId) @@ -725,21 +733,11 @@ namespace IW4MAdmin.Application return ConfigHandler; } - public bool ShutdownRequested() - { - return !Running; - } - public IEventHandler GetEventHandler() { return Handler; } - public void SetHasEvent() - { - - } - public IList GetPluginAssemblies() { return SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies.Union(SharedLibraryCore.Plugins.PluginImporter.Assemblies).ToList(); diff --git a/Application/BuildScripts/PostPublish.bat b/Application/BuildScripts/PostPublish.bat index 0f159edc1..22d06a63a 100644 --- a/Application/BuildScripts/PostPublish.bat +++ b/Application/BuildScripts/PostPublish.bat @@ -108,8 +108,8 @@ if "%CurrentConfiguration%" == "Release" ( ) echo making start scripts -@(echo @echo off && echo @title IW4MAdmin && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd" -@(echo @echo off && echo @title IW4MAdmin && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd" +@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd" +@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd" -@(echo #!/bin/bash && echo dotnet Lib/IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh" -@(echo #!/bin/bash && echo dotnet Lib/IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh" +@(echo #!/bin/bash && echo export DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib/IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh" +@(echo #!/bin/bash && echo export DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib/IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh" diff --git a/Application/DefaultSettings.json b/Application/DefaultSettings.json index bd953c342..811565d16 100644 --- a/Application/DefaultSettings.json +++ b/Application/DefaultSettings.json @@ -16,7 +16,7 @@ "Keep grenade launcher use to a minimum", "Balance teams at ALL times" ], - "DisallowedClientNames": ["Unknown Soldier", "VickNet", "UnknownSoldier", "CHEATER"], + "DisallowedClientNames": [ "Unknown Soldier", "VickNet", "UnknownSoldier", "CHEATER", "Play77" ], "QuickMessages": [ { "Game": "IW4", @@ -517,6 +517,10 @@ "Alias": "Hanoi", "Name": "mp_hanoi" }, + { + "Alias": "Havana", + "Name": "mp_cairo" + }, { "Alias": "Hazard", "Name": "mp_golfcourse" diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index 3af8d2930..35cb588f3 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -8,10 +8,10 @@ namespace IW4MAdmin.Application.IO { class GameLogEventDetection { - Server Server; - long PreviousFileSize; - IGameLogReader Reader; - readonly string GameLogFile; + private long previousFileSize; + private readonly Server _server; + private readonly IGameLogReader _reader; + private readonly string _gameLogFile; class EventState { @@ -21,16 +21,16 @@ namespace IW4MAdmin.Application.IO public GameLogEventDetection(Server server, string gameLogPath, Uri gameLogServerUri) { - GameLogFile = gameLogPath; - Reader = gameLogServerUri != null ? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser) : Reader = new GameLogReader(gameLogPath, server.EventParser); - Server = server; + _gameLogFile = gameLogPath; + _reader = gameLogServerUri != null ? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser) : _reader = new GameLogReader(gameLogPath, server.EventParser); + _server = server; } public async Task PollForChanges() { - while (!Server.Manager.ShutdownRequested()) + while (!_server.Manager.CancellationToken.IsCancellationRequested) { - if (Server.IsInitialized) + if (_server.IsInitialized) { try { @@ -39,39 +39,40 @@ namespace IW4MAdmin.Application.IO catch (Exception e) { - Server.Logger.WriteWarning($"Failed to update log event for {Server.EndPoint}"); - Server.Logger.WriteDebug($"Exception: {e.Message}"); - Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}"); + _server.Logger.WriteWarning($"Failed to update log event for {_server.EndPoint}"); + _server.Logger.WriteDebug($"Exception: {e.Message}"); + _server.Logger.WriteDebug($"StackTrace: {e.StackTrace}"); } } - Thread.Sleep(Reader.UpdateInterval); + + await Task.Delay(_reader.UpdateInterval, _server.Manager.CancellationToken); } } private async Task UpdateLogEvents() { - long fileSize = Reader.Length; + long fileSize = _reader.Length; - if (PreviousFileSize == 0) - PreviousFileSize = fileSize; + if (previousFileSize == 0) + previousFileSize = fileSize; - long fileDiff = fileSize - PreviousFileSize; + long fileDiff = fileSize - previousFileSize; // this makes the http log get pulled if (fileDiff < 1 && fileSize != -1) return; - PreviousFileSize = fileSize; + previousFileSize = fileSize; - var events = await Reader.ReadEventsFromLog(Server, fileDiff, 0); + var events = await _reader.ReadEventsFromLog(_server, fileDiff, 0); foreach (var ev in events) { - Server.Manager.GetEventHandler().AddEvent(ev); - await ev.WaitAsync(); + _server.Manager.GetEventHandler().AddEvent(ev); + await ev.WaitAsync(Utilities.DefaultCommandTimeout, _server.Manager.CancellationToken); } - PreviousFileSize = fileSize; + previousFileSize = fileSize; } } } diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index e63f836ca..903df21bf 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -296,6 +296,20 @@ namespace IW4MAdmin Target = E.Target, Reason = E.Data }); + + Penalty newReport = new Penalty() + { + Type = Penalty.PenaltyType.Report, + Expires = DateTime.UtcNow, + Offender = E.Target, + Offense = E.Message, + Punisher = E.Origin, + Active = true, + When = DateTime.UtcNow, + Link = E.Target.AliasLink + }; + + await Manager.GetPenaltyService().Create(newReport); } else if (E.Type == GameEvent.EventType.TempBan) @@ -548,7 +562,7 @@ namespace IW4MAdmin try { #region SHUTDOWN - if (Manager.ShutdownRequested()) + if (Manager.CancellationToken.IsCancellationRequested) { foreach (var client in GetClientsAsList()) { @@ -560,7 +574,7 @@ namespace IW4MAdmin }; Manager.GetEventHandler().AddEvent(e); - await e.WaitAsync(); + await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken); } foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) @@ -597,7 +611,7 @@ namespace IW4MAdmin waiterList.Add(e); } // wait for all the disconnect tasks to finish - await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); + await Task.WhenAll(waiterList.Select(e => e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken))); waiterList.Clear(); // this are our new connecting clients @@ -621,7 +635,7 @@ namespace IW4MAdmin } // wait for all the connect tasks to finish - await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); + await Task.WhenAll(waiterList.Select(e => e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken))); waiterList.Clear(); // these are the clients that have updated @@ -638,7 +652,7 @@ namespace IW4MAdmin waiterList.Add(e); } - await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); + await Task.WhenAll(waiterList.Select(e => e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken))); if (ConnectionErrors > 0) { diff --git a/Application/Main.cs b/Application/Main.cs index 841226802..b56b26144 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -1,204 +1,221 @@ using IW4MAdmin.Application.Migration; using SharedLibraryCore; -using SharedLibraryCore.Localization; using System; -using System.IO; using System.Text; -using System.Threading; using System.Threading.Tasks; namespace IW4MAdmin.Application { public class Program { - static public double Version { get; private set; } - static public ApplicationManager ServerManager; - private static ManualResetEventSlim OnShutdownComplete = new ManualResetEventSlim(); + public static double Version { get; private set; } = Utilities.GetVersionAsDouble(); + public static ApplicationManager ServerManager; + private static Task ApplicationTask; - public static void Main(string[] args) + /// + /// entrypoint of the application + /// + /// + public static async Task Main() { AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory); + Console.OutputEncoding = Encoding.UTF8; Console.ForegroundColor = ConsoleColor.Gray; - Version = Utilities.GetVersionAsDouble(); + Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey); Console.WriteLine("====================================================="); - Console.WriteLine(" IW4M ADMIN"); + Console.WriteLine(" IW4MAdmin"); Console.WriteLine(" by RaidMax "); Console.WriteLine($" Version {Utilities.GetVersionAsString()}"); Console.WriteLine("====================================================="); - Index loc = null; + await LaunchAsync(); + } + /// + /// event callback executed when the control + c combination is detected + /// gracefully stops the server manager and waits for all tasks to finish + /// + /// + /// + private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e) + { + ServerManager?.Stop(); + await ApplicationTask; + } + + /// + /// task that initializes application and starts the application monitoring and runtime tasks + /// + /// + private static async Task LaunchAsync() + { + restart: try { ServerManager = ApplicationManager.GetInstance(); var configuration = ServerManager.GetApplicationSettings().Configuration(); + Localization.Configure.Initialize(configuration?.EnableCustomLocale ?? false ? (configuration.CustomLocale ?? "windows-1252") : "windows-1252"); - if (configuration != null) - { - Localization.Configure.Initialize(configuration.EnableCustomLocale ? (configuration.CustomLocale ?? "windows-1252") : "windows-1252"); - } - - else - { - Localization.Configure.Initialize(); - } - - loc = Utilities.CurrentLocalization.LocalizationIndex; - Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey); - - CheckDirectories(); - // do any needed migrations - // todo: move out + // do any needed housekeeping file/folder migrations ConfigurationMigration.MoveConfigFolder10518(null); + ConfigurationMigration.CheckDirectories(); - ServerManager.Logger.WriteInfo($"Version is {Version}"); + ServerManager.Logger.WriteInfo(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_VERSION"].FormatExt(Version)); - var api = API.Master.Endpoint.Get(); - - var version = new API.Master.VersionInfo() - { - CurrentVersionStable = 99.99f - }; - - try - { - version = api.GetVersion().Result; - } - - catch (Exception e) - { - ServerManager.Logger.WriteWarning(loc["MANAGER_VERSION_FAIL"]); - while (e.InnerException != null) - { - e = e.InnerException; - } - - ServerManager.Logger.WriteDebug(e.Message); - } - - if (version.CurrentVersionStable == 99.99f) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(loc["MANAGER_VERSION_FAIL"]); - Console.ForegroundColor = ConsoleColor.Gray; - } - -#if !PRERELEASE - else if (version.CurrentVersionStable > Version) - { - Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]"); - Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}]")); - Console.ForegroundColor = ConsoleColor.Gray; - } -#else - else if (version.CurrentVersionPrerelease > Version) - { - 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"].FormatExt($"[v{Version.ToString("0.0")}-pr]")); - Console.ForegroundColor = ConsoleColor.Gray; - } -#endif - else - { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(loc["MANAGER_VERSION_SUCCESS"]); - Console.ForegroundColor = ConsoleColor.Gray; - } - - ServerManager.Init().Wait(); - - var consoleTask = Task.Run(async () => - { - string userInput; - var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]); - - do - { - userInput = Console.ReadLine(); - - if (userInput?.ToLower() == "quit") - { - ServerManager.Stop(); - } - - if (ServerManager.Servers.Count == 0) - { - Console.WriteLine(loc["MANAGER_CONSOLE_NOSERV"]); - continue; - } - - if (userInput?.Length > 0) - { - GameEvent E = new GameEvent() - { - Type = GameEvent.EventType.Command, - Data = userInput, - Origin = Origin, - Owner = ServerManager.Servers[0] - }; - - ServerManager.GetEventHandler().AddEvent(E); - await E.WaitAsync(30 * 1000); - } - Console.Write('>'); - - } while (ServerManager.Running); - }); + await CheckVersion(); + await ServerManager.Init(); } catch (Exception e) { + var loc = Utilities.CurrentLocalization.LocalizationIndex; string failMessage = loc == null ? "Failed to initalize IW4MAdmin" : loc["MANAGER_INIT_FAIL"]; string exitMessage = loc == null ? "Press any key to exit..." : loc["MANAGER_EXIT"]; Console.WriteLine(failMessage); + while (e.InnerException != null) { e = e.InnerException; } + Console.WriteLine(e.Message); Console.WriteLine(exitMessage); Console.ReadKey(); - return; } - if (ServerManager.GetApplicationSettings().Configuration().EnableWebFront) + try { - Task.Run(() => WebfrontCore.Program.Init(ServerManager)); + ApplicationTask = RunApplicationTasksAsync(); + await ApplicationTask; } - OnShutdownComplete.Reset(); - ServerManager.Start(); - ServerManager.Logger.WriteVerbose(loc["MANAGER_SHUTDOWN_SUCCESS"]); - OnShutdownComplete.Set(); + catch { } + + if (ServerManager.IsRestartRequested) + { + goto restart; + } } - private static void OnCancelKey(object sender, ConsoleCancelEventArgs e) + /// + /// runs the core application tasks + /// + /// + private static async Task RunApplicationTasksAsync() { - ServerManager.Stop(); - OnShutdownComplete.Wait(); + var webfrontTask = ServerManager.GetApplicationSettings().Configuration().EnableWebFront ? + WebfrontCore.Program.Init(ServerManager, ServerManager.CancellationToken) : + Task.CompletedTask; + + var tasks = new[] + { + webfrontTask, + ReadConsoleInput(), + ServerManager.Start(), + }; + + await Task.WhenAll(tasks); + + ServerManager.Logger.WriteVerbose(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_SHUTDOWN_SUCCESS"]); } - static void CheckDirectories() + /// + /// checks for latest version of the application + /// notifies user if an update is available + /// + /// + private static async Task CheckVersion() { - if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Plugins"))) + var api = API.Master.Endpoint.Get(); + var loc = Utilities.CurrentLocalization.LocalizationIndex; + + var version = new API.Master.VersionInfo() { - Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Plugins")); + CurrentVersionStable = 99.99f + }; + + try + { + version = await api.GetVersion(); } - if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Database"))) + catch (Exception e) { - Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Database")); + ServerManager.Logger.WriteWarning(loc["MANAGER_VERSION_FAIL"]); + while (e.InnerException != null) + { + e = e.InnerException; + } + + ServerManager.Logger.WriteDebug(e.Message); } - if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Log"))) + if (version.CurrentVersionStable == 99.99f) { - Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Log")); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(loc["MANAGER_VERSION_FAIL"]); + Console.ForegroundColor = ConsoleColor.Gray; + } + +#if !PRERELEASE + else if (version.CurrentVersionStable > Version) + { + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]"); + Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}]")); + Console.ForegroundColor = ConsoleColor.Gray; + } +#else + else if (version.CurrentVersionPrerelease > Version) + { + 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"].FormatExt($"[v{Version.ToString("0.0")}-pr]")); + Console.ForegroundColor = ConsoleColor.Gray; + } +#endif + else + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(loc["MANAGER_VERSION_SUCCESS"]); + Console.ForegroundColor = ConsoleColor.Gray; + } + } + + /// + /// reads input from the console and executes entered commands on the default server + /// + /// + private static async Task ReadConsoleInput() + { + string lastCommand; + var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]); + + while (!ServerManager.CancellationToken.IsCancellationRequested) + { + lastCommand = Console.ReadLine(); + + if (lastCommand?.Length > 0) + { + if (lastCommand?.Length > 0) + { + GameEvent E = new GameEvent() + { + Type = GameEvent.EventType.Command, + Data = lastCommand, + Origin = Origin, + Owner = ServerManager.Servers[0] + }; + + ServerManager.GetEventHandler().AddEvent(E); + await E.WaitAsync(Utilities.DefaultCommandTimeout, ServerManager.CancellationToken); + Console.Write('>'); + } + } } } } diff --git a/Application/Migration/ConfigurationMigration.cs b/Application/Migration/ConfigurationMigration.cs index 8edd25152..487f7e735 100644 --- a/Application/Migration/ConfigurationMigration.cs +++ b/Application/Migration/ConfigurationMigration.cs @@ -15,6 +15,27 @@ namespace IW4MAdmin.Application.Migration /// class ConfigurationMigration { + /// + /// ensures required directories are created + /// + public static void CheckDirectories() + { + if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Plugins"))) + { + Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Plugins")); + } + + if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Database"))) + { + Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Database")); + } + + if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Log"))) + { + Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Log")); + } + } + /// /// moves existing configs from the root folder into a configs folder /// diff --git a/Application/Misc/Logger.cs b/Application/Misc/Logger.cs index 13ae7d3d7..79080c667 100644 --- a/Application/Misc/Logger.cs +++ b/Application/Misc/Logger.cs @@ -55,6 +55,7 @@ namespace IW4MAdmin.Application void Write(string msg, LogType type) { + return; OnLogWriting.Wait(); string stringType = type.ToString(); diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln index 90bfe320b..b96653921 100644 --- a/IW4MAdmin.sln +++ b/IW4MAdmin.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.16 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.352 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8B310-269E-46D4-A612-24601F16065F}" EndProject diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 27a6b0988..5aea63a9f 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -20,25 +20,22 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { public class StatManager { - private ConcurrentDictionary Servers; - private ILogger Log; - private readonly IManager Manager; - + private readonly ConcurrentDictionary _servers; + private readonly ILogger _log; private readonly SemaphoreSlim OnProcessingPenalty; private readonly SemaphoreSlim OnProcessingSensitive; public StatManager(IManager mgr) { - Servers = new ConcurrentDictionary(); - Log = mgr.GetLogger(0); - Manager = mgr; + _servers = new ConcurrentDictionary(); + _log = mgr.GetLogger(0); OnProcessingPenalty = new SemaphoreSlim(1, 1); OnProcessingSensitive = new SemaphoreSlim(1, 1); } public EFClientStatistics GetClientStats(int clientId, long serverId) { - return Servers[serverId].PlayerStats[clientId]; + return _servers[serverId].PlayerStats[clientId]; } public static Expression> GetRankingFunc(long? serverId = null) @@ -135,6 +132,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var iqStatsInfo = (from stat in context.Set() where clientIds.Contains(stat.ClientId) + where stat.Kills > 0 || stat.Deaths > 0 group stat by stat.ClientId into s select new { @@ -242,7 +240,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // check to see if the stats have ever been initialized var serverStats = InitializeServerStats(server.ServerId); - Servers.TryAdd(serverId, new ServerStats(server, serverStats) + _servers.TryAdd(serverId, new ServerStats(server, serverStats) { IsTeamBased = sv.Gametype != "dm" }); @@ -250,8 +248,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers catch (Exception e) { - Log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}"); - Log.WriteDebug(e.GetExceptionInfo()); + _log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}"); + _log.WriteDebug(e.GetExceptionInfo()); } } @@ -268,18 +266,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { long serverId = await GetIdForServer(pl.CurrentServer); - if (!Servers.ContainsKey(serverId)) + if (!_servers.ContainsKey(serverId)) { - Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found"); + _log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found"); return null; } - var playerStats = Servers[serverId].PlayerStats; - var detectionStats = Servers[serverId].PlayerDetections; + var playerStats = _servers[serverId].PlayerStats; + var detectionStats = _servers[serverId].PlayerDetections; if (playerStats.ContainsKey(pl.ClientId)) { - Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}"); + _log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}"); return playerStats[pl.ClientId]; } @@ -321,7 +319,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers if (!playerStats.TryAdd(clientStats.ClientId, clientStats)) { - Log.WriteWarning("Adding new client to stats failed"); + _log.WriteWarning("Adding new client to stats failed"); } } @@ -329,7 +327,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { if (!playerStats.TryAdd(clientStats.ClientId, clientStats)) { - Log.WriteWarning("Adding pre-existing client to stats failed"); + _log.WriteWarning("Adding pre-existing client to stats failed"); } } @@ -366,9 +364,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers clientStats.SessionScore = pl.Score; clientStats.LastScore = pl.Score; - if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats))) + if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(_log, clientStats))) { - Log.WriteWarning("Could not add client to detection"); + _log.WriteWarning("Could not add client to detection"); } pl.CurrentServer.Logger.WriteInfo($"Adding {pl} to stats"); @@ -379,8 +377,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers catch (Exception ex) { - Log.WriteWarning("Could not add client to stats"); - Log.WriteDebug(ex.GetExceptionInfo()); + _log.WriteWarning("Could not add client to stats"); + _log.WriteDebug(ex.GetExceptionInfo()); } finally @@ -401,9 +399,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers pl.CurrentServer.Logger.WriteInfo($"Removing {pl} from stats"); long serverId = await GetIdForServer(pl.CurrentServer); - var playerStats = Servers[serverId].PlayerStats; - var detectionStats = Servers[serverId].PlayerDetections; - var serverStats = Servers[serverId].ServerStatistics; + var playerStats = _servers[serverId].PlayerStats; + var detectionStats = _servers[serverId].PlayerDetections; + var serverStats = _servers[serverId].ServerStatistics; if (!playerStats.ContainsKey(pl.ClientId)) { @@ -473,8 +471,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers catch (FormatException) { - Log.WriteWarning("Could not parse kill or death origin or viewangle vectors"); - Log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles}"); + _log.WriteWarning("Could not parse kill or death origin or viewangle vectors"); + _log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles}"); await AddStandardKill(attacker, victim); return; } @@ -491,7 +489,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers catch (FormatException) { - Log.WriteWarning("Could not parse snapshot angles"); + _log.WriteWarning("Could not parse snapshot angles"); return; } @@ -537,13 +535,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } // incase the add player event get delayed - if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) + if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) { await AddPlayer(attacker); } - var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId]; - var clientStats = Servers[serverId].PlayerStats[attacker.ClientId]; + var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId]; + var clientStats = _servers[serverId].PlayerStats[attacker.ClientId]; using (var ctx = new DatabaseContext(disableTracking: true)) { @@ -598,8 +596,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers catch (Exception ex) { - Log.WriteError("Could not save hit or AC info"); - Log.WriteDebug(ex.GetExceptionInfo()); + _log.WriteError("Could not save hit or AC info"); + _log.WriteDebug(ex.GetExceptionInfo()); } OnProcessingPenalty.Release(1); @@ -627,7 +625,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } }; - await attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], penaltyClient, false).WaitAsync(); + await attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], penaltyClient, false).WaitAsync(Utilities.DefaultCommandTimeout, attacker.CurrentServer.Manager.CancellationToken); if (clientDetection.Tracker.HasChanges) { @@ -644,7 +642,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" : $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}"; - await attacker.Flag(flagReason, penaltyClient).WaitAsync(); + await attacker.Flag(flagReason, penaltyClient).WaitAsync(Utilities.DefaultCommandTimeout, attacker.CurrentServer.Manager.CancellationToken); if (clientDetection.Tracker.HasChanges) { @@ -714,33 +712,33 @@ namespace IW4MAdmin.Plugins.Stats.Helpers long serverId = await GetIdForServer(attacker.CurrentServer); EFClientStatistics attackerStats = null; - if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) + if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) { attackerStats = await AddPlayer(attacker); } else { - attackerStats = Servers[serverId].PlayerStats[attacker.ClientId]; + attackerStats = _servers[serverId].PlayerStats[attacker.ClientId]; } EFClientStatistics victimStats = null; - if (!Servers[serverId].PlayerStats.ContainsKey(victim.ClientId)) + if (!_servers[serverId].PlayerStats.ContainsKey(victim.ClientId)) { victimStats = await AddPlayer(victim); } else { - victimStats = Servers[serverId].PlayerStats[victim.ClientId]; + victimStats = _servers[serverId].PlayerStats[victim.ClientId]; } #if DEBUG - Log.WriteDebug("Calculating standard kill"); + _log.WriteDebug("Calculating standard kill"); #endif // update the total stats - Servers[serverId].ServerStatistics.TotalKills += 1; + _servers[serverId].ServerStatistics.TotalKills += 1; await Sync(attacker.CurrentServer); // this happens when the round has changed @@ -777,14 +775,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // fixme: why? if (double.IsNaN(victimStats.SPM) || double.IsNaN(victimStats.Skill)) { - Log.WriteDebug($"[StatManager::AddStandardKill] victim SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); + _log.WriteDebug($"[StatManager::AddStandardKill] victim SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); victimStats.SPM = 0.0; victimStats.Skill = 0.0; } if (double.IsNaN(attackerStats.SPM) || double.IsNaN(attackerStats.Skill)) { - Log.WriteDebug($"[StatManager::AddStandardKill] attacker SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); + _log.WriteDebug($"[StatManager::AddStandardKill] attacker SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); attackerStats.SPM = 0.0; attackerStats.Skill = 0.0; } @@ -1014,7 +1012,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers attackerStats = UpdateStats(attackerStats); // calulate elo - if (Servers[attackerStats.ServerId].PlayerStats.Count > 1) + if (_servers[attackerStats.ServerId].PlayerStats.Count > 1) { #region DEPRECATED /* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats @@ -1095,7 +1093,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } double killSPM = scoreDifference / timeSinceLastCalc; - double spmMultiplier = 2.934 * Math.Pow(Servers[clientStats.ServerId].TeamCount(clientStats.Team == IW4Info.Team.Allies ? IW4Info.Team.Axis : IW4Info.Team.Allies), -0.454); + double spmMultiplier = 2.934 * Math.Pow(_servers[clientStats.ServerId].TeamCount(clientStats.Team == IW4Info.Team.Allies ? IW4Info.Team.Axis : IW4Info.Team.Allies), -0.454); killSPM *= Math.Max(1, spmMultiplier); // update this for ac tracking @@ -1120,8 +1118,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers if (clientStats.SPM < 0) { - Log.WriteWarning("[StatManager:UpdateStats] clientStats SPM < 0"); - Log.WriteDebug($"{scoreDifference}-{clientStats.RoundScore} - {clientStats.LastScore} - {clientStats.SessionScore}"); + _log.WriteWarning("[StatManager:UpdateStats] clientStats SPM < 0"); + _log.WriteDebug($"{scoreDifference}-{clientStats.RoundScore} - {clientStats.LastScore} - {clientStats.SessionScore}"); clientStats.SPM = 0; } @@ -1131,8 +1129,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // fixme: how does this happen? if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill)) { - Log.WriteWarning("[StatManager::UpdateStats] clientStats SPM/Skill NaN"); - Log.WriteDebug($"{killSPM}-{KDRWeight}-{totalPlayTime}-{SPMAgainstPlayWeight}-{clientStats.SPM}-{clientStats.Skill}-{scoreDifference}"); + _log.WriteWarning("[StatManager::UpdateStats] clientStats SPM/Skill NaN"); + _log.WriteDebug($"{killSPM}-{KDRWeight}-{totalPlayTime}-{SPMAgainstPlayWeight}-{clientStats.SPM}-{clientStats.Skill}-{scoreDifference}"); clientStats.SPM = 0; clientStats.Skill = 0; } @@ -1154,7 +1152,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers if (serverStats == null) { - Log.WriteDebug($"Initializing server stats for {serverId}"); + _log.WriteDebug($"Initializing server stats for {serverId}"); // server stats have never been generated before serverStats = new EFServerStatistics() { @@ -1173,7 +1171,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public void ResetKillstreaks(long serverId) { - var serverStats = Servers[serverId]; + var serverStats = _servers[serverId]; foreach (var stat in serverStats.PlayerStats.Values) { @@ -1183,7 +1181,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public void ResetStats(int clientId, long serverId) { - var stats = Servers[serverId].PlayerStats[clientId]; + var stats = _servers[serverId].PlayerStats[clientId]; stats.Kills = 0; stats.Deaths = 0; stats.SPM = 0; @@ -1221,10 +1219,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers using (var ctx = new DatabaseContext(disableTracking: true)) { var serverSet = ctx.Set(); - serverSet.Update(Servers[serverId].Server); + serverSet.Update(_servers[serverId].Server); var serverStatsSet = ctx.Set(); - serverStatsSet.Update(Servers[serverId].ServerStatistics); + serverStatsSet.Update(_servers[serverId].ServerStatistics); await ctx.SaveChangesAsync(); } @@ -1232,7 +1230,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public void SetTeamBased(long serverId, bool isTeamBased) { - Servers[serverId].IsTeamBased = isTeamBased; + _servers[serverId].IsTeamBased = isTeamBased; } public static async Task GetIdForServer(Server server) diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index dcc3e1b53..c6faf8e14 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -23,7 +23,22 @@ namespace SharedLibraryCore.Commands public override Task ExecuteAsync(GameEvent E) { - return Task.Run(() => { E.Owner.Manager.Stop(); }); + E.Owner.Manager.Stop(); + return Task.CompletedTask; + } + } + + public class CRestart : Command + { + public CRestart() : + base("restart", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RESTART_DESC"], "rs", EFClient.Permission.Owner, false) + { } + + public override Task ExecuteAsync(GameEvent E) + { + E.Owner.Manager.Restart(); + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RESTART_SUCCESS"]); + return Task.CompletedTask; } } @@ -120,9 +135,18 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent E) { - var _ = !(await E.Target.Kick(E.Data, E.Origin).WaitAsync()).Failed ? - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"].FormatExt(E.Target.Name)) : - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"].FormatExt(E.Target.Name)); + switch ((await E.Target.Kick(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) + { + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"].FormatExt(E.Target.Name)); + break; + case GameEvent.EventFailReason.Exception: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; + default: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"].FormatExt(E.Target.Name)); + break; + } } } @@ -186,9 +210,18 @@ namespace SharedLibraryCore.Commands else { - var _ = !(await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync()).Failed ? - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(E.Target, length.TimeSpanText())) : - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"].FormatExt(E.Target.Name)); + switch ((await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) + { + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(E.Target, length.TimeSpanText())); + break; + case GameEvent.EventFailReason.Exception: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; + default: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"].FormatExt(E.Target.Name)); + break; + } } } } @@ -214,9 +247,18 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent E) { - var _ = !(await E.Target.Ban(E.Data, E.Origin, false).WaitAsync()).Failed ? - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"].FormatExt(E.Target.Name)) : + switch ((await E.Target.Ban(E.Data, E.Origin, false).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) + { + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"].FormatExt(E.Target.Name)); + break; + case GameEvent.EventFailReason.Exception: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; + default: E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_FAIL"].FormatExt(E.Target.Name)); + break; + } } } @@ -240,12 +282,21 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent E) { + // todo: don't do the lookup here var penalties = await E.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(E.Target.AliasLinkId); if (penalties.Where(p => p.Type == Penalty.PenaltyType.Ban || p.Type == Penalty.PenaltyType.TempBan).FirstOrDefault() != null) { - await E.Target.Unban(E.Data, E.Origin).WaitAsync(); - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_SUCCESS"].FormatExt(E.Target)); + switch ((await E.Target.Unban(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) + { + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_SUCCESS"].FormatExt(E.Target)); + break; + default: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; + } } + else { E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_FAIL"].FormatExt(E.Target)); @@ -587,12 +638,13 @@ namespace SharedLibraryCore.Commands if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap) { E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_SUCCESS"].FormatExt(m.Alias)); - await Task.Delay(5000); + await Task.Delay((int)(Utilities.DefaultCommandTimeout.TotalMilliseconds / 2.0)); await E.Owner.LoadMap(m.Name); return; } } + // todo: this can be moved into a single statement E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_UKN"].FormatExt(newMap)); await Task.Delay(5000); await E.Owner.LoadMap(newMap); @@ -720,27 +772,23 @@ namespace SharedLibraryCore.Commands }) { } - public override Task ExecuteAsync(GameEvent E) + public override async Task ExecuteAsync(GameEvent E) { - var flagEvent = E.Target.Flag(E.Data, E.Origin); - - if (E.FailReason == GameEvent.EventFailReason.Permission) + switch ((await E.Target.Flag(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) { - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_FAIL"].FormatExt(E.Target.Name)); + case GameEvent.EventFailReason.Permission: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_FAIL"].FormatExt(E.Target.Name)); + break; + case GameEvent.EventFailReason.Invalid: + E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_ALREADYFLAGGED"]}"); + break; + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_SUCCESS"].FormatExt(E.Target.Name)); + break; + default: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; } - - else if (E.FailReason == GameEvent.EventFailReason.Invalid) - { - E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_ALREADYFLAGGED"]}"); - } - - else - { - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_SUCCESS"].FormatExt(E.Target.Name)); - } - - return Task.CompletedTask; - } } @@ -757,27 +805,23 @@ namespace SharedLibraryCore.Commands }) { } - public override Task ExecuteAsync(GameEvent E) + public override async Task ExecuteAsync(GameEvent E) { - var unflagEvent = E.Target.Unflag(E.Data, E.Origin); - - if (unflagEvent.FailReason == GameEvent.EventFailReason.Permission) + switch ((await E.Target.Unflag(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) { - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_FAIL"].FormatExt(E.Target.Name)); + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"].FormatExt(E.Target.Name)); + break; + case GameEvent.EventFailReason.Permission: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_FAIL"].FormatExt(E.Target.Name)); + break; + case GameEvent.EventFailReason.Invalid: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_NOTFLAGGED"]); + break; + default: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; } - - else if (unflagEvent.FailReason == GameEvent.EventFailReason.Invalid) - { - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_NOTFLAGGED"]); - } - - else - { - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"].FormatExt(E.Target.Name)); - } - - return Task.CompletedTask; - // todo: update immediately? } } @@ -808,46 +852,30 @@ namespace SharedLibraryCore.Commands return; } - var reportEvent = commandEvent.Target.Report(commandEvent.Data, commandEvent.Origin); + bool success = false; - if (reportEvent.FailReason == GameEvent.EventFailReason.Permission) + switch ((await commandEvent.Target.Report(commandEvent.Data, commandEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, commandEvent.Owner.Manager.CancellationToken)).FailReason) { - commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL"].FormatExt(commandEvent.Target.Name)); + case GameEvent.EventFailReason.None: + commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_SUCCESS"]); + success = true; + break; + case GameEvent.EventFailReason.Exception: + commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_DUPLICATE"]); + break; + case GameEvent.EventFailReason.Permission: + commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL"].FormatExt(commandEvent.Target.Name)); + break; + case GameEvent.EventFailReason.Invalid: + commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_SELF"]); + break; + case GameEvent.EventFailReason.Throttle: + commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_TOOMANY"]); + break; } - else if (reportEvent.FailReason == GameEvent.EventFailReason.Invalid) + if (success) { - commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_SELF"]); - } - - else if (reportEvent.FailReason == GameEvent.EventFailReason.Throttle) - { - commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_TOOMANY"]); - } - - else if (reportEvent.Failed) - { - commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_DUPLICATE"]); - } - - else - { - // todo: move into server - Penalty newReport = new Penalty() - { - Type = Penalty.PenaltyType.Report, - Expires = DateTime.UtcNow, - Offender = commandEvent.Target, - Offense = commandEvent.Data, - Punisher = commandEvent.Origin, - Active = true, - When = DateTime.UtcNow, - Link = commandEvent.Target.AliasLink - }; - - await commandEvent.Owner.Manager.GetPenaltyService().Create(newReport); - - commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_SUCCESS"]); commandEvent.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", commandEvent.Origin.Name, commandEvent.Target.Name, commandEvent.Data)); } } @@ -1118,10 +1146,6 @@ namespace SharedLibraryCore.Commands E.Origin.Password = hashedPassword[0]; E.Origin.PasswordSalt = hashedPassword[1]; - // update the password for the client in privileged - //E.Owner.Manager.PrivilegedClients[E.Origin.ClientId].Password = hashedPassword[0]; - //E.Owner.Manager.PrivilegedClients[E.Origin.ClientId].PasswordSalt = hashedPassword[1]; - await E.Owner.Manager.GetClientService().Update(E.Origin); E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PASSWORD_SUCCESS"]); } diff --git a/SharedLibraryCore/Database/DatabaseContext.cs b/SharedLibraryCore/Database/DatabaseContext.cs index b9d9b10ff..bf644876b 100644 --- a/SharedLibraryCore/Database/DatabaseContext.cs +++ b/SharedLibraryCore/Database/DatabaseContext.cs @@ -29,7 +29,7 @@ namespace SharedLibraryCore.Database { #if DEBUG == true activeContextCount++; - Console.WriteLine($"Initialized DB Context #{activeContextCount}"); + //Console.WriteLine($"Initialized DB Context #{activeContextCount}"); #endif } @@ -37,7 +37,7 @@ namespace SharedLibraryCore.Database { #if DEBUG == true activeContextCount++; - Console.WriteLine($"Initialized DB Context #{activeContextCount}"); + //Console.WriteLine($"Initialized DB Context #{activeContextCount}"); #endif } @@ -45,7 +45,7 @@ namespace SharedLibraryCore.Database { #if DEBUG == true - Console.WriteLine($"Disposed DB Context #{activeContextCount}"); + //Console.WriteLine($"Disposed DB Context #{activeContextCount}"); activeContextCount--; #endif } diff --git a/SharedLibraryCore/Events/GameEvent.cs b/SharedLibraryCore/Events/GameEvent.cs index a77eb427a..9cafac0a6 100644 --- a/SharedLibraryCore/Events/GameEvent.cs +++ b/SharedLibraryCore/Events/GameEvent.cs @@ -212,11 +212,11 @@ namespace SharedLibraryCore /// asynchronously wait for GameEvent to be processed /// /// waitable task - public Task WaitAsync(int timeOut = int.MaxValue) + public Task WaitAsync(TimeSpan timeSpan, CancellationToken token) { return Task.Run(() => { - OnProcessed.Wait(timeOut); + OnProcessed.Wait(timeSpan, token); return this; }); } diff --git a/SharedLibraryCore/Interfaces/IManager.cs b/SharedLibraryCore/Interfaces/IManager.cs index 0f28d960d..5f2e1a4fb 100644 --- a/SharedLibraryCore/Interfaces/IManager.cs +++ b/SharedLibraryCore/Interfaces/IManager.cs @@ -7,14 +7,16 @@ using SharedLibraryCore.Configuration; using System.Reflection; using SharedLibraryCore.Database.Models; using System.Collections.Concurrent; +using System.Threading; namespace SharedLibraryCore.Interfaces { public interface IManager { Task Init(); - void Start(); + Task Start(); void Stop(); + void Restart(); ILogger GetLogger(long serverId); IList GetServers(); IList GetCommands(); @@ -29,11 +31,6 @@ namespace SharedLibraryCore.Interfaces /// /// EventHandler for the manager IEventHandler GetEventHandler(); - /// - /// Signal to the manager that event(s) needs to be processed - /// - void SetHasEvent(); - bool ShutdownRequested(); IList GetPluginAssemblies(); /// /// provides a page list to add and remove from @@ -47,5 +44,7 @@ namespace SharedLibraryCore.Interfaces string Version { get;} ITokenAuthentication TokenAuthenticator { get; } string ExternalIPAddress { get; } + CancellationToken CancellationToken { get; } + bool IsRestartRequested { get; } } } diff --git a/SharedLibraryCore/Objects/EFClient.cs b/SharedLibraryCore/Objects/EFClient.cs index e1203aca6..562657302 100644 --- a/SharedLibraryCore/Objects/EFClient.cs +++ b/SharedLibraryCore/Objects/EFClient.cs @@ -546,6 +546,7 @@ namespace SharedLibraryCore.Database.Models profileBan = (await CurrentServer.Manager .GetPenaltyService() .GetActivePenaltiesAsync(AliasLinkId)) + .OrderByDescending(_penalty => _penalty.When) .FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.Ban); CurrentServer.Logger.WriteWarning($"Client {this} is GUID banned, but no previous penalty exists for their ban"); @@ -596,18 +597,6 @@ namespace SharedLibraryCore.Database.Models // we want to get any penalties that are tied to their IP or AliasLink (but not necessarily their GUID) var activePenalties = await CurrentServer.Manager.GetPenaltyService().GetActivePenaltiesAsync(AliasLinkId, ipAddress); - #region CLIENT_LINKED_TEMPBAN - var tempBan = activePenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan); - - // they have an active tempban tied to their AliasLink - if (tempBan != null) - { - CurrentServer.Logger.WriteDebug($"Tempbanning {this} because their AliasLink is temporarily banned, but they are not"); - TempBan(tempBan.Offense, DateTime.UtcNow - (tempBan.Expires ?? DateTime.UtcNow), autoKickClient); - return false; - } - #endregion - #region CLIENT_LINKED_BAN var currentBan = activePenalties.FirstOrDefault(p => p.Type == Penalty.PenaltyType.Ban); @@ -642,6 +631,20 @@ namespace SharedLibraryCore.Database.Models } #endregion + #region CLIENT_LINKED_TEMPBAN + var tempBan = activePenalties + .OrderByDescending(_penalty => _penalty.When) + .FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan); + + // they have an active tempban tied to their AliasLink + if (tempBan != null) + { + CurrentServer.Logger.WriteDebug($"Tempbanning {this} because their AliasLink is temporarily banned, but they are not"); + TempBan(tempBan.Offense, DateTime.UtcNow - (tempBan.Expires ?? DateTime.UtcNow), autoKickClient); + return false; + } + #endregion + #region CLIENT_LINKED_FLAG if (Level != Permission.Flagged) { diff --git a/SharedLibraryCore/ScriptPlugin.cs b/SharedLibraryCore/ScriptPlugin.cs index ed5bf3a1b..aecebd39b 100644 --- a/SharedLibraryCore/ScriptPlugin.cs +++ b/SharedLibraryCore/ScriptPlugin.cs @@ -42,7 +42,7 @@ namespace SharedLibraryCore } catch (Exception ex) { - Manager.GetLogger(0).WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Name}"); + Manager.GetLogger(0).WriteError(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"].FormatExt(Name)); Manager.GetLogger(0).WriteDebug(ex.Message); } } diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 3bb26ba58..dfc0f4ca9 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -23,10 +23,10 @@ namespace SharedLibraryCore IW4 = 2, IW5 = 3, IW6 = 4, - T4 = 5, - T5 = 6, - T6 = 7, - T7 = 8 + T4 = 5, + T5 = 6, + T6 = 7, + T7 = 8 } public Server(IManager mgr, ServerConfiguration config) @@ -159,14 +159,16 @@ namespace SharedLibraryCore if (Target.Level == EFClient.Permission.Console) { - Console.ForegroundColor = ConsoleColor.Cyan; + Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(Message.StripColors()); Console.ForegroundColor = ConsoleColor.Gray; } // prevent this from queueing up too many command responses if (CommandResult.Count > 15) + { CommandResult.RemoveAt(0); + } // it was a remote command so we need to add it to the command result queue if (Target.ClientNumber < 0) diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index e0f56ca64..e042ba5a0 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -447,8 +447,6 @@ namespace SharedLibraryCore.Services return new List(); } - identifier = identifier.ToLower(); - using (var context = new DatabaseContext(disableTracking: true)) { long? networkId = null; @@ -472,7 +470,7 @@ namespace SharedLibraryCore.Services // todo maybe not make it start with wildcard? else { - iqLinkIds = iqLinkIds.Where(_alias => EF.Functions.Like(_alias.Name, $"%{identifier}%")); + iqLinkIds = iqLinkIds.Where(_alias => EF.Functions.Like(_alias.Name.ToLower(), $"%{identifier.ToLower()}%")); } var linkIds = await iqLinkIds diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index dfffc0ff5..65b9e30f7 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -30,6 +30,8 @@ namespace SharedLibraryCore #endif public static Encoding EncodingType; public static Localization.Layout CurrentLocalization = new Localization.Layout(new Dictionary()); + public static TimeSpan DefaultCommandTimeout = new TimeSpan(0, 0, 10); + public static EFClient IW4MAdminClient(Server server = null) { return new EFClient() @@ -320,7 +322,7 @@ namespace SharedLibraryCore if (Elapsed.TotalSeconds < 30) { - return CurrentLocalization.LocalizationIndex["GLOBAL_TIME_JUSTNOW"]; + return CurrentLocalization.LocalizationIndex["GLOBAL_TIME_JUSTNOW"] + ago; } if (Elapsed.TotalMinutes < 120) { @@ -783,6 +785,11 @@ namespace SharedLibraryCore } } + /// + /// Determines if the given message is a quick message + /// + /// + /// true if the public static bool IsQuickMessage(this string message) { return Regex.IsMatch(message, @"^\u0014(?:[A-Z]|_)+$"); diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index e85dacd0b..ca8b5c85a 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -53,7 +53,7 @@ namespace WebfrontCore.Controllers Manager.GetEventHandler().AddEvent(remoteEvent); List response; // wait for the event to process - if (!(await remoteEvent.WaitAsync(60 * 1000)).Failed) + if (!(await remoteEvent.WaitAsync(Utilities.DefaultCommandTimeout, server.Manager.CancellationToken)).Failed) { response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList(); diff --git a/WebfrontCore/Program.cs b/WebfrontCore/Program.cs index cb093725c..8b5fd970a 100644 --- a/WebfrontCore/Program.cs +++ b/WebfrontCore/Program.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using SharedLibraryCore.Interfaces; @@ -9,18 +11,18 @@ namespace WebfrontCore { public static IManager Manager; - static void Main(string[] args) + static void Main() { throw new System.Exception("Webfront core cannot be run as a standalone application"); } - public static void Init(IManager mgr) + public static Task Init(IManager mgr, CancellationToken cancellationToken) { Manager = mgr; - BuildWebHost().Run(); + return BuildWebHost().RunAsync(cancellationToken); } - public static IWebHost BuildWebHost() + private static IWebHost BuildWebHost() { var config = new ConfigurationBuilder() .AddEnvironmentVariables()