Compare commits
18 Commits
2.3-Prerel
...
2020.11.07
Author | SHA1 | Date | |
---|---|---|---|
fd7bd7e0da | |||
e76976799b | |||
84189cf136 | |||
98ee997bf3 | |||
3f7372e780 | |||
08676f1d1e | |||
2bbafbd8f0 | |||
40cb2a9df6 | |||
59f1699228 | |||
1484d63b97 | |||
04217e96ee | |||
c41fc27a1a | |||
1f1f4de67a | |||
7f11921757 | |||
70cae976a0 | |||
2ab0cfa9be | |||
7e3c74e63c | |||
a4a65a486a |
@ -1,5 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using IW4MAdmin.Application.Helpers;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using RestEase;
|
using RestEase;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
@ -35,6 +37,13 @@ namespace IW4MAdmin.Application.API.Master
|
|||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PluginSubscriptionContent
|
||||||
|
{
|
||||||
|
public string Content { get; set; }
|
||||||
|
public PluginType Type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the capabilities of the master API
|
/// Defines the capabilities of the master API
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -63,5 +72,8 @@ namespace IW4MAdmin.Application.API.Master
|
|||||||
|
|
||||||
[Get("localization/{languageTag}")]
|
[Get("localization/{languageTag}")]
|
||||||
Task<SharedLibraryCore.Localization.Layout> GetLocalization([Path("languageTag")] string languageTag);
|
Task<SharedLibraryCore.Localization.Layout> GetLocalization([Path("languageTag")] string languageTag);
|
||||||
|
|
||||||
|
[Get("plugin_subscriptions")]
|
||||||
|
Task<IEnumerable<PluginSubscriptionContent>> GetPluginSubscription([Query("instance_id")] Guid instanceId, [Query("subscription_id")] string subscription_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,13 +67,14 @@ namespace IW4MAdmin.Application
|
|||||||
private readonly IEventHandler _eventHandler;
|
private readonly IEventHandler _eventHandler;
|
||||||
private readonly IScriptCommandFactory _scriptCommandFactory;
|
private readonly IScriptCommandFactory _scriptCommandFactory;
|
||||||
private readonly IMetaRegistration _metaRegistration;
|
private readonly IMetaRegistration _metaRegistration;
|
||||||
|
private readonly IScriptPluginServiceResolver _scriptPluginServiceResolver;
|
||||||
|
|
||||||
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
|
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
|
||||||
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
|
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
|
||||||
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
|
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
|
||||||
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
|
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
|
||||||
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IMetaService metaService,
|
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IMetaService metaService,
|
||||||
IMetaRegistration metaRegistration)
|
IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver)
|
||||||
{
|
{
|
||||||
MiddlewareActionHandler = actionHandler;
|
MiddlewareActionHandler = actionHandler;
|
||||||
_servers = new ConcurrentBag<Server>();
|
_servers = new ConcurrentBag<Server>();
|
||||||
@ -100,6 +101,7 @@ namespace IW4MAdmin.Application
|
|||||||
_eventHandler = eventHandler;
|
_eventHandler = eventHandler;
|
||||||
_scriptCommandFactory = scriptCommandFactory;
|
_scriptCommandFactory = scriptCommandFactory;
|
||||||
_metaRegistration = metaRegistration;
|
_metaRegistration = metaRegistration;
|
||||||
|
_scriptPluginServiceResolver = scriptPluginServiceResolver;
|
||||||
Plugins = plugins;
|
Plugins = plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,12 +279,12 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
if (plugin is ScriptPlugin scriptPlugin)
|
if (plugin is ScriptPlugin scriptPlugin)
|
||||||
{
|
{
|
||||||
await scriptPlugin.Initialize(this, _scriptCommandFactory);
|
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver);
|
||||||
scriptPlugin.Watcher.Changed += async (sender, e) =>
|
scriptPlugin.Watcher.Changed += async (sender, e) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await scriptPlugin.Initialize(this, _scriptCommandFactory);
|
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -449,7 +451,8 @@ namespace IW4MAdmin.Application
|
|||||||
Name = cmd.Name,
|
Name = cmd.Name,
|
||||||
Alias = cmd.Alias,
|
Alias = cmd.Alias,
|
||||||
MinimumPermission = cmd.Permission,
|
MinimumPermission = cmd.Permission,
|
||||||
AllowImpersonation = cmd.AllowImpersonation
|
AllowImpersonation = cmd.AllowImpersonation,
|
||||||
|
SupportedGames = cmd.SupportedGames
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +255,7 @@ namespace IW4MAdmin.Application.EventParsers
|
|||||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||||
State = EFClient.ClientState.Connecting,
|
State = EFClient.ClientState.Connecting,
|
||||||
},
|
},
|
||||||
|
Extra = originIdString,
|
||||||
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
||||||
IsBlocking = true,
|
IsBlocking = true,
|
||||||
GameTime = gameTime,
|
GameTime = gameTime,
|
||||||
|
@ -25,7 +25,7 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction)
|
public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, bool isTargetRequired, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction)
|
||||||
{
|
{
|
||||||
var permissionEnum = Enum.Parse<Permission>(permission);
|
var permissionEnum = Enum.Parse<Permission>(permission);
|
||||||
var argsArray = args.Select(_arg => new CommandArgument
|
var argsArray = args.Select(_arg => new CommandArgument
|
||||||
@ -34,7 +34,7 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
Required = _arg.Item2
|
Required = _arg.Item2
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
return new ScriptCommand(name, alias, description, permissionEnum, argsArray, executeAction, _config, _transLookup);
|
return new ScriptCommand(name, alias, description, isTargetRequired, permissionEnum, argsArray, executeAction, _config, _transLookup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -816,6 +816,7 @@ namespace IW4MAdmin
|
|||||||
Origin = client,
|
Origin = client,
|
||||||
Owner = this,
|
Owner = this,
|
||||||
IsBlocking = true,
|
IsBlocking = true,
|
||||||
|
Extra = client.GetAdditionalProperty<string>("BotGuid"),
|
||||||
Source = GameEvent.EventSource.Status
|
Source = GameEvent.EventSource.Status
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -971,7 +972,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
|
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
|
||||||
{
|
{
|
||||||
throw new ServerException(loc["SERVER_ERROR_NOT_RUNNING"]);
|
throw new ServerException(loc["SERVER_ERROR_NOT_RUNNING"].FormatExt(this.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null;
|
var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null;
|
||||||
@ -1041,9 +1042,10 @@ namespace IW4MAdmin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (needsRestart)
|
if (needsRestart)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning("Game log file not properly initialized, restarting map...");
|
// disabling this for the time being
|
||||||
await this.ExecuteCommandAsync("map_restart");
|
/*Logger.WriteWarning("Game log file not properly initialized, restarting map...");
|
||||||
|
await this.ExecuteCommandAsync("map_restart");*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// this DVAR isn't set until the a map is loaded
|
// this DVAR isn't set until the a map is loaded
|
||||||
|
@ -135,7 +135,7 @@ namespace IW4MAdmin.Application
|
|||||||
await ApplicationTask;
|
await ApplicationTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
string failMessage = translationLookup == null ? "Failed to initalize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"];
|
string failMessage = translationLookup == null ? "Failed to initalize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"];
|
||||||
Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}");
|
Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}");
|
||||||
@ -226,12 +226,17 @@ namespace IW4MAdmin.Application
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static IServiceCollection ConfigureServices(string[] args)
|
private static IServiceCollection ConfigureServices(string[] args)
|
||||||
{
|
{
|
||||||
|
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
||||||
|
var appConfig = appConfigHandler.Configuration();
|
||||||
var defaultLogger = new Logger("IW4MAdmin-Manager");
|
var defaultLogger = new Logger("IW4MAdmin-Manager");
|
||||||
var pluginImporter = new PluginImporter(defaultLogger);
|
|
||||||
|
var masterUri = Utilities.IsDevelopment ? new Uri("http://127.0.0.1:8080") : appConfig?.MasterUrl ?? new ApplicationConfiguration().MasterUrl;
|
||||||
|
var apiClient = RestClient.For<IMasterApi>(masterUri);
|
||||||
|
var pluginImporter = new PluginImporter(defaultLogger, appConfig, apiClient, new RemoteAssemblyHandler(defaultLogger, appConfig));
|
||||||
|
|
||||||
var serviceCollection = new ServiceCollection();
|
var serviceCollection = new ServiceCollection();
|
||||||
serviceCollection.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
|
serviceCollection.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
|
||||||
.AddSingleton(new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings") as IConfigurationHandler<ApplicationConfiguration>)
|
.AddSingleton(appConfigHandler as IConfigurationHandler<ApplicationConfiguration>)
|
||||||
.AddSingleton(new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as IConfigurationHandler<CommandConfiguration>)
|
.AddSingleton(new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as IConfigurationHandler<CommandConfiguration>)
|
||||||
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration() ?? new ApplicationConfiguration())
|
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration() ?? new ApplicationConfiguration())
|
||||||
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>().Configuration() ?? new CommandConfiguration())
|
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>().Configuration() ?? new CommandConfiguration())
|
||||||
@ -249,24 +254,23 @@ namespace IW4MAdmin.Application
|
|||||||
.AddSingleton<IEntityService<EFClient>, ClientService>()
|
.AddSingleton<IEntityService<EFClient>, ClientService>()
|
||||||
.AddSingleton<IMetaService, MetaService>()
|
.AddSingleton<IMetaService, MetaService>()
|
||||||
.AddSingleton<IMetaRegistration, MetaRegistration>()
|
.AddSingleton<IMetaRegistration, MetaRegistration>()
|
||||||
|
.AddSingleton<IScriptPluginServiceResolver, ScriptPluginServiceResolver>()
|
||||||
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>, ReceivedPenaltyResourceQueryHelper>()
|
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>, ReceivedPenaltyResourceQueryHelper>()
|
||||||
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>, AdministeredPenaltyResourceQueryHelper>()
|
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>, AdministeredPenaltyResourceQueryHelper>()
|
||||||
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>, UpdatedAliasResourceQueryHelper>()
|
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>, UpdatedAliasResourceQueryHelper>()
|
||||||
.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>()
|
.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>()
|
||||||
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
||||||
|
.AddSingleton<IRemoteAssemblyHandler, RemoteAssemblyHandler>()
|
||||||
|
.AddSingleton<IMasterCommunication, MasterCommunication>()
|
||||||
|
.AddSingleton<IManager, ApplicationManager>()
|
||||||
|
.AddSingleton(apiClient)
|
||||||
.AddSingleton(_serviceProvider =>
|
.AddSingleton(_serviceProvider =>
|
||||||
{
|
{
|
||||||
var config = _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration();
|
var config = _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration();
|
||||||
return Localization.Configure.Initialize(useLocalTranslation: config?.UseLocalTranslations ?? false,
|
return Localization.Configure.Initialize(useLocalTranslation: config?.UseLocalTranslations ?? false,
|
||||||
apiInstance: _serviceProvider.GetRequiredService<IMasterApi>(),
|
apiInstance: _serviceProvider.GetRequiredService<IMasterApi>(),
|
||||||
customLocale: config?.EnableCustomLocale ?? false ? (config.CustomLocale ?? "en-US") : "en-US");
|
customLocale: config?.EnableCustomLocale ?? false ? (config.CustomLocale ?? "en-US") : "en-US");
|
||||||
})
|
});
|
||||||
.AddSingleton<IManager, ApplicationManager>()
|
|
||||||
.AddSingleton(_serviceProvider => RestClient
|
|
||||||
.For<IMasterApi>(Utilities.IsDevelopment ? new Uri("http://127.0.0.1:8080") : _serviceProvider
|
|
||||||
.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration()?.MasterUrl ??
|
|
||||||
new ApplicationConfiguration().MasterUrl))
|
|
||||||
.AddSingleton<IMasterCommunication, MasterCommunication>();
|
|
||||||
|
|
||||||
if (args.Contains("serialevents"))
|
if (args.Contains("serialevents"))
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,8 @@ using SharedLibraryCore.Interfaces;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using IW4MAdmin.Application.Misc;
|
using IW4MAdmin.Application.Misc;
|
||||||
|
using IW4MAdmin.Application.API.Master;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Helpers
|
namespace IW4MAdmin.Application.Helpers
|
||||||
{
|
{
|
||||||
@ -15,12 +17,19 @@ namespace IW4MAdmin.Application.Helpers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PluginImporter : IPluginImporter
|
public class PluginImporter : IPluginImporter
|
||||||
{
|
{
|
||||||
|
private IEnumerable<PluginSubscriptionContent> _pluginSubscription;
|
||||||
private static readonly string PLUGIN_DIR = "Plugins";
|
private static readonly string PLUGIN_DIR = "Plugins";
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly IRemoteAssemblyHandler _remoteAssemblyHandler;
|
||||||
|
private readonly IMasterApi _masterApi;
|
||||||
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
|
||||||
public PluginImporter(ILogger logger)
|
public PluginImporter(ILogger logger, ApplicationConfiguration appConfig, IMasterApi masterApi, IRemoteAssemblyHandler remoteAssemblyHandler)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_masterApi = masterApi;
|
||||||
|
_remoteAssemblyHandler = remoteAssemblyHandler;
|
||||||
|
_appConfig = appConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -33,11 +42,11 @@ namespace IW4MAdmin.Application.Helpers
|
|||||||
|
|
||||||
if (Directory.Exists(pluginDir))
|
if (Directory.Exists(pluginDir))
|
||||||
{
|
{
|
||||||
string[] scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js");
|
var scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts());
|
||||||
|
|
||||||
_logger.WriteInfo($"Discovered {scriptPluginFiles.Length} potential script plugins");
|
_logger.WriteInfo($"Discovered {scriptPluginFiles.Count()} potential script plugins");
|
||||||
|
|
||||||
if (scriptPluginFiles.Length > 0)
|
if (scriptPluginFiles.Count() > 0)
|
||||||
{
|
{
|
||||||
foreach (string fileName in scriptPluginFiles)
|
foreach (string fileName in scriptPluginFiles)
|
||||||
{
|
{
|
||||||
@ -66,7 +75,10 @@ namespace IW4MAdmin.Application.Helpers
|
|||||||
|
|
||||||
if (dllFileNames.Length > 0)
|
if (dllFileNames.Length > 0)
|
||||||
{
|
{
|
||||||
var assemblies = dllFileNames.Select(_name => Assembly.LoadFrom(_name));
|
// we only want to load the most recent assembly in case of duplicates
|
||||||
|
var assemblies = dllFileNames.Select(_name => Assembly.LoadFrom(_name))
|
||||||
|
.Union(GetRemoteAssemblies())
|
||||||
|
.GroupBy(_assembly => _assembly.FullName).Select(_assembly => _assembly.OrderByDescending(_assembly => _assembly.GetName().Version).First());
|
||||||
|
|
||||||
pluginTypes = assemblies
|
pluginTypes = assemblies
|
||||||
.SelectMany(_asm => _asm.GetTypes())
|
.SelectMany(_asm => _asm.GetTypes())
|
||||||
@ -84,5 +96,47 @@ namespace IW4MAdmin.Application.Helpers
|
|||||||
|
|
||||||
return (pluginTypes, commandTypes);
|
return (pluginTypes, commandTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Assembly> GetRemoteAssemblies()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_pluginSubscription == null)
|
||||||
|
_pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
||||||
|
|
||||||
|
return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.WriteWarning("Could not load remote assemblies");
|
||||||
|
_logger.WriteDebug(ex.GetExceptionInfo());
|
||||||
|
return Enumerable.Empty<Assembly>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<string> GetRemoteScripts()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_pluginSubscription == null)
|
||||||
|
_pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
||||||
|
|
||||||
|
return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.WriteWarning("Could not load remote assemblies");
|
||||||
|
_logger.WriteDebug(ex.GetExceptionInfo());
|
||||||
|
return Enumerable.Empty<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PluginType
|
||||||
|
{
|
||||||
|
Binary,
|
||||||
|
Script
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
76
Application/Misc/RemoteAssemblyHandler.cs
Normal file
76
Application/Misc/RemoteAssemblyHandler.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc
|
||||||
|
{
|
||||||
|
public class RemoteAssemblyHandler : IRemoteAssemblyHandler
|
||||||
|
{
|
||||||
|
private const int keyLength = 32;
|
||||||
|
private const int tagLength = 16;
|
||||||
|
private const int nonceLength = 12;
|
||||||
|
private const int iterationCount = 10000;
|
||||||
|
|
||||||
|
private readonly ApplicationConfiguration _appconfig;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public RemoteAssemblyHandler(ILogger logger, ApplicationConfiguration appconfig)
|
||||||
|
{
|
||||||
|
_appconfig = appconfig;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Assembly> DecryptAssemblies(string[] encryptedAssemblies)
|
||||||
|
{
|
||||||
|
return DecryptContent(encryptedAssemblies)
|
||||||
|
.Select(decryptedAssembly => Assembly.Load(decryptedAssembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> DecryptScripts(string[] encryptedScripts)
|
||||||
|
{
|
||||||
|
return DecryptContent(encryptedScripts).Select(decryptedScript => Encoding.UTF8.GetString(decryptedScript));
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[][] DecryptContent(string[] content)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_appconfig.Id) || string.IsNullOrWhiteSpace(_appconfig.SubscriptionId))
|
||||||
|
{
|
||||||
|
_logger.WriteWarning($"{nameof(_appconfig.Id)} and {nameof(_appconfig.SubscriptionId)} must be provided to attempt loading remote assemblies/scripts");
|
||||||
|
return new byte[0][];
|
||||||
|
}
|
||||||
|
|
||||||
|
var assemblies = content.Select(piece =>
|
||||||
|
{
|
||||||
|
byte[] byteContent = Convert.FromBase64String(piece);
|
||||||
|
byte[] encryptedContent = byteContent.Take(byteContent.Length - (tagLength + nonceLength)).ToArray();
|
||||||
|
byte[] tag = byteContent.Skip(byteContent.Length - (tagLength + nonceLength)).Take(tagLength).ToArray();
|
||||||
|
byte[] nonce = byteContent.Skip(byteContent.Length - nonceLength).Take(nonceLength).ToArray();
|
||||||
|
byte[] decryptedContent = new byte[encryptedContent.Length];
|
||||||
|
|
||||||
|
var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id.ToString()), iterationCount, HashAlgorithmName.SHA512);
|
||||||
|
var encryption = new AesGcm(keyGen.GetBytes(keyLength));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
encryption.Decrypt(nonce, encryptedContent, tag, decryptedContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (CryptographicException ex)
|
||||||
|
{
|
||||||
|
_logger.WriteError("Could not obtain remote plugin assemblies");
|
||||||
|
_logger.WriteDebug(ex.GetExceptionInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptedContent;
|
||||||
|
});
|
||||||
|
|
||||||
|
return assemblies.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
{
|
{
|
||||||
private readonly Action<GameEvent> _executeAction;
|
private readonly Action<GameEvent> _executeAction;
|
||||||
|
|
||||||
public ScriptCommand(string name, string alias, string description, Permission permission,
|
public ScriptCommand(string name, string alias, string description, bool isTargetRequired, Permission permission,
|
||||||
CommandArgument[] args, Action<GameEvent> executeAction, CommandConfiguration config, ITranslationLookup layout)
|
CommandArgument[] args, Action<GameEvent> executeAction, CommandConfiguration config, ITranslationLookup layout)
|
||||||
: base(config, layout)
|
: base(config, layout)
|
||||||
{
|
{
|
||||||
@ -24,6 +24,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
Name = name;
|
Name = name;
|
||||||
Alias = alias;
|
Alias = alias;
|
||||||
Description = description;
|
Description = description;
|
||||||
|
RequiresTarget = isTargetRequired;
|
||||||
Permission = permission;
|
Permission = permission;
|
||||||
Arguments = args;
|
Arguments = args;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
_onProcessing.Dispose();
|
_onProcessing.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory)
|
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory, IScriptPluginServiceResolver serviceResolver)
|
||||||
{
|
{
|
||||||
await _onProcessing.WaitAsync();
|
await _onProcessing.WaitAsync();
|
||||||
|
|
||||||
@ -114,6 +114,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
_scriptEngine.Execute(script);
|
_scriptEngine.Execute(script);
|
||||||
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
|
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
|
||||||
|
_scriptEngine.SetValue("_serviceResolver", serviceResolver);
|
||||||
dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject();
|
dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject();
|
||||||
|
|
||||||
Author = pluginObject.author;
|
Author = pluginObject.author;
|
||||||
@ -164,6 +165,11 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
successfullyLoaded = true;
|
successfullyLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
catch (JavaScriptException ex)
|
||||||
|
{
|
||||||
|
throw new PluginException($"An error occured while initializing script plugin: {ex.Error} (Line: {ex.Location.Start.Line}, Character: {ex.Location.Start.Column})") { PluginFile = _fileName };
|
||||||
|
}
|
||||||
|
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
@ -246,6 +252,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
string alias = dynamicCommand.alias;
|
string alias = dynamicCommand.alias;
|
||||||
string description = dynamicCommand.description;
|
string description = dynamicCommand.description;
|
||||||
string permission = dynamicCommand.permission;
|
string permission = dynamicCommand.permission;
|
||||||
|
bool targetRequired = false;
|
||||||
|
|
||||||
List<(string, bool)> args = new List<(string, bool)>();
|
List<(string, bool)> args = new List<(string, bool)>();
|
||||||
dynamic arguments = null;
|
dynamic arguments = null;
|
||||||
@ -260,6 +267,16 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
// arguments are optional
|
// arguments are optional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
targetRequired = dynamicCommand.targetRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (RuntimeBinderException)
|
||||||
|
{
|
||||||
|
// arguments are optional
|
||||||
|
}
|
||||||
|
|
||||||
if (arguments != null)
|
if (arguments != null)
|
||||||
{
|
{
|
||||||
foreach (var arg in dynamicCommand.arguments)
|
foreach (var arg in dynamicCommand.arguments)
|
||||||
@ -284,7 +301,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, args, execute));
|
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, targetRequired, args, execute));
|
||||||
}
|
}
|
||||||
|
|
||||||
return commandList;
|
return commandList;
|
||||||
|
48
Application/Misc/ScriptPluginServiceResolver.cs
Normal file
48
Application/Misc/ScriptPluginServiceResolver.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// implementation of IScriptPluginServiceResolver
|
||||||
|
/// </summary>
|
||||||
|
public class ScriptPluginServiceResolver : IScriptPluginServiceResolver
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public ScriptPluginServiceResolver(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ResolveService(string serviceName)
|
||||||
|
{
|
||||||
|
var serviceType = DetermineRootType(serviceName);
|
||||||
|
return _serviceProvider.GetService(serviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ResolveService(string serviceName, string[] genericParameters)
|
||||||
|
{
|
||||||
|
var serviceType = DetermineRootType(serviceName, genericParameters.Length);
|
||||||
|
var genericTypes = genericParameters.Select(_genericTypeParam => DetermineRootType(_genericTypeParam));
|
||||||
|
var resolvedServiceType = serviceType.MakeGenericType(genericTypes.ToArray());
|
||||||
|
return _serviceProvider.GetService(resolvedServiceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type DetermineRootType(string serviceName, int genericParamCount = 0)
|
||||||
|
{
|
||||||
|
var typeCollection = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
.SelectMany(t => t.GetTypes());
|
||||||
|
string generatedName = $"{serviceName}{(genericParamCount == 0 ? "" : $"`{genericParamCount}")}".ToLower();
|
||||||
|
var serviceType = typeCollection.FirstOrDefault(_type => _type.Name.ToLower() == generatedName);
|
||||||
|
|
||||||
|
if (serviceType == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"No object type '{serviceName}' defined in loaded assemblies");
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,9 +49,11 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
var connectionState = ActiveQueries[this.Endpoint];
|
var connectionState = ActiveQueries[this.Endpoint];
|
||||||
|
|
||||||
#if DEBUG == true
|
if (Utilities.IsDevelopment)
|
||||||
_log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]");
|
{
|
||||||
#endif
|
_log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]");
|
||||||
|
}
|
||||||
|
|
||||||
// enter the semaphore so only one query is sent at a time per server.
|
// enter the semaphore so only one query is sent at a time per server.
|
||||||
await connectionState.OnComplete.WaitAsync();
|
await connectionState.OnComplete.WaitAsync();
|
||||||
|
|
||||||
@ -64,10 +66,11 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
connectionState.LastQuery = DateTime.Now;
|
connectionState.LastQuery = DateTime.Now;
|
||||||
|
|
||||||
#if DEBUG == true
|
if (Utilities.IsDevelopment)
|
||||||
_log.WriteDebug($"Semaphore has been released [{this.Endpoint}]");
|
{
|
||||||
_log.WriteDebug($"Query [{this.Endpoint},{type.ToString()},{parameters}]");
|
_log.WriteDebug($"Semaphore has been released [{Endpoint}]");
|
||||||
#endif
|
_log.WriteDebug($"Query [{Endpoint},{type},{parameters}]");
|
||||||
|
}
|
||||||
|
|
||||||
byte[] payload = null;
|
byte[] payload = null;
|
||||||
bool waitForResponse = config.WaitForResponse;
|
bool waitForResponse = config.WaitForResponse;
|
||||||
@ -133,6 +136,7 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
connectionState.OnReceivedData.Reset();
|
connectionState.OnReceivedData.Reset();
|
||||||
connectionState.ConnectionAttempts++;
|
connectionState.ConnectionAttempts++;
|
||||||
connectionState.BytesReadPerSegment.Clear();
|
connectionState.BytesReadPerSegment.Clear();
|
||||||
|
bool exceptionCaught = false;
|
||||||
#if DEBUG == true
|
#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
|
#endif
|
||||||
@ -150,9 +154,11 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
// we want to retry with a delay
|
||||||
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
|
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
|
||||||
{
|
{
|
||||||
await Task.Delay(StaticHelpers.FloodProtectionInterval);
|
exceptionCaught = true;
|
||||||
|
await Task.Delay(StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts));
|
||||||
goto retrySend;
|
goto retrySend;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +167,8 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (connectionState.OnComplete.CurrentCount == 0)
|
// we don't want to release if we're going to retry the query
|
||||||
|
if (connectionState.OnComplete.CurrentCount == 0 && !exceptionCaught)
|
||||||
{
|
{
|
||||||
connectionState.OnComplete.Release(1);
|
connectionState.OnComplete.Release(1);
|
||||||
}
|
}
|
||||||
@ -170,13 +177,12 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (response.Length == 0)
|
if (response.Length == 0)
|
||||||
{
|
{
|
||||||
_log.WriteWarning($"Received empty response for request [{type.ToString()}, {parameters}, {Endpoint.ToString()}]");
|
_log.WriteWarning($"Received empty response for request [{type}, {parameters}, {Endpoint}]");
|
||||||
return new string[0];
|
return new string[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
||||||
ReassembleSegmentedStatus(response) :
|
ReassembleSegmentedStatus(response) : RecombineMessages(response);
|
||||||
_gameEncoding.GetString(response[0]) + '\n';
|
|
||||||
|
|
||||||
// note: not all games respond if the pasword is wrong or not set
|
// note: not all games respond if the pasword is wrong or not set
|
||||||
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
||||||
@ -234,6 +240,35 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
return string.Join("", splitStatusStrings);
|
return string.Join("", splitStatusStrings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recombines multiple game messages into one
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="payload"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private string RecombineMessages(byte[][] payload)
|
||||||
|
{
|
||||||
|
if (payload.Length == 1)
|
||||||
|
{
|
||||||
|
return _gameEncoding.GetString(payload[0]).TrimEnd('\n') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
for (int i = 0; i < payload.Length; i++)
|
||||||
|
{
|
||||||
|
string message = _gameEncoding.GetString(payload[i]).TrimEnd('\n') + '\n';
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
message = message.Replace(config.CommandPrefixes.RConResponse, "");
|
||||||
|
}
|
||||||
|
builder.Append(message);
|
||||||
|
}
|
||||||
|
builder.Append('\n');
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse)
|
private async Task<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse)
|
||||||
{
|
{
|
||||||
var connectionState = ActiveQueries[this.Endpoint];
|
var connectionState = ActiveQueries[this.Endpoint];
|
||||||
@ -259,7 +294,8 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
if (sendDataPending)
|
if (sendDataPending)
|
||||||
{
|
{
|
||||||
// the send has not been completed asyncronously
|
// the send has not been completed asyncronously
|
||||||
if (!await Task.Run(() => connectionState.OnSentData.Wait(StaticHelpers.SocketTimeout)))
|
// this really shouldn't ever happen because it's UDP
|
||||||
|
if (!await Task.Run(() => connectionState.OnSentData.Wait(StaticHelpers.SocketTimeout(1))))
|
||||||
{
|
{
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
throw new NetworkException("Timed out sending data", rconSocket);
|
throw new NetworkException("Timed out sending data", rconSocket);
|
||||||
@ -278,7 +314,11 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (receiveDataPending)
|
if (receiveDataPending)
|
||||||
{
|
{
|
||||||
if (!await Task.Run(() => connectionState.OnReceivedData.Wait(10000)))
|
if (Utilities.IsDevelopment)
|
||||||
|
{
|
||||||
|
_log.WriteDebug($"Waiting to asynchrously receive data on attempt #{connectionState.ConnectionAttempts}");
|
||||||
|
}
|
||||||
|
if (!await Task.Run(() => connectionState.OnReceivedData.Wait(StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts))))
|
||||||
{
|
{
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
throw new NetworkException("Timed out waiting for response", rconSocket);
|
throw new NetworkException("Timed out waiting for response", rconSocket);
|
||||||
@ -287,6 +327,11 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
|
|
||||||
|
return GetResponseData(connectionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[][] GetResponseData(ConnectionState connectionState)
|
||||||
|
{
|
||||||
var responseList = new List<byte[]>();
|
var responseList = new List<byte[]>();
|
||||||
int totalBytesRead = 0;
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
@ -305,9 +350,10 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
if (Utilities.IsDevelopment)
|
||||||
_log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
|
{
|
||||||
#endif
|
_log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint}");
|
||||||
|
}
|
||||||
|
|
||||||
// this occurs when we close the socket
|
// this occurs when we close the socket
|
||||||
if (e.BytesTransferred == 0)
|
if (e.BytesTransferred == 0)
|
||||||
@ -330,9 +376,10 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (!sock.ReceiveAsync(state.ReceiveEventArgs))
|
if (!sock.ReceiveAsync(state.ReceiveEventArgs))
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
if (Utilities.IsDevelopment)
|
||||||
_log.WriteDebug($"Read {state.ReceiveEventArgs.BytesTransferred} synchronous bytes from {e.RemoteEndPoint.ToString()}");
|
{
|
||||||
#endif
|
_log.WriteDebug($"Read {state.ReceiveEventArgs.BytesTransferred} synchronous bytes from {e.RemoteEndPoint}");
|
||||||
|
}
|
||||||
// we need to increment this here because the callback isn't executed if there's no pending IO
|
// we need to increment this here because the callback isn't executed if there's no pending IO
|
||||||
state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred);
|
state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred);
|
||||||
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
||||||
@ -354,9 +401,10 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
if (Utilities.IsDevelopment)
|
||||||
_log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
|
{
|
||||||
#endif
|
_log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
|
||||||
|
}
|
||||||
ActiveQueries[this.Endpoint].OnSentData.Set();
|
ActiveQueries[this.Endpoint].OnSentData.Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,22 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
|
|
||||||
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
||||||
{
|
{
|
||||||
string[] lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
|
string[] lineSplit;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (fallbackValue == null)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
lineSplit = new string[0];
|
||||||
|
}
|
||||||
|
|
||||||
string response = string.Join('\n', lineSplit).TrimEnd('\0');
|
string response = string.Join('\n', lineSplit).TrimEnd('\0');
|
||||||
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
||||||
|
|
||||||
@ -203,10 +218,11 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
|
|
||||||
long networkId;
|
long networkId;
|
||||||
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
||||||
|
string networkIdString;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
|
networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
|
||||||
|
|
||||||
networkId = networkIdString.IsBotGuid() ?
|
networkId = networkIdString.IsBotGuid() ?
|
||||||
name.GenerateGuidFromString() :
|
name.GenerateGuidFromString() :
|
||||||
@ -234,6 +250,8 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
State = EFClient.ClientState.Connecting
|
State = EFClient.ClientState.Connecting
|
||||||
};
|
};
|
||||||
|
|
||||||
|
client.SetAdditionalProperty("BotGuid", networkIdString);
|
||||||
|
|
||||||
StatusPlayers.Add(client);
|
StatusPlayers.Add(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
|
name: '$(Date:yyyy.MM.dd)$(Rev:.r)'
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
batch: true
|
batch: true
|
||||||
branches:
|
branches:
|
||||||
include:
|
include:
|
||||||
- releases/*
|
- release/pre
|
||||||
- 2.4-pr
|
- master
|
||||||
paths:
|
|
||||||
exclude:
|
|
||||||
- azure-pipelines.yml
|
|
||||||
- Master/*
|
|
||||||
|
|
||||||
pr: none
|
pr: none
|
||||||
|
|
||||||
@ -17,110 +15,132 @@ pool:
|
|||||||
variables:
|
variables:
|
||||||
solution: 'IW4MAdmin.sln'
|
solution: 'IW4MAdmin.sln'
|
||||||
buildPlatform: 'Any CPU'
|
buildPlatform: 'Any CPU'
|
||||||
buildConfiguration: 'Prerelease'
|
|
||||||
outputFolder: '$(Build.ArtifactStagingDirectory)\Publish\$(buildConfiguration)'
|
outputFolder: '$(Build.ArtifactStagingDirectory)\Publish\$(buildConfiguration)'
|
||||||
|
releaseType: verified
|
||||||
|
buildConfiguration: Stable
|
||||||
|
isPreRelease: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: NuGetToolInstaller@1
|
- task: PowerShell@2
|
||||||
|
displayName: 'Setup Pre-Release configuration'
|
||||||
|
condition: eq(variables['Build.SourceBranch'], 'refs/heads/release/pre')
|
||||||
|
inputs:
|
||||||
|
targetType: 'inline'
|
||||||
|
script: |
|
||||||
|
echo '##vso[task.setvariable variable=releaseType]prerelease'
|
||||||
|
echo '##vso[task.setvariable variable=buildConfiguration]Prerelease'
|
||||||
|
echo '##vso[task.setvariable variable=isPreRelease]true'
|
||||||
|
failOnStderr: true
|
||||||
|
|
||||||
- task: NuGetCommand@2
|
- task: NuGetCommand@2
|
||||||
|
displayName: 'Restore nuget packages'
|
||||||
inputs:
|
inputs:
|
||||||
restoreSolution: '$(solution)'
|
restoreSolution: '$(solution)'
|
||||||
|
|
||||||
- task: PowerShell@2
|
- task: PowerShell@2
|
||||||
|
displayName: 'Preload external resources'
|
||||||
inputs:
|
inputs:
|
||||||
targetType: 'inline'
|
targetType: 'inline'
|
||||||
script: |
|
script: |
|
||||||
|
Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)'
|
||||||
md -Force lib\open-iconic\font\css
|
md -Force lib\open-iconic\font\css
|
||||||
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap.scss
|
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap.scss
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
||||||
|
|
||||||
- task: projectversionasvariable@1
|
|
||||||
inputs:
|
|
||||||
path: '$(Build.Repository.LocalPath)\Application\Application.csproj'
|
|
||||||
|
|
||||||
- task: VSBuild@1
|
- task: VSBuild@1
|
||||||
|
displayName: 'Build projects'
|
||||||
inputs:
|
inputs:
|
||||||
solution: '$(solution)'
|
solution: '$(solution)'
|
||||||
msbuildArgs: '/p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)" /p:Version=$(Version.Major).$(Version.Minor).$(Version.Build).$(Build.BuildId)'
|
msbuildArgs: '/p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)" /p:Version=$(Build.BuildNumber)'
|
||||||
platform: '$(buildPlatform)'
|
platform: '$(buildPlatform)'
|
||||||
configuration: '$(buildConfiguration)'
|
configuration: '$(buildConfiguration)'
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
|
displayName: 'Publish projects'
|
||||||
inputs:
|
inputs:
|
||||||
command: 'publish'
|
command: 'publish'
|
||||||
publishWebProjects: false
|
publishWebProjects: false
|
||||||
projects: |
|
projects: |
|
||||||
**/WebfrontCore.csproj
|
**/WebfrontCore.csproj
|
||||||
**/Application.csproj
|
**/Application.csproj
|
||||||
arguments: '-c $(buildConfiguration) -o $(outputFolder) /p:Version=$(Version.Major).$(Version.Minor).$(Version.Build).$(Build.BuildId)'
|
arguments: '-c $(buildConfiguration) -o $(outputFolder) /p:Version=$(Build.BuildNumber)'
|
||||||
zipAfterPublish: false
|
zipAfterPublish: false
|
||||||
modifyOutputPath: false
|
modifyOutputPath: false
|
||||||
|
|
||||||
- task: PowerShell@2
|
- task: PowerShell@2
|
||||||
|
displayName: 'Run publish script 1'
|
||||||
inputs:
|
inputs:
|
||||||
targetType: 'inline'
|
filePath: 'DeploymentFiles/PostPublish.ps1'
|
||||||
script: 'wget https://raidmax.org/downloads/dos2unix.exe'
|
|
||||||
failOnStderr: true
|
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
|
||||||
|
|
||||||
- task: PowerShell@2
|
|
||||||
inputs:
|
|
||||||
filePath: 'PostPublish.ps1'
|
|
||||||
arguments: '$(outputFolder)'
|
arguments: '$(outputFolder)'
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||||
|
|
||||||
- task: BatchScript@1
|
- task: BatchScript@1
|
||||||
|
displayName: 'Run publish script 2'
|
||||||
inputs:
|
inputs:
|
||||||
filename: 'Application\BuildScripts\PostPublish.bat'
|
filename: 'Application\BuildScripts\PostPublish.bat'
|
||||||
workingFolder: '$(Build.Repository.LocalPath)'
|
workingFolder: '$(Build.Repository.LocalPath)'
|
||||||
arguments: '$(outputFolder)'
|
arguments: '$(outputFolder)'
|
||||||
failOnStandardError: true
|
failOnStandardError: true
|
||||||
|
|
||||||
|
- task: PowerShell@2
|
||||||
|
displayName: 'Download dos2unix for line endings'
|
||||||
|
inputs:
|
||||||
|
targetType: 'inline'
|
||||||
|
script: 'wget https://raidmax.org/downloads/dos2unix.exe'
|
||||||
|
failOnStderr: true
|
||||||
|
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
|
displayName: 'Convert Linux start script line endings'
|
||||||
inputs:
|
inputs:
|
||||||
script: |
|
script: |
|
||||||
echo changing to encoding for linux start script
|
echo changing to encoding for linux start script
|
||||||
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
||||||
echo creating website version filename
|
echo creating website version filename
|
||||||
@echo IW4MAdmin-$(Version.Major).$(Version.Minor)-$(buildConfiguration)$(Version.Build)b$(Build.BuildId) > $(Build.ArtifactStagingDirectory)\version_prerelease.txt
|
@echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||||
|
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2
|
||||||
|
displayName: 'Move script plugins into publish directory'
|
||||||
inputs:
|
inputs:
|
||||||
SourceFolder: '$(Build.Repository.LocalPath)\Plugins\ScriptPlugins'
|
SourceFolder: '$(Build.Repository.LocalPath)\Plugins\ScriptPlugins'
|
||||||
Contents: '*.js'
|
Contents: '*.js'
|
||||||
TargetFolder: '$(outputFolder)\Plugins'
|
TargetFolder: '$(outputFolder)\Plugins'
|
||||||
|
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2
|
||||||
|
displayName: 'Move binary plugins into publish directory'
|
||||||
inputs:
|
inputs:
|
||||||
SourceFolder: '$(Build.Repository.LocalPath)\BUILD\Plugins\'
|
SourceFolder: '$(Build.Repository.LocalPath)\BUILD\Plugins\'
|
||||||
Contents: '*.dll'
|
Contents: '*.dll'
|
||||||
TargetFolder: '$(outputFolder)\Plugins'
|
TargetFolder: '$(outputFolder)\Plugins'
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
|
displayName: 'Move webfront resources into publish directory'
|
||||||
inputs:
|
inputs:
|
||||||
script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot'
|
script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot'
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\BUILD\Plugins'
|
workingDirectory: '$(Build.Repository.LocalPath)\BUILD\Plugins'
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
|
displayName: 'Move gamescript files into publish directory'
|
||||||
inputs:
|
inputs:
|
||||||
script: 'echo d | xcopy /s /y /f GameFiles $(outputFolder)\GameFiles'
|
script: 'echo d | xcopy /s /y /f GameFiles $(outputFolder)\GameFiles'
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
|
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
|
displayName: 'Generate final zip file'
|
||||||
inputs:
|
inputs:
|
||||||
rootFolderOrFile: '$(outputFolder)'
|
rootFolderOrFile: '$(outputFolder)'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Version.Major).$(Version.Minor)-$(buildConfiguration)$(Version.Build)b$(Build.BuildId).zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
||||||
replaceExistingArchive: true
|
replaceExistingArchive: true
|
||||||
|
|
||||||
- task: FtpUpload@2
|
- task: FtpUpload@2
|
||||||
|
displayName: 'Upload zip file to website'
|
||||||
inputs:
|
inputs:
|
||||||
credentialsOption: 'inputs'
|
credentialsOption: 'inputs'
|
||||||
serverUrl: '$(FTPUrl)'
|
serverUrl: '$(FTPUrl)'
|
||||||
@ -135,13 +155,14 @@ steps:
|
|||||||
trustSSL: false
|
trustSSL: false
|
||||||
|
|
||||||
- task: FtpUpload@2
|
- task: FtpUpload@2
|
||||||
|
displayName: 'Upload version info to website'
|
||||||
inputs:
|
inputs:
|
||||||
credentialsOption: 'inputs'
|
credentialsOption: 'inputs'
|
||||||
serverUrl: '$(FTPUrl)'
|
serverUrl: '$(FTPUrl)'
|
||||||
username: '$(FTPUsername)'
|
username: '$(FTPUsername)'
|
||||||
password: '$(FTPPassword)'
|
password: '$(FTPPassword)'
|
||||||
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||||
filePatterns: 'version_prerelease.txt'
|
filePatterns: 'version_$(releaseType).txt'
|
||||||
remoteDirectory: 'IW4MAdmin'
|
remoteDirectory: 'IW4MAdmin'
|
||||||
clean: false
|
clean: false
|
||||||
cleanContents: false
|
cleanContents: false
|
||||||
@ -149,27 +170,29 @@ steps:
|
|||||||
trustSSL: false
|
trustSSL: false
|
||||||
|
|
||||||
- task: GitHubRelease@1
|
- task: GitHubRelease@1
|
||||||
|
displayName: 'Make GitHub release'
|
||||||
inputs:
|
inputs:
|
||||||
gitHubConnection: 'github.com_RaidMax'
|
gitHubConnection: 'github.com_RaidMax'
|
||||||
repositoryName: 'RaidMax/IW4M-Admin'
|
repositoryName: 'RaidMax/IW4M-Admin'
|
||||||
action: 'create'
|
action: 'create'
|
||||||
target: '$(Build.SourceVersion)'
|
target: '$(Build.SourceVersion)'
|
||||||
tagSource: 'userSpecifiedTag'
|
tagSource: 'userSpecifiedTag'
|
||||||
tag: '$(Version.Major).$(Version.Minor)-$(buildConfiguration)$(Version.Build)b$(Build.BuildId)'
|
tag: '$(Build.BuildNumber)-$(releaseType)'
|
||||||
title: 'Version $(Version.Major).$(Version.Minor) $(buildConfiguration) Feature $(Version.Build) Build $(Build.BuildId)'
|
title: 'IW4MAdmin $(Build.BuildNumber) ($(releaseType))'
|
||||||
assets: '$(Build.ArtifactStagingDirectory)/*.zip'
|
assets: '$(Build.ArtifactStagingDirectory)/*.zip'
|
||||||
isPreRelease: true
|
isPreRelease: $(isPreRelease)
|
||||||
releaseNotesSource: 'inline'
|
releaseNotesSource: 'inline'
|
||||||
releaseNotesInline: 'todo'
|
releaseNotesInline: 'todo'
|
||||||
changeLogCompareToRelease: 'lastNonDraftRelease'
|
changeLogCompareToRelease: 'lastNonDraftRelease'
|
||||||
changeLogType: 'commitBased'
|
changeLogType: 'commitBased'
|
||||||
|
|
||||||
- task: PowerShell@2
|
- task: PowerShell@2
|
||||||
|
displayName: 'Update master version'
|
||||||
inputs:
|
inputs:
|
||||||
targetType: 'inline'
|
targetType: 'inline'
|
||||||
script: |
|
script: |
|
||||||
$payload = @{
|
$payload = @{
|
||||||
'current-version-prerelease' = '$(Version.Major).$(Version.Minor).$(Version.Build).$(Build.BuildId)'
|
'current-version-$(releaseType)' = '$(Build.BuildNumber)'
|
||||||
'jwt-secret' = '$(JWTSecret)'
|
'jwt-secret' = '$(JWTSecret)'
|
||||||
} | ConvertTo-Json
|
} | ConvertTo-Json
|
||||||
|
|
||||||
@ -184,7 +207,8 @@ steps:
|
|||||||
Invoke-RestMethod @params
|
Invoke-RestMethod @params
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@1
|
- task: PublishPipelineArtifact@1
|
||||||
|
displayName: 'Publish artifact for analysis'
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: '$(outputFolder)'
|
targetPath: '$(outputFolder)'
|
||||||
artifact: 'IW4MAdmin.$(buildConfiguration)'
|
artifact: 'IW4MAdmin.$(buildConfiguration)'
|
||||||
publishLocation: 'pipeline'
|
publishLocation: 'pipeline'
|
@ -8,10 +8,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
GameFiles\IW4x\userraw\scripts\_commands.gsc = GameFiles\IW4x\userraw\scripts\_commands.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
|
GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc = GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc
|
||||||
PostPublish.ps1 = PostPublish.ps1
|
DeploymentFiles\deployment-pipeline.yml = DeploymentFiles\deployment-pipeline.yml
|
||||||
|
DeploymentFiles\PostPublish.ps1 = DeploymentFiles\PostPublish.ps1
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
RunPublishPre.cmd = RunPublishPre.cmd
|
|
||||||
RunPublishRelease.cmd = RunPublishRelease.cmd
|
|
||||||
version.txt = version.txt
|
version.txt = version.txt
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
@ -36,6 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
|||||||
Plugins\ScriptPlugins\ActionOnReport.js = Plugins\ScriptPlugins\ActionOnReport.js
|
Plugins\ScriptPlugins\ActionOnReport.js = Plugins\ScriptPlugins\ActionOnReport.js
|
||||||
Plugins\ScriptPlugins\ParserCoD4x.js = Plugins\ScriptPlugins\ParserCoD4x.js
|
Plugins\ScriptPlugins\ParserCoD4x.js = Plugins\ScriptPlugins\ParserCoD4x.js
|
||||||
Plugins\ScriptPlugins\ParserIW4x.js = Plugins\ScriptPlugins\ParserIW4x.js
|
Plugins\ScriptPlugins\ParserIW4x.js = Plugins\ScriptPlugins\ParserIW4x.js
|
||||||
|
Plugins\ScriptPlugins\ParserIW6x.js = Plugins\ScriptPlugins\ParserIW6x.js
|
||||||
Plugins\ScriptPlugins\ParserPIW5.js = Plugins\ScriptPlugins\ParserPIW5.js
|
Plugins\ScriptPlugins\ParserPIW5.js = Plugins\ScriptPlugins\ParserPIW5.js
|
||||||
Plugins\ScriptPlugins\ParserPT6.js = Plugins\ScriptPlugins\ParserPT6.js
|
Plugins\ScriptPlugins\ParserPT6.js = Plugins\ScriptPlugins\ParserPT6.js
|
||||||
Plugins\ScriptPlugins\ParserRektT5M.js = Plugins\ScriptPlugins\ParserRektT5M.js
|
Plugins\ScriptPlugins\ParserRektT5M.js = Plugins\ScriptPlugins\ParserRektT5M.js
|
||||||
|
@ -74,14 +74,14 @@ namespace LiveRadar.Web.Controllers
|
|||||||
[Route("Radar/Update")]
|
[Route("Radar/Update")]
|
||||||
public IActionResult Update(string payload)
|
public IActionResult Update(string payload)
|
||||||
{
|
{
|
||||||
var radarUpdate = RadarEvent.Parse(payload);
|
/*var radarUpdate = RadarEvent.Parse(payload);
|
||||||
var client = _manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
var client = _manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
||||||
|
|
||||||
if (client != null)
|
if (client != null)
|
||||||
{
|
{
|
||||||
radarUpdate.Name = client.Name.StripColors();
|
radarUpdate.Name = client.Name.StripColors();
|
||||||
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
33
Plugins/LiveRadar/Events/Script.cs
Normal file
33
Plugins/LiveRadar/Events/Script.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using EventGeneratorCallback = System.ValueTuple<string, string,
|
||||||
|
System.Func<string, SharedLibraryCore.Interfaces.IEventParserConfiguration,
|
||||||
|
SharedLibraryCore.GameEvent,
|
||||||
|
SharedLibraryCore.GameEvent>>;
|
||||||
|
|
||||||
|
namespace LiveRadar.Events
|
||||||
|
{
|
||||||
|
public class Script : IRegisterEvent
|
||||||
|
{
|
||||||
|
private const string EVENT_LIVERADAR = "LiveRadar";
|
||||||
|
private EventGeneratorCallback LiveRadar()
|
||||||
|
{
|
||||||
|
return (EVENT_LIVERADAR, EVENT_LIVERADAR, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
|
||||||
|
{
|
||||||
|
string[] lineSplit = eventLine.Split(";");
|
||||||
|
|
||||||
|
autoEvent.Type = GameEvent.EventType.Other;
|
||||||
|
autoEvent.Subtype = EVENT_LIVERADAR;
|
||||||
|
autoEvent.Origin = new EFClient() { NetworkId = 0 };
|
||||||
|
autoEvent.Extra = lineSplit[1]; // guid
|
||||||
|
|
||||||
|
return autoEvent;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<EventGeneratorCallback> Events => new[] { LiveRadar() };
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ using SharedLibraryCore;
|
|||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -17,12 +18,14 @@ namespace LiveRadar
|
|||||||
public string Author => "RaidMax";
|
public string Author => "RaidMax";
|
||||||
|
|
||||||
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
|
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
|
||||||
|
private readonly Dictionary<string, long> _botGuidLookups;
|
||||||
private bool addedPage;
|
private bool addedPage;
|
||||||
private readonly object lockObject = new object();
|
private readonly object lockObject = new object();
|
||||||
|
|
||||||
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
|
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
|
||||||
{
|
{
|
||||||
_configurationHandler = configurationHandlerFactory.GetConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration");
|
_configurationHandler = configurationHandlerFactory.GetConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration");
|
||||||
|
_botGuidLookups = new Dictionary<string, long>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnEventAsync(GameEvent E, Server S)
|
public Task OnEventAsync(GameEvent E, Server S)
|
||||||
@ -41,28 +44,45 @@ namespace LiveRadar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (E.Type == GameEvent.EventType.Unknown)
|
if (E.Type == GameEvent.EventType.PreConnect && E.Origin.IsBot)
|
||||||
{
|
{
|
||||||
if (E.Data?.StartsWith("LiveRadar") ?? false)
|
string botKey = $"BotGuid_{E.Extra}";
|
||||||
|
lock (lockObject)
|
||||||
{
|
{
|
||||||
try
|
if (!_botGuidLookups.ContainsKey(botKey))
|
||||||
{
|
{
|
||||||
var radarUpdate = RadarEvent.Parse(E.Data);
|
_botGuidLookups.Add(botKey, E.Origin.NetworkId);
|
||||||
var client = S.Manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (client != null)
|
if (E.Type == GameEvent.EventType.Other && E.Subtype == "LiveRadar")
|
||||||
{
|
{
|
||||||
radarUpdate.Name = client.Name.StripColors();
|
try
|
||||||
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
{
|
||||||
}
|
string botKey = $"BotGuid_{E.Extra}";
|
||||||
|
long generatedBotGuid;
|
||||||
|
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
generatedBotGuid = _botGuidLookups.ContainsKey(botKey) ? _botGuidLookups[botKey] : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
var radarUpdate = RadarEvent.Parse(E.Data, generatedBotGuid);
|
||||||
|
var client = S.Manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
||||||
|
|
||||||
|
if (client != null)
|
||||||
{
|
{
|
||||||
S.Logger.WriteWarning($"Could not parse live radar output: {e.Data}");
|
radarUpdate.Name = client.Name.StripColors();
|
||||||
S.Logger.WriteDebug(e.GetExceptionInfo());
|
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
S.Logger.WriteWarning($"Could not parse live radar output: {e.Data}");
|
||||||
|
S.Logger.WriteDebug(e.GetExceptionInfo());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LiveRadar
|
namespace LiveRadar
|
||||||
{
|
{
|
||||||
@ -39,13 +37,13 @@ namespace LiveRadar
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RadarEvent Parse(string input)
|
public static RadarEvent Parse(string input, long generatedBotGuid)
|
||||||
{
|
{
|
||||||
var items = input.Split(';').Skip(1).ToList();
|
var items = input.Split(';').Skip(1).ToList();
|
||||||
|
|
||||||
var parsedEvent = new RadarEvent()
|
var parsedEvent = new RadarEvent()
|
||||||
{
|
{
|
||||||
Guid = items[0].ConvertGuidToLong(System.Globalization.NumberStyles.HexNumber),
|
Guid = generatedBotGuid,
|
||||||
Location = Vector3.Parse(items[1]),
|
Location = Vector3.Parse(items[1]),
|
||||||
ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(),
|
ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(),
|
||||||
Team = items[3],
|
Team = items[3],
|
||||||
|
43
Plugins/ScriptPlugins/ParserIW6x.js
Normal file
43
Plugins/ScriptPlugins/ParserIW6x.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
var rconParser;
|
||||||
|
var eventParser;
|
||||||
|
|
||||||
|
var plugin = {
|
||||||
|
author: 'Xerxes, RaidMax',
|
||||||
|
version: 0.1,
|
||||||
|
name: 'IW6x Parser',
|
||||||
|
isParser: true,
|
||||||
|
|
||||||
|
onEventAsync: function (gameEvent, server) {
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadAsync: function (manager) {
|
||||||
|
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||||
|
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||||
|
|
||||||
|
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0} "{1}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0} "{1}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick {0} "{1}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
||||||
|
rconParser.Configuration.Dvar.Pattern = '^ *\\"(.+)\\" is: \\"(.+)?\\" default: \\"(.+)?\\"\\n(?:latched: \\"(.+)?\\"\\n)? *(.+)$';
|
||||||
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(Yes|No) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) *$';
|
||||||
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +address +qport *';
|
||||||
|
rconParser.Configuration.WaitForResponse = false;
|
||||||
|
rconParser.Configuration.Status.AddMapping(102, 4);
|
||||||
|
rconParser.Configuration.Status.AddMapping(103, 5);
|
||||||
|
rconParser.Configuration.Status.AddMapping(104, 6);
|
||||||
|
|
||||||
|
rconParser.Version = 'IW6 MP 3.15 build 2 Sat Sep 14 2013 03:58:30PM win64';
|
||||||
|
rconParser.GameName = 4; // IW6
|
||||||
|
eventParser.Version = 'IW6 MP 3.15 build 2 Sat Sep 14 2013 03:58:30PM win64';
|
||||||
|
eventParser.GameName = 4; // IW6
|
||||||
|
eventParser.Configuration.GameDirectory = 'iw6x';
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnloadAsync: function () {
|
||||||
|
},
|
||||||
|
|
||||||
|
onTickAsync: function (server) {
|
||||||
|
}
|
||||||
|
};
|
@ -3,7 +3,7 @@ var eventParser;
|
|||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: 0.2,
|
version: 0.3,
|
||||||
name: 'RektT5m Parser',
|
name: 'RektT5m Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ var plugin = {
|
|||||||
|
|
||||||
eventParser.Configuration.GameDirectory = 'data';
|
eventParser.Configuration.GameDirectory = 'data';
|
||||||
|
|
||||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff\x01print';
|
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff\x01print\n';
|
||||||
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
|
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
|
||||||
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
|
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ let commands = [{
|
|||||||
alias: "pp",
|
alias: "pp",
|
||||||
// required
|
// required
|
||||||
permission: "User",
|
permission: "User",
|
||||||
|
// optional (defaults to false)
|
||||||
|
targetRequired: false,
|
||||||
// optional
|
// optional
|
||||||
arguments: [{
|
arguments: [{
|
||||||
name: "times to ping",
|
name: "times to ping",
|
||||||
@ -44,6 +46,8 @@ let plugin = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onLoadAsync: function (manager) {
|
onLoadAsync: function (manager) {
|
||||||
|
this.logger = _serviceResolver.ResolveService("ILogger");
|
||||||
|
this.logger.WriteDebug("sample plugin loaded");
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnloadAsync: function () {
|
onUnloadAsync: function () {
|
||||||
|
@ -20,7 +20,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
Offset,
|
Offset,
|
||||||
Strain,
|
Strain,
|
||||||
Recoil,
|
Recoil,
|
||||||
Snap
|
Snap,
|
||||||
|
Button
|
||||||
};
|
};
|
||||||
|
|
||||||
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
||||||
@ -38,11 +39,12 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
ILogger Log;
|
ILogger Log;
|
||||||
Strain Strain;
|
Strain Strain;
|
||||||
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
||||||
private double sessionAverageRecoilAmount;
|
private double mapAverageRecoilAmount;
|
||||||
private double sessionAverageSnapAmount;
|
private double sessionAverageSnapAmount;
|
||||||
private int sessionSnapHits;
|
private int sessionSnapHits;
|
||||||
private EFClientKill lastHit;
|
private EFClientKill lastHit;
|
||||||
private int validRecoilHitCount;
|
private int validRecoilHitCount;
|
||||||
|
private int validButtonHitCount;
|
||||||
|
|
||||||
private class HitInfo
|
private class HitInfo
|
||||||
{
|
{
|
||||||
@ -282,18 +284,30 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
|
|
||||||
#region RECOIL
|
#region RECOIL
|
||||||
float hitRecoilAverage = 0;
|
float hitRecoilAverage = 0;
|
||||||
if (!Plugin.Config.Configuration().RecoilessWeapons.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex)))
|
bool shouldIgnoreDetection = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Recoil]
|
||||||
|
.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldIgnoreDetection)
|
||||||
{
|
{
|
||||||
validRecoilHitCount++;
|
validRecoilHitCount++;
|
||||||
hitRecoilAverage = (hit.AnglesList.Sum(_angle => _angle.Z) + hit.ViewAngles.Z) / (hit.AnglesList.Count + 1);
|
hitRecoilAverage = (hit.AnglesList.Sum(_angle => _angle.Z) + hit.ViewAngles.Z) / (hit.AnglesList.Count + 1);
|
||||||
sessionAverageRecoilAmount = (sessionAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount;
|
mapAverageRecoilAmount = (mapAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount;
|
||||||
|
|
||||||
if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && sessionAverageRecoilAmount == 0)
|
if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && mapAverageRecoilAmount == 0)
|
||||||
{
|
{
|
||||||
results.Add(new DetectionPenaltyResult()
|
results.Add(new DetectionPenaltyResult()
|
||||||
{
|
{
|
||||||
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
||||||
Value = sessionAverageRecoilAmount,
|
Value = mapAverageRecoilAmount,
|
||||||
HitCount = HitCount,
|
HitCount = HitCount,
|
||||||
Type = DetectionType.Recoil
|
Type = DetectionType.Recoil
|
||||||
});
|
});
|
||||||
@ -301,6 +315,37 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region BUTTON
|
||||||
|
try
|
||||||
|
{
|
||||||
|
shouldIgnoreDetection = false;
|
||||||
|
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Button]
|
||||||
|
.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldIgnoreDetection)
|
||||||
|
{
|
||||||
|
validButtonHitCount++;
|
||||||
|
|
||||||
|
double lastDiff = hit.TimeOffset - hit.TimeSinceLastAttack;
|
||||||
|
if (validButtonHitCount > 0 && lastDiff <= 0)
|
||||||
|
{
|
||||||
|
results.Add(new DetectionPenaltyResult()
|
||||||
|
{
|
||||||
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
||||||
|
Value = lastDiff,
|
||||||
|
HitCount = HitCount,
|
||||||
|
Type = DetectionType.Button
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region SESSION_RATIOS
|
#region SESSION_RATIOS
|
||||||
if (Kills >= Thresholds.LowSampleMinKills)
|
if (Kills >= Thresholds.LowSampleMinKills)
|
||||||
{
|
{
|
||||||
@ -384,7 +429,19 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
#region CHEST_ABDOMEN_RATIO_SESSION
|
#region CHEST_ABDOMEN_RATIO_SESSION
|
||||||
int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper].Count;
|
int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper].Count;
|
||||||
|
|
||||||
if (chestHits >= Thresholds.MediumSampleMinKills)
|
try
|
||||||
|
{
|
||||||
|
shouldIgnoreDetection = false; // reset previous value
|
||||||
|
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Chest]
|
||||||
|
.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chestHits >= Thresholds.MediumSampleMinKills && !shouldIgnoreDetection)
|
||||||
{
|
{
|
||||||
double marginOfError = Thresholds.GetMarginOfError(chestHits);
|
double marginOfError = Thresholds.GetMarginOfError(chestHits);
|
||||||
double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
|
double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
|
||||||
@ -466,5 +523,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnMapChange()
|
||||||
|
{
|
||||||
|
mapAverageRecoilAmount = 0;
|
||||||
|
validRecoilHitCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
Plugins/Stats/Config/AnticheatConfiguration.cs
Normal file
24
Plugins/Stats/Config/AnticheatConfiguration.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
|
namespace Stats.Config
|
||||||
|
{
|
||||||
|
public class AnticheatConfiguration
|
||||||
|
{
|
||||||
|
public bool Enable { get; set; }
|
||||||
|
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; } = new Dictionary<long, DetectionType[]>();
|
||||||
|
public IList<long> IgnoredClientIds { get; set; } = new List<long>();
|
||||||
|
public IDictionary<Game, IDictionary<DetectionType, string[]>> IgnoredDetectionSpecification{ get; set; } = new Dictionary<Game, IDictionary<DetectionType, string[]>>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Game.IW4, new Dictionary<DetectionType, string[]>
|
||||||
|
{
|
||||||
|
{ DetectionType.Chest, new[] { "m21.+" } },
|
||||||
|
{ DetectionType.Recoil, new[] { "ranger.*_mp", "model1887.*_mp", ".+shotgun.*_mp" } },
|
||||||
|
{ DetectionType.Button, new[] { ".*akimbo.*" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||||
|
|
||||||
@ -8,21 +9,41 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
|||||||
{
|
{
|
||||||
public class StatsConfiguration : IBaseConfiguration
|
public class StatsConfiguration : IBaseConfiguration
|
||||||
{
|
{
|
||||||
public bool EnableAntiCheat { get; set; }
|
[Obsolete]
|
||||||
|
public bool? EnableAntiCheat { get; set; }
|
||||||
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }
|
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }
|
||||||
public List<StreakMessageConfiguration> DeathstreakMessages { get; set; }
|
public List<StreakMessageConfiguration> DeathstreakMessages { get; set; }
|
||||||
public List<string> RecoilessWeapons { get; set; }
|
|
||||||
public int TopPlayersMinPlayTime { get; set; }
|
public int TopPlayersMinPlayTime { get; set; }
|
||||||
public bool StoreClientKills { get; set; }
|
public bool StoreClientKills { get; set; }
|
||||||
public int MostKillsMaxInactivityDays { get; set; } = 30;
|
public int MostKillsMaxInactivityDays { get; set; } = 30;
|
||||||
public int MostKillsClientLimit { get; set; } = 5;
|
public int MostKillsClientLimit { get; set; } = 5;
|
||||||
public IDictionary<DetectionType, DistributionConfiguration> DetectionDistributions { get; set; }
|
[Obsolete]
|
||||||
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; }
|
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; }
|
||||||
|
public AnticheatConfiguration AnticheatConfiguration { get; set; } = new AnticheatConfiguration();
|
||||||
|
|
||||||
|
#pragma warning disable CS0612 // Type or member is obsolete
|
||||||
|
public void ApplyMigration()
|
||||||
|
{
|
||||||
|
if (ServerDetectionTypes != null)
|
||||||
|
{
|
||||||
|
AnticheatConfiguration.ServerDetectionTypes = ServerDetectionTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerDetectionTypes = null;
|
||||||
|
|
||||||
|
if (EnableAntiCheat != null)
|
||||||
|
{
|
||||||
|
AnticheatConfiguration.Enable = EnableAntiCheat.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnableAntiCheat = null;
|
||||||
|
}
|
||||||
|
#pragma warning restore CS0612 // Type or member is obsolete
|
||||||
|
|
||||||
public string Name() => "StatsPluginSettings";
|
public string Name() => "StatsPluginSettings";
|
||||||
public IBaseConfiguration Generate()
|
public IBaseConfiguration Generate()
|
||||||
{
|
{
|
||||||
EnableAntiCheat = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);
|
AnticheatConfiguration.Enable = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);
|
||||||
KillstreakMessages = new List<StreakMessageConfiguration>()
|
KillstreakMessages = new List<StreakMessageConfiguration>()
|
||||||
{
|
{
|
||||||
new StreakMessageConfiguration(){
|
new StreakMessageConfiguration(){
|
||||||
@ -57,16 +78,9 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
RecoilessWeapons = new List<string>()
|
|
||||||
{
|
|
||||||
"ranger.*_mp",
|
|
||||||
"model1887.*_mp",
|
|
||||||
".+shotgun.*_mp"
|
|
||||||
};
|
|
||||||
|
|
||||||
TopPlayersMinPlayTime = 3600 * 3;
|
TopPlayersMinPlayTime = 3600 * 3;
|
||||||
StoreClientKills = false;
|
StoreClientKills = false;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -481,7 +481,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
IsKill = !isDamage,
|
IsKill = !isDamage,
|
||||||
AnglesList = snapshotAngles,
|
AnglesList = snapshotAngles,
|
||||||
IsAlive = isAlive == "1",
|
IsAlive = isAlive == "1",
|
||||||
TimeSinceLastAttack = long.Parse(lastAttackTime)
|
TimeSinceLastAttack = long.Parse(lastAttackTime),
|
||||||
|
GameName = attacker.CurrentServer.GameName
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hit.HitLoc == IW4Info.HitLocation.shield)
|
if (hit.HitLoc == IW4Info.HitLocation.shield)
|
||||||
@ -539,7 +540,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Plugin.Config.Configuration().EnableAntiCheat && !attacker.IsBot && attacker.ClientId != victim.ClientId)
|
if (Plugin.Config.Configuration().AnticheatConfiguration.Enable && !attacker.IsBot && attacker.ClientId != victim.ClientId)
|
||||||
{
|
{
|
||||||
clientDetection.TrackedHits.Add(hit);
|
clientDetection.TrackedHits.Add(hit);
|
||||||
|
|
||||||
@ -555,10 +556,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
if (oldestHit.IsAlive)
|
if (oldestHit.IsAlive)
|
||||||
{
|
{
|
||||||
var result = DeterminePenaltyResult(clientDetection.ProcessHit(oldestHit), attacker.CurrentServer.EndPoint);
|
var result = DeterminePenaltyResult(clientDetection.ProcessHit(oldestHit), attacker);
|
||||||
#if !DEBUG
|
|
||||||
await ApplyPenalty(result, attacker);
|
if (!Utilities.IsDevelopment)
|
||||||
#endif
|
{
|
||||||
|
await ApplyPenalty(result, attacker);
|
||||||
|
}
|
||||||
|
|
||||||
if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any)
|
if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any)
|
||||||
{
|
{
|
||||||
@ -594,10 +597,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DetectionPenaltyResult DeterminePenaltyResult(IEnumerable<DetectionPenaltyResult> results, long serverId)
|
private DetectionPenaltyResult DeterminePenaltyResult(IEnumerable<DetectionPenaltyResult> results, EFClient client)
|
||||||
{
|
{
|
||||||
// allow disabling of certain detection types
|
// allow disabling of certain detection types
|
||||||
results = results.Where(_result => ShouldUseDetection(serverId, _result.Type));
|
results = results.Where(_result => ShouldUseDetection(client.CurrentServer, _result.Type, client.ClientId));
|
||||||
return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ??
|
return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ??
|
||||||
results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ??
|
results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ??
|
||||||
new DetectionPenaltyResult()
|
new DetectionPenaltyResult()
|
||||||
@ -617,21 +620,31 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldUseDetection(long serverId, DetectionType detectionType)
|
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
|
||||||
{
|
{
|
||||||
var detectionTypes = Plugin.Config.Configuration().ServerDetectionTypes;
|
var detectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.ServerDetectionTypes;
|
||||||
|
var ignoredClients = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredClientIds;
|
||||||
|
|
||||||
if (detectionTypes == null)
|
if (ignoredClients.Contains(clientId))
|
||||||
{
|
{
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!detectionTypes.ContainsKey(serverId))
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return true;
|
if (!detectionTypes[server.EndPoint].Contains(detectionType))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return detectionTypes[serverId].Contains(detectionType);
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task ApplyPenalty(DetectionPenaltyResult penalty, EFClient attacker)
|
async Task ApplyPenalty(DetectionPenaltyResult penalty, EFClient attacker)
|
||||||
@ -1139,10 +1152,15 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
public void ResetKillstreaks(Server sv)
|
public void ResetKillstreaks(Server sv)
|
||||||
{
|
{
|
||||||
foreach (var stat in sv.GetClientsAsList()
|
foreach (var session in sv.GetClientsAsList()
|
||||||
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY)))
|
.Select(_client => new
|
||||||
|
{
|
||||||
|
stat = _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY),
|
||||||
|
detection = _client.GetAdditionalProperty<Detection>(CLIENT_DETECTIONS_KEY)
|
||||||
|
}))
|
||||||
{
|
{
|
||||||
stat?.StartNewSession();
|
session.stat?.StartNewSession();
|
||||||
|
session.detection?.OnMapChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1232,6 +1250,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return 886229536;
|
return 886229536;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: this is not stable and will need to be migrated again...
|
||||||
long id = HashCode.Combine(server.IP, server.Port);
|
long id = HashCode.Combine(server.IP, server.Port);
|
||||||
id = id < 0 ? Math.Abs(id) : id;
|
id = id < 0 ? Math.Abs(id) : id;
|
||||||
long? serverId;
|
long? serverId;
|
||||||
|
@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Models
|
namespace IW4MAdmin.Plugins.Stats.Models
|
||||||
{
|
{
|
||||||
@ -44,6 +45,8 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
|||||||
public float AdsPercent { get; set; }
|
public float AdsPercent { get; set; }
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public List<Vector3> AnglesList { get; set; }
|
public List<Vector3> AnglesList { get; set; }
|
||||||
|
[NotMapped]
|
||||||
|
public Game GameName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the attacker was alive after last captured angle
|
/// Indicates if the attacker was alive after last captured angle
|
||||||
|
@ -182,8 +182,9 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
if (Config.Configuration() == null)
|
if (Config.Configuration() == null)
|
||||||
{
|
{
|
||||||
Config.Set((StatsConfiguration)new StatsConfiguration().Generate());
|
Config.Set((StatsConfiguration)new StatsConfiguration().Generate());
|
||||||
await Config.Save();
|
|
||||||
}
|
}
|
||||||
|
Config.Configuration().ApplyMigration();
|
||||||
|
await Config.Save();
|
||||||
|
|
||||||
// register the topstats page
|
// register the topstats page
|
||||||
// todo:generate the URL/Location instead of hardcoding
|
// todo:generate the URL/Location instead of hardcoding
|
||||||
@ -405,7 +406,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
return (await _chatQueryHelper.QueryResource(query)).Results;
|
return (await _chatQueryHelper.QueryResource(query)).Results;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.Configuration().EnableAntiCheat)
|
if (Config.Configuration().AnticheatConfiguration.Enable)
|
||||||
{
|
{
|
||||||
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, getAnticheatInfo);
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, getAnticheatInfo);
|
||||||
}
|
}
|
||||||
@ -496,6 +497,6 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="s"></param>
|
/// <param name="s"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private bool ShouldOverrideAnticheatSetting(Server s) => Config.Configuration().EnableAntiCheat && s.GameName == Server.Game.IW5;
|
private bool ShouldOverrideAnticheatSetting(Server s) => Config.Configuration().AnticheatConfiguration.Enable && s.GameName == Server.Game.IW5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
dotnet publish WebfrontCore/WebfrontCore.csproj -c Prerelease -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\WindowsPrerelease /p:PublishProfile=Prerelease
|
|
||||||
dotnet publish Application/Application.csproj -c Prerelease -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\WindowsPrerelease /p:PublishProfile=Prerelease
|
|
||||||
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\WindowsPrerelease\GameLogServer
|
|
||||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
|
|
||||||
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=PreRelease /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\GameLogServer\
|
|
||||||
cd "X:\IW4MAdmin\DEPLOY\"
|
|
||||||
PowerShell ".\upload_prerelease.ps1"
|
|
@ -1,7 +0,0 @@
|
|||||||
dotnet publish WebfrontCore/WebfrontCore.csproj -c Release -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\Windows /p:PublishProfile=sTABLE
|
|
||||||
dotnet publish Application/Application.csproj -c Release -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\Windows /p:PublishProfile=Stable
|
|
||||||
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\WindowsPrerelease\GameLogServer
|
|
||||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
|
|
||||||
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\GameLogServer\
|
|
||||||
cd "X:\IW4MAdmin\DEPLOY\"
|
|
||||||
PowerShell ".\upload_release.ps1"
|
|
@ -5,6 +5,7 @@ using SharedLibraryCore.Commands;
|
|||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace SharedLibraryCore
|
namespace SharedLibraryCore
|
||||||
{
|
{
|
||||||
@ -13,7 +14,7 @@ namespace SharedLibraryCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Command : IManagerCommand
|
public abstract class Command : IManagerCommand
|
||||||
{
|
{
|
||||||
private readonly CommandConfiguration _config;
|
protected readonly CommandConfiguration _config;
|
||||||
protected readonly ITranslationLookup _translationLookup;
|
protected readonly ITranslationLookup _translationLookup;
|
||||||
protected ILogger logger;
|
protected ILogger logger;
|
||||||
|
|
||||||
@ -113,6 +114,25 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
private EFClient.Permission permission;
|
private EFClient.Permission permission;
|
||||||
|
|
||||||
|
public Game[] SupportedGames
|
||||||
|
{
|
||||||
|
get => supportedGames;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var savedGames = _config?.Commands[GetType().Name].SupportedGames;
|
||||||
|
supportedGames = savedGames?.Length != 0 ? savedGames : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
supportedGames = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Game[] supportedGames;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Argument list for the command
|
/// Argument list for the command
|
||||||
|
@ -239,6 +239,42 @@ namespace SharedLibraryCore.Commands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prints out a message to all clients on all servers
|
||||||
|
/// </summary>
|
||||||
|
public class SayAllCommand : Command
|
||||||
|
{
|
||||||
|
public SayAllCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
|
{
|
||||||
|
Name = "sayall";
|
||||||
|
Description = _translationLookup["COMMANDS_SAY_ALL_DESC"];
|
||||||
|
Alias = "sa";
|
||||||
|
Permission = Permission.Moderator;
|
||||||
|
RequiresTarget = false;
|
||||||
|
Arguments = new[]
|
||||||
|
{
|
||||||
|
new CommandArgument()
|
||||||
|
{
|
||||||
|
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
|
||||||
|
Required = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
|
{
|
||||||
|
string message = _translationLookup["COMMANDS_SAY_ALL_MESSAGE_FORMAT"].FormatExt(E.Origin.Name, E.Data);
|
||||||
|
|
||||||
|
foreach (var server in E.Owner.Manager.GetServers())
|
||||||
|
{
|
||||||
|
server.Broadcast(message, E.Origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
E.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Temporarily bans a client
|
/// Temporarily bans a client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -596,11 +632,11 @@ namespace SharedLibraryCore.Commands
|
|||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
var _ = !E.Origin.Masked ?
|
_ = !E.Origin.Masked ?
|
||||||
E.Owner.Broadcast($"{_translationLookup["COMMANDS_MAPROTATE"]} [^5{E.Origin.Name}^7]", E.Origin) :
|
E.Owner.Broadcast($"{_translationLookup["COMMANDS_MAPROTATE"]} [^5{E.Origin.Name}^7]", E.Origin) :
|
||||||
E.Owner.Broadcast(_translationLookup["COMMANDS_MAPROTATE"], E.Origin);
|
E.Owner.Broadcast(_translationLookup["COMMANDS_MAPROTATE"], E.Origin);
|
||||||
|
|
||||||
await Task.Delay(5000);
|
await Task.Delay(E.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds * 1000);
|
||||||
await E.Owner.ExecuteCommandAsync("map_rotate");
|
await E.Owner.ExecuteCommandAsync("map_rotate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -772,8 +808,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ListAdminsCommand : Command
|
public class ListAdminsCommand : Command
|
||||||
{
|
{
|
||||||
private readonly CommandConfiguration _config;
|
|
||||||
|
|
||||||
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "admins";
|
Name = "admins";
|
||||||
@ -781,8 +815,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
Alias = "a";
|
Alias = "a";
|
||||||
Permission = Permission.User;
|
Permission = Permission.User;
|
||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
|
|
||||||
_config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string OnlineAdmins(Server S, ITranslationLookup lookup)
|
public static string OnlineAdmins(Server S, ITranslationLookup lookup)
|
||||||
@ -832,22 +864,18 @@ namespace SharedLibraryCore.Commands
|
|||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
string newMap = E.Data.Trim().ToLower();
|
string newMap = E.Data.Trim();
|
||||||
foreach (Map m in E.Owner.Maps)
|
int delay = E.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds * 1000;
|
||||||
{
|
|
||||||
if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap)
|
|
||||||
{
|
|
||||||
E.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(m.Alias));
|
|
||||||
await Task.Delay((int)(Utilities.DefaultCommandTimeout.TotalMilliseconds / 2.0));
|
|
||||||
await E.Owner.LoadMap(m.Name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: this can be moved into a single statement
|
var foundMap = E.Owner.Maps.FirstOrDefault(_map => _map.Name.Equals(newMap, StringComparison.InvariantCultureIgnoreCase) ||
|
||||||
E.Owner.Broadcast(_translationLookup["COMMANDS_MAP_UKN"].FormatExt(newMap));
|
_map.Alias.Equals(newMap, StringComparison.InvariantCultureIgnoreCase));
|
||||||
await Task.Delay(5000);
|
|
||||||
await E.Owner.LoadMap(newMap);
|
_ = foundMap == null ?
|
||||||
|
E.Owner.Broadcast(_translationLookup["COMMANDS_MAP_UKN"].FormatExt(newMap)) :
|
||||||
|
E.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(foundMap.Alias));
|
||||||
|
|
||||||
|
await Task.Delay(delay);
|
||||||
|
await E.Owner.LoadMap(foundMap?.Name ?? newMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -901,8 +929,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ListRulesCommands : Command
|
public class ListRulesCommands : Command
|
||||||
{
|
{
|
||||||
private readonly CommandConfiguration _config;
|
|
||||||
|
|
||||||
public ListRulesCommands(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
public ListRulesCommands(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "rules";
|
Name = "rules";
|
||||||
@ -910,8 +936,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
Alias = "r";
|
Alias = "r";
|
||||||
Permission = Permission.User;
|
Permission = Permission.User;
|
||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
|
|
||||||
_config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task ExecuteAsync(GameEvent E)
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
@ -1537,7 +1561,7 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SetGravatarCommand : Command
|
public class SetGravatarCommand : Command
|
||||||
{
|
{
|
||||||
private readonly IMetaService _metaService;
|
private readonly IMetaService _metaService;
|
||||||
|
|
||||||
public SetGravatarCommand(CommandConfiguration config, ITranslationLookup translationLookup, IMetaService metaService) : base(config, translationLookup)
|
public SetGravatarCommand(CommandConfiguration config, ITranslationLookup translationLookup, IMetaService metaService) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
|
37
SharedLibraryCore/Commands/PrivateMessageAdminsCommand.cs
Normal file
37
SharedLibraryCore/Commands/PrivateMessageAdminsCommand.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Commands
|
||||||
|
{
|
||||||
|
public class PrivateMessageAdminsCommand : Command
|
||||||
|
{
|
||||||
|
public PrivateMessageAdminsCommand(CommandConfiguration config, ITranslationLookup lookup) : base(config, lookup)
|
||||||
|
{
|
||||||
|
Name = "privatemessageadmin";
|
||||||
|
Description = lookup["COMMANDS_PMADMINS_DESC"];
|
||||||
|
Alias = "pma";
|
||||||
|
Permission = EFClient.Permission.Moderator;
|
||||||
|
SupportedGames = new[] { Game.IW4, Game.IW5 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
|
{
|
||||||
|
bool isGameSupported = _config.Commands[nameof(PrivateMessageAdminsCommand)].SupportedGames.Length > 0 &&
|
||||||
|
_config.Commands[nameof(PrivateMessageAdminsCommand)].SupportedGames.Contains(E.Owner.GameName);
|
||||||
|
|
||||||
|
if (!isGameSupported)
|
||||||
|
{
|
||||||
|
E.Origin.Tell(_translationLookup["COMMANDS_GAME_NOT_SUPPORTED"].FormatExt(nameof(PrivateMessageAdminsCommand)));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
E.Owner.ToAdmins(E.Data);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -91,12 +91,16 @@ namespace SharedLibraryCore.Configuration
|
|||||||
public string[] GlobalRules { get; set; } = new string[0];
|
public string[] GlobalRules { get; set; } = new string[0];
|
||||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_DISALLOWED_NAMES")]
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_DISALLOWED_NAMES")]
|
||||||
public string[] DisallowedClientNames { get; set; } = new string[0];
|
public string[] DisallowedClientNames { get; set; } = new string[0];
|
||||||
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_MAP_CHANGE_DELAY")]
|
||||||
|
public int MapChangeDelaySeconds { get; set; } = 5;
|
||||||
[UIHint("ServerConfiguration")]
|
[UIHint("ServerConfiguration")]
|
||||||
public ServerConfiguration[] Servers { get; set; }
|
public ServerConfiguration[] Servers { get; set; }
|
||||||
|
|
||||||
[ConfigurationIgnore]
|
[ConfigurationIgnore]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
[ConfigurationIgnore]
|
[ConfigurationIgnore]
|
||||||
|
public string SubscriptionId { get; set; }
|
||||||
|
[ConfigurationIgnore]
|
||||||
public MapConfiguration[] Maps { get; set; }
|
public MapConfiguration[] Maps { get; set; }
|
||||||
[ConfigurationIgnore]
|
[ConfigurationIgnore]
|
||||||
public QuickMessageConfiguration[] QuickMessages { get; set; }
|
public QuickMessageConfiguration[] QuickMessages { get; set; }
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json;
|
||||||
using System.Text.Json.Serialization;
|
using Newtonsoft.Json.Converters;
|
||||||
using static SharedLibraryCore.Database.Models.EFClient;
|
using static SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Configuration
|
namespace SharedLibraryCore.Configuration
|
||||||
{
|
{
|
||||||
@ -29,5 +30,11 @@ namespace SharedLibraryCore.Configuration
|
|||||||
/// Indicates if the command can be run by another user (impersonation)
|
/// Indicates if the command can be run by another user (impersonation)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllowImpersonation { get; set; }
|
public bool AllowImpersonation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the games supporting the functionality of the command
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
|
||||||
|
public Game[] SupportedGames { get; set; } = new Game[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,18 @@ namespace SharedLibraryCore.Database
|
|||||||
pluginDir = Path.Join(Utilities.OperatingDirectory, "..", "..", "..", "..", "BUILD", "Plugins");
|
pluginDir = Path.Join(Utilities.OperatingDirectory, "..", "..", "..", "..", "BUILD", "Plugins");
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<string> directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
|
IEnumerable<string> directoryFiles = Enumerable.Empty<string>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
// this is just an ugly thing for unit testing
|
||||||
|
directoryFiles = Directory.GetFiles(@"X:\IW4MAdmin\Tests\ApplicationTests\bin\Debug\netcoreapp3.1").Where(f => f.EndsWith("dll"));
|
||||||
|
}
|
||||||
|
|
||||||
foreach (string dllPath in directoryFiles)
|
foreach (string dllPath in directoryFiles)
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
using System;
|
namespace SharedLibraryCore.Dtos.Meta.Responses
|
||||||
|
|
||||||
namespace SharedLibraryCore.Dtos.Meta.Responses
|
|
||||||
{
|
{
|
||||||
public class UpdatedAliasResponse : BaseMetaResponse
|
public class UpdatedAliasResponse : BaseMetaResponse
|
||||||
{
|
{
|
||||||
@ -17,6 +15,6 @@ namespace SharedLibraryCore.Dtos.Meta.Responses
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode() => HashCode.Combine(Name.StripColors(), IPAddress);
|
public override int GetHashCode() => $"{Name.StripColors()}{IPAddress}".GetStableHashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static SharedLibraryCore.Database.Models.EFClient;
|
using static SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Interfaces
|
namespace SharedLibraryCore.Interfaces
|
||||||
{
|
{
|
||||||
@ -35,6 +36,11 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Permission Permission { get; }
|
Permission Permission { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Games the command is supported on
|
||||||
|
/// </summary>
|
||||||
|
Game[] SupportedGames { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Syntax for using the command
|
/// Syntax for using the command
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
11
SharedLibraryCore/Interfaces/IRemoteAssemblyHandler.cs
Normal file
11
SharedLibraryCore/Interfaces/IRemoteAssemblyHandler.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces
|
||||||
|
{
|
||||||
|
public interface IRemoteAssemblyHandler
|
||||||
|
{
|
||||||
|
IEnumerable<Assembly> DecryptAssemblies(string[] encryptedAssemblies);
|
||||||
|
IEnumerable<string> DecryptScripts(string[] encryptedScripts);
|
||||||
|
}
|
||||||
|
}
|
@ -15,9 +15,10 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="alias">alias of command</param>
|
/// <param name="alias">alias of command</param>
|
||||||
/// <param name="description">description of command</param>
|
/// <param name="description">description of command</param>
|
||||||
/// <param name="permission">minimum required permission</param>
|
/// <param name="permission">minimum required permission</param>
|
||||||
|
/// <param name="isTargetRequired">target required or not</param>
|
||||||
/// <param name="args">command arguments (name, is required)</param>
|
/// <param name="args">command arguments (name, is required)</param>
|
||||||
/// <param name="executeAction">action to peform when commmand is executed</param>
|
/// <param name="executeAction">action to peform when commmand is executed</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction);
|
IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, bool isTargetRequired, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs
Normal file
25
SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// interface used to dynamically resolve services by string name
|
||||||
|
/// </summary>
|
||||||
|
public interface IScriptPluginServiceResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// resolves a service with the given name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceName">class name of service</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
object ResolveService(string serviceName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// resolves a service with the given name and generic params
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceName">class name of service</param>
|
||||||
|
/// <param name="genericParams">generic class names</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
object ResolveService(string serviceName, string[] genericParameters);
|
||||||
|
}
|
||||||
|
}
|
@ -49,14 +49,23 @@ namespace SharedLibraryCore.RCon
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// timeout in seconds to wait for a socket send or receive before giving up
|
/// timeout in seconds to wait for a socket send or receive before giving up
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly int SocketTimeout = 10000;
|
public static TimeSpan SocketTimeout(int retryAttempt)
|
||||||
|
{
|
||||||
|
return retryAttempt switch
|
||||||
|
{
|
||||||
|
1 => TimeSpan.FromMilliseconds(550),
|
||||||
|
2 => TimeSpan.FromMilliseconds(1000),
|
||||||
|
3 => TimeSpan.FromMilliseconds(2000),
|
||||||
|
_ => TimeSpan.FromMilliseconds(5000),
|
||||||
|
};
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// interval in milliseconds to wait before sending the next RCon request
|
/// interval in milliseconds to wait before sending the next RCon request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly int FloodProtectionInterval = 650;
|
public static readonly int FloodProtectionInterval = 750;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// how many failed connection attempts before aborting connection
|
/// how many failed connection attempts before aborting connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly int AllowedConnectionFails = 3;
|
public static readonly int AllowedConnectionFails = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ namespace SharedLibraryCore
|
|||||||
/// <param name="message">Message to be sent to all players</param>
|
/// <param name="message">Message to be sent to all players</param>
|
||||||
public GameEvent Broadcast(string message, EFClient sender = null)
|
public GameEvent Broadcast(string message, EFClient sender = null)
|
||||||
{
|
{
|
||||||
string formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Say, $"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message.FixIW4ForwardSlash()}");
|
string formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Say ?? "", $"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message.FixIW4ForwardSlash()}");
|
||||||
#if DEBUG == true
|
#if DEBUG == true
|
||||||
Logger.WriteVerbose(message.StripColors());
|
Logger.WriteVerbose(message.StripColors());
|
||||||
#endif
|
#endif
|
||||||
@ -269,7 +269,7 @@ namespace SharedLibraryCore
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (await this.GetDvarAsync<string>("sv_customcallbacks")).Value == "1";
|
return (await this.GetDvarAsync("sv_customcallbacks", "0")).Value == "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exceptions.DvarException)
|
catch (Exceptions.DvarException)
|
||||||
|
@ -369,7 +369,27 @@ namespace SharedLibraryCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">value string</param>
|
/// <param name="value">value string</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static long GenerateGuidFromString(this string value) => string.IsNullOrEmpty(value) ? -1 : HashCode.Combine(value.StripColors());
|
public static long GenerateGuidFromString(this string value) => string.IsNullOrEmpty(value) ? -1 : GetStableHashCode(value.StripColors());
|
||||||
|
|
||||||
|
/// https://stackoverflow.com/questions/36845430/persistent-hashcode-for-strings
|
||||||
|
public static int GetStableHashCode(this string str)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int hash1 = 5381;
|
||||||
|
int hash2 = hash1;
|
||||||
|
|
||||||
|
for (int i = 0; i < str.Length && str[i] != '\0'; i += 2)
|
||||||
|
{
|
||||||
|
hash1 = ((hash1 << 5) + hash1) ^ str[i];
|
||||||
|
if (i == str.Length - 1 || str[i + 1] == '\0')
|
||||||
|
break;
|
||||||
|
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash1 + (hash2 * 1566083941);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static int? ConvertToIP(this string str)
|
public static int? ConvertToIP(this string str)
|
||||||
{
|
{
|
||||||
@ -773,6 +793,8 @@ namespace SharedLibraryCore
|
|||||||
byte[] bytes = toTest.GetAddressBytes();
|
byte[] bytes = toTest.GetAddressBytes();
|
||||||
switch (bytes[0])
|
switch (bytes[0])
|
||||||
{
|
{
|
||||||
|
case 0:
|
||||||
|
return bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 0;
|
||||||
case 10:
|
case 10:
|
||||||
return true;
|
return true;
|
||||||
case 172:
|
case 172:
|
||||||
@ -954,7 +976,7 @@ namespace SharedLibraryCore
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// wrapper method for humanizee that uses current current culture
|
/// wrapper method for humanizee that uses current current culture
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string HumanizeForCurrentCulture(this TimeSpan timeSpan, int precision = 1, TimeUnit maxUnit = TimeUnit.Week,
|
public static string HumanizeForCurrentCulture(this TimeSpan timeSpan, int precision = 1, TimeUnit maxUnit = TimeUnit.Week,
|
||||||
TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", bool toWords = false)
|
TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", bool toWords = false)
|
||||||
{
|
{
|
||||||
return timeSpan.Humanize(precision, CurrentLocalization.Culture, maxUnit, minUnit, collectionSeparator, toWords);
|
return timeSpan.Humanize(precision, CurrentLocalization.Culture, maxUnit, minUnit, collectionSeparator, toWords);
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FakeItEasy" Version="6.2.0" />
|
<PackageReference Include="FakeItEasy" Version="6.2.0" />
|
||||||
|
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
|
||||||
|
@ -14,6 +14,8 @@ using SharedLibraryCore;
|
|||||||
using ApplicationTests.Mocks;
|
using ApplicationTests.Mocks;
|
||||||
using SharedLibraryCore.Services;
|
using SharedLibraryCore.Services;
|
||||||
using static SharedLibraryCore.Database.Models.EFClient;
|
using static SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
using FluentAssertions;
|
||||||
|
using FluentAssertions.Extensions;
|
||||||
|
|
||||||
namespace ApplicationTests
|
namespace ApplicationTests
|
||||||
{
|
{
|
||||||
@ -33,11 +35,14 @@ namespace ApplicationTests
|
|||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
logger = A.Fake<ILogger>();
|
logger = A.Fake<ILogger>();
|
||||||
cmdConfig = new CommandConfiguration();
|
|
||||||
|
|
||||||
serviceProvider = new ServiceCollection()
|
serviceProvider = new ServiceCollection()
|
||||||
.BuildBase(new EventHandlerMock(true))
|
.BuildBase(new EventHandlerMock(true))
|
||||||
.AddSingleton(A.Fake<ClientService>())
|
.AddSingleton(A.Fake<ClientService>())
|
||||||
|
.AddSingleton<LoadMapCommand>()
|
||||||
|
.AddSingleton<SetLevelCommand>()
|
||||||
|
.AddSingleton<RunAsCommand>()
|
||||||
|
.AddSingleton<PrivateMessageAdminsCommand>()
|
||||||
.BuildServiceProvider()
|
.BuildServiceProvider()
|
||||||
.SetupTestHooks();
|
.SetupTestHooks();
|
||||||
|
|
||||||
@ -46,6 +51,8 @@ namespace ApplicationTests
|
|||||||
transLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
|
transLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
|
||||||
clientService = serviceProvider.GetRequiredService<ClientService>();
|
clientService = serviceProvider.GetRequiredService<ClientService>();
|
||||||
appConfig = serviceProvider.GetRequiredService<ApplicationConfiguration>();
|
appConfig = serviceProvider.GetRequiredService<ApplicationConfiguration>();
|
||||||
|
appConfig.MapChangeDelaySeconds = 1;
|
||||||
|
cmdConfig = serviceProvider.GetRequiredService<CommandConfiguration>();
|
||||||
|
|
||||||
A.CallTo(() => manager.GetClientService())
|
A.CallTo(() => manager.GetClientService())
|
||||||
.Returns(clientService);
|
.Returns(clientService);
|
||||||
@ -69,7 +76,7 @@ namespace ApplicationTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task Test_RunAsFailsOnSelf()
|
public async Task Test_RunAsFailsOnSelf()
|
||||||
{
|
{
|
||||||
var cmd = new RunAsCommand(cmdConfig, transLookup);
|
var cmd = serviceProvider.GetRequiredService<RunAsCommand>();
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
|
|
||||||
@ -88,7 +95,7 @@ namespace ApplicationTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task Test_RunAsFailsOnHigherPrivilege()
|
public async Task Test_RunAsFailsOnHigherPrivilege()
|
||||||
{
|
{
|
||||||
var cmd = new RunAsCommand(cmdConfig, transLookup);
|
var cmd = serviceProvider.GetRequiredService<RunAsCommand>();
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
target.Level = EFClient.Permission.Administrator;
|
target.Level = EFClient.Permission.Administrator;
|
||||||
@ -111,7 +118,7 @@ namespace ApplicationTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task Test_RunAsFailsOnSamePrivilege()
|
public async Task Test_RunAsFailsOnSamePrivilege()
|
||||||
{
|
{
|
||||||
var cmd = new RunAsCommand(cmdConfig, transLookup);
|
var cmd = serviceProvider.GetRequiredService<RunAsCommand>();
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
target.Level = EFClient.Permission.Administrator;
|
target.Level = EFClient.Permission.Administrator;
|
||||||
@ -134,7 +141,7 @@ namespace ApplicationTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task Test_RunAsFailsOnDisallowedCommand()
|
public async Task Test_RunAsFailsOnDisallowedCommand()
|
||||||
{
|
{
|
||||||
var cmd = new RunAsCommand(cmdConfig, transLookup);
|
var cmd = serviceProvider.GetRequiredService<RunAsCommand>();
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
target.Level = EFClient.Permission.Moderator;
|
target.Level = EFClient.Permission.Moderator;
|
||||||
@ -160,7 +167,7 @@ namespace ApplicationTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task Test_RunAsQueuesEventAndResponse()
|
public async Task Test_RunAsQueuesEventAndResponse()
|
||||||
{
|
{
|
||||||
var cmd = new RunAsCommand(cmdConfig, transLookup);
|
var cmd = serviceProvider.GetRequiredService<RunAsCommand>();
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
target.Level = EFClient.Permission.Moderator;
|
target.Level = EFClient.Permission.Moderator;
|
||||||
@ -187,7 +194,7 @@ namespace ApplicationTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task Test_SetLevelFailOnSelf()
|
public async Task Test_SetLevelFailOnSelf()
|
||||||
{
|
{
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
target.Level = Permission.Owner;
|
target.Level = Permission.Owner;
|
||||||
@ -211,7 +218,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelFailWithSourcePrivilegeTooLow()
|
public async Task Test_SetLevelFailWithSourcePrivilegeTooLow()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
origin.Level = Permission.Moderator;
|
origin.Level = Permission.Moderator;
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
@ -239,7 +246,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelFailWithExistingOwner_AndOnlyOneOwnerAllowed()
|
public async Task Test_SetLevelFailWithExistingOwner_AndOnlyOneOwnerAllowed()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
target.Level = Permission.User;
|
target.Level = Permission.User;
|
||||||
@ -266,7 +273,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelFailWithStepPrivilegesDisabled_AndNonOwner()
|
public async Task Test_SetLevelFailWithStepPrivilegesDisabled_AndNonOwner()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
origin.Level = Permission.SeniorAdmin;
|
origin.Level = Permission.SeniorAdmin;
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
@ -294,7 +301,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelFailWithStepPrivilegesEnabled_ButNewPermissionTooHigh()
|
public async Task Test_SetLevelFailWithStepPrivilegesEnabled_ButNewPermissionTooHigh()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
origin.Level = Permission.Moderator;
|
origin.Level = Permission.Moderator;
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
@ -320,7 +327,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelFailInvalidGroup()
|
public async Task Test_SetLevelFailInvalidGroup()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
origin.Level = Permission.Owner;
|
origin.Level = Permission.Owner;
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
@ -345,7 +352,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelSucceedWithNoExistingOwner_AndOnlyOneOwnerAllowed()
|
public async Task Test_SetLevelSucceedWithNoExistingOwner_AndOnlyOneOwnerAllowed()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
origin.Level = Permission.Owner;
|
origin.Level = Permission.Owner;
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
@ -373,7 +380,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelOwnerSucceedWithMultiOwnerAllowed()
|
public async Task Test_SetLevelOwnerSucceedWithMultiOwnerAllowed()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
origin.Level = Permission.Owner;
|
origin.Level = Permission.Owner;
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
@ -402,7 +409,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelOwnerSucceedWithMultiOwnerAllowed_AndSteppedPrivileges()
|
public async Task Test_SetLevelOwnerSucceedWithMultiOwnerAllowed_AndSteppedPrivileges()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
origin.Level = Permission.Owner;
|
origin.Level = Permission.Owner;
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
@ -432,7 +439,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelSucceedWithSteppedPrivileges()
|
public async Task Test_SetLevelSucceedWithSteppedPrivileges()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
origin.Level = Permission.Moderator;
|
origin.Level = Permission.Moderator;
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
@ -461,7 +468,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelSucceed()
|
public async Task Test_SetLevelSucceed()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
origin.Level = Permission.Owner;
|
origin.Level = Permission.Owner;
|
||||||
var target = ClientGenerators.CreateBasicClient(server);
|
var target = ClientGenerators.CreateBasicClient(server);
|
||||||
@ -490,7 +497,7 @@ namespace ApplicationTests
|
|||||||
public async Task Test_SetLevelSucceed_AndFindsIngameClient()
|
public async Task Test_SetLevelSucceed_AndFindsIngameClient()
|
||||||
{
|
{
|
||||||
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
var cmd = new SetLevelCommand(cmdConfig, transLookup, logger);
|
var cmd = serviceProvider.GetRequiredService<SetLevelCommand>();
|
||||||
var origin = ClientGenerators.CreateBasicClient(server);
|
var origin = ClientGenerators.CreateBasicClient(server);
|
||||||
origin.Level = Permission.Owner;
|
origin.Level = Permission.Owner;
|
||||||
var databaseTarget = ClientGenerators.CreateDatabaseClient();
|
var databaseTarget = ClientGenerators.CreateDatabaseClient();
|
||||||
@ -537,5 +544,87 @@ namespace ApplicationTests
|
|||||||
Assert.IsTrue(result.IsBroadcast);
|
Assert.IsTrue(result.IsBroadcast);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region PMADMINS
|
||||||
|
[Test]
|
||||||
|
public async Task Test_PrivateMessageAdmins_HappyPath()
|
||||||
|
{
|
||||||
|
var cmd = serviceProvider.GetRequiredService<PrivateMessageAdminsCommand>();
|
||||||
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
|
var origin = ClientGenerators.CreateDatabaseClient();
|
||||||
|
origin.Level = Permission.Administrator;
|
||||||
|
origin.CurrentServer = server;
|
||||||
|
var gameEvent = EventGenerators.GenerateEvent(GameEvent.EventType.Command, "", server);
|
||||||
|
cmdConfig.Commands.Add(nameof(PrivateMessageAdminsCommand), new CommandProperties { SupportedGames = new[] { server.GameName } });
|
||||||
|
|
||||||
|
server.Clients[0] = origin;
|
||||||
|
server.Clients[1] = origin;
|
||||||
|
await cmd.ExecuteAsync(gameEvent);
|
||||||
|
int expectedEvents = 2;
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedEvents, mockEventHandler.Events.Count(_event => _event.Type == GameEvent.EventType.Tell));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Test_PrivateMessageAdmins_GameNotSupported()
|
||||||
|
{
|
||||||
|
var cmd = serviceProvider.GetRequiredService<PrivateMessageAdminsCommand>();
|
||||||
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
|
var origin = ClientGenerators.CreateDatabaseClient();
|
||||||
|
origin.Level = Permission.Administrator;
|
||||||
|
origin.CurrentServer = server;
|
||||||
|
var gameEvent = EventGenerators.GenerateEvent(GameEvent.EventType.Command, "", server);
|
||||||
|
gameEvent.Origin = origin;
|
||||||
|
cmdConfig.Commands.Add(nameof(PrivateMessageAdminsCommand), new CommandProperties());
|
||||||
|
|
||||||
|
server.Clients[0] = origin;
|
||||||
|
server.Clients[1] = origin;
|
||||||
|
await cmd.ExecuteAsync(gameEvent);
|
||||||
|
int expectedEvents = 1;
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedEvents, mockEventHandler.Events.Count(_event => _event.Type == GameEvent.EventType.Tell));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region LOADMAP
|
||||||
|
[Test]
|
||||||
|
public void Test_LoadMap_WaitsAppropriateTime_BeforeExecutingCommand()
|
||||||
|
{
|
||||||
|
var cmd = serviceProvider.GetRequiredService<LoadMapCommand>();
|
||||||
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
|
var rconParser = serviceProvider.GetRequiredService<IRConParser>();
|
||||||
|
server.Maps.Add(new Map()
|
||||||
|
{
|
||||||
|
Name = "mp_test",
|
||||||
|
Alias = "test"
|
||||||
|
});
|
||||||
|
var gameEvent = EventGenerators.GenerateEvent(GameEvent.EventType.Command, server.Maps.First().Name, server);
|
||||||
|
|
||||||
|
Func<Task> act = () => cmd.ExecuteAsync(gameEvent);
|
||||||
|
|
||||||
|
act.ExecutionTime().Should().BeCloseTo(appConfig.MapChangeDelaySeconds.Seconds(), 500.Milliseconds());
|
||||||
|
A.CallTo(() => rconParser.ExecuteCommandAsync(A<IRConConnection>.Ignored, A<string>.Ignored))
|
||||||
|
.MustHaveHappened();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Test_LoadMap_FindsMapName_FromPartialAlias()
|
||||||
|
{
|
||||||
|
var cmd = serviceProvider.GetRequiredService<LoadMapCommand>();
|
||||||
|
var server = serviceProvider.GetRequiredService<IW4MServer>();
|
||||||
|
var rconParser = serviceProvider.GetRequiredService<IRConParser>();
|
||||||
|
server.Maps.Add(new Map()
|
||||||
|
{
|
||||||
|
Name = "mp_test",
|
||||||
|
Alias = "test"
|
||||||
|
});
|
||||||
|
var gameEvent = EventGenerators.GenerateEvent(GameEvent.EventType.Command, server.Maps.First().Name, server);
|
||||||
|
|
||||||
|
await cmd.ExecuteAsync(gameEvent);
|
||||||
|
|
||||||
|
A.CallTo(() => rconParser.ExecuteCommandAsync(A<IRConConnection>.Ignored, A<string>.That.Contains(server.Maps[0].Name)))
|
||||||
|
.MustHaveHappened();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,10 @@ namespace ApplicationTests
|
|||||||
var manager = A.Fake<IManager>();
|
var manager = A.Fake<IManager>();
|
||||||
var logger = A.Fake<ILogger>();
|
var logger = A.Fake<ILogger>();
|
||||||
|
|
||||||
|
var transLookup = A.Fake<ITranslationLookup>();
|
||||||
|
A.CallTo(() => transLookup[A<string>.Ignored])
|
||||||
|
.Returns("test");
|
||||||
|
|
||||||
A.CallTo(() => manager.GetLogger(A<long>.Ignored))
|
A.CallTo(() => manager.GetLogger(A<long>.Ignored))
|
||||||
.Returns(logger);
|
.Returns(logger);
|
||||||
|
|
||||||
@ -38,7 +42,7 @@ namespace ApplicationTests
|
|||||||
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactoryMock>()
|
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactoryMock>()
|
||||||
.AddSingleton(A.Fake<IRConConnectionFactory>())
|
.AddSingleton(A.Fake<IRConConnectionFactory>())
|
||||||
.AddSingleton(A.Fake<IRConConnection>())
|
.AddSingleton(A.Fake<IRConConnection>())
|
||||||
.AddSingleton(A.Fake<ITranslationLookup>())
|
.AddSingleton(transLookup)
|
||||||
.AddSingleton(A.Fake<IRConParser>())
|
.AddSingleton(A.Fake<IRConParser>())
|
||||||
.AddSingleton(A.Fake<IParserRegexFactory>())
|
.AddSingleton(A.Fake<IParserRegexFactory>())
|
||||||
.AddSingleton<DataFileLoader>()
|
.AddSingleton<DataFileLoader>()
|
||||||
|
23
Tests/ApplicationTests/Mocks/ScriptResolverGenericMock.cs
Normal file
23
Tests/ApplicationTests/Mocks/ScriptResolverGenericMock.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace ApplicationTests.Mocks
|
||||||
|
{
|
||||||
|
public interface IScriptResolverMock
|
||||||
|
{
|
||||||
|
string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScriptResolverMock : IScriptResolverMock
|
||||||
|
{
|
||||||
|
public string Value { get; set; }
|
||||||
|
}
|
||||||
|
public interface IScriptResolverGenericMock<T, V>
|
||||||
|
{
|
||||||
|
T Value { get; set; }
|
||||||
|
V Value2 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScriptResolverGenericMock<T, V> : IScriptResolverGenericMock<T, V>
|
||||||
|
{
|
||||||
|
public T Value { get; set; }
|
||||||
|
public V Value2 { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ namespace ApplicationTests
|
|||||||
serviceProvider = new ServiceCollection().BuildBase()
|
serviceProvider = new ServiceCollection().BuildBase()
|
||||||
.AddSingleton(A.Fake<ClientService>())
|
.AddSingleton(A.Fake<ClientService>())
|
||||||
.AddSingleton<IScriptCommandFactory, ScriptCommandFactory>()
|
.AddSingleton<IScriptCommandFactory, ScriptCommandFactory>()
|
||||||
|
.AddSingleton(A.Fake<IScriptPluginServiceResolver>())
|
||||||
.BuildServiceProvider();
|
.BuildServiceProvider();
|
||||||
fakeManager = serviceProvider.GetRequiredService<IManager>();
|
fakeManager = serviceProvider.GetRequiredService<IManager>();
|
||||||
mockEventHandler = serviceProvider.GetRequiredService<EventHandlerMock>();
|
mockEventHandler = serviceProvider.GetRequiredService<EventHandlerMock>();
|
||||||
@ -66,7 +67,7 @@ namespace ApplicationTests
|
|||||||
A.CallTo(() => fakeManager.GetClientService())
|
A.CallTo(() => fakeManager.GetClientService())
|
||||||
.Returns(fakeClientService);
|
.Returns(fakeClientService);
|
||||||
|
|
||||||
await plugin.Initialize(serviceProvider.GetRequiredService<IManager>(), serviceProvider.GetRequiredService<IScriptCommandFactory>());
|
await plugin.Initialize(serviceProvider.GetRequiredService<IManager>(), serviceProvider.GetRequiredService<IScriptCommandFactory>(), serviceProvider.GetRequiredService<IScriptPluginServiceResolver>());
|
||||||
|
|
||||||
var gameEvent = new GameEvent()
|
var gameEvent = new GameEvent()
|
||||||
{
|
{
|
||||||
|
66
Tests/ApplicationTests/ScriptPluginServiceResolverTests.cs
Normal file
66
Tests/ApplicationTests/ScriptPluginServiceResolverTests.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using ApplicationTests.Mocks;
|
||||||
|
using IW4MAdmin.Application.Misc;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ApplicationTests
|
||||||
|
{
|
||||||
|
public class ScriptPluginServiceResolverTests
|
||||||
|
{
|
||||||
|
private IServiceProvider serviceProvider;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
serviceProvider = new ServiceCollection()
|
||||||
|
.BuildBase()
|
||||||
|
.AddSingleton<ScriptPluginServiceResolver>()
|
||||||
|
.AddSingleton<IScriptResolverMock, ScriptResolverMock>()
|
||||||
|
.AddSingleton(new ScriptResolverMock { Value = "test" })
|
||||||
|
.AddSingleton<IScriptResolverGenericMock<int, string>, ScriptResolverGenericMock<int,string>>()
|
||||||
|
.AddSingleton(new ScriptResolverGenericMock<int, string> { Value = 123, Value2 = "test" })
|
||||||
|
.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_ResolveType()
|
||||||
|
{
|
||||||
|
var resolver = serviceProvider.GetService<ScriptPluginServiceResolver>();
|
||||||
|
var expectedResolvedService = serviceProvider.GetService<ScriptResolverMock>();
|
||||||
|
var resolvedService = resolver.ResolveService(nameof(ScriptResolverMock));
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedResolvedService, resolvedService);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_ResolveType_Interface()
|
||||||
|
{
|
||||||
|
var resolver = serviceProvider.GetService<ScriptPluginServiceResolver>();
|
||||||
|
var expectedResolvedService = serviceProvider.GetService<IScriptResolverMock>();
|
||||||
|
var resolvedService = resolver.ResolveService(nameof(IScriptResolverMock));
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedResolvedService, resolvedService);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_ResolveGenericType()
|
||||||
|
{
|
||||||
|
var resolver = serviceProvider.GetService<ScriptPluginServiceResolver>();
|
||||||
|
var expectedResolvedService = serviceProvider.GetService<ScriptResolverGenericMock<int, string>>();
|
||||||
|
var resolvedService = resolver.ResolveService("ScriptResolverGenericMock", new[] { "Int32", "String" });
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedResolvedService, resolvedService);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_ResolveGenericType_Interface()
|
||||||
|
{
|
||||||
|
var resolver = serviceProvider.GetService<ScriptPluginServiceResolver>();
|
||||||
|
var expectedResolvedService = serviceProvider.GetService<IScriptResolverGenericMock<int, string>>();
|
||||||
|
var resolvedService = resolver.ResolveService("IScriptResolverGenericMock", new[] { "Int32", "String" });
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedResolvedService, resolvedService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -50,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (Model.ActivePenalty != null)
|
@if (Model.ActivePenalty != null && (Model.ActivePenalty.Type != EFPenalty.PenaltyType.Flag || ViewBag.Authorized))
|
||||||
{
|
{
|
||||||
<div class="font-weight-bold h4 mb-0 penalties-color-@Model.ActivePenalty.Type.ToString().ToLower()">
|
<div class="font-weight-bold h4 mb-0 penalties-color-@Model.ActivePenalty.Type.ToString().ToLower()">
|
||||||
@foreach (var result in Utilities.SplitTranslationTokens(translationKey))
|
@foreach (var result in Utilities.SplitTranslationTokens(translationKey))
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
else if (match.MatchValue == "reason")
|
else if (match.MatchValue == "reason")
|
||||||
{
|
{
|
||||||
<span class="text-white">
|
<span class="text-white">
|
||||||
@if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != SharedLibraryCore.Database.Models.EFPenalty.PenaltyType.Warning)
|
@if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != SharedLibraryCore.Database.Models.EFPenalty.PenaltyType.Warning && Model.PenaltyType != SharedLibraryCore.Database.Models.EFPenalty.PenaltyType.Kick)
|
||||||
{
|
{
|
||||||
<span>@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense)</span>
|
<span>@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense)</span>
|
||||||
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
|
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
|
||||||
|
Reference in New Issue
Block a user