implement new eventing system
This commit is contained in:
parent
2e726ea9ed
commit
f41ce39180
@ -21,7 +21,6 @@
|
|||||||
<Win32Resource />
|
<Win32Resource />
|
||||||
<RootNamespace>IW4MAdmin.Application</RootNamespace>
|
<RootNamespace>IW4MAdmin.Application</RootNamespace>
|
||||||
<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
|
<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -16,6 +16,7 @@ using System.Collections;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -23,12 +24,18 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Abstractions;
|
using Data.Abstractions;
|
||||||
using Data.Context;
|
using Data.Context;
|
||||||
|
using Data.Models;
|
||||||
|
using IW4MAdmin.Application.Configuration;
|
||||||
using IW4MAdmin.Application.Migration;
|
using IW4MAdmin.Application.Migration;
|
||||||
using IW4MAdmin.Application.Plugin.Script;
|
using IW4MAdmin.Application.Plugin.Script;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Serilog.Context;
|
using Serilog.Context;
|
||||||
|
using SharedLibraryCore.Events;
|
||||||
|
using SharedLibraryCore.Events.Management;
|
||||||
|
using SharedLibraryCore.Events.Server;
|
||||||
using SharedLibraryCore.Formatting;
|
using SharedLibraryCore.Formatting;
|
||||||
|
using SharedLibraryCore.Interfaces.Events;
|
||||||
using static SharedLibraryCore.GameEvent;
|
using static SharedLibraryCore.GameEvent;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
using ObsoleteLogger = SharedLibraryCore.Interfaces.ILogger;
|
using ObsoleteLogger = SharedLibraryCore.Interfaces.ILogger;
|
||||||
@ -50,7 +57,7 @@ namespace IW4MAdmin.Application
|
|||||||
public IList<Func<GameEvent, bool>> CommandInterceptors { get; set; } =
|
public IList<Func<GameEvent, bool>> CommandInterceptors { get; set; } =
|
||||||
new List<Func<GameEvent, bool>>();
|
new List<Func<GameEvent, bool>>();
|
||||||
public ITokenAuthentication TokenAuthenticator { get; }
|
public ITokenAuthentication TokenAuthenticator { get; }
|
||||||
public CancellationToken CancellationToken => _tokenSource.Token;
|
public CancellationToken CancellationToken => _isRunningTokenSource.Token;
|
||||||
public string ExternalIPAddress { get; private set; }
|
public string ExternalIPAddress { get; private set; }
|
||||||
public bool IsRestartRequested { get; private set; }
|
public bool IsRestartRequested { get; private set; }
|
||||||
public IMiddlewareActionHandler MiddlewareActionHandler { get; }
|
public IMiddlewareActionHandler MiddlewareActionHandler { get; }
|
||||||
@ -64,29 +71,30 @@ namespace IW4MAdmin.Application
|
|||||||
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
|
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
|
||||||
readonly IPageList PageList;
|
readonly IPageList PageList;
|
||||||
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
|
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
|
||||||
private CancellationTokenSource _tokenSource;
|
private CancellationTokenSource _isRunningTokenSource;
|
||||||
|
private CancellationTokenSource _eventHandlerTokenSource;
|
||||||
private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>();
|
private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>();
|
||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
private readonly IConfigurationHandler<CommandConfiguration> _commandConfiguration;
|
private readonly IConfigurationHandler<CommandConfiguration> _commandConfiguration;
|
||||||
private readonly IGameServerInstanceFactory _serverInstanceFactory;
|
private readonly IGameServerInstanceFactory _serverInstanceFactory;
|
||||||
private readonly IParserRegexFactory _parserRegexFactory;
|
private readonly IParserRegexFactory _parserRegexFactory;
|
||||||
private readonly IEnumerable<IRegisterEvent> _customParserEvents;
|
private readonly IEnumerable<IRegisterEvent> _customParserEvents;
|
||||||
private readonly IEventHandler _eventHandler;
|
private readonly ICoreEventHandler _coreEventHandler;
|
||||||
private readonly IScriptCommandFactory _scriptCommandFactory;
|
private readonly IScriptCommandFactory _scriptCommandFactory;
|
||||||
private readonly IMetaRegistration _metaRegistration;
|
private readonly IMetaRegistration _metaRegistration;
|
||||||
private readonly IScriptPluginServiceResolver _scriptPluginServiceResolver;
|
private readonly IScriptPluginServiceResolver _scriptPluginServiceResolver;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly ChangeHistoryService _changeHistoryService;
|
private readonly ChangeHistoryService _changeHistoryService;
|
||||||
private readonly ApplicationConfiguration _appConfig;
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
public ConcurrentDictionary<long, GameEvent> ProcessingEvents { get; } = new ConcurrentDictionary<long, GameEvent>();
|
public ConcurrentDictionary<long, GameEvent> ProcessingEvents { get; } = new();
|
||||||
|
|
||||||
public ApplicationManager(ILogger<ApplicationManager> logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
|
public ApplicationManager(ILogger<ApplicationManager> 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,
|
ICoreEventHandler coreEventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory,
|
||||||
IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider,
|
IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider,
|
||||||
ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService, IAlertManager alertManager, IInteractionRegistration interactionRegistration)
|
ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService, IAlertManager alertManager, IInteractionRegistration interactionRegistration, IEnumerable<IPluginV2> v2PLugins)
|
||||||
{
|
{
|
||||||
MiddlewareActionHandler = actionHandler;
|
MiddlewareActionHandler = actionHandler;
|
||||||
_servers = new ConcurrentBag<Server>();
|
_servers = new ConcurrentBag<Server>();
|
||||||
@ -101,14 +109,14 @@ namespace IW4MAdmin.Application
|
|||||||
AdditionalRConParsers = new List<IRConParser> { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) };
|
AdditionalRConParsers = new List<IRConParser> { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) };
|
||||||
TokenAuthenticator = new TokenAuthentication();
|
TokenAuthenticator = new TokenAuthentication();
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_tokenSource = new CancellationTokenSource();
|
_isRunningTokenSource = new CancellationTokenSource();
|
||||||
_commands = commands.ToList();
|
_commands = commands.ToList();
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
_commandConfiguration = commandConfiguration;
|
_commandConfiguration = commandConfiguration;
|
||||||
_serverInstanceFactory = serverInstanceFactory;
|
_serverInstanceFactory = serverInstanceFactory;
|
||||||
_parserRegexFactory = parserRegexFactory;
|
_parserRegexFactory = parserRegexFactory;
|
||||||
_customParserEvents = customParserEvents;
|
_customParserEvents = customParserEvents;
|
||||||
_eventHandler = eventHandler;
|
_coreEventHandler = coreEventHandler;
|
||||||
_scriptCommandFactory = scriptCommandFactory;
|
_scriptCommandFactory = scriptCommandFactory;
|
||||||
_metaRegistration = metaRegistration;
|
_metaRegistration = metaRegistration;
|
||||||
_scriptPluginServiceResolver = scriptPluginServiceResolver;
|
_scriptPluginServiceResolver = scriptPluginServiceResolver;
|
||||||
@ -117,6 +125,8 @@ namespace IW4MAdmin.Application
|
|||||||
_appConfig = appConfig;
|
_appConfig = appConfig;
|
||||||
Plugins = plugins;
|
Plugins = plugins;
|
||||||
InteractionRegistration = interactionRegistration;
|
InteractionRegistration = interactionRegistration;
|
||||||
|
|
||||||
|
IManagementEventSubscriptions.ClientPersistentIdReceived += OnClientPersistentIdReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IPlugin> Plugins { get; }
|
public IEnumerable<IPlugin> Plugins { get; }
|
||||||
@ -124,7 +134,7 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
public async Task ExecuteEvent(GameEvent newEvent)
|
public async Task ExecuteEvent(GameEvent newEvent)
|
||||||
{
|
{
|
||||||
ProcessingEvents.TryAdd(newEvent.Id, newEvent);
|
ProcessingEvents.TryAdd(newEvent.IncrementalId, newEvent);
|
||||||
|
|
||||||
// the event has failed already
|
// the event has failed already
|
||||||
if (newEvent.Failed)
|
if (newEvent.Failed)
|
||||||
@ -142,12 +152,12 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Received quit signal for event id {eventId}, so we are aborting early", newEvent.Id);
|
_logger.LogDebug("Received quit signal for event id {EventId}, so we are aborting early", newEvent.IncrementalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Received quit signal for event id {eventId}, so we are aborting early", newEvent.Id);
|
_logger.LogDebug("Received quit signal for event id {EventId}, so we are aborting early", newEvent.IncrementalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this happens if a plugin requires login
|
// this happens if a plugin requires login
|
||||||
@ -186,11 +196,11 @@ namespace IW4MAdmin.Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
skip:
|
skip:
|
||||||
if (newEvent.Type == EventType.Command && newEvent.ImpersonationOrigin == null)
|
if (newEvent.Type == EventType.Command && newEvent.ImpersonationOrigin == null && newEvent.CorrelationId is not null)
|
||||||
{
|
{
|
||||||
var correlatedEvents =
|
var correlatedEvents =
|
||||||
ProcessingEvents.Values.Where(ev =>
|
ProcessingEvents.Values.Where(ev =>
|
||||||
ev.CorrelationId == newEvent.CorrelationId && ev.Id != newEvent.Id)
|
ev.CorrelationId == newEvent.CorrelationId && ev.IncrementalId != newEvent.IncrementalId)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
await Task.WhenAll(correlatedEvents.Select(ev =>
|
await Task.WhenAll(correlatedEvents.Select(ev =>
|
||||||
@ -199,14 +209,16 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
foreach (var correlatedEvent in correlatedEvents)
|
foreach (var correlatedEvent in correlatedEvents)
|
||||||
{
|
{
|
||||||
ProcessingEvents.Remove(correlatedEvent.Id, out _);
|
ProcessingEvents.Remove(correlatedEvent.IncrementalId, out _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't want to remove events that are correlated to command
|
// we don't want to remove events that are correlated to command
|
||||||
if (ProcessingEvents.Values.ToList()?.Count(gameEvent => gameEvent.CorrelationId == newEvent.CorrelationId) == 1)
|
if (ProcessingEvents.Values.Count(gameEvent =>
|
||||||
|
newEvent.CorrelationId is not null && newEvent.CorrelationId == gameEvent.CorrelationId) == 1 ||
|
||||||
|
newEvent.CorrelationId is null)
|
||||||
{
|
{
|
||||||
ProcessingEvents.Remove(newEvent.Id, out _);
|
ProcessingEvents.Remove(newEvent.IncrementalId, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
// tell anyone waiting for the output that we're done
|
// tell anyone waiting for the output that we're done
|
||||||
@ -226,75 +238,58 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
public IReadOnlyList<IManagerCommand> Commands => _commands.ToImmutableList();
|
public IReadOnlyList<IManagerCommand> Commands => _commands.ToImmutableList();
|
||||||
|
|
||||||
public async Task UpdateServerStates()
|
private Task UpdateServerStates()
|
||||||
{
|
{
|
||||||
// store the server hash code and task for it
|
var index = 0;
|
||||||
var runningUpdateTasks = new Dictionary<long, (Task task, CancellationTokenSource tokenSource, DateTime startTime)>();
|
return Task.WhenAll(_servers.Select(server =>
|
||||||
var timeout = TimeSpan.FromSeconds(60);
|
|
||||||
|
|
||||||
while (!_tokenSource.IsCancellationRequested) // main shutdown requested
|
|
||||||
{
|
{
|
||||||
// select the server ids that have completed the update task
|
var thisIndex = index;
|
||||||
var serverTasksToRemove = runningUpdateTasks
|
Interlocked.Increment(ref index);
|
||||||
.Where(ut => ut.Value.task.IsCompleted)
|
return ProcessUpdateHandler(server, thisIndex);
|
||||||
.Select(ut => ut.Key)
|
}));
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// remove the update tasks as they have completed
|
|
||||||
foreach (var serverId in serverTasksToRemove.Where(serverId => runningUpdateTasks.ContainsKey(serverId)))
|
|
||||||
{
|
|
||||||
if (!runningUpdateTasks[serverId].tokenSource.Token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
runningUpdateTasks[serverId].tokenSource.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
runningUpdateTasks.Remove(serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// select the servers where the tasks have completed
|
|
||||||
var newTaskServers = Servers.Select(s => s.EndPoint).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
|
|
||||||
|
|
||||||
foreach (var server in Servers.Where(s => newTaskServers.Contains(s.EndPoint)))
|
|
||||||
{
|
|
||||||
var firstTokenSource = new CancellationTokenSource();
|
|
||||||
firstTokenSource.CancelAfter(timeout);
|
|
||||||
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(firstTokenSource.Token, _tokenSource.Token);
|
|
||||||
runningUpdateTasks.Add(server.EndPoint, (ProcessUpdateHandler(server, linkedTokenSource.Token), linkedTokenSource, DateTime.Now));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(ConfigHandler.Configuration().RConPollRate, _tokenSource.Token);
|
|
||||||
}
|
|
||||||
// if a cancellation is received, we want to return immediately after shutting down
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
foreach (var server in Servers.Where(s => newTaskServers.Contains(s.EndPoint)))
|
|
||||||
{
|
|
||||||
await server.ProcessUpdatesAsync(_tokenSource.Token);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessUpdateHandler(Server server, CancellationToken token)
|
private async Task ProcessUpdateHandler(Server server, int index)
|
||||||
{
|
{
|
||||||
try
|
const int delayScalar = 50; // Task.Delay is inconsistent enough there's no reason to try to prevent collisions
|
||||||
|
var timeout = TimeSpan.FromMinutes(2);
|
||||||
|
|
||||||
|
while (!_isRunningTokenSource.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await server.ProcessUpdatesAsync(token);
|
try
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
using (LogContext.PushProperty("Server", server.ToString()))
|
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Failed to update status");
|
var delayFactor = Math.Min(_appConfig.RConPollRate, delayScalar * index);
|
||||||
|
await Task.Delay(delayFactor, _isRunningTokenSource.Token);
|
||||||
|
|
||||||
|
using var timeoutTokenSource = new CancellationTokenSource();
|
||||||
|
timeoutTokenSource.CancelAfter(timeout);
|
||||||
|
using var linkedTokenSource =
|
||||||
|
CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token,
|
||||||
|
_isRunningTokenSource.Token);
|
||||||
|
await server.ProcessUpdatesAsync(linkedTokenSource.Token);
|
||||||
|
|
||||||
|
await Task.Delay(Math.Max(1000, _appConfig.RConPollRate - delayFactor),
|
||||||
|
_isRunningTokenSource.Token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
using (LogContext.PushProperty("Server", server.Id))
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to update status");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
server.IsInitialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
// run the final updates to clean up server
|
||||||
server.IsInitialized = true;
|
await server.ProcessUpdatesAsync(_isRunningTokenSource.Token);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Init()
|
public async Task Init()
|
||||||
@ -305,18 +300,24 @@ namespace IW4MAdmin.Application
|
|||||||
#region DATABASE
|
#region DATABASE
|
||||||
_logger.LogInformation("Beginning database migration sync");
|
_logger.LogInformation("Beginning database migration sync");
|
||||||
Console.WriteLine(_translationLookup["MANAGER_MIGRATION_START"]);
|
Console.WriteLine(_translationLookup["MANAGER_MIGRATION_START"]);
|
||||||
await ContextSeed.Seed(_serviceProvider.GetRequiredService<IDatabaseContextFactory>(), _tokenSource.Token);
|
await ContextSeed.Seed(_serviceProvider.GetRequiredService<IDatabaseContextFactory>(), _isRunningTokenSource.Token);
|
||||||
await DatabaseHousekeeping.RemoveOldRatings(_serviceProvider.GetRequiredService<IDatabaseContextFactory>(), _tokenSource.Token);
|
await DatabaseHousekeeping.RemoveOldRatings(_serviceProvider.GetRequiredService<IDatabaseContextFactory>(), _isRunningTokenSource.Token);
|
||||||
_logger.LogInformation("Finished database migration sync");
|
_logger.LogInformation("Finished database migration sync");
|
||||||
Console.WriteLine(_translationLookup["MANAGER_MIGRATION_END"]);
|
Console.WriteLine(_translationLookup["MANAGER_MIGRATION_END"]);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region EVENTS
|
||||||
|
IGameServerEventSubscriptions.ServerValueRequested += OnServerValueRequested;
|
||||||
|
IGameServerEventSubscriptions.ServerValueSetRequested += OnServerValueSetRequested;
|
||||||
|
await IManagementEventSubscriptions.InvokeLoadAsync(this, CancellationToken);
|
||||||
|
# endregion
|
||||||
|
|
||||||
#region PLUGINS
|
#region PLUGINS
|
||||||
foreach (var plugin in Plugins)
|
foreach (var plugin in Plugins)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (plugin is ScriptPlugin scriptPlugin)
|
if (plugin is ScriptPlugin scriptPlugin && !plugin.IsParser)
|
||||||
{
|
{
|
||||||
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver,
|
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver,
|
||||||
_serviceProvider.GetService<IConfigurationHandlerV2<ScriptPluginConfiguration>>());
|
_serviceProvider.GetService<IConfigurationHandlerV2<ScriptPluginConfiguration>>());
|
||||||
@ -391,13 +392,11 @@ namespace IW4MAdmin.Application
|
|||||||
if (string.IsNullOrEmpty(_appConfig.Id))
|
if (string.IsNullOrEmpty(_appConfig.Id))
|
||||||
{
|
{
|
||||||
_appConfig.Id = Guid.NewGuid().ToString();
|
_appConfig.Id = Guid.NewGuid().ToString();
|
||||||
await ConfigHandler.Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_appConfig.WebfrontBindUrl))
|
if (string.IsNullOrEmpty(_appConfig.WebfrontBindUrl))
|
||||||
{
|
{
|
||||||
_appConfig.WebfrontBindUrl = "http://0.0.0.0:1624";
|
_appConfig.WebfrontBindUrl = "http://0.0.0.0:1624";
|
||||||
await ConfigHandler.Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable 618
|
#pragma warning disable 618
|
||||||
@ -442,8 +441,8 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
serverConfig.ModifyParsers();
|
serverConfig.ModifyParsers();
|
||||||
}
|
}
|
||||||
await ConfigHandler.Save();
|
|
||||||
}
|
}
|
||||||
|
await ConfigHandler.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_appConfig.Servers.Length == 0)
|
if (_appConfig.Servers.Length == 0)
|
||||||
@ -468,7 +467,7 @@ namespace IW4MAdmin.Application
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region COMMANDS
|
#region COMMANDS
|
||||||
if (await ClientSvc.HasOwnerAsync(_tokenSource.Token))
|
if (await ClientSvc.HasOwnerAsync(_isRunningTokenSource.Token))
|
||||||
{
|
{
|
||||||
_commands.RemoveAll(_cmd => _cmd.GetType() == typeof(OwnerCommand));
|
_commands.RemoveAll(_cmd => _cmd.GetType() == typeof(OwnerCommand));
|
||||||
}
|
}
|
||||||
@ -526,7 +525,7 @@ namespace IW4MAdmin.Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]);
|
Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]);
|
||||||
await InitializeServers();
|
await InitializeServers();
|
||||||
IsInitialized = true;
|
IsInitialized = true;
|
||||||
@ -543,26 +542,23 @@ namespace IW4MAdmin.Application
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// todo: this might not always be an IW4MServer
|
// todo: this might not always be an IW4MServer
|
||||||
var ServerInstance = _serverInstanceFactory.CreateServer(Conf, this) as IW4MServer;
|
var serverInstance = _serverInstanceFactory.CreateServer(Conf, this) as IW4MServer;
|
||||||
using (LogContext.PushProperty("Server", ServerInstance.ToString()))
|
using (LogContext.PushProperty("Server", serverInstance!.ToString()))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Beginning server communication initialization");
|
_logger.LogInformation("Beginning server communication initialization");
|
||||||
await ServerInstance.Initialize();
|
await serverInstance.Initialize();
|
||||||
|
|
||||||
_servers.Add(ServerInstance);
|
_servers.Add(serverInstance);
|
||||||
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(ServerInstance.Hostname.StripColors()));
|
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(serverInstance.Hostname.StripColors()));
|
||||||
_logger.LogInformation("Finishing initialization and now monitoring [{Server}]", ServerInstance.Hostname);
|
_logger.LogInformation("Finishing initialization and now monitoring [{Server}]", serverInstance.Hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the start event for this server
|
QueueEvent(new MonitorStartEvent
|
||||||
var e = new GameEvent()
|
|
||||||
{
|
{
|
||||||
Type = EventType.Start,
|
Server = serverInstance,
|
||||||
Data = $"{ServerInstance.GameName} started",
|
Source = this
|
||||||
Owner = ServerInstance
|
});
|
||||||
};
|
|
||||||
|
|
||||||
AddEvent(e);
|
|
||||||
successServers++;
|
successServers++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -593,11 +589,27 @@ namespace IW4MAdmin.Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Start() => await UpdateServerStates();
|
public async Task Start()
|
||||||
|
{
|
||||||
|
_eventHandlerTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var eventHandlerThread = new Thread(() =>
|
||||||
|
{
|
||||||
|
_coreEventHandler.StartProcessing(_eventHandlerTokenSource.Token);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Name = nameof(CoreEventHandler)
|
||||||
|
};
|
||||||
|
|
||||||
|
eventHandlerThread.Start();
|
||||||
|
await UpdateServerStates();
|
||||||
|
_eventHandlerTokenSource.Cancel();
|
||||||
|
eventHandlerThread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task Stop()
|
public async Task Stop()
|
||||||
{
|
{
|
||||||
foreach (var plugin in Plugins)
|
foreach (var plugin in Plugins.Where(plugin => !plugin.IsParser))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -607,19 +619,32 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
_logger.LogError(ex, "Could not cleanly unload plugin {PluginName}", plugin.Name);
|
_logger.LogError(ex, "Could not cleanly unload plugin {PluginName}", plugin.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_tokenSource.Cancel();
|
_isRunningTokenSource.Cancel();
|
||||||
|
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Restart()
|
public async Task Restart()
|
||||||
{
|
{
|
||||||
IsRestartRequested = true;
|
IsRestartRequested = true;
|
||||||
Stop().GetAwaiter().GetResult();
|
await Stop();
|
||||||
_tokenSource.Dispose();
|
|
||||||
_tokenSource = new CancellationTokenSource();
|
using var subscriptionTimeoutToken = new CancellationTokenSource();
|
||||||
|
subscriptionTimeoutToken.CancelAfter(Utilities.DefaultCommandTimeout);
|
||||||
|
|
||||||
|
await IManagementEventSubscriptions.InvokeUnloadAsync(this, subscriptionTimeoutToken.Token);
|
||||||
|
|
||||||
|
IGameEventSubscriptions.ClearEventInvocations();
|
||||||
|
IGameServerEventSubscriptions.ClearEventInvocations();
|
||||||
|
IManagementEventSubscriptions.ClearEventInvocations();
|
||||||
|
|
||||||
|
_isRunningTokenSource.Dispose();
|
||||||
|
_isRunningTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
_eventHandlerTokenSource.Dispose();
|
||||||
|
_eventHandlerTokenSource = new CancellationTokenSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
@ -661,9 +686,14 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
public void AddEvent(GameEvent gameEvent)
|
public void AddEvent(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
_eventHandler.HandleEvent(this, gameEvent);
|
_coreEventHandler.QueueEvent(this, gameEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void QueueEvent(CoreEvent coreEvent)
|
||||||
|
{
|
||||||
|
_coreEventHandler.QueueEvent(this, coreEvent);
|
||||||
|
}
|
||||||
|
|
||||||
public IPageList GetPageList()
|
public IPageList GetPageList()
|
||||||
{
|
{
|
||||||
return PageList;
|
return PageList;
|
||||||
@ -698,15 +728,132 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
public void AddAdditionalCommand(IManagerCommand command)
|
public void AddAdditionalCommand(IManagerCommand command)
|
||||||
{
|
{
|
||||||
if (_commands.Any(_command => _command.Name == command.Name || _command.Alias == command.Alias))
|
lock (_commands)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Duplicate command name or alias ({command.Name}, {command.Alias})");
|
if (_commands.Any(cmd => cmd.Name == command.Name || cmd.Alias == command.Alias))
|
||||||
}
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Duplicate command name or alias ({command.Name}, {command.Alias})");
|
||||||
|
}
|
||||||
|
|
||||||
_commands.Add(command);
|
_commands.Add(command);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveCommandByName(string commandName) => _commands.RemoveAll(_command => _command.Name == commandName);
|
public void RemoveCommandByName(string commandName) => _commands.RemoveAll(_command => _command.Name == commandName);
|
||||||
public IAlertManager AlertManager => _alertManager;
|
public IAlertManager AlertManager => _alertManager;
|
||||||
|
|
||||||
|
private async Task OnServerValueRequested(ServerValueRequestEvent requestEvent, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (requestEvent.Server is not IW4MServer server)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dvar<string> serverValue = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (requestEvent.DelayMs.HasValue)
|
||||||
|
{
|
||||||
|
await Task.Delay(requestEvent.DelayMs.Value, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
var waitToken = token;
|
||||||
|
using var timeoutTokenSource = new CancellationTokenSource();
|
||||||
|
using var linkedTokenSource =
|
||||||
|
CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, token);
|
||||||
|
|
||||||
|
if (requestEvent.TimeoutMs is not null)
|
||||||
|
{
|
||||||
|
timeoutTokenSource.CancelAfter(requestEvent.TimeoutMs.Value);
|
||||||
|
waitToken = linkedTokenSource.Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverValue =
|
||||||
|
await server.GetDvarAsync(requestEvent.ValueName, requestEvent.FallbackValue, waitToken);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
QueueEvent(new ServerValueReceiveEvent
|
||||||
|
{
|
||||||
|
Server = server,
|
||||||
|
Source = server,
|
||||||
|
Response = serverValue ?? new Dvar<string> { Name = requestEvent.ValueName },
|
||||||
|
Success = serverValue is not null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnServerValueSetRequested(ServerValueSetRequestEvent requestEvent, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (requestEvent.Server is not IW4MServer server)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var completed = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (requestEvent.DelayMs.HasValue)
|
||||||
|
{
|
||||||
|
await Task.Delay(requestEvent.DelayMs.Value, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestEvent.TimeoutMs is not null)
|
||||||
|
{
|
||||||
|
using var timeoutTokenSource = new CancellationTokenSource(requestEvent.TimeoutMs.Value);
|
||||||
|
using var linkedTokenSource =
|
||||||
|
CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, token);
|
||||||
|
token = linkedTokenSource.Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.SetDvarAsync(requestEvent.ValueName, requestEvent.Value, token);
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
QueueEvent(new ServerValueSetCompleteEvent
|
||||||
|
{
|
||||||
|
Server = server,
|
||||||
|
Source = server,
|
||||||
|
Success = completed,
|
||||||
|
Value = requestEvent.Value,
|
||||||
|
ValueName = requestEvent.ValueName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnClientPersistentIdReceived(ClientPersistentIdReceiveEvent receiveEvent, CancellationToken token)
|
||||||
|
{
|
||||||
|
var parts = receiveEvent.PersistentId.Split(",");
|
||||||
|
|
||||||
|
if (parts.Length == 2 && int.TryParse(parts[0], out var high) &&
|
||||||
|
int.TryParse(parts[1], out var low))
|
||||||
|
{
|
||||||
|
var guid = long.Parse(high.ToString("X") + low.ToString("X"), NumberStyles.HexNumber);
|
||||||
|
|
||||||
|
var penalties = await PenaltySvc
|
||||||
|
.GetActivePenaltiesByIdentifier(null, guid, receiveEvent.Client.GameName);
|
||||||
|
var banPenalty =
|
||||||
|
penalties.FirstOrDefault(penalty => penalty.Type == EFPenalty.PenaltyType.Ban);
|
||||||
|
|
||||||
|
if (banPenalty is not null && receiveEvent.Client.Level != Data.Models.Client.EFClient.Permission.Banned)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Banning {Client} as they have have provided a persistent clientId of {PersistentClientId}, which is banned",
|
||||||
|
receiveEvent.Client, guid);
|
||||||
|
receiveEvent.Client.Ban(_translationLookup["SERVER_BAN_EVADE"].FormatExt(guid),
|
||||||
|
receiveEvent.Client.CurrentServer.AsConsoleClient(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
145
Application/CoreEventHandler.cs
Normal file
145
Application/CoreEventHandler.cs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Events;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharedLibraryCore.Events.Management;
|
||||||
|
using SharedLibraryCore.Events.Server;
|
||||||
|
using SharedLibraryCore.Interfaces.Events;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application
|
||||||
|
{
|
||||||
|
public class CoreEventHandler : ICoreEventHandler
|
||||||
|
{
|
||||||
|
private const int MaxCurrentEvents = 25;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly SemaphoreSlim _onProcessingEvents = new(MaxCurrentEvents, MaxCurrentEvents);
|
||||||
|
private readonly ManualResetEventSlim _onEventReady = new(false);
|
||||||
|
private readonly ConcurrentQueue<(IManager, CoreEvent)> _runningEventTasks = new();
|
||||||
|
private CancellationToken _cancellationToken;
|
||||||
|
private int _activeTasks;
|
||||||
|
|
||||||
|
private static readonly GameEvent.EventType[] OverrideEvents =
|
||||||
|
{
|
||||||
|
GameEvent.EventType.Connect,
|
||||||
|
GameEvent.EventType.Disconnect,
|
||||||
|
GameEvent.EventType.Quit,
|
||||||
|
GameEvent.EventType.Stop
|
||||||
|
};
|
||||||
|
|
||||||
|
public CoreEventHandler(ILogger<CoreEventHandler> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueEvent(IManager manager, CoreEvent coreEvent)
|
||||||
|
{
|
||||||
|
_runningEventTasks.Enqueue((manager, coreEvent));
|
||||||
|
_onEventReady.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartProcessing(CancellationToken token)
|
||||||
|
{
|
||||||
|
_cancellationToken = token;
|
||||||
|
|
||||||
|
while (!_cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
_onEventReady.Reset();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_onProcessingEvents.Wait(_cancellationToken);
|
||||||
|
|
||||||
|
if (!_runningEventTasks.TryDequeue(out var coreEvent))
|
||||||
|
{
|
||||||
|
if (_onProcessingEvents.CurrentCount < MaxCurrentEvents)
|
||||||
|
{
|
||||||
|
_onProcessingEvents.Release(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onEventReady.Wait(_cancellationToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Start processing event {Name} {SemaphoreCount} - {QueuedTasks}",
|
||||||
|
coreEvent.Item2.GetType().Name, _onProcessingEvents.CurrentCount, _runningEventTasks.Count);
|
||||||
|
|
||||||
|
_ = Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _activeTasks);
|
||||||
|
_logger.LogDebug("[Start] Active Tasks = {TaskCount}", _activeTasks);
|
||||||
|
return HandleEventTaskExecute(coreEvent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Could not enqueue event for processing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleEventTaskExecute((IManager, CoreEvent) coreEvent)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await GetEventTask(coreEvent.Item1, coreEvent.Item2);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Event timed out {Type}", coreEvent.Item2.GetType().Name);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Could not complete invoke for {EventType}",
|
||||||
|
coreEvent.Item2.GetType().Name);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_onProcessingEvents.CurrentCount < MaxCurrentEvents)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Freeing up event semaphore for next event {SemaphoreCount}",
|
||||||
|
_onProcessingEvents.CurrentCount);
|
||||||
|
_onProcessingEvents.Release(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Interlocked.Decrement(ref _activeTasks);
|
||||||
|
_logger.LogDebug("[Complete] {Type}, Active Tasks = {TaskCount} - {Queue}", coreEvent.Item2.GetType(),
|
||||||
|
_activeTasks, _runningEventTasks.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task GetEventTask(IManager manager, CoreEvent coreEvent)
|
||||||
|
{
|
||||||
|
return coreEvent switch
|
||||||
|
{
|
||||||
|
GameEvent gameEvent => BuildLegacyEventTask(manager, coreEvent, gameEvent),
|
||||||
|
GameServerEvent gameServerEvent => IGameServerEventSubscriptions.InvokeEventAsync(gameServerEvent,
|
||||||
|
manager.CancellationToken),
|
||||||
|
ManagementEvent managementEvent => IManagementEventSubscriptions.InvokeEventAsync(managementEvent,
|
||||||
|
manager.CancellationToken),
|
||||||
|
_ => Task.CompletedTask
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task BuildLegacyEventTask(IManager manager, CoreEvent coreEvent, GameEvent gameEvent)
|
||||||
|
{
|
||||||
|
if (manager.IsRunning || OverrideEvents.Contains(gameEvent.Type))
|
||||||
|
{
|
||||||
|
await manager.ExecuteEvent(gameEvent);
|
||||||
|
await IGameEventSubscriptions.InvokeEventAsync(coreEvent, manager.CancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Skipping event as we're shutting down {EventId}", gameEvent.IncrementalId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@ using SharedLibraryCore.Helpers;
|
|||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@ -31,6 +31,9 @@ using IW4MAdmin.Application.Plugin.Script;
|
|||||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SharedLibraryCore.Alerts;
|
using SharedLibraryCore.Alerts;
|
||||||
|
using SharedLibraryCore.Events.Management;
|
||||||
|
using SharedLibraryCore.Events.Server;
|
||||||
|
using SharedLibraryCore.Interfaces.Events;
|
||||||
using static Data.Models.Client.EFClient;
|
using static Data.Models.Client.EFClient;
|
||||||
|
|
||||||
namespace IW4MAdmin
|
namespace IW4MAdmin
|
||||||
@ -44,11 +47,12 @@ namespace IW4MAdmin
|
|||||||
private const int REPORT_FLAG_COUNT = 4;
|
private const int REPORT_FLAG_COUNT = 4;
|
||||||
private long lastGameTime = 0;
|
private long lastGameTime = 0;
|
||||||
|
|
||||||
public int Id { get; private set; }
|
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IClientNoticeMessageFormatter _messageFormatter;
|
private readonly IClientNoticeMessageFormatter _messageFormatter;
|
||||||
private readonly ILookupCache<EFServer> _serverCache;
|
private readonly ILookupCache<EFServer> _serverCache;
|
||||||
private readonly CommandConfiguration _commandConfiguration;
|
private readonly CommandConfiguration _commandConfiguration;
|
||||||
|
private EFServer _cachedDatabaseServer;
|
||||||
|
private readonly StatManager _statManager;
|
||||||
|
|
||||||
public IW4MServer(
|
public IW4MServer(
|
||||||
ServerConfiguration serverConfiguration,
|
ServerConfiguration serverConfiguration,
|
||||||
@ -72,6 +76,18 @@ namespace IW4MAdmin
|
|||||||
_messageFormatter = messageFormatter;
|
_messageFormatter = messageFormatter;
|
||||||
_serverCache = serverCache;
|
_serverCache = serverCache;
|
||||||
_commandConfiguration = commandConfiguration;
|
_commandConfiguration = commandConfiguration;
|
||||||
|
_statManager = serviceProvider.GetRequiredService<StatManager>();
|
||||||
|
|
||||||
|
IGameServerEventSubscriptions.MonitoringStarted += async (gameEvent, token) =>
|
||||||
|
{
|
||||||
|
if (gameEvent.Server.Id != Id)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await EnsureServerAdded();
|
||||||
|
await _statManager.EnsureServerAdded(gameEvent.Server, token);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<EFClient> OnClientConnected(EFClient clientFromLog)
|
public override async Task<EFClient> OnClientConnected(EFClient clientFromLog)
|
||||||
@ -108,7 +124,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
Clients[client.ClientNumber] = client;
|
Clients[client.ClientNumber] = client;
|
||||||
ServerLogger.LogDebug("End PreConnect for {client}", client.ToString());
|
ServerLogger.LogDebug("End PreConnect for {client}", client.ToString());
|
||||||
var e = new GameEvent()
|
var e = new GameEvent
|
||||||
{
|
{
|
||||||
Origin = client,
|
Origin = client,
|
||||||
Owner = this,
|
Owner = this,
|
||||||
@ -116,6 +132,11 @@ namespace IW4MAdmin
|
|||||||
};
|
};
|
||||||
|
|
||||||
Manager.AddEvent(e);
|
Manager.AddEvent(e);
|
||||||
|
Manager.QueueEvent(new ClientStateInitializeEvent
|
||||||
|
{
|
||||||
|
Client = client,
|
||||||
|
Source = this,
|
||||||
|
});
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,10 +231,17 @@ namespace IW4MAdmin
|
|||||||
ServerLogger.LogInformation("Executing command {Command} for {Client}", cmd.Name,
|
ServerLogger.LogInformation("Executing command {Command} for {Client}", cmd.Name,
|
||||||
E.Origin.ToString());
|
E.Origin.ToString());
|
||||||
await cmd.ExecuteAsync(E);
|
await cmd.ExecuteAsync(E);
|
||||||
|
Manager.QueueEvent(new ClientExecuteCommandEvent
|
||||||
|
{
|
||||||
|
Command = cmd,
|
||||||
|
Client = E.Origin,
|
||||||
|
Source = this,
|
||||||
|
CommandText = E.Data
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var pluginTasks = Manager.Plugins
|
var pluginTasks = Manager.Plugins.Where(plugin => !plugin.IsParser)
|
||||||
.Select(async plugin => await CreatePluginTask(plugin, E));
|
.Select(plugin => CreatePluginTask(plugin, E));
|
||||||
|
|
||||||
await Task.WhenAll(pluginTasks);
|
await Task.WhenAll(pluginTasks);
|
||||||
}
|
}
|
||||||
@ -250,7 +278,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await plugin.OnEventAsync(gameEvent, this).WithWaitCancellation(tokenSource.Token);
|
await plugin.OnEventAsync(gameEvent, this);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@ -277,29 +305,7 @@ namespace IW4MAdmin
|
|||||||
{
|
{
|
||||||
ServerLogger.LogDebug("processing event of type {type}", E.Type);
|
ServerLogger.LogDebug("processing event of type {type}", E.Type);
|
||||||
|
|
||||||
if (E.Type == GameEvent.EventType.Start)
|
if (E.Type == GameEvent.EventType.ConnectionLost)
|
||||||
{
|
|
||||||
var existingServer = (await _serverCache
|
|
||||||
.FirstAsync(server => server.Id == EndPoint));
|
|
||||||
|
|
||||||
var serverId = await GetIdForServer(E.Owner);
|
|
||||||
|
|
||||||
if (existingServer == null)
|
|
||||||
{
|
|
||||||
var server = new EFServer()
|
|
||||||
{
|
|
||||||
Port = Port,
|
|
||||||
EndPoint = ToString(),
|
|
||||||
ServerId = serverId,
|
|
||||||
GameName = (Reference.Game?)GameName,
|
|
||||||
HostName = Hostname
|
|
||||||
};
|
|
||||||
|
|
||||||
await _serverCache.AddAsync(server);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.ConnectionLost)
|
|
||||||
{
|
{
|
||||||
var exception = E.Extra as Exception;
|
var exception = E.Extra as Exception;
|
||||||
ServerLogger.LogError(exception,
|
ServerLogger.LogError(exception,
|
||||||
@ -350,9 +356,18 @@ namespace IW4MAdmin
|
|||||||
else if (E.Type == GameEvent.EventType.ChangePermission)
|
else if (E.Type == GameEvent.EventType.ChangePermission)
|
||||||
{
|
{
|
||||||
var newPermission = (Permission) E.Extra;
|
var newPermission = (Permission) E.Extra;
|
||||||
|
var oldPermission = E.Target.Level;
|
||||||
ServerLogger.LogInformation("{origin} is setting {target} to permission level {newPermission}",
|
ServerLogger.LogInformation("{origin} is setting {target} to permission level {newPermission}",
|
||||||
E.Origin.ToString(), E.Target.ToString(), newPermission);
|
E.Origin.ToString(), E.Target.ToString(), newPermission);
|
||||||
await Manager.GetClientService().UpdateLevel(newPermission, E.Target, E.Origin);
|
await Manager.GetClientService().UpdateLevel(newPermission, E.Target, E.Origin);
|
||||||
|
|
||||||
|
Manager.QueueEvent(new ClientPermissionChangeEvent
|
||||||
|
{
|
||||||
|
Client = E.Origin,
|
||||||
|
Source = this,
|
||||||
|
OldPermission = oldPermission,
|
||||||
|
NewPermission = newPermission
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.Connect)
|
else if (E.Type == GameEvent.EventType.Connect)
|
||||||
@ -500,6 +515,12 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
await Manager.GetPenaltyService().Create(newPenalty);
|
await Manager.GetPenaltyService().Create(newPenalty);
|
||||||
E.Target.SetLevel(Permission.Flagged, E.Origin);
|
E.Target.SetLevel(Permission.Flagged, E.Origin);
|
||||||
|
|
||||||
|
Manager.QueueEvent(new ClientPenaltyEvent
|
||||||
|
{
|
||||||
|
Client = E.Target,
|
||||||
|
Penalty = newPenalty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.Unflag)
|
else if (E.Type == GameEvent.EventType.Unflag)
|
||||||
@ -519,6 +540,12 @@ namespace IW4MAdmin
|
|||||||
await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId,
|
await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId,
|
||||||
E.Target.GameName, E.Target.CurrentAlias?.IPAddress);
|
E.Target.GameName, E.Target.CurrentAlias?.IPAddress);
|
||||||
await Manager.GetPenaltyService().Create(unflagPenalty);
|
await Manager.GetPenaltyService().Create(unflagPenalty);
|
||||||
|
|
||||||
|
Manager.QueueEvent(new ClientPenaltyRevokeEvent
|
||||||
|
{
|
||||||
|
Client = E.Target,
|
||||||
|
Penalty = unflagPenalty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.Report)
|
else if (E.Type == GameEvent.EventType.Report)
|
||||||
@ -554,6 +581,13 @@ namespace IW4MAdmin
|
|||||||
Utilities.CurrentLocalization.LocalizationIndex["SERVER_AUTO_FLAG_REPORT"]
|
Utilities.CurrentLocalization.LocalizationIndex["SERVER_AUTO_FLAG_REPORT"]
|
||||||
.FormatExt(reportNum), Utilities.IW4MAdminClient(E.Owner));
|
.FormatExt(reportNum), Utilities.IW4MAdminClient(E.Owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Manager.QueueEvent(new ClientPenaltyEvent
|
||||||
|
{
|
||||||
|
Client = E.Target,
|
||||||
|
Penalty = newReport,
|
||||||
|
Source = this
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.TempBan)
|
else if (E.Type == GameEvent.EventType.TempBan)
|
||||||
@ -728,6 +762,11 @@ namespace IW4MAdmin
|
|||||||
{
|
{
|
||||||
MaxClients = int.Parse(dict["com_maxclients"]);
|
MaxClients = int.Parse(dict["com_maxclients"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (dict.ContainsKey("com_maxplayers"))
|
||||||
|
{
|
||||||
|
MaxClients = int.Parse(dict["com_maxplayers"]);
|
||||||
|
}
|
||||||
|
|
||||||
if (dict.ContainsKey("mapname"))
|
if (dict.ContainsKey("mapname"))
|
||||||
{
|
{
|
||||||
@ -772,34 +811,6 @@ namespace IW4MAdmin
|
|||||||
{
|
{
|
||||||
E.Origin.UpdateTeam(E.Extra as string);
|
E.Origin.UpdateTeam(E.Extra as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.MetaUpdated)
|
|
||||||
{
|
|
||||||
if (E.Extra is "PersistentClientGuid")
|
|
||||||
{
|
|
||||||
var parts = E.Data.Split(",");
|
|
||||||
|
|
||||||
if (parts.Length == 2 && int.TryParse(parts[0], out var high) &&
|
|
||||||
int.TryParse(parts[1], out var low))
|
|
||||||
{
|
|
||||||
var guid = long.Parse(high.ToString("X") + low.ToString("X"), NumberStyles.HexNumber);
|
|
||||||
|
|
||||||
var penalties = await Manager.GetPenaltyService()
|
|
||||||
.GetActivePenaltiesByIdentifier(null, guid, (Reference.Game)GameName);
|
|
||||||
var banPenalty =
|
|
||||||
penalties.FirstOrDefault(penalty => penalty.Type == EFPenalty.PenaltyType.Ban);
|
|
||||||
|
|
||||||
if (banPenalty is not null && E.Origin.Level != Permission.Banned)
|
|
||||||
{
|
|
||||||
ServerLogger.LogInformation(
|
|
||||||
"Banning {Client} as they have have provided a persistent clientId of {PersistentClientId}, which is banned",
|
|
||||||
E.Origin.ToString(), guid);
|
|
||||||
E.Origin.Ban(loc["SERVER_BAN_EVADE"].FormatExt(guid),
|
|
||||||
Utilities.IW4MAdminClient(this), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (ChatHistory)
|
lock (ChatHistory)
|
||||||
{
|
{
|
||||||
@ -820,6 +831,53 @@ namespace IW4MAdmin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task EnsureServerAdded()
|
||||||
|
{
|
||||||
|
var gameServer = await _serverCache
|
||||||
|
.FirstAsync(server => server.EndPoint == base.Id);
|
||||||
|
|
||||||
|
if (gameServer == null)
|
||||||
|
{
|
||||||
|
gameServer = new EFServer
|
||||||
|
{
|
||||||
|
Port = ListenPort,
|
||||||
|
EndPoint = base.Id,
|
||||||
|
ServerId = BuildLegacyDatabaseId(),
|
||||||
|
GameName = (Reference.Game?)GameName,
|
||||||
|
HostName = ServerName
|
||||||
|
};
|
||||||
|
|
||||||
|
await _serverCache.AddAsync(gameServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var context = _serviceProvider.GetRequiredService<IDatabaseContextFactory>()
|
||||||
|
.CreateContext(enableTracking: false);
|
||||||
|
|
||||||
|
context.Servers.Attach(gameServer);
|
||||||
|
|
||||||
|
// we want to set the gamename up if it's never been set, or it changed
|
||||||
|
if (!gameServer.GameName.HasValue || gameServer.GameName.Value != GameCode)
|
||||||
|
{
|
||||||
|
gameServer.GameName = GameCode;
|
||||||
|
context.Entry(gameServer).Property(property => property.GameName).IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameServer.HostName == null || gameServer.HostName != ServerName)
|
||||||
|
{
|
||||||
|
gameServer.HostName = ServerName;
|
||||||
|
context.Entry(gameServer).Property(property => property.HostName).IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameServer.IsPasswordProtected != !string.IsNullOrEmpty(GamePassword))
|
||||||
|
{
|
||||||
|
gameServer.IsPasswordProtected = !string.IsNullOrEmpty(GamePassword);
|
||||||
|
context.Entry(gameServer).Property(property => property.IsPasswordProtected).IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
_cachedDatabaseServer = gameServer;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnClientUpdate(EFClient origin)
|
private async Task OnClientUpdate(EFClient origin)
|
||||||
{
|
{
|
||||||
var client = GetClientsAsList().FirstOrDefault(c => c.NetworkId == origin.NetworkId);
|
var client = GetClientsAsList().FirstOrDefault(c => c.NetworkId == origin.NetworkId);
|
||||||
@ -909,22 +967,15 @@ namespace IW4MAdmin
|
|||||||
public override async Task<long> GetIdForServer(Server server = null)
|
public override async Task<long> GetIdForServer(Server server = null)
|
||||||
{
|
{
|
||||||
server ??= this;
|
server ??= this;
|
||||||
|
|
||||||
if ($"{server.IP}:{server.Port.ToString()}" == "66.150.121.184:28965")
|
|
||||||
{
|
|
||||||
return 886229536;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: this is not stable and will need to be migrated again...
|
return (await _serverCache.FirstAsync(cachedServer =>
|
||||||
long id = HashCode.Combine(server.IP, server.Port);
|
cachedServer.EndPoint == server.Id || cachedServer.ServerId == server.EndPoint)).ServerId;
|
||||||
id = id < 0 ? Math.Abs(id) : id;
|
}
|
||||||
|
|
||||||
var serverId = (await _serverCache
|
private long BuildLegacyDatabaseId()
|
||||||
.FirstAsync(_server => _server.ServerId == server.EndPoint ||
|
{
|
||||||
_server.EndPoint == server.ToString() ||
|
long id = HashCode.Combine(ListenAddress, ListenPort);
|
||||||
_server.ServerId == id))?.ServerId;
|
return id < 0 ? Math.Abs(id) : id;
|
||||||
|
|
||||||
return !serverId.HasValue ? id : serverId.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMap(string mapname)
|
private void UpdateMap(string mapname)
|
||||||
@ -983,7 +1034,7 @@ namespace IW4MAdmin
|
|||||||
{
|
{
|
||||||
await client.OnDisconnect();
|
await client.OnDisconnect();
|
||||||
|
|
||||||
var e = new GameEvent()
|
var e = new GameEvent
|
||||||
{
|
{
|
||||||
Type = GameEvent.EventType.Disconnect,
|
Type = GameEvent.EventType.Disconnect,
|
||||||
Owner = this,
|
Owner = this,
|
||||||
@ -994,6 +1045,14 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token);
|
await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var tokenSource = new CancellationTokenSource();
|
||||||
|
tokenSource.CancelAfter(Utilities.DefaultCommandTimeout);
|
||||||
|
|
||||||
|
Manager.QueueEvent(new MonitorStopEvent
|
||||||
|
{
|
||||||
|
Server = this
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private DateTime _lastMessageSent = DateTime.Now;
|
private DateTime _lastMessageSent = DateTime.Now;
|
||||||
@ -1075,6 +1134,16 @@ namespace IW4MAdmin
|
|||||||
Manager.AddEvent(gameEvent);
|
Manager.AddEvent(gameEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (polledClients[2].Any())
|
||||||
|
{
|
||||||
|
Manager.QueueEvent(new ClientDataUpdateEvent
|
||||||
|
{
|
||||||
|
Clients = new ReadOnlyCollection<EFClient>(polledClients[2]),
|
||||||
|
Server = this,
|
||||||
|
Source = this,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (Throttled)
|
if (Throttled)
|
||||||
{
|
{
|
||||||
var gameEvent = new GameEvent
|
var gameEvent = new GameEvent
|
||||||
@ -1086,6 +1155,12 @@ namespace IW4MAdmin
|
|||||||
};
|
};
|
||||||
|
|
||||||
Manager.AddEvent(gameEvent);
|
Manager.AddEvent(gameEvent);
|
||||||
|
|
||||||
|
Manager.QueueEvent(new ConnectionRestoreEvent
|
||||||
|
{
|
||||||
|
Server = this,
|
||||||
|
Source = this
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LastPoll = DateTime.Now;
|
LastPoll = DateTime.Now;
|
||||||
@ -1109,6 +1184,12 @@ namespace IW4MAdmin
|
|||||||
};
|
};
|
||||||
|
|
||||||
Manager.AddEvent(gameEvent);
|
Manager.AddEvent(gameEvent);
|
||||||
|
Manager.QueueEvent(new ConnectionInterruptEvent
|
||||||
|
{
|
||||||
|
Server = this,
|
||||||
|
Source = this
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@ -1469,6 +1550,12 @@ namespace IW4MAdmin
|
|||||||
.FormatExt(activeClient.Warnings, activeClient.Name, reason);
|
.FormatExt(activeClient.Warnings, activeClient.Name, reason);
|
||||||
activeClient.CurrentServer.Broadcast(message);
|
activeClient.CurrentServer.Broadcast(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Manager.QueueEvent(new ClientPenaltyEvent
|
||||||
|
{
|
||||||
|
Client = targetClient,
|
||||||
|
Penalty = newPenalty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task Kick(string reason, EFClient targetClient, EFClient originClient, EFPenalty previousPenalty)
|
public override async Task Kick(string reason, EFClient targetClient, EFClient originClient, EFPenalty previousPenalty)
|
||||||
@ -1507,6 +1594,12 @@ namespace IW4MAdmin
|
|||||||
ServerLogger.LogDebug("Executing tempban kick command for {ActiveClient}", activeClient.ToString());
|
ServerLogger.LogDebug("Executing tempban kick command for {ActiveClient}", activeClient.ToString());
|
||||||
await activeClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
await activeClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Manager.QueueEvent(new ClientPenaltyEvent
|
||||||
|
{
|
||||||
|
Client = targetClient,
|
||||||
|
Penalty = newPenalty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task TempBan(string reason, TimeSpan length, EFClient targetClient, EFClient originClient)
|
public override async Task TempBan(string reason, TimeSpan length, EFClient targetClient, EFClient originClient)
|
||||||
@ -1540,6 +1633,12 @@ namespace IW4MAdmin
|
|||||||
ServerLogger.LogDebug("Executing tempban kick command for {ActiveClient}", activeClient.ToString());
|
ServerLogger.LogDebug("Executing tempban kick command for {ActiveClient}", activeClient.ToString());
|
||||||
await activeClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
await activeClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Manager.QueueEvent(new ClientPenaltyEvent
|
||||||
|
{
|
||||||
|
Client = targetClient,
|
||||||
|
Penalty = newPenalty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task Ban(string reason, EFClient targetClient, EFClient originClient, bool isEvade = false)
|
public override async Task Ban(string reason, EFClient targetClient, EFClient originClient, bool isEvade = false)
|
||||||
@ -1575,6 +1674,12 @@ namespace IW4MAdmin
|
|||||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||||
await activeClient.CurrentServer.ExecuteCommandAsync(formattedString);
|
await activeClient.CurrentServer.ExecuteCommandAsync(formattedString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Manager.QueueEvent(new ClientPenaltyEvent
|
||||||
|
{
|
||||||
|
Client = targetClient,
|
||||||
|
Penalty = newPenalty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task Unban(string reason, EFClient targetClient, EFClient originClient)
|
public override async Task Unban(string reason, EFClient targetClient, EFClient originClient)
|
||||||
@ -1596,6 +1701,12 @@ namespace IW4MAdmin
|
|||||||
await Manager.GetPenaltyService().RemoveActivePenalties(targetClient.AliasLink.AliasLinkId,
|
await Manager.GetPenaltyService().RemoveActivePenalties(targetClient.AliasLink.AliasLinkId,
|
||||||
targetClient.NetworkId, targetClient.GameName, targetClient.CurrentAlias?.IPAddress);
|
targetClient.NetworkId, targetClient.GameName, targetClient.CurrentAlias?.IPAddress);
|
||||||
await Manager.GetPenaltyService().Create(unbanPenalty);
|
await Manager.GetPenaltyService().Create(unbanPenalty);
|
||||||
|
|
||||||
|
Manager.QueueEvent(new ClientPenaltyRevokeEvent
|
||||||
|
{
|
||||||
|
Client = targetClient,
|
||||||
|
Penalty = unbanPenalty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void InitializeTokens()
|
public override void InitializeTokens()
|
||||||
@ -1605,5 +1716,7 @@ namespace IW4MAdmin
|
|||||||
Manager.GetMessageTokens().Add(new MessageToken("NEXTMAP", (Server s) => SharedLibraryCore.Commands.NextMapCommand.GetNextMap(s, _translationLookup)));
|
Manager.GetMessageTokens().Add(new MessageToken("NEXTMAP", (Server s) => SharedLibraryCore.Commands.NextMapCommand.GetNextMap(s, _translationLookup)));
|
||||||
Manager.GetMessageTokens().Add(new MessageToken("ADMINS", (Server s) => Task.FromResult(ListAdminsCommand.OnlineAdmins(s, _translationLookup))));
|
Manager.GetMessageTokens().Add(new MessageToken("ADMINS", (Server s) => Task.FromResult(ListAdminsCommand.OnlineAdmins(s, _translationLookup))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override long LegacyDatabaseId => _cachedDatabaseServer.ServerId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ namespace IW4MAdmin.Application
|
|||||||
public static BuildNumber Version { get; } = BuildNumber.Parse(Utilities.GetVersionAsString());
|
public static BuildNumber Version { get; } = BuildNumber.Parse(Utilities.GetVersionAsString());
|
||||||
private static ApplicationManager _serverManager;
|
private static ApplicationManager _serverManager;
|
||||||
private static Task _applicationTask;
|
private static Task _applicationTask;
|
||||||
private static ServiceProvider _serviceProvider;
|
private static IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// entrypoint of the application
|
/// entrypoint of the application
|
||||||
@ -112,23 +112,24 @@ namespace IW4MAdmin.Application
|
|||||||
ConfigurationMigration.MoveConfigFolder10518(null);
|
ConfigurationMigration.MoveConfigFolder10518(null);
|
||||||
ConfigurationMigration.CheckDirectories();
|
ConfigurationMigration.CheckDirectories();
|
||||||
ConfigurationMigration.RemoveObsoletePlugins20210322();
|
ConfigurationMigration.RemoveObsoletePlugins20210322();
|
||||||
|
|
||||||
logger.LogDebug("Configuring services...");
|
logger.LogDebug("Configuring services...");
|
||||||
var services = await ConfigureServices(args);
|
|
||||||
_serviceProvider = services.BuildServiceProvider();
|
|
||||||
var versionChecker = _serviceProvider.GetRequiredService<IMasterCommunication>();
|
|
||||||
_serverManager = (ApplicationManager) _serviceProvider.GetRequiredService<IManager>();
|
|
||||||
translationLookup = _serviceProvider.GetRequiredService<ITranslationLookup>();
|
|
||||||
|
|
||||||
_applicationTask = RunApplicationTasksAsync(logger, services);
|
var configHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
||||||
var tasks = new[]
|
await configHandler.BuildAsync();
|
||||||
{
|
_serviceProvider = WebfrontCore.Program.InitializeServices(ConfigureServices,
|
||||||
versionChecker.CheckVersion(),
|
(configHandler.Configuration() ?? new ApplicationConfiguration()).WebfrontBindUrl);
|
||||||
_applicationTask
|
|
||||||
};
|
_serverManager = (ApplicationManager)_serviceProvider.GetRequiredService<IManager>();
|
||||||
|
translationLookup = _serviceProvider.GetRequiredService<ITranslationLookup>();
|
||||||
|
|
||||||
await _serverManager.Init();
|
await _serverManager.Init();
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
_applicationTask = Task.WhenAll(RunApplicationTasksAsync(logger, _serviceProvider),
|
||||||
|
_serverManager.Start());
|
||||||
|
|
||||||
|
await _applicationTask;
|
||||||
|
logger.LogInformation("Shutdown completed successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -178,21 +179,20 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
goto restart;
|
goto restart;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _serviceProvider.DisposeAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// runs the core application tasks
|
/// runs the core application tasks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static async Task RunApplicationTasksAsync(ILogger logger, IServiceCollection services)
|
private static Task RunApplicationTasksAsync(ILogger logger, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
var webfrontTask = _serverManager.GetApplicationSettings().Configuration().EnableWebFront
|
var webfrontTask = _serverManager.GetApplicationSettings().Configuration().EnableWebFront
|
||||||
? WebfrontCore.Program.Init(_serverManager, _serviceProvider, services, _serverManager.CancellationToken)
|
? WebfrontCore.Program.GetWebHostTask(_serverManager.CancellationToken)
|
||||||
: Task.CompletedTask;
|
: Task.CompletedTask;
|
||||||
|
|
||||||
var collectionService = _serviceProvider.GetRequiredService<IServerDataCollector>();
|
var collectionService = serviceProvider.GetRequiredService<IServerDataCollector>();
|
||||||
|
var versionChecker = serviceProvider.GetRequiredService<IMasterCommunication>();
|
||||||
|
|
||||||
// we want to run this one on a manual thread instead of letting the thread pool handle it,
|
// we want to run this one on a manual thread instead of letting the thread pool handle it,
|
||||||
// because we can't exit early from waiting on console input, and it prevents us from restarting
|
// because we can't exit early from waiting on console input, and it prevents us from restarting
|
||||||
@ -203,18 +203,15 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
var tasks = new[]
|
var tasks = new[]
|
||||||
{
|
{
|
||||||
|
versionChecker.CheckVersion(),
|
||||||
webfrontTask,
|
webfrontTask,
|
||||||
_serverManager.Start(),
|
serviceProvider.GetRequiredService<IMasterCommunication>()
|
||||||
_serviceProvider.GetRequiredService<IMasterCommunication>()
|
|
||||||
.RunUploadStatus(_serverManager.CancellationToken),
|
.RunUploadStatus(_serverManager.CancellationToken),
|
||||||
collectionService.BeginCollectionAsync(cancellationToken: _serverManager.CancellationToken)
|
collectionService.BeginCollectionAsync(cancellationToken: _serverManager.CancellationToken)
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.LogDebug("Starting webfront and input tasks");
|
logger.LogDebug("Starting webfront and input tasks");
|
||||||
await Task.WhenAll(tasks);
|
return Task.WhenAll(tasks);
|
||||||
|
|
||||||
logger.LogInformation("Shutdown completed successfully");
|
|
||||||
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_SHUTDOWN_SUCCESS"]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -302,8 +299,21 @@ namespace IW4MAdmin.Application
|
|||||||
var (plugins, commands, configurations) = pluginImporter.DiscoverAssemblyPluginImplementations();
|
var (plugins, commands, configurations) = pluginImporter.DiscoverAssemblyPluginImplementations();
|
||||||
foreach (var pluginType in plugins)
|
foreach (var pluginType in plugins)
|
||||||
{
|
{
|
||||||
defaultLogger.LogDebug("Registered plugin type {Name}", pluginType.FullName);
|
var isV2 = pluginType.GetInterface(nameof(IPluginV2), false) != null;
|
||||||
serviceCollection.AddSingleton(typeof(IPlugin), pluginType);
|
|
||||||
|
defaultLogger.LogDebug("Registering plugin type {Name}", pluginType.FullName);
|
||||||
|
|
||||||
|
serviceCollection.AddSingleton(!isV2 ? typeof(IPlugin) : typeof(IPluginV2), pluginType);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var registrationMethod = pluginType.GetMethod(nameof(IPluginV2.RegisterDependencies));
|
||||||
|
registrationMethod?.Invoke(null, new object[] { serviceCollection });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
defaultLogger.LogError(ex, "Could not register plugin of type {Type}", pluginType.Name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// register the plugin commands
|
// register the plugin commands
|
||||||
@ -351,13 +361,11 @@ namespace IW4MAdmin.Application
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures the dependency injection services
|
/// Configures the dependency injection services
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task<IServiceCollection> ConfigureServices(string[] args)
|
private static void ConfigureServices(IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
// todo: this is a quick fix
|
// todo: this is a quick fix
|
||||||
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
||||||
|
|
||||||
// setup the static resources (config/master api/translations)
|
|
||||||
var serviceCollection = new ServiceCollection();
|
|
||||||
serviceCollection.AddConfiguration<ApplicationConfiguration>("IW4MAdminSettings")
|
serviceCollection.AddConfiguration<ApplicationConfiguration>("IW4MAdminSettings")
|
||||||
.AddConfiguration<DefaultSettings>()
|
.AddConfiguration<DefaultSettings>()
|
||||||
.AddConfiguration<CommandConfiguration>()
|
.AddConfiguration<CommandConfiguration>()
|
||||||
@ -365,14 +373,10 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
// for legacy purposes. update at some point
|
// for legacy purposes. update at some point
|
||||||
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
||||||
await appConfigHandler.BuildAsync();
|
appConfigHandler.BuildAsync().GetAwaiter().GetResult();
|
||||||
var defaultConfigHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings");
|
|
||||||
await defaultConfigHandler.BuildAsync();
|
|
||||||
var commandConfigHandler = new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration");
|
var commandConfigHandler = new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration");
|
||||||
await commandConfigHandler.BuildAsync();
|
commandConfigHandler.BuildAsync().GetAwaiter().GetResult();
|
||||||
var statsCommandHandler = new BaseConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
|
|
||||||
await statsCommandHandler.BuildAsync();
|
|
||||||
var defaultConfig = defaultConfigHandler.Configuration();
|
|
||||||
var appConfig = appConfigHandler.Configuration();
|
var appConfig = appConfigHandler.Configuration();
|
||||||
var masterUri = Utilities.IsDevelopment
|
var masterUri = Utilities.IsDevelopment
|
||||||
? new Uri("http://127.0.0.1:8080")
|
? new Uri("http://127.0.0.1:8080")
|
||||||
@ -385,13 +389,6 @@ namespace IW4MAdmin.Application
|
|||||||
var masterRestClient = RestClient.For<IMasterApi>(httpClient);
|
var masterRestClient = RestClient.For<IMasterApi>(httpClient);
|
||||||
var translationLookup = Configure.Initialize(Utilities.DefaultLogger, masterRestClient, appConfig);
|
var translationLookup = Configure.Initialize(Utilities.DefaultLogger, masterRestClient, appConfig);
|
||||||
|
|
||||||
if (appConfig == null)
|
|
||||||
{
|
|
||||||
appConfig = (ApplicationConfiguration) new ApplicationConfiguration().Generate();
|
|
||||||
appConfigHandler.Set(appConfig);
|
|
||||||
await appConfigHandler.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// register override level names
|
// register override level names
|
||||||
foreach (var (key, value) in appConfig.OverridePermissionLevelNames)
|
foreach (var (key, value) in appConfig.OverridePermissionLevelNames)
|
||||||
{
|
{
|
||||||
@ -402,17 +399,10 @@ namespace IW4MAdmin.Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build the dependency list
|
// build the dependency list
|
||||||
HandlePluginRegistration(appConfig, serviceCollection, masterRestClient);
|
|
||||||
|
|
||||||
serviceCollection
|
serviceCollection
|
||||||
.AddBaseLogger(appConfig)
|
.AddBaseLogger(appConfig)
|
||||||
.AddSingleton(defaultConfig)
|
|
||||||
.AddSingleton<IServiceCollection>(serviceCollection)
|
|
||||||
.AddSingleton<IConfigurationHandler<DefaultSettings>, BaseConfigurationHandler<DefaultSettings>>()
|
|
||||||
.AddSingleton((IConfigurationHandler<ApplicationConfiguration>) appConfigHandler)
|
.AddSingleton((IConfigurationHandler<ApplicationConfiguration>) appConfigHandler)
|
||||||
.AddSingleton<IConfigurationHandler<CommandConfiguration>>(commandConfigHandler)
|
.AddSingleton<IConfigurationHandler<CommandConfiguration>>(commandConfigHandler)
|
||||||
.AddSingleton(appConfig)
|
|
||||||
.AddSingleton(statsCommandHandler.Configuration() ?? new StatsConfiguration())
|
|
||||||
.AddSingleton(serviceProvider =>
|
.AddSingleton(serviceProvider =>
|
||||||
serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>()
|
serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>()
|
||||||
.Configuration() ?? new CommandConfiguration())
|
.Configuration() ?? new CommandConfiguration())
|
||||||
@ -464,7 +454,9 @@ namespace IW4MAdmin.Application
|
|||||||
.AddSingleton<IServerDataCollector, ServerDataCollector>()
|
.AddSingleton<IServerDataCollector, ServerDataCollector>()
|
||||||
.AddSingleton<IGeoLocationService>(new GeoLocationService(Path.Join(".", "Resources", "GeoLite2-Country.mmdb")))
|
.AddSingleton<IGeoLocationService>(new GeoLocationService(Path.Join(".", "Resources", "GeoLite2-Country.mmdb")))
|
||||||
.AddSingleton<IAlertManager, AlertManager>()
|
.AddSingleton<IAlertManager, AlertManager>()
|
||||||
|
#pragma warning disable CS0618
|
||||||
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
||||||
|
#pragma warning restore CS0618
|
||||||
.AddSingleton<IInteractionRegistration, InteractionRegistration>()
|
.AddSingleton<IInteractionRegistration, InteractionRegistration>()
|
||||||
.AddSingleton<IRemoteCommandService, RemoteCommandService>()
|
.AddSingleton<IRemoteCommandService, RemoteCommandService>()
|
||||||
.AddSingleton(new ConfigurationWatcher())
|
.AddSingleton(new ConfigurationWatcher())
|
||||||
@ -472,19 +464,10 @@ namespace IW4MAdmin.Application
|
|||||||
.AddSingleton<IScriptPluginFactory, ScriptPluginFactory>()
|
.AddSingleton<IScriptPluginFactory, ScriptPluginFactory>()
|
||||||
.AddSingleton(translationLookup)
|
.AddSingleton(translationLookup)
|
||||||
.AddDatabaseContextOptions(appConfig);
|
.AddDatabaseContextOptions(appConfig);
|
||||||
|
|
||||||
if (args.Contains("serialevents"))
|
serviceCollection.AddSingleton<ICoreEventHandler, CoreEventHandler>();
|
||||||
{
|
|
||||||
serviceCollection.AddSingleton<IEventHandler, SerialGameEventHandler>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
serviceCollection.AddSingleton<IEventHandler, GameEventHandler>();
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceCollection.AddSource();
|
serviceCollection.AddSource();
|
||||||
|
HandlePluginRegistration(appConfig, serviceCollection, masterRestClient);
|
||||||
return serviceCollection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ILogger BuildDefaultLogger<T>(ApplicationConfiguration appConfig)
|
private static ILogger BuildDefaultLogger<T>(ApplicationConfiguration appConfig)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -27,7 +28,7 @@ public class RemoteCommandService : IRemoteCommandService
|
|||||||
public async Task<IEnumerable<CommandResponseInfo>> Execute(int originId, int? targetId, string command,
|
public async Task<IEnumerable<CommandResponseInfo>> Execute(int originId, int? targetId, string command,
|
||||||
IEnumerable<string> arguments, Server server)
|
IEnumerable<string> arguments, Server server)
|
||||||
{
|
{
|
||||||
var (success, result) = await ExecuteWithResult(originId, targetId, command, arguments, server);
|
var (_, result) = await ExecuteWithResult(originId, targetId, command, arguments, server);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -56,7 +57,8 @@ public class RemoteCommandService : IRemoteCommandService
|
|||||||
: $"{_appConfig.CommandPrefix}{command}",
|
: $"{_appConfig.CommandPrefix}{command}",
|
||||||
Origin = client,
|
Origin = client,
|
||||||
Owner = server,
|
Owner = server,
|
||||||
IsRemote = true
|
IsRemote = true,
|
||||||
|
CorrelationId = Guid.NewGuid()
|
||||||
};
|
};
|
||||||
|
|
||||||
server.Manager.AddEvent(remoteEvent);
|
server.Manager.AddEvent(remoteEvent);
|
||||||
@ -72,7 +74,7 @@ public class RemoteCommandService : IRemoteCommandService
|
|||||||
{
|
{
|
||||||
response = new[]
|
response = new[]
|
||||||
{
|
{
|
||||||
new CommandResponseInfo()
|
new CommandResponseInfo
|
||||||
{
|
{
|
||||||
ClientId = client.ClientId,
|
ClientId = client.ClientId,
|
||||||
Response = Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]
|
Response = Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]
|
||||||
@ -90,7 +92,7 @@ public class RemoteCommandService : IRemoteCommandService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (System.OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
response = new[]
|
response = new[]
|
||||||
{
|
{
|
||||||
|
@ -12,8 +12,10 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Events.Management;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using SharedLibraryCore.Interfaces.Events;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Misc
|
namespace IW4MAdmin.Application.Misc
|
||||||
{
|
{
|
||||||
@ -24,28 +26,20 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
private readonly IManager _manager;
|
private readonly IManager _manager;
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
private readonly ApplicationConfiguration _appConfig;
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
private readonly IEventPublisher _eventPublisher;
|
|
||||||
|
|
||||||
private bool _inProgress;
|
private bool _inProgress;
|
||||||
private TimeSpan _period;
|
private TimeSpan _period;
|
||||||
|
|
||||||
public ServerDataCollector(ILogger<ServerDataCollector> logger, ApplicationConfiguration appConfig,
|
public ServerDataCollector(ILogger<ServerDataCollector> logger, ApplicationConfiguration appConfig,
|
||||||
IManager manager, IDatabaseContextFactory contextFactory, IEventPublisher eventPublisher)
|
IManager manager, IDatabaseContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_appConfig = appConfig;
|
_appConfig = appConfig;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_eventPublisher = eventPublisher;
|
|
||||||
|
|
||||||
_eventPublisher.OnClientConnect += SaveConnectionInfo;
|
IManagementEventSubscriptions.ClientStateAuthorized += SaveConnectionInfo;
|
||||||
_eventPublisher.OnClientDisconnect += SaveConnectionInfo;
|
IManagementEventSubscriptions.ClientStateDisposed += SaveConnectionInfo;
|
||||||
}
|
|
||||||
|
|
||||||
~ServerDataCollector()
|
|
||||||
{
|
|
||||||
_eventPublisher.OnClientConnect -= SaveConnectionInfo;
|
|
||||||
_eventPublisher.OnClientDisconnect -= SaveConnectionInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BeginCollectionAsync(TimeSpan? period = null, CancellationToken cancellationToken = default)
|
public async Task BeginCollectionAsync(TimeSpan? period = null, CancellationToken cancellationToken = default)
|
||||||
@ -131,18 +125,19 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
await context.SaveChangesAsync(token);
|
await context.SaveChangesAsync(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveConnectionInfo(object sender, GameEvent gameEvent)
|
private async Task SaveConnectionInfo(ClientStateEvent stateEvent, CancellationToken token)
|
||||||
{
|
{
|
||||||
using var context = _contextFactory.CreateContext(enableTracking: false);
|
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||||
context.ConnectionHistory.Add(new EFClientConnectionHistory
|
context.ConnectionHistory.Add(new EFClientConnectionHistory
|
||||||
{
|
{
|
||||||
ClientId = gameEvent.Origin.ClientId,
|
ClientId = stateEvent.Client.ClientId,
|
||||||
ServerId = gameEvent.Owner.GetIdForServer().Result,
|
ServerId = await stateEvent.Client.CurrentServer.GetIdForServer(),
|
||||||
ConnectionType = gameEvent.Type == GameEvent.EventType.Connect
|
ConnectionType = stateEvent is ClientStateAuthorizeEvent
|
||||||
? Reference.ConnectionType.Connect
|
? Reference.ConnectionType.Connect
|
||||||
: Reference.ConnectionType.Disconnect
|
: Reference.ConnectionType.Disconnect
|
||||||
});
|
});
|
||||||
context.SaveChanges();
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
.Where(rating => rating.Client.Level != EFClient.Permission.Banned)
|
.Where(rating => rating.Client.Level != EFClient.Permission.Banned)
|
||||||
.Where(rating => rating.Ranking != null)
|
.Where(rating => rating.Ranking != null)
|
||||||
.CountAsync(cancellationToken);
|
.CountAsync(cancellationToken);
|
||||||
}, nameof(_rankedClientsCache), serverId is null ? null: new[] { (object)serverId }, _cacheTimeSpan);
|
}, nameof(_rankedClientsCache), serverId is null ? null: new[] { (object)serverId }, _cacheTimeSpan);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -151,10 +151,12 @@ public class ScriptPluginV2 : IPluginV2
|
|||||||
}
|
}
|
||||||
|
|
||||||
ScriptEngine.Execute(pluginScript);
|
ScriptEngine.Execute(pluginScript);
|
||||||
|
#pragma warning disable CS8974
|
||||||
var initResult = ScriptEngine.Call("init", JsValue.FromObject(ScriptEngine, EventCallbackWrapper),
|
var initResult = ScriptEngine.Call("init", JsValue.FromObject(ScriptEngine, EventCallbackWrapper),
|
||||||
JsValue.FromObject(ScriptEngine, _pluginServiceResolver),
|
JsValue.FromObject(ScriptEngine, _pluginServiceResolver),
|
||||||
JsValue.FromObject(ScriptEngine, _scriptPluginConfigurationWrapper),
|
JsValue.FromObject(ScriptEngine, _scriptPluginConfigurationWrapper),
|
||||||
JsValue.FromObject(ScriptEngine, new ScriptPluginHelper(manager, this)));
|
JsValue.FromObject(ScriptEngine, new ScriptPluginHelper(manager, this)));
|
||||||
|
#pragma warning restore CS8974
|
||||||
|
|
||||||
if (initResult.IsNull() || initResult.IsUndefined())
|
if (initResult.IsNull() || initResult.IsUndefined())
|
||||||
{
|
{
|
||||||
|
@ -142,12 +142,12 @@ public class ClientResourceQueryHelper : IResourceQueryHelper<ClientResourceRequ
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Func<IGrouping<int, ClientResourceResponse>, DateTime> SearchByAliasLocal(string? clientName,
|
private static Func<IGrouping<int, ClientResourceResponse>, DateTime> SearchByAliasLocal(string clientName,
|
||||||
string? ipAddress)
|
string ipAddress)
|
||||||
{
|
{
|
||||||
return group =>
|
return group =>
|
||||||
{
|
{
|
||||||
ClientResourceResponse? match = null;
|
ClientResourceResponse match = null;
|
||||||
var lowercaseClientName = clientName?.ToLower();
|
var lowercaseClientName = clientName?.ToLower();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(lowercaseClientName))
|
if (!string.IsNullOrWhiteSpace(lowercaseClientName))
|
||||||
|
@ -43,7 +43,7 @@ namespace IW4MAdmin.Plugins.LiveRadar.Web.Controllers
|
|||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("Radar/{serverId}/Map")]
|
[Route("Radar/{serverId}/Map")]
|
||||||
public async Task<IActionResult> Map(string serverId = null)
|
public IActionResult Map(string serverId = null)
|
||||||
{
|
{
|
||||||
var server = serverId == null
|
var server = serverId == null
|
||||||
? _manager.GetServers().FirstOrDefault()
|
? _manager.GetServers().FirstOrDefault()
|
||||||
|
@ -59,7 +59,9 @@ public class Plugin : IPluginV2
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var muteMeta = _muteManager.GetCurrentMuteState(gameEvent.Origin).GetAwaiter().GetResult();
|
var muteMeta = Task.Run(() => _muteManager.GetCurrentMuteState(gameEvent.Origin), cancellationToken)
|
||||||
|
.GetAwaiter().GetResult();
|
||||||
|
|
||||||
if (muteMeta.MuteState is not MuteState.Muted)
|
if (muteMeta.MuteState is not MuteState.Muted)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -196,7 +196,7 @@ const plugin = {
|
|||||||
|
|
||||||
let data = [];
|
let data = [];
|
||||||
|
|
||||||
const metaService = this.serviceResolver.ResolveService('IMetaServiceV2');
|
const metaService = this.serviceResolver.resolveService('IMetaServiceV2');
|
||||||
|
|
||||||
if (event.subType === 'Meta') {
|
if (event.subType === 'Meta') {
|
||||||
const meta = (await metaService.getPersistentMeta(event.data, client.clientId, token)).result;
|
const meta = (await metaService.getPersistentMeta(event.data, client.clientId, token)).result;
|
||||||
@ -237,8 +237,8 @@ const plugin = {
|
|||||||
|
|
||||||
this.logger.logDebug('ClientId={clientId}', clientId);
|
this.logger.logDebug('ClientId={clientId}', clientId);
|
||||||
|
|
||||||
if (clientId == null) {
|
if (clientId == null || isNaN(clientId)) {
|
||||||
this.logger.logWarning('Could not find client slot {clientNumber} when processing {eventType}', event.clientNumber, event.eventType);
|
this.logger.logWarning('Could not find client slot {clientNumber} when processing {eventType}: {EventData}', event.clientNumber, event.eventType, event.data);
|
||||||
this.sendEventMessage(server, false, 'SetClientDataCompleted', 'Meta', {
|
this.sendEventMessage(server, false, 'SetClientDataCompleted', 'Meta', {
|
||||||
ClientNumber: event.clientNumber
|
ClientNumber: event.clientNumber
|
||||||
}, undefined, {
|
}, undefined, {
|
||||||
|
12
SharedLibraryCore/Events/CoreEvent.cs
Normal file
12
SharedLibraryCore/Events/CoreEvent.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Events;
|
||||||
|
|
||||||
|
public abstract class CoreEvent
|
||||||
|
{
|
||||||
|
public Guid Id { get; } = Guid.NewGuid();
|
||||||
|
public Guid? CorrelationId { get; init; }
|
||||||
|
public object Source { get; init; }
|
||||||
|
public DateTimeOffset CreatedAt { get; } = DateTimeOffset.UtcNow;
|
||||||
|
public DateTimeOffset? ProcessedAt { get; set; }
|
||||||
|
}
|
@ -1,88 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using SharedLibraryCore.Dtos;
|
|
||||||
|
|
||||||
namespace SharedLibraryCore.Events
|
|
||||||
{
|
|
||||||
public class EventApi
|
|
||||||
{
|
|
||||||
private const int MaxEvents = 25;
|
|
||||||
private static readonly ConcurrentQueue<EventInfo> RecentEvents = new ConcurrentQueue<EventInfo>();
|
|
||||||
|
|
||||||
public static IEnumerable<EventInfo> GetEvents(bool shouldConsume)
|
|
||||||
{
|
|
||||||
var eventList = RecentEvents.ToArray();
|
|
||||||
|
|
||||||
// clear queue if events should be consumed
|
|
||||||
if (shouldConsume)
|
|
||||||
{
|
|
||||||
RecentEvents.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
return eventList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void OnGameEvent(GameEvent gameEvent)
|
|
||||||
{
|
|
||||||
var E = gameEvent;
|
|
||||||
// don't want to clog up the api with unknown events
|
|
||||||
if (E.Type == GameEvent.EventType.Unknown)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var apiEvent = new EventInfo
|
|
||||||
{
|
|
||||||
ExtraInfo = E.Extra?.ToString() ?? E.Data,
|
|
||||||
GameInfo = new EntityInfo
|
|
||||||
{
|
|
||||||
Name = E.Owner.GameName.ToString(),
|
|
||||||
Id = (int)E.Owner.GameName
|
|
||||||
},
|
|
||||||
OwnerEntity = new EntityInfo
|
|
||||||
{
|
|
||||||
Name = E.Owner.Hostname,
|
|
||||||
Id = E.Owner.EndPoint
|
|
||||||
},
|
|
||||||
OriginEntity = E.Origin == null
|
|
||||||
? null
|
|
||||||
: new EntityInfo
|
|
||||||
{
|
|
||||||
Id = E.Origin.ClientId,
|
|
||||||
Name = E.Origin.Name
|
|
||||||
},
|
|
||||||
TargetEntity = E.Target == null
|
|
||||||
? null
|
|
||||||
: new EntityInfo
|
|
||||||
{
|
|
||||||
Id = E.Target.ClientId,
|
|
||||||
Name = E.Target.Name
|
|
||||||
},
|
|
||||||
EventType = new EntityInfo
|
|
||||||
{
|
|
||||||
Id = (int)E.Type,
|
|
||||||
Name = E.Type.ToString()
|
|
||||||
},
|
|
||||||
EventTime = E.Time
|
|
||||||
};
|
|
||||||
|
|
||||||
// add the new event to the list
|
|
||||||
AddNewEvent(apiEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds event to the list and removes first added if reached max capacity
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="info">EventInfo to add</param>
|
|
||||||
private static void AddNewEvent(EventInfo info)
|
|
||||||
{
|
|
||||||
// remove the first added event
|
|
||||||
if (RecentEvents.Count >= MaxEvents)
|
|
||||||
{
|
|
||||||
RecentEvents.TryDequeue(out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
RecentEvents.Enqueue(info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
44
SharedLibraryCore/Events/EventExtensions.cs
Normal file
44
SharedLibraryCore/Events/EventExtensions.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Events;
|
||||||
|
|
||||||
|
public static class EventExtensions
|
||||||
|
{
|
||||||
|
public static Task InvokeAsync<TEventType>(this Func<TEventType, CancellationToken, Task> function,
|
||||||
|
TEventType eventArgType, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (function is null)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.WhenAll(function.GetInvocationList().Cast<Func<TEventType, CancellationToken, Task>>()
|
||||||
|
.Select(x => RunHandler(x, eventArgType, token)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task RunHandler<TEventType>(Func<TEventType, CancellationToken, Task> handler,
|
||||||
|
TEventType eventArgType, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (token == CancellationToken.None)
|
||||||
|
{
|
||||||
|
// special case to allow tasks like request after delay to run longer
|
||||||
|
await handler(eventArgType, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var timeoutToken = new CancellationTokenSource(Utilities.DefaultCommandTimeout);
|
||||||
|
using var tokenSource =
|
||||||
|
CancellationTokenSource.CreateLinkedTokenSource(token, timeoutToken.Token);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await handler(eventArgType, tokenSource.Token);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,5 +4,5 @@ namespace SharedLibraryCore.Events.Game;
|
|||||||
|
|
||||||
public abstract class GameEventV2 : GameEvent
|
public abstract class GameEventV2 : GameEvent
|
||||||
{
|
{
|
||||||
public IGameServer Server { get; init; }
|
public IGameServer Server => Owner;
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,11 @@ using System.Threading.Tasks;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Serilog.Context;
|
using Serilog.Context;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Events;
|
||||||
|
|
||||||
namespace SharedLibraryCore
|
namespace SharedLibraryCore
|
||||||
{
|
{
|
||||||
public class GameEvent
|
public class GameEvent : CoreEvent
|
||||||
{
|
{
|
||||||
public enum EventFailReason
|
public enum EventFailReason
|
||||||
{
|
{
|
||||||
@ -133,6 +134,8 @@ namespace SharedLibraryCore
|
|||||||
/// connection was restored to a server (the server began responding again)
|
/// connection was restored to a server (the server began responding again)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ConnectionRestored,
|
ConnectionRestored,
|
||||||
|
|
||||||
|
SayTeam = 99,
|
||||||
|
|
||||||
// events "generated" by clients
|
// events "generated" by clients
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -246,7 +249,7 @@ namespace SharedLibraryCore
|
|||||||
/// team info printed out by game script
|
/// team info printed out by game script
|
||||||
/// </summary>
|
/// </summary>
|
||||||
JoinTeam = 304,
|
JoinTeam = 304,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// used for community generated plugin events
|
/// used for community generated plugin events
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -267,7 +270,7 @@ namespace SharedLibraryCore
|
|||||||
public GameEvent()
|
public GameEvent()
|
||||||
{
|
{
|
||||||
Time = DateTime.UtcNow;
|
Time = DateTime.UtcNow;
|
||||||
Id = GetNextEventId();
|
IncrementalId = GetNextEventId();
|
||||||
}
|
}
|
||||||
|
|
||||||
~GameEvent()
|
~GameEvent()
|
||||||
@ -275,8 +278,6 @@ namespace SharedLibraryCore
|
|||||||
_eventFinishedWaiter.Dispose();
|
_eventFinishedWaiter.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventSource Source { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// suptype of the event for more detailed classification
|
/// suptype of the event for more detailed classification
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -293,11 +294,10 @@ namespace SharedLibraryCore
|
|||||||
public bool IsRemote { get; set; }
|
public bool IsRemote { get; set; }
|
||||||
public object Extra { get; set; }
|
public object Extra { get; set; }
|
||||||
public DateTime Time { get; set; }
|
public DateTime Time { get; set; }
|
||||||
public long Id { get; }
|
public long IncrementalId { get; }
|
||||||
public EventFailReason FailReason { get; set; }
|
public EventFailReason FailReason { get; set; }
|
||||||
public bool Failed => FailReason != EventFailReason.None;
|
public bool Failed => FailReason != EventFailReason.None;
|
||||||
public Guid CorrelationId { get; set; } = Guid.NewGuid();
|
public List<string> Output { get; set; } = new();
|
||||||
public List<string> Output { get; set; } = new List<string>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the event should block until it is complete
|
/// Indicates if the event should block until it is complete
|
||||||
@ -328,23 +328,31 @@ namespace SharedLibraryCore
|
|||||||
/// <returns>waitable task </returns>
|
/// <returns>waitable task </returns>
|
||||||
public async Task<GameEvent> WaitAsync(TimeSpan timeSpan, CancellationToken token)
|
public async Task<GameEvent> WaitAsync(TimeSpan timeSpan, CancellationToken token)
|
||||||
{
|
{
|
||||||
var processed = false;
|
if (FailReason == EventFailReason.Timeout)
|
||||||
Utilities.DefaultLogger.LogDebug("Begin wait for event {Id}", Id);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
processed = await _eventFinishedWaiter.WaitAsync(timeSpan, token);
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (TaskCanceledException)
|
var processed = false;
|
||||||
|
Utilities.DefaultLogger.LogDebug("Begin wait for {Name}, {Type}, {Id}", Type, GetType().Name,
|
||||||
|
IncrementalId);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
await _eventFinishedWaiter.WaitAsync(timeSpan, token);
|
||||||
processed = true;
|
processed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
processed = false;
|
||||||
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (_eventFinishedWaiter.CurrentCount == 0)
|
if (processed)
|
||||||
{
|
{
|
||||||
_eventFinishedWaiter.Release();
|
Complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Events.Management;
|
||||||
|
|
||||||
|
public class ClientPersistentIdReceiveEvent : ClientStateEvent
|
||||||
|
{
|
||||||
|
public ClientPersistentIdReceiveEvent(EFClient client, string persistentId)
|
||||||
|
{
|
||||||
|
Client = client;
|
||||||
|
PersistentId = persistentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PersistentId { get; init; }
|
||||||
|
}
|
120
SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs
Normal file
120
SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using SharedLibraryCore.Events;
|
||||||
|
using SharedLibraryCore.Events.Game;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces.Events;
|
||||||
|
|
||||||
|
public interface IGameEventSubscriptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when game log prints that match has started
|
||||||
|
/// <example>InitGame</example>
|
||||||
|
/// <value><see cref="MatchStartEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<MatchStartEvent, CancellationToken, Task> MatchStarted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when game log prints that match has ended
|
||||||
|
/// <example>ShutdownGame:</example>
|
||||||
|
/// <value><see cref="MatchEndEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<MatchEndEvent, CancellationToken, Task> MatchEnded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when game log printed that client has entered the match
|
||||||
|
/// <remarks>J;clientNetworkId;clientSlotNumber;clientName</remarks>
|
||||||
|
/// <example>J;110000100000000;0;bot</example>
|
||||||
|
/// <value><see cref="ClientEnterMatchEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
public static event Func<ClientEnterMatchEvent, CancellationToken, Task> ClientEnteredMatch;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when game log prints that client has exited the match
|
||||||
|
/// <remarks>Q;clientNetworkId;clientSlotNumber;clientName</remarks>
|
||||||
|
/// <example>Q;110000100000000;0;bot</example>
|
||||||
|
/// <value><see cref="ClientExitMatchEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientExitMatchEvent, CancellationToken, Task> ClientExitedMatch;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when game log prints that client has joined a team
|
||||||
|
/// <remarks>JT;clientNetworkId;clientSlotNumber;clientTeam;clientName</remarks>
|
||||||
|
/// <example>JT;110000100000000;0;axis;bot</example>
|
||||||
|
/// <value><see cref="ClientJoinTeamEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientJoinTeamEvent, CancellationToken, Task> ClientJoinedTeam;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when game log prints that client has been damaged
|
||||||
|
/// <remarks>D;victimNetworkId;victimSlotNumber;victimTeam;victimName;attackerNetworkId;attackerSlotNumber;attackerTeam;attackerName;weapon;damage;meansOfDeath;hitLocation</remarks>
|
||||||
|
/// <example>D;110000100000000;17;axis;bot_0;110000100000001;4;allies;bot_1;scar_mp;38;MOD_HEAD_SHOT;head</example>
|
||||||
|
/// <value><see cref="ClientDamageEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientDamageEvent, CancellationToken, Task> ClientDamaged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when game log prints that client has been killed
|
||||||
|
/// <remarks>K;victimNetworkId;victimSlotNumber;victimTeam;victimName;attackerNetworkId;attackerSlotNumber;attackerTeam;attackerName;weapon;damage;meansOfDeath;hitLocation</remarks>
|
||||||
|
/// <example>K;110000100000000;17;axis;bot_0;110000100000001;4;allies;bot_1;scar_mp;100;MOD_HEAD_SHOT;head</example>
|
||||||
|
/// <value><see cref="ClientKillEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientKillEvent, CancellationToken, Task> ClientKilled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when game log prints that client entered a chat message
|
||||||
|
/// <remarks>say;clientNetworkId;clientSlotNumber;clientName;message</remarks>
|
||||||
|
/// <example>say;110000100000000;0;bot;hello world!</example>
|
||||||
|
/// <value><see cref="ClientMessageEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientMessageEvent, CancellationToken, Task> ClientMessaged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when game log prints that client entered a command (chat message prefixed with command character(s))
|
||||||
|
/// <remarks>say;clientNetworkId;clientSlotNumber;clientName;command</remarks>
|
||||||
|
/// <example>say;110000100000000;0;bot;!command</example>
|
||||||
|
/// <value><see cref="ClientCommandEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientCommandEvent, CancellationToken, Task> ClientEnteredCommand;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when game log prints user generated script event
|
||||||
|
/// <remarks>GSE;data</remarks>
|
||||||
|
/// <example>GSE;loadBank=1</example>
|
||||||
|
/// <value><see cref="GameScriptEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<GameScriptEvent, CancellationToken, Task> ScriptEventTriggered;
|
||||||
|
|
||||||
|
static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token)
|
||||||
|
{
|
||||||
|
return coreEvent switch
|
||||||
|
{
|
||||||
|
MatchStartEvent matchStartEvent => MatchStarted?.InvokeAsync(matchStartEvent, token) ?? Task.CompletedTask,
|
||||||
|
MatchEndEvent matchEndEvent => MatchEnded?.InvokeAsync(matchEndEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientEnterMatchEvent clientEnterMatchEvent => ClientEnteredMatch?.InvokeAsync(clientEnterMatchEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientExitMatchEvent clientExitMatchEvent => ClientExitedMatch?.InvokeAsync(clientExitMatchEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientJoinTeamEvent clientJoinTeamEvent => ClientJoinedTeam?.InvokeAsync(clientJoinTeamEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientKillEvent clientKillEvent => ClientKilled?.InvokeAsync(clientKillEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientDamageEvent clientDamageEvent => ClientDamaged?.InvokeAsync(clientDamageEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientCommandEvent clientCommandEvent => ClientEnteredCommand?.InvokeAsync(clientCommandEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientMessageEvent clientMessageEvent => ClientMessaged?.InvokeAsync(clientMessageEvent, token) ?? Task.CompletedTask,
|
||||||
|
GameScriptEvent gameScriptEvent => ScriptEventTriggered?.InvokeAsync(gameScriptEvent, token) ?? Task.CompletedTask,
|
||||||
|
_ => Task.CompletedTask
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ClearEventInvocations()
|
||||||
|
{
|
||||||
|
MatchStarted = null;
|
||||||
|
MatchEnded = null;
|
||||||
|
ClientEnteredMatch = null;
|
||||||
|
ClientExitedMatch = null;
|
||||||
|
ClientJoinedTeam = null;
|
||||||
|
ClientDamaged = null;
|
||||||
|
ClientKilled = null;
|
||||||
|
ClientMessaged = null;
|
||||||
|
ClientEnteredCommand = null;
|
||||||
|
ScriptEventTriggered = null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using SharedLibraryCore.Events;
|
||||||
|
using SharedLibraryCore.Events.Server;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces.Events;
|
||||||
|
|
||||||
|
public interface IGameServerEventSubscriptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when IW4MAdmin starts monitoring a game server
|
||||||
|
/// <value><see cref="MonitorStartEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<MonitorStartEvent, CancellationToken, Task> MonitoringStarted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when IW4MAdmin stops monitoring a game server
|
||||||
|
/// <value><see cref="MonitorStopEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<MonitorStopEvent, CancellationToken, Task> MonitoringStopped;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when communication was interrupted with a game server
|
||||||
|
/// <value><see cref="ConnectionInterruptEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ConnectionInterruptEvent, CancellationToken, Task> ConnectionInterrupted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when communication was resumed with a game server
|
||||||
|
/// <value><see cref="ConnectionRestoreEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ConnectionRestoreEvent, CancellationToken, Task> ConnectionRestored;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when updated client data was received from a game server
|
||||||
|
/// <value><see cref="ClientDataUpdateEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientDataUpdateEvent, CancellationToken, Task> ClientDataUpdated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a command was executed on a game server
|
||||||
|
/// <value><see cref="ServerCommandExecuteEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ServerCommandExecuteEvent, CancellationToken, Task> ServerCommandExecuted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a server value is requested for a game server
|
||||||
|
/// <value><see cref="ServerValueRequestEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ServerValueRequestEvent, CancellationToken, Task> ServerValueRequested;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a server value was received from a game server (success or fail)
|
||||||
|
/// <value><see cref="ServerValueReceiveEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ServerValueReceiveEvent, CancellationToken, Task> ServerValueReceived;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a request to set a server value on a game server is received
|
||||||
|
/// <value><see cref="ServerValueSetRequestEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ServerValueSetRequestEvent, CancellationToken, Task> ServerValueSetRequested;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a setting server value on a game server is completed (success or fail)
|
||||||
|
/// <value><see cref="ServerValueSetRequestEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ServerValueSetCompleteEvent, CancellationToken, Task> ServerValueSetCompleted;
|
||||||
|
|
||||||
|
static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token)
|
||||||
|
{
|
||||||
|
return coreEvent switch
|
||||||
|
{
|
||||||
|
MonitorStartEvent monitoringStartEvent => MonitoringStarted?.InvokeAsync(monitoringStartEvent, token) ?? Task.CompletedTask,
|
||||||
|
MonitorStopEvent monitorStopEvent => MonitoringStopped?.InvokeAsync(monitorStopEvent, token) ?? Task.CompletedTask,
|
||||||
|
ConnectionInterruptEvent connectionInterruptEvent => ConnectionInterrupted?.InvokeAsync(connectionInterruptEvent, token) ?? Task.CompletedTask,
|
||||||
|
ConnectionRestoreEvent connectionRestoreEvent => ConnectionRestored?.InvokeAsync(connectionRestoreEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientDataUpdateEvent clientDataUpdateEvent => ClientDataUpdated?.InvokeAsync(clientDataUpdateEvent, token) ?? Task.CompletedTask,
|
||||||
|
ServerCommandExecuteEvent dataReceiveEvent => ServerCommandExecuted?.InvokeAsync(dataReceiveEvent, token) ?? Task.CompletedTask,
|
||||||
|
ServerValueRequestEvent serverValueRequestEvent => ServerValueRequested?.InvokeAsync(serverValueRequestEvent, token) ?? Task.CompletedTask,
|
||||||
|
ServerValueReceiveEvent serverValueReceiveEvent => ServerValueReceived?.InvokeAsync(serverValueReceiveEvent, token) ?? Task.CompletedTask,
|
||||||
|
ServerValueSetRequestEvent serverValueSetRequestEvent => ServerValueSetRequested?.InvokeAsync(serverValueSetRequestEvent, token) ?? Task.CompletedTask,
|
||||||
|
ServerValueSetCompleteEvent serverValueSetCompleteEvent => ServerValueSetCompleted?.InvokeAsync(serverValueSetCompleteEvent, token) ?? Task.CompletedTask,
|
||||||
|
_ => Task.CompletedTask
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ClearEventInvocations()
|
||||||
|
{
|
||||||
|
MonitoringStarted = null;
|
||||||
|
MonitoringStopped = null;
|
||||||
|
ConnectionInterrupted = null;
|
||||||
|
ConnectionRestored = null;
|
||||||
|
ClientDataUpdated = null;
|
||||||
|
ServerCommandExecuted = null;
|
||||||
|
ServerValueReceived = null;
|
||||||
|
ServerValueRequested = null;
|
||||||
|
ServerValueSetRequested = null;
|
||||||
|
ServerValueSetCompleted = null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using SharedLibraryCore.Events;
|
||||||
|
using SharedLibraryCore.Events.Management;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces.Events;
|
||||||
|
|
||||||
|
public interface IManagementEventSubscriptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when <see cref="IManager"/> is loading
|
||||||
|
/// </summary>
|
||||||
|
static event Func<IManager, CancellationToken, Task> Load;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when <see cref="IManager"/> is restarting
|
||||||
|
/// </summary>
|
||||||
|
static event Func<IManager, CancellationToken, Task> Unload;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when client enters a tracked state
|
||||||
|
/// <remarks>
|
||||||
|
/// At this point, the client is not guaranteed to be allowed to play on the server.
|
||||||
|
/// See <see cref="ClientStateAuthorized"/> for final state.
|
||||||
|
/// </remarks>
|
||||||
|
/// <value><see cref="ClientStateInitializeEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientStateInitializeEvent, CancellationToken, Task> ClientStateInitialized;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when client enters an authorized state (valid data and no bans)
|
||||||
|
/// <value><see cref="ClientStateAuthorizeEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientStateAuthorizeEvent, CancellationToken, Task> ClientStateAuthorized;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when client is no longer tracked (unknown state)
|
||||||
|
/// <remarks>At this point any references to the client should be dropped</remarks>
|
||||||
|
/// <value><see cref="ClientStateDisposeEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientStateDisposeEvent, CancellationToken, Task> ClientStateDisposed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a client receives a penalty
|
||||||
|
/// <value><see cref="ClientPenaltyEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientPenaltyEvent, CancellationToken, Task> ClientPenaltyAdministered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a client penalty is revoked (eg unflag/unban)
|
||||||
|
/// <value><see cref="ClientPenaltyRevokeEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientPenaltyRevokeEvent, CancellationToken, Task> ClientPenaltyRevoked;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a client command is executed (after completion of the command)
|
||||||
|
/// <value><see cref="ClientExecuteCommandEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientExecuteCommandEvent, CancellationToken, Task> ClientCommandExecuted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a client's permission level changes
|
||||||
|
/// <value><see cref="ClientPermissionChangeEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientPermissionChangeEvent, CancellationToken, Task> ClientPermissionChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a client logs in to the webfront or ingame
|
||||||
|
/// <value><see cref="LoginEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<LoginEvent, CancellationToken, Task> ClientLoggedIn;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a client logs out of the webfront
|
||||||
|
/// <value><see cref="LogoutEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<LogoutEvent, CancellationToken, Task> ClientLoggedOut;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a client's persistent id (stats file marker) is received
|
||||||
|
/// <value><see cref="ClientPersistentIdReceiveEvent"/></value>
|
||||||
|
/// </summary>
|
||||||
|
static event Func<ClientPersistentIdReceiveEvent, CancellationToken, Task> ClientPersistentIdReceived;
|
||||||
|
|
||||||
|
static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token)
|
||||||
|
{
|
||||||
|
return coreEvent switch
|
||||||
|
{
|
||||||
|
ClientStateInitializeEvent clientStateInitializeEvent => ClientStateInitialized?.InvokeAsync(
|
||||||
|
clientStateInitializeEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientStateDisposeEvent clientStateDisposedEvent => ClientStateDisposed?.InvokeAsync(
|
||||||
|
clientStateDisposedEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientStateAuthorizeEvent clientStateAuthorizeEvent => ClientStateAuthorized?.InvokeAsync(
|
||||||
|
clientStateAuthorizeEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientPenaltyRevokeEvent clientPenaltyRevokeEvent => ClientPenaltyRevoked?.InvokeAsync(
|
||||||
|
clientPenaltyRevokeEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientPenaltyEvent clientPenaltyEvent =>
|
||||||
|
ClientPenaltyAdministered?.InvokeAsync(clientPenaltyEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientPermissionChangeEvent clientPermissionChangeEvent => ClientPermissionChanged?.InvokeAsync(
|
||||||
|
clientPermissionChangeEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientExecuteCommandEvent clientExecuteCommandEvent => ClientCommandExecuted?.InvokeAsync(
|
||||||
|
clientExecuteCommandEvent, token) ?? Task.CompletedTask,
|
||||||
|
LogoutEvent logoutEvent => ClientLoggedOut?.InvokeAsync(logoutEvent, token) ?? Task.CompletedTask,
|
||||||
|
LoginEvent loginEvent => ClientLoggedIn?.InvokeAsync(loginEvent, token) ?? Task.CompletedTask,
|
||||||
|
ClientPersistentIdReceiveEvent clientPersistentIdReceiveEvent => ClientPersistentIdReceived?.InvokeAsync(
|
||||||
|
clientPersistentIdReceiveEvent, token) ?? Task.CompletedTask,
|
||||||
|
_ => Task.CompletedTask
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Task InvokeLoadAsync(IManager manager, CancellationToken token) => Load?.InvokeAsync(manager, token) ?? Task.CompletedTask;
|
||||||
|
static Task InvokeUnloadAsync(IManager manager, CancellationToken token) => Unload?.InvokeAsync(manager, token) ?? Task.CompletedTask;
|
||||||
|
|
||||||
|
static void ClearEventInvocations()
|
||||||
|
{
|
||||||
|
Load = null;
|
||||||
|
Unload = null;
|
||||||
|
ClientStateInitialized = null;
|
||||||
|
ClientStateAuthorized = null;
|
||||||
|
ClientStateDisposed = null;
|
||||||
|
ClientPenaltyAdministered = null;
|
||||||
|
ClientPenaltyRevoked = null;
|
||||||
|
ClientCommandExecuted = null;
|
||||||
|
ClientPermissionChanged = null;
|
||||||
|
ClientLoggedIn = null;
|
||||||
|
ClientLoggedOut = null;
|
||||||
|
ClientPersistentIdReceived = null;
|
||||||
|
}
|
||||||
|
}
|
19
SharedLibraryCore/Interfaces/ICoreEventHandler.cs
Normal file
19
SharedLibraryCore/Interfaces/ICoreEventHandler.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using SharedLibraryCore.Events;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles games events (from log, manual events, etc)
|
||||||
|
/// </summary>
|
||||||
|
public interface ICoreEventHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Add a core event event to the queue to be processed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager"><see cref="IManager"/></param>
|
||||||
|
/// <param name="coreEvent"><see cref="CoreEvent"/></param>
|
||||||
|
void QueueEvent(IManager manager, CoreEvent coreEvent);
|
||||||
|
|
||||||
|
void StartProcessing(CancellationToken token);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models;
|
using Data.Models;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
@ -16,7 +17,70 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="previousPenalty">previous penalty the kick is occuring for (if applicable)</param>
|
/// <param name="previousPenalty">previous penalty the kick is occuring for (if applicable)</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task Kick(string reason, EFClient target, EFClient origin, EFPenalty previousPenalty = null);
|
Task Kick(string reason, EFClient target, EFClient origin, EFPenalty previousPenalty = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time the most recent match ended
|
||||||
|
/// </summary>
|
||||||
DateTime? MatchEndTime { get; }
|
DateTime? MatchEndTime { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time the current match started
|
||||||
|
/// </summary>
|
||||||
DateTime? MatchStartTime { get; }
|
DateTime? MatchStartTime { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of connected clients
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyList<EFClient> ConnectedClients { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Game code corresponding to the development studio project
|
||||||
|
/// </summary>
|
||||||
|
Reference.Game GameCode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the anticheat/custom callbacks/live radar integration is enabled
|
||||||
|
/// </summary>
|
||||||
|
bool IsLegacyGameIntegrationEnabled { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unique identifier for the server (typically ip:port)
|
||||||
|
/// </summary>
|
||||||
|
string Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Network address the server is listening on
|
||||||
|
/// </summary>
|
||||||
|
string ListenAddress { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Network port the server is listening on
|
||||||
|
/// </summary>
|
||||||
|
int ListenPort { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the server (hostname)
|
||||||
|
/// </summary>
|
||||||
|
string ServerName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current gametype
|
||||||
|
/// </summary>
|
||||||
|
string Gametype { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Game password (required to join)
|
||||||
|
/// </summary>
|
||||||
|
string GamePassword { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current map the game server is running
|
||||||
|
/// </summary>
|
||||||
|
Map Map { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Database id for EFServer table and references
|
||||||
|
/// </summary>
|
||||||
|
long LegacyDatabaseId { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Events;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Services;
|
using SharedLibraryCore.Services;
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
Task Init();
|
Task Init();
|
||||||
Task Start();
|
Task Start();
|
||||||
Task Stop();
|
Task Stop();
|
||||||
void Restart();
|
Task Restart();
|
||||||
|
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
ILogger GetLogger(long serverId);
|
ILogger GetLogger(long serverId);
|
||||||
@ -87,6 +88,11 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="gameEvent">event to be processed</param>
|
/// <param name="gameEvent">event to be processed</param>
|
||||||
void AddEvent(GameEvent gameEvent);
|
void AddEvent(GameEvent gameEvent);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// queues an event for processing
|
||||||
|
/// </summary>
|
||||||
|
void QueueEvent(CoreEvent coreEvent);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// adds an additional (script) command to the command list
|
/// adds an additional (script) command to the command list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
11
SharedLibraryCore/Interfaces/IModularAssembly.cs
Normal file
11
SharedLibraryCore/Interfaces/IModularAssembly.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
public interface IModularAssembly
|
||||||
|
{
|
||||||
|
string Name { get; }
|
||||||
|
string Author { get; }
|
||||||
|
string Version { get; }
|
||||||
|
string Scope => string.Empty;
|
||||||
|
string Role => string.Empty;
|
||||||
|
string[] Claims => System.Array.Empty<string>();
|
||||||
|
}
|
@ -55,8 +55,6 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default);
|
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default);
|
||||||
|
|
||||||
void BeginGetDvar(IRConConnection connection, string dvarName, AsyncCallback callback, CancellationToken token = default);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// set value of DVAR by name
|
/// set value of DVAR by name
|
||||||
@ -67,9 +65,7 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default);
|
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default);
|
||||||
|
|
||||||
void BeginSetDvar(IRConConnection connection, string dvarName, object dvarValue, AsyncCallback callback, CancellationToken token = default);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// executes a console command on the server
|
/// executes a console command on the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
|||||||
using Data.Models;
|
using Data.Models;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Serilog.Context;
|
using Serilog.Context;
|
||||||
|
using SharedLibraryCore.Events.Management;
|
||||||
using SharedLibraryCore.Localization;
|
using SharedLibraryCore.Localization;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Database.Models
|
namespace SharedLibraryCore.Database.Models
|
||||||
@ -603,7 +604,13 @@ namespace SharedLibraryCore.Database.Models
|
|||||||
LastConnection = DateTime.UtcNow;
|
LastConnection = DateTime.UtcNow;
|
||||||
|
|
||||||
Utilities.DefaultLogger.LogInformation("Client {client} is leaving the game", ToString());
|
Utilities.DefaultLogger.LogInformation("Client {client} is leaving the game", ToString());
|
||||||
|
|
||||||
|
CurrentServer?.Manager.QueueEvent(new ClientStateDisposeEvent
|
||||||
|
{
|
||||||
|
Source = CurrentServer,
|
||||||
|
Client = this
|
||||||
|
});
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await CurrentServer.Manager.GetClientService().Update(this);
|
await CurrentServer.Manager.GetClientService().Update(this);
|
||||||
@ -658,6 +665,11 @@ namespace SharedLibraryCore.Database.Models
|
|||||||
};
|
};
|
||||||
|
|
||||||
CurrentServer.Manager.AddEvent(e);
|
CurrentServer.Manager.AddEvent(e);
|
||||||
|
CurrentServer.Manager.QueueEvent(new ClientStateAuthorizeEvent
|
||||||
|
{
|
||||||
|
Source = CurrentServer,
|
||||||
|
Client = this
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -63,7 +64,7 @@ namespace SharedLibraryCore
|
|||||||
{
|
{
|
||||||
Password = config.Password;
|
Password = config.Password;
|
||||||
IP = config.IPAddress;
|
IP = config.IPAddress;
|
||||||
Port = config.Port;
|
ListenPort = config.Port;
|
||||||
Manager = mgr;
|
Manager = mgr;
|
||||||
#pragma warning disable CS0612
|
#pragma warning disable CS0612
|
||||||
Logger = deprecatedLogger ?? throw new ArgumentNullException(nameof(deprecatedLogger));
|
Logger = deprecatedLogger ?? throw new ArgumentNullException(nameof(deprecatedLogger));
|
||||||
@ -89,8 +90,6 @@ namespace SharedLibraryCore
|
|||||||
? Convert.ToInt64($"{ListenAddress!.Replace(".", "")}{ListenPort}")
|
? Convert.ToInt64($"{ListenAddress!.Replace(".", "")}{ListenPort}")
|
||||||
: $"{ListenAddress!.Replace(".", "")}{ListenPort}".GetStableHashCode();
|
: $"{ListenAddress!.Replace(".", "")}{ListenPort}".GetStableHashCode();
|
||||||
|
|
||||||
public long LegacyEndpoint => EndPoint;
|
|
||||||
|
|
||||||
public abstract long LegacyDatabaseId { get; }
|
public abstract long LegacyDatabaseId { get; }
|
||||||
public string Id => $"{ListenAddress}:{ListenPort}";
|
public string Id => $"{ListenAddress}:{ListenPort}";
|
||||||
|
|
||||||
@ -105,6 +104,7 @@ namespace SharedLibraryCore
|
|||||||
public List<ChatInfo> ChatHistory { get; protected set; }
|
public List<ChatInfo> ChatHistory { get; protected set; }
|
||||||
public ClientHistoryInfo ClientHistory { get; }
|
public ClientHistoryInfo ClientHistory { get; }
|
||||||
public Game GameName { get; set; }
|
public Game GameName { get; set; }
|
||||||
|
public Reference.Game GameCode => (Reference.Game)GameName;
|
||||||
public DateTime? MatchEndTime { get; protected set; }
|
public DateTime? MatchEndTime { get; protected set; }
|
||||||
public DateTime? MatchStartTime { get; protected set; }
|
public DateTime? MatchStartTime { get; protected set; }
|
||||||
|
|
||||||
@ -114,14 +114,17 @@ namespace SharedLibraryCore
|
|||||||
protected set => hostname = value;
|
protected set => hostname = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ServerName => Hostname;
|
||||||
|
|
||||||
public string Website { get; protected set; }
|
public string Website { get; protected set; }
|
||||||
public string Gametype { get; set; }
|
public string Gametype { get; set; }
|
||||||
|
|
||||||
public string GametypeName => DefaultSettings.Gametypes.FirstOrDefault(gt => gt.Game == GameName)?.Gametypes
|
public string GametypeName => DefaultSettings.Gametypes?.FirstOrDefault(gt => gt.Game == GameName)?.Gametypes
|
||||||
?.FirstOrDefault(gt => gt.Name == Gametype)?.Alias ?? Gametype;
|
?.FirstOrDefault(gt => gt.Name == Gametype)?.Alias ?? Gametype;
|
||||||
|
|
||||||
public string GamePassword { get; protected set; }
|
public string GamePassword { get; protected set; }
|
||||||
public Map CurrentMap { get; set; }
|
public Map CurrentMap { get; set; }
|
||||||
|
public Map Map => CurrentMap;
|
||||||
|
|
||||||
public int ClientNum
|
public int ClientNum
|
||||||
{
|
{
|
||||||
@ -130,9 +133,13 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
public int MaxClients { get; protected set; }
|
public int MaxClients { get; protected set; }
|
||||||
public List<EFClient> Clients { get; protected set; }
|
public List<EFClient> Clients { get; protected set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<EFClient> ConnectedClients =>
|
||||||
|
new ReadOnlyCollection<EFClient>(GetClientsAsList());
|
||||||
public string Password { get; }
|
public string Password { get; }
|
||||||
public bool Throttled { get; protected set; }
|
public bool Throttled { get; protected set; }
|
||||||
public bool CustomCallback { get; protected set; }
|
public bool CustomCallback { get; protected set; }
|
||||||
|
public bool IsLegacyGameIntegrationEnabled => CustomCallback;
|
||||||
public string WorkingDirectory { get; protected set; }
|
public string WorkingDirectory { get; protected set; }
|
||||||
public IRConConnection RemoteConnection { get; protected set; }
|
public IRConConnection RemoteConnection { get; protected set; }
|
||||||
public IRConParser RconParser { get; set; }
|
public IRConParser RconParser { get; set; }
|
||||||
@ -143,7 +150,7 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
// Internal
|
// Internal
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// this is actually the hostname now
|
/// this is actually the listen address now
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string IP { get; protected set; }
|
public string IP { get; protected set; }
|
||||||
|
|
||||||
@ -153,7 +160,7 @@ namespace SharedLibraryCore
|
|||||||
public string Version { get; protected set; }
|
public string Version { get; protected set; }
|
||||||
public bool IsInitialized { get; set; }
|
public bool IsInitialized { get; set; }
|
||||||
|
|
||||||
public int Port { get; }
|
public int ListenPort { get; }
|
||||||
public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty);
|
public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -416,35 +423,6 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
public abstract Task<long> GetIdForServer(Server server = null);
|
public abstract Task<long> GetIdForServer(Server server = null);
|
||||||
|
|
||||||
public string GetServerDvar(string dvarName, int timeoutMs = 1000)
|
|
||||||
{
|
|
||||||
using var tokenSource = new CancellationTokenSource();
|
|
||||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeoutMs));
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return this.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SetServerDvar(string dvarName, string dvarValue, int timeoutMs = 1000)
|
|
||||||
{
|
|
||||||
using var tokenSource = new CancellationTokenSource();
|
|
||||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeoutMs));
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EFClient GetClientByNumber(int clientNumber) =>
|
public EFClient GetClientByNumber(int clientNumber) =>
|
||||||
GetClientsAsList().FirstOrDefault(client => client.ClientNumber == clientNumber);
|
GetClientsAsList().FirstOrDefault(client => client.ClientNumber == clientNumber);
|
||||||
}
|
}
|
||||||
|
@ -4,22 +4,22 @@
|
|||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||||
<Version>2022.10.13.1</Version>
|
<Version>2023.4.5.1</Version>
|
||||||
<Authors>RaidMax</Authors>
|
<Authors>RaidMax</Authors>
|
||||||
<Company>Forever None</Company>
|
<Company>Forever None</Company>
|
||||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||||
<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
|
<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
|
||||||
<LangVersion>default</LangVersion>
|
<LangVersion>Preview</LangVersion>
|
||||||
<PackageTags>IW4MAdmin</PackageTags>
|
<PackageTags>IW4MAdmin</PackageTags>
|
||||||
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin/</RepositoryUrl>
|
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin/</RepositoryUrl>
|
||||||
<PackageProjectUrl>https://www.raidmax.org/IW4MAdmin/</PackageProjectUrl>
|
<PackageProjectUrl>https://www.raidmax.org/IW4MAdmin/</PackageProjectUrl>
|
||||||
<Copyright>2022</Copyright>
|
<Copyright>2023</Copyright>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<Description>Shared Library for IW4MAdmin</Description>
|
<Description>Shared Library for IW4MAdmin</Description>
|
||||||
<PackageVersion>2022.10.13.1</PackageVersion>
|
<PackageVersion>2023.4.5.1</PackageVersion>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -14,10 +14,13 @@ using System.Threading.Tasks;
|
|||||||
using Data.Models;
|
using Data.Models;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Humanizer.Localisation;
|
using Humanizer.Localisation;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Dtos.Meta;
|
using SharedLibraryCore.Dtos.Meta;
|
||||||
|
using SharedLibraryCore.Events.Server;
|
||||||
|
using SharedLibraryCore.Exceptions;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using SharedLibraryCore.Localization;
|
using SharedLibraryCore.Localization;
|
||||||
@ -26,7 +29,6 @@ using static SharedLibraryCore.Server;
|
|||||||
using static Data.Models.Client.EFClient;
|
using static Data.Models.Client.EFClient;
|
||||||
using static Data.Models.EFPenalty;
|
using static Data.Models.EFPenalty;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
using RegionInfo = System.Globalization.RegionInfo;
|
|
||||||
|
|
||||||
namespace SharedLibraryCore
|
namespace SharedLibraryCore
|
||||||
{
|
{
|
||||||
@ -43,7 +45,7 @@ namespace SharedLibraryCore
|
|||||||
public static Encoding EncodingType;
|
public static Encoding EncodingType;
|
||||||
public static Layout CurrentLocalization = new Layout(new Dictionary<string, string>());
|
public static Layout CurrentLocalization = new Layout(new Dictionary<string, string>());
|
||||||
|
|
||||||
public static TimeSpan DefaultCommandTimeout { get; set; } = new(0, 0, Utilities.IsDevelopment ? 360 : 25);
|
public static TimeSpan DefaultCommandTimeout { get; set; } = new(0, 0, /*Utilities.IsDevelopment ? 360 : */25);
|
||||||
public static char[] DirectorySeparatorChars = { '\\', '/' };
|
public static char[] DirectorySeparatorChars = { '\\', '/' };
|
||||||
public static char CommandPrefix { get; set; } = '!';
|
public static char CommandPrefix { get; set; } = '!';
|
||||||
|
|
||||||
@ -66,6 +68,22 @@ namespace SharedLibraryCore
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static EFClient AsConsoleClient(this IGameServer server)
|
||||||
|
{
|
||||||
|
return new EFClient
|
||||||
|
{
|
||||||
|
ClientId = 1,
|
||||||
|
State = EFClient.ClientState.Connected,
|
||||||
|
Level = Permission.Console,
|
||||||
|
CurrentServer = server as Server,
|
||||||
|
CurrentAlias = new EFAlias
|
||||||
|
{
|
||||||
|
Name = "IW4MAdmin"
|
||||||
|
},
|
||||||
|
AdministeredPenalties = new List<EFPenalty>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// fallback id for world events
|
/// fallback id for world events
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -95,14 +113,19 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// caps client name to the specified character length - 3
|
/// caps client name to the specified character length - 3
|
||||||
/// and adds ellipses to the end of the reamining client name
|
/// and adds ellipses to the end of the remaining client name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="str">client name</param>
|
/// <param name="name">client name</param>
|
||||||
/// <param name="maxLength">max number of characters for the name</param>
|
/// <param name="maxLength">max number of characters for the name</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string CapClientName(this string str, int maxLength)
|
public static string CapClientName(this string name, int maxLength)
|
||||||
{
|
{
|
||||||
return str.Length > maxLength ? $"{str.Substring(0, maxLength - 3)}..." : str;
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
return name.Length > maxLength ? $"{name[..(maxLength - 3)]}..." : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Permission MatchPermission(string str)
|
public static Permission MatchPermission(string str)
|
||||||
@ -712,15 +735,21 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
public static Dictionary<string, string> DictionaryFromKeyValue(this string eventLine)
|
public static Dictionary<string, string> DictionaryFromKeyValue(this string eventLine)
|
||||||
{
|
{
|
||||||
var values = eventLine.Substring(1).Split('\\');
|
var values = eventLine[1..].Split('\\');
|
||||||
|
|
||||||
Dictionary<string, string> dict = null;
|
Dictionary<string, string> dict = new();
|
||||||
|
|
||||||
if (values.Length > 1)
|
if (values.Length <= 1)
|
||||||
{
|
{
|
||||||
dict = new Dictionary<string, string>();
|
return dict;
|
||||||
for (var i = values.Length % 2 == 0 ? 0 : 1; i < values.Length; i += 2)
|
}
|
||||||
|
|
||||||
|
for (var i = values.Length % 2 == 0 ? 0 : 1; i < values.Length; i += 2)
|
||||||
|
{
|
||||||
|
if (!dict.ContainsKey(values[i]))
|
||||||
|
{
|
||||||
dict.Add(values[i], values[i + 1]);
|
dict.Add(values[i], values[i + 1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dict;
|
return dict;
|
||||||
@ -771,11 +800,6 @@ namespace SharedLibraryCore
|
|||||||
{
|
{
|
||||||
return await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue, token);
|
return await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void BeginGetDvar(this Server server, string dvarName, AsyncCallback callback, CancellationToken token = default)
|
|
||||||
{
|
|
||||||
server.RconParser.BeginGetDvar(server.RemoteConnection, dvarName, callback, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName,
|
public static async Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName,
|
||||||
T fallbackValue = default)
|
T fallbackValue = default)
|
||||||
@ -807,30 +831,36 @@ namespace SharedLibraryCore
|
|||||||
return await server.GetDvarAsync(mappedKey, defaultValue, token: token);
|
return await server.GetDvarAsync(mappedKey, defaultValue, token: token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue, CancellationToken token = default)
|
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue,
|
||||||
|
CancellationToken token)
|
||||||
{
|
{
|
||||||
await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue, token);
|
await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void BeginSetDvar(this Server server, string dvarName, object dvarValue,
|
|
||||||
AsyncCallback callback, CancellationToken token = default)
|
|
||||||
{
|
|
||||||
server.RconParser.BeginSetDvar(server.RemoteConnection, dvarName, dvarValue, callback, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
|
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
|
||||||
{
|
{
|
||||||
await SetDvarAsync(server, dvarName, dvarValue, default);
|
await SetDvarAsync(server, dvarName, dvarValue, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName, CancellationToken token = default)
|
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName,
|
||||||
|
CancellationToken token)
|
||||||
{
|
{
|
||||||
return await server.RconParser.ExecuteCommandAsync(server.RemoteConnection, commandName, token);
|
var response = await server.RconParser.ExecuteCommandAsync(server.RemoteConnection, commandName, token);
|
||||||
|
|
||||||
|
server.Manager.QueueEvent(new ServerCommandExecuteEvent
|
||||||
|
{
|
||||||
|
Server = server,
|
||||||
|
Source = server,
|
||||||
|
Command = commandName,
|
||||||
|
Output = response
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName)
|
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName)
|
||||||
{
|
{
|
||||||
return await ExecuteCommandAsync(server, commandName, default);
|
return await ExecuteCommandAsync(server, commandName, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IStatusResponse> GetStatusAsync(this Server server, CancellationToken token)
|
public static async Task<IStatusResponse> GetStatusAsync(this Server server, CancellationToken token)
|
||||||
@ -1262,5 +1292,44 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
public static string MakeAbbreviation(string gameName) => string.Join("",
|
public static string MakeAbbreviation(string gameName) => string.Join("",
|
||||||
gameName.Split(' ').Select(word => char.ToUpper(word.First())).ToArray());
|
gameName.Split(' ').Select(word => char.ToUpper(word.First())).ToArray());
|
||||||
|
|
||||||
|
public static IServiceCollection AddConfiguration<TConfigurationType>(
|
||||||
|
this IServiceCollection serviceCollection, string fileName = null, TConfigurationType defaultConfig = null)
|
||||||
|
where TConfigurationType : class
|
||||||
|
{
|
||||||
|
serviceCollection.AddSingleton(serviceProvider =>
|
||||||
|
{
|
||||||
|
var configurationHandler =
|
||||||
|
serviceProvider.GetRequiredService<IConfigurationHandlerV2<TConfigurationType>>();
|
||||||
|
|
||||||
|
var configuration =
|
||||||
|
Task.Run(() => configurationHandler.Get(fileName ?? typeof(TConfigurationType).Name, defaultConfig))
|
||||||
|
.GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (typeof(TConfigurationType).GetInterface(nameof(IBaseConfiguration)) is not null &&
|
||||||
|
defaultConfig is null && configuration is null)
|
||||||
|
{
|
||||||
|
defaultConfig =
|
||||||
|
(TConfigurationType)((IBaseConfiguration)Activator.CreateInstance<TConfigurationType>())
|
||||||
|
.Generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultConfig is not null && configuration is null)
|
||||||
|
{
|
||||||
|
Task.Run(() => configurationHandler.Set(defaultConfig)).GetAwaiter().GetResult();
|
||||||
|
configuration = defaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration is null)
|
||||||
|
{
|
||||||
|
throw new ConfigurationException(
|
||||||
|
$"Could not register configuration {typeof(TConfigurationType).Name}. Configuration file does not exist and no default configuration was provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration;
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceCollection;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Authentication;
|
|||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Events.Management;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Services;
|
using SharedLibraryCore.Services;
|
||||||
using WebfrontCore.Controllers.API.Dtos;
|
using WebfrontCore.Controllers.API.Dtos;
|
||||||
@ -136,6 +137,16 @@ namespace WebfrontCore.Controllers.API
|
|||||||
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
||||||
: HttpContext.Connection.RemoteIpAddress.ToString()
|
: HttpContext.Connection.RemoteIpAddress.ToString()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Manager.QueueEvent(new LoginEvent
|
||||||
|
{
|
||||||
|
Source = this,
|
||||||
|
LoginSource = LoginEvent.LoginSourceType.Webfront,
|
||||||
|
EntityId = Client.ClientId.ToString(),
|
||||||
|
Identifier = HttpContext.Request.Headers.ContainsKey("X-Forwarded-For")
|
||||||
|
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
||||||
|
: HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@ -165,6 +176,16 @@ namespace WebfrontCore.Controllers.API
|
|||||||
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
||||||
: HttpContext.Connection.RemoteIpAddress.ToString()
|
: HttpContext.Connection.RemoteIpAddress.ToString()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Manager.QueueEvent(new LogoutEvent
|
||||||
|
{
|
||||||
|
Source = this,
|
||||||
|
LoginSource = LoginEvent.LoginSourceType.Webfront,
|
||||||
|
EntityId = Client.ClientId.ToString(),
|
||||||
|
Identifier = HttpContext.Request.Headers.ContainsKey("X-Forwarded-For")
|
||||||
|
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
||||||
|
: HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
@ -91,7 +91,7 @@ namespace WebfrontCore.Controllers.API
|
|||||||
var start = DateTime.Now;
|
var start = DateTime.Now;
|
||||||
Client.CurrentServer = foundServer;
|
Client.CurrentServer = foundServer;
|
||||||
|
|
||||||
var commandEvent = new GameEvent()
|
var commandEvent = new GameEvent
|
||||||
{
|
{
|
||||||
Type = GameEvent.EventType.Command,
|
Type = GameEvent.EventType.Command,
|
||||||
Owner = foundServer,
|
Owner = foundServer,
|
||||||
|
@ -47,4 +47,4 @@ namespace WebfrontCore.Controllers
|
|||||||
return View(info);
|
return View(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using SharedLibraryCore.Events.Management;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
|
|
||||||
namespace WebfrontCore.Controllers
|
namespace WebfrontCore.Controllers
|
||||||
@ -72,6 +73,16 @@ namespace WebfrontCore.Controllers
|
|||||||
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
||||||
: HttpContext.Connection.RemoteIpAddress?.ToString()
|
: HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Manager.QueueEvent(new LoginEvent
|
||||||
|
{
|
||||||
|
Source = this,
|
||||||
|
LoginSource = LoginEvent.LoginSourceType.Webfront,
|
||||||
|
EntityId = privilegedClient.ClientId.ToString(),
|
||||||
|
Identifier = HttpContext.Request.Headers.ContainsKey("X-Forwarded-For")
|
||||||
|
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
||||||
|
: HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
return Ok(Localization["WEBFRONT_ACTION_LOGIN_SUCCESS"].FormatExt(privilegedClient.CleanedName));
|
return Ok(Localization["WEBFRONT_ACTION_LOGIN_SUCCESS"].FormatExt(privilegedClient.CleanedName));
|
||||||
}
|
}
|
||||||
@ -99,6 +110,16 @@ namespace WebfrontCore.Controllers
|
|||||||
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
||||||
: HttpContext.Connection.RemoteIpAddress?.ToString()
|
: HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Manager.QueueEvent(new LogoutEvent
|
||||||
|
{
|
||||||
|
Source = this,
|
||||||
|
LoginSource = LoginEvent.LoginSourceType.Webfront,
|
||||||
|
EntityId = Client.ClientId.ToString(),
|
||||||
|
Identifier = HttpContext.Request.Headers.ContainsKey("X-Forwarded-For")
|
||||||
|
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
|
||||||
|
: HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
@ -7,7 +7,6 @@ using SharedLibraryCore.Interfaces;
|
|||||||
using SharedLibraryCore.QueryHelper;
|
using SharedLibraryCore.QueryHelper;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
@ -41,12 +40,12 @@ namespace WebfrontCore.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var server = Manager.GetServers().FirstOrDefault(server => server.ToString() == serverId);
|
var server = Manager.GetServers().FirstOrDefault(server => server.Id == serverId) as IGameServer;
|
||||||
long? matchedServerId = null;
|
long? matchedServerId = null;
|
||||||
|
|
||||||
if (server != null)
|
if (server != null)
|
||||||
{
|
{
|
||||||
matchedServerId = StatManager.GetIdForServer(server);
|
matchedServerId = server.LegacyDatabaseId;
|
||||||
}
|
}
|
||||||
|
|
||||||
hitInfo.TotalRankedClients = await _serverDataViewer.RankedClientsCountAsync(matchedServerId, token);
|
hitInfo.TotalRankedClients = await _serverDataViewer.RankedClientsCountAsync(matchedServerId, token);
|
||||||
|
@ -3,9 +3,8 @@ using System.IO;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Serilog;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using WebfrontCore.Middleware;
|
using WebfrontCore.Middleware;
|
||||||
|
|
||||||
@ -14,33 +13,36 @@ namespace WebfrontCore
|
|||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
public static IManager Manager;
|
public static IManager Manager;
|
||||||
public static IServiceCollection Services;
|
private static IWebHost _webHost;
|
||||||
public static IServiceProvider ApplicationServiceProvider;
|
|
||||||
|
|
||||||
public static Task Init(IManager mgr, IServiceProvider existingServiceProvider, IServiceCollection services, CancellationToken cancellationToken)
|
public static IServiceProvider InitializeServices(Action<IServiceCollection> registerDependenciesAction, string bindUrl)
|
||||||
{
|
{
|
||||||
Services = services;
|
_webHost = BuildWebHost(registerDependenciesAction, bindUrl);
|
||||||
Manager = mgr;
|
Manager = _webHost.Services.GetRequiredService<IManager>();
|
||||||
ApplicationServiceProvider = existingServiceProvider;
|
return _webHost.Services;
|
||||||
var config = Manager.GetApplicationSettings().Configuration();
|
|
||||||
Manager.MiddlewareActionHandler.Register(null, new CustomCssAccentMiddlewareAction("#007ACC", "#fd7e14", config.WebfrontPrimaryColor, config.WebfrontSecondaryColor), "custom_css_accent");
|
|
||||||
return BuildWebHost().RunAsync(cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IWebHost BuildWebHost()
|
public static Task GetWebHostTask(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var config = _webHost.Services.GetRequiredService<ApplicationConfiguration>();
|
||||||
|
Manager.MiddlewareActionHandler.Register(null,
|
||||||
|
new CustomCssAccentMiddlewareAction("#007ACC", "#fd7e14", config.WebfrontPrimaryColor,
|
||||||
|
config.WebfrontSecondaryColor), "custom_css_accent");
|
||||||
|
|
||||||
|
return _webHost?.RunAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IWebHost BuildWebHost(Action<IServiceCollection> registerDependenciesAction, string bindUrl)
|
||||||
{
|
{
|
||||||
var config = new ConfigurationBuilder()
|
|
||||||
.AddEnvironmentVariables()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
return new WebHostBuilder()
|
return new WebHostBuilder()
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
.UseContentRoot(Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\..\", "WebfrontCore")))
|
.UseContentRoot(Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\..\", "WebfrontCore")))
|
||||||
#else
|
#else
|
||||||
.UseContentRoot(SharedLibraryCore.Utilities.OperatingDirectory)
|
.UseContentRoot(SharedLibraryCore.Utilities.OperatingDirectory)
|
||||||
#endif
|
#endif
|
||||||
.UseUrls(Manager.GetApplicationSettings().Configuration().WebfrontBindUrl)
|
.UseUrls(bindUrl)
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
|
.ConfigureServices(registerDependenciesAction)
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ using System.Reflection;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Abstractions;
|
using Data.Abstractions;
|
||||||
using Data.Helpers;
|
using Data.Helpers;
|
||||||
|
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||||
using Stats.Client.Abstractions;
|
using Stats.Client.Abstractions;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
using WebfrontCore.Controllers.API.Validation;
|
using WebfrontCore.Controllers.API.Validation;
|
||||||
@ -64,23 +65,8 @@ namespace WebfrontCore
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add framework services.
|
// Add framework services.
|
||||||
var mvcBuilder = services.AddMvc(_options => _options.SuppressAsyncSuffixInActionNames = false)
|
var mvcBuilder = services.AddMvc(options => options.SuppressAsyncSuffixInActionNames = false);
|
||||||
.AddFluentValidation()
|
services.AddFluentValidationAutoValidation().AddFluentValidationClientsideAdapters();
|
||||||
.ConfigureApplicationPartManager(_partManager =>
|
|
||||||
{
|
|
||||||
foreach (var assembly in pluginAssemblies())
|
|
||||||
{
|
|
||||||
if (assembly.FullName.Contains("Views"))
|
|
||||||
{
|
|
||||||
_partManager.ApplicationParts.Add(new CompiledRazorAssemblyPart(assembly));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (assembly.FullName.Contains("Web"))
|
|
||||||
{
|
|
||||||
_partManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
{
|
{
|
||||||
@ -109,42 +95,13 @@ namespace WebfrontCore
|
|||||||
options.Events.OnSignedIn += ClaimsPermissionRemoval.OnSignedIn;
|
options.Events.OnSignedIn += ClaimsPermissionRemoval.OnSignedIn;
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddSingleton(Program.Manager);
|
|
||||||
services.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>();
|
services.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>();
|
||||||
services.AddTransient<IValidator<FindClientRequest>, FindClientRequestValidator>();
|
services.AddTransient<IValidator<FindClientRequest>, FindClientRequestValidator>();
|
||||||
services.AddSingleton<IResourceQueryHelper<FindClientRequest, FindClientResult>, ClientService>();
|
services.AddSingleton<IResourceQueryHelper<FindClientRequest, FindClientResult>, ClientService>();
|
||||||
services.AddSingleton<IResourceQueryHelper<StatsInfoRequest, StatsInfoResult>, StatsResourceQueryHelper>();
|
services.AddSingleton<IResourceQueryHelper<StatsInfoRequest, StatsInfoResult>, StatsResourceQueryHelper>();
|
||||||
services.AddSingleton<IResourceQueryHelper<StatsInfoRequest, AdvancedStatsInfo>, AdvancedClientStatsResourceQueryHelper>();
|
services.AddSingleton<IResourceQueryHelper<StatsInfoRequest, AdvancedStatsInfo>, AdvancedClientStatsResourceQueryHelper>();
|
||||||
services.AddScoped(sp =>
|
|
||||||
Program.ApplicationServiceProvider
|
|
||||||
.GetRequiredService<IResourceQueryHelper<ClientResourceRequest, ClientResourceResponse>>());
|
|
||||||
services.AddSingleton(typeof(IDataValueCache<,>), typeof(DataValueCache<,>));
|
services.AddSingleton(typeof(IDataValueCache<,>), typeof(DataValueCache<,>));
|
||||||
// todo: this needs to be handled more gracefully
|
services.AddSingleton<IResourceQueryHelper<BanInfoRequest, BanInfo>, BanInfoResourceQueryHelper>();
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<DefaultSettings>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ILoggerFactory>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IConfigurationHandlerFactory>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IDatabaseContextFactory>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IAuditInformationRepository>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ITranslationLookup>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IEnumerable<IManagerCommand>>());
|
|
||||||
#pragma warning disable CS0618
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IMetaService>());
|
|
||||||
#pragma warning restore CS0618
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IMetaServiceV2>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ApplicationConfiguration>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ClientService>());
|
|
||||||
services.AddSingleton<IResourceQueryHelper<BanInfoRequest, BanInfo>, BanInfoResourceQueryHelper>();
|
|
||||||
services.AddSingleton(
|
|
||||||
Program.ApplicationServiceProvider.GetRequiredService<IServerDistributionCalculator>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider
|
|
||||||
.GetRequiredService<IConfigurationHandler<DefaultSettings>>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider
|
|
||||||
.GetRequiredService<IGeoLocationService>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider
|
|
||||||
.GetRequiredService<StatsConfiguration>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IServerDataViewer>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IInteractionRegistration>());
|
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IRemoteCommandService>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
Loading…
Reference in New Issue
Block a user