[tweaks and fixes]

reenable tekno support
address vagrant thread issue
refactor game log reader creation to follow better practices
fix bot issues/address how guids are generated for bots/none provided
This commit is contained in:
RaidMax 2020-05-04 16:50:02 -05:00
parent b49592d666
commit 267e0b8cbe
50 changed files with 775 additions and 233 deletions

1
.gitignore vendored
View File

@ -242,3 +242,4 @@ launchSettings.json
/WebfrontCore/wwwroot/font /WebfrontCore/wwwroot/font
/Plugins/Tests/TestSourceFiles /Plugins/Tests/TestSourceFiles
/Tests/ApplicationTests/Files/GameEvents.json /Tests/ApplicationTests/Files/GameEvents.json
/Tests/ApplicationTests/Files/replay.json

View File

@ -10,7 +10,7 @@
<Company>Forever None</Company> <Company>Forever None</Company>
<Product>IW4MAdmin</Product> <Product>IW4MAdmin</Product>
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated servers</Description> <Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated servers</Description>
<Copyright>2019</Copyright> <Copyright>2020</Copyright>
<PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl> <PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin</RepositoryUrl> <RepositoryUrl>https://github.com/RaidMax/IW4M-Admin</RepositoryUrl>

View File

@ -31,7 +31,7 @@ namespace IW4MAdmin.Application
private readonly ConcurrentBag<Server> _servers; private readonly ConcurrentBag<Server> _servers;
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList(); public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
public ILogger Logger => GetLogger(0); public ILogger Logger => GetLogger(0);
public bool Running { get; private set; } public bool IsRunning { get; private set; }
public bool IsInitialized { get; private set; } public bool IsInitialized { get; private set; }
public DateTime StartTime { get; private set; } public DateTime StartTime { get; private set; }
public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString(); public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString();
@ -50,7 +50,6 @@ namespace IW4MAdmin.Application
readonly AliasService AliasSvc; readonly AliasService AliasSvc;
readonly PenaltyService PenaltySvc; readonly PenaltyService PenaltySvc;
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler; public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
GameEventHandler Handler;
readonly IPageList PageList; readonly IPageList PageList;
private readonly Dictionary<long, ILogger> _loggers = new Dictionary<long, ILogger>(); private readonly Dictionary<long, ILogger> _loggers = new Dictionary<long, ILogger>();
private readonly MetaService _metaService; private readonly MetaService _metaService;
@ -62,11 +61,13 @@ namespace IW4MAdmin.Application
private readonly IGameServerInstanceFactory _serverInstanceFactory; private readonly IGameServerInstanceFactory _serverInstanceFactory;
private readonly IParserRegexFactory _parserRegexFactory; private readonly IParserRegexFactory _parserRegexFactory;
private readonly IEnumerable<IRegisterEvent> _customParserEvents; private readonly IEnumerable<IRegisterEvent> _customParserEvents;
private readonly IEventHandler _eventHandler;
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands, public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration, ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory, IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents) IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
IEventHandler eventHandler)
{ {
MiddlewareActionHandler = actionHandler; MiddlewareActionHandler = actionHandler;
_servers = new ConcurrentBag<Server>(); _servers = new ConcurrentBag<Server>();
@ -90,6 +91,7 @@ namespace IW4MAdmin.Application
_serverInstanceFactory = serverInstanceFactory; _serverInstanceFactory = serverInstanceFactory;
_parserRegexFactory = parserRegexFactory; _parserRegexFactory = parserRegexFactory;
_customParserEvents = customParserEvents; _customParserEvents = customParserEvents;
_eventHandler = eventHandler;
Plugins = plugins; Plugins = plugins;
} }
@ -255,7 +257,7 @@ namespace IW4MAdmin.Application
public async Task Init() public async Task Init()
{ {
Running = true; IsRunning = true;
ExternalIPAddress = await Utilities.GetExternalIP(); ExternalIPAddress = await Utilities.GetExternalIP();
#region PLUGINS #region PLUGINS
@ -589,9 +591,6 @@ namespace IW4MAdmin.Application
async Task Init(ServerConfiguration Conf) async Task Init(ServerConfiguration Conf)
{ {
// setup the event handler after the class is initialized
Handler = new GameEventHandler(this);
try try
{ {
// todo: this might not always be an IW4MServer // todo: this might not always be an IW4MServer
@ -610,7 +609,7 @@ namespace IW4MAdmin.Application
Owner = ServerInstance Owner = ServerInstance
}; };
Handler.AddEvent(e); AddEvent(e);
successServers++; successServers++;
} }
@ -726,7 +725,7 @@ namespace IW4MAdmin.Application
public void Stop() public void Stop()
{ {
_tokenSource.Cancel(); _tokenSource.Cancel();
Running = false; IsRunning = false;
} }
public void Restart() public void Restart()
@ -782,9 +781,9 @@ namespace IW4MAdmin.Application
return ConfigHandler; return ConfigHandler;
} }
public IEventHandler GetEventHandler() public void AddEvent(GameEvent gameEvent)
{ {
return Handler; _eventHandler.HandleEvent(this, gameEvent);
} }
public IPageList GetPageList() public IPageList GetPageList()

View File

@ -23,26 +23,26 @@ namespace IW4MAdmin.Application.EventParsers
GameDirectory = "main", GameDirectory = "main",
}; };
Configuration.Say.Pattern = @"^(say|sayteam);(-?[A-Fa-f0-9_]{1,32});([0-9]+);(.+);(.*)$"; Configuration.Say.Pattern = @"^(say|sayteam);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);(.+);(.*)$";
Configuration.Say.AddMapping(ParserRegex.GroupType.EventType, 1); Configuration.Say.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2); Configuration.Say.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3); Configuration.Say.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginName, 4); Configuration.Say.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Say.AddMapping(ParserRegex.GroupType.Message, 5); Configuration.Say.AddMapping(ParserRegex.GroupType.Message, 5);
Configuration.Quit.Pattern = @"^(Q);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);([0-9]+);(.*)$"; Configuration.Quit.Pattern = @"^(Q);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);(.*)$";
Configuration.Quit.AddMapping(ParserRegex.GroupType.EventType, 1); Configuration.Quit.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2); Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3); Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginName, 4); Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Join.Pattern = @"^(J);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);([0-9]+);(.*)$"; Configuration.Join.Pattern = @"^(J);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);(.*)$";
Configuration.Join.AddMapping(ParserRegex.GroupType.EventType, 1); Configuration.Join.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2); Configuration.Join.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3); Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4); Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+)?;-?([0-9]+);(axis|allies|world)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1); Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2); Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3); Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
@ -57,7 +57,7 @@ namespace IW4MAdmin.Application.EventParsers
Configuration.Damage.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12); Configuration.Damage.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
Configuration.Damage.AddMapping(ParserRegex.GroupType.HitLocation, 13); Configuration.Damage.AddMapping(ParserRegex.GroupType.HitLocation, 13);
Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+)?;-?([0-9]+);(axis|allies|world)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Kill.AddMapping(ParserRegex.GroupType.EventType, 1); Configuration.Kill.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2); Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3); Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
@ -118,7 +118,13 @@ namespace IW4MAdmin.Application.EventParsers
if (message.Length > 0) if (message.Length > 0)
{ {
long originId = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle); string originIdString = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
string originName = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
long originId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
int clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]); int clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
// todo: these need to defined outside of here // todo: these need to defined outside of here
@ -132,7 +138,8 @@ namespace IW4MAdmin.Application.EventParsers
Message = message, Message = message,
Extra = logLine, Extra = logLine,
RequiredEntity = GameEvent.EventRequiredEntity.Origin, RequiredEntity = GameEvent.EventRequiredEntity.Origin,
GameTime = gameTime GameTime = gameTime,
Source = GameEvent.EventSource.Log
}; };
} }
@ -144,7 +151,8 @@ namespace IW4MAdmin.Application.EventParsers
Message = message, Message = message,
Extra = logLine, Extra = logLine,
RequiredEntity = GameEvent.EventRequiredEntity.Origin, RequiredEntity = GameEvent.EventRequiredEntity.Origin,
GameTime = gameTime GameTime = gameTime,
Source = GameEvent.EventSource.Log
}; };
} }
} }
@ -156,8 +164,18 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success) if (match.Success)
{ {
long originId = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); string originIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
long targetId = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); string targetIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString();
string originName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
string targetName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetName]].ToString();
long originId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
long targetId = targetIdString.IsBotGuid() ?
targetName.GenerateGuidFromString() :
targetIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
int originClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]); int originClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
int targetClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]); int targetClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
@ -168,7 +186,8 @@ namespace IW4MAdmin.Application.EventParsers
Origin = new EFClient() { NetworkId = originId, ClientNumber = originClientNumber }, Origin = new EFClient() { NetworkId = originId, ClientNumber = originClientNumber },
Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber }, Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber },
RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target, RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target,
GameTime = gameTime GameTime = gameTime,
Source = GameEvent.EventSource.Log
}; };
} }
} }
@ -179,8 +198,18 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success) if (match.Success)
{ {
long originId = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); string originIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
long targetId = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); string targetIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString();
string originName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
string targetName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetName]].ToString();
long originId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
long targetId = targetIdString.IsBotGuid() ?
targetName.GenerateGuidFromString() :
targetIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
int originClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]); int originClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
int targetClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]); int targetClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
@ -191,7 +220,8 @@ namespace IW4MAdmin.Application.EventParsers
Origin = new EFClient() { NetworkId = originId, ClientNumber = originClientNumber }, Origin = new EFClient() { NetworkId = originId, ClientNumber = originClientNumber },
Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber }, Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber },
RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target, RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target,
GameTime = gameTime GameTime = gameTime,
Source = GameEvent.EventSource.Log
}; };
} }
} }
@ -202,6 +232,13 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success) if (match.Success)
{ {
string originIdString = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
string originName = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
long networkId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.PreConnect, Type = GameEvent.EventType.PreConnect,
@ -212,13 +249,14 @@ namespace IW4MAdmin.Application.EventParsers
{ {
Name = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine(), Name = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine(),
}, },
NetworkId = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle), NetworkId = networkId,
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()), ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
State = EFClient.ClientState.Connecting, State = EFClient.ClientState.Connecting,
}, },
RequiredEntity = GameEvent.EventRequiredEntity.None, RequiredEntity = GameEvent.EventRequiredEntity.None,
IsBlocking = true, IsBlocking = true,
GameTime = gameTime GameTime = gameTime,
Source = GameEvent.EventSource.Log
}; };
} }
} }
@ -229,6 +267,13 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success) if (match.Success)
{ {
string originIdString = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
string originName = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
long networkId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.PreDisconnect, Type = GameEvent.EventType.PreDisconnect,
@ -239,13 +284,14 @@ namespace IW4MAdmin.Application.EventParsers
{ {
Name = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine() Name = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine()
}, },
NetworkId = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle), NetworkId = networkId,
ClientNumber = Convert.ToInt32(match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()), ClientNumber = Convert.ToInt32(match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
State = EFClient.ClientState.Disconnecting State = EFClient.ClientState.Disconnecting
}, },
RequiredEntity = GameEvent.EventRequiredEntity.None, RequiredEntity = GameEvent.EventRequiredEntity.None,
IsBlocking = true, IsBlocking = true,
GameTime = gameTime GameTime = gameTime,
Source = GameEvent.EventSource.Log
}; };
} }
} }
@ -259,7 +305,8 @@ namespace IW4MAdmin.Application.EventParsers
Origin = Utilities.IW4MAdminClient(), Origin = Utilities.IW4MAdminClient(),
Target = Utilities.IW4MAdminClient(), Target = Utilities.IW4MAdminClient(),
RequiredEntity = GameEvent.EventRequiredEntity.None, RequiredEntity = GameEvent.EventRequiredEntity.None,
GameTime = gameTime GameTime = gameTime,
Source = GameEvent.EventSource.Log
}; };
} }
@ -275,7 +322,8 @@ namespace IW4MAdmin.Application.EventParsers
Target = Utilities.IW4MAdminClient(), Target = Utilities.IW4MAdminClient(),
Extra = dump.DictionaryFromKeyValue(), Extra = dump.DictionaryFromKeyValue(),
RequiredEntity = GameEvent.EventRequiredEntity.None, RequiredEntity = GameEvent.EventRequiredEntity.None,
GameTime = gameTime GameTime = gameTime,
Source = GameEvent.EventSource.Log
}; };
} }
@ -290,7 +338,8 @@ namespace IW4MAdmin.Application.EventParsers
Type = GameEvent.EventType.Other, Type = GameEvent.EventType.Other,
Data = logLine, Data = logLine,
Subtype = eventModifier.Item1, Subtype = eventModifier.Item1,
GameTime = gameTime GameTime = gameTime,
Source = GameEvent.EventSource.Log
}); });
} }
@ -307,7 +356,8 @@ namespace IW4MAdmin.Application.EventParsers
Origin = Utilities.IW4MAdminClient(), Origin = Utilities.IW4MAdminClient(),
Target = Utilities.IW4MAdminClient(), Target = Utilities.IW4MAdminClient(),
RequiredEntity = GameEvent.EventRequiredEntity.None, RequiredEntity = GameEvent.EventRequiredEntity.None,
GameTime = gameTime GameTime = gameTime,
Source = GameEvent.EventSource.Log
}; };
} }

