1
0
mirror of https://github.com/RaidMax/IW4M-Admin.git synced 2025-06-07 21:58:06 -05:00

implement functionality to dynamically populate property values from events that inherit from GameScriptEvent

This commit is contained in:
RaidMax 2024-06-22 17:02:04 -05:00
parent dffcae8344
commit 1596af1548
15 changed files with 232 additions and 75 deletions

View File

@ -105,7 +105,7 @@ namespace IW4MAdmin.Application
ConfigHandler = appConfigHandler; ConfigHandler = appConfigHandler;
StartTime = DateTime.UtcNow; StartTime = DateTime.UtcNow;
PageList = new PageList(); PageList = new PageList();
AdditionalEventParsers = new List<IEventParser> { new BaseEventParser(parserRegexFactory, logger, _appConfig) }; AdditionalEventParsers = new List<IEventParser> { new BaseEventParser(parserRegexFactory, logger, _appConfig, serviceProvider.GetRequiredService<IGameScriptEventFactory>()) };
AdditionalRConParsers = new List<IRConParser> { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) }; AdditionalRConParsers = new List<IRConParser> { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) };
TokenAuthenticator = new TokenAuthentication(); TokenAuthenticator = new TokenAuthentication();
_logger = logger; _logger = logger;
@ -710,7 +710,7 @@ namespace IW4MAdmin.Application
public IEventParser GenerateDynamicEventParser(string name) public IEventParser GenerateDynamicEventParser(string name)
{ {
return new DynamicEventParser(_parserRegexFactory, _logger, ConfigHandler.Configuration()) return new DynamicEventParser(_parserRegexFactory, _logger, ConfigHandler.Configuration(), _serviceProvider.GetRequiredService<IGameScriptEventFactory>())
{ {
Name = name Name = name
}; };

View File

@ -8,6 +8,7 @@ using System.Linq;
using Data.Models; using Data.Models;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SharedLibraryCore.Events.Game; using SharedLibraryCore.Events.Game;
using SharedLibraryCore.Interfaces.Events;
using static System.Int32; using static System.Int32;
using static SharedLibraryCore.Server; using static SharedLibraryCore.Server;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -21,16 +22,18 @@ namespace IW4MAdmin.Application.EventParsers
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ApplicationConfiguration _appConfig; private readonly ApplicationConfiguration _appConfig;
private readonly IGameScriptEventFactory _gameScriptEventFactory;
private readonly Dictionary<ParserRegex, GameEvent.EventType> _regexMap; private readonly Dictionary<ParserRegex, GameEvent.EventType> _regexMap;
private readonly Dictionary<string, GameEvent.EventType> _eventTypeMap; private readonly Dictionary<string, GameEvent.EventType> _eventTypeMap;
public BaseEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, public BaseEventParser(IParserRegexFactory parserRegexFactory, ILogger logger,
ApplicationConfiguration appConfig) ApplicationConfiguration appConfig, IGameScriptEventFactory gameScriptEventFactory)
{ {
_customEventRegistrations = _customEventRegistrations =
new Dictionary<string, (string, Func<string, IEventParserConfiguration, GameEvent, GameEvent>)>(); new Dictionary<string, (string, Func<string, IEventParserConfiguration, GameEvent, GameEvent>)>();
_logger = logger; _logger = logger;
_appConfig = appConfig; _appConfig = appConfig;
_gameScriptEventFactory = gameScriptEventFactory;
Configuration = new DynamicEventParserConfiguration(parserRegexFactory) Configuration = new DynamicEventParserConfiguration(parserRegexFactory)
{ {
@ -183,14 +186,28 @@ namespace IW4MAdmin.Application.EventParsers
if (logLine.StartsWith("GSE;")) if (logLine.StartsWith("GSE;"))
{ {
return new GameScriptEvent var gscEvent = new GameScriptEvent
{ {
ScriptData = logLine, ScriptData = logLine,
GameTime = gameTime, GameTime = gameTime,
Source = GameEvent.EventSource.Log 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)) if (eventKey is null || !_customEventRegistrations.ContainsKey(eventKey))
{ {
return GenerateDefaultEvent(logLine, gameTime); return GenerateDefaultEvent(logLine, gameTime);

View File

@ -1,5 +1,6 @@
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Interfaces.Events;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.EventParsers namespace IW4MAdmin.Application.EventParsers
@ -10,7 +11,9 @@ namespace IW4MAdmin.Application.EventParsers
/// </summary> /// </summary>
sealed internal class DynamicEventParser : BaseEventParser 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)
{ {
} }
} }

View File

@ -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<string, Type> _gameScriptEventMap;
public GameScriptEventFactory(IServiceProvider serviceProvider, IEnumerable<IGameScriptEvent> 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;
}
}

View File

@ -39,6 +39,7 @@ using IW4MAdmin.Plugins.Stats.Client.Abstractions;
using IW4MAdmin.Plugins.Stats.Client; using IW4MAdmin.Plugins.Stats.Client;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Refit; using Refit;
using SharedLibraryCore.Interfaces.Events;
using Stats.Client.Abstractions; using Stats.Client.Abstractions;
using Stats.Client; using Stats.Client;
using Stats.Config; using Stats.Config;
@ -527,6 +528,7 @@ namespace IW4MAdmin.Application
.AddSingleton(new ConfigurationWatcher()) .AddSingleton(new ConfigurationWatcher())
.AddSingleton(typeof(IConfigurationHandlerV2<>), typeof(BaseConfigurationHandlerV2<>)) .AddSingleton(typeof(IConfigurationHandlerV2<>), typeof(BaseConfigurationHandlerV2<>))
.AddSingleton<IScriptPluginFactory, ScriptPluginFactory>() .AddSingleton<IScriptPluginFactory, ScriptPluginFactory>()
.AddSingleton<IGameScriptEventFactory, GameScriptEventFactory>()
.AddSingleton(translationLookup) .AddSingleton(translationLookup)
.AddDatabaseContextOptions(appConfig); .AddDatabaseContextOptions(appConfig);

View File

@ -1,12 +1,13 @@
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
// ReSharper disable CompareOfFloatsByEqualityOperator // ReSharper disable CompareOfFloatsByEqualityOperator
#pragma warning disable CS0659 #pragma warning disable CS0659
namespace Data.Models namespace Data.Models
{ {
public class Vector3 public class Vector3 : IParsable<Vector3>
{ {
[Key] public int Vector3Id { get; set; } [Key] public int Vector3Id { get; set; }
public float X { get; protected set; } public float X { get; protected set; }
@ -111,5 +112,30 @@ namespace Data.Models
public double Magnitude() => Math.Sqrt((X * X) + (Y * Y) + (Z * Z)); 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 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;
}
} }
} }

View File

@ -3,7 +3,6 @@ using SharedLibraryCore;
using SharedLibraryCore.Dtos; using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using IW4MAdmin.Plugins.LiveRadar.Configuration; using IW4MAdmin.Plugins.LiveRadar.Configuration;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -82,7 +81,7 @@ namespace IW4MAdmin.Plugins.LiveRadar.Web.Controllers
} }
var radarInfo = server.GetClientsAsList() var radarInfo = server.GetClientsAsList()
.Select(client => client.GetAdditionalProperty<RadarEvent>("LiveRadar")).ToList(); .Select(client => client.GetAdditionalProperty<RadarDto>("LiveRadar")).ToList();
return Json(radarInfo); return Json(radarInfo);
} }

