diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 98e39512..4dfda257 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -105,7 +105,7 @@ namespace IW4MAdmin.Application ConfigHandler = appConfigHandler; StartTime = DateTime.UtcNow; PageList = new PageList(); - AdditionalEventParsers = new List { new BaseEventParser(parserRegexFactory, logger, _appConfig) }; + AdditionalEventParsers = new List { new BaseEventParser(parserRegexFactory, logger, _appConfig, serviceProvider.GetRequiredService()) }; AdditionalRConParsers = new List { new BaseRConParser(serviceProvider.GetRequiredService>(), parserRegexFactory) }; TokenAuthenticator = new TokenAuthentication(); _logger = logger; @@ -710,7 +710,7 @@ namespace IW4MAdmin.Application public IEventParser GenerateDynamicEventParser(string name) { - return new DynamicEventParser(_parserRegexFactory, _logger, ConfigHandler.Configuration()) + return new DynamicEventParser(_parserRegexFactory, _logger, ConfigHandler.Configuration(), _serviceProvider.GetRequiredService()) { Name = name }; diff --git a/Application/EventParsers/BaseEventParser.cs b/Application/EventParsers/BaseEventParser.cs index ff05d483..105e2ec7 100644 --- a/Application/EventParsers/BaseEventParser.cs +++ b/Application/EventParsers/BaseEventParser.cs @@ -8,6 +8,7 @@ using System.Linq; using Data.Models; using Microsoft.Extensions.Logging; using SharedLibraryCore.Events.Game; +using SharedLibraryCore.Interfaces.Events; using static System.Int32; using static SharedLibraryCore.Server; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -21,16 +22,18 @@ namespace IW4MAdmin.Application.EventParsers private readonly ILogger _logger; private readonly ApplicationConfiguration _appConfig; + private readonly IGameScriptEventFactory _gameScriptEventFactory; private readonly Dictionary _regexMap; private readonly Dictionary _eventTypeMap; public BaseEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, - ApplicationConfiguration appConfig) + ApplicationConfiguration appConfig, IGameScriptEventFactory gameScriptEventFactory) { _customEventRegistrations = new Dictionary)>(); _logger = logger; _appConfig = appConfig; + _gameScriptEventFactory = gameScriptEventFactory; Configuration = new DynamicEventParserConfiguration(parserRegexFactory) { @@ -183,14 +186,28 @@ namespace IW4MAdmin.Application.EventParsers if (logLine.StartsWith("GSE;")) { - return new GameScriptEvent + var gscEvent = new GameScriptEvent { ScriptData = logLine, GameTime = gameTime, Source = GameEvent.EventSource.Log }; + return gscEvent; } + var split = logLine.Split(";", StringSplitOptions.RemoveEmptyEntries); + + if (split.Length > 1) + { + var createdEvent = _gameScriptEventFactory.Create(split[0], logLine.Replace(split[0], "")); + if (createdEvent is not null) + { + createdEvent.ParseArguments(); + return createdEvent as GameEventV2; + } + } + + if (eventKey is null || !_customEventRegistrations.ContainsKey(eventKey)) { return GenerateDefaultEvent(logLine, gameTime); diff --git a/Application/EventParsers/DynamicEventParser.cs b/Application/EventParsers/DynamicEventParser.cs index 8c8c0fb3..2a6378c9 100644 --- a/Application/EventParsers/DynamicEventParser.cs +++ b/Application/EventParsers/DynamicEventParser.cs @@ -1,5 +1,6 @@ using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Interfaces.Events; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace IW4MAdmin.Application.EventParsers @@ -10,7 +11,9 @@ namespace IW4MAdmin.Application.EventParsers /// sealed internal class DynamicEventParser : BaseEventParser { - public DynamicEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, ApplicationConfiguration appConfig) : base(parserRegexFactory, logger, appConfig) + public DynamicEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, + ApplicationConfiguration appConfig, IGameScriptEventFactory gameScriptEventFactory) : base( + parserRegexFactory, logger, appConfig, gameScriptEventFactory) { } } diff --git a/Application/Factories/GameScriptEventFactory.cs b/Application/Factories/GameScriptEventFactory.cs new file mode 100644 index 00000000..3ffb7985 --- /dev/null +++ b/Application/Factories/GameScriptEventFactory.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using SharedLibraryCore.Interfaces.Events; + +namespace IW4MAdmin.Application.Factories; + +public class GameScriptEventFactory : IGameScriptEventFactory +{ + private readonly IServiceProvider _serviceProvider; + private readonly Dictionary _gameScriptEventMap; + + public GameScriptEventFactory(IServiceProvider serviceProvider, IEnumerable gameScriptEventTypes) + { + _serviceProvider = serviceProvider; + + _gameScriptEventMap = gameScriptEventTypes + .ToLookup(kvp => kvp.EventName ?? kvp.GetType().Name.Replace("ScriptEvent", "")) + .ToDictionary(kvp => kvp.Key, kvp => kvp.First().GetType()); + } + + public IGameScriptEvent Create(string eventType, string logData) + { + if (string.IsNullOrEmpty(eventType) || !_gameScriptEventMap.TryGetValue(eventType, out var matchedType)) + { + return null; + } + + var newEvent = _serviceProvider.GetRequiredService(matchedType) as IGameScriptEvent; + + if (newEvent is not null) + { + newEvent.ScriptData = logData; + } + + return newEvent; + } +} diff --git a/Application/Main.cs b/Application/Main.cs index d4c3e62b..0362688b 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -39,6 +39,7 @@ using IW4MAdmin.Plugins.Stats.Client.Abstractions; using IW4MAdmin.Plugins.Stats.Client; using Microsoft.Extensions.Hosting; using Refit; +using SharedLibraryCore.Interfaces.Events; using Stats.Client.Abstractions; using Stats.Client; using Stats.Config; @@ -527,6 +528,7 @@ namespace IW4MAdmin.Application .AddSingleton(new ConfigurationWatcher()) .AddSingleton(typeof(IConfigurationHandlerV2<>), typeof(BaseConfigurationHandlerV2<>)) .AddSingleton() + .AddSingleton() .AddSingleton(translationLookup) .AddDatabaseContextOptions(appConfig); diff --git a/Data/Models/Vector3.cs b/Data/Models/Vector3.cs index 991a8385..12b20f27 100644 --- a/Data/Models/Vector3.cs +++ b/Data/Models/Vector3.cs @@ -1,12 +1,13 @@ using System; using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; + // ReSharper disable CompareOfFloatsByEqualityOperator #pragma warning disable CS0659 namespace Data.Models { - public class Vector3 + public class Vector3 : IParsable { [Key] public int Vector3Id { get; set; } public float X { get; protected set; } @@ -78,7 +79,7 @@ namespace Data.Models return Math.Sqrt((dx * dx) + (dy * dy)); } - + public static double ViewAngleDistance(Vector3 a, Vector3 b, Vector3 c) { double dabX = Math.Abs(a.X - b.X); @@ -111,5 +112,30 @@ namespace Data.Models public double Magnitude() => Math.Sqrt((X * X) + (Y * Y) + (Z * Z)); public double AngleBetween(Vector3 a) => Math.Acos(this.DotProduct(a) / (a.Magnitude() * this.Magnitude())); + + public static Vector3 Parse(string s, IFormatProvider provider) + { + return Parse(s); + } + + public static bool TryParse(string s, IFormatProvider provider, out Vector3 result) + { + result = new Vector3(); + + try + { + var parsed = Parse(s); + result.X = parsed.X; + result.Y = parsed.Y; + result.Z = parsed.Z; + return true; + } + catch + { + // ignored + } + + return false; + } } } diff --git a/Plugins/LiveRadar/Controllers/RadarController.cs b/Plugins/LiveRadar/Controllers/RadarController.cs index 397277a2..d711d87a 100644 --- a/Plugins/LiveRadar/Controllers/RadarController.cs +++ b/Plugins/LiveRadar/Controllers/RadarController.cs @@ -3,7 +3,6 @@ using SharedLibraryCore; using SharedLibraryCore.Dtos; using SharedLibraryCore.Interfaces; using System.Linq; -using System.Threading.Tasks; using IW4MAdmin.Plugins.LiveRadar.Configuration; using Microsoft.AspNetCore.Http; @@ -82,7 +81,7 @@ namespace IW4MAdmin.Plugins.LiveRadar.Web.Controllers } var radarInfo = server.GetClientsAsList() - .Select(client => client.GetAdditionalProperty("LiveRadar")).ToList(); + .Select(client => client.GetAdditionalProperty("LiveRadar")).ToList(); return Json(radarInfo); } diff --git a/Plugins/LiveRadar/Events/LiveRadarEvent.cs b/Plugins/LiveRadar/Events/LiveRadarEvent.cs deleted file mode 100644 index 7121e582..00000000 --- a/Plugins/LiveRadar/Events/LiveRadarEvent.cs +++ /dev/null @@ -1,38 +0,0 @@ -using SharedLibraryCore; -using SharedLibraryCore.Database.Models; -using SharedLibraryCore.Interfaces; -using System.Collections.Generic; -using SharedLibraryCore.Events.Game; -using EventGeneratorCallback = System.ValueTuple>; - -namespace IW4MAdmin.Plugins.LiveRadar.Events; - -public class Script : IRegisterEvent -{ - private const string EventLiveRadar = "LiveRadar"; - - private EventGeneratorCallback LiveRadar() - { - return (EventLiveRadar, EventLiveRadar, (eventLine, _, _) => - { - var radarEvent = new LiveRadarEvent - { - Type = GameEvent.EventType.Other, - Subtype = EventLiveRadar, - Origin = new EFClient { NetworkId = 0 }, - ScriptData = eventLine - }; - return radarEvent; - } - ); - } - - public IEnumerable Events => new[] { LiveRadar() }; -} - -public class LiveRadarEvent : GameScriptEvent -{ -} diff --git a/Plugins/LiveRadar/Events/LiveRadarScriptEvent.cs b/Plugins/LiveRadar/Events/LiveRadarScriptEvent.cs new file mode 100644 index 00000000..4522d04c --- /dev/null +++ b/Plugins/LiveRadar/Events/LiveRadarScriptEvent.cs @@ -0,0 +1,19 @@ +using Data.Models; +using SharedLibraryCore.Events.Game; + +namespace IW4MAdmin.Plugins.LiveRadar.Events; + +public class LiveRadarScriptEvent : GameScriptEvent +{ + public string Name { get; set; } + public Vector3 Location { get; set; } + public Vector3 ViewAngles { get; set; } + public string Team { get; set; } + public int Kills { get; set; } + public int Deaths { get; set; } + public int Score { get; set; } + public string Weapon { get; set; } + public int Health { get; set; } + public bool IsAlive { get; set; } + public int PlayTime { get; set; } +} diff --git a/Plugins/LiveRadar/LiveRadar.csproj b/Plugins/LiveRadar/LiveRadar.csproj index a1630dfd..a7001bb1 100644 --- a/Plugins/LiveRadar/LiveRadar.csproj +++ b/Plugins/LiveRadar/LiveRadar.csproj @@ -15,8 +15,11 @@ - - + + + + + diff --git a/Plugins/LiveRadar/Plugin.cs b/Plugins/LiveRadar/Plugin.cs index 467359b8..61231edb 100644 --- a/Plugins/LiveRadar/Plugin.cs +++ b/Plugins/LiveRadar/Plugin.cs @@ -36,6 +36,9 @@ public class Plugin : IPluginV2 public static void RegisterDependencies(IServiceCollection serviceCollection) { serviceCollection.AddConfiguration(); + + serviceCollection.AddSingleton(); // for identification + serviceCollection.AddTransient(); // for factory } public Plugin(ILogger logger, ApplicationConfiguration appConfig) @@ -51,7 +54,7 @@ public class Plugin : IPluginV2 private Task OnScriptEvent(GameScriptEvent scriptEvent, CancellationToken token) { - if (scriptEvent is not LiveRadarEvent radarEvent) + if (scriptEvent is not LiveRadarScriptEvent radarEvent) { return Task.CompletedTask; } @@ -83,14 +86,15 @@ public class Plugin : IPluginV2 : (originalBotGuid ?? "0").ConvertGuidToLong(NumberStyles.HexNumber); } - var radarUpdate = RadarEvent.Parse(scriptEvent.ScriptData, generatedBotGuid); + var radarDto = RadarDto.FromScriptEvent(radarEvent, generatedBotGuid); + var client = - radarEvent.Owner.ConnectedClients.FirstOrDefault(client => client.NetworkId == radarUpdate.Guid); + radarEvent.Owner.ConnectedClients.FirstOrDefault(client => client.NetworkId == radarDto.Guid); if (client != null) { - radarUpdate.Name = client.Name.StripColors(); - client.SetAdditionalProperty("LiveRadar", radarUpdate); + radarDto.Name = client.Name.StripColors(); + client.SetAdditionalProperty("LiveRadar", radarDto); } } diff --git a/Plugins/LiveRadar/RadarEvent.cs b/Plugins/LiveRadar/RadarDto.cs similarity index 65% rename from Plugins/LiveRadar/RadarEvent.cs rename to Plugins/LiveRadar/RadarDto.cs index bcbe7cea..4608043a 100644 --- a/Plugins/LiveRadar/RadarEvent.cs +++ b/Plugins/LiveRadar/RadarDto.cs @@ -1,13 +1,13 @@ using Data.Models; using SharedLibraryCore; -using System; -using System.Linq; +using IW4MAdmin.Plugins.LiveRadar.Events; + // ReSharper disable CompareOfFloatsByEqualityOperator #pragma warning disable CS0659 namespace IW4MAdmin.Plugins.LiveRadar; -public class RadarEvent +public class RadarDto { public string Name { get; set; } public long Guid { get; set; } @@ -26,7 +26,7 @@ public class RadarEvent public override bool Equals(object obj) { - if (obj is RadarEvent re) + if (obj is RadarDto re) { return re.ViewAngles.X == ViewAngles.X && re.ViewAngles.Y == ViewAngles.Y && @@ -39,23 +39,21 @@ public class RadarEvent return false; } - public static RadarEvent Parse(string input, long generatedBotGuid) + public static RadarDto FromScriptEvent(LiveRadarScriptEvent scriptEvent, long generatedBotGuid) { - var items = input.Split(';').Skip(1).ToList(); - - var parsedEvent = new RadarEvent() + var parsedEvent = new RadarDto { Guid = generatedBotGuid, - Location = Vector3.Parse(items[1]), - ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(), - Team = items[3], - Kills = int.Parse(items[4]), - Deaths = int.Parse(items[5]), - Score = int.Parse(items[6]), - Weapon = items[7], - Health = int.Parse(items[8]), - IsAlive = items[9] == "1", - PlayTime = Convert.ToInt32(items[10]) + Location = scriptEvent.Location, + ViewAngles = scriptEvent.ViewAngles.FixIW4Angles(), + Team = scriptEvent.Team, + Kills = scriptEvent.Kills, + Deaths = scriptEvent.Deaths, + Score = scriptEvent.Score, + Weapon =scriptEvent.Weapon, + Health = scriptEvent.Health, + IsAlive = scriptEvent.IsAlive, + PlayTime = scriptEvent.PlayTime }; return parsedEvent; diff --git a/SharedLibraryCore/Events/Game/GameScriptEvent.cs b/SharedLibraryCore/Events/Game/GameScriptEvent.cs index c14f4bc7..b1f8af99 100644 --- a/SharedLibraryCore/Events/Game/GameScriptEvent.cs +++ b/SharedLibraryCore/Events/Game/GameScriptEvent.cs @@ -1,6 +1,77 @@ -namespace SharedLibraryCore.Events.Game; +using System; +using System.Reflection; +using SharedLibraryCore.Interfaces.Events; -public class GameScriptEvent : GameEventV2 +namespace SharedLibraryCore.Events.Game; + +public class GameScriptEvent : GameEventV2, IGameScriptEvent { - public string ScriptData { get; init; } + public string ScriptData { get; set; } + public string EventName { get; } = null; + + public virtual void ParseArguments() + { + var arguments = ScriptData.Split(';', StringSplitOptions.RemoveEmptyEntries); + + var propIndex = 0; + foreach (var argument in arguments) + { + var parts = argument.Split(['='], 2); + PropertyInfo propertyInfo = null; + string rawValue; + + if (parts.Length == 2) // handle as key/value pairs + { + var propertyName = parts[0].Trim(); + rawValue = parts[1].Trim(); + propertyInfo = GetType().GetProperty(propertyName, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly); + } + else + { + rawValue = argument; + + try + { + propertyInfo = + GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | + BindingFlags.DeclaredOnly)[ + propIndex]; + } + catch + { + // ignored + } + } + + if (propertyInfo is null) + { + continue; + } + + try + { + var method = propertyInfo.PropertyType.GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, + [typeof(string)]); + + var convertedValue = method is not null + ? method!.Invoke(null, [rawValue])! + : Convert.ChangeType(rawValue, propertyInfo.PropertyType); + + propertyInfo.SetValue(this, convertedValue); + } + catch (TargetInvocationException ex) when (ex.InnerException is FormatException && + propertyInfo.PropertyType == typeof(bool)) + { + propertyInfo.SetValue(this, rawValue != "0"); + } + + catch + { + // ignored + } + + propIndex++; + } + } } diff --git a/SharedLibraryCore/Interfaces/Events/IGameScriptEvent.cs b/SharedLibraryCore/Interfaces/Events/IGameScriptEvent.cs new file mode 100644 index 00000000..b6e5458a --- /dev/null +++ b/SharedLibraryCore/Interfaces/Events/IGameScriptEvent.cs @@ -0,0 +1,8 @@ +namespace SharedLibraryCore.Interfaces.Events; + +public interface IGameScriptEvent +{ + string ScriptData { get; set; } + string EventName { get; } + void ParseArguments(); +} diff --git a/SharedLibraryCore/Interfaces/Events/IGameScriptEventFactory.cs b/SharedLibraryCore/Interfaces/Events/IGameScriptEventFactory.cs new file mode 100644 index 00000000..77bb71ce --- /dev/null +++ b/SharedLibraryCore/Interfaces/Events/IGameScriptEventFactory.cs @@ -0,0 +1,6 @@ +namespace SharedLibraryCore.Interfaces.Events; + +public interface IGameScriptEventFactory +{ + IGameScriptEvent Create(string eventType, string logData); +}