View File

@ -0,0 +1,33 @@
using IW4MAdmin.Application.IO;
using Microsoft.Extensions.DependencyInjection;
using SharedLibraryCore.Interfaces;
using System;
namespace IW4MAdmin.Application.Factories
{
public class GameLogReaderFactory : IGameLogReaderFactory
{
private readonly IServiceProvider _serviceProvider;
public GameLogReaderFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IGameLogReader CreateGameLogReader(Uri[] logUris, IEventParser eventParser)
{
var baseUri = logUris[0];
if (baseUri.Scheme == Uri.UriSchemeHttp)
{
return new GameLogReaderHttp(logUris, eventParser, _serviceProvider.GetRequiredService<ILogger>());
}
else if (baseUri.Scheme == Uri.UriSchemeFile)
{
return new GameLogReader(baseUri.LocalPath, eventParser, _serviceProvider.GetRequiredService<ILogger>());
}
throw new NotImplementedException($"No log reader implemented for Uri scheme \"{baseUri.Scheme}\"");
}
}
}

View File

@ -12,16 +12,18 @@ namespace IW4MAdmin.Application.Factories
{ {
private readonly ITranslationLookup _translationLookup; private readonly ITranslationLookup _translationLookup;
private readonly IRConConnectionFactory _rconConnectionFactory; private readonly IRConConnectionFactory _rconConnectionFactory;
private readonly IGameLogReaderFactory _gameLogReaderFactory;
/// <summary> /// <summary>
/// base constructor /// base constructor
/// </summary> /// </summary>
/// <param name="translationLookup"></param> /// <param name="translationLookup"></param>
/// <param name="rconConnectionFactory"></param> /// <param name="rconConnectionFactory"></param>
public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory) public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory)
{ {
_translationLookup = translationLookup; _translationLookup = translationLookup;
_rconConnectionFactory = rconConnectionFactory; _rconConnectionFactory = rconConnectionFactory;
_gameLogReaderFactory = gameLogReaderFactory;
} }
/// <summary> /// <summary>
@ -32,7 +34,7 @@ namespace IW4MAdmin.Application.Factories
/// <returns></returns> /// <returns></returns>
public Server CreateServer(ServerConfiguration config, IManager manager) public Server CreateServer(ServerConfiguration config, IManager manager)
{ {
return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory); return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory, _gameLogReaderFactory);
} }
} }
} }

View File

@ -1,19 +1,19 @@
using IW4MAdmin.Application.Misc; using IW4MAdmin.Application.Misc;
using Newtonsoft.Json;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Events; using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
class GameEventHandler : IEventHandler public class GameEventHandler : IEventHandler
{ {
private const int MAX_CONCURRENT_EVENTS = 10; private readonly EventLog _eventLog;
private readonly ApplicationManager _manager;
private readonly SemaphoreSlim _processingEvents;
private static readonly GameEvent.EventType[] overrideEvents = new[] private static readonly GameEvent.EventType[] overrideEvents = new[]
{ {
GameEvent.EventType.Connect, GameEvent.EventType.Connect,
@ -22,39 +22,12 @@ namespace IW4MAdmin.Application
GameEvent.EventType.Stop GameEvent.EventType.Stop
}; };
public GameEventHandler(IManager mgr) public GameEventHandler()
{ {
_manager = (ApplicationManager)mgr; _eventLog = new EventLog();
_processingEvents = new SemaphoreSlim(MAX_CONCURRENT_EVENTS, MAX_CONCURRENT_EVENTS);
} }
private Task GameEventHandler_GameEventAdded(object sender, GameEventArgs args) public void HandleEvent(IManager manager, GameEvent gameEvent)
{
try
{
// this is not elegant and there's probably a much better way to do it, but it works for now
_processingEvents.Wait();
EventApi.OnGameEvent(sender, args);
return _manager.ExecuteEvent(args.Event);
}
catch
{
}
finally
{
if (_processingEvents.CurrentCount < MAX_CONCURRENT_EVENTS)
{
_processingEvents.Release();
}
}
return Task.CompletedTask;
}
public void AddEvent(GameEvent gameEvent)
{ {
#if DEBUG #if DEBUG
ThreadPool.GetMaxThreads(out int workerThreads, out int n); ThreadPool.GetMaxThreads(out int workerThreads, out int n);
@ -62,12 +35,23 @@ namespace IW4MAdmin.Application
gameEvent.Owner.Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks"); gameEvent.Owner.Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
#endif #endif
if (_manager.Running || overrideEvents.Contains(gameEvent.Type)) if (manager.IsRunning || overrideEvents.Contains(gameEvent.Type))
{ {
#if DEBUG #if DEBUG
gameEvent.Owner.Logger.WriteDebug($"Adding event with id {gameEvent.Id}"); gameEvent.Owner.Logger.WriteDebug($"Adding event with id {gameEvent.Id}");
#endif #endif
Task.Run(() => GameEventHandler_GameEventAdded(this, new GameEventArgs(null, false, gameEvent)));
EventApi.OnGameEvent(gameEvent);
Task.Factory.StartNew(() => manager.ExecuteEvent(gameEvent));
/*if (!_eventLog.ContainsKey(gameEvent.Owner.EndPoint))
{
_eventLog.Add(gameEvent.Owner.EndPoint,new List<GameEvent>());
}
_eventLog[gameEvent.Owner.EndPoint].Add(gameEvent);
string serializedEvents = JsonConvert.SerializeObject(_eventLog, EventLog.BuildVcrSerializationSettings());
System.IO.File.WriteAllText("output.json", serializedEvents);*/
//Task.Run(() => GameEventHandler_GameEventAdded(this, new GameEventArgs(null, false, gameEvent)));
} }
#if DEBUG #if DEBUG
else else

View File

@ -13,17 +13,9 @@ namespace IW4MAdmin.Application.IO
private readonly IGameLogReader _reader; private readonly IGameLogReader _reader;
private readonly bool _ignoreBots; private readonly bool _ignoreBots;
class EventState public GameLogEventDetection(Server server, Uri[] gameLogUris, IGameLogReaderFactory gameLogReaderFactory)
{ {
public ILogger Log { get; set; } _reader = gameLogReaderFactory.CreateGameLogReader(gameLogUris, server.EventParser);
public string ServerId { get; set; }
}
public GameLogEventDetection(Server server, string gameLogPath, Uri gameLogServerUri, IGameLogReader reader = null)
{
_reader = gameLogServerUri != null
? reader ?? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser)
: reader ?? new GameLogReader(gameLogPath, server.EventParser);
_server = server; _server = server;
_ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false; _ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false;
} }
@ -70,7 +62,7 @@ namespace IW4MAdmin.Application.IO
return; return;
} }
var events = await _reader.ReadEventsFromLog(_server, fileDiff, previousFileSize); var events = await _reader.ReadEventsFromLog(fileDiff, previousFileSize);
foreach (var gameEvent in events) foreach (var gameEvent in events)
{ {
@ -84,9 +76,9 @@ namespace IW4MAdmin.Application.IO
// we don't want to add the event if ignoreBots is on and the event comes from a bot // we don't want to add the event if ignoreBots is on and the event comes from a bot
if (!_ignoreBots || (_ignoreBots && !((gameEvent.Origin?.IsBot ?? false) || (gameEvent.Target?.IsBot ?? false)))) if (!_ignoreBots || (_ignoreBots && !((gameEvent.Origin?.IsBot ?? false) || (gameEvent.Target?.IsBot ?? false))))
{ {
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != 1) if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != Utilities.WORLD_ID)
{ {
gameEvent.Origin = _server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId); gameEvent.Origin = _server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId);;
} }
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Target) == GameEvent.EventRequiredEntity.Target) if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Target) == GameEvent.EventRequiredEntity.Target)
@ -104,7 +96,7 @@ namespace IW4MAdmin.Application.IO
gameEvent.Target.CurrentServer = _server; gameEvent.Target.CurrentServer = _server;
} }
_server.Manager.GetEventHandler().AddEvent(gameEvent); _server.Manager.AddEvent(gameEvent);
} }
} }