View File

@ -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<string, string,
System.Func<string, SharedLibraryCore.Interfaces.IEventParserConfiguration,
SharedLibraryCore.GameEvent,
SharedLibraryCore.GameEvent>>;
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<EventGeneratorCallback> Events => new[] { LiveRadar() };
}
public class LiveRadarEvent : GameScriptEvent
{
}

View File

@ -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; }
}

View File

@ -15,8 +15,11 @@
<StartupObject /> <StartupObject />
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup><!--<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.2.5.9" PrivateAssets="All" />-->
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.2.5.9" PrivateAssets="All" />
<ProjectReference Include="..\..\Data\Data.csproj" />
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -36,6 +36,9 @@ public class Plugin : IPluginV2
public static void RegisterDependencies(IServiceCollection serviceCollection) public static void RegisterDependencies(IServiceCollection serviceCollection)
{ {
serviceCollection.AddConfiguration<LiveRadarConfiguration>(); serviceCollection.AddConfiguration<LiveRadarConfiguration>();
serviceCollection.AddSingleton<IGameScriptEvent, LiveRadarScriptEvent>(); // for identification
serviceCollection.AddTransient<LiveRadarScriptEvent>(); // for factory
} }
public Plugin(ILogger<Plugin> logger, ApplicationConfiguration appConfig) public Plugin(ILogger<Plugin> logger, ApplicationConfiguration appConfig)
@ -51,7 +54,7 @@ public class Plugin : IPluginV2
private Task OnScriptEvent(GameScriptEvent scriptEvent, CancellationToken token) private Task OnScriptEvent(GameScriptEvent scriptEvent, CancellationToken token)
{ {
if (scriptEvent is not LiveRadarEvent radarEvent) if (scriptEvent is not LiveRadarScriptEvent radarEvent)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -83,14 +86,15 @@ public class Plugin : IPluginV2
: (originalBotGuid ?? "0").ConvertGuidToLong(NumberStyles.HexNumber); : (originalBotGuid ?? "0").ConvertGuidToLong(NumberStyles.HexNumber);
} }
var radarUpdate = RadarEvent.Parse(scriptEvent.ScriptData, generatedBotGuid); var radarDto = RadarDto.FromScriptEvent(radarEvent, generatedBotGuid);
var client = var client =
radarEvent.Owner.ConnectedClients.FirstOrDefault(client => client.NetworkId == radarUpdate.Guid); radarEvent.Owner.ConnectedClients.FirstOrDefault(client => client.NetworkId == radarDto.Guid);
if (client != null) if (client != null)
{ {
radarUpdate.Name = client.Name.StripColors(); radarDto.Name = client.Name.StripColors();
client.SetAdditionalProperty("LiveRadar", radarUpdate); client.SetAdditionalProperty("LiveRadar", radarDto);
} }
} }

