From 4caa4655e22a33a66d9095e4a270e19a69eaa538 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Fri, 13 Apr 2018 01:32:30 -0500 Subject: [PATCH] abstracting rcon parsing and event parsing changed Event to GameEvent finally fixed the stats NaN check ip for bans consolidated console, profile, and logout into dropdown make sure game is iw4 before using :^ in say fix pm not showing from name if in web console show time left of temban on profile --- Application/API/EventAPI.cs | 6 +- Application/EventParsers/IW4EventParser.cs | 108 ++++++++++++++++++ Application/EventParsers/T6MEventParser.cs | 92 +++++++++++++++ Application/Main.cs | 6 +- Application/Manager.cs | 2 +- .../{IW4Parser.cs => IW4RConParser.cs} | 15 ++- .../{T6MParser.cs => T6MRConParser.cs} | 22 +++- Application/Server.cs | 100 ++++++++-------- Plugins/ProfanityDeterment/Plugin.cs | 8 +- Plugins/Stats/Cheat/Detection.cs | 6 +- Plugins/Stats/Commands/ResetStats.cs | 2 +- Plugins/Stats/Commands/TopStats.cs | 2 +- Plugins/Stats/Commands/ViewStats.cs | 2 +- Plugins/Stats/Helpers/StatManager.cs | 25 ++-- Plugins/Stats/Plugin.cs | 38 +++--- Plugins/Welcome/Plugin.cs | 12 +- SharedLibraryCore/Command.cs | 2 +- SharedLibraryCore/Commands/NativeCommands.cs | 78 ++++++------- SharedLibraryCore/Event.cs | 59 +--------- SharedLibraryCore/Interfaces/IEventApi.cs | 2 +- SharedLibraryCore/Interfaces/IEventParser.cs | 11 ++ SharedLibraryCore/Interfaces/IPlugin.cs | 2 +- SharedLibraryCore/Interfaces/IRConParser.cs | 16 +-- SharedLibraryCore/RCon/CommandPrefix.cs | 17 +++ SharedLibraryCore/Server.cs | 27 ++--- SharedLibraryCore/Services/PenaltyService.cs | 39 +++++-- SharedLibraryCore/Utilities.cs | 11 ++ WebfrontCore/Controllers/BaseController.cs | 2 - WebfrontCore/Controllers/ConsoleController.cs | 12 +- .../PenaltyListViewComponent.cs | 24 +--- WebfrontCore/Views/Shared/_Layout.cshtml | 29 ++++- .../wwwroot/css/bootstrap-custom.scss | 7 +- WebfrontCore/wwwroot/js/penalty.js | 2 +- WebfrontCore/wwwroot/js/profile.js | 5 +- 34 files changed, 519 insertions(+), 272 deletions(-) create mode 100644 Application/EventParsers/IW4EventParser.cs create mode 100644 Application/EventParsers/T6MEventParser.cs rename Application/RconParsers/{IW4Parser.cs => IW4RConParser.cs} (88%) rename Application/RconParsers/{T6MParser.cs => T6MRConParser.cs} (82%) create mode 100644 SharedLibraryCore/Interfaces/IEventParser.cs create mode 100644 SharedLibraryCore/RCon/CommandPrefix.cs diff --git a/Application/API/EventAPI.cs b/Application/API/EventAPI.cs index 61fbc6747..34c43fe6c 100644 --- a/Application/API/EventAPI.cs +++ b/Application/API/EventAPI.cs @@ -28,9 +28,9 @@ namespace IW4MAdmin.Application.API public Queue GetEvents() => Events; - public void OnServerEvent(object sender, Event E) + public void OnServerEvent(object sender, GameEvent E) { - if (E.Type == Event.GType.Say && E.Origin.Level < Player.Permission.Trusted) + if (E.Type == GameEvent.EventType.Say && E.Origin.Level < Player.Permission.Trusted) { bool flaggedMessage = false; foreach (string msg in FlaggedMessageContains) @@ -62,7 +62,7 @@ namespace IW4MAdmin.Application.API } } - if (E.Type == Event.GType.Report) + if (E.Type == GameEvent.EventType.Report) { Events.Enqueue(new EventInfo( EventInfo.EventType.ALERT, diff --git a/Application/EventParsers/IW4EventParser.cs b/Application/EventParsers/IW4EventParser.cs new file mode 100644 index 000000000..347b7cc78 --- /dev/null +++ b/Application/EventParsers/IW4EventParser.cs @@ -0,0 +1,108 @@ +using System; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Objects; + +namespace Application.EventParsers +{ + class IW4EventParser : IEventParser + { + public GameEvent GetEvent(Server server, string logLine) + { + string[] lineSplit = logLine.Split(';'); + string cleanedEventLine = Regex.Replace(lineSplit[0], @"[0-9]+:[0-9]+\ ", ""); + + if (cleanedEventLine[0] == 'K') + { + if (!server.CustomCallback) + { + return new GameEvent() + { + Type = GameEvent.EventType.Script, + Data = logLine, + Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)), + Target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), + Owner = server + }; + } + } + + if (lineSplit[0].Substring(lineSplit[0].Length - 3).Trim() == "say") + { + return new GameEvent() + { + Type = GameEvent.EventType.Say, + Data = lineSplit[4].Replace("\x15", ""), + Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), + Owner = server, + Message = lineSplit[4] + }; + } + + if (cleanedEventLine.Contains("ScriptKill")) + { + return new GameEvent() + { + Type = GameEvent.EventType.Script, + Data = logLine, + Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()), + Target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()), + Owner = server + }; + } + + if (cleanedEventLine.Contains("ExitLevel")) + { + return new GameEvent() + { + Type = GameEvent.EventType.MapEnd, + Data = lineSplit[0], + Origin = new Player() + { + ClientId = 1 + }, + Target = new Player() + { + ClientId = 1 + }, + Owner = server + }; + } + + if (cleanedEventLine.Contains("InitGame")) + { + return new GameEvent() + { + Type = GameEvent.EventType.MapChange, + Data = lineSplit[0], + Origin = new Player() + { + ClientId = 1 + }, + Target = new Player() + { + ClientId = 1 + }, + Owner = server + }; + } + + return new GameEvent() + { + Type = GameEvent.EventType.Unknown, + Origin = new Player() + { + ClientId = 1 + }, + Target = new Player() + { + ClientId = 1 + }, + Owner = server + }; + } + } +} diff --git a/Application/EventParsers/T6MEventParser.cs b/Application/EventParsers/T6MEventParser.cs new file mode 100644 index 000000000..7267c687b --- /dev/null +++ b/Application/EventParsers/T6MEventParser.cs @@ -0,0 +1,92 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Objects; + +namespace Application.EventParsers +{ + class T6MEventParser : IEventParser + { + public GameEvent GetEvent(Server server, string logLine) + { + string[] lineSplit = logLine.Split(';'); + string cleanedEventName = Regex.Replace(lineSplit[0], @" +[0-9]+:[0-9]+ +", ""); + + if (cleanedEventName[0] == 'K') + { + return new GameEvent() + { + Type = GameEvent.EventType.Script, + Data = logLine, + Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)), + Target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), + Owner = server + }; + } + + if (cleanedEventName == "say") + { + return new GameEvent() + { + Type = GameEvent.EventType.Say, + Data = lineSplit[4], + Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), + Owner = server, + Message = lineSplit[4] + }; + } + + if (cleanedEventName.Contains("ShutdownGame")) + { + return new GameEvent() + { + Type = GameEvent.EventType.MapEnd, + Data = lineSplit[0], + Origin = new Player() + { + ClientId = 1 + }, + Target = new Player() + { + ClientId = 1 + }, + Owner = server + }; + } + + if (cleanedEventName.Contains("InitGame")) + { + return new GameEvent() + { + Type = GameEvent.EventType.MapChange, + Data = lineSplit[0], + Origin = new Player() + { + ClientId = 1 + }, + Target = new Player() + { + ClientId = 1 + }, + Owner = server + }; + } + + return new GameEvent() + { + Type = GameEvent.EventType.Unknown, + Origin = new Player() + { + ClientId = 1 + }, + Target = new Player() + { + ClientId = 1 + }, + Owner = server + }; + } + } +} diff --git a/Application/Main.cs b/Application/Main.cs index 4f899673b..f80826d40 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -38,7 +38,7 @@ namespace IW4MAdmin.Application ServerManager = ApplicationManager.GetInstance(); ServerManager.Init().Wait(); - Task.Run(() => + Task.Run((Action)(() => { String userInput; Player Origin = ServerManager.GetClientService().Get(1).Result.AsPlayer(); @@ -54,12 +54,12 @@ namespace IW4MAdmin.Application return; Origin.CurrentServer = ServerManager.Servers[0]; - Event E = new Event(Event.GType.Say, userInput, Origin, null, ServerManager.Servers[0]); + GameEvent E = new GameEvent((GameEvent.EventType)GameEvent.EventType.Say, userInput, Origin, null, ServerManager.Servers[0]); ServerManager.Servers[0].ExecuteEvent(E); Console.Write('>'); } while (ServerManager.Running); - }); + })); Task.Run(() => WebfrontCore.Program.Init(ServerManager)); ServerManager.Start(); diff --git a/Application/Manager.cs b/Application/Manager.cs index a89a8d133..661be03a6 100644 --- a/Application/Manager.cs +++ b/Application/Manager.cs @@ -28,7 +28,7 @@ namespace IW4MAdmin.Application public Dictionary PrivilegedClients { get; set; } public ILogger Logger { get; private set; } public bool Running { get; private set; } - public EventHandler ServerEventOccurred { get; private set; } + public EventHandler ServerEventOccurred { get; private set; } static ApplicationManager Instance; List TaskStatuses; diff --git a/Application/RconParsers/IW4Parser.cs b/Application/RconParsers/IW4RConParser.cs similarity index 88% rename from Application/RconParsers/IW4Parser.cs rename to Application/RconParsers/IW4RConParser.cs index 1234d941c..31d5947f6 100644 --- a/Application/RconParsers/IW4Parser.cs +++ b/Application/RconParsers/IW4RConParser.cs @@ -12,8 +12,17 @@ using SharedLibraryCore.Exceptions; namespace Application.RconParsers { - class IW4Parser : IRConParser + class IW4RConParser : IRConParser { + private static CommandPrefix Prefixes = new CommandPrefix() + { + Tell = "tellraw {0} {1}", + Say = "sayraw {0}", + Kick = "clientkick {0} \"{1}\"", + Ban = "clientkick {0} \"{1}\"", + TempBan = "tempbanclient {0} \"{1}\"" + }; + public async Task ExecuteCommandAsync(Connection connection, string command) { return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command)).Skip(1).ToArray(); @@ -60,6 +69,8 @@ namespace Application.RconParsers return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0; } + public CommandPrefix GetCommandPrefixes() => Prefixes; + private List ClientsFromStatus(string[] Status) { List StatusPlayers = new List(); @@ -68,7 +79,7 @@ namespace Application.RconParsers { String responseLine = S.Trim(); - if (Regex.Matches(responseLine, @"\d+$", RegexOptions.IgnoreCase).Count > 0 && responseLine.Length > 72) // its a client line! + if (Regex.Matches(responseLine, @"^\d+", RegexOptions.IgnoreCase).Count > 0) { String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); int cID = -1; diff --git a/Application/RconParsers/T6MParser.cs b/Application/RconParsers/T6MRConParser.cs similarity index 82% rename from Application/RconParsers/T6MParser.cs rename to Application/RconParsers/T6MRConParser.cs index e696454e0..feb1b1ff1 100644 --- a/Application/RconParsers/T6MParser.cs +++ b/Application/RconParsers/T6MRConParser.cs @@ -12,8 +12,19 @@ using System.Text; namespace Application.RconParsers { - public class T6MParser : IRConParser + public class T6MRConParser : IRConParser { + private static CommandPrefix Prefixes = new CommandPrefix() + { + Tell = "tell {0} {1}", + Say = "say {0}", + Kick = "clientKick {0}", + Ban = "clientKick {0}", + TempBan = "clientKick {0}" + }; + + public CommandPrefix GetCommandPrefixes() => Prefixes; + public async Task ExecuteCommandAsync(Connection connection, string command) { await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, false); @@ -69,19 +80,20 @@ namespace Application.RconParsers foreach (string statusLine in status) { - String responseLine = statusLine.Trim(); + String responseLine = statusLine; - if (Regex.Matches(responseLine, @"\d+$", RegexOptions.IgnoreCase).Count > 0 && responseLine.Length > 72) // its a client line! + if (Regex.Matches(responseLine, @"^\d+", RegexOptions.IgnoreCase).Count > 0) // 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()))); + var regex = Regex.Match(responseLine, @"\^7.*\ +0 "); + string name = Encoding.UTF8.GetString(Encoding.Convert(Encoding.UTF7, Encoding.UTF8, Encoding.UTF7.GetBytes(regex.Value.Substring(0, regex.Value.Length - 2).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}"); + regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}"); #if DEBUG Ping = 1; #endif diff --git a/Application/Server.cs b/Application/Server.cs index 71f211d29..fe934c5e5 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -15,6 +15,7 @@ using SharedLibraryCore.Configuration; using IW4MAdmin.Application.Misc; using Application.RconParsers; +using Application.EventParsers; namespace IW4MAdmin { @@ -59,19 +60,20 @@ namespace IW4MAdmin Players[polledPlayer.ClientNumber].Score = polledPlayer.Score; return true; } - #if !DEBUG if (polledPlayer.Name.Length < 3) { Logger.WriteDebug($"Kicking {polledPlayer} because their name is too short"); - await this.ExecuteCommandAsync($"clientkick {polledPlayer.ClientNumber} \"Your name must contain atleast 3 characters.\""); + string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, "Your name must contain atleast 3 characters."); + await this.ExecuteCommandAsync(formattedKick); return false; } if (Players.FirstOrDefault(p => p != null && p.Name == polledPlayer.Name) != null) { Logger.WriteDebug($"Kicking {polledPlayer} because their name is already in use"); - await this.ExecuteCommandAsync($"clientkick {polledPlayer.ClientNumber} \"Your name is being used by someone else.\""); + string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, "Your name is being used by someone else."); + await this.ExecuteCommandAsync(formattedKick); return false; } @@ -80,14 +82,16 @@ namespace IW4MAdmin polledPlayer.Name == "CHEATER") { Logger.WriteDebug($"Kicking {polledPlayer} because their name is generic"); - await this.ExecuteCommandAsync($"clientkick {polledPlayer.ClientNumber} \"Please change your name using /name.\""); + string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, "Please change your name using /name."); + await this.ExecuteCommandAsync(formattedKick); return false; } if (polledPlayer.Name.Where(c => Char.IsControl(c)).Count() > 0) { Logger.WriteDebug($"Kicking {polledPlayer} because their contains control characters"); - await this.ExecuteCommandAsync($"clientkick {polledPlayer.ClientNumber} \"Your name cannot contain control characters.\""); + string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, "Your name cannot contain control characters."); + await this.ExecuteCommandAsync(formattedKick); return false; } @@ -145,7 +149,7 @@ namespace IW4MAdmin player.CurrentServer = this; Players[player.ClientNumber] = player; - var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(player.AliasLinkId); + var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(player.AliasLinkId, player.IPAddress); var currentBan = activePenalties.FirstOrDefault(b => b.Expires > DateTime.UtcNow); if (currentBan != null) @@ -155,7 +159,10 @@ namespace IW4MAdmin autoKickClient.CurrentServer = this; if (currentBan.Type == Penalty.PenaltyType.TempBan) - await this.ExecuteCommandAsync($"clientkick {player.ClientNumber} \"You are temporarily banned. ({(currentBan.Expires - DateTime.UtcNow).TimeSpanText()} left)\""); + { + string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, $"You are temporarily banned. ({(currentBan.Expires - DateTime.UtcNow).TimeSpanText()} left)"); + await this.ExecuteCommandAsync(formattedKick); + } else await player.Kick($"Previously banned for {currentBan.Offense}", autoKickClient); @@ -166,7 +173,7 @@ namespace IW4MAdmin Logger.WriteInfo($"Client {player} connecting..."); - await ExecuteEvent(new Event(Event.GType.Connect, "", player, null, this)); + await ExecuteEvent(new GameEvent(GameEvent.EventType.Connect, "", player, null, this)); if (!Manager.GetApplicationSettings().Configuration().EnableClientVPNs && @@ -194,7 +201,7 @@ namespace IW4MAdmin Player Leaving = Players[cNum]; Logger.WriteInfo($"Client {Leaving} disconnecting..."); - await ExecuteEvent(new Event(Event.GType.Disconnect, "", Leaving, null, this)); + await ExecuteEvent(new GameEvent(GameEvent.EventType.Disconnect, "", Leaving, null, this)); Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds; Leaving.LastConnection = DateTime.UtcNow; @@ -203,34 +210,9 @@ namespace IW4MAdmin } } - //Another version of client from line, written for the line created by a kill or death event - override public Player ParseClientFromString(String[] L, int cIDPos) - { - if (L.Length < cIDPos) - { - Logger.WriteError("Line sent for client creation is not long enough!"); - return null; - } - - int pID = -2; // apparently falling = -1 cID so i can't use it now - int.TryParse(L[cIDPos].Trim(), out pID); - - if (pID == -1) // special case similar to mod_suicide - int.TryParse(L[2], out pID); - - if (pID < 0 || pID > 17) - { - Logger.WriteError("Event player index " + pID + " is out of bounds!"); - Logger.WriteDebug("Offending line -- " + String.Join(";", L)); - return null; - } - - return Players[pID]; - } - //Process requested command correlating to an event // todo: this needs to be removed out of here - override public async Task ValidateCommand(Event E) + override public async Task ValidateCommand(GameEvent E) { string CommandString = E.Data.Substring(1, E.Data.Length - 1).Split(' ')[0]; E.Message = E.Data; @@ -368,7 +350,7 @@ namespace IW4MAdmin return C; } - public override async Task ExecuteEvent(Event E) + public override async Task ExecuteEvent(GameEvent E) { if (Throttled) return; @@ -457,7 +439,7 @@ namespace IW4MAdmin // first start if (firstRun) { - await ExecuteEvent(new Event(Event.GType.Start, "Server started", null, null, this)); + await ExecuteEvent(new GameEvent(GameEvent.EventType.Start, "Server started", null, null, this)); firstRun = false; } @@ -546,8 +528,7 @@ namespace IW4MAdmin else { - string[] game_event = lines[count].Split(';'); - Event event_ = Event.ParseEventString(game_event, this); + GameEvent event_ = EventParser.GetEvent(this, lines[count]); if (event_ != null) { if (event_.Origin == null) @@ -590,7 +571,8 @@ namespace IW4MAdmin public async Task Initialize() { - RconParser = ServerConfig.UseT6MParser ? (IRConParser)new T6MParser() : new IW4Parser(); + RconParser = ServerConfig.UseT6MParser ? (IRConParser)new T6MRConParser() : new IW4RConParser(); + EventParser = ServerConfig.UseT6MParser ? (IEventParser)new T6MEventParser() : new IW4EventParser(); var version = await this.GetDvarAsync("version"); GameName = Utilities.GetGame(version.Value); @@ -666,7 +648,9 @@ namespace IW4MAdmin mainPath = (GameName == Game.T5M) ? "rzodemo" : mainPath; // patch for T6M:PLUTONIUM mainPath = (GameName == Game.T6M) ? $"t6r{Path.DirectorySeparatorChar}data" : mainPath; - +#if DEBUG + basepath.Value = @"\\192.168.88.253\Call of Duty Black Ops II"; +#endif string logPath = (game.Value == "" || onelog?.Value == 1) ? $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" : $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{game.Value.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{logfile.Value}"; @@ -685,16 +669,16 @@ 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 } //Process any server event - override protected async Task ProcessEvent(Event E) + override protected async Task ProcessEvent(GameEvent E) { - if (E.Type == Event.GType.Connect) + if (E.Type == GameEvent.EventType.Connect) { ChatHistory.Add(new ChatInfo() { @@ -707,7 +691,7 @@ namespace IW4MAdmin await E.Origin.Tell($"There are ^5{Reports.Count} ^7recent reports"); } - else if (E.Type == Event.GType.Disconnect) + else if (E.Type == GameEvent.EventType.Disconnect) { ChatHistory.Add(new ChatInfo() { @@ -717,12 +701,12 @@ namespace IW4MAdmin }); } - else if (E.Type == Event.GType.Script) + else if (E.Type == GameEvent.EventType.Script) { - await ExecuteEvent(new Event(Event.GType.Kill, E.Data, E.Origin, E.Target, this)); + await ExecuteEvent(new GameEvent(GameEvent.EventType.Kill, E.Data, E.Origin, E.Target, this)); } - if (E.Type == Event.GType.Say && E.Data.Length >= 2) + if (E.Type == GameEvent.EventType.Say && E.Data.Length >= 2) { if (E.Data.Substring(0, 1) == "!" || E.Data.Substring(0, 1) == "@" || E.Origin.Level == Player.Permission.Console) { @@ -776,7 +760,7 @@ namespace IW4MAdmin } } - if (E.Type == Event.GType.MapChange) + if (E.Type == GameEvent.EventType.MapChange) { Logger.WriteInfo($"New map loaded - {ClientNum} active players"); @@ -788,7 +772,7 @@ namespace IW4MAdmin CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; } - if (E.Type == Event.GType.MapEnd) + if (E.Type == GameEvent.EventType.MapEnd) { Logger.WriteInfo("Game ending..."); } @@ -821,7 +805,10 @@ namespace IW4MAdmin else { if (Target.Warnings >= 4) + { await Target.Kick("Too many warnings!", (await Manager.GetClientService().Get(1)).AsPlayer()); + return; + } Target.Warnings++; String Message = String.Format("^1WARNING ^7[^3{0}^7]: ^3{1}^7, {2}", Target.Warnings, Target.Name, Reason); @@ -859,7 +846,10 @@ namespace IW4MAdmin } #if !DEBUG else - await Target.CurrentServer.ExecuteCommandAsync($"clientkick {Target.ClientNumber} \"Player Kicked: ^5{Reason}^7\""); + { + string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"You were Kicked - ^5{Reason}^7"); + await Target.CurrentServer.ExecuteCommandAsync(formattedKick); + } #endif #if DEBUG @@ -897,7 +887,10 @@ namespace IW4MAdmin } #if !DEBUG else - await Target.CurrentServer.ExecuteCommandAsync($"clientkick {Target.ClientNumber } \"^7Player Temporarily Banned: ^5{ Reason }\""); + { + string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"^7You're Temporarily Banned - ^5{Reason}"); + await Target.CurrentServer.ExecuteCommandAsync(formattedKick); + } #else await Target.CurrentServer.RemovePlayer(Target.ClientNumber); #endif @@ -941,7 +934,8 @@ namespace IW4MAdmin // this is set only because they're still in the server. Target.Level = Player.Permission.Banned; #if !DEBUG - await Target.CurrentServer.ExecuteCommandAsync($"clientkick {Target.ClientNumber} \"Player Banned: ^5{Message} ^7(appeal at {Website}) ^7\""); + string formattedString = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"You're Banned - ^5{Message} ^7(appeal at {Website})^7"); + await Target.CurrentServer.ExecuteCommandAsync(formattedString); #else await Target.CurrentServer.RemovePlayer(Target.ClientNumber); #endif diff --git a/Plugins/ProfanityDeterment/Plugin.cs b/Plugins/ProfanityDeterment/Plugin.cs index 586fdeef2..68ac19abd 100644 --- a/Plugins/ProfanityDeterment/Plugin.cs +++ b/Plugins/ProfanityDeterment/Plugin.cs @@ -22,12 +22,12 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment IManager Manager; Task CompletedTask = Task.FromResult(false); - public async Task OnEventAsync(Event E, Server S) + public async Task OnEventAsync(GameEvent E, Server S) { if (!Settings.Configuration().EnableProfanityDeterment) return; - if (E.Type == Event.GType.Connect) + if (E.Type == GameEvent.EventType.Connect) { if (!ProfanityCounts.TryAdd(E.Origin.ClientId, new Tracking(E.Origin))) { @@ -36,7 +36,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment } - if (E.Type == Event.GType.Disconnect) + if (E.Type == GameEvent.EventType.Disconnect) { if (!ProfanityCounts.TryRemove(E.Origin.ClientId, out Tracking old)) { @@ -44,7 +44,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment } } - if (E.Type == Event.GType.Say) + if (E.Type == GameEvent.EventType.Say) { var objectionalWords = Settings.Configuration().OffensiveWords; bool containsObjectionalWord = objectionalWords.FirstOrDefault(w => E.Data.ToLower().Contains(w)) != null; diff --git a/Plugins/Stats/Cheat/Detection.cs b/Plugins/Stats/Cheat/Detection.cs index b0d50a0c8..2aab26908 100644 --- a/Plugins/Stats/Cheat/Detection.cs +++ b/Plugins/Stats/Cheat/Detection.cs @@ -68,7 +68,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat double newAverage = (previousAverage * (hitLoc.HitCount - 1) + angle) / hitLoc.HitCount; hitLoc.HitOffsetAverage = (float)newAverage; - if (hitLoc.HitOffsetAverage == float.NaN) + if (double.IsNaN(hitLoc.HitOffsetAverage)) { Log.WriteWarning("[Detection::ProcessKill] HitOffsetAvgerage NaN"); Log.WriteDebug($"{previousAverage}-{hitLoc.HitCount}-{hitLoc}-{newAverage}"); @@ -269,7 +269,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat public DetectionPenaltyResult ProcessTotalRatio(EFClientStatistics stats) { - int totalChestKills = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.left_arm_upper).HitCount; + int totalChestKills = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.torso_upper).HitCount; if (totalChestKills >= 60) { @@ -279,7 +279,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), Thresholds.ChestAbdomenRatioThresholdHighSample(2), lerpAmount) + marginOfError; double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(4.0), Thresholds.ChestAbdomenRatioThresholdHighSample(4.0), lerpAmount) + marginOfError; - double currentChestAbdomenRatio = stats.HitLocations.Single(hl => hl.Location == IW4Info.HitLocation.torso_upper).HitCount / + double currentChestAbdomenRatio = totalChestKills / stats.HitLocations.Single(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount; if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag) diff --git a/Plugins/Stats/Commands/ResetStats.cs b/Plugins/Stats/Commands/ResetStats.cs index 2fc775991..018bd56f1 100644 --- a/Plugins/Stats/Commands/ResetStats.cs +++ b/Plugins/Stats/Commands/ResetStats.cs @@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands { public ResetStats() : base("resetstats", "reset your stats to factory-new", "rs", Player.Permission.User, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Origin.ClientNumber >= 0) { diff --git a/Plugins/Stats/Commands/TopStats.cs b/Plugins/Stats/Commands/TopStats.cs index 31da779c2..225b3a475 100644 --- a/Plugins/Stats/Commands/TopStats.cs +++ b/Plugins/Stats/Commands/TopStats.cs @@ -14,7 +14,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands { public TopStats() : base("topstats", "view the top 5 players on this server", "ts", Player.Permission.User, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { var statsSvc = new GenericRepository(); int serverId = E.Owner.GetHashCode(); diff --git a/Plugins/Stats/Commands/ViewStats.cs b/Plugins/Stats/Commands/ViewStats.cs index de45d6067..7da9cb60e 100644 --- a/Plugins/Stats/Commands/ViewStats.cs +++ b/Plugins/Stats/Commands/ViewStats.cs @@ -22,7 +22,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Target?.ClientNumber < 0) { diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 6060a0007..ccf17bf92 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -193,7 +193,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2); // sync their stats before they leave - UpdateStats(clientStats); + clientStats = UpdateStats(clientStats); // todo: should this be saved every disconnect? statsSvc.ClientStatSvc.Update(clientStats); @@ -211,12 +211,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads) { var statsSvc = ContextThreads[serverId]; - - Vector3 vDeathOrigin = null; Vector3 vKillOrigin = null; - try { vDeathOrigin = Vector3.Parse(deathOrigin); @@ -295,7 +292,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers if (attacker.Level != Player.Permission.User) break; var flagCmd = new CFlag(); - await flagCmd.ExecuteAsync(new Event(Event.GType.Flag, $"{(int)penalty.Bone}-{Math.Round(penalty.RatioAmount, 2).ToString()}@{penalty.KillCount}", new Player() + await flagCmd.ExecuteAsync(new GameEvent(GameEvent.EventType.Flag, $"{(int)penalty.Bone}-{Math.Round(penalty.RatioAmount, 2).ToString()}@{penalty.KillCount}", new Player() { ClientId = 1, Level = Player.Permission.Console, @@ -356,12 +353,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers await attacker.Tell(streakMessage); // fixme: why? - if (victimStats.SPM == double.NaN || victimStats.Skill == double.NaN) + if (double.IsNaN(victimStats.SPM) || double.IsNaN(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}"); + attackerStats.SPM = 0.0; + attackerStats.Skill = 0.0; + } + // todo: do we want to save this immediately? var statsSvc = ContextThreads[serverId]; statsSvc.ClientStatSvc.Update(attackerStats); @@ -393,7 +398,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers victimStats.KillStreak = 0; // process the attacker's stats after the kills - UpdateStats(attackerStats); + attackerStats = UpdateStats(attackerStats); // update after calculation attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds; @@ -410,7 +415,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers private EFClientStatistics UpdateStats(EFClientStatistics clientStats) { // prevent NaN or inactive time lowering SPM - if ((DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0 < 0.1 || + if ((DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0 < 0.01 || (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0 > 3 || clientStats.SessionScore < 1) return clientStats; @@ -439,13 +444,11 @@ 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)); - 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) + 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}"); diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index f97b50d56..32d14be39 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -28,50 +28,50 @@ namespace IW4MAdmin.Plugins.Stats private IManager ServerManager; public static BaseConfigurationHandler Config { get; private set; } - public async Task OnEventAsync(Event E, Server S) + public async Task OnEventAsync(GameEvent E, Server S) { switch (E.Type) { - case Event.GType.Start: + case GameEvent.EventType.Start: Manager.AddServer(S); break; - case Event.GType.Stop: + case GameEvent.EventType.Stop: break; - case Event.GType.Connect: + case GameEvent.EventType.Connect: await Manager.AddPlayer(E.Origin); break; - case Event.GType.Disconnect: + case GameEvent.EventType.Disconnect: await Manager.RemovePlayer(E.Origin); break; - case Event.GType.Say: + case GameEvent.EventType.Say: if (E.Data != string.Empty && E.Data.Trim().Length > 0 && E.Message.Trim()[0] != '!' && E.Origin.ClientId > 1) await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data); break; - case Event.GType.MapChange: + case GameEvent.EventType.MapChange: Manager.ResetKillstreaks(S.GetHashCode()); await Manager.Sync(S); break; - case Event.GType.MapEnd: + case GameEvent.EventType.MapEnd: break; - case Event.GType.Broadcast: + case GameEvent.EventType.Broadcast: break; - case Event.GType.Tell: + case GameEvent.EventType.Tell: break; - case Event.GType.Kick: + case GameEvent.EventType.Kick: break; - case Event.GType.Ban: + case GameEvent.EventType.Ban: break; - case Event.GType.Remote: + case GameEvent.EventType.Remote: break; - case Event.GType.Unknown: + case GameEvent.EventType.Unknown: break; - case Event.GType.Report: + case GameEvent.EventType.Report: break; - case Event.GType.Flag: + case GameEvent.EventType.Flag: break; - case Event.GType.Script: + case GameEvent.EventType.Script: break; - case Event.GType.Kill: + case GameEvent.EventType.Kill: string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; if (killInfo.Length >= 9 && killInfo[0].Contains("ScriptKill") && E.Owner.CustomCallback) await Manager.AddScriptKill(E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], @@ -79,7 +79,7 @@ namespace IW4MAdmin.Plugins.Stats else if (!E.Owner.CustomCallback) await Manager.AddStandardKill(E.Origin, E.Target); break; - case Event.GType.Death: + case GameEvent.EventType.Death: break; } } diff --git a/Plugins/Welcome/Plugin.cs b/Plugins/Welcome/Plugin.cs index c2241c9cd..4197e19f9 100644 --- a/Plugins/Welcome/Plugin.cs +++ b/Plugins/Welcome/Plugin.cs @@ -5,6 +5,9 @@ using SharedLibraryCore; using SharedLibraryCore.Interfaces; using SharedLibraryCore.Objects; using SharedLibraryCore.Configuration; +using SharedLibraryCore.Services; +using SharedLibraryCore.Database.Models; +using System.Linq; namespace IW4MAdmin.Plugins.Welcome { @@ -73,9 +76,9 @@ namespace IW4MAdmin.Plugins.Welcome public Task OnTickAsync(Server S) => Utilities.CompletedTask; - public async Task OnEventAsync(Event E, Server S) + public async Task OnEventAsync(GameEvent E, Server S) { - if (E.Type == Event.GType.Connect) + if (E.Type == GameEvent.EventType.Connect) { Player newPlayer = E.Origin; if (newPlayer.Level >= Player.Permission.Trusted && !E.Origin.Masked) @@ -84,7 +87,10 @@ namespace IW4MAdmin.Plugins.Welcome await newPlayer.Tell(ProcessAnnouncement(Config.Configuration().UserWelcomeMessage, newPlayer)); if (newPlayer.Level == Player.Permission.Flagged) - await E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7has joined!"); + { + var penalty = await new GenericRepository().FindAsync(p => p.OffenderId == newPlayer.ClientId && p.Type == Penalty.PenaltyType.Flag); + await E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7({penalty.FirstOrDefault()?.Offense}) has joined!"); + } else await E.Owner.Broadcast(ProcessAnnouncement(Config.Configuration().UserAnnouncementMessage, newPlayer)); } diff --git a/SharedLibraryCore/Command.cs b/SharedLibraryCore/Command.cs index f0252b982..8675371c0 100644 --- a/SharedLibraryCore/Command.cs +++ b/SharedLibraryCore/Command.cs @@ -25,7 +25,7 @@ namespace SharedLibraryCore } //Execute the command - abstract public Task ExecuteAsync(Event E); + abstract public Task ExecuteAsync(GameEvent E); public String Name { get; private set; } public String Description { get; private set; } diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index 4b46a658d..90ca1c48c 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -18,7 +18,7 @@ namespace SharedLibraryCore.Commands base("quit", "quit IW4MAdmin", "q", Player.Permission.Owner, false) { } - public override Task ExecuteAsync(Event E) + public override Task ExecuteAsync(GameEvent E) { return Task.Run(() => { E.Owner.Manager.Stop(); }); } @@ -30,7 +30,7 @@ namespace SharedLibraryCore.Commands base("owner", "claim ownership of the server", "o", Player.Permission.User, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if ((await (E.Owner.Manager.GetClientService() as Services.ClientService).GetOwners()).Count == 0) { @@ -61,7 +61,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Origin.Level <= E.Target.Level) await E.Origin.Tell($"You do not have the required privileges to warn {E.Target.Name}"); @@ -83,7 +83,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { E.Target.Warnings = 0; String Message = String.Format("All warning cleared for {0}", E.Target.Name); @@ -109,11 +109,11 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Origin.Level > E.Target.Level) { - await E.Owner.ExecuteEvent(new Event(Event.GType.Kick, E.Data, E.Origin, E.Target, E.Owner)); + await E.Owner.ExecuteEvent(new GameEvent(GameEvent.EventType.Kick, E.Data, E.Origin, E.Target, E.Owner)); await E.Target.Kick(E.Data, E.Origin); await E.Origin.Tell($"^5{E.Target} ^7has been kicked"); } @@ -135,9 +135,9 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { - await E.Owner.Broadcast($"^:{E.Origin.Name} - ^6{E.Data}^7"); + await E.Owner.Broadcast($"{(E.Owner.GameName == Server.Game.IW4 ? "^:" : "")}{E.Origin.Name} - ^6{E.Data}^7"); } } @@ -164,7 +164,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { String Message = Utilities.RemoveWords(E.Data, 1).Trim(); var length = E.Data.Split(' ')[0].ToLower().ParseTimespan(); @@ -199,7 +199,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Origin.Level > E.Target.Level) { @@ -229,10 +229,10 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { var penalties = await E.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(E.Target.AliasLinkId); - if (penalties.Where(p => p.Type == Penalty.PenaltyType.Ban).FirstOrDefault() != null) + if (penalties.Where(p => p.Type == Penalty.PenaltyType.Ban || p.Type == Penalty.PenaltyType.TempBan).FirstOrDefault() != null) { await E.Owner.Unban(E.Data, E.Target, E.Origin); await E.Origin.Tell($"Successfully unbanned {E.Target}"); @@ -250,7 +250,7 @@ namespace SharedLibraryCore.Commands base("whoami", "give information about yourself.", "who", Player.Permission.User, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { String You = String.Format("{0} [^3#{1}^7] {2} [^3@{3}^7] [{4}^7] IP: {5}", E.Origin.Name, E.Origin.ClientNumber, E.Origin.NetworkId, E.Origin.ClientId, Utilities.ConvertLevelToColor(E.Origin.Level), E.Origin.IPAddressString); await E.Origin.Tell(You); @@ -263,7 +263,7 @@ namespace SharedLibraryCore.Commands base("list", "list active clients", "l", Player.Permission.Moderator, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { StringBuilder playerList = new StringBuilder(); int count = 0; @@ -305,7 +305,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { String cmd = E.Data.Trim(); @@ -361,7 +361,7 @@ namespace SharedLibraryCore.Commands base("fastrestart", "fast restart current map", "fr", Player.Permission.Moderator, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { await E.Owner.ExecuteCommandAsync("fast_restart"); @@ -378,7 +378,7 @@ namespace SharedLibraryCore.Commands base("maprotate", "cycle to the next map in rotation", "mr", Player.Permission.Administrator, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (!E.Origin.Masked) await E.Owner.Broadcast($"Map rotating in ^55 ^7seconds [^5{E.Origin.Name}^7]"); @@ -407,7 +407,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Target == E.Origin) { @@ -481,7 +481,7 @@ namespace SharedLibraryCore.Commands base("usage", "get current application memory usage", "us", Player.Permission.Moderator, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { await E.Origin.Tell("IW4M Admin is using " + Math.Round(((System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / 2048f) / 1200f), 1) + "MB"); } @@ -493,7 +493,7 @@ namespace SharedLibraryCore.Commands base("uptime", "get current application running time", "up", Player.Permission.Moderator, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { TimeSpan uptime = DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime; await E.Origin.Tell(String.Format("IW4M Admin has been up for {0} days, {1} hours, and {2} minutes", uptime.Days, uptime.Hours, uptime.Minutes)); @@ -506,7 +506,7 @@ namespace SharedLibraryCore.Commands base("admins", "list currently connected admins", "a", Player.Permission.User, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { int numOnline = 0; for (int i = 0; i < E.Owner.Players.Count; i++) @@ -540,7 +540,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { string newMap = E.Data.Trim().ToLower(); foreach (Map m in E.Owner.Maps) @@ -573,7 +573,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { IList db_players = (await (E.Owner.Manager.GetClientService() as ClientService) .GetClientByName(E.Data)) @@ -603,7 +603,7 @@ namespace SharedLibraryCore.Commands base("rules", "list server rules", "r", Player.Permission.User, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules?.Count < 1 && E.Owner.ServerConfig.Rules?.Count < 1) @@ -650,7 +650,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { await E.Target.Tell($"^1{E.Origin.Name} ^3[PM]^7 - {E.Data}"); await E.Origin.Tell($"To ^3{E.Target.Name} ^7-> {E.Data}"); @@ -675,7 +675,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { // todo: move unflag to seperate command if (E.Target.Level >= E.Origin.Level) @@ -708,7 +708,7 @@ namespace SharedLibraryCore.Commands }; await E.Owner.Manager.GetPenaltyService().Create(newPenalty); - await E.Owner.ExecuteEvent(new Event(Event.GType.Flag, E.Data, E.Origin, E.Target, E.Owner)); + await E.Owner.ExecuteEvent(new GameEvent(GameEvent.EventType.Flag, E.Data, E.Origin, E.Target, E.Owner)); await E.Origin.Tell($"You have flagged ^5{E.Target.Name}"); } @@ -733,7 +733,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Data.ToLower().Contains("camp")) { @@ -762,7 +762,7 @@ namespace SharedLibraryCore.Commands E.Owner.Reports.Add(new Report(E.Target, E.Origin, E.Data)); await E.Origin.Tell($"Thank you for your report, an administrator has been notified"); - await E.Owner.ExecuteEvent(new Event(Event.GType.Report, E.Data, E.Origin, E.Target, E.Owner)); + await E.Owner.ExecuteEvent(new GameEvent(GameEvent.EventType.Report, E.Data, E.Origin, E.Target, E.Owner)); await E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data)); } } @@ -780,7 +780,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Data != null && E.Data.ToLower().Contains("clear")) { @@ -806,7 +806,7 @@ namespace SharedLibraryCore.Commands base("mask", "hide your presence as an administrator", "hide", Player.Permission.Moderator, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Origin.Masked) { @@ -836,7 +836,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { var B = await E.Owner.Manager.GetPenaltyService().GetClientPenaltiesAsync(E.Target.ClientId); @@ -865,7 +865,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { StringBuilder message = new StringBuilder(); var names = new List(E.Target.AliasLink.Children.Select(a => a.Name)); @@ -897,7 +897,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { var Response = await E.Owner.ExecuteCommandAsync(E.Data.Trim()); foreach (string S in Response) @@ -913,7 +913,7 @@ namespace SharedLibraryCore.Commands base("plugins", "view all loaded plugins", "p", Player.Permission.Administrator, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { await E.Origin.Tell("^5Loaded Plugins:"); foreach (var P in Plugins.PluginImporter.ActivePlugins) @@ -929,7 +929,7 @@ namespace SharedLibraryCore.Commands base("getexternalip", "view your external IP address", "ip", Player.Permission.User, false) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { await E.Origin.Tell($"Your external IP is ^5{E.Origin.IPAddressString}"); } @@ -947,7 +947,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { int inactiveDays = 30; @@ -997,7 +997,7 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { if (E.Data.Length < 5) { @@ -1024,7 +1024,7 @@ namespace SharedLibraryCore.Commands { } - public override async Task ExecuteAsync(Event E) + public override async Task ExecuteAsync(GameEvent E) { var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x"); var currentProcess = gameserverProcesses.FirstOrDefault(g => g.MainWindowTitle.Contains(E.Owner.Hostname)); diff --git a/SharedLibraryCore/Event.cs b/SharedLibraryCore/Event.cs index d62b23933..326e9c3b3 100644 --- a/SharedLibraryCore/Event.cs +++ b/SharedLibraryCore/Event.cs @@ -7,9 +7,9 @@ using SharedLibraryCore.Objects; namespace SharedLibraryCore { - public class Event + public class GameEvent { - public enum GType + public enum EventType { //FROM SERVER Start, @@ -38,7 +38,7 @@ namespace SharedLibraryCore Death, } - public Event(GType t, string d, Player O, Player T, Server S) + public GameEvent(EventType t, string d, Player O, Player T, Server S) { Type = t; Data = d?.Trim(); @@ -47,59 +47,10 @@ namespace SharedLibraryCore Owner = S; } - public static Event ParseEventString(String[] line, Server SV) - { -#if DEBUG == false - try -#endif - { - string removeTime = Regex.Replace(line[0], @"[0-9]+:[0-9]+\ ", ""); - - if (removeTime[0] == 'K') - { - StringBuilder Data = new StringBuilder(); - if (line.Length > 9) - { - for (int i = 9; i < line.Length; i++) - Data.Append(line[i] + ";"); - } - - if (!SV.CustomCallback) - return new Event(GType.Script, Data.ToString(), SV.ParseClientFromString(line, 6), SV.ParseClientFromString(line, 2), SV); - } - - if (line[0].Substring(line[0].Length - 3).Trim() == "say") - { - Regex rgx = new Regex("[^a-zA-Z0-9 -! -_]"); - string message = rgx.Replace(line[4], ""); - return new Event(GType.Say, message.StripColors(), SV.ParseClientFromString(line, 2), null, SV) { Message = message }; - } - - if (removeTime.Contains("ScriptKill")) - { - return new Event(GType.Script, String.Join(";", line), SV.Players.First(p => p != null && p.NetworkId == line[1].ConvertLong()), SV.Players.First(p => p != null && p.NetworkId == line[2].ConvertLong()), SV); - } - - if (removeTime.Contains("ExitLevel")) - return new Event(GType.MapEnd, line[0], new Player() { ClientId = 1 }, null, SV); - - if (removeTime.Contains("InitGame")) - return new Event(GType.MapChange, line[0], new Player() { ClientId = 1 }, null, SV); + public GameEvent() { } - return null; - } -#if DEBUG == false - catch (Exception E) - { - SV.Manager.GetLogger().WriteError("Error requesting event " + E.Message); - return null; - } -#endif - } - - - public GType Type; + public EventType Type; public string Data; // Data is usually the message sent by player public string Message; public Player Origin; diff --git a/SharedLibraryCore/Interfaces/IEventApi.cs b/SharedLibraryCore/Interfaces/IEventApi.cs index 3aaf8d8bf..62b21445e 100644 --- a/SharedLibraryCore/Interfaces/IEventApi.cs +++ b/SharedLibraryCore/Interfaces/IEventApi.cs @@ -5,7 +5,7 @@ namespace SharedLibraryCore.Interfaces { public interface IEventApi { - void OnServerEvent(object sender, Event E); + void OnServerEvent(object sender, GameEvent E); Queue GetEvents(); } } diff --git a/SharedLibraryCore/Interfaces/IEventParser.cs b/SharedLibraryCore/Interfaces/IEventParser.cs new file mode 100644 index 000000000..369f08990 --- /dev/null +++ b/SharedLibraryCore/Interfaces/IEventParser.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Interfaces +{ + public interface IEventParser + { + GameEvent GetEvent(Server server, string logLine); + } +} diff --git a/SharedLibraryCore/Interfaces/IPlugin.cs b/SharedLibraryCore/Interfaces/IPlugin.cs index 04394e160..47b1737e5 100644 --- a/SharedLibraryCore/Interfaces/IPlugin.cs +++ b/SharedLibraryCore/Interfaces/IPlugin.cs @@ -7,7 +7,7 @@ namespace SharedLibraryCore.Interfaces { Task OnLoadAsync(IManager manager); Task OnUnloadAsync(); - Task OnEventAsync(Event E, Server S); + Task OnEventAsync(GameEvent E, Server S); Task OnTickAsync(Server S); //for logging purposes diff --git a/SharedLibraryCore/Interfaces/IRConParser.cs b/SharedLibraryCore/Interfaces/IRConParser.cs index ce308f45b..6380e2075 100644 --- a/SharedLibraryCore/Interfaces/IRConParser.cs +++ b/SharedLibraryCore/Interfaces/IRConParser.cs @@ -1,16 +1,16 @@ -using SharedLibraryCore.Objects; -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; using System.Threading.Tasks; +using SharedLibraryCore.Objects; +using SharedLibraryCore.RCon; 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); + Task> GetDvarAsync(Connection connection, string dvarName); + Task SetDvarAsync(Connection connection, string dvarName, object dvarValue); + Task ExecuteCommandAsync(Connection connection, string command); + Task> GetStatusAsync(Connection connection); + CommandPrefix GetCommandPrefixes(); } } diff --git a/SharedLibraryCore/RCon/CommandPrefix.cs b/SharedLibraryCore/RCon/CommandPrefix.cs new file mode 100644 index 000000000..e9f639255 --- /dev/null +++ b/SharedLibraryCore/RCon/CommandPrefix.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.RCon +{ + public class CommandPrefix + { + public string Tell { get; set; } + public string Say { get; set; } + public string Set { get; set; } + public string Kick { get; set; } + public string Ban { get; set; } + public string Unban { get; set; } + public string TempBan { get; set; } + } +} diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 07f8f5992..9df932c8b 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -79,13 +79,6 @@ namespace SharedLibraryCore /// true if removal succeded, false otherwise abstract public Task RemovePlayer(int cNum); - /// - /// Get the player from the server's list by line from game long - /// - /// Game log line containing event - /// Position in the line where the cliet ID is written - /// Matching player if found - abstract public Player ParseClientFromString(String[] L, int cIDPos); /// /// Get a player by name @@ -113,7 +106,7 @@ namespace SharedLibraryCore /// Event parameter /// Command requested from the event /// - abstract public Task ValidateCommand(Event E); + abstract public Task ValidateCommand(GameEvent E); virtual public Task ProcessUpdatesAsync(CancellationToken cts) { @@ -125,8 +118,8 @@ namespace SharedLibraryCore /// /// Event /// True on sucess - abstract protected Task ProcessEvent(Event E); - abstract public Task ExecuteEvent(Event E); + abstract protected Task ProcessEvent(GameEvent E); + abstract public Task ExecuteEvent(GameEvent E); /// /// Send a message to all players @@ -134,10 +127,9 @@ namespace SharedLibraryCore /// Message to be sent to all players public async Task Broadcast(String Message) { - - string sayCommand = (GameName == Game.IW4) ? "sayraw" : "say"; #if !DEBUG - await this.ExecuteCommandAsync($"{sayCommand} {(CustomSayEnabled ? CustomSayName : "")} {Message}"); + string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, Message); + await this.ExecuteCommandAsync(formattedMessage); #else Logger.WriteVerbose(Message.StripColors()); await Utilities.CompletedTask; @@ -151,11 +143,11 @@ namespace SharedLibraryCore /// Player to send message to public async Task Tell(String Message, Player Target) { - string tellCommand = (GameName == Game.IW4) ? "tellraw" : "tell"; #if !DEBUG + string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Tell, Target.ClientNumber, Message); if (Target.ClientNumber > -1 && Message.Length > 0 && Target.Level != Player.Permission.Console) - await this.ExecuteCommandAsync($"{tellCommand} {Target.ClientNumber} {(CustomSayEnabled ? CustomSayName : "")} {Message}^7"); + await this.ExecuteCommandAsync(formattedMessage); #else Logger.WriteVerbose($"{Target.ClientNumber}->{Message.StripColors()}"); await Utilities.CompletedTask; @@ -286,8 +278,8 @@ namespace SharedLibraryCore } // Objects - public Interfaces.IManager Manager { get; protected set; } - public Interfaces.ILogger Logger { get; private set; } + public IManager Manager { get; protected set; } + public ILogger Logger { get; private set; } public ServerConfiguration ServerConfig { get; private set; } public List Maps { get; protected set; } public List Reports { get; set; } @@ -315,6 +307,7 @@ namespace SharedLibraryCore public string WorkingDirectory { get; protected set; } public RCon.Connection RemoteConnection { get; protected set; } public IRConParser RconParser { get; protected set; } + public IEventParser EventParser { get; set; } // Internal protected string IP; diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index 6198f4c4a..2f52bfda2 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -139,6 +139,7 @@ namespace SharedLibraryCore.Services if (victim) { + var now = DateTime.UtcNow; var iqPenalties = from penalty in context.Penalties.AsNoTracking() where penalty.OffenderId == clientId join victimClient in context.Clients.AsNoTracking() @@ -160,13 +161,22 @@ namespace SharedLibraryCore.Services PunisherName = punisherAlias.Name, PunisherId = penalty.PunisherId, Offense = penalty.Offense, - Type = penalty.Type.ToString() + Type = penalty.Type.ToString(), + TimeRemaining = now > penalty.Expires ? "" : penalty.Expires.ToString() }, When = penalty.When, Sensitive = penalty.Type == Objects.Penalty.PenaltyType.Flag }; // fixme: is this good and fast? - return await iqPenalties.ToListAsync(); + var list = await iqPenalties.ToListAsync(); + list.ForEach(p => + { + var pi = ((PenaltyInfo)p.Value); + if (pi.TimeRemaining.Length > 0) + pi.TimeRemaining = (DateTime.Parse(((PenaltyInfo)p.Value).TimeRemaining) - now).TimeSpanText(); + } + ); + return list; } else @@ -204,17 +214,26 @@ namespace SharedLibraryCore.Services } } - public async Task> GetActivePenaltiesAsync(int aliasId) + public async Task> GetActivePenaltiesAsync(int aliasId, int ip = 0) { using (var context = new DatabaseContext()) { - var iqPenalties = from link in context.AliasLinks - where link.AliasLinkId == aliasId - join penalty in context.Penalties - on link.AliasLinkId equals penalty.LinkId - where penalty.Active - select penalty; - return await iqPenalties.ToListAsync(); + var iqPenalties = await (from link in context.AliasLinks + where link.AliasLinkId == aliasId + join penalty in context.Penalties + on link.AliasLinkId equals penalty.LinkId + where penalty.Active + select penalty).ToListAsync(); + if (ip != 0) + { + iqPenalties.AddRange(await (from alias in context.Aliases + where alias.IPAddress == ip + join penalty in context.Penalties + on alias.LinkId equals penalty.LinkId + where penalty.Active + select penalty).ToListAsync()); + } + return iqPenalties; } } diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 25bf5dffd..5737a16a8 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -355,6 +355,17 @@ namespace SharedLibraryCore return response; } + public static int ClientIdFromString(String[] lineSplit, int cIDPos) + { + int pID = -2; // apparently falling = -1 cID so i can't use it now + int.TryParse(lineSplit[cIDPos].Trim(), out pID); + + if (pID == -1) // special case similar to mod_suicide + int.TryParse(lineSplit[2], out pID); + + return pID; + } + public static async Task> GetDvarAsync(this Server server, string dvarName) => await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName); public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue) => await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue); diff --git a/WebfrontCore/Controllers/BaseController.cs b/WebfrontCore/Controllers/BaseController.cs index dc2524aeb..7d5bdd2cc 100644 --- a/WebfrontCore/Controllers/BaseController.cs +++ b/WebfrontCore/Controllers/BaseController.cs @@ -29,8 +29,6 @@ namespace WebfrontCore.Controllers if (HttpContext.Connection.RemoteIpAddress.ToString() != "127.0.0.1") { - - try { User.ClientId = Convert.ToInt32(base.User.Claims.First(c => c.Type == ClaimTypes.Sid).Value); diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index 14f06b0cd..0723cbda7 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -34,10 +34,16 @@ namespace WebfrontCore.Controllers { ClientId = User.ClientId, Level = User.Level, - CurrentServer = server + CurrentServer = server, + CurrentAlias = new Alias() { Name = User.Name } + }; + var remoteEvent = new GameEvent() + { + Type = GameEvent.EventType.Say, + Data = command, + Origin = client, + Owner = server }; - - var remoteEvent = new Event(Event.GType.Say, command, client, null, server); await server.ExecuteEvent(remoteEvent); diff --git a/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs b/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs index a39e2041c..2c674f60b 100644 --- a/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs +++ b/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs @@ -1,9 +1,10 @@ using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; using SharedLibraryCore.Dtos; +using SharedLibraryCore.Objects; using System; -using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; namespace WebfrontCore.ViewComponents @@ -12,22 +13,6 @@ namespace WebfrontCore.ViewComponents { public async Task InvokeAsync(int offset) { - int ip = HttpContext.Connection.RemoteIpAddress - .ToString().ConvertToIP(); - - bool authed = false; - - try - { - // var a = IW4MAdmin.ApplicationManager.GetInstance() - //.PrivilegedClients[HttpContext.Connection.RemoteIpAddress.ToString().ConvertToIP()]; - } - - catch (KeyNotFoundException) - { - - } - var penalties = await Program.Manager.GetPenaltyService().GetRecentPenalties(15, offset); var penaltiesDto = penalties.Select(p => new PenaltyInfo() { @@ -40,10 +25,11 @@ namespace WebfrontCore.ViewComponents Type = p.Type.ToString(), TimePunished = Utilities.GetTimePassed(p.When, false), TimeRemaining = DateTime.UtcNow > p.Expires ? "" : Utilities.TimeSpanText(p.Expires - DateTime.UtcNow), - Sensitive = p.Type == SharedLibraryCore.Objects.Penalty.PenaltyType.Flag + Sensitive = p.Type == Penalty.PenaltyType.Flag }); - penaltiesDto = authed ? penaltiesDto.ToList() : penaltiesDto.Where(p => !p.Sensitive).ToList(); + bool authorized = User.Identity.IsAuthenticated; + penaltiesDto = authorized ? penaltiesDto.ToList() : penaltiesDto.Where(p => !p.Sensitive).ToList(); return View("_List", penaltiesDto); } diff --git a/WebfrontCore/Views/Shared/_Layout.cshtml b/WebfrontCore/Views/Shared/_Layout.cshtml index d48da7b31..958a6c215 100644 --- a/WebfrontCore/Views/Shared/_Layout.cshtml +++ b/WebfrontCore/Views/Shared/_Layout.cshtml @@ -33,15 +33,36 @@ - + @if (!string.IsNullOrEmpty(ViewBag.DiscordLink)) { } @if (ViewBag.Authorized) { - - + + + } else { @@ -57,7 +78,7 @@ -
+