View File

@ -13,18 +13,20 @@ namespace IW4MAdmin.Application.IO
{ {
private readonly IEventParser _parser; private readonly IEventParser _parser;
private readonly string _logFile; private readonly string _logFile;
private readonly ILogger _logger;
public long Length => new FileInfo(_logFile).Length; public long Length => new FileInfo(_logFile).Length;
public int UpdateInterval => 300; public int UpdateInterval => 300;
public GameLogReader(string logFile, IEventParser parser) public GameLogReader(string logFile, IEventParser parser, ILogger logger)
{ {
_logFile = logFile; _logFile = logFile;
_parser = parser; _parser = parser;
_logger = logger;
} }
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition)
{ {
// allocate the bytes for the new log lines // allocate the bytes for the new log lines
List<string> logLines = new List<string>(); List<string> logLines = new List<string>();
@ -34,7 +36,7 @@ namespace IW4MAdmin.Application.IO
{ {
byte[] buff = new byte[fileSizeDiff]; byte[] buff = new byte[fileSizeDiff];
fs.Seek(startPosition, SeekOrigin.Begin); fs.Seek(startPosition, SeekOrigin.Begin);
await fs.ReadAsync(buff, 0, (int)fileSizeDiff, server.Manager.CancellationToken); await fs.ReadAsync(buff, 0, (int)fileSizeDiff);
var stringBuilder = new StringBuilder(); var stringBuilder = new StringBuilder();
char[] charBuff = Utilities.EncodingType.GetChars(buff); char[] charBuff = Utilities.EncodingType.GetChars(buff);
@ -71,9 +73,9 @@ namespace IW4MAdmin.Application.IO
catch (Exception e) catch (Exception e)
{ {
server.Logger.WriteWarning("Could not properly parse event line"); _logger.WriteWarning("Could not properly parse event line");
server.Logger.WriteDebug(e.Message); _logger.WriteDebug(e.Message);
server.Logger.WriteDebug(eventLine); _logger.WriteDebug(eventLine);
} }
} }

View File

@ -5,43 +5,42 @@ using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using static SharedLibraryCore.Utilities;
namespace IW4MAdmin.Application.IO namespace IW4MAdmin.Application.IO
{ {
/// <summary> /// <summary>
/// provides capibility of reading log files over HTTP /// provides capability of reading log files over HTTP
/// </summary> /// </summary>
class GameLogReaderHttp : IGameLogReader class GameLogReaderHttp : IGameLogReader
{ {
private readonly IEventParser _eventParser; private readonly IEventParser _eventParser;
private readonly IGameLogServer _logServerApi; private readonly IGameLogServer _logServerApi;
readonly string logPath; private readonly ILogger _logger;
private readonly string _safeLogPath;
private string lastKey = "next"; private string lastKey = "next";
public GameLogReaderHttp(Uri gameLogServerUri, string logPath, IEventParser parser) public GameLogReaderHttp(Uri[] gameLogServerUris, IEventParser parser, ILogger logger)
{ {
this.logPath = logPath.ToBase64UrlSafeString();
_eventParser = parser; _eventParser = parser;
_logServerApi = RestClient.For<IGameLogServer>(gameLogServerUri); _logServerApi = RestClient.For<IGameLogServer>(gameLogServerUris[0].ToString());
_safeLogPath = gameLogServerUris[1].LocalPath.ToBase64UrlSafeString();
_logger = logger;
} }
public long Length => -1; public long Length => -1;
public int UpdateInterval => 500; public int UpdateInterval => 500;
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition)
{ {
var events = new List<GameEvent>(); var events = new List<GameEvent>();
string b64Path = logPath; var response = await _logServerApi.Log(_safeLogPath, lastKey);
var response = await _logServerApi.Log(b64Path, lastKey);
lastKey = response.NextKey; lastKey = response.NextKey;
if (!response.Success && string.IsNullOrEmpty(lastKey)) if (!response.Success && string.IsNullOrEmpty(lastKey))
{ {
server.Logger.WriteError($"Could not get log server info of {logPath}/{b64Path} ({server.LogPath})"); _logger.WriteError($"Could not get log server info of {_safeLogPath}");
return events; return events;
} }
@ -63,9 +62,9 @@ namespace IW4MAdmin.Application.IO
catch (Exception e) catch (Exception e)
{ {
server.Logger.WriteError("Could not properly parse event line from http"); _logger.WriteError("Could not properly parse event line from http");
server.Logger.WriteDebug(e.Message); _logger.WriteDebug(e.Message);
server.Logger.WriteDebug(eventLine); _logger.WriteDebug(eventLine);
} }
} }
} }

View File

@ -13,6 +13,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -31,7 +32,7 @@ namespace IW4MAdmin
public int Id { get; private set; } public int Id { get; private set; }
public IW4MServer(IManager mgr, ServerConfiguration cfg, ITranslationLookup lookup, public IW4MServer(IManager mgr, ServerConfiguration cfg, ITranslationLookup lookup,
IRConConnectionFactory connectionFactory) : base(mgr, connectionFactory, cfg) IRConConnectionFactory connectionFactory, IGameLogReaderFactory gameLogReaderFactory) : base(cfg, mgr, connectionFactory, gameLogReaderFactory)
{ {
_translationLookup = lookup; _translationLookup = lookup;
} }
@ -77,7 +78,7 @@ namespace IW4MAdmin
Type = GameEvent.EventType.Connect Type = GameEvent.EventType.Connect
}; };
Manager.GetEventHandler().AddEvent(e); Manager.AddEvent(e);
return client; return client;
} }
@ -104,7 +105,7 @@ namespace IW4MAdmin
Type = GameEvent.EventType.Disconnect Type = GameEvent.EventType.Disconnect
}; };
Manager.GetEventHandler().AddEvent(e); Manager.AddEvent(e);
#if DEBUG == true #if DEBUG == true
} }
#endif #endif
@ -176,6 +177,8 @@ namespace IW4MAdmin
await command.ExecuteAsync(E); await command.ExecuteAsync(E);
} }
var pluginTasks = Manager.Plugins.Where(_plugin => _plugin.Name != "Login").Select(async _plugin => var pluginTasks = Manager.Plugins.Where(_plugin => _plugin.Name != "Login").Select(async _plugin =>
{ {
try try
@ -186,7 +189,11 @@ namespace IW4MAdmin
return; return;
} }
await _plugin.OnEventAsync(E, this); using (var tokenSource = new CancellationTokenSource())
{
tokenSource.CancelAfter(Utilities.DefaultCommandTimeout);
await (_plugin.OnEventAsync(E, this)).WithWaitCancellation(tokenSource.Token);
}
} }
catch (Exception Except) catch (Exception Except)
{ {
@ -195,7 +202,7 @@ namespace IW4MAdmin
} }
}); });
Parallel.ForEach(pluginTasks, async (_task) => await _task); await Task.WhenAny(pluginTasks);
} }
catch (Exception e) catch (Exception e)
@ -314,7 +321,8 @@ namespace IW4MAdmin
// this happens for some reason rarely where the client spots get out of order // this happens for some reason rarely where the client spots get out of order
// possible a connect/reconnect game event before we get to process it here // possible a connect/reconnect game event before we get to process it here
// it appears that new games decide to switch client slots between maps (even if the clients aren't disconnecting) // it appears that new games decide to switch client slots between maps (even if the clients aren't disconnecting)
else if (existingClient != null && existingClient.ClientNumber != E.Origin.ClientNumber) // bots can have duplicate names which causes conflicting GUIDs
else if (existingClient != null && existingClient.ClientNumber != E.Origin.ClientNumber && !E.Origin.IsBot)
{ {
Logger.WriteWarning($"client {E.Origin} is trying to connect in client slot {E.Origin.ClientNumber}, but they are already registed in client slot {existingClient.ClientNumber}, swapping..."); Logger.WriteWarning($"client {E.Origin} is trying to connect in client slot {E.Origin.ClientNumber}, but they are already registed in client slot {existingClient.ClientNumber}, swapping...");
// we need to remove them so the client spots can swap // we need to remove them so the client spots can swap
@ -727,7 +735,7 @@ namespace IW4MAdmin
Origin = client Origin = client
}; };
Manager.GetEventHandler().AddEvent(e); Manager.AddEvent(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token); await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token);
} }
@ -771,10 +779,11 @@ namespace IW4MAdmin
{ {
Type = GameEvent.EventType.PreDisconnect, Type = GameEvent.EventType.PreDisconnect,
Origin = disconnectingClient, Origin = disconnectingClient,
Owner = this Owner = this,
Source = GameEvent.EventSource.Status
}; };
Manager.GetEventHandler().AddEvent(e); Manager.AddEvent(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken); await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken);
} }
@ -793,10 +802,11 @@ namespace IW4MAdmin
Type = GameEvent.EventType.PreConnect, Type = GameEvent.EventType.PreConnect,
Origin = client, Origin = client,
Owner = this, Owner = this,
IsBlocking = true IsBlocking = true,
Source = GameEvent.EventSource.Status
}; };
Manager.GetEventHandler().AddEvent(e); Manager.AddEvent(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken); await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken);
} }
@ -811,7 +821,7 @@ namespace IW4MAdmin
Owner = this Owner = this
}; };
Manager.GetEventHandler().AddEvent(e); Manager.AddEvent(e);
} }
if (ConnectionErrors > 0) if (ConnectionErrors > 0)
@ -824,7 +834,7 @@ namespace IW4MAdmin
Target = Utilities.IW4MAdminClient(this) Target = Utilities.IW4MAdminClient(this)
}; };
Manager.GetEventHandler().AddEvent(_event); Manager.AddEvent(_event);
} }
ConnectionErrors = 0; ConnectionErrors = 0;
@ -846,7 +856,7 @@ namespace IW4MAdmin
Data = ConnectionErrors.ToString() Data = ConnectionErrors.ToString()
}; };
Manager.GetEventHandler().AddEvent(_event); Manager.AddEvent(_event);
} }
return true; return true;
} }
@ -1071,7 +1081,7 @@ namespace IW4MAdmin
} }
} }
LogEvent = new GameLogEventDetection(this, LogPath, ServerConfig.GameLogServerUrl); LogEvent = new GameLogEventDetection(this, GenerateUriForLog(LogPath, ServerConfig.GameLogServerUrl?.AbsoluteUri), gameLogReaderFactory);
Logger.WriteInfo($"Log file is {LogPath}"); Logger.WriteInfo($"Log file is {LogPath}");
_ = Task.Run(() => LogEvent.PollForChanges()); _ = Task.Run(() => LogEvent.PollForChanges());
@ -1080,6 +1090,21 @@ namespace IW4MAdmin
#endif #endif
} }
public Uri[] GenerateUriForLog(string logPath, string gameLogServerUrl)
{
var logUri = new Uri(logPath);
if (string.IsNullOrEmpty(gameLogServerUrl))
{
return new[] { logUri };
}
else
{
return new[] { new Uri(gameLogServerUrl), logUri };
}
}
public static string GenerateLogPath(LogPathGeneratorInfo logInfo) public static string GenerateLogPath(LogPathGeneratorInfo logInfo)
{ {
string logPath; string logPath;
@ -1179,7 +1204,7 @@ namespace IW4MAdmin
Owner = this Owner = this
}; };
Manager.GetEventHandler().AddEvent(e); Manager.AddEvent(e);
string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_KICK_TEXT"]} - ^5{Reason}^7"); string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_KICK_TEXT"]} - ^5{Reason}^7");
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick); await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);