View File

@ -1,13 +1,13 @@
using Data.Models; using Data.Models;
using SharedLibraryCore; using SharedLibraryCore;
using System; using IW4MAdmin.Plugins.LiveRadar.Events;
using System.Linq;
// ReSharper disable CompareOfFloatsByEqualityOperator // ReSharper disable CompareOfFloatsByEqualityOperator
#pragma warning disable CS0659 #pragma warning disable CS0659
namespace IW4MAdmin.Plugins.LiveRadar; namespace IW4MAdmin.Plugins.LiveRadar;
public class RadarEvent public class RadarDto
{ {
public string Name { get; set; } public string Name { get; set; }
public long Guid { get; set; } public long Guid { get; set; }
@ -26,7 +26,7 @@ public class RadarEvent
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if (obj is RadarEvent re) if (obj is RadarDto re)
{ {
return re.ViewAngles.X == ViewAngles.X && return re.ViewAngles.X == ViewAngles.X &&
re.ViewAngles.Y == ViewAngles.Y && re.ViewAngles.Y == ViewAngles.Y &&
@ -39,23 +39,21 @@ public class RadarEvent
return false; 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 RadarDto
var parsedEvent = new RadarEvent()
{ {
Guid = generatedBotGuid, Guid = generatedBotGuid,
Location = Vector3.Parse(items[1]), Location = scriptEvent.Location,
ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(), ViewAngles = scriptEvent.ViewAngles.FixIW4Angles(),
Team = items[3], Team = scriptEvent.Team,
Kills = int.Parse(items[4]), Kills = scriptEvent.Kills,
Deaths = int.Parse(items[5]), Deaths = scriptEvent.Deaths,
Score = int.Parse(items[6]), Score = scriptEvent.Score,
Weapon = items[7], Weapon =scriptEvent.Weapon,
Health = int.Parse(items[8]), Health = scriptEvent.Health,
IsAlive = items[9] == "1", IsAlive = scriptEvent.IsAlive,
PlayTime = Convert.ToInt32(items[10]) PlayTime = scriptEvent.PlayTime
}; };
return parsedEvent; return parsedEvent;

View File

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

View File

@ -0,0 +1,8 @@
namespace SharedLibraryCore.Interfaces.Events;
public interface IGameScriptEvent
{
string ScriptData { get; set; }
string EventName { get; }
void ParseArguments();
}

View File

@ -0,0 +1,6 @@
namespace SharedLibraryCore.Interfaces.Events;
public interface IGameScriptEventFactory
{
IGameScriptEvent Create(string eventType, string logData);
}