refactor a good bit of stuff for better dependency injection

fix regular expression for T6 log parsing
This commit is contained in:
RaidMax 2020-02-11 16:44:06 -06:00
parent ec053eb854
commit c3c21a7749
56 changed files with 820 additions and 322 deletions

View File

@ -58,11 +58,12 @@ namespace IW4MAdmin.Application
private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>();
private readonly ITranslationLookup _translationLookup;
private readonly IConfigurationHandler<CommandConfiguration> _commandConfiguration;
private readonly IPluginImporter _pluginImporter;
private readonly IGameServerInstanceFactory _serverInstanceFactory;
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IPluginImporter pluginImporter)
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
IEnumerable<IPlugin> plugins)
{
MiddlewareActionHandler = actionHandler;
_servers = new ConcurrentBag<Server>();
@ -73,8 +74,8 @@ namespace IW4MAdmin.Application
ConfigHandler = appConfigHandler;
StartTime = DateTime.UtcNow;
PageList = new PageList();
AdditionalEventParsers = new List<IEventParser>();
AdditionalRConParsers = new List<IRConParser>();
AdditionalEventParsers = new List<IEventParser>() { new BaseEventParser() };
AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser() };
TokenAuthenticator = new TokenAuthentication();
_metaService = new MetaService();
_tokenSource = new CancellationTokenSource();
@ -82,9 +83,12 @@ namespace IW4MAdmin.Application
_commands = commands.ToList();
_translationLookup = translationLookup;
_commandConfiguration = commandConfiguration;
_pluginImporter = pluginImporter;
_serverInstanceFactory = serverInstanceFactory;
Plugins = plugins;
}
public IEnumerable<IPlugin> Plugins { get; }
public async Task ExecuteEvent(GameEvent newEvent)
{
#if DEBUG == true
@ -249,7 +253,7 @@ namespace IW4MAdmin.Application
ExternalIPAddress = await Utilities.GetExternalIP();
#region PLUGINS
foreach (var plugin in _pluginImporter.ActivePlugins)
foreach (var plugin in Plugins)
{
try
{
@ -567,7 +571,8 @@ namespace IW4MAdmin.Application
try
{
var ServerInstance = new IW4MServer(this, Conf, _translationLookup, _pluginImporter);
// todo: this might not always be an IW4MServer
var ServerInstance = _serverInstanceFactory.CreateServer(Conf, this) as IW4MServer;
await ServerInstance.Initialize();
_servers.Add(ServerInstance);
@ -759,11 +764,6 @@ namespace IW4MAdmin.Application
return Handler;
}
public IList<Assembly> GetPluginAssemblies()
{
return _pluginImporter.PluginAssemblies.Union(_pluginImporter.Assemblies).ToList();
}
public IPageList GetPageList()
{
return PageList;

View File

@ -9,7 +9,7 @@ using static SharedLibraryCore.Server;
namespace IW4MAdmin.Application.EventParsers
{
class BaseEventParser : IEventParser
public class BaseEventParser : IEventParser
{
public BaseEventParser()
{
@ -37,7 +37,7 @@ namespace IW4MAdmin.Application.EventParsers
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-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.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
@ -52,7 +52,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-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.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);

View File

@ -0,0 +1,23 @@
using IW4MAdmin.Application.Misc;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Factories
{
/// <summary>
/// implementation of IConfigurationHandlerFactory
/// provides base functionality to create configuration handlers
/// </summary>
public class ConfigurationHandlerFactory : IConfigurationHandlerFactory
{
/// <summary>
/// creates a base configuration handler
/// </summary>
/// <typeparam name="T">base configuration type</typeparam>
/// <param name="name">name of the config file</param>
/// <returns></returns>
public IConfigurationHandler<T> GetConfigurationHandler<T>(string name) where T : IBaseConfiguration
{
return new BaseConfigurationHandler<T>(name);
}
}
}

View File

@ -0,0 +1,38 @@
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
using System.Collections;
namespace IW4MAdmin.Application.Factories
{
/// <summary>
/// implementation of IGameServerInstanceFactory
/// </summary>
internal class GameServerInstanceFactory : IGameServerInstanceFactory
{
private readonly ITranslationLookup _translationLookup;
private readonly IRConConnectionFactory _rconConnectionFactory;
/// <summary>
/// base constructor
/// </summary>
/// <param name="translationLookup"></param>
/// <param name="rconConnectionFactory"></param>
public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory)
{
_translationLookup = translationLookup;
_rconConnectionFactory = rconConnectionFactory;
}
/// <summary>
/// creates an IW4MServer instance
/// </summary>
/// <param name="config">server configuration</param>
/// <param name="manager">application manager</param>
/// <returns></returns>
public Server CreateServer(ServerConfiguration config, IManager manager)
{
return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory);
}
}
}

View File

@ -0,0 +1,36 @@
using IW4MAdmin.Application.RCon;
using SharedLibraryCore.Interfaces;
using System.Text;
namespace IW4MAdmin.Application.Factories
{
/// <summary>
/// implementation of IRConConnectionFactory
/// </summary>
internal class RConConnectionFactory : IRConConnectionFactory
{
private static readonly Encoding gameEncoding = Encoding.GetEncoding("windows-1252");
private readonly ILogger _logger;
/// <summary>
/// Base constructor
/// </summary>
/// <param name="logger"></param>
public RConConnectionFactory(ILogger logger)
{
_logger = logger;
}
/// <summary>
/// creates a new rcon connection instance
/// </summary>
/// <param name="ipAddress">ip address of the server</param>
/// <param name="port">port of the server</param>
/// <param name="password">rcon password of the server</param>
/// <returns></returns>
public IRConConnection CreateConnection(string ipAddress, int port, string password)
{
return new RConConnection(ipAddress, port, password, _logger, gameEncoding);
}
}
}

View File

@ -27,15 +27,14 @@ namespace IW4MAdmin
private GameLogEventDetection LogEvent;
private readonly ITranslationLookup _translationLookup;
private const int REPORT_FLAG_COUNT = 4;
private readonly IPluginImporter _pluginImporter;
private int lastGameTime = 0;
public int Id { get; private set; }
public IW4MServer(IManager mgr, ServerConfiguration cfg, ITranslationLookup lookup, IPluginImporter pluginImporter) : base(mgr, cfg)
public IW4MServer(IManager mgr, ServerConfiguration cfg, ITranslationLookup lookup,
IRConConnectionFactory connectionFactory) : base(mgr, connectionFactory, cfg)
{
_translationLookup = lookup;
_pluginImporter = pluginImporter;
}
override public async Task<EFClient> OnClientConnected(EFClient clientFromLog)
@ -66,6 +65,7 @@ namespace IW4MAdmin
client.Score = clientFromLog.Score;
client.Ping = clientFromLog.Ping;
client.CurrentServer = this;
client.State = ClientState.Connecting;
Clients[client.ClientNumber] = client;
#if DEBUG == true
@ -153,7 +153,7 @@ namespace IW4MAdmin
}
}
foreach (var plugin in _pluginImporter.ActivePlugins)
foreach (var plugin in Manager.Plugins)
{
try
{
@ -182,6 +182,11 @@ namespace IW4MAdmin
catch (Exception e)
{
lastException = e;
if (E.Origin != null)
{
E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
}
}
finally
@ -695,7 +700,7 @@ namespace IW4MAdmin
await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token);
}
foreach (var plugin in _pluginImporter.ActivePlugins)
foreach (var plugin in Manager.Plugins)
{
await plugin.OnUnloadAsync();
}

View File

@ -1,4 +1,6 @@
using IW4MAdmin.Application.Helpers;
using IW4MAdmin.Application.Factories;
using IW4MAdmin.Application.Helpers;
using IW4MAdmin.Application.IO;
using IW4MAdmin.Application.Migration;
using IW4MAdmin.Application.Misc;
using Microsoft.Extensions.DependencyInjection;
@ -8,7 +10,6 @@ using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
@ -266,27 +267,21 @@ namespace IW4MAdmin.Application
/// </summary>
private static IServiceCollection ConfigureServices()
{
var defaultLogger = new Logger("IW4MAdmin-Manager");
var pluginImporter = new PluginImporter(defaultLogger);
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
.AddSingleton(new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings") as IConfigurationHandler<ApplicationConfiguration>)
.AddSingleton(new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as IConfigurationHandler<CommandConfiguration>)
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration())
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>().Configuration() ?? new CommandConfiguration())
.AddSingleton<ILogger>(_serviceProvider => new Logger("IW4MAdmin-Manager"))
.AddSingleton<ILogger>(_serviceProvider => defaultLogger)
.AddSingleton<IPluginImporter, PluginImporter>()
.AddSingleton<IMiddlewareActionHandler, MiddlewareActionHandler>()
.AddTransient(_serviceProvider =>
{
var importer = _serviceProvider.GetRequiredService<IPluginImporter>();
var config = _serviceProvider.GetRequiredService<CommandConfiguration>();
var layout = _serviceProvider.GetRequiredService<ITranslationLookup>();
// todo: this is disgusting, but I need it until I can figure out a way to dynamically load the plugins without creating an instance.
return importer.CommandTypes.
Union(typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
.Where(_command => _command.BaseType == typeof(Command)))
.Select(_cmdType => Activator.CreateInstance(_cmdType, config, layout) as IManagerCommand);
})
.AddSingleton<IRConConnectionFactory, RConConnectionFactory>()
.AddSingleton<IGameServerInstanceFactory, GameServerInstanceFactory>()
.AddSingleton<IConfigurationHandlerFactory, ConfigurationHandlerFactory>()
.AddSingleton(_serviceProvider =>
{
var config = _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration();
@ -295,6 +290,35 @@ namespace IW4MAdmin.Application
})
.AddSingleton<IManager, ApplicationManager>();
// register the native commands
foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
.Where(_command => _command.BaseType == typeof(Command)))
{
defaultLogger.WriteInfo($"Registered native command type {commandType.Name}");
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
}
// register the plugin implementations
var pluginImplementations = pluginImporter.DiscoverAssemblyPluginImplementations();
foreach (var pluginType in pluginImplementations.Item1)
{
defaultLogger.WriteInfo($"Registered plugin type {pluginType.FullName}");
serviceCollection.AddSingleton(typeof(IPlugin), pluginType);
}
// register the plugin commands
foreach (var commandType in pluginImplementations.Item2)
{
defaultLogger.WriteInfo($"Registered plugin command type {commandType.FullName}");
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
}
// register any script plugins
foreach (var scriptPlugin in pluginImporter.DiscoverScriptPlugins())
{
serviceCollection.AddSingleton(scriptPlugin);
}
return serviceCollection;
}
}