View File

@ -30,7 +30,7 @@ namespace IW4MAdmin.Application
/// entrypoint of the application /// entrypoint of the application
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static async Task Main() public static async Task Main(string[] args)
{ {
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory); AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
@ -45,7 +45,7 @@ namespace IW4MAdmin.Application
Console.WriteLine($" Version {Utilities.GetVersionAsString()}"); Console.WriteLine($" Version {Utilities.GetVersionAsString()}");
Console.WriteLine("====================================================="); Console.WriteLine("=====================================================");
await LaunchAsync(); await LaunchAsync(args);
} }
/// <summary> /// <summary>
@ -64,7 +64,7 @@ namespace IW4MAdmin.Application
/// task that initializes application and starts the application monitoring and runtime tasks /// task that initializes application and starts the application monitoring and runtime tasks
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private static async Task LaunchAsync() private static async Task LaunchAsync(string[] args)
{ {
restart: restart:
ITranslationLookup translationLookup = null; ITranslationLookup translationLookup = null;
@ -74,7 +74,7 @@ namespace IW4MAdmin.Application
ConfigurationMigration.MoveConfigFolder10518(null); ConfigurationMigration.MoveConfigFolder10518(null);
ConfigurationMigration.CheckDirectories(); ConfigurationMigration.CheckDirectories();
var services = ConfigureServices(); var services = ConfigureServices(args);
serviceProvider = services.BuildServiceProvider(); serviceProvider = services.BuildServiceProvider();
ServerManager = (ApplicationManager)serviceProvider.GetRequiredService<IManager>(); ServerManager = (ApplicationManager)serviceProvider.GetRequiredService<IManager>();
translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>(); translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
@ -252,7 +252,7 @@ namespace IW4MAdmin.Application
Owner = ServerManager.Servers[0] Owner = ServerManager.Servers[0]
}; };
ServerManager.GetEventHandler().AddEvent(E); ServerManager.AddEvent(E);
await E.WaitAsync(Utilities.DefaultCommandTimeout, ServerManager.CancellationToken); await E.WaitAsync(Utilities.DefaultCommandTimeout, ServerManager.CancellationToken);
Console.Write('>'); Console.Write('>');
} }
@ -266,7 +266,7 @@ namespace IW4MAdmin.Application
/// <summary> /// <summary>
/// Configures the dependency injection services /// Configures the dependency injection services
/// </summary> /// </summary>
private static IServiceCollection ConfigureServices() private static IServiceCollection ConfigureServices(string[] args)
{ {
var defaultLogger = new Logger("IW4MAdmin-Manager"); var defaultLogger = new Logger("IW4MAdmin-Manager");
var pluginImporter = new PluginImporter(defaultLogger); var pluginImporter = new PluginImporter(defaultLogger);
@ -285,6 +285,7 @@ namespace IW4MAdmin.Application
.AddSingleton<IConfigurationHandlerFactory, ConfigurationHandlerFactory>() .AddSingleton<IConfigurationHandlerFactory, ConfigurationHandlerFactory>()
.AddSingleton<IParserRegexFactory, ParserRegexFactory>() .AddSingleton<IParserRegexFactory, ParserRegexFactory>()
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>() .AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>()
.AddSingleton<IGameLogReaderFactory, GameLogReaderFactory>()
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>() .AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>() .AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
.AddSingleton(_serviceProvider => .AddSingleton(_serviceProvider =>
@ -295,6 +296,15 @@ namespace IW4MAdmin.Application
}) })
.AddSingleton<IManager, ApplicationManager>(); .AddSingleton<IManager, ApplicationManager>();
if (args.Contains("serialevents"))
{
serviceCollection.AddSingleton<IEventHandler, SerialGameEventHandler>();
}
else
{
serviceCollection.AddSingleton<IEventHandler, GameEventHandler>();
}
// register the native commands // register the native commands
foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes() foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
.Where(_command => _command.BaseType == typeof(Command))) .Where(_command => _command.BaseType == typeof(Command)))

View File

@ -0,0 +1,27 @@
using Newtonsoft.Json;
using SharedLibraryCore;
using System;
using System.Collections.Generic;
using System.Text;
namespace IW4MAdmin.Application.Misc
{
public class EventLog : Dictionary<long, IList<GameEvent>>
{
private static JsonSerializerSettings serializationSettings;
public static JsonSerializerSettings BuildVcrSerializationSettings()
{
if (serializationSettings == null)
{
serializationSettings = new JsonSerializerSettings() { Formatting = Formatting.Indented, ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
serializationSettings.Converters.Add(new IPAddressConverter());
serializationSettings.Converters.Add(new IPEndPointConverter());
serializationSettings.Converters.Add(new GameEventConverter());
serializationSettings.Converters.Add(new ClientEntityConverter());
}
return serializationSettings;
}
}
}

View File

@ -0,0 +1,149 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using System;
using System.Net;
using static SharedLibraryCore.Database.Models.EFClient;
using static SharedLibraryCore.GameEvent;
namespace IW4MAdmin.Application.Misc
{
class IPAddressConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPAddress));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return IPAddress.Parse((string)reader.Value);
}
}
class IPEndPointConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPEndPoint));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IPEndPoint ep = (IPEndPoint)value;
JObject jo = new JObject();
jo.Add("Address", JToken.FromObject(ep.Address, serializer));
jo.Add("Port", ep.Port);
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
IPAddress address = jo["Address"].ToObject<IPAddress>(serializer);
int port = (int)jo["Port"];
return new IPEndPoint(address, port);
}
}
class ClientEntityConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(EFClient);
public override object ReadJson(JsonReader reader, Type objectType,object existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
{
return null;
}
var jsonObject = JObject.Load(reader);
return new EFClient
{
NetworkId = (long)jsonObject["NetworkId"],
ClientNumber = (int)jsonObject["ClientNumber"],
State = Enum.Parse<ClientState>(jsonObject["state"].ToString()),
CurrentAlias = new EFAlias()
{
IPAddress = (int?)jsonObject["IPAddress"],
Name = jsonObject["Name"].ToString()
}
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var client = value as EFClient;
var jsonObject = new JObject
{
{ "NetworkId", client.NetworkId },
{ "ClientNumber", client.ClientNumber },
{ "IPAddress", client.CurrentAlias?.IPAddress },
{ "Name", client.CurrentAlias?.Name },
{ "State", (int)client.State }
};
jsonObject.WriteTo(writer);
}
}
class GameEventConverter : JsonConverter
{
public override bool CanConvert(Type objectType) =>objectType == typeof(GameEvent);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
return new GameEvent
{
Type = Enum.Parse<EventType>(jsonObject["Type"].ToString()),
Subtype = jsonObject["Subtype"]?.ToString(),
Source = Enum.Parse<EventSource>(jsonObject["Source"].ToString()),
RequiredEntity = Enum.Parse<EventRequiredEntity>(jsonObject["RequiredEntity"].ToString()),
Data = jsonObject["Data"].ToString(),
Message = jsonObject["Message"].ToString(),
GameTime = (int?)jsonObject["GameTime"],
Origin = jsonObject["Origin"]?.ToObject<EFClient>(serializer),
Target = jsonObject["Target"]?.ToObject<EFClient>(serializer),
ImpersonationOrigin = jsonObject["ImpersonationOrigin"]?.ToObject<EFClient>(serializer),
IsRemote = (bool)jsonObject["IsRemote"],
Extra = null, // fix
Time = (DateTime)jsonObject["Time"],
IsBlocking = (bool)jsonObject["IsBlocking"]
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var gameEvent = value as GameEvent;
var jsonObject = new JObject
{
{ "Type", (int)gameEvent.Type },
{ "Subtype", gameEvent.Subtype },
{ "Source", (int)gameEvent.Source },
{ "RequiredEntity", (int)gameEvent.RequiredEntity },
{ "Data", gameEvent.Data },
{ "Message", gameEvent.Message },
{ "GameTime", gameEvent.GameTime },
{ "Origin", gameEvent.Origin != null ? JToken.FromObject(gameEvent.Origin, serializer) : null },
{ "Target", gameEvent.Target != null ? JToken.FromObject(gameEvent.Target, serializer) : null },
{ "ImpersonationOrigin", gameEvent.ImpersonationOrigin != null ? JToken.FromObject(gameEvent.ImpersonationOrigin, serializer) : null},
{ "IsRemote", gameEvent.IsRemote },
{ "Extra", gameEvent.Extra?.ToString() },
{ "Time", gameEvent.Time },
{ "IsBlocking", gameEvent.IsBlocking }
};
jsonObject.WriteTo(writer);
}
}
}

