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 @@ -
+