View File

@ -34,6 +34,11 @@ namespace IW4MAdmin.Application.Migration
{
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Log"));
}
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Localization")))
{
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Localization"));
}
}
/// <summary>

View File

@ -1,12 +1,17 @@
using Newtonsoft.Json;
using SharedLibraryCore;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using System;
using System.IO;
using System.Threading.Tasks;
namespace SharedLibraryCore.Configuration
namespace IW4MAdmin.Application.Misc
{
/// <summary>
/// default implementation of IConfigurationHandler
/// </summary>
/// <typeparam name="T">base configuration type</typeparam>
public class BaseConfigurationHandler<T> : IConfigurationHandler<T> where T : IBaseConfiguration
{
T _configuration;

View File

@ -1,12 +1,14 @@
using SharedLibraryCore;
using IW4MAdmin.Application.IO;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace IW4MAdmin.Application
{
class Logger : ILogger
public class Logger : ILogger
{
enum LogType
{
@ -77,9 +79,7 @@ namespace IW4MAdmin.Application
{
#if DEBUG
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
Console.WriteLine(LogLine);
//File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}");
//Debug.WriteLine(msg);
Console.WriteLine(msg);
#else
if (type == LogType.Error || type == LogType.Verbose)
{

View File

@ -5,111 +5,84 @@ using System.Reflection;
using SharedLibraryCore.Interfaces;
using System.Linq;
using SharedLibraryCore;
using IW4MAdmin.Application.Misc;
namespace IW4MAdmin.Application.Helpers
{
/// <summary>
/// implementation of IPluginImporter
/// discovers plugins and script plugins
/// </summary>
public class PluginImporter : IPluginImporter
{
public IList<Type> CommandTypes { get; private set; } = new List<Type>();
public IList<IPlugin> ActivePlugins { get; private set; } = new List<IPlugin>();
public IList<Assembly> PluginAssemblies { get; private set; } = new List<Assembly>();
public IList<Assembly> Assemblies { get; private set; } = new List<Assembly>();
private static readonly string PLUGIN_DIR = "Plugins";
private readonly ILogger _logger;
private readonly ITranslationLookup _translationLookup;
public PluginImporter(ILogger logger, ITranslationLookup translationLookup)
public PluginImporter(ILogger logger)
{
_logger = logger;
_translationLookup = translationLookup;
Load();
}
/// <summary>
/// Loads all the assembly and javascript plugins
/// discovers all the script plugins in the plugins dir
/// </summary>
private void Load()
/// <returns></returns>
public IEnumerable<IPlugin> DiscoverScriptPlugins()
{
string pluginDir = $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}";
string[] dllFileNames = null;
string[] scriptFileNames = null;
string pluginDir = $"{Utilities.OperatingDirectory}{PLUGIN_DIR}{Path.DirectorySeparatorChar}";
if (Directory.Exists(pluginDir))
{
dllFileNames = Directory.GetFiles($"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}", "*.dll");
scriptFileNames = Directory.GetFiles($"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}", "*.js");
}
string[] scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js");
else
{
dllFileNames = new string[0];
scriptFileNames = new string[0];
}
_logger.WriteInfo($"Discovered {scriptPluginFiles.Length} potential script plugins");
if (dllFileNames.Length == 0 &&
scriptFileNames.Length == 0)
{
_logger.WriteDebug(_translationLookup["PLUGIN_IMPORTER_NOTFOUND"]);
return;
}
// load up the script plugins
foreach (string fileName in scriptFileNames)
{
var plugin = new ScriptPlugin(fileName);
_logger.WriteDebug($"Loaded script plugin \"{ plugin.Name }\" [{plugin.Version}]");
ActivePlugins.Add(plugin);
}
ICollection<Assembly> assemblies = new List<Assembly>(dllFileNames.Length);
foreach (string dllFile in dllFileNames)
{
assemblies.Add(Assembly.LoadFrom(dllFile));
}
int LoadedCommands = 0;
foreach (Assembly Plugin in assemblies)
{
if (Plugin != null)
if (scriptPluginFiles.Length > 0)
{
Assemblies.Add(Plugin);
Type[] types = Plugin.GetTypes();
foreach (Type assemblyType in types)
foreach (string fileName in scriptPluginFiles)
{
if (assemblyType.IsClass && assemblyType.BaseType == typeof(Command))
{
CommandTypes.Add(assemblyType);
_logger.WriteDebug($"{_translationLookup["PLUGIN_IMPORTER_REGISTERCMD"]} \"{assemblyType.Name}\"");
LoadedCommands++;
continue;
}
try
{
if (assemblyType.GetInterface("IPlugin", false) == null)
continue;
var notifyObject = Activator.CreateInstance(assemblyType);
IPlugin newNotify = (IPlugin)notifyObject;
if (ActivePlugins.FirstOrDefault(x => x.Name == newNotify.Name) == null)
{
ActivePlugins.Add(newNotify);
PluginAssemblies.Add(Plugin);
_logger.WriteDebug($"Loaded plugin \"{newNotify.Name}\" [{newNotify.Version}]");
}
}
catch (Exception e)
{
_logger.WriteWarning(_translationLookup["PLUGIN_IMPORTER_ERROR"].FormatExt(Plugin.Location));
_logger.WriteDebug(e.GetExceptionInfo());
}
_logger.WriteInfo($"Discovered script plugin {fileName}");
var plugin = new ScriptPlugin(fileName);
yield return plugin;
}
}
}
}
_logger.WriteInfo($"Loaded {ActivePlugins.Count} plugins and registered {LoadedCommands} plugin commands.");
/// <summary>
/// discovers all the C# assembly plugins and commands
/// </summary>
/// <returns></returns>
public (IEnumerable<Type>, IEnumerable<Type>) DiscoverAssemblyPluginImplementations()
{
string pluginDir = $"{Utilities.OperatingDirectory}{PLUGIN_DIR}{Path.DirectorySeparatorChar}";
var pluginTypes = Enumerable.Empty<Type>();
var commandTypes = Enumerable.Empty<Type>();
if (Directory.Exists(pluginDir))
{
var dllFileNames = Directory.GetFiles(pluginDir, "*.dll");
_logger.WriteInfo($"Discovered {dllFileNames.Length} potential plugin assemblies");
if (dllFileNames.Length > 0)
{
var assemblies = dllFileNames.Select(_name => Assembly.LoadFrom(_name));
pluginTypes = assemblies
.SelectMany(_asm => _asm.GetTypes())
.Where(_assemblyType => _assemblyType.GetInterface(nameof(IPlugin), false) != null);
_logger.WriteInfo($"Discovered {pluginTypes.Count()} plugin implementations");
commandTypes = assemblies
.SelectMany(_asm => _asm.GetTypes())
.Where(_assemblyType => _assemblyType.IsClass && _assemblyType.BaseType == typeof(Command));
_logger.WriteInfo($"Discovered {commandTypes.Count()} plugin commands");
}
}
return (pluginTypes, commandTypes);
}
}
}

View File

@ -1,15 +1,19 @@
using Jint;
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SharedLibraryCore
namespace IW4MAdmin.Application.Misc
{
/// <summary>
/// implementation of IPlugin
/// used to proxy script plugin requests
/// </summary>
public class ScriptPlugin : IPlugin
{
public string Name { get; set; }
@ -45,7 +49,6 @@ namespace SharedLibraryCore
_onProcessing.Dispose();
}
public async Task Initialize(IManager manager)
{
await _onProcessing.WaitAsync();

View File

@ -0,0 +1,29 @@
using System;
using System.Net.Sockets;
using System.Threading;
namespace IW4MAdmin.Application.RCon
{
/// <summary>
/// used to keep track of the udp connection state
/// </summary>
internal class ConnectionState
{
~ConnectionState()
{
OnComplete.Dispose();
OnSentData.Dispose();
OnReceivedData.Dispose();
}
public int ConnectionAttempts { get; set; }
const int BufferSize = 4096;
public readonly byte[] ReceiveBuffer = new byte[BufferSize];
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
public readonly ManualResetEventSlim OnSentData = new ManualResetEventSlim(false);
public readonly ManualResetEventSlim OnReceivedData = new ManualResetEventSlim(false);
public SocketAsyncEventArgs SendEventArgs { get; set; } = new SocketAsyncEventArgs();
public SocketAsyncEventArgs ReceiveEventArgs { get; set; } = new SocketAsyncEventArgs();
public DateTime LastQuery { get; set; } = DateTime.Now;
}
}

View File

@ -1,58 +1,41 @@
using SharedLibraryCore.Exceptions;
using SharedLibraryCore;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.RCon;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SharedLibraryCore.RCon
namespace IW4MAdmin.Application.RCon
{
class ConnectionState
{
~ConnectionState()
{
OnComplete.Dispose();
OnSentData.Dispose();
OnReceivedData.Dispose();
}
public int ConnectionAttempts { get; set; }
const int BufferSize = 4096;
public readonly byte[] ReceiveBuffer = new byte[BufferSize];
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
public readonly ManualResetEventSlim OnSentData = new ManualResetEventSlim(false);
public readonly ManualResetEventSlim OnReceivedData = new ManualResetEventSlim(false);
public SocketAsyncEventArgs SendEventArgs { get; set; } = new SocketAsyncEventArgs();
public SocketAsyncEventArgs ReceiveEventArgs { get; set; } = new SocketAsyncEventArgs();
public DateTime LastQuery { get; set; } = DateTime.Now;
}
public class Connection
/// <summary>
/// implementation of IRConConnection
/// </summary>
public class RConConnection : IRConConnection
{
static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new ConcurrentDictionary<EndPoint, ConnectionState>();
public IPEndPoint Endpoint { get; private set; }
public string RConPassword { get; private set; }
private readonly ILogger Log;
private IRConParserConfiguration Config;
private readonly Encoding defaultEncoding;
private IRConParserConfiguration config;
private readonly ILogger _log;
private readonly Encoding _gameEncoding;
public Connection(string ipAddress, int port, string password, ILogger log, IRConParserConfiguration config)
public RConConnection(string ipAddress, int port, string password, ILogger log, Encoding gameEncoding)
{
Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
defaultEncoding = Encoding.GetEncoding("windows-1252");
_gameEncoding = gameEncoding;
RConPassword = password;
Log = log;
Config = config;
_log = log;
}
public void SetConfiguration(IRConParserConfiguration config)
{
Config = config;
this.config = config;
}
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
@ -65,7 +48,7 @@ namespace SharedLibraryCore.RCon
var connectionState = ActiveQueries[this.Endpoint];
#if DEBUG == true
Log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]");
_log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]");
#endif
// enter the semaphore so only one query is sent at a time per server.
await connectionState.OnComplete.WaitAsync();
@ -80,17 +63,17 @@ namespace SharedLibraryCore.RCon
connectionState.LastQuery = DateTime.Now;
#if DEBUG == true
Log.WriteDebug($"Semaphore has been released [{this.Endpoint}]");
Log.WriteDebug($"Query [{this.Endpoint},{type.ToString()},{parameters}]");
_log.WriteDebug($"Semaphore has been released [{this.Endpoint}]");
_log.WriteDebug($"Query [{this.Endpoint},{type.ToString()},{parameters}]");
#endif
byte[] payload = null;
bool waitForResponse = Config.WaitForResponse;
bool waitForResponse = config.WaitForResponse;
string convertEncoding(string text)
{
byte[] convertedBytes = Utilities.EncodingType.GetBytes(text);
return defaultEncoding.GetString(convertedBytes);
return _gameEncoding.GetString(convertedBytes);
}
try
@ -102,25 +85,25 @@ namespace SharedLibraryCore.RCon
{
case StaticHelpers.QueryType.GET_DVAR:
waitForResponse |= true;
payload = string.Format(Config.CommandPrefixes.RConGetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray();
payload = string.Format(config.CommandPrefixes.RConGetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray();
break;
case StaticHelpers.QueryType.SET_DVAR:
payload = string.Format(Config.CommandPrefixes.RConSetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray();
payload = string.Format(config.CommandPrefixes.RConSetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray();
break;
case StaticHelpers.QueryType.COMMAND:
payload = string.Format(Config.CommandPrefixes.RConCommand, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray();
payload = string.Format(config.CommandPrefixes.RConCommand, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray();
break;
case StaticHelpers.QueryType.GET_STATUS:
waitForResponse |= true;
payload = (Config.CommandPrefixes.RConGetStatus + '\0').Select(Convert.ToByte).ToArray();
payload = (config.CommandPrefixes.RConGetStatus + '\0').Select(Convert.ToByte).ToArray();
break;
case StaticHelpers.QueryType.GET_INFO:
waitForResponse |= true;
payload = (Config.CommandPrefixes.RConGetInfo + '\0').Select(Convert.ToByte).ToArray();
payload = (config.CommandPrefixes.RConGetInfo + '\0').Select(Convert.ToByte).ToArray();
break;
case StaticHelpers.QueryType.COMMAND_STATUS:
waitForResponse |= true;
payload = string.Format(Config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0").Select(Convert.ToByte).ToArray();
payload = string.Format(config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0").Select(Convert.ToByte).ToArray();
break;
}
}
@ -148,7 +131,7 @@ namespace SharedLibraryCore.RCon
connectionState.OnReceivedData.Reset();
connectionState.ConnectionAttempts++;
#if DEBUG == true
Log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
_log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
#endif
try
{
@ -182,7 +165,7 @@ namespace SharedLibraryCore.RCon
}
}
string responseString = defaultEncoding.GetString(response, 0, response.Length) + '\n';
string responseString = _gameEncoding.GetString(response, 0, response.Length) + '\n';
// note: not all games respond if the pasword is wrong or not set
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
@ -195,7 +178,7 @@ namespace SharedLibraryCore.RCon
throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_NOTSET"]);
}
if (responseString.Contains(Config.ServerNotRunningResponse))
if (responseString.Contains(config.ServerNotRunningResponse))
{
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString()));
}
@ -269,7 +252,7 @@ namespace SharedLibraryCore.RCon
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
{
#if DEBUG == true
Log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
_log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
#endif
ActiveQueries[this.Endpoint].OnReceivedData.Set();
}
@ -277,7 +260,7 @@ namespace SharedLibraryCore.RCon
private void OnDataSent(object sender, SocketAsyncEventArgs e)
{
#if DEBUG == true
Log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
_log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
#endif
ActiveQueries[this.Endpoint].OnSentData.Set();
}

View File

@ -65,13 +65,13 @@ namespace IW4MAdmin.Application.RconParsers
public bool CanGenerateLogPath { get; set; } = true;
public string Name { get; set; } = "Call of Duty";
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command)
{
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
return response.Skip(1).ToArray();
}
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName)
{
string[] lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
string response = string.Join('\n', lineSplit.Skip(1));
@ -105,7 +105,7 @@ namespace IW4MAdmin.Application.RconParsers
};
}
public virtual async Task<(List<EFClient>, string)> GetStatusAsync(Connection connection)
public virtual async Task<(List<EFClient>, string)> GetStatusAsync(IRConConnection connection)
{
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
#if DEBUG
@ -132,7 +132,7 @@ namespace IW4MAdmin.Application.RconParsers
return map;
}
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue)
{
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, $"{dvarName} {dvarValue}")).Length > 0;
}

View File

@ -6,8 +6,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C8F3945-0AEF-4949-A1F7-B18E952E50BC}"
ProjectSection(SolutionItems) = preProject
GameFiles\IW4x\userraw\_commands.gsc = GameFiles\IW4x\userraw\_commands.gsc
GameFiles\IW4x\userraw\_customcallbacks.gsc = GameFiles\IW4x\userraw\_customcallbacks.gsc
GameFiles\IW4x\userraw\scripts\_commands.gsc = GameFiles\IW4x\userraw\scripts\_commands.gsc
GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc = GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc
azure-pipelines.yml = azure-pipelines.yml
PostPublish.ps1 = PostPublish.ps1
README.md = README.md
@ -57,6 +57,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveRadar", "Plugins\LiveRadar\LiveRadar.csproj", "{00A1FED2-2254-4AF7-A5DB-2357FA7C88CD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3065279E-17F0-4CE0-AF5B-014E04263D77}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationTests", "Tests\ApplicationTests\ApplicationTests.csproj", "{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -405,6 +409,29 @@ Global
{00A1FED2-2254-4AF7-A5DB-2357FA7C88CD}.Release|x64.Build.0 = Release|Any CPU
{00A1FED2-2254-4AF7-A5DB-2357FA7C88CD}.Release|x86.ActiveCfg = Release|Any CPU
{00A1FED2-2254-4AF7-A5DB-2357FA7C88CD}.Release|x86.Build.0 = Release|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Debug|x64.ActiveCfg = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Debug|x64.Build.0 = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Debug|x86.ActiveCfg = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Debug|x86.Build.0 = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Prerelease|x64.ActiveCfg = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Prerelease|x64.Build.0 = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Prerelease|x86.ActiveCfg = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Prerelease|x86.Build.0 = Debug|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Release|Any CPU.Build.0 = Release|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Release|x64.ActiveCfg = Release|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Release|x64.Build.0 = Release|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Release|x86.ActiveCfg = Release|Any CPU
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -421,6 +448,7 @@ Global
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B} = {A848FCF1-8527-4AA8-A1AA-50D29695C678}
{F5815359-CFC7-44B4-9A3B-C04BACAD5836} = {26E8B310-269E-46D4-A612-24601F16065F}
{00A1FED2-2254-4AF7-A5DB-2357FA7C88CD} = {26E8B310-269E-46D4-A612-24601F16065F}
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B} = {3065279E-17F0-4CE0-AF5B-014E04263D77}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}

View File

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.6" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.7" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -20,14 +20,19 @@ namespace AutomessageFeed
public string Author => "RaidMax";
private Configuration _configuration;
private int _currentFeedItem;
private readonly IConfigurationHandler<Configuration> _configurationHandler;
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
{
_configurationHandler = configurationHandlerFactory.GetConfigurationHandler<Configuration>("AutomessageFeedPluginSettings");
}
private async Task<string> GetNextFeedItem(Server server)
{
var items = new List<string>();
using (var reader = XmlReader.Create(_configuration.FeedUrl, new XmlReaderSettings() { Async = true }))
using (var reader = XmlReader.Create(_configurationHandler.Configuration().FeedUrl, new XmlReaderSettings() { Async = true }))
{
var feedReader = new RssFeedReader(reader);
@ -43,7 +48,7 @@ namespace AutomessageFeed
}
}
if (_currentFeedItem < items.Count && (_configuration.MaxFeedItems == 0 || _currentFeedItem < _configuration.MaxFeedItems))
if (_currentFeedItem < items.Count && (_configurationHandler.Configuration().MaxFeedItems == 0 || _currentFeedItem < _configurationHandler.Configuration().MaxFeedItems))
{
_currentFeedItem++;
return items[_currentFeedItem - 1];
@ -60,15 +65,12 @@ namespace AutomessageFeed
public async Task OnLoadAsync(IManager manager)
{
var cfg = new BaseConfigurationHandler<Configuration>("AutomessageFeedPluginSettings");
if (cfg.Configuration() == null)
if (_configurationHandler.Configuration() == null)
{
cfg.Set((Configuration)new Configuration().Generate());
await cfg.Save();
_configurationHandler.Set((Configuration)new Configuration().Generate());
await _configurationHandler.Save();
}
_configuration = cfg.Configuration();
manager.GetMessageTokens().Add(new MessageToken("FEED", GetNextFeedItem));
}

View File

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

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using LiveRadar.Configuration;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using SharedLibraryCore;
using SharedLibraryCore.Dtos;
@ -16,10 +17,12 @@ namespace LiveRadar.Web.Controllers
};
private readonly IManager _manager;
private readonly LiveRadarConfiguration _config;
public RadarController(IManager manager) : base(manager)
public RadarController(IManager manager, IConfigurationHandlerFactory configurationHandlerFactory) : base(manager)
{
_manager = manager;
_config = configurationHandlerFactory.GetConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration").Configuration() ?? new LiveRadarConfiguration();
}
[HttpGet]
@ -45,7 +48,7 @@ namespace LiveRadar.Web.Controllers
public IActionResult Map(long? serverId = null)
{
var server = serverId == null ? _manager.GetServers().FirstOrDefault() : _manager.GetServers().FirstOrDefault(_server => _server.EndPoint == serverId);
var map = Plugin.Config.Configuration().Maps.FirstOrDefault(_map => _map.Name == server.CurrentMap.Name);
var map = _config.Maps.FirstOrDefault(_map => _map.Name == server.CurrentMap.Name);
if (map != null)
{

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.6" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.7" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@ -16,7 +16,12 @@ namespace LiveRadar
public string Author => "RaidMax";
internal static BaseConfigurationHandler<LiveRadarConfiguration> Config;
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
{
_configurationHandler = configurationHandlerFactory.GetConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration");
}
public Task OnEventAsync(GameEvent E, Server S)
{
@ -36,7 +41,7 @@ namespace LiveRadar
}
}
catch(Exception e)
catch (Exception e)
{
S.Logger.WriteWarning($"Could not parse live radar output: {e.Data}");
S.Logger.WriteDebug(e.GetExceptionInfo());
@ -49,12 +54,10 @@ namespace LiveRadar
public async Task OnLoadAsync(IManager manager)
{
// load custom configuration
Config = new BaseConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration");
if (Config.Configuration() == null)
if (_configurationHandler.Configuration() == null)
{
Config.Set((LiveRadarConfiguration)new LiveRadarConfiguration().Generate());
await Config.Save();
_configurationHandler.Set((LiveRadarConfiguration)new LiveRadarConfiguration().Generate());
await _configurationHandler.Save();
}
manager.GetPageList().Pages.Add(Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_RADAR_TITLE"], "/Radar/All");

View File

@ -23,7 +23,7 @@
</Target>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.6" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.7" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -4,7 +4,6 @@ using System.Threading.Tasks;
using IW4MAdmin.Plugins.Login.Commands;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
@ -20,11 +19,16 @@ namespace IW4MAdmin.Plugins.Login
public string Author => "RaidMax";
public static ConcurrentDictionary<int, bool> AuthorizedClients { get; private set; }
private Configuration Config;
private readonly IConfigurationHandler<Configuration> _configHandler;
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
{
_configHandler = configurationHandlerFactory.GetConfigurationHandler<Configuration>("LoginPluginSettings");
}
public Task OnEventAsync(GameEvent E, Server S)
{
if (E.IsRemote || Config.RequirePrivilegedClientLogin == false)
if (E.IsRemote || _configHandler.Configuration().RequirePrivilegedClientLogin == false)
return Task.CompletedTask;
if (E.Type == GameEvent.EventType.Connect)
@ -72,14 +76,11 @@ namespace IW4MAdmin.Plugins.Login
{
AuthorizedClients = new ConcurrentDictionary<int, bool>();
var cfg = new BaseConfigurationHandler<Configuration>("LoginPluginSettings");
if (cfg.Configuration() == null)
if (_configHandler.Configuration() == null)
{
cfg.Set((Configuration)new Configuration().Generate());
await cfg.Save();
_configHandler.Set((Configuration)new Configuration().Generate());
await _configHandler.Save();
}
Config = cfg.Configuration();
}
public Task OnTickAsync(Server S) => Task.CompletedTask;

View File

@ -17,18 +17,23 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
public string Author => "RaidMax";
BaseConfigurationHandler<Configuration> Settings;
private readonly IConfigurationHandler<Configuration> _configHandler;
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
{
_configHandler = configurationHandlerFactory.GetConfigurationHandler<Configuration>("ProfanityDetermentSettings");
}
public Task OnEventAsync(GameEvent E, Server S)
{
if (!Settings.Configuration().EnableProfanityDeterment)
if (!_configHandler.Configuration().EnableProfanityDeterment)
return Task.CompletedTask;
if (E.Type == GameEvent.EventType.Connect)
{
E.Origin.SetAdditionalProperty("_profanityInfringements", 0);
var objectionalWords = Settings.Configuration().OffensiveWords;
var objectionalWords = _configHandler.Configuration().OffensiveWords;
var matchedFilters = new List<string>();
bool containsObjectionalWord = false;
@ -51,7 +56,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
AutomatedOffense = $"{E.Origin.Name} - {string.Join(",", matchedFilters)}"
}
};
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, sender);
E.Origin.Kick(_configHandler.Configuration().ProfanityKickMessage, sender);
};
}
@ -62,7 +67,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
if (E.Type == GameEvent.EventType.Say)
{
var objectionalWords = Settings.Configuration().OffensiveWords;
var objectionalWords = _configHandler.Configuration().OffensiveWords;
bool containsObjectionalWord = false;
var matchedFilters = new List<string>();
@ -88,15 +93,15 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
}
};
if (profanityInfringments >= Settings.Configuration().KickAfterInfringementCount)
if (profanityInfringments >= _configHandler.Configuration().KickAfterInfringementCount)
{
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, sender);
E.Origin.Kick(_configHandler.Configuration().ProfanityKickMessage, sender);
}
else if (profanityInfringments < Settings.Configuration().KickAfterInfringementCount)
else if (profanityInfringments < _configHandler.Configuration().KickAfterInfringementCount)
{
E.Origin.SetAdditionalProperty("_profanityInfringements", profanityInfringments + 1);
E.Origin.Warn(Settings.Configuration().ProfanityWarningMessage, sender);
E.Origin.Warn(_configHandler.Configuration().ProfanityWarningMessage, sender);
}
}
}
@ -105,12 +110,10 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
public async Task OnLoadAsync(IManager manager)
{
// load custom configuration
Settings = new BaseConfigurationHandler<Configuration>("ProfanityDetermentSettings");
if (Settings.Configuration() == null)
if (_configHandler.Configuration() == null)
{
Settings.Set((Configuration)new Configuration().Generate());
await Settings.Save();
_configHandler.Set((Configuration)new Configuration().Generate());
await _configHandler.Save();
}
}