View File

@ -9,6 +9,7 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace IW4MAdmin.Application.RCon namespace IW4MAdmin.Application.RCon
@ -193,7 +194,8 @@ namespace IW4MAdmin.Application.RCon
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString())); throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString()));
} }
string[] headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO ? config.CommandPrefixes.RconGetInfoResponseHeader : config.CommandPrefixes.RConResponse); string responseHeaderMatch = Regex.Match(responseString, config.CommandPrefixes.RConResponse).Value;
string[] headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO ? config.CommandPrefixes.RconGetInfoResponseHeader : responseHeaderMatch);
if (headerSplit.Length != 2) if (headerSplit.Length != 2)
{ {

View File

@ -179,9 +179,15 @@ namespace IW4MAdmin.Application.RconParsers
} }
long networkId; long networkId;
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
try try
{ {
networkId = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]].ConvertGuidToLong(Configuration.GuidNumberStyle); string networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
networkId = networkIdString.IsBotGuid() ?
name.GenerateGuidFromString() :
networkIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
} }
catch (FormatException) catch (FormatException)
@ -189,7 +195,6 @@ namespace IW4MAdmin.Application.RconParsers
continue; continue;
} }
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
int? ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP(); int? ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP();
var client = new EFClient() var client = new EFClient()
@ -206,13 +211,13 @@ namespace IW4MAdmin.Application.RconParsers
State = EFClient.ClientState.Connecting State = EFClient.ClientState.Connecting
}; };
#if DEBUG //#if DEBUG
if (client.NetworkId < 1000 && client.NetworkId > 0) // if (client.NetworkId < 1000 && client.NetworkId > 0)
{ // {
client.IPAddress = 2147483646; // client.IPAddress = 2147483646;
client.Ping = 0; // client.Ping = 0;
} // }
#endif //#endif
StatusPlayers.Add(client); StatusPlayers.Add(client);
} }

View File

@ -0,0 +1,41 @@
using SharedLibraryCore;
using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces;
using System;
using System.Linq;
namespace IW4MAdmin.Application
{
class SerialGameEventHandler : IEventHandler
{
private delegate void GameEventAddedEventHandler(object sender, GameEventArgs args);
private event GameEventAddedEventHandler GameEventAdded;
private static readonly GameEvent.EventType[] overrideEvents = new[]
{
GameEvent.EventType.Connect,
GameEvent.EventType.Disconnect,
GameEvent.EventType.Quit,
GameEvent.EventType.Stop
};
public SerialGameEventHandler()
{
GameEventAdded += GameEventHandler_GameEventAdded;
}
private async void GameEventHandler_GameEventAdded(object sender, GameEventArgs args)
{
await (sender as IManager).ExecuteEvent(args.Event);
EventApi.OnGameEvent(args.Event);
}
public void HandleEvent(IManager manager, GameEvent gameEvent)
{
if (manager.IsRunning || overrideEvents.Contains(gameEvent.Type))
{
GameEventAdded?.Invoke(manager, new GameEventArgs(null, false, gameEvent));
}
}
}
}

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = { var plugin = {
author: 'RaidMax', author: 'RaidMax',
version: 0.2, version: 0.3,
name: 'Plutonium IW5 Parser', name: 'Plutonium IW5 Parser',
isParser: true, isParser: true,
@ -28,7 +28,7 @@ var plugin = {
rconParser.Configuration.CanGenerateLogPath = true; rconParser.Configuration.CanGenerateLogPath = true;
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *'; rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *';
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(?:[0-1]{1}) +([0-9]+) +([A-F0-9]+) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$'; rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(?:[0-1]{1}) +([0-9]+) +([A-F0-9]+|0) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
rconParser.Configuration.Status.AddMapping(100, 1); rconParser.Configuration.Status.AddMapping(100, 1);
rconParser.Configuration.Status.AddMapping(101, 2); rconParser.Configuration.Status.AddMapping(101, 2);
rconParser.Configuration.Status.AddMapping(102, 3); rconParser.Configuration.Status.AddMapping(102, 3);

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = { var plugin = {
author: 'RaidMax, Xerxes', author: 'RaidMax, Xerxes',
version: 0.7, version: 0.8,
name: 'Plutonium T6 Parser', name: 'Plutonium T6 Parser',
isParser: true, isParser: true,
@ -27,7 +27,7 @@ var plugin = {
rconParser.Configuration.WaitForResponse = false; rconParser.Configuration.WaitForResponse = false;
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *'; rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *';
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(?:[0-1]{1}) +([0-9]+) +([A-F0-9]+) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$'; rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(?:[0-1]{1}) +([0-9]+) +([A-F0-9]+|0) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
rconParser.Configuration.Status.AddMapping(100, 1); rconParser.Configuration.Status.AddMapping(100, 1);
rconParser.Configuration.Status.AddMapping(101, 2); rconParser.Configuration.Status.AddMapping(101, 2);
rconParser.Configuration.Status.AddMapping(102, 3); rconParser.Configuration.Status.AddMapping(102, 3);

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = { var plugin = {
author: 'RaidMax', author: 'RaidMax',
version: 0.3, version: 0.4,
name: 'Tekno MW3 Parser', name: 'Tekno MW3 Parser',
isParser: true, isParser: true,
@ -14,11 +14,11 @@ var plugin = {
rconParser = manager.GenerateDynamicRConParser(this.name); rconParser = manager.GenerateDynamicRConParser(this.name);
eventParser = manager.GenerateDynamicEventParser(this.name); eventParser = manager.GenerateDynamicEventParser(this.name);
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[A-Z]|[0-9]){16,32})\t +(.{0,16}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+\\:-?\\d{1,5}|loopback) *$'; rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[A-Z]|[0-9]){16,32}|0)\t +(.{0,16}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+\\:-?\\d{1,5}|loopback) *$';
rconParser.Configuration.Status.AddMapping(104, 5); // RConName rconParser.Configuration.Status.AddMapping(104, 5); // RConName
rconParser.Configuration.Status.AddMapping(103, 4); // RConNetworkId rconParser.Configuration.Status.AddMapping(103, 4); // RConNetworkId
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined; rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff'; rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff(print)?';
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}'; rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
rconParser.Configuration.CommandPrefixes.Say = 'say {0}'; rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
rconParser.Configuration.CommandPrefixes.Kick = 'dropclient {0} "{1}"'; rconParser.Configuration.CommandPrefixes.Kick = 'dropclient {0} "{1}"';

View File

@ -24,6 +24,12 @@ namespace IW4MAdmin.Plugins.Stats.Events
return (EVENT_SCRIPTKILL, EVENT_SCRIPTKILL, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) => return (EVENT_SCRIPTKILL, EVENT_SCRIPTKILL, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
{ {
string[] lineSplit = eventLine.Split(";"); string[] lineSplit = eventLine.Split(";");
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
{
return autoEvent;
}
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1); long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1); long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
@ -48,6 +54,12 @@ namespace IW4MAdmin.Plugins.Stats.Events
return (EVENT_SCRIPTDAMAGE, EVENT_SCRIPTDAMAGE, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) => return (EVENT_SCRIPTDAMAGE, EVENT_SCRIPTDAMAGE, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
{ {
string[] lineSplit = eventLine.Split(";"); string[] lineSplit = eventLine.Split(";");
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
{
return autoEvent;
}
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1); long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1); long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
@ -71,6 +83,12 @@ namespace IW4MAdmin.Plugins.Stats.Events
return (EVENT_JOINTEAM, EVENT_JOINTEAM, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) => return (EVENT_JOINTEAM, EVENT_JOINTEAM, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
{ {
string[] lineSplit = eventLine.Split(";"); string[] lineSplit = eventLine.Split(";");
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
{
return autoEvent;
}
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1); long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1); long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);

View File

@ -530,7 +530,7 @@ namespace IW4MAdmin.Plugins.Stats
/// </summary> /// </summary>
/// <param name="origin"></param> /// <param name="origin"></param>
/// <returns></returns> /// <returns></returns>
private bool IsWorldDamage(EFClient origin) => origin?.NetworkId == 1; private bool IsWorldDamage(EFClient origin) => origin?.NetworkId == Utilities.WORLD_ID || origin?.ClientId == Utilities.WORLD_ID;
/// <summary> /// <summary>
/// Indicates if we should try to use anticheat even if sv_customcallbacks is not defined /// Indicates if we should try to use anticheat even if sv_customcallbacks is not defined

View File

@ -16,7 +16,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.11" PrivateAssets="All" /> <PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.12" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -72,7 +72,7 @@ namespace Tests
} }
}; };
_manager.GetEventHandler().AddEvent(e); _manager.AddEvent(e);
e.Complete(); e.Complete();
e = new GameEvent() e = new GameEvent()
@ -91,7 +91,7 @@ namespace Tests
} }
}; };
_manager.GetEventHandler().AddEvent(e); _manager.AddEvent(e);
e.Complete(); e.Complete();
e = new GameEvent() e = new GameEvent()
@ -110,7 +110,7 @@ namespace Tests
} }
}; };
_manager.GetEventHandler().AddEvent(e); _manager.AddEvent(e);
e.Complete(); e.Complete();
} }

