diff --git a/Application/Localization/IW4MAdmin.en-EN.json b/Application/Localization/IW4MAdmin.en-EN.json index 84dd43c9b..e9145bcd5 100644 --- a/Application/Localization/IW4MAdmin.en-EN.json +++ b/Application/Localization/IW4MAdmin.en-EN.json @@ -192,6 +192,9 @@ "GLOBAL_ERROR": "Error", "GLOBAL_WARNING": "Warning", "GLOBAL_INFO": "Info", - "GLOBAL_VERBOSE": "Verbose" + "GLOBAL_VERBOSE": "Verbose", + + "MANAGER_CONSOLE_NOSERV": "No servers are currently being monitored", + "SERVER_PLUGIN_ERROR": "A plugin generated an error" } } \ No newline at end of file diff --git a/Application/Localization/IW4MAdmin.ru-RU.json b/Application/Localization/IW4MAdmin.ru-RU.json index da10739e1..60d9430a5 100644 --- a/Application/Localization/IW4MAdmin.ru-RU.json +++ b/Application/Localization/IW4MAdmin.ru-RU.json @@ -40,7 +40,7 @@ "SERVER_WARNLIMT_REACHED": "Слишком много предупреждений", "SERVER_WARNING": "Предупреждение", "SERVER_WEBSITE_GENERIC": "веб-сайт этого сервера", - "BROADCAST_ONLINE": "^5IW4MADMIN ^7сейчас ^В СЕТИ", + "BROADCAST_ONLINE": "^5IW4MADMIN ^7сейчас СЕТИ", "BROADCAST_OFFLINE": "IW4MAdmin отключается", "COMMAND_HELP_SYNTAX": "синтаксис:", "COMMAND_HELP_OPTIONAL": "опционально", diff --git a/Application/Misc/VPNCheck.cs b/Application/Misc/VPNCheck.cs index d21a19c22..b01b82a1c 100644 --- a/Application/Misc/VPNCheck.cs +++ b/Application/Misc/VPNCheck.cs @@ -23,6 +23,11 @@ namespace Application.Misc string response = await RequestClient.GetStringAsync($"http://v2.api.iphub.info/ip/{ip}"); var responseJson = JsonConvert.DeserializeObject(response); int blockType = Convert.ToInt32(responseJson["block"]); + if (responseJson.ContainsKey("isp")) + { + if (responseJson["isp"].ToString() == "TSF-IP-CORE") + return true; + } return blockType == 1; } } diff --git a/Application/RconParsers/IW4RConParser.cs b/Application/RconParsers/IW4RConParser.cs index d2ea8d63e..b0a6c2cae 100644 --- a/Application/RconParsers/IW4RConParser.cs +++ b/Application/RconParsers/IW4RConParser.cs @@ -22,7 +22,9 @@ namespace Application.RconParsers Ban = "clientkick {0} \"{1}\"", TempBan = "tempbanclient {0} \"{1}\"" }; - + + private static string StatusRegex = @"^( *[0-9]+) +([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|bot[0-9]+) +(.{0,20}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:\d{1,5}|0+.0+:\d{1,5}) +(-*[0-9]+) +([0-9]+) *$"; + public async Task ExecuteCommandAsync(Connection connection, string command) { return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command)).Skip(1).ToArray(); @@ -82,30 +84,33 @@ namespace Application.RconParsers { String responseLine = S.Trim(); - if (Regex.Matches(responseLine, @" *^\d+", RegexOptions.IgnoreCase).Count > 0) + var regex = Regex.Match(responseLine, StatusRegex, RegexOptions.IgnoreCase); + if (regex.Success) { - 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); - String cName = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(responseLine.Substring(46, 18).StripColors().Trim()))); - long npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}|bot[0-9]+", RegexOptions.IgnoreCase).Value.ConvertLong(); - int.TryParse(playerInfo[0], out cID); - var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}"); - 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]); + int clientNumber = int.Parse(regex.Groups[1].Value); + int score = int.Parse(regex.Groups[2].Value); + + int ping = 999; + + // their state can be CNCT, ZMBI etc + if (regex.Groups[3].Value.Length <= 3) + { + ping = int.Parse(regex.Groups[3].Value); + } + + long networkId = regex.Groups[4].Value.ConvertLong(); + string name = regex.Groups[5].Value.StripColors().Trim(); + int ip = regex.Groups[7].Value.Split(':')[0].ConvertToIP(); + Player P = new Player() { - Name = cName, - NetworkId = npID, - ClientNumber = cID, - IPAddress = cIP, - Ping = Ping, + Name = name, + NetworkId = networkId, + ClientNumber = clientNumber, + IPAddress = ip, + Ping = ping, Score = score, - IsBot = cIP == 0 + IsBot = ip == 0 }; StatusPlayers.Add(P); diff --git a/Application/Server.cs b/Application/Server.cs index 9abbd4542..5cbe9ec1b 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -178,6 +178,9 @@ namespace IW4MAdmin if (player.Level != Player.Permission.Banned && currentBan.Type == Penalty.PenaltyType.Ban) await player.Ban($"{loc["SERVER_BAN_PREV"]} {currentBan.Offense}", autoKickClient); + + // they didn't fully connect so empty their slot + Players[player.ClientNumber] = null; return true; } @@ -186,7 +189,7 @@ namespace IW4MAdmin var e = new GameEvent(GameEvent.EventType.Connect, "", player, null, this); Manager.GetEventHandler().AddEvent(e); - // e.OnProcessed.Wait(); + e.OnProcessed.Wait(); if (!Manager.GetApplicationSettings().Configuration().EnableClientVPNs && await VPNCheck.UsingVPN(player.IPAddressString, Manager.GetApplicationSettings().Configuration().IPHubAPIKey)) @@ -362,14 +365,15 @@ namespace IW4MAdmin await ProcessEvent(E); Manager.GetEventApi().OnServerEvent(this, E); - foreach (IPlugin P in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) + // this allows us to catch exceptions but still run it parallel + async Task pluginHandlingAsync(Task onEvent, string pluginName) { try { if (cts.IsCancellationRequested) - break; + return; - await P.OnEventAsync(E, this); + await onEvent; } // this happens if a plugin (login) wants to stop commands from executing @@ -381,7 +385,7 @@ namespace IW4MAdmin catch (Exception Except) { - Logger.WriteError(String.Format("The plugin \"{0}\" generated an error. ( see log )", P.Name)); + Logger.WriteError($"{loc["SERVER_PLUGIN_ERROR"]} [{pluginName}]"); Logger.WriteDebug(String.Format("Error Message: {0}", Except.Message)); Logger.WriteDebug(String.Format("Error Trace: {0}", Except.StackTrace)); while (Except.InnerException != null) @@ -389,11 +393,15 @@ namespace IW4MAdmin Except = Except.InnerException; Logger.WriteDebug($"Inner exception: {Except.Message}"); } - continue; } - } + var pluginTasks = SharedLibraryCore.Plugins.PluginImporter.ActivePlugins. + Select(p => pluginHandlingAsync(p.OnEventAsync(E, this), p.Name)); + + // execute all the plugin updates simultaneously + await Task.WhenAll(pluginTasks); + // hack: this prevents commands from getting executing that 'shouldn't' be if (E.Type == GameEvent.EventType.Command && E.Extra != null && @@ -402,7 +410,6 @@ namespace IW4MAdmin { await (((Command)E.Extra).ExecuteAsync(E)); } - } /// @@ -599,27 +606,35 @@ namespace IW4MAdmin override public async Task ProcessUpdatesAsync(CancellationToken cts) { + // this isn't really used anymore this.cts = cts; + try { + if (Manager.ShutdownRequested()) + { + foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) + await plugin.OnUnloadAsync(); + + for (int i = 0; i < Players.Count; i++) + await RemovePlayer(i); + } + + // only check every 2 minutes if the server doesn't seem to be responding if ((DateTime.Now - LastPoll).TotalMinutes < 2 && ConnectionErrors >= 1) return true; try { - // trying to reduce the polling rate as every 450ms is unnecessary - if ((DateTime.Now - LastPoll).TotalSeconds >= 10) - { - int polledPlayerCount = await PollPlayersAsync(); + int polledPlayerCount = await PollPlayersAsync(); - if (ConnectionErrors > 0) - { - Logger.WriteVerbose($"{loc["MANAGER_CONNECTION_REST"]} {IP}:{Port}"); - Throttled = false; - } - ConnectionErrors = 0; - LastPoll = DateTime.Now; + if (ConnectionErrors > 0) + { + Logger.WriteVerbose($"{loc["MANAGER_CONNECTION_REST"]} {IP}:{Port}"); + Throttled = false; } + ConnectionErrors = 0; + LastPoll = DateTime.Now; } catch (NetworkException e) @@ -637,6 +652,8 @@ namespace IW4MAdmin LastMessage = DateTime.Now - start; lastCount = DateTime.Now; + // todo: re-enable on tick + /* if ((DateTime.Now - tickTime).TotalMilliseconds >= 1000) { foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) @@ -647,8 +664,9 @@ namespace IW4MAdmin await Plugin.OnTickAsync(this); } tickTime = DateTime.Now; - } + }*/ + // update the player history if ((lastCount - playerCountStart).TotalMinutes >= SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) { while (PlayerHistory.Count > ((60 / SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours @@ -657,6 +675,7 @@ namespace IW4MAdmin playerCountStart = DateTime.Now; } + // send out broadcast messages if (LastMessage.TotalSeconds > Manager.GetApplicationSettings().Configuration().AutoMessagePeriod && BroadcastMessages.Count > 0 && ClientNum > 0) @@ -666,14 +685,6 @@ namespace IW4MAdmin start = DateTime.Now; } - if (Manager.ShutdownRequested()) - { - foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) - await plugin.OnUnloadAsync(); - - for (int i = 0; i < Players.Count; i++) - await RemovePlayer(i); - } return true; } diff --git a/Plugins/ProfanityDeterment/Plugin.cs b/Plugins/ProfanityDeterment/Plugin.cs index 03fbccae1..b857226b3 100644 --- a/Plugins/ProfanityDeterment/Plugin.cs +++ b/Plugins/ProfanityDeterment/Plugin.cs @@ -33,6 +33,16 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment S.Logger.WriteWarning("Could not add client to profanity tracking"); } + var objectionalWords = Settings.Configuration().OffensiveWords; + bool containsObjectionalWord = objectionalWords.FirstOrDefault(w => E.Origin.Name.ToLower().Contains(w)) != null; + + if (containsObjectionalWord) + { + await E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player() + { + ClientId = 1 + }); + }; } if (E.Type == GameEvent.EventType.Disconnect) diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index da026fc3b..dcd1a6150 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -467,6 +467,14 @@ 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)); + + if (clientStats.SPM < 0) + { + Log.WriteWarning("[StatManager:UpdateStats] clientStats SPM < 0"); + Log.WriteDebug($"{scoreDifference}-{clientStats.RoundScore} - {clientStats.LastScore} - {clientStats.SessionScore}"); + clientStats.SPM = 0; + } + clientStats.SPM = Math.Round(clientStats.SPM, 3); clientStats.Skill = Math.Round((clientStats.SPM * KDRWeight), 3); diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index 036d96975..8014a2ec6 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -37,6 +37,7 @@ namespace WebfrontCore.Controllers CurrentServer = server, Name = Client.Name }; + var remoteEvent = new GameEvent() { Type = GameEvent.EventType.Say,