View File

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

View File

@ -17,7 +17,7 @@ namespace IW4MAdmin.Plugins.Stats.Config
public IDictionary<DetectionType, DistributionConfiguration> DetectionDistributions { get; set; }
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; }
public string Name() => "Stats";
public string Name() => "StatsPluginSettings";
public IBaseConfiguration Generate()
{
EnableAntiCheat = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);

View File

@ -21,8 +21,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var killstreakMessage = Plugin.Config.Configuration().KillstreakMessages;
var deathstreakMessage = Plugin.Config.Configuration().DeathstreakMessages;
string message = killstreakMessage.FirstOrDefault(m => m.Count == killStreak)?.Message;
message = message ?? deathstreakMessage.FirstOrDefault(m => m.Count == deathStreak)?.Message;
string message = killstreakMessage?.FirstOrDefault(m => m.Count == killStreak)?.Message;
message = message ?? deathstreakMessage?.FirstOrDefault(m => m.Count == deathStreak)?.Message;
return message ?? "";
}
}

View File

@ -3,7 +3,6 @@ using IW4MAdmin.Plugins.Stats.Helpers;
using IW4MAdmin.Plugins.Stats.Models;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
@ -13,7 +12,6 @@ using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats
@ -28,12 +26,17 @@ namespace IW4MAdmin.Plugins.Stats
public static StatManager Manager { get; private set; }
public static IManager ServerManager;
public static BaseConfigurationHandler<StatsConfiguration> Config { get; private set; }
public static IConfigurationHandler<StatsConfiguration> Config { get; private set; }
#if DEBUG
int scriptDamageCount;
int scriptKillCount;
#endif
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
{
Config = configurationHandlerFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
}
public async Task OnEventAsync(GameEvent E, Server S)
{
switch (E.Type)
@ -53,16 +56,16 @@ namespace IW4MAdmin.Plugins.Stats
if (!string.IsNullOrEmpty(E.Data) &&
E.Origin.ClientId > 1)
{
await Manager.AddMessageAsync(E.Origin.ClientId, StatManager.GetIdForServer(E.Owner), E.Data);
await Manager.AddMessageAsync(E.Origin.ClientId, StatManager.GetIdForServer(S), E.Data);
}
break;
case GameEvent.EventType.MapChange:
Manager.SetTeamBased(StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm");
Manager.ResetKillstreaks(E.Owner);
await Manager.Sync(E.Owner);
Manager.SetTeamBased(StatManager.GetIdForServer(S), S.Gametype != "dm");
Manager.ResetKillstreaks(S);
await Manager.Sync(S);
break;
case GameEvent.EventType.MapEnd:
await Manager.Sync(E.Owner);
await Manager.Sync(S);
break;
case GameEvent.EventType.JoinTeam:
break;
@ -82,7 +85,7 @@ namespace IW4MAdmin.Plugins.Stats
break;
case GameEvent.EventType.ScriptKill:
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if ((E.Owner.CustomCallback || ShouldOverrideAnticheatSetting(E.Owner)) && killInfo.Length >= 18 && !ShouldIgnoreEvent(E.Origin, E.Target))
if ((S.CustomCallback || ShouldOverrideAnticheatSetting(S)) && killInfo.Length >= 18 && !ShouldIgnoreEvent(E.Origin, E.Target))
{
// this treats "world" damage as self damage
if (IsWorldDamage(E.Origin))
@ -95,7 +98,7 @@ namespace IW4MAdmin.Plugins.Stats
S.Logger.WriteInfo($"Start ScriptKill {scriptKillCount}");
#endif
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(S), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15], killInfo[16], killInfo[17]);
#if DEBUG
@ -105,7 +108,7 @@ namespace IW4MAdmin.Plugins.Stats
else
{
E.Owner.Logger.WriteDebug("Skipping script kill as it is ignored or data in customcallbacks is outdated/missing");
S.Logger.WriteDebug("Skipping script kill as it is ignored or data in customcallbacks is outdated/missing");
}
break;
case GameEvent.EventType.Kill:
@ -129,12 +132,12 @@ namespace IW4MAdmin.Plugins.Stats
E.Origin = E.Target;
}
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, StatManager.GetIdForServer(E.Owner));
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, StatManager.GetIdForServer(S));
}
break;
case GameEvent.EventType.ScriptDamage:
killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if ((E.Owner.CustomCallback || ShouldOverrideAnticheatSetting(E.Owner)) && killInfo.Length >= 18 && !ShouldIgnoreEvent(E.Origin, E.Target))
if ((S.CustomCallback || ShouldOverrideAnticheatSetting(S)) && killInfo.Length >= 18 && !ShouldIgnoreEvent(E.Origin, E.Target))
{
// this treats "world" damage as self damage
if (IsWorldDamage(E.Origin))
@ -147,7 +150,7 @@ namespace IW4MAdmin.Plugins.Stats
S.Logger.WriteInfo($"Start ScriptDamage {scriptDamageCount}");
#endif
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(S), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15], killInfo[16], killInfo[17]);
#if DEBUG
@ -157,7 +160,7 @@ namespace IW4MAdmin.Plugins.Stats
else
{
E.Owner.Logger.WriteDebug("Skipping script damage as it is ignored or data in customcallbacks is outdated/missing");
S.Logger.WriteDebug("Skipping script damage as it is ignored or data in customcallbacks is outdated/missing");
}
break;
}
@ -166,7 +169,6 @@ namespace IW4MAdmin.Plugins.Stats
public async Task OnLoadAsync(IManager manager)
{
// load custom configuration
Config = new BaseConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
if (Config.Configuration() == null)
{
Config.Set((StatsConfiguration)new StatsConfiguration().Generate());
@ -518,7 +520,7 @@ namespace IW4MAdmin.Plugins.Stats
/// <returns></returns>
private bool ShouldIgnoreEvent(EFClient origin, EFClient target)
{
return ((origin?.NetworkId == 1 && target?.NetworkId == 1) || (origin?.ClientId <= 1 && target?.ClientId <= 1));
return ((origin?.NetworkId == 1 && target?.NetworkId == 1));
}
/// <summary>

View File

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

View File

@ -1,4 +1,5 @@
using IW4MAdmin.Application;
using IW4MAdmin.Application.Misc;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
using System;

View File

@ -1,4 +1,5 @@
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.RCon;
using System;
using System.Collections.Generic;
@ -14,7 +15,7 @@ namespace Tests
public override string Version => "test";
public override async Task<(List<EFClient>, string)> GetStatusAsync(Connection connection)
public override async Task<(List<EFClient>, string)> GetStatusAsync(IRConConnection connection)
{
var clientList = new List<EFClient>();

View File

@ -14,7 +14,7 @@
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.6" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.7" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@ -64,16 +64,19 @@ namespace IW4MAdmin.Plugins.Welcome
public string Name => "Welcome Plugin";
private BaseConfigurationHandler<WelcomeConfiguration> Config;
private readonly IConfigurationHandler<WelcomeConfiguration> _configHandler;
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
{
_configHandler = configurationHandlerFactory.GetConfigurationHandler<WelcomeConfiguration>("WelcomePluginSettings");
}
public async Task OnLoadAsync(IManager manager)
{
// load custom configuration
Config = new BaseConfigurationHandler<WelcomeConfiguration>("WelcomePluginSettings");
if (Config.Configuration() == null)
if (_configHandler.Configuration() == null)
{
Config.Set((WelcomeConfiguration)new WelcomeConfiguration().Generate());
await Config.Save();
_configHandler.Set((WelcomeConfiguration)new WelcomeConfiguration().Generate());
await _configHandler.Save();
}
}
@ -87,9 +90,9 @@ namespace IW4MAdmin.Plugins.Welcome
{
EFClient newPlayer = E.Origin;
if (newPlayer.Level >= Permission.Trusted && !E.Origin.Masked)
E.Owner.Broadcast(await ProcessAnnouncement(Config.Configuration().PrivilegedAnnouncementMessage, newPlayer));
E.Owner.Broadcast(await ProcessAnnouncement(_configHandler.Configuration().PrivilegedAnnouncementMessage, newPlayer));
newPlayer.Tell(await ProcessAnnouncement(Config.Configuration().UserWelcomeMessage, newPlayer));
newPlayer.Tell(await ProcessAnnouncement(_configHandler.Configuration().UserWelcomeMessage, newPlayer));
if (newPlayer.Level == Permission.Flagged)
{
@ -107,7 +110,7 @@ namespace IW4MAdmin.Plugins.Welcome
E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7({penaltyReason}) has joined!");
}
else
E.Owner.Broadcast(await ProcessAnnouncement(Config.Configuration().UserAnnouncementMessage, newPlayer));
E.Owner.Broadcast(await ProcessAnnouncement(_configHandler.Configuration().UserAnnouncementMessage, newPlayer));
}
}

View File

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

View File

@ -500,18 +500,18 @@ namespace SharedLibraryCore.Commands
public override Task ExecuteAsync(GameEvent E)
{
String cmd = E.Data.Trim();
string cmd = E.Data.Trim();
if (cmd.Length > 2)
{
bool found = false;
foreach (Command C in E.Owner.Manager.GetCommands())
foreach (var command in E.Owner.Manager.GetCommands())
{
if (C.Name == cmd.ToLower() ||
C.Alias == cmd.ToLower())
if (command.Name == cmd.ToLower() ||
command.Alias == cmd.ToLower())
{
E.Origin.Tell($"[^3{C.Name}^7] {C.Description}");
E.Origin.Tell(C.Syntax);
E.Origin.Tell($"[^3{command.Name}^7] {command.Description}");
E.Origin.Tell(command.Syntax);
found = true;
}
}

View File

@ -56,11 +56,11 @@ namespace SharedLibraryCore.Configuration
{
var loc = Utilities.CurrentLocalization.LocalizationIndex;
var parserVersions = rconParsers.Select(_parser => _parser.Name).ToArray();
var selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_RCON_PARSER_VERSION"]} ({IPAddress}:{Port})", $"{loc["SETUP_PROMPT_DEFAULT"]} (Call of Duty)", null, parserVersions);
var selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_RCON_PARSER_VERSION"]} ({IPAddress}:{Port})", parserVersions[0], null, parserVersions);
if (selection.Item1 >= 0)
{
RConParserVersion = rconParsers.First(_parser => _parser.Name == selection.Item2).Version;
RConParserVersion = rconParsers.FirstOrDefault(_parser => _parser.Name == selection.Item2)?.Version;
if (selection.Item1 > 0 && !rconParsers[selection.Item1 - 1].CanGenerateLogPath)
{
@ -70,11 +70,11 @@ namespace SharedLibraryCore.Configuration
}
parserVersions = eventParsers.Select(_parser => _parser.Name).ToArray();
selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_EVENT_PARSER_VERSION"]} ({IPAddress}:{Port})", $"{loc["SETUP_PROMPT_DEFAULT"]} (Call of Duty)", null, parserVersions);
selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_EVENT_PARSER_VERSION"]} ({IPAddress}:{Port})", parserVersions[0], null, parserVersions);
if (selection.Item1 >= 0)
{
EventParserVersion = eventParsers.First(_parser => _parser.Name == selection.Item2).Version;
EventParserVersion = eventParsers.FirstOrDefault(_parser => _parser.Name == selection.Item2)?.Version;
}
}

View File

@ -26,12 +26,6 @@ namespace SharedLibraryCore.Configuration.Validation
RuleForEach(_server => _server.AutoMessages)
.NotEmpty();
RuleFor(_server => _server.RConParserVersion)
.NotEmpty();
RuleFor(_server => _server.EventParserVersion)
.NotEmpty();
RuleFor(_server => _server.ReservedSlotNumber)
.InclusiveBetween(0, 32);
}