View File

@ -30,7 +30,7 @@ namespace Tests
Owner = Manager.GetServers()[0] Owner = Manager.GetServers()[0]
}; };
Manager.GetEventHandler().AddEvent(e); Manager.AddEvent(e);
e.Complete(); e.Complete();
var client = Manager.GetServers()[0].Clients[0]; var client = Manager.GetServers()[0].Clients[0];
@ -43,7 +43,7 @@ namespace Tests
Owner = e.Owner Owner = e.Owner
}; };
Manager.GetEventHandler().AddEvent(e); Manager.AddEvent(e);
e.Complete(); e.Complete();
Assert.True(client.Warnings == 1, "client wasn't warned for objectional language"); Assert.True(client.Warnings == 1, "client wasn't warned for objectional language");

View File

@ -49,7 +49,7 @@ namespace SharedLibraryCore.Commands
Data = cmd, Data = cmd,
Owner = E.Owner Owner = E.Owner
}; };
E.Owner.Manager.GetEventHandler().AddEvent(impersonatedCommandEvent); E.Owner.Manager.AddEvent(impersonatedCommandEvent);
var result = await impersonatedCommandEvent.WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken); var result = await impersonatedCommandEvent.WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken);
var response = E.Owner.CommandResult.Where(c => c.ClientId == E.Target.ClientId).ToList(); var response = E.Owner.CommandResult.Where(c => c.ClientId == E.Target.ClientId).ToList();

View File

@ -23,9 +23,9 @@ namespace SharedLibraryCore.Events
return eventList; return eventList;
} }
public static void OnGameEvent(object sender, GameEventArgs eventState) public static void OnGameEvent(GameEvent gameEvent)
{ {
var E = eventState.Event; var E = gameEvent;
// don't want to clog up the api with unknown events // don't want to clog up the api with unknown events
if (E.Type == GameEvent.EventType.Unknown) if (E.Type == GameEvent.EventType.Unknown)
return; return;

View File

@ -194,6 +194,14 @@ namespace SharedLibraryCore
Target = 4 Target = 4
} }
public enum EventSource
{
Unspecified,
Log,
Status,
Internal
}
static long NextEventId; static long NextEventId;
static long GetNextEventId() static long GetNextEventId()
{ {
@ -214,6 +222,7 @@ namespace SharedLibraryCore
} }
public EventType Type; public EventType Type;
public EventSource Source { get; set; }
/// <summary> /// <summary>
/// suptype of the event for more detailed classification /// suptype of the event for more detailed classification
/// </summary> /// </summary>
@ -229,7 +238,7 @@ namespace SharedLibraryCore
public EFClient Target; public EFClient Target;
public EFClient ImpersonationOrigin { get; set; } public EFClient ImpersonationOrigin { get; set; }
public Server Owner; public Server Owner;
public bool IsRemote { get; set; } = false; public bool IsRemote { get; set; }
public object Extra { get; set; } public object Extra { get; set; }
private readonly ManualResetEvent _eventFinishedWaiter; private readonly ManualResetEvent _eventFinishedWaiter;
public DateTime Time { get; set; } public DateTime Time { get; set; }

View File

@ -1,18 +1,15 @@
using System; namespace SharedLibraryCore.Interfaces
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Interfaces
{ {
/// <summary> /// <summary>
/// This class handle games events (from log, manual events, etc) /// handles games events (from log, manual events, etc)
/// </summary> /// </summary>
public interface IEventHandler public interface IEventHandler
{ {
/// <summary> /// <summary>
/// Add a game event event to the queue to be processed /// Add a game event event to the queue to be processed
/// </summary> /// </summary>
/// <param name="gameEvent">Game event</param> /// <param name="manager">application manager instance</param>
void AddEvent(GameEvent gameEvent); /// <param name="gameEvent">game event</param>
void HandleEvent(IManager manager, GameEvent gameEvent);
} }
} }

View File

@ -4,18 +4,17 @@ using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces namespace SharedLibraryCore.Interfaces
{ {
/// <summary> /// <summary>
/// represents the abtraction of game log reading /// represents the abstraction of game log reading
/// </summary> /// </summary>
public interface IGameLogReader public interface IGameLogReader
{ {
/// <summary> /// <summary>
/// get new events that have occured since the last poll /// get new events that have occured since the last poll
/// </summary> /// </summary>
/// <param name="server"></param>
/// <param name="fileSizeDiff"></param> /// <param name="fileSizeDiff"></param>
/// <param name="startPosition"></param> /// <param name="startPosition"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition); Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition);
/// <summary> /// <summary>
/// how long the log file is /// how long the log file is

View File

@ -0,0 +1,18 @@
using System;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// factory interface to create game log readers based on the log file uri
/// </summary>
public interface IGameLogReaderFactory
{
/// <summary>
/// generates a new game log reader based on the provided Uri
/// </summary>
/// <param name="logUris">collection of log uri used to generate the log reader</param>
/// <param name="eventParser">event parser for the log reader</param>
/// <returns></returns>
IGameLogReader CreateGameLogReader(Uri[] logUris, IEventParser eventParser);
}
}

View File

@ -24,11 +24,6 @@ namespace SharedLibraryCore.Interfaces
AliasService GetAliasService(); AliasService GetAliasService();
PenaltyService GetPenaltyService(); PenaltyService GetPenaltyService();
/// <summary> /// <summary>
/// Get the event handlers
/// </summary>
/// <returns>EventHandler for the manager</returns>
IEventHandler GetEventHandler();
/// <summary>
/// enumerates the registered plugin instances /// enumerates the registered plugin instances
/// </summary> /// </summary>
IEnumerable<IPlugin> Plugins { get; } IEnumerable<IPlugin> Plugins { get; }
@ -68,7 +63,12 @@ namespace SharedLibraryCore.Interfaces
string ExternalIPAddress { get; } string ExternalIPAddress { get; }
CancellationToken CancellationToken { get; } CancellationToken CancellationToken { get; }
bool IsRestartRequested { get; } bool IsRestartRequested { get; }
//OnServerEventEventHandler OnServerEvent { get; set; } bool IsRunning { get; }
Task ExecuteEvent(GameEvent gameEvent); Task ExecuteEvent(GameEvent gameEvent);
/// <summary>
/// queues an event for processing
/// </summary>
/// <param name="gameEvent">event to be processed</param>
void AddEvent(GameEvent gameEvent);
} }
} }

View File

@ -142,7 +142,7 @@ namespace SharedLibraryCore.Database.Models
Data = message Data = message
}; };
CurrentServer?.Manager.GetEventHandler().AddEvent(e); CurrentServer?.Manager.AddEvent(e);
return e; return e;
} }
@ -174,7 +174,7 @@ namespace SharedLibraryCore.Database.Models
Warnings++; Warnings++;
} }
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.AddEvent(e);
return e; return e;
} }
@ -202,7 +202,7 @@ namespace SharedLibraryCore.Database.Models
Warnings = 0; Warnings = 0;
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.AddEvent(e);
return e; return e;
} }
@ -243,7 +243,7 @@ namespace SharedLibraryCore.Database.Models
} }
sender.SetAdditionalProperty("_reportCount", reportCount + 1); sender.SetAdditionalProperty("_reportCount", reportCount + 1);
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.AddEvent(e);
return e; return e;
} }
@ -276,7 +276,7 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Invalid; e.FailReason = GameEvent.EventFailReason.Invalid;
} }
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.AddEvent(e);
return e; return e;
} }
@ -308,7 +308,7 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Invalid; e.FailReason = GameEvent.EventFailReason.Invalid;
} }
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.AddEvent(e);
return e; return e;
} }
@ -336,7 +336,7 @@ namespace SharedLibraryCore.Database.Models
} }
State = ClientState.Disconnecting; State = ClientState.Disconnecting;
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.AddEvent(e);
return e; return e;
} }
@ -366,7 +366,7 @@ namespace SharedLibraryCore.Database.Models
} }
State = ClientState.Disconnecting; State = ClientState.Disconnecting;
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.AddEvent(e);
return e; return e;
} }
@ -400,7 +400,7 @@ namespace SharedLibraryCore.Database.Models
} }
State = ClientState.Disconnecting; State = ClientState.Disconnecting;
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.AddEvent(e);
return e; return e;
} }
@ -428,7 +428,7 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Permission; e.FailReason = GameEvent.EventFailReason.Permission;
} }
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.AddEvent(e);
return e; return e;
} }
@ -464,7 +464,7 @@ namespace SharedLibraryCore.Database.Models
Level = newPermission; Level = newPermission;
} }
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.AddEvent(e);
return e; return e;
} }
@ -565,7 +565,7 @@ namespace SharedLibraryCore.Database.Models
Owner = CurrentServer, Owner = CurrentServer,
}; };
CurrentServer.Manager.GetEventHandler().AddEvent(e); CurrentServer.Manager.AddEvent(e);
} }
} }
@ -655,7 +655,7 @@ namespace SharedLibraryCore.Database.Models
[NotMapped] [NotMapped]
public int Score { get; set; } public int Score { get; set; }
[NotMapped] [NotMapped]
public bool IsBot => NetworkId == -1; public bool IsBot => NetworkId == Name.GenerateGuidFromString();
[NotMapped] [NotMapped]
public ClientState State { get; set; } public ClientState State { get; set; }
@ -694,7 +694,7 @@ namespace SharedLibraryCore.Database.Models
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return ((EFClient)obj).NetworkId == this.NetworkId; return obj.GetType() == typeof(EFClient) && ((EFClient)obj).NetworkId == this.NetworkId;
} }
public override int GetHashCode() public override int GetHashCode()

