diff --git a/Application/ConfigurationGenerator.cs b/Application/ConfigurationGenerator.cs index 04fb5e2ab..5886c4fea 100644 --- a/Application/ConfigurationGenerator.cs +++ b/Application/ConfigurationGenerator.cs @@ -51,6 +51,8 @@ namespace IW4MAdmin.Application newConfig.AutoMessages = new List(); newConfig.Rules = new List(); + newConfig.UseT6MParser = Utilities.PromptBool("Use T6M parser"); + configList.Add(newConfig); Console.Write("Configuration saved, add another? [y/n]:"); diff --git a/Application/RconParsers/IW4Parser.cs b/Application/RconParsers/IW4Parser.cs new file mode 100644 index 000000000..1234d941c --- /dev/null +++ b/Application/RconParsers/IW4Parser.cs @@ -0,0 +1,103 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Text; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Objects; +using SharedLibraryCore; +using SharedLibraryCore.RCon; +using SharedLibraryCore.Exceptions; + +namespace Application.RconParsers +{ + class IW4Parser : IRConParser + { + public async Task ExecuteCommandAsync(Connection connection, string command) + { + return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command)).Skip(1).ToArray(); + } + + public async Task> GetDvarAsync(Connection connection, string dvarName) + { + string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, dvarName); + + if (LineSplit.Length != 3) + { + var e = new DvarException($"DVAR \"{dvarName}\" does not exist"); + e.Data["dvar_name"] = dvarName; + throw e; + } + + string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }, StringSplitOptions.RemoveEmptyEntries); + + if (ValueSplit.Length != 5) + { + var e = new DvarException($"DVAR \"{dvarName}\" does not exist"); + e.Data["dvar_name"] = dvarName; + throw e; + } + + string DvarName = Regex.Replace(ValueSplit[0], @"\^[0-9]", ""); + string DvarCurrentValue = Regex.Replace(ValueSplit[2], @"\^[0-9]", ""); + string DvarDefaultValue = Regex.Replace(ValueSplit[4], @"\^[0-9]", ""); + + return new Dvar(DvarName) + { + Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) + }; + } + + public async Task> GetStatusAsync(Connection connection) + { + string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status"); + return ClientsFromStatus(response); + } + + public async Task SetDvarAsync(Connection connection, string dvarName, object dvarValue) + { + return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0; + } + + private List ClientsFromStatus(string[] Status) + { + List StatusPlayers = new List(); + + foreach (String S in Status) + { + String responseLine = S.Trim(); + + if (Regex.Matches(responseLine, @"\d+$", RegexOptions.IgnoreCase).Count > 0 && responseLine.Length > 72) // its a client line! + { + String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + int cID = -1; + int Ping = -1; + Int32.TryParse(playerInfo[2], out Ping); + String cName = Encoding.UTF8.GetString(Encoding.Convert(Encoding.UTF7, Encoding.UTF8, Encoding.UTF7.GetBytes(responseLine.Substring(46, 18).StripColors().Trim()))); + long npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}", RegexOptions.IgnoreCase).Value.ConvertLong(); + int.TryParse(playerInfo[0], out cID); + var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}"); +#if DEBUG + Ping = 1; +#endif + int cIP = regex.Value.Split(':')[0].ConvertToIP(); + regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+"); + int score = Int32.Parse(regex.Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]); + Player P = new Player() + { + Name = cName, + NetworkId = npID, + ClientNumber = cID, + IPAddress = cIP, + Ping = Ping, + Score = score + }; + StatusPlayers.Add(P); + } + } + + return StatusPlayers; + } + } +} diff --git a/Application/RconParsers/T6MParser.cs b/Application/RconParsers/T6MParser.cs new file mode 100644 index 000000000..e696454e0 --- /dev/null +++ b/Application/RconParsers/T6MParser.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Objects; +using SharedLibraryCore.RCon; +using SharedLibraryCore.Exceptions; +using System.Text; + +namespace Application.RconParsers +{ + public class T6MParser : IRConParser + { + public async Task ExecuteCommandAsync(Connection connection, string command) + { + await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, false); + return new string[] { "Command Executed" }; + } + + public async Task> GetDvarAsync(Connection connection, string dvarName) + { + string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"get {dvarName}"); + + + if (LineSplit.Length < 2) + { + var e = new DvarException($"DVAR \"{dvarName}\" does not exist"); + e.Data["dvar_name"] = dvarName; + throw e; + } + + string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }); + + if (ValueSplit.Length == 0) + { + var e = new DvarException($"DVAR \"{dvarName}\" does not exist"); + e.Data["dvar_name"] = dvarName; + throw e; + } + + string DvarName = dvarName; + string DvarCurrentValue = Regex.Replace(ValueSplit[1], @"\^[0-9]", ""); + + return new Dvar(DvarName) + { + Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) + }; + } + + public async Task> GetStatusAsync(Connection connection) + { + string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status"); + return ClientsFromStatus(response); + } + + public async Task SetDvarAsync(Connection connection, string dvarName, object dvarValue) + { + // T6M doesn't respond with anything when a value is set, so we can only hope for the best :c + await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, $"set {dvarName} {dvarValue}", false); + return true; + } + + private List ClientsFromStatus(string[] status) + { + List StatusPlayers = new List(); + + foreach (string statusLine in status) + { + String responseLine = statusLine.Trim(); + + if (Regex.Matches(responseLine, @"\d+$", RegexOptions.IgnoreCase).Count > 0 && responseLine.Length > 72) // its a client line! + { + String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + int clientId = -1; + int Ping = -1; + + Int32.TryParse(playerInfo[3], out Ping); + string name = Encoding.UTF8.GetString(Encoding.Convert(Encoding.UTF7, Encoding.UTF8, Encoding.UTF7.GetBytes(responseLine.Substring(50, 15).StripColors().Trim()))); + long networkId = playerInfo[4].ConvertLong(); + int.TryParse(playerInfo[0], out clientId); + var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}"); +#if DEBUG + Ping = 1; +#endif + int ipAddress = regex.Value.Split(':')[0].ConvertToIP(); + regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+"); + int score = Int32.Parse(playerInfo[1]); + + StatusPlayers.Add(new Player() + { + Name = name, + NetworkId = networkId, + ClientNumber = clientId, + IPAddress = ipAddress, + Ping = Ping, + Score = score + }); + } + } + + return StatusPlayers; + } + } +} diff --git a/Application/Server.cs b/Application/Server.cs index eaf49d8df..71f211d29 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -14,6 +14,7 @@ using SharedLibraryCore.Dtos; using SharedLibraryCore.Configuration; using IW4MAdmin.Application.Misc; +using Application.RconParsers; namespace IW4MAdmin { @@ -589,6 +590,8 @@ namespace IW4MAdmin public async Task Initialize() { + RconParser = ServerConfig.UseT6MParser ? (IRConParser)new T6MParser() : new IW4Parser(); + var version = await this.GetDvarAsync("version"); GameName = Utilities.GetGame(version.Value); @@ -608,7 +611,7 @@ namespace IW4MAdmin var logfile = await this.GetDvarAsync("g_log"); var logsync = await this.GetDvarAsync("g_logsync"); - DVAR onelog = null; + Dvar onelog = null; if (GameName == Game.IW4) { try @@ -618,7 +621,7 @@ namespace IW4MAdmin catch (Exception) { - onelog = new DVAR("iw4x_onelog") + onelog = new Dvar("iw4x_onelog") { Value = -1 }; @@ -682,7 +685,7 @@ namespace IW4MAdmin 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("IW4M Admin is now ^2ONLINE"); #endif diff --git a/Plugins/Stats/Cheat/Detection.cs b/Plugins/Stats/Cheat/Detection.cs index dbac0820d..b0d50a0c8 100644 --- a/Plugins/Stats/Cheat/Detection.cs +++ b/Plugins/Stats/Cheat/Detection.cs @@ -67,6 +67,13 @@ namespace IW4MAdmin.Plugins.Stats.Cheat float previousAverage = hitLoc.HitOffsetAverage; double newAverage = (previousAverage * (hitLoc.HitCount - 1) + angle) / hitLoc.HitCount; hitLoc.HitOffsetAverage = (float)newAverage; + + if (hitLoc.HitOffsetAverage == float.NaN) + { + Log.WriteWarning("[Detection::ProcessKill] HitOffsetAvgerage NaN"); + Log.WriteDebug($"{previousAverage}-{hitLoc.HitCount}-{hitLoc}-{newAverage}"); + hitLoc.HitOffsetAverage = 0f; + } } #endregion diff --git a/Plugins/Stats/Commands/ResetStats.cs b/Plugins/Stats/Commands/ResetStats.cs index 943bcd463..2fc775991 100644 --- a/Plugins/Stats/Commands/ResetStats.cs +++ b/Plugins/Stats/Commands/ResetStats.cs @@ -23,8 +23,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands stats.Deaths = 0; stats.Kills = 0; - stats.SPM = 0; - stats.Skill = 0; + stats.SPM = 0.0; + stats.Skill = 0.0; // reset the cached version Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode()); diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index f32b968c3..6060a0007 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -355,6 +355,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers if (streakMessage != string.Empty) await attacker.Tell(streakMessage); + // fixme: why? + if (victimStats.SPM == double.NaN || victimStats.Skill == double.NaN) + { + victimStats.SPM = 0.0; + victimStats.Skill = 0.0; + } + // todo: do we want to save this immediately? var statsSvc = ContextThreads[serverId]; statsSvc.ClientStatSvc.Update(attackerStats); @@ -417,10 +424,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // calculate how much the KDR should weigh // 1.637 is a Eddie-Generated number that weights the KDR nicely - double KDRWeight = Math.Round(Math.Pow(clientStats.KDR, 1.637 / Math.E), 3); + double kdr = clientStats.Deaths == 0 ? clientStats.Kills : clientStats.KDR; + double KDRWeight = Math.Round(Math.Pow(kdr, 1.637 / Math.E), 3); // if no SPM, weight is 1 else the weight ishe current session's spm / lifetime average score per minute - double SPMWeightAgainstAverage = (clientStats.SPM < 1) ? 1 : killSPM / clientStats.SPM; + //double SPMWeightAgainstAverage = (clientStats.SPM < 1) ? 1 : killSPM / clientStats.SPM; // calculate the weight of the new play time against last 10 hours of gameplay int totalPlayTime = (clientStats.TimePlayed == 0) ? @@ -431,12 +439,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // calculate the new weight against average times the weight against play time clientStats.SPM = (killSPM * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight)); - // fixme: how does this happen? - if (clientStats.SPM == double.NaN) - clientStats.SPM = 0; + clientStats.SPM = Math.Round(clientStats.SPM, 3); + clientStats.Skill = Math.Round((clientStats.SPM * KDRWeight), 3); + // fixme: how does this happen? + if (clientStats.SPM == double.NaN || clientStats.Skill == double.NaN) + { + Log.WriteWarning("[StatManager::UpdateStats] clientStats SPM/Skill NaN"); + Log.WriteDebug($"{killSPM}-{KDRWeight}-{totalPlayTime}-{SPMAgainstPlayWeight}-{clientStats.SPM}-{clientStats.Skill}-{scoreDifference}"); + clientStats.SPM = 0; + clientStats.Skill = 0; + } + clientStats.LastStatCalculation = DateTime.UtcNow; clientStats.LastScore = clientStats.SessionScore; diff --git a/SharedLibraryCore/Configuration/ServerConfiguration.cs b/SharedLibraryCore/Configuration/ServerConfiguration.cs index 811101232..b73b95960 100644 --- a/SharedLibraryCore/Configuration/ServerConfiguration.cs +++ b/SharedLibraryCore/Configuration/ServerConfiguration.cs @@ -1,13 +1,23 @@ -using System.Collections.Generic; +using SharedLibraryCore.Interfaces; +using System.Collections.Generic; namespace SharedLibraryCore.Configuration { - public class ServerConfiguration + public class ServerConfiguration : IBaseConfiguration { public string IPAddress { get; set; } public short Port { get; set; } public string Password { get; set; } public List Rules { get; set; } public List AutoMessages { get; set; } + public bool UseT6MParser { get; set; } + + public IBaseConfiguration Generate() + { + UseT6MParser = Utilities.PromptBool("Use T6M parser"); + return this; + } + + public string Name() => "ServerConfiguration"; } } diff --git a/SharedLibraryCore/Dvar.cs b/SharedLibraryCore/Dvar.cs index 238debbaf..8c8929c87 100644 --- a/SharedLibraryCore/Dvar.cs +++ b/SharedLibraryCore/Dvar.cs @@ -1,11 +1,11 @@ namespace SharedLibraryCore { - public class DVAR + public class Dvar { public string Name { get; private set; } public T Value; - public DVAR(string name) + public Dvar(string name) { Name = name; } diff --git a/SharedLibraryCore/Interfaces/IRConParser.cs b/SharedLibraryCore/Interfaces/IRConParser.cs new file mode 100644 index 000000000..ce308f45b --- /dev/null +++ b/SharedLibraryCore/Interfaces/IRConParser.cs @@ -0,0 +1,16 @@ +using SharedLibraryCore.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace SharedLibraryCore.Interfaces +{ + public interface IRConParser + { + Task> GetDvarAsync(RCon.Connection connection, string dvarName); + Task SetDvarAsync(RCon.Connection connection, string dvarName, object dvarValue); + Task ExecuteCommandAsync(RCon.Connection connection, string command); + Task> GetStatusAsync(RCon.Connection connection); + } +} diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index 946d1fd19..e0f44fe9d 100644 --- a/SharedLibraryCore/RCon/Connection.cs +++ b/SharedLibraryCore/RCon/Connection.cs @@ -159,7 +159,7 @@ namespace SharedLibraryCore.RCon } } - public async Task SendQueryAsync(StaticHelpers.QueryType type, string parameters = "") + public async Task SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", bool waitForResponse = true) { // will this really prevent flooding? if ((DateTime.Now - LastQuery).TotalMilliseconds < 35) @@ -222,6 +222,9 @@ namespace SharedLibraryCore.RCon throw new NetworkException($"Unexpected error while sending data to server - {e.Message}"); } + if (!waitForResponse) + return await Task.FromResult(new string[] { "" }); + var connectionState = new ConnectionState(ServerConnection); retryReceive: @@ -233,11 +236,6 @@ namespace SharedLibraryCore.RCon if (!success) { - // t6m doesn't respond to set requests - if (type == StaticHelpers.QueryType.DVAR && parameters.Contains("set ")) - { - return await Task.FromResult(new string[] { "" }); - } FailedReceives++; #if DEBUG diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 1ca2dfa7a..07f8f5992 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -8,6 +8,7 @@ using SharedLibraryCore.Helpers; using SharedLibraryCore.Objects; using SharedLibraryCore.Dtos; using SharedLibraryCore.Configuration; +using SharedLibraryCore.Interfaces; namespace SharedLibraryCore { @@ -25,7 +26,7 @@ namespace SharedLibraryCore T6M, } - public Server(Interfaces.IManager mgr, ServerConfiguration config) + public Server(IManager mgr, ServerConfiguration config) { Password = config.Password; IP = config.IPAddress; @@ -313,6 +314,7 @@ namespace SharedLibraryCore public bool CustomCallback { get; protected set; } public string WorkingDirectory { get; protected set; } public RCon.Connection RemoteConnection { get; protected set; } + public IRConParser RconParser { get; protected set; } // Internal protected string IP; diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index dd047934d..25bf5dffd 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -52,54 +52,6 @@ namespace SharedLibraryCore return newStr; } - public static List PlayersFromStatus(this Server sv, string[] Status) - { - List StatusPlayers = new List(); - - foreach (String S in Status) - { - String responseLine = S.Trim(); - - if (Regex.Matches(responseLine, @"\d+$", RegexOptions.IgnoreCase).Count > 0 && responseLine.Length > 72) // its a client line! - { - String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - int cID = -1; - int Ping = -1; - - try - { - Ping = (sv.GameName != Game.T6M) ? - Int32.Parse(playerInfo[2]) : - Int32.Parse(playerInfo[3]); - } - - catch (FormatException) { } - String cName = (sv.GameName != Game.T6M) ? - Encoding.UTF8.GetString(Encoding.Convert(Encoding.UTF7, Encoding.UTF8, Encoding.UTF7.GetBytes(StripColors(responseLine.Substring(46, 18)).Trim()))) : - Encoding.UTF8.GetString(Encoding.Convert(Encoding.UTF7, Encoding.UTF8, Encoding.UTF7.GetBytes(StripColors(responseLine.Substring(50, 15)).Trim()))); - long npID = sv.GameName != Game.T6M ? - Regex.Match(responseLine, @"([a-z]|[0-9]){16}", RegexOptions.IgnoreCase).Value.ConvertLong() : - playerInfo[4].ConvertLong(); - - int.TryParse(playerInfo[0], out cID); - var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}"); -#if DEBUG - Ping = 1; -#endif - - int cIP = regex.Value.Split(':')[0].ConvertToIP(); - regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+"); - int score = (sv.GameName != Game.T6M) ? - Int32.Parse(regex.Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]) : - Int32.Parse(playerInfo[1]); - Player P = new Player() { Name = cName, NetworkId = npID, ClientNumber = cID, IPAddress = cIP, Ping = Ping, Score = score }; - StatusPlayers.Add(P); - } - } - - return StatusPlayers; - } - public static Player.Permission MatchPermission(String str) { String lookingFor = str.ToLower(); @@ -393,115 +345,23 @@ namespace SharedLibraryCore public static string PromptString(string question) { - Console.Write($"{question}: "); - string response; do { + Console.Write($"{question}: "); response = Console.ReadLine(); } while (string.IsNullOrWhiteSpace(response)); return response; } - public static async Task> GetDvarAsync(this Server server, string dvarName) - { - string[] LineSplit = null; - bool t6m = false; - if (server.GameName == Game.UKN) - { - LineSplit = await server.RemoteConnection.SendQueryAsync(QueryType.COMMAND, $"get {dvarName}"); - if (LineSplit.Where(l => l.Contains("Unknown command")).Count() > 0) - { - LineSplit = await server.RemoteConnection.SendQueryAsync(QueryType.DVAR, dvarName); - } + public static async Task> GetDvarAsync(this Server server, string dvarName) => await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName); - else - t6m = true; - } + public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue) => await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue); - else if (server.GameName == Game.T6M) - { - LineSplit = await server.RemoteConnection.SendQueryAsync(QueryType.COMMAND, $"get {dvarName}"); - } + public static async Task ExecuteCommandAsync(this Server server, string commandName) => await server.RconParser.ExecuteCommandAsync(server.RemoteConnection, commandName); - else - { - LineSplit = await server.RemoteConnection.SendQueryAsync(QueryType.DVAR, dvarName); ; - } + public static async Task> GetStatusAsync(this Server server) => await server.RconParser.GetStatusAsync(server.RemoteConnection); - if (server.GameName != Game.T6M && !t6m) - { - if (LineSplit.Length < 3) - { - var e = new Exceptions.DvarException($"DVAR \"{dvarName}\" does not exist"); - e.Data["dvar_name"] = dvarName; - throw e; - } - - string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }, StringSplitOptions.RemoveEmptyEntries); - - if (ValueSplit.Length != 5) - { - var e = new Exceptions.DvarException($"DVAR \"{dvarName}\" does not exist"); - e.Data["dvar_name"] = dvarName; - throw e; - } - - string DvarName = Regex.Replace(ValueSplit[0], @"\^[0-9]", ""); - string DvarCurrentValue = Regex.Replace(ValueSplit[2], @"\^[0-9]", ""); - string DvarDefaultValue = Regex.Replace(ValueSplit[4], @"\^[0-9]", ""); - - return new DVAR(DvarName) { Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) }; - } - - else - { - if (LineSplit.Length < 2) - { - var e = new Exceptions.DvarException($"DVAR \"{dvarName}\" does not exist"); - e.Data["dvar_name"] = dvarName; - throw e; - } - - string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }); - - if (ValueSplit.Length == 0) - { - var e = new Exceptions.DvarException($"DVAR \"{dvarName}\" does not exist"); - e.Data["dvar_name"] = dvarName; - throw e; - } - - string DvarName = dvarName; - string DvarCurrentValue = Regex.Replace(ValueSplit[1], @"\^[0-9]", ""); - - return new DVAR(DvarName) { Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) }; - } - - - } - - public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue) - { - await server.RemoteConnection.SendQueryAsync(QueryType.DVAR, $"set {dvarName} {dvarValue}"); - } - - public static async Task ExecuteCommandAsync(this Server server, string commandName) - { - return (await server.RemoteConnection.SendQueryAsync(QueryType.COMMAND, commandName)).Skip(1).ToArray(); - } - - public static async Task> GetStatusAsync(this Server server) - { -#if DEBUG && DEBUG_PLAYERS - string[] response = await Task.Run(() => System.IO.File.ReadAllLines("players.txt")); -#else - string[] response = await server.RemoteConnection.SendQueryAsync(QueryType.DVAR, "status"); -#endif - return server.PlayersFromStatus(response); - } - - public static bool IsRunningOnMono() => Type.GetType("Mono.Runtime") != null; } } diff --git a/WebfrontCore/Views/Shared/_Layout.cshtml b/WebfrontCore/Views/Shared/_Layout.cshtml index 0cd1f3b2d..d48da7b31 100644 --- a/WebfrontCore/Views/Shared/_Layout.cshtml +++ b/WebfrontCore/Views/Shared/_Layout.cshtml @@ -23,7 +23,7 @@
-