View File

@ -0,0 +1,14 @@
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// defines the capabilities for providing a base path
/// unused as of now, will be used later during refactorying
/// </summary>
public interface IBasePathProvider
{
/// <summary>
/// working directory of IW4MAdmin
/// </summary>
string BasePath { get; }
}
}

View File

@ -0,0 +1,17 @@
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// defines the capabilities of the configuration handler factory
/// used to generate new instance of configuration handlers
/// </summary>
public interface IConfigurationHandlerFactory
{
/// <summary>
/// generates a new configuration handler
/// </summary>
/// <typeparam name="T">base configuration type</typeparam>
/// <param name="name">file name of configuration</param>
/// <returns>new configuration handler instance</returns>
IConfigurationHandler<T> GetConfigurationHandler<T>(string name) where T : IBaseConfiguration;
}
}

View File

@ -0,0 +1,18 @@
using SharedLibraryCore.Configuration;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// defines the capabilities of game server instance factory
/// </summary>
public interface IGameServerInstanceFactory
{
/// <summary>
/// creates the instance of a game server
/// </summary>
/// <param name="config">server configuration</param>
/// <param name="manager">application manager</param>
/// <returns></returns>
Server CreateServer(ServerConfiguration config, IManager manager);
}
}

View File

@ -2,7 +2,6 @@
using System.Threading.Tasks;
using SharedLibraryCore.Services;
using SharedLibraryCore.Configuration;
using System.Reflection;
using SharedLibraryCore.Database.Models;
using System.Threading;
using System.Collections;
@ -29,7 +28,10 @@ namespace SharedLibraryCore.Interfaces
/// </summary>
/// <returns>EventHandler for the manager</returns>
IEventHandler GetEventHandler();
IList<Assembly> GetPluginAssemblies();
/// <summary>
/// enumerates the registered plugin instances
/// </summary>
IEnumerable<IPlugin> Plugins { get; }
/// <summary>
/// provides a page list to add and remove from
/// </summary>