View File

@ -28,7 +28,7 @@ namespace SharedLibraryCore
T7 = 8 T7 = 8
} }
public Server(IManager mgr, IRConConnectionFactory rconConnectionFactory, ServerConfiguration config) public Server(ServerConfiguration config, IManager mgr, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory)
{ {
Password = config.Password; Password = config.Password;
IP = config.IPAddress; IP = config.IPAddress;
@ -46,6 +46,7 @@ namespace SharedLibraryCore
NextMessage = 0; NextMessage = 0;
CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName; CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName;
CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName; CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName;
this.gameLogReaderFactory = gameLogReaderFactory;
InitializeTokens(); InitializeTokens();
InitializeAutoMessages(); InitializeAutoMessages();
} }
@ -134,7 +135,7 @@ namespace SharedLibraryCore
Origin = sender, Origin = sender,
}; };
Manager.GetEventHandler().AddEvent(e); Manager.AddEvent(e);
return e; return e;
} }
@ -296,7 +297,7 @@ namespace SharedLibraryCore
{ {
get get
{ {
return Clients.Where(p => p != null && !p.IsBot).Count(); return Clients.Where(p => p != null/* && !p.IsBot*/).Count();
} }
} }
public int MaxClients { get; protected set; } public int MaxClients { get; protected set; }
@ -325,6 +326,7 @@ namespace SharedLibraryCore
protected TimeSpan LastMessage; protected TimeSpan LastMessage;
protected DateTime LastPoll; protected DateTime LastPoll;
protected ManualResetEventSlim OnRemoteCommandResponse; protected ManualResetEventSlim OnRemoteCommandResponse;
protected IGameLogReaderFactory gameLogReaderFactory;
// only here for performance // only here for performance
private readonly bool CustomSayEnabled; private readonly bool CustomSayEnabled;

View File

@ -6,7 +6,7 @@
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId> <PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2.2.11</Version> <Version>2.2.12</Version>
<Authors>RaidMax</Authors> <Authors>RaidMax</Authors>
<Company>Forever None</Company> <Company>Forever None</Company>
<Configurations>Debug;Release;Prerelease</Configurations> <Configurations>Debug;Release;Prerelease</Configurations>
@ -20,8 +20,8 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>Shared Library for IW4MAdmin</Description> <Description>Shared Library for IW4MAdmin</Description>
<AssemblyVersion>2.2.11.0</AssemblyVersion> <AssemblyVersion>2.2.12.0</AssemblyVersion>
<FileVersion>2.2.11.0</FileVersion> <FileVersion>2.2.12.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">

View File

@ -16,6 +16,7 @@ using System.Net;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient; using static SharedLibraryCore.Database.Models.EFClient;
using static SharedLibraryCore.Database.Models.EFPenalty; using static SharedLibraryCore.Database.Models.EFPenalty;
@ -50,6 +51,10 @@ namespace SharedLibraryCore
AdministeredPenalties = new List<EFPenalty>() AdministeredPenalties = new List<EFPenalty>()
}; };
} }
/// <summary>
/// fallback id for world events
/// </summary>
public const long WORLD_ID = -1;
public static string HttpRequest(string location, string header, string headerValue) public static string HttpRequest(string location, string header, string headerValue)
{ {
@ -295,39 +300,46 @@ namespace SharedLibraryCore
} }
} }
/// <summary>
/// converts a string to numerical guid
/// </summary>
/// <param name="str">source string for guid</param>
/// <param name="numberStyle">how to parse the guid</param>
/// <param name="fallback">value to use if string is empty</param>
/// <returns></returns>
public static long ConvertGuidToLong(this string str, NumberStyles numberStyle, long? fallback = null) public static long ConvertGuidToLong(this string str, NumberStyles numberStyle, long? fallback = null)
{ {
str = str.Substring(0, Math.Min(str.Length, 19)); str = str.Substring(0, Math.Min(str.Length, 19));
var bot = Regex.Match(str, @"bot[0-9]+").Value; var parsableAsNumber = Regex.Match(str, @"([A-F]|[a-f]|[0-9])+").Value;
if (string.IsNullOrWhiteSpace(str) && fallback.HasValue) if (string.IsNullOrWhiteSpace(str) && fallback.HasValue)
{ {
return fallback.Value; return fallback.Value;
} }
long id = 0; long id;
if (!string.IsNullOrEmpty(parsableAsNumber))
if (numberStyle == NumberStyles.Integer)
{ {
long.TryParse(str, numberStyle, CultureInfo.InvariantCulture, out id); if (numberStyle == NumberStyles.Integer)
if (id < 0)
{ {
id = (uint)id; long.TryParse(str, numberStyle, CultureInfo.InvariantCulture, out id);
if (id < 0)
{
id = (uint)id;
}
}
else
{
long.TryParse(str.Length > 16 ? str.Substring(0, 16) : str, numberStyle, CultureInfo.InvariantCulture, out id);
} }
} }
else else
{ {
long.TryParse(str.Length > 16 ? str.Substring(0, 16) : str, numberStyle, CultureInfo.InvariantCulture, out id); // this is a special case for when a real guid is not provided, so we generated it from another source
} id = str.GenerateGuidFromString();
if (!string.IsNullOrEmpty(bot))
{
id = -1;
#if DEBUG
id = str.Sum(_c => _c);
#endif
} }
if (id == 0) if (id == 0)
@ -338,6 +350,23 @@ namespace SharedLibraryCore
return id; return id;
} }
/// <summary>
/// determines if the guid provided appears to be a bot guid
/// </summary>
/// <param name="guid">value of the guid</param>
/// <returns>true if is bot guid, otherwise false</returns>
public static bool IsBotGuid(this string guid)
{
return guid.Contains("bot") || guid == "0";
}
/// <summary>
/// generates a numerical hashcode from a string value
/// </summary>
/// <param name="value">value string</param>
/// <returns></returns>
public static long GenerateGuidFromString(this string value) => string.IsNullOrEmpty(value) ? -1 : HashCode.Combine(value.StripColors());
public static int? ConvertToIP(this string str) public static int? ConvertToIP(this string str)
{ {
bool success = IPAddress.TryParse(str, out IPAddress ip); bool success = IPAddress.TryParse(str, out IPAddress ip);
@ -900,6 +929,24 @@ namespace SharedLibraryCore
return false; return false;
} }
/// <summary>
/// https://www.planetgeek.ch/2016/12/08/async-method-without-cancellation-support-do-it-my-way/
/// </summary>
public static async Task WithWaitCancellation(this Task task,
CancellationToken cancellationToken)
{
Task completedTask = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, cancellationToken));
if (completedTask == task)
{
await task;
}
else
{
cancellationToken.ThrowIfCancellationRequested();
throw new InvalidOperationException("Infinite delay task completed.");
}
}
public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged; public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged;
/// <summary> /// <summary>

View File

@ -23,6 +23,9 @@
<None Update="Files\GameEvents.json"> <None Update="Files\GameEvents.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="Files\replay.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\T6Game.log"> <None Update="Files\T6Game.log">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

View File

@ -37,12 +37,10 @@ namespace ApplicationTests
cmdConfig = new CommandConfiguration(); cmdConfig = new CommandConfiguration();
serviceProvider = new ServiceCollection() serviceProvider = new ServiceCollection()
.BuildBase() .BuildBase(new MockEventHandler(true))
.BuildServiceProvider(); .BuildServiceProvider();
mockEventHandler = new MockEventHandler(true); mockEventHandler = serviceProvider.GetRequiredService<MockEventHandler>();
A.CallTo(() => serviceProvider.GetRequiredService<IManager>().GetEventHandler())
.Returns(mockEventHandler);
var mgr = serviceProvider.GetRequiredService<IManager>(); var mgr = serviceProvider.GetRequiredService<IManager>();
transLookup = serviceProvider.GetRequiredService<ITranslationLookup>(); transLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
@ -54,7 +52,9 @@ namespace ApplicationTests
new NonImpersonatableCommand(cmdConfig, transLookup) new NonImpersonatableCommand(cmdConfig, transLookup)
}); });
//Utilities.DefaultCommandTimeout = new TimeSpan(0, 0, 2); A.CallTo(() => mgr.AddEvent(A<GameEvent>.Ignored))
.Invokes((fakeCall) => mockEventHandler.HandleEvent(mgr, fakeCall.Arguments[0] as GameEvent));
} }
#region RUNAS #region RUNAS

View File

