diff --git a/.gitignore b/.gitignore
index 8f7e6f1cc..8b804f4d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -242,3 +242,4 @@ launchSettings.json
/WebfrontCore/wwwroot/font
/Plugins/Tests/TestSourceFiles
/Tests/ApplicationTests/Files/GameEvents.json
+/Tests/ApplicationTests/Files/replay.json
diff --git a/Application/Application.csproj b/Application/Application.csproj
index 4f37f0940..1951099e9 100644
--- a/Application/Application.csproj
+++ b/Application/Application.csproj
@@ -10,7 +10,7 @@
Forever None
IW4MAdmin
IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated servers
- 2019
+ 2020
https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE
https://raidmax.org/IW4MAdmin
https://github.com/RaidMax/IW4M-Admin
diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs
index 9729dc426..6ace023c0 100644
--- a/Application/ApplicationManager.cs
+++ b/Application/ApplicationManager.cs
@@ -31,7 +31,7 @@ namespace IW4MAdmin.Application
private readonly ConcurrentBag _servers;
public List Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
public ILogger Logger => GetLogger(0);
- public bool Running { get; private set; }
+ public bool IsRunning { get; private set; }
public bool IsInitialized { get; private set; }
public DateTime StartTime { get; private set; }
public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString();
@@ -50,7 +50,6 @@ namespace IW4MAdmin.Application
readonly AliasService AliasSvc;
readonly PenaltyService PenaltySvc;
public IConfigurationHandler ConfigHandler;
- GameEventHandler Handler;
readonly IPageList PageList;
private readonly Dictionary _loggers = new Dictionary();
private readonly MetaService _metaService;
@@ -62,11 +61,13 @@ namespace IW4MAdmin.Application
private readonly IGameServerInstanceFactory _serverInstanceFactory;
private readonly IParserRegexFactory _parserRegexFactory;
private readonly IEnumerable _customParserEvents;
+ private readonly IEventHandler _eventHandler;
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable commands,
ITranslationLookup translationLookup, IConfigurationHandler commandConfiguration,
IConfigurationHandler appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
- IEnumerable plugins, IParserRegexFactory parserRegexFactory, IEnumerable customParserEvents)
+ IEnumerable plugins, IParserRegexFactory parserRegexFactory, IEnumerable customParserEvents,
+ IEventHandler eventHandler)
{
MiddlewareActionHandler = actionHandler;
_servers = new ConcurrentBag();
@@ -90,6 +91,7 @@ namespace IW4MAdmin.Application
_serverInstanceFactory = serverInstanceFactory;
_parserRegexFactory = parserRegexFactory;
_customParserEvents = customParserEvents;
+ _eventHandler = eventHandler;
Plugins = plugins;
}
@@ -255,7 +257,7 @@ namespace IW4MAdmin.Application
public async Task Init()
{
- Running = true;
+ IsRunning = true;
ExternalIPAddress = await Utilities.GetExternalIP();
#region PLUGINS
@@ -589,9 +591,6 @@ namespace IW4MAdmin.Application
async Task Init(ServerConfiguration Conf)
{
- // setup the event handler after the class is initialized
- Handler = new GameEventHandler(this);
-
try
{
// todo: this might not always be an IW4MServer
@@ -610,7 +609,7 @@ namespace IW4MAdmin.Application
Owner = ServerInstance
};
- Handler.AddEvent(e);
+ AddEvent(e);
successServers++;
}
@@ -726,7 +725,7 @@ namespace IW4MAdmin.Application
public void Stop()
{
_tokenSource.Cancel();
- Running = false;
+ IsRunning = false;
}
public void Restart()
@@ -782,9 +781,9 @@ namespace IW4MAdmin.Application
return ConfigHandler;
}
- public IEventHandler GetEventHandler()
+ public void AddEvent(GameEvent gameEvent)
{
- return Handler;
+ _eventHandler.HandleEvent(this, gameEvent);
}
public IPageList GetPageList()
diff --git a/Application/EventParsers/BaseEventParser.cs b/Application/EventParsers/BaseEventParser.cs
index 7550a3113..d07ddd5bb 100644
--- a/Application/EventParsers/BaseEventParser.cs
+++ b/Application/EventParsers/BaseEventParser.cs
@@ -23,26 +23,26 @@ namespace IW4MAdmin.Application.EventParsers
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.OriginNetworkId, 2);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginName, 4);
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.OriginNetworkId, 2);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
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.OriginNetworkId, 2);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
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.TargetNetworkId, 2);
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.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.TargetNetworkId, 2);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
@@ -118,7 +118,13 @@ namespace IW4MAdmin.Application.EventParsers
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]]);
// todo: these need to defined outside of here
@@ -132,7 +138,8 @@ namespace IW4MAdmin.Application.EventParsers
Message = message,
Extra = logLine,
RequiredEntity = GameEvent.EventRequiredEntity.Origin,
- GameTime = gameTime
+ GameTime = gameTime,
+ Source = GameEvent.EventSource.Log
};
}
@@ -144,7 +151,8 @@ namespace IW4MAdmin.Application.EventParsers
Message = message,
Extra = logLine,
RequiredEntity = GameEvent.EventRequiredEntity.Origin,
- GameTime = gameTime
+ GameTime = gameTime,
+ Source = GameEvent.EventSource.Log
};
}
}
@@ -156,8 +164,18 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success)
{
- long originId = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
- long targetId = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
+ string originIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
+ 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 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 },
Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber },
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)
{
- long originId = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
- long targetId = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
+ string originIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
+ 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 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 },
Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber },
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)
{
+ 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()
{
Type = GameEvent.EventType.PreConnect,
@@ -212,13 +249,14 @@ namespace IW4MAdmin.Application.EventParsers
{
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()),
State = EFClient.ClientState.Connecting,
},
RequiredEntity = GameEvent.EventRequiredEntity.None,
IsBlocking = true,
- GameTime = gameTime
+ GameTime = gameTime,
+ Source = GameEvent.EventSource.Log
};
}
}
@@ -229,6 +267,13 @@ namespace IW4MAdmin.Application.EventParsers
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()
{
Type = GameEvent.EventType.PreDisconnect,
@@ -239,13 +284,14 @@ namespace IW4MAdmin.Application.EventParsers
{
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()),
State = EFClient.ClientState.Disconnecting
},
RequiredEntity = GameEvent.EventRequiredEntity.None,
IsBlocking = true,
- GameTime = gameTime
+ GameTime = gameTime,
+ Source = GameEvent.EventSource.Log
};
}
}
@@ -259,7 +305,8 @@ namespace IW4MAdmin.Application.EventParsers
Origin = Utilities.IW4MAdminClient(),
Target = Utilities.IW4MAdminClient(),
RequiredEntity = GameEvent.EventRequiredEntity.None,
- GameTime = gameTime
+ GameTime = gameTime,
+ Source = GameEvent.EventSource.Log
};
}
@@ -275,7 +322,8 @@ namespace IW4MAdmin.Application.EventParsers
Target = Utilities.IW4MAdminClient(),
Extra = dump.DictionaryFromKeyValue(),
RequiredEntity = GameEvent.EventRequiredEntity.None,
- GameTime = gameTime
+ GameTime = gameTime,
+ Source = GameEvent.EventSource.Log
};
}
@@ -290,7 +338,8 @@ namespace IW4MAdmin.Application.EventParsers
Type = GameEvent.EventType.Other,
Data = logLine,
Subtype = eventModifier.Item1,
- GameTime = gameTime
+ GameTime = gameTime,
+ Source = GameEvent.EventSource.Log
});
}
@@ -307,7 +356,8 @@ namespace IW4MAdmin.Application.EventParsers
Origin = Utilities.IW4MAdminClient(),
Target = Utilities.IW4MAdminClient(),
RequiredEntity = GameEvent.EventRequiredEntity.None,
- GameTime = gameTime
+ GameTime = gameTime,
+ Source = GameEvent.EventSource.Log
};
}
diff --git a/Application/Factories/GameLogReaderFactory.cs b/Application/Factories/GameLogReaderFactory.cs
new file mode 100644
index 000000000..53854e8b0
--- /dev/null
+++ b/Application/Factories/GameLogReaderFactory.cs
@@ -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());
+ }
+
+ else if (baseUri.Scheme == Uri.UriSchemeFile)
+ {
+ return new GameLogReader(baseUri.LocalPath, eventParser, _serviceProvider.GetRequiredService());
+ }
+
+ throw new NotImplementedException($"No log reader implemented for Uri scheme \"{baseUri.Scheme}\"");
+ }
+ }
+}
diff --git a/Application/Factories/GameServerInstanceFactory.cs b/Application/Factories/GameServerInstanceFactory.cs
index 3f4f9e7b8..afab231a0 100644
--- a/Application/Factories/GameServerInstanceFactory.cs
+++ b/Application/Factories/GameServerInstanceFactory.cs
@@ -12,16 +12,18 @@ namespace IW4MAdmin.Application.Factories
{
private readonly ITranslationLookup _translationLookup;
private readonly IRConConnectionFactory _rconConnectionFactory;
+ private readonly IGameLogReaderFactory _gameLogReaderFactory;
///
/// base constructor
///
///
///
- public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory)
+ public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory)
{
_translationLookup = translationLookup;
_rconConnectionFactory = rconConnectionFactory;
+ _gameLogReaderFactory = gameLogReaderFactory;
}
///
@@ -32,7 +34,7 @@ namespace IW4MAdmin.Application.Factories
///
public Server CreateServer(ServerConfiguration config, IManager manager)
{
- return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory);
+ return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory, _gameLogReaderFactory);
}
}
}
diff --git a/Application/GameEventHandler.cs b/Application/GameEventHandler.cs
index 8a8d25ea4..f43b646f6 100644
--- a/Application/GameEventHandler.cs
+++ b/Application/GameEventHandler.cs
@@ -1,19 +1,19 @@
using IW4MAdmin.Application.Misc;
+using Newtonsoft.Json;
using SharedLibraryCore;
using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces;
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application
{
- class GameEventHandler : IEventHandler
+ public class GameEventHandler : IEventHandler
{
- private const int MAX_CONCURRENT_EVENTS = 10;
- private readonly ApplicationManager _manager;
- private readonly SemaphoreSlim _processingEvents;
+ private readonly EventLog _eventLog;
private static readonly GameEvent.EventType[] overrideEvents = new[]
{
GameEvent.EventType.Connect,
@@ -22,39 +22,12 @@ namespace IW4MAdmin.Application
GameEvent.EventType.Stop
};
- public GameEventHandler(IManager mgr)
+ public GameEventHandler()
{
- _manager = (ApplicationManager)mgr;
- _processingEvents = new SemaphoreSlim(MAX_CONCURRENT_EVENTS, MAX_CONCURRENT_EVENTS);
+ _eventLog = new EventLog();
}
- private Task GameEventHandler_GameEventAdded(object sender, GameEventArgs args)
- {
- 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)
+ public void HandleEvent(IManager manager, GameEvent gameEvent)
{
#if DEBUG
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");
#endif
- if (_manager.Running || overrideEvents.Contains(gameEvent.Type))
+ if (manager.IsRunning || overrideEvents.Contains(gameEvent.Type))
{
#if DEBUG
gameEvent.Owner.Logger.WriteDebug($"Adding event with id {gameEvent.Id}");
#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());
+ }
+ _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
else
diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs
index 5055aad7c..be9468a89 100644
--- a/Application/IO/GameLogEventDetection.cs
+++ b/Application/IO/GameLogEventDetection.cs
@@ -13,17 +13,9 @@ namespace IW4MAdmin.Application.IO
private readonly IGameLogReader _reader;
private readonly bool _ignoreBots;
- class EventState
+ public GameLogEventDetection(Server server, Uri[] gameLogUris, IGameLogReaderFactory gameLogReaderFactory)
{
- public ILogger Log { get; set; }
- 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);
+ _reader = gameLogReaderFactory.CreateGameLogReader(gameLogUris, server.EventParser);
_server = server;
_ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false;
}
@@ -70,7 +62,7 @@ namespace IW4MAdmin.Application.IO
return;
}
- var events = await _reader.ReadEventsFromLog(_server, fileDiff, previousFileSize);
+ var events = await _reader.ReadEventsFromLog(fileDiff, previousFileSize);
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
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)
@@ -104,7 +96,7 @@ namespace IW4MAdmin.Application.IO
gameEvent.Target.CurrentServer = _server;
}
- _server.Manager.GetEventHandler().AddEvent(gameEvent);
+ _server.Manager.AddEvent(gameEvent);
}
}
diff --git a/Application/IO/GameLogReader.cs b/Application/IO/GameLogReader.cs
index 07a398632..764de90a7 100644
--- a/Application/IO/GameLogReader.cs
+++ b/Application/IO/GameLogReader.cs
@@ -13,18 +13,20 @@ namespace IW4MAdmin.Application.IO
{
private readonly IEventParser _parser;
private readonly string _logFile;
+ private readonly ILogger _logger;
public long Length => new FileInfo(_logFile).Length;
public int UpdateInterval => 300;
- public GameLogReader(string logFile, IEventParser parser)
+ public GameLogReader(string logFile, IEventParser parser, ILogger logger)
{
_logFile = logFile;
_parser = parser;
+ _logger = logger;
}
- public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
+ public async Task> ReadEventsFromLog(long fileSizeDiff, long startPosition)
{
// allocate the bytes for the new log lines
List logLines = new List();
@@ -34,7 +36,7 @@ namespace IW4MAdmin.Application.IO
{
byte[] buff = new byte[fileSizeDiff];
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();
char[] charBuff = Utilities.EncodingType.GetChars(buff);
@@ -71,9 +73,9 @@ namespace IW4MAdmin.Application.IO
catch (Exception e)
{
- server.Logger.WriteWarning("Could not properly parse event line");
- server.Logger.WriteDebug(e.Message);
- server.Logger.WriteDebug(eventLine);
+ _logger.WriteWarning("Could not properly parse event line");
+ _logger.WriteDebug(e.Message);
+ _logger.WriteDebug(eventLine);
}
}
diff --git a/Application/IO/GameLogReaderHttp.cs b/Application/IO/GameLogReaderHttp.cs
index 8f64f3562..822861d0f 100644
--- a/Application/IO/GameLogReaderHttp.cs
+++ b/Application/IO/GameLogReaderHttp.cs
@@ -5,43 +5,42 @@ using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Net.Http;
using System.Threading.Tasks;
-using static SharedLibraryCore.Utilities;
namespace IW4MAdmin.Application.IO
{
///
- /// provides capibility of reading log files over HTTP
+ /// provides capability of reading log files over HTTP
///
class GameLogReaderHttp : IGameLogReader
{
private readonly IEventParser _eventParser;
private readonly IGameLogServer _logServerApi;
- readonly string logPath;
+ private readonly ILogger _logger;
+ private readonly string _safeLogPath;
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;
- _logServerApi = RestClient.For(gameLogServerUri);
+ _logServerApi = RestClient.For(gameLogServerUris[0].ToString());
+ _safeLogPath = gameLogServerUris[1].LocalPath.ToBase64UrlSafeString();
+ _logger = logger;
}
public long Length => -1;
public int UpdateInterval => 500;
- public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
+ public async Task> ReadEventsFromLog(long fileSizeDiff, long startPosition)
{
var events = new List();
- string b64Path = logPath;
- var response = await _logServerApi.Log(b64Path, lastKey);
+ var response = await _logServerApi.Log(_safeLogPath, lastKey);
lastKey = response.NextKey;
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;
}
@@ -63,9 +62,9 @@ namespace IW4MAdmin.Application.IO
catch (Exception e)
{
- server.Logger.WriteError("Could not properly parse event line from http");
- server.Logger.WriteDebug(e.Message);
- server.Logger.WriteDebug(eventLine);
+ _logger.WriteError("Could not properly parse event line from http");
+ _logger.WriteDebug(e.Message);
+ _logger.WriteDebug(eventLine);
}
}
}
diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs
index b54bf3dd8..0da506839 100644
--- a/Application/IW4MServer.cs
+++ b/Application/IW4MServer.cs
@@ -13,6 +13,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
+using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -31,7 +32,7 @@ namespace IW4MAdmin
public int Id { get; private set; }
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;
}
@@ -77,7 +78,7 @@ namespace IW4MAdmin
Type = GameEvent.EventType.Connect
};
- Manager.GetEventHandler().AddEvent(e);
+ Manager.AddEvent(e);
return client;
}
@@ -104,7 +105,7 @@ namespace IW4MAdmin
Type = GameEvent.EventType.Disconnect
};
- Manager.GetEventHandler().AddEvent(e);
+ Manager.AddEvent(e);
#if DEBUG == true
}
#endif
@@ -176,6 +177,8 @@ namespace IW4MAdmin
await command.ExecuteAsync(E);
}
+
+
var pluginTasks = Manager.Plugins.Where(_plugin => _plugin.Name != "Login").Select(async _plugin =>
{
try
@@ -186,7 +189,11 @@ namespace IW4MAdmin
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)
{
@@ -195,7 +202,7 @@ namespace IW4MAdmin
}
});
- Parallel.ForEach(pluginTasks, async (_task) => await _task);
+ await Task.WhenAny(pluginTasks);
}
catch (Exception e)
@@ -314,7 +321,8 @@ namespace IW4MAdmin
// 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
// 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...");
// we need to remove them so the client spots can swap
@@ -727,7 +735,7 @@ namespace IW4MAdmin
Origin = client
};
- Manager.GetEventHandler().AddEvent(e);
+ Manager.AddEvent(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token);
}
@@ -771,10 +779,11 @@ namespace IW4MAdmin
{
Type = GameEvent.EventType.PreDisconnect,
Origin = disconnectingClient,
- Owner = this
+ Owner = this,
+ Source = GameEvent.EventSource.Status
};
- Manager.GetEventHandler().AddEvent(e);
+ Manager.AddEvent(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken);
}
@@ -793,10 +802,11 @@ namespace IW4MAdmin
Type = GameEvent.EventType.PreConnect,
Origin = client,
Owner = this,
- IsBlocking = true
+ IsBlocking = true,
+ Source = GameEvent.EventSource.Status
};
- Manager.GetEventHandler().AddEvent(e);
+ Manager.AddEvent(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken);
}
@@ -811,7 +821,7 @@ namespace IW4MAdmin
Owner = this
};
- Manager.GetEventHandler().AddEvent(e);
+ Manager.AddEvent(e);
}
if (ConnectionErrors > 0)
@@ -824,7 +834,7 @@ namespace IW4MAdmin
Target = Utilities.IW4MAdminClient(this)
};
- Manager.GetEventHandler().AddEvent(_event);
+ Manager.AddEvent(_event);
}
ConnectionErrors = 0;
@@ -846,7 +856,7 @@ namespace IW4MAdmin
Data = ConnectionErrors.ToString()
};
- Manager.GetEventHandler().AddEvent(_event);
+ Manager.AddEvent(_event);
}
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}");
_ = Task.Run(() => LogEvent.PollForChanges());
@@ -1080,6 +1090,21 @@ namespace IW4MAdmin
#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)
{
string logPath;
@@ -1179,7 +1204,7 @@ namespace IW4MAdmin
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");
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
diff --git a/Application/Main.cs b/Application/Main.cs
index 4369d2c6d..a89ffe6ff 100644
--- a/Application/Main.cs
+++ b/Application/Main.cs
@@ -30,7 +30,7 @@ namespace IW4MAdmin.Application
/// entrypoint of the application
///
///
- public static async Task Main()
+ public static async Task Main(string[] args)
{
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
@@ -45,7 +45,7 @@ namespace IW4MAdmin.Application
Console.WriteLine($" Version {Utilities.GetVersionAsString()}");
Console.WriteLine("=====================================================");
- await LaunchAsync();
+ await LaunchAsync(args);
}
///
@@ -64,7 +64,7 @@ namespace IW4MAdmin.Application
/// task that initializes application and starts the application monitoring and runtime tasks
///
///
- private static async Task LaunchAsync()
+ private static async Task LaunchAsync(string[] args)
{
restart:
ITranslationLookup translationLookup = null;
@@ -74,7 +74,7 @@ namespace IW4MAdmin.Application
ConfigurationMigration.MoveConfigFolder10518(null);
ConfigurationMigration.CheckDirectories();
- var services = ConfigureServices();
+ var services = ConfigureServices(args);
serviceProvider = services.BuildServiceProvider();
ServerManager = (ApplicationManager)serviceProvider.GetRequiredService();
translationLookup = serviceProvider.GetRequiredService();
@@ -252,7 +252,7 @@ namespace IW4MAdmin.Application
Owner = ServerManager.Servers[0]
};
- ServerManager.GetEventHandler().AddEvent(E);
+ ServerManager.AddEvent(E);
await E.WaitAsync(Utilities.DefaultCommandTimeout, ServerManager.CancellationToken);
Console.Write('>');
}
@@ -266,7 +266,7 @@ namespace IW4MAdmin.Application
///
/// Configures the dependency injection services
///
- private static IServiceCollection ConfigureServices()
+ private static IServiceCollection ConfigureServices(string[] args)
{
var defaultLogger = new Logger("IW4MAdmin-Manager");
var pluginImporter = new PluginImporter(defaultLogger);
@@ -285,6 +285,7 @@ namespace IW4MAdmin.Application
.AddSingleton()
.AddSingleton()
.AddSingleton()
+ .AddSingleton()
.AddSingleton()
.AddTransient()
.AddSingleton(_serviceProvider =>
@@ -295,6 +296,15 @@ namespace IW4MAdmin.Application
})
.AddSingleton();
+ if (args.Contains("serialevents"))
+ {
+ serviceCollection.AddSingleton();
+ }
+ else
+ {
+ serviceCollection.AddSingleton();
+ }
+
// register the native commands
foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
.Where(_command => _command.BaseType == typeof(Command)))
diff --git a/Application/Misc/EventLog.cs b/Application/Misc/EventLog.cs
new file mode 100644
index 000000000..6a8ef608e
--- /dev/null
+++ b/Application/Misc/EventLog.cs
@@ -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>
+ {
+ 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;
+ }
+ }
+}
diff --git a/Application/Misc/SerializationHelpers.cs b/Application/Misc/SerializationHelpers.cs
new file mode 100644
index 000000000..d2753dafa
--- /dev/null
+++ b/Application/Misc/SerializationHelpers.cs
@@ -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(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(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(jsonObject["Type"].ToString()),
+ Subtype = jsonObject["Subtype"]?.ToString(),
+ Source = Enum.Parse(jsonObject["Source"].ToString()),
+ RequiredEntity = Enum.Parse(jsonObject["RequiredEntity"].ToString()),
+ Data = jsonObject["Data"].ToString(),
+ Message = jsonObject["Message"].ToString(),
+ GameTime = (int?)jsonObject["GameTime"],
+ Origin = jsonObject["Origin"]?.ToObject(serializer),
+ Target = jsonObject["Target"]?.ToObject(serializer),
+ ImpersonationOrigin = jsonObject["ImpersonationOrigin"]?.ToObject(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);
+ }
+ }
+}
diff --git a/Application/RCon/RConConnection.cs b/Application/RCon/RConConnection.cs
index 3ced4b19a..ca131f4c8 100644
--- a/Application/RCon/RConConnection.cs
+++ b/Application/RCon/RConConnection.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
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()));
}
- 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)
{
diff --git a/Application/RconParsers/BaseRConParser.cs b/Application/RconParsers/BaseRConParser.cs
index d2554cdb4..d170bef78 100644
--- a/Application/RconParsers/BaseRConParser.cs
+++ b/Application/RconParsers/BaseRConParser.cs
@@ -179,9 +179,15 @@ namespace IW4MAdmin.Application.RconParsers
}
long networkId;
+ string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
+
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)
@@ -189,7 +195,6 @@ namespace IW4MAdmin.Application.RconParsers
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();
var client = new EFClient()
@@ -206,13 +211,13 @@ namespace IW4MAdmin.Application.RconParsers
State = EFClient.ClientState.Connecting
};
-#if DEBUG
- if (client.NetworkId < 1000 && client.NetworkId > 0)
- {
- client.IPAddress = 2147483646;
- client.Ping = 0;
- }
-#endif
+//#if DEBUG
+// if (client.NetworkId < 1000 && client.NetworkId > 0)
+// {
+// client.IPAddress = 2147483646;
+// client.Ping = 0;
+// }
+//#endif
StatusPlayers.Add(client);
}
diff --git a/Application/SerialGameEventHandler.cs b/Application/SerialGameEventHandler.cs
new file mode 100644
index 000000000..5eea99101
--- /dev/null
+++ b/Application/SerialGameEventHandler.cs
@@ -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));
+ }
+ }
+ }
+}
diff --git a/Plugins/ScriptPlugins/ParserPIW5.js b/Plugins/ScriptPlugins/ParserPIW5.js
index 45e699648..a638e6add 100644
--- a/Plugins/ScriptPlugins/ParserPIW5.js
+++ b/Plugins/ScriptPlugins/ParserPIW5.js
@@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
- version: 0.2,
+ version: 0.3,
name: 'Plutonium IW5 Parser',
isParser: true,
@@ -28,7 +28,7 @@ var plugin = {
rconParser.Configuration.CanGenerateLogPath = true;
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(101, 2);
rconParser.Configuration.Status.AddMapping(102, 3);
diff --git a/Plugins/ScriptPlugins/ParserPT6.js b/Plugins/ScriptPlugins/ParserPT6.js
index b1aa28320..51eb460c5 100644
--- a/Plugins/ScriptPlugins/ParserPT6.js
+++ b/Plugins/ScriptPlugins/ParserPT6.js
@@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax, Xerxes',
- version: 0.7,
+ version: 0.8,
name: 'Plutonium T6 Parser',
isParser: true,
@@ -27,7 +27,7 @@ var plugin = {
rconParser.Configuration.WaitForResponse = false;
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(101, 2);
rconParser.Configuration.Status.AddMapping(102, 3);
diff --git a/Plugins/ScriptPlugins/ParserTeknoMW3.js b/Plugins/ScriptPlugins/ParserTeknoMW3.js
index 793c8fff0..3c7dcfa87 100644
--- a/Plugins/ScriptPlugins/ParserTeknoMW3.js
+++ b/Plugins/ScriptPlugins/ParserTeknoMW3.js
@@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
- version: 0.3,
+ version: 0.4,
name: 'Tekno MW3 Parser',
isParser: true,
@@ -14,11 +14,11 @@ var plugin = {
rconParser = manager.GenerateDynamicRConParser(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(103, 4); // RConNetworkId
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.Say = 'say {0}';
rconParser.Configuration.CommandPrefixes.Kick = 'dropclient {0} "{1}"';
diff --git a/Plugins/Stats/Events/Script.cs b/Plugins/Stats/Events/Script.cs
index 8fa09d374..b4eeb2e45 100644
--- a/Plugins/Stats/Events/Script.cs
+++ b/Plugins/Stats/Events/Script.cs
@@ -24,6 +24,12 @@ namespace IW4MAdmin.Plugins.Stats.Events
return (EVENT_SCRIPTKILL, EVENT_SCRIPTKILL, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
{
string[] lineSplit = eventLine.Split(";");
+
+ if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
+ {
+ return autoEvent;
+ }
+
long originId = lineSplit[1].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) =>
{
string[] lineSplit = eventLine.Split(";");
+
+ if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
+ {
+ return autoEvent;
+ }
+
long originId = lineSplit[1].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) =>
{
string[] lineSplit = eventLine.Split(";");
+
+ if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
+ {
+ return autoEvent;
+ }
+
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs
index 6aeb76eae..733439bf9 100644
--- a/Plugins/Stats/Plugin.cs
+++ b/Plugins/Stats/Plugin.cs
@@ -530,7 +530,7 @@ namespace IW4MAdmin.Plugins.Stats
///
///
///
- private bool IsWorldDamage(EFClient origin) => origin?.NetworkId == 1;
+ private bool IsWorldDamage(EFClient origin) => origin?.NetworkId == Utilities.WORLD_ID || origin?.ClientId == Utilities.WORLD_ID;
///
/// Indicates if we should try to use anticheat even if sv_customcallbacks is not defined
diff --git a/Plugins/Stats/Stats.csproj b/Plugins/Stats/Stats.csproj
index 7b680892f..52ff5583f 100644
--- a/Plugins/Stats/Stats.csproj
+++ b/Plugins/Stats/Stats.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/Plugins/Tests/ClientTests.cs b/Plugins/Tests/ClientTests.cs
index e72f0dfab..79770a33b 100644
--- a/Plugins/Tests/ClientTests.cs
+++ b/Plugins/Tests/ClientTests.cs
@@ -72,7 +72,7 @@ namespace Tests
}
};
- _manager.GetEventHandler().AddEvent(e);
+ _manager.AddEvent(e);
e.Complete();
e = new GameEvent()
@@ -91,7 +91,7 @@ namespace Tests
}
};
- _manager.GetEventHandler().AddEvent(e);
+ _manager.AddEvent(e);
e.Complete();
e = new GameEvent()
@@ -110,7 +110,7 @@ namespace Tests
}
};
- _manager.GetEventHandler().AddEvent(e);
+ _manager.AddEvent(e);
e.Complete();
}
diff --git a/Plugins/Tests/PluginTests.cs b/Plugins/Tests/PluginTests.cs
index 980467141..ffc5600d3 100644
--- a/Plugins/Tests/PluginTests.cs
+++ b/Plugins/Tests/PluginTests.cs
@@ -30,7 +30,7 @@ namespace Tests
Owner = Manager.GetServers()[0]
};
- Manager.GetEventHandler().AddEvent(e);
+ Manager.AddEvent(e);
e.Complete();
var client = Manager.GetServers()[0].Clients[0];
@@ -43,7 +43,7 @@ namespace Tests
Owner = e.Owner
};
- Manager.GetEventHandler().AddEvent(e);
+ Manager.AddEvent(e);
e.Complete();
Assert.True(client.Warnings == 1, "client wasn't warned for objectional language");
diff --git a/SharedLibraryCore/Commands/RunAsCommand.cs b/SharedLibraryCore/Commands/RunAsCommand.cs
index 67e7ac477..c20a8d9d4 100644
--- a/SharedLibraryCore/Commands/RunAsCommand.cs
+++ b/SharedLibraryCore/Commands/RunAsCommand.cs
@@ -49,7 +49,7 @@ namespace SharedLibraryCore.Commands
Data = cmd,
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 response = E.Owner.CommandResult.Where(c => c.ClientId == E.Target.ClientId).ToList();
diff --git a/SharedLibraryCore/Events/EventAPI.cs b/SharedLibraryCore/Events/EventAPI.cs
index 102f5fe98..720b4a4e7 100644
--- a/SharedLibraryCore/Events/EventAPI.cs
+++ b/SharedLibraryCore/Events/EventAPI.cs
@@ -23,9 +23,9 @@ namespace SharedLibraryCore.Events
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
if (E.Type == GameEvent.EventType.Unknown)
return;
diff --git a/SharedLibraryCore/Events/GameEvent.cs b/SharedLibraryCore/Events/GameEvent.cs
index 490edd74d..40586d389 100644
--- a/SharedLibraryCore/Events/GameEvent.cs
+++ b/SharedLibraryCore/Events/GameEvent.cs
@@ -194,6 +194,14 @@ namespace SharedLibraryCore
Target = 4
}
+ public enum EventSource
+ {
+ Unspecified,
+ Log,
+ Status,
+ Internal
+ }
+
static long NextEventId;
static long GetNextEventId()
{
@@ -214,6 +222,7 @@ namespace SharedLibraryCore
}
public EventType Type;
+ public EventSource Source { get; set; }
///
/// suptype of the event for more detailed classification
///
@@ -229,7 +238,7 @@ namespace SharedLibraryCore
public EFClient Target;
public EFClient ImpersonationOrigin { get; set; }
public Server Owner;
- public bool IsRemote { get; set; } = false;
+ public bool IsRemote { get; set; }
public object Extra { get; set; }
private readonly ManualResetEvent _eventFinishedWaiter;
public DateTime Time { get; set; }
diff --git a/SharedLibraryCore/Interfaces/IEventHandler.cs b/SharedLibraryCore/Interfaces/IEventHandler.cs
index c92c5afd7..b8437f5e5 100644
--- a/SharedLibraryCore/Interfaces/IEventHandler.cs
+++ b/SharedLibraryCore/Interfaces/IEventHandler.cs
@@ -1,18 +1,15 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharedLibraryCore.Interfaces
+namespace SharedLibraryCore.Interfaces
{
///
- /// This class handle games events (from log, manual events, etc)
+ /// handles games events (from log, manual events, etc)
///
public interface IEventHandler
{
///
/// Add a game event event to the queue to be processed
///
- /// Game event
- void AddEvent(GameEvent gameEvent);
+ /// application manager instance
+ /// game event
+ void HandleEvent(IManager manager, GameEvent gameEvent);
}
}
diff --git a/SharedLibraryCore/Interfaces/IGameLogReader.cs b/SharedLibraryCore/Interfaces/IGameLogReader.cs
index 25bf59707..155c1851b 100644
--- a/SharedLibraryCore/Interfaces/IGameLogReader.cs
+++ b/SharedLibraryCore/Interfaces/IGameLogReader.cs
@@ -4,18 +4,17 @@ using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
///
- /// represents the abtraction of game log reading
+ /// represents the abstraction of game log reading
///
public interface IGameLogReader
{
///
/// get new events that have occured since the last poll
///
- ///
///
///
///
- Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition);
+ Task> ReadEventsFromLog(long fileSizeDiff, long startPosition);
///
/// how long the log file is
diff --git a/SharedLibraryCore/Interfaces/IGameLogReaderFactory.cs b/SharedLibraryCore/Interfaces/IGameLogReaderFactory.cs
new file mode 100644
index 000000000..54eb716bf
--- /dev/null
+++ b/SharedLibraryCore/Interfaces/IGameLogReaderFactory.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace SharedLibraryCore.Interfaces
+{
+ ///
+ /// factory interface to create game log readers based on the log file uri
+ ///
+ public interface IGameLogReaderFactory
+ {
+ ///
+ /// generates a new game log reader based on the provided Uri
+ ///
+ /// collection of log uri used to generate the log reader
+ /// event parser for the log reader
+ ///
+ IGameLogReader CreateGameLogReader(Uri[] logUris, IEventParser eventParser);
+ }
+}
diff --git a/SharedLibraryCore/Interfaces/IManager.cs b/SharedLibraryCore/Interfaces/IManager.cs
index f058a3985..337a3a1b9 100644
--- a/SharedLibraryCore/Interfaces/IManager.cs
+++ b/SharedLibraryCore/Interfaces/IManager.cs
@@ -24,11 +24,6 @@ namespace SharedLibraryCore.Interfaces
AliasService GetAliasService();
PenaltyService GetPenaltyService();
///
- /// Get the event handlers
- ///
- /// EventHandler for the manager
- IEventHandler GetEventHandler();
- ///
/// enumerates the registered plugin instances
///
IEnumerable Plugins { get; }
@@ -68,7 +63,12 @@ namespace SharedLibraryCore.Interfaces
string ExternalIPAddress { get; }
CancellationToken CancellationToken { get; }
bool IsRestartRequested { get; }
- //OnServerEventEventHandler OnServerEvent { get; set; }
+ bool IsRunning { get; }
Task ExecuteEvent(GameEvent gameEvent);
+ ///
+ /// queues an event for processing
+ ///
+ /// event to be processed
+ void AddEvent(GameEvent gameEvent);
}
}
diff --git a/SharedLibraryCore/PartialEntities/EFClient.cs b/SharedLibraryCore/PartialEntities/EFClient.cs
index c8c0e58fc..e9a87cfe3 100644
--- a/SharedLibraryCore/PartialEntities/EFClient.cs
+++ b/SharedLibraryCore/PartialEntities/EFClient.cs
@@ -142,7 +142,7 @@ namespace SharedLibraryCore.Database.Models
Data = message
};
- CurrentServer?.Manager.GetEventHandler().AddEvent(e);
+ CurrentServer?.Manager.AddEvent(e);
return e;
}
@@ -174,7 +174,7 @@ namespace SharedLibraryCore.Database.Models
Warnings++;
}
- sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@@ -202,7 +202,7 @@ namespace SharedLibraryCore.Database.Models
Warnings = 0;
- sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@@ -243,7 +243,7 @@ namespace SharedLibraryCore.Database.Models
}
sender.SetAdditionalProperty("_reportCount", reportCount + 1);
- sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@@ -276,7 +276,7 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Invalid;
}
- sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@@ -308,7 +308,7 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Invalid;
}
- sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@@ -336,7 +336,7 @@ namespace SharedLibraryCore.Database.Models
}
State = ClientState.Disconnecting;
- sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@@ -366,7 +366,7 @@ namespace SharedLibraryCore.Database.Models
}
State = ClientState.Disconnecting;
- sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@@ -400,7 +400,7 @@ namespace SharedLibraryCore.Database.Models
}
State = ClientState.Disconnecting;
- sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@@ -428,7 +428,7 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Permission;
}
- sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@@ -464,7 +464,7 @@ namespace SharedLibraryCore.Database.Models
Level = newPermission;
}
- sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@@ -565,7 +565,7 @@ namespace SharedLibraryCore.Database.Models
Owner = CurrentServer,
};
- CurrentServer.Manager.GetEventHandler().AddEvent(e);
+ CurrentServer.Manager.AddEvent(e);
}
}
@@ -655,7 +655,7 @@ namespace SharedLibraryCore.Database.Models
[NotMapped]
public int Score { get; set; }
[NotMapped]
- public bool IsBot => NetworkId == -1;
+ public bool IsBot => NetworkId == Name.GenerateGuidFromString();
[NotMapped]
public ClientState State { get; set; }
@@ -694,7 +694,7 @@ namespace SharedLibraryCore.Database.Models
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()
diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs
index 687151c3a..6e675bd67 100644
--- a/SharedLibraryCore/Server.cs
+++ b/SharedLibraryCore/Server.cs
@@ -28,7 +28,7 @@ namespace SharedLibraryCore
T7 = 8
}
- public Server(IManager mgr, IRConConnectionFactory rconConnectionFactory, ServerConfiguration config)
+ public Server(ServerConfiguration config, IManager mgr, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory)
{
Password = config.Password;
IP = config.IPAddress;
@@ -46,6 +46,7 @@ namespace SharedLibraryCore
NextMessage = 0;
CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName;
CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName;
+ this.gameLogReaderFactory = gameLogReaderFactory;
InitializeTokens();
InitializeAutoMessages();
}
@@ -134,7 +135,7 @@ namespace SharedLibraryCore
Origin = sender,
};
- Manager.GetEventHandler().AddEvent(e);
+ Manager.AddEvent(e);
return e;
}
@@ -296,7 +297,7 @@ namespace SharedLibraryCore
{
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; }
@@ -325,6 +326,7 @@ namespace SharedLibraryCore
protected TimeSpan LastMessage;
protected DateTime LastPoll;
protected ManualResetEventSlim OnRemoteCommandResponse;
+ protected IGameLogReaderFactory gameLogReaderFactory;
// only here for performance
private readonly bool CustomSayEnabled;
diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj
index a96af91f7..0c9e3d18c 100644
--- a/SharedLibraryCore/SharedLibraryCore.csproj
+++ b/SharedLibraryCore/SharedLibraryCore.csproj
@@ -6,7 +6,7 @@
RaidMax.IW4MAdmin.SharedLibraryCore
- 2.2.11
+ 2.2.12
RaidMax
Forever None
Debug;Release;Prerelease
@@ -20,8 +20,8 @@
true
MIT
Shared Library for IW4MAdmin
- 2.2.11.0
- 2.2.11.0
+ 2.2.12.0
+ 2.2.12.0
diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs
index 80afde181..b76460dbe 100644
--- a/SharedLibraryCore/Utilities.cs
+++ b/SharedLibraryCore/Utilities.cs
@@ -16,6 +16,7 @@ using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
+using System.Threading;
using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
using static SharedLibraryCore.Database.Models.EFPenalty;
@@ -50,6 +51,10 @@ namespace SharedLibraryCore
AdministeredPenalties = new List()
};
}
+ ///
+ /// fallback id for world events
+ ///
+ public const long WORLD_ID = -1;
public static string HttpRequest(string location, string header, string headerValue)
{
@@ -295,39 +300,46 @@ namespace SharedLibraryCore
}
}
+ ///
+ /// converts a string to numerical guid
+ ///
+ /// source string for guid
+ /// how to parse the guid
+ /// value to use if string is empty
+ ///
public static long ConvertGuidToLong(this string str, NumberStyles numberStyle, long? fallback = null)
{
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)
{
return fallback.Value;
}
- long id = 0;
-
- if (numberStyle == NumberStyles.Integer)
+ long id;
+ if (!string.IsNullOrEmpty(parsableAsNumber))
{
- long.TryParse(str, numberStyle, CultureInfo.InvariantCulture, out id);
-
- if (id < 0)
+ if (numberStyle == NumberStyles.Integer)
{
- 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
{
- long.TryParse(str.Length > 16 ? str.Substring(0, 16) : str, numberStyle, CultureInfo.InvariantCulture, out id);
- }
-
- if (!string.IsNullOrEmpty(bot))
- {
- id = -1;
-#if DEBUG
- id = str.Sum(_c => _c);
-#endif
+ // this is a special case for when a real guid is not provided, so we generated it from another source
+ id = str.GenerateGuidFromString();
}
if (id == 0)
@@ -338,6 +350,23 @@ namespace SharedLibraryCore
return id;
}
+ ///
+ /// determines if the guid provided appears to be a bot guid
+ ///
+ /// value of the guid
+ /// true if is bot guid, otherwise false
+ public static bool IsBotGuid(this string guid)
+ {
+ return guid.Contains("bot") || guid == "0";
+ }
+
+ ///
+ /// generates a numerical hashcode from a string value
+ ///
+ /// value string
+ ///
+ public static long GenerateGuidFromString(this string value) => string.IsNullOrEmpty(value) ? -1 : HashCode.Combine(value.StripColors());
+
public static int? ConvertToIP(this string str)
{
bool success = IPAddress.TryParse(str, out IPAddress ip);
@@ -900,6 +929,24 @@ namespace SharedLibraryCore
return false;
}
+ ///
+ /// https://www.planetgeek.ch/2016/12/08/async-method-without-cancellation-support-do-it-my-way/
+ ///
+ 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;
///
diff --git a/Tests/ApplicationTests/ApplicationTests.csproj b/Tests/ApplicationTests/ApplicationTests.csproj
index ba2cfe133..1c0b41a26 100644
--- a/Tests/ApplicationTests/ApplicationTests.csproj
+++ b/Tests/ApplicationTests/ApplicationTests.csproj
@@ -23,6 +23,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/Tests/ApplicationTests/CommandTests.cs b/Tests/ApplicationTests/CommandTests.cs
index f6bb9ec5b..88b6bbd4d 100644
--- a/Tests/ApplicationTests/CommandTests.cs
+++ b/Tests/ApplicationTests/CommandTests.cs
@@ -37,12 +37,10 @@ namespace ApplicationTests
cmdConfig = new CommandConfiguration();
serviceProvider = new ServiceCollection()
- .BuildBase()
+ .BuildBase(new MockEventHandler(true))
.BuildServiceProvider();
- mockEventHandler = new MockEventHandler(true);
- A.CallTo(() => serviceProvider.GetRequiredService().GetEventHandler())
- .Returns(mockEventHandler);
+ mockEventHandler = serviceProvider.GetRequiredService();
var mgr = serviceProvider.GetRequiredService();
transLookup = serviceProvider.GetRequiredService();
@@ -54,7 +52,9 @@ namespace ApplicationTests
new NonImpersonatableCommand(cmdConfig, transLookup)
});
- //Utilities.DefaultCommandTimeout = new TimeSpan(0, 0, 2);
+ A.CallTo(() => mgr.AddEvent(A.Ignored))
+ .Invokes((fakeCall) => mockEventHandler.HandleEvent(mgr, fakeCall.Arguments[0] as GameEvent));
+
}
#region RUNAS
diff --git a/Tests/ApplicationTests/DepedencyInjectionExtensions.cs b/Tests/ApplicationTests/DependencyInjectionExtensions.cs
similarity index 63%
rename from Tests/ApplicationTests/DepedencyInjectionExtensions.cs
rename to Tests/ApplicationTests/DependencyInjectionExtensions.cs
index daafc2bb9..2784955fa 100644
--- a/Tests/ApplicationTests/DepedencyInjectionExtensions.cs
+++ b/Tests/ApplicationTests/DependencyInjectionExtensions.cs
@@ -9,10 +9,22 @@ using SharedLibraryCore.Services;
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();
var logger = A.Fake();
@@ -27,10 +39,13 @@ namespace ApplicationTests
.AddSingleton(A.Fake())
.AddSingleton(A.Fake())
.AddSingleton(A.Fake())
- .AddSingleton(A.Fake());
+ .AddSingleton()
+ .AddSingleton(A.Fake())
+ .AddSingleton(A.Fake())
+ .AddSingleton(eventHandler);
serviceCollection.AddSingleton(_sp => new IW4MServer(_sp.GetRequiredService(), ConfigurationGenerators.CreateServerConfiguration(),
- _sp.GetRequiredService(), _sp.GetRequiredService())
+ _sp.GetRequiredService(), _sp.GetRequiredService(), _sp.GetRequiredService())
{
RconParser = _sp.GetRequiredService()
});
diff --git a/Tests/ApplicationTests/Fixtures/DataFileLoader.cs b/Tests/ApplicationTests/Fixtures/DataFileLoader.cs
new file mode 100644
index 000000000..ccd9a2705
--- /dev/null
+++ b/Tests/ApplicationTests/Fixtures/DataFileLoader.cs
@@ -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 Load(string fileName)
+ {
+ string data = await File.ReadAllTextAsync($"{fileName}.json");
+ return JsonConvert.DeserializeObject(data, EventLog.BuildVcrSerializationSettings());
+ }
+ }
+}
diff --git a/Tests/ApplicationTests/GameEventHandlerTests.cs b/Tests/ApplicationTests/GameEventHandlerTests.cs
new file mode 100644
index 000000000..f0f42afb0
--- /dev/null
+++ b/Tests/ApplicationTests/GameEventHandlerTests.cs
@@ -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
+ {
+ }
+}
diff --git a/Tests/ApplicationTests/IOTests.cs b/Tests/ApplicationTests/IOTests.cs
index f4cd32382..9c09767a2 100644
--- a/Tests/ApplicationTests/IOTests.cs
+++ b/Tests/ApplicationTests/IOTests.cs
@@ -1,5 +1,7 @@
using FakeItEasy;
+using IW4MAdmin;
using IW4MAdmin.Application.IO;
+using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
@@ -12,11 +14,24 @@ namespace ApplicationTests
public class IOTests
{
+ private IServiceProvider serviceProvider;
+
+ [SetUp]
+ public void Setup()
+ {
+ serviceProvider = new ServiceCollection().BuildBase().BuildServiceProvider();
+ }
+
[Test]
public async Task GameLogEventDetection_WorksAfterFileSizeReset()
{
var reader = A.Fake();
- var detect = new GameLogEventDetection(null, "", A.Fake(), reader);
+ var factory = A.Fake();
+
+ A.CallTo(() => factory.CreateGameLogReader(A.Ignored, A.Ignored))
+ .Returns(reader);
+
+ var detect = new GameLogEventDetection(serviceProvider.GetService(), new Uri[] { new Uri("C:\\test.log") }, factory);
A.CallTo(() => reader.Length)
.Returns(100)
@@ -35,7 +50,7 @@ namespace ApplicationTests
await detect.UpdateLogEvents();
}
- A.CallTo(() => reader.ReadEventsFromLog(A.Ignored, A.Ignored, A.Ignored))
+ A.CallTo(() => reader.ReadEventsFromLog(A.Ignored, A.Ignored))
.MustHaveHappenedTwiceExactly();
}
}
diff --git a/Tests/ApplicationTests/IW4MServerTests.cs b/Tests/ApplicationTests/IW4MServerTests.cs
index b0cc1d50f..899be42f0 100644
--- a/Tests/ApplicationTests/IW4MServerTests.cs
+++ b/Tests/ApplicationTests/IW4MServerTests.cs
@@ -36,6 +36,7 @@ namespace ApplicationTests
fakeManager = serviceProvider.GetRequiredService();
fakeRConConnection = serviceProvider.GetRequiredService();
fakeRConParser = serviceProvider.GetRequiredService();
+ mockEventHandler = serviceProvider.GetRequiredService();
var rconConnectionFactory = serviceProvider.GetRequiredService();
@@ -45,10 +46,9 @@ namespace ApplicationTests
A.CallTo(() => fakeRConParser.Configuration)
.Returns(ConfigurationGenerators.CreateRConParserConfiguration(serviceProvider.GetRequiredService()));
-
- mockEventHandler = new MockEventHandler();
- A.CallTo(() => fakeManager.GetEventHandler())
- .Returns(mockEventHandler);
+ A.CallTo(() => fakeManager.AddEvent(A.Ignored))
+ .Invokes((fakeCall) => mockEventHandler.HandleEvent(fakeManager, fakeCall.Arguments[0] as GameEvent));
+
}
#region LOG
diff --git a/Tests/ApplicationTests/Mocks/EventHandler.cs b/Tests/ApplicationTests/Mocks/EventHandler.cs
index ec1caea20..e61a9a206 100644
--- a/Tests/ApplicationTests/Mocks/EventHandler.cs
+++ b/Tests/ApplicationTests/Mocks/EventHandler.cs
@@ -14,7 +14,7 @@ namespace ApplicationTests.Mocks
_autoExecute = autoExecute;
}
- public void AddEvent(GameEvent gameEvent)
+ public void HandleEvent(IManager manager, GameEvent gameEvent)
{
Events.Add(gameEvent);
diff --git a/Tests/ApplicationTests/Mocks/VcrEventReader.cs b/Tests/ApplicationTests/Mocks/VcrEventReader.cs
new file mode 100644
index 000000000..5176f6fdb
--- /dev/null
+++ b/Tests/ApplicationTests/Mocks/VcrEventReader.cs
@@ -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> ReadEventsFromLog(long fileSizeDiff, long startPosition)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Tests/ApplicationTests/PluginTests.cs b/Tests/ApplicationTests/PluginTests.cs
index f9ea27b6b..f4f5ac017 100644
--- a/Tests/ApplicationTests/PluginTests.cs
+++ b/Tests/ApplicationTests/PluginTests.cs
@@ -31,9 +31,7 @@ namespace ApplicationTests
{
serviceProvider = new ServiceCollection().BuildBase().BuildServiceProvider();
fakeManager = serviceProvider.GetRequiredService();
- mockEventHandler = new MockEventHandler();
- A.CallTo(() => fakeManager.GetEventHandler())
- .Returns(mockEventHandler);
+ mockEventHandler = serviceProvider.GetRequiredService();
var rconConnectionFactory = serviceProvider.GetRequiredService();
@@ -42,6 +40,9 @@ namespace ApplicationTests
A.CallTo(() => serviceProvider.GetRequiredService().Configuration)
.Returns(ConfigurationGenerators.CreateRConParserConfiguration(serviceProvider.GetRequiredService()));
+
+ A.CallTo(() => fakeManager.AddEvent(A.Ignored))
+ .Invokes((fakeCall) => mockEventHandler.HandleEvent(fakeManager, fakeCall.Arguments[0] as GameEvent));
}
[Test]
diff --git a/Tests/ApplicationTests/ServerTests.cs b/Tests/ApplicationTests/ServerTests.cs
index e37437870..70dfb0c23 100644
--- a/Tests/ApplicationTests/ServerTests.cs
+++ b/Tests/ApplicationTests/ServerTests.cs
@@ -33,7 +33,7 @@ namespace ApplicationTests
var mgr = A.Fake();
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
- A.Fake(), A.Fake());
+ A.Fake(), A.Fake(), A.Fake());
var parser = new BaseEventParser(A.Fake(), A.Fake());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
@@ -59,7 +59,7 @@ namespace ApplicationTests
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
- A.Fake(), A.Fake());
+ A.Fake(), A.Fake(), A.Fake());
var parser = new BaseEventParser(A.Fake(), A.Fake());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
diff --git a/Tests/ApplicationTests/StatsTests.cs b/Tests/ApplicationTests/StatsTests.cs
index d9759e2fe..a2296ed00 100644
--- a/Tests/ApplicationTests/StatsTests.cs
+++ b/Tests/ApplicationTests/StatsTests.cs
@@ -63,7 +63,7 @@ namespace ApplicationTests
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake(),
- A.Fake());
+ A.Fake(), A.Fake());
var parser = new BaseEventParser(A.Fake(), A.Fake());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
diff --git a/Tests/ApplicationTests/VcrTests.cs b/Tests/ApplicationTests/VcrTests.cs
new file mode 100644
index 000000000..d6639d7f4
--- /dev/null
+++ b/Tests/ApplicationTests/VcrTests.cs
@@ -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()
+ .Load(source);
+
+ var server = serviceProvider.GetRequiredService();
+
+ foreach (var gameEvent in sourceData.Values.First())
+ {
+ await server.ExecuteEvent(gameEvent);
+ }
+ }
+ }
+}
diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs
index 59ff81926..15547ef35 100644
--- a/WebfrontCore/Controllers/ConsoleController.cs
+++ b/WebfrontCore/Controllers/ConsoleController.cs
@@ -56,7 +56,7 @@ namespace WebfrontCore.Controllers
IsRemote = true
};
- Manager.GetEventHandler().AddEvent(remoteEvent);
+ Manager.AddEvent(remoteEvent);
List response = null;
try