View File

@ -1,32 +1,23 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// Defines the capabilities of the plugin importer
/// defines the capabilities of the plugin importer
/// </summary>
public interface IPluginImporter
{
{
/// <summary>
/// Command types that are defined in plugin assemblies
/// discovers C# assembly plugin and command types
/// </summary>
IList<Type> CommandTypes { get; }
/// <returns>tuple of IPlugin implementation type definitions, and IManagerCommand type definitions</returns>
(IEnumerable<Type>, IEnumerable<Type>) DiscoverAssemblyPluginImplementations();
/// <summary>
/// The loaded plugins from plugin assemblies
/// discovers the script plugins
/// </summary>
IList<IPlugin> ActivePlugins { get; }
/// <summary>
/// Assemblies that contain plugins
/// </summary>
IList<Assembly> PluginAssemblies { get; }
/// <summary>
/// All assemblies in the plugin folder
/// </summary>
IList<Assembly> Assemblies { get; }
/// <returns>initialized script plugin collection</returns>
IEnumerable<IPlugin> DiscoverScriptPlugins();
}
}

View File

@ -0,0 +1,25 @@
using SharedLibraryCore.RCon;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// defines the capabilities of an RCon connection
/// </summary>
public interface IRConConnection
{
/// <summary>
/// sends a query with the instance of the rcon connection
/// </summary>
/// <param name="type">type of RCon query to perform</param>
/// <param name="parameters">optional parameter list</param>
/// <returns></returns>
Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "");
/// <summary>
/// sets the rcon parser configuration
/// </summary>
/// <param name="config">parser config</param>
void SetConfiguration(IRConParserConfiguration config);
}
}