@ -9,10 +9,22 @@ using SharedLibraryCore.Services;
namespace ApplicationTests namespace ApplicationTests
{ {
static class DepedencyInjectionExtensions static class DependencyInjectionExtensions
{ {
public static IServiceCollection BuildBase(this IServiceCollection serviceCollection) public static IServiceCollection BuildBase(this IServiceCollection serviceCollection, IEventHandler eventHandler = null)
{ {
if (eventHandler == null)
{
eventHandler = new MockEventHandler();
serviceCollection.AddSingleton(eventHandler as MockEventHandler);
}
else if (eventHandler is MockEventHandler mockEventHandler)
{
serviceCollection.AddSingleton(mockEventHandler);
}
var manager = A.Fake<IManager>(); var manager = A.Fake<IManager>();
var logger = A.Fake<ILogger>(); var logger = A.Fake<ILogger>();
@ -27,10 +39,13 @@ namespace ApplicationTests
.AddSingleton(A.Fake<ITranslationLookup>()) .AddSingleton(A.Fake<ITranslationLookup>())
.AddSingleton(A.Fake<IRConParser>()) .AddSingleton(A.Fake<IRConParser>())
.AddSingleton(A.Fake<IParserRegexFactory>()) .AddSingleton(A.Fake<IParserRegexFactory>())
.AddSingleton(A.Fake<ClientService>()); .AddSingleton<DataFileLoader>()
.AddSingleton(A.Fake<ClientService>())
.AddSingleton(A.Fake<IGameLogReaderFactory>())
.AddSingleton(eventHandler);
serviceCollection.AddSingleton(_sp => new IW4MServer(_sp.GetRequiredService<IManager>(), ConfigurationGenerators.CreateServerConfiguration(), serviceCollection.AddSingleton(_sp => new IW4MServer(_sp.GetRequiredService<IManager>(), ConfigurationGenerators.CreateServerConfiguration(),
_sp.GetRequiredService<ITranslationLookup>(), _sp.GetRequiredService<IRConConnectionFactory>()) _sp.GetRequiredService<ITranslationLookup>(), _sp.GetRequiredService<IRConConnectionFactory>(), _sp.GetRequiredService<IGameLogReaderFactory>())
{ {
RconParser = _sp.GetRequiredService<IRConParser>() RconParser = _sp.GetRequiredService<IRConParser>()
}); });

View File

@ -0,0 +1,16 @@
using IW4MAdmin.Application.Misc;
using Newtonsoft.Json;
using System.IO;
using System.Threading.Tasks;
namespace ApplicationTests.Fixtures
{
class DataFileLoader
{
public async Task<T> Load<T>(string fileName)
{
string data = await File.ReadAllTextAsync($"{fileName}.json");
return JsonConvert.DeserializeObject<T>(data, EventLog.BuildVcrSerializationSettings());
}
}
}

View File

@ -0,0 +1,17 @@
using IW4MAdmin;
using IW4MAdmin.Application;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using SharedLibraryCore;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace ApplicationTests
{
[TestFixture]
public class GameEventHandlerTests
{
}
}

View File

@ -1,5 +1,7 @@
using FakeItEasy; using FakeItEasy;
using IW4MAdmin;
using IW4MAdmin.Application.IO; using IW4MAdmin.Application.IO;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework; using NUnit.Framework;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
@ -12,11 +14,24 @@ namespace ApplicationTests
public class IOTests public class IOTests
{ {
private IServiceProvider serviceProvider;
[SetUp]
public void Setup()
{
serviceProvider = new ServiceCollection().BuildBase().BuildServiceProvider();
}
[Test] [Test]
public async Task GameLogEventDetection_WorksAfterFileSizeReset() public async Task GameLogEventDetection_WorksAfterFileSizeReset()
{ {
var reader = A.Fake<IGameLogReader>(); var reader = A.Fake<IGameLogReader>();
var detect = new GameLogEventDetection(null, "", A.Fake<Uri>(), reader); var factory = A.Fake<IGameLogReaderFactory>();
A.CallTo(() => factory.CreateGameLogReader(A<Uri[]>.Ignored, A<IEventParser>.Ignored))
.Returns(reader);
var detect = new GameLogEventDetection(serviceProvider.GetService<IW4MServer>(), new Uri[] { new Uri("C:\\test.log") }, factory);
A.CallTo(() => reader.Length) A.CallTo(() => reader.Length)
.Returns(100) .Returns(100)
@ -35,7 +50,7 @@ namespace ApplicationTests
await detect.UpdateLogEvents(); await detect.UpdateLogEvents();
} }
A.CallTo(() => reader.ReadEventsFromLog(A<Server>.Ignored, A<long>.Ignored, A<long>.Ignored)) A.CallTo(() => reader.ReadEventsFromLog(A<long>.Ignored, A<long>.Ignored))
.MustHaveHappenedTwiceExactly(); .MustHaveHappenedTwiceExactly();
} }
} }

View File

@ -36,6 +36,7 @@ namespace ApplicationTests
fakeManager = serviceProvider.GetRequiredService<IManager>(); fakeManager = serviceProvider.GetRequiredService<IManager>();
fakeRConConnection = serviceProvider.GetRequiredService<IRConConnection>(); fakeRConConnection = serviceProvider.GetRequiredService<IRConConnection>();
fakeRConParser = serviceProvider.GetRequiredService<IRConParser>(); fakeRConParser = serviceProvider.GetRequiredService<IRConParser>();
mockEventHandler = serviceProvider.GetRequiredService<MockEventHandler>();
var rconConnectionFactory = serviceProvider.GetRequiredService<IRConConnectionFactory>(); var rconConnectionFactory = serviceProvider.GetRequiredService<IRConConnectionFactory>();
@ -45,10 +46,9 @@ namespace ApplicationTests
A.CallTo(() => fakeRConParser.Configuration) A.CallTo(() => fakeRConParser.Configuration)
.Returns(ConfigurationGenerators.CreateRConParserConfiguration(serviceProvider.GetRequiredService<IParserRegexFactory>())); .Returns(ConfigurationGenerators.CreateRConParserConfiguration(serviceProvider.GetRequiredService<IParserRegexFactory>()));
A.CallTo(() => fakeManager.AddEvent(A<GameEvent>.Ignored))
mockEventHandler = new MockEventHandler(); .Invokes((fakeCall) => mockEventHandler.HandleEvent(fakeManager, fakeCall.Arguments[0] as GameEvent));
A.CallTo(() => fakeManager.GetEventHandler())
.Returns(mockEventHandler);
} }
#region LOG #region LOG

View File

@ -14,7 +14,7 @@ namespace ApplicationTests.Mocks
_autoExecute = autoExecute; _autoExecute = autoExecute;
} }
public void AddEvent(GameEvent gameEvent) public void HandleEvent(IManager manager, GameEvent gameEvent)
{ {
Events.Add(gameEvent); Events.Add(gameEvent);

View File

@ -0,0 +1,21 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace ApplicationTests.Mocks
{
class VcrEventReader : IGameLogReader
{
public long Length => throw new NotImplementedException();
public int UpdateInterval => throw new NotImplementedException();
public Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition)
{
throw new NotImplementedException();
}
}
}

View File

@ -31,9 +31,7 @@ namespace ApplicationTests
{ {
serviceProvider = new ServiceCollection().BuildBase().BuildServiceProvider(); serviceProvider = new ServiceCollection().BuildBase().BuildServiceProvider();
fakeManager = serviceProvider.GetRequiredService<IManager>(); fakeManager = serviceProvider.GetRequiredService<IManager>();
mockEventHandler = new MockEventHandler(); mockEventHandler = serviceProvider.GetRequiredService<MockEventHandler>();
A.CallTo(() => fakeManager.GetEventHandler())
.Returns(mockEventHandler);
var rconConnectionFactory = serviceProvider.GetRequiredService<IRConConnectionFactory>(); var rconConnectionFactory = serviceProvider.GetRequiredService<IRConConnectionFactory>();
@ -42,6 +40,9 @@ namespace ApplicationTests
A.CallTo(() => serviceProvider.GetRequiredService<IRConParser>().Configuration) A.CallTo(() => serviceProvider.GetRequiredService<IRConParser>().Configuration)
.Returns(ConfigurationGenerators.CreateRConParserConfiguration(serviceProvider.GetRequiredService<IParserRegexFactory>())); .Returns(ConfigurationGenerators.CreateRConParserConfiguration(serviceProvider.GetRequiredService<IParserRegexFactory>()));
A.CallTo(() => fakeManager.AddEvent(A<GameEvent>.Ignored))
.Invokes((fakeCall) => mockEventHandler.HandleEvent(fakeManager, fakeCall.Arguments[0] as GameEvent));
} }
[Test] [Test]

View File

@ -33,7 +33,7 @@ namespace ApplicationTests
var mgr = A.Fake<IManager>(); var mgr = A.Fake<IManager>();
var server = new IW4MServer(mgr, var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 }, new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>()); A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>());
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>()); var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
@ -59,7 +59,7 @@ namespace ApplicationTests
var server = new IW4MServer(mgr, var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 }, new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>()); A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>());
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>()); var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;

View File

@ -63,7 +63,7 @@ namespace ApplicationTests
var server = new IW4MServer(mgr, var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 }, new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(), A.Fake<ITranslationLookup>(),
A.Fake<IRConConnectionFactory>()); A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>());
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>()); var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;

View File

@ -0,0 +1,43 @@
using ApplicationTests.Fixtures;
using IW4MAdmin;
using IW4MAdmin.Application;
using IW4MAdmin.Application.Misc;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ApplicationTests
{
[TestFixture]
public class VcrTests
{
private IServiceProvider serviceProvider;
[SetUp]
public void Setup()
{
serviceProvider = new ServiceCollection().BuildBase()
.BuildServiceProvider();
}
[Test]
[TestCase("replay")]
public async Task ReplayEvents(string source)
{
var sourceData = await serviceProvider
.GetRequiredService<DataFileLoader>()
.Load<EventLog>(source);
var server = serviceProvider.GetRequiredService<IW4MServer>();
foreach (var gameEvent in sourceData.Values.First())
{
await server.ExecuteEvent(gameEvent);
}
}
}
}

View File

@ -56,7 +56,7 @@ namespace WebfrontCore.Controllers
IsRemote = true IsRemote = true
}; };
Manager.GetEventHandler().AddEvent(remoteEvent); Manager.AddEvent(remoteEvent);
List<CommandResponseInfo> response = null; List<CommandResponseInfo> response = null;
try try