View File

@ -0,0 +1,17 @@
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// defines the capabilities of an RCon connection factory
/// </summary>
public interface IRConConnectionFactory
{
/// <summary>
/// creates an rcon connection instance
/// </summary>
/// <param name="ipAddress">ip address of the server</param>
/// <param name="port">port of the server</param>
/// <param name="password"> password of the server</param>
/// <returns>instance of rcon connection</returns>
IRConConnection CreateConnection(string ipAddress, int port, string password);
}
}

View File

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.RCon;
using static SharedLibraryCore.Server;
namespace SharedLibraryCore.Interfaces
@ -15,7 +14,7 @@ namespace SharedLibraryCore.Interfaces
/// <param name="connection">RCon connection to retrieve with</param>
/// <param name="dvarName">name of DVAR</param>
/// <returns></returns>
Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName);
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName);
/// <summary>
/// set value of DVAR by name
@ -24,7 +23,7 @@ namespace SharedLibraryCore.Interfaces
/// <param name="dvarName">name of DVAR to set</param>
/// <param name="dvarValue">value to set DVAR to</param>
/// <returns></returns>
Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue);
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue);
/// <summary>
/// executes a console command on the server
@ -32,14 +31,14 @@ namespace SharedLibraryCore.Interfaces
/// <param name="connection">RCon connection to use</param>
/// <param name="command">console command to execute</param>
/// <returns></returns>
Task<string[]> ExecuteCommandAsync(Connection connection, string command);
Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command);
/// <summary>
/// get the list of connected clients from status response
/// </summary>
/// <param name="connection">RCon connection to use</param>
/// <returns>list of clients, and current map</returns>
Task<(List<EFClient>, string)> GetStatusAsync(Connection connection);
Task<(List<EFClient>, string)> GetStatusAsync(IRConConnection connection);
/// <summary>
/// stores the RCon configuration

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.RCon
namespace SharedLibraryCore.RCon
{
public class CommandPrefix
{

View File

@ -28,7 +28,7 @@ namespace SharedLibraryCore
T7 = 8
}
public Server(IManager mgr, ServerConfiguration config)
public Server(IManager mgr, IRConConnectionFactory rconConnectionFactory, ServerConfiguration config)
{
Password = config.Password;
IP = config.IPAddress;
@ -37,8 +37,7 @@ namespace SharedLibraryCore
Logger = Manager.GetLogger(this.EndPoint);
Logger.WriteInfo(this.ToString());
ServerConfig = config;
RemoteConnection = new RCon.Connection(IP, Port, Password, Logger, null);
RemoteConnection = rconConnectionFactory.CreateConnection(IP, Port, Password);
EventProcessing = new SemaphoreSlim(1, 1);
Clients = new List<EFClient>(new EFClient[18]);
Reports = new List<Report>();
@ -283,7 +282,7 @@ namespace SharedLibraryCore
public IManager Manager { get; protected set; }
public ILogger Logger { get; private set; }
public ServerConfiguration ServerConfig { get; private set; }
public List<Map> Maps { get; protected set; }
public List<Map> Maps { get; protected set; } = new List<Map>();
public List<Report> Reports { get; set; }
public List<ChatInfo> ChatHistory { get; protected set; }
public Queue<PlayerHistory> ClientHistory { get; private set; }
@ -307,7 +306,7 @@ namespace SharedLibraryCore
public bool Throttled { get; protected set; }
public bool CustomCallback { get; protected set; }
public string WorkingDirectory { get; protected set; }
public RCon.Connection RemoteConnection { get; protected set; }
public IRConConnection RemoteConnection { get; protected set; }
public IRConParser RconParser { get; protected set; }
public IEventParser EventParser { get; set; }
public string LogPath { get; protected set; }

View File

@ -6,7 +6,7 @@
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2.2.6</Version>
<Version>2.2.7</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Configurations>Debug;Release;Prerelease</Configurations>
@ -20,8 +20,8 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>Shared Library for IW4MAdmin</Description>
<AssemblyVersion>2.2.6.0</AssemblyVersion>
<FileVersion>2.2.6.0</FileVersion>
<AssemblyVersion>2.2.7.0</AssemblyVersion>
<FileVersion>2.2.7.0</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
@ -68,6 +68,10 @@
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.0" />
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.1" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="if not exist &quot;$(ProjectDir)..\BUILD&quot; (&#xD;&#xA;if $(ConfigurationName) == Debug (&#xD;&#xA;md &quot;$(ProjectDir)..\BUILD&quot;&#xD;&#xA;)&#xD;&#xA;)&#xD;&#xA;if not exist &quot;$(ProjectDir)..\BUILD\Plugins&quot; (&#xD;&#xA;if $(ConfigurationName) == Debug (&#xD;&#xA;md &quot;$(ProjectDir)..\BUILD\Plugins&quot;&#xD;&#xA;)&#xD;&#xA;)" />

View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Application\Application.csproj" />
<ProjectReference Include="..\..\Plugins\Stats\Stats.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Files\T6Game.log">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\T6GameStats.log">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\T6MapRotation.log">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,85 @@
using FakeItEasy;
using IW4MAdmin;
using IW4MAdmin.Application;
using IW4MAdmin.Application.EventParsers;
using NUnit.Framework;
using SharedLibraryCore.Interfaces;
using System;
using System.Diagnostics;
namespace ApplicationTests
{
[TestFixture]
public class ServerTests
{
ILogger logger;
[SetUp]
public void Setup()
{
logger = A.Fake<ILogger>();
void testLog(string msg) => Console.WriteLine(msg);
A.CallTo(() => logger.WriteError(A<string>.Ignored)).Invokes((string msg) => testLog(msg));
A.CallTo(() => logger.WriteWarning(A<string>.Ignored)).Invokes((string msg) => testLog(msg));
A.CallTo(() => logger.WriteInfo(A<string>.Ignored)).Invokes((string msg) => testLog(msg));
A.CallTo(() => logger.WriteDebug(A<string>.Ignored)).Invokes((string msg) => testLog(msg));
}
[Test]
public void GameTimeFalseQuitTest()
{
var mgr = A.Fake<IManager>();
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>());
var parser = new BaseEventParser();
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
var log = System.IO.File.ReadAllLines("Files\\T6MapRotation.log");
foreach (string line in log)
{
var e = parser.GenerateGameEvent(line);
if (e.Origin != null)
{
e.Origin.CurrentServer = server;
}
server.ExecuteEvent(e).Wait();
}
}
[Test]
public void LogFileReplay()
{
var mgr = A.Fake<IManager>();
A.CallTo(() => mgr.GetLogger(A<long>.Ignored)).Returns(logger);
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>());
var parser = new BaseEventParser();
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
var log = System.IO.File.ReadAllLines("Files\\T6Game.log");
long lastEventId = 0;
foreach (string line in log)
{
var e = parser.GenerateGameEvent(line);
server.Logger.WriteInfo($"{e.GameTime}");
if (e.Origin != null)
{
e.Origin.CurrentServer = server;
}
server.ExecuteEvent(e).Wait();
lastEventId = e.Id;
}
Assert.GreaterOrEqual(lastEventId, log.Length);
}
}
}

View File

@ -0,0 +1,117 @@
using NUnit.Framework;
using System;
using SharedLibraryCore.Interfaces;
using IW4MAdmin;
using FakeItEasy;
using IW4MAdmin.Application.EventParsers;
using System.Linq;
using IW4MAdmin.Plugins.Stats.Models;
using IW4MAdmin.Application.Helpers;
using IW4MAdmin.Plugins.Stats.Config;
using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
namespace ApplicationTests
{
[TestFixture]
public class StatsTests
{
ILogger logger;
[SetUp]
public void Setup()
{
logger = A.Fake<ILogger>();
void testLog(string msg) => Console.WriteLine(msg);
A.CallTo(() => logger.WriteError(A<string>.Ignored)).Invokes((string msg) => testLog(msg));
A.CallTo(() => logger.WriteWarning(A<string>.Ignored)).Invokes((string msg) => testLog(msg));
A.CallTo(() => logger.WriteInfo(A<string>.Ignored)).Invokes((string msg) => testLog(msg));
A.CallTo(() => logger.WriteDebug(A<string>.Ignored)).Invokes((string msg) => testLog(msg));
}
[Test]
public void TestKDR()
{
var mgr = A.Fake<IManager>();
var handlerFactory = A.Fake<IConfigurationHandlerFactory>();
var config = A.Fake<IConfigurationHandler<StatsConfiguration>>();
var plugin = new IW4MAdmin.Plugins.Stats.Plugin(handlerFactory);
A.CallTo(() => config.Configuration())
.Returns(new StatsConfiguration()
{
EnableAntiCheat = true
});
A.CallTo(() => handlerFactory.GetConfigurationHandler<StatsConfiguration>(A<string>.Ignored))
.Returns(config);
A.CallTo(() => mgr.GetLogger(A<long>.Ignored))
.Returns(logger);
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(),
A.Fake<IRConConnectionFactory>());
var parser = new BaseEventParser();
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
var log = System.IO.File.ReadAllLines("Files\\T6GameStats.log");
plugin.OnLoadAsync(mgr).Wait();
plugin.OnEventAsync(new SharedLibraryCore.GameEvent() { Type = SharedLibraryCore.GameEvent.EventType.Start, Owner = server }, server).Wait();
var clientList = new Dictionary<long, EFClient>();
foreach (string line in log)
{
var e = parser.GenerateGameEvent(line);
if (e.Origin != null)
{
//if (!clientList.ContainsKey(e.Origin.NetworkId))
//{
// clientList.Add(e.Origin.NetworkId, e.Origin);
//}
//else
//{
// e.Origin = clientList[e.Origin.NetworkId];
//}
e.Origin = server.GetClientsAsList().FirstOrDefault(_client => _client.NetworkId == e.Origin.NetworkId) ?? e.Origin;
e.Origin.CurrentServer = server;
}
if (e.Target != null)
{
//if (!clientList.ContainsKey(e.Target.NetworkId))
//{
// clientList.Add(e.Target.NetworkId, e.Target);
//}
//else
//{
// e.Target = clientList[e.Target.NetworkId];
//}
e.Target = server.GetClientsAsList().FirstOrDefault(_client => _client.NetworkId == e.Target.NetworkId) ?? e.Target;
e.Target.CurrentServer = server;
}
server.ExecuteEvent(e).Wait();
plugin.OnEventAsync(e, server).Wait();
}
var client = server.GetClientsAsList().First(_client => _client?.NetworkId == 2028755667);
var stats = client.GetAdditionalProperty<EFClientStatistics>("ClientStats");
}
class BasePathProvider : IBasePathProvider
{
public string BasePath => @"X:\IW4MAdmin\BUILD\Plugins";
}
}
}

View File

@ -11,11 +11,8 @@ namespace WebfrontCore.Controllers
{
public class HomeController : BaseController
{
private readonly IPluginImporter _pluginImporter;
public HomeController(IManager manager, IPluginImporter importer) : base(manager)
public HomeController(IManager manager) : base(manager)
{
_pluginImporter = importer;
}
public async Task<IActionResult> Index()
@ -68,7 +65,7 @@ namespace WebfrontCore.Controllers
var pluginType = _cmd.GetType().Assembly.GetTypes().FirstOrDefault(_type => _type.Assembly != excludedAssembly && typeof(IPlugin).IsAssignableFrom(_type));
return pluginType == null ?
Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_HELP_COMMAND_NATIVE"] :
_pluginImporter.ActivePlugins.First(_plugin => _plugin.GetType() == pluginType).Name; // for now we're just returning the name of the plugin, maybe later we'll include more info
Manager.Plugins.First(_plugin => _plugin.GetType() == pluginType).Name; // for now we're just returning the name of the plugin, maybe later we'll include more info
})
.Select(_grp => (_grp.Key, _grp.AsEnumerable()));

View File

@ -7,9 +7,14 @@ using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Interfaces;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using WebfrontCore.Middleware;
@ -32,20 +37,33 @@ namespace WebfrontCore
});
});
IEnumerable<Assembly> pluginAssemblies()
{
string pluginDir = $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}";
if (Directory.Exists(pluginDir))
{
var dllFileNames = Directory.GetFiles($"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}", "*.dll");
return dllFileNames.Select(_file => Assembly.LoadFrom(_file));
}
return Enumerable.Empty<Assembly>();
}
// Add framework services.
var mvcBuilder = services.AddMvc(_options => _options.SuppressAsyncSuffixInActionNames = false)
.ConfigureApplicationPartManager(_ =>
.ConfigureApplicationPartManager(_partManager =>
{
foreach (var assembly in Program.Manager.GetPluginAssemblies())
foreach (var assembly in pluginAssemblies())
{
if (assembly.FullName.Contains("Views"))
{
_.ApplicationParts.Add(new CompiledRazorAssemblyPart(assembly));
_partManager.ApplicationParts.Add(new CompiledRazorAssemblyPart(assembly));
}
else if (assembly.FullName.Contains("Web"))
{
_.ApplicationParts.Add(new AssemblyPart(assembly));
_partManager.ApplicationParts.Add(new AssemblyPart(assembly));
}
}
});
@ -58,7 +76,7 @@ namespace WebfrontCore
});
#endif
foreach (var asm in Program.Manager.GetPluginAssemblies())
foreach (var asm in pluginAssemblies())
{
mvcBuilder.AddApplicationPart(asm);
}
@ -83,7 +101,7 @@ namespace WebfrontCore
#endif
services.AddSingleton(Program.Manager);
services.AddSingleton(Program.ApplicationServiceProvider.GetService(typeof(IPluginImporter)) as IPluginImporter);
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IConfigurationHandlerFactory>());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.