Compare commits
45 Commits
2.3-Prerel
...
2021.01.24
Author | SHA1 | Date | |
---|---|---|---|
c4f19e94ef | |||
c419d80b57 | |||
23a33ba489 | |||
dd3ebf6b34 | |||
28373b9325 | |||
843c01061d | |||
5cb2d05f33 | |||
5a288dafc1 | |||
4afc478076 | |||
928cbef845 | |||
02b910234a | |||
f03626c3ae | |||
6648b75255 | |||
bd3f0caf60 | |||
b2d282d412 | |||
36a02b3d7b | |||
8ef2959f63 | |||
d58b24b5b2 | |||
09f37d7941 | |||
103d2726c2 | |||
941d9cea73 | |||
a574fb0d4b | |||
664eb32587 | |||
6619ce714a | |||
e997b94b3b | |||
5d9c8f5369 | |||
570a228c92 | |||
fd7bd7e0da | |||
e76976799b | |||
84189cf136 | |||
98ee997bf3 | |||
3f7372e780 | |||
08676f1d1e | |||
2bbafbd8f0 | |||
40cb2a9df6 | |||
59f1699228 | |||
1484d63b97 | |||
04217e96ee | |||
c41fc27a1a | |||
1f1f4de67a | |||
7f11921757 | |||
70cae976a0 | |||
2ab0cfa9be | |||
7e3c74e63c | |||
a4a65a486a |
2
.gitignore
vendored
2
.gitignore
vendored
@ -244,3 +244,5 @@ launchSettings.json
|
|||||||
/Tests/ApplicationTests/Files/GameEvents.json
|
/Tests/ApplicationTests/Files/GameEvents.json
|
||||||
/Tests/ApplicationTests/Files/replay.json
|
/Tests/ApplicationTests/Files/replay.json
|
||||||
/GameLogServer/game_log_server_env
|
/GameLogServer/game_log_server_env
|
||||||
|
.idea/*
|
||||||
|
*.db
|
@ -1,5 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using IW4MAdmin.Application.Misc;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using RestEase;
|
using RestEase;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
@ -35,6 +37,13 @@ namespace IW4MAdmin.Application.API.Master
|
|||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PluginSubscriptionContent
|
||||||
|
{
|
||||||
|
public string Content { get; set; }
|
||||||
|
public PluginType Type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the capabilities of the master API
|
/// Defines the capabilities of the master API
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -63,5 +72,8 @@ namespace IW4MAdmin.Application.API.Master
|
|||||||
|
|
||||||
[Get("localization/{languageTag}")]
|
[Get("localization/{languageTag}")]
|
||||||
Task<SharedLibraryCore.Localization.Layout> GetLocalization([Path("languageTag")] string languageTag);
|
Task<SharedLibraryCore.Localization.Layout> GetLocalization([Path("languageTag")] string languageTag);
|
||||||
|
|
||||||
|
[Get("plugin_subscriptions")]
|
||||||
|
Task<IEnumerable<PluginSubscriptionContent>> GetPluginSubscription([Query("instance_id")] Guid instanceId, [Query("subscription_id")] string subscription_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
||||||
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
||||||
<Version>2.3.2.0</Version>
|
<Version>2020.0.0.0</Version>
|
||||||
<Authors>RaidMax</Authors>
|
<Authors>RaidMax</Authors>
|
||||||
<Company>Forever None</Company>
|
<Company>Forever None</Company>
|
||||||
<Product>IW4MAdmin</Product>
|
<Product>IW4MAdmin</Product>
|
||||||
@ -25,13 +25,13 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Jint" Version="3.0.0-beta-1632" />
|
<PackageReference Include="Jint" Version="3.0.0-beta-1632" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.10">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.7" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.10" />
|
||||||
<PackageReference Include="RestEase" Version="1.5.0" />
|
<PackageReference Include="RestEase" Version="1.5.1" />
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -39,7 +39,6 @@
|
|||||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||||
<TieredCompilation>true</TieredCompilation>
|
<TieredCompilation>true</TieredCompilation>
|
||||||
<LangVersion>Latest</LangVersion>
|
<LangVersion>Latest</LangVersion>
|
||||||
<StartupObject></StartupObject>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
||||||
@ -59,6 +58,9 @@
|
|||||||
<None Update="DefaultSettings.json">
|
<None Update="DefaultSettings.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Configuration\LoggingConfiguration.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using IW4MAdmin.Application.API.Master;
|
using IW4MAdmin.Application.EventParsers;
|
||||||
using IW4MAdmin.Application.EventParsers;
|
|
||||||
using IW4MAdmin.Application.Extensions;
|
using IW4MAdmin.Application.Extensions;
|
||||||
using IW4MAdmin.Application.Misc;
|
using IW4MAdmin.Application.Misc;
|
||||||
using IW4MAdmin.Application.RconParsers;
|
using IW4MAdmin.Application.RconParsers;
|
||||||
@ -9,11 +8,9 @@ using SharedLibraryCore.Configuration;
|
|||||||
using SharedLibraryCore.Configuration.Validation;
|
using SharedLibraryCore.Configuration.Validation;
|
||||||
using SharedLibraryCore.Database;
|
using SharedLibraryCore.Database;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Dtos;
|
|
||||||
using SharedLibraryCore.Exceptions;
|
using SharedLibraryCore.Exceptions;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using SharedLibraryCore.QueryHelper;
|
|
||||||
using SharedLibraryCore.Services;
|
using SharedLibraryCore.Services;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
@ -24,7 +21,13 @@ using System.Reflection;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using IW4MAdmin.Application.Migration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog.Context;
|
||||||
using static SharedLibraryCore.GameEvent;
|
using static SharedLibraryCore.GameEvent;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
using ObsoleteLogger = SharedLibraryCore.Interfaces.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application
|
namespace IW4MAdmin.Application
|
||||||
{
|
{
|
||||||
@ -32,7 +35,7 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
private readonly ConcurrentBag<Server> _servers;
|
private readonly ConcurrentBag<Server> _servers;
|
||||||
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
|
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
|
||||||
public ILogger Logger => GetLogger(0);
|
[Obsolete] public ObsoleteLogger Logger => _serviceProvider.GetRequiredService<ObsoleteLogger>();
|
||||||
public bool IsRunning { get; private set; }
|
public bool IsRunning { get; private set; }
|
||||||
public bool IsInitialized { get; private set; }
|
public bool IsInitialized { get; private set; }
|
||||||
public DateTime StartTime { get; private set; }
|
public DateTime StartTime { get; private set; }
|
||||||
@ -50,11 +53,9 @@ namespace IW4MAdmin.Application
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly List<MessageToken> MessageTokens;
|
private readonly List<MessageToken> MessageTokens;
|
||||||
private readonly ClientService ClientSvc;
|
private readonly ClientService ClientSvc;
|
||||||
readonly AliasService AliasSvc;
|
|
||||||
readonly PenaltyService PenaltySvc;
|
readonly PenaltyService PenaltySvc;
|
||||||
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
|
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
|
||||||
readonly IPageList PageList;
|
readonly IPageList PageList;
|
||||||
private readonly Dictionary<long, ILogger> _loggers = new Dictionary<long, ILogger>();
|
|
||||||
private readonly IMetaService _metaService;
|
private readonly IMetaService _metaService;
|
||||||
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
|
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
|
||||||
private readonly CancellationTokenSource _tokenSource;
|
private readonly CancellationTokenSource _tokenSource;
|
||||||
@ -67,30 +68,34 @@ namespace IW4MAdmin.Application
|
|||||||
private readonly IEventHandler _eventHandler;
|
private readonly IEventHandler _eventHandler;
|
||||||
private readonly IScriptCommandFactory _scriptCommandFactory;
|
private readonly IScriptCommandFactory _scriptCommandFactory;
|
||||||
private readonly IMetaRegistration _metaRegistration;
|
private readonly IMetaRegistration _metaRegistration;
|
||||||
|
private readonly IScriptPluginServiceResolver _scriptPluginServiceResolver;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly ChangeHistoryService _changeHistoryService;
|
||||||
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
public ConcurrentDictionary<long, GameEvent> ProcessingEvents { get; } = new ConcurrentDictionary<long, GameEvent>();
|
||||||
|
|
||||||
public ApplicationManager(ILogger 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, IMetaService metaService,
|
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IMetaService metaService,
|
||||||
IMetaRegistration metaRegistration)
|
IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider,
|
||||||
|
ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService)
|
||||||
{
|
{
|
||||||
MiddlewareActionHandler = actionHandler;
|
MiddlewareActionHandler = actionHandler;
|
||||||
_servers = new ConcurrentBag<Server>();
|
_servers = new ConcurrentBag<Server>();
|
||||||
MessageTokens = new List<MessageToken>();
|
MessageTokens = new List<MessageToken>();
|
||||||
ClientSvc = new ClientService(contextFactory);
|
ClientSvc = clientService;
|
||||||
AliasSvc = new AliasService();
|
PenaltySvc = penaltyService;
|
||||||
PenaltySvc = new PenaltyService();
|
|
||||||
ConfigHandler = appConfigHandler;
|
ConfigHandler = appConfigHandler;
|
||||||
StartTime = DateTime.UtcNow;
|
StartTime = DateTime.UtcNow;
|
||||||
PageList = new PageList();
|
PageList = new PageList();
|
||||||
AdditionalEventParsers = new List<IEventParser>() { new BaseEventParser(parserRegexFactory, logger, appConfigHandler.Configuration()) };
|
AdditionalEventParsers = new List<IEventParser>() { new BaseEventParser(parserRegexFactory, logger, _appConfig) };
|
||||||
AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser(parserRegexFactory) };
|
AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) };
|
||||||
TokenAuthenticator = new TokenAuthentication();
|
TokenAuthenticator = new TokenAuthentication();
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_metaService = metaService;
|
_metaService = metaService;
|
||||||
_tokenSource = new CancellationTokenSource();
|
_tokenSource = new CancellationTokenSource();
|
||||||
_loggers.Add(0, logger);
|
|
||||||
_commands = commands.ToList();
|
_commands = commands.ToList();
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
_commandConfiguration = commandConfiguration;
|
_commandConfiguration = commandConfiguration;
|
||||||
@ -100,6 +105,10 @@ namespace IW4MAdmin.Application
|
|||||||
_eventHandler = eventHandler;
|
_eventHandler = eventHandler;
|
||||||
_scriptCommandFactory = scriptCommandFactory;
|
_scriptCommandFactory = scriptCommandFactory;
|
||||||
_metaRegistration = metaRegistration;
|
_metaRegistration = metaRegistration;
|
||||||
|
_scriptPluginServiceResolver = scriptPluginServiceResolver;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_changeHistoryService = changeHistoryService;
|
||||||
|
_appConfig = appConfig;
|
||||||
Plugins = plugins;
|
Plugins = plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,9 +116,7 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
public async Task ExecuteEvent(GameEvent newEvent)
|
public async Task ExecuteEvent(GameEvent newEvent)
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
ProcessingEvents.TryAdd(newEvent.Id, newEvent);
|
||||||
Logger.WriteDebug($"Entering event process for {newEvent.Id}");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// the event has failed already
|
// the event has failed already
|
||||||
if (newEvent.Failed)
|
if (newEvent.Failed)
|
||||||
@ -122,22 +129,17 @@ namespace IW4MAdmin.Application
|
|||||||
await newEvent.Owner.ExecuteEvent(newEvent);
|
await newEvent.Owner.ExecuteEvent(newEvent);
|
||||||
|
|
||||||
// save the event info to the database
|
// save the event info to the database
|
||||||
var changeHistorySvc = new ChangeHistoryService();
|
await _changeHistoryService.Add(newEvent);
|
||||||
await changeHistorySvc.Add(newEvent);
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
Logger.WriteDebug($"Processed event with id {newEvent.Id}");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
Logger.WriteInfo($"Received quit signal for event id {newEvent.Id}, so we are aborting early");
|
_logger.LogDebug("Received quit signal for event id {eventId}, so we are aborting early", newEvent.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
Logger.WriteInfo($"Received quit signal for event id {newEvent.Id}, so we are aborting early");
|
_logger.LogDebug("Received quit signal for event id {eventId}, so we are aborting early", newEvent.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this happens if a plugin requires login
|
// this happens if a plugin requires login
|
||||||
@ -150,31 +152,58 @@ namespace IW4MAdmin.Application
|
|||||||
catch (NetworkException ex)
|
catch (NetworkException ex)
|
||||||
{
|
{
|
||||||
newEvent.FailReason = EventFailReason.Exception;
|
newEvent.FailReason = EventFailReason.Exception;
|
||||||
Logger.WriteError(ex.Message);
|
using (LogContext.PushProperty("Server", newEvent.Owner?.ToString()))
|
||||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
{
|
||||||
|
_logger.LogError(ex, ex.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (ServerException ex)
|
catch (ServerException ex)
|
||||||
{
|
{
|
||||||
newEvent.FailReason = EventFailReason.Exception;
|
newEvent.FailReason = EventFailReason.Exception;
|
||||||
Logger.WriteWarning(ex.Message);
|
using (LogContext.PushProperty("Server", newEvent.Owner?.ToString()))
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, ex.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
newEvent.FailReason = EventFailReason.Exception;
|
newEvent.FailReason = EventFailReason.Exception;
|
||||||
Logger.WriteError(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"].FormatExt(newEvent.Owner));
|
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"].FormatExt(newEvent.Owner));
|
||||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
using (LogContext.PushProperty("Server", newEvent.Owner?.ToString()))
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Unexpected exception");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
skip:
|
skip:
|
||||||
|
if (newEvent.Type == EventType.Command && newEvent.ImpersonationOrigin == null)
|
||||||
|
{
|
||||||
|
var correlatedEvents =
|
||||||
|
ProcessingEvents.Values.Where(ev =>
|
||||||
|
ev.CorrelationId == newEvent.CorrelationId && ev.Id != newEvent.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
await Task.WhenAll(correlatedEvents.Select(ev =>
|
||||||
|
ev.WaitAsync(Utilities.DefaultCommandTimeout, CancellationToken)));
|
||||||
|
newEvent.Output.AddRange(correlatedEvents.SelectMany(ev => ev.Output));
|
||||||
|
|
||||||
|
foreach (var correlatedEvent in correlatedEvents)
|
||||||
|
{
|
||||||
|
ProcessingEvents.Remove(correlatedEvent.Id, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't want to remove events that are correlated to command
|
||||||
|
if (ProcessingEvents.Values.ToList()?.Count(gameEvent => gameEvent.CorrelationId == newEvent.CorrelationId) == 1)
|
||||||
|
{
|
||||||
|
ProcessingEvents.Remove(newEvent.Id, out _);
|
||||||
|
}
|
||||||
|
|
||||||
// tell anyone waiting for the output that we're done
|
// tell anyone waiting for the output that we're done
|
||||||
newEvent.Complete();
|
newEvent.Complete();
|
||||||
OnGameEventExecuted?.Invoke(this, newEvent);
|
OnGameEventExecuted?.Invoke(this, newEvent);
|
||||||
|
|
||||||
#if DEBUG == true
|
|
||||||
Logger.WriteDebug($"Exiting event process for {newEvent.Id}");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<Server> GetServers()
|
public IList<Server> GetServers()
|
||||||
@ -224,17 +253,14 @@ namespace IW4MAdmin.Application
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await server.ProcessUpdatesAsync(_tokenSource.Token);
|
await server.ProcessUpdatesAsync(_tokenSource.Token);
|
||||||
|
|
||||||
if (server.Throttled)
|
|
||||||
{
|
|
||||||
await Task.Delay((int)_throttleTimeout.TotalMilliseconds, _tokenSource.Token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning($"Failed to update status for {server}");
|
using (LogContext.PushProperty("Server", server.ToString()))
|
||||||
Logger.WriteDebug(e.GetExceptionInfo());
|
{
|
||||||
|
_logger.LogError(e, "Failed to update status");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
@ -243,12 +269,7 @@ namespace IW4MAdmin.Application
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
#if DEBUG
|
|
||||||
Logger.WriteDebug($"{runningUpdateTasks.Count} servers queued for stats updates");
|
|
||||||
ThreadPool.GetMaxThreads(out int workerThreads, out int n);
|
|
||||||
ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
|
|
||||||
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
|
|
||||||
#endif
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(ConfigHandler.Configuration().RConPollRate, _tokenSource.Token);
|
await Task.Delay(ConfigHandler.Configuration().RConPollRate, _tokenSource.Token);
|
||||||
@ -277,18 +298,18 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
if (plugin is ScriptPlugin scriptPlugin)
|
if (plugin is ScriptPlugin scriptPlugin)
|
||||||
{
|
{
|
||||||
await scriptPlugin.Initialize(this, _scriptCommandFactory);
|
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver);
|
||||||
scriptPlugin.Watcher.Changed += async (sender, e) =>
|
scriptPlugin.Watcher.Changed += async (sender, e) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await scriptPlugin.Initialize(this, _scriptCommandFactory);
|
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.WriteError(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"].FormatExt(scriptPlugin.Name));
|
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"].FormatExt(scriptPlugin.Name));
|
||||||
Logger.WriteDebug(ex.Message);
|
_logger.LogError(ex, "Could not properly load plugin {plugin}", scriptPlugin.Name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -301,32 +322,29 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.WriteError($"{_translationLookup["SERVER_ERROR_PLUGIN"]} {plugin.Name}");
|
_logger.LogError(ex, $"{_translationLookup["SERVER_ERROR_PLUGIN"]} {plugin.Name}");
|
||||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region CONFIG
|
#region CONFIG
|
||||||
var config = ConfigHandler.Configuration();
|
|
||||||
|
|
||||||
// copy over default config if it doesn't exist
|
// copy over default config if it doesn't exist
|
||||||
if (config == null)
|
if (!_appConfig.Servers?.Any() ?? true)
|
||||||
{
|
{
|
||||||
var defaultConfig = new BaseConfigurationHandler<DefaultConfiguration>("DefaultSettings").Configuration();
|
var defaultConfig = new BaseConfigurationHandler<DefaultConfiguration>("DefaultSettings").Configuration();
|
||||||
ConfigHandler.Set((ApplicationConfiguration)new ApplicationConfiguration().Generate());
|
//ConfigHandler.Set((ApplicationConfiguration)new ApplicationConfiguration().Generate());
|
||||||
var newConfig = ConfigHandler.Configuration();
|
//var newConfig = ConfigHandler.Configuration();
|
||||||
|
|
||||||
newConfig.AutoMessages = defaultConfig.AutoMessages;
|
_appConfig.AutoMessages = defaultConfig.AutoMessages;
|
||||||
newConfig.GlobalRules = defaultConfig.GlobalRules;
|
_appConfig.GlobalRules = defaultConfig.GlobalRules;
|
||||||
newConfig.Maps = defaultConfig.Maps;
|
_appConfig.Maps = defaultConfig.Maps;
|
||||||
newConfig.DisallowedClientNames = defaultConfig.DisallowedClientNames;
|
_appConfig.DisallowedClientNames = defaultConfig.DisallowedClientNames;
|
||||||
newConfig.QuickMessages = defaultConfig.QuickMessages;
|
_appConfig.QuickMessages = defaultConfig.QuickMessages;
|
||||||
|
|
||||||
if (newConfig.Servers == null)
|
//if (newConfig.Servers == null)
|
||||||
{
|
{
|
||||||
ConfigHandler.Set(newConfig);
|
ConfigHandler.Set(_appConfig);
|
||||||
newConfig.Servers = new ServerConfiguration[1];
|
_appConfig.Servers = new ServerConfiguration[1];
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@ -341,30 +359,29 @@ namespace IW4MAdmin.Application
|
|||||||
serverConfig.AddEventParser(parser);
|
serverConfig.AddEventParser(parser);
|
||||||
}
|
}
|
||||||
|
|
||||||
newConfig.Servers = newConfig.Servers.Where(_servers => _servers != null).Append((ServerConfiguration)serverConfig.Generate()).ToArray();
|
_appConfig.Servers = _appConfig.Servers.Where(_servers => _servers != null).Append((ServerConfiguration)serverConfig.Generate()).ToArray();
|
||||||
} while (Utilities.PromptBool(_translationLookup["SETUP_SERVER_SAVE"]));
|
} while (Utilities.PromptBool(_translationLookup["SETUP_SERVER_SAVE"]));
|
||||||
|
|
||||||
config = newConfig;
|
|
||||||
await ConfigHandler.Save();
|
await ConfigHandler.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(config.Id))
|
if (string.IsNullOrEmpty(_appConfig.Id))
|
||||||
{
|
{
|
||||||
config.Id = Guid.NewGuid().ToString();
|
_appConfig.Id = Guid.NewGuid().ToString();
|
||||||
await ConfigHandler.Save();
|
await ConfigHandler.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(config.WebfrontBindUrl))
|
if (string.IsNullOrEmpty(_appConfig.WebfrontBindUrl))
|
||||||
{
|
{
|
||||||
config.WebfrontBindUrl = "http://0.0.0.0:1624";
|
_appConfig.WebfrontBindUrl = "http://0.0.0.0:1624";
|
||||||
await ConfigHandler.Save();
|
await ConfigHandler.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
var validator = new ApplicationConfigurationValidator();
|
var validator = new ApplicationConfigurationValidator();
|
||||||
var validationResult = validator.Validate(config);
|
var validationResult = validator.Validate(_appConfig);
|
||||||
|
|
||||||
if (!validationResult.IsValid)
|
if (!validationResult.IsValid)
|
||||||
{
|
{
|
||||||
@ -375,7 +392,7 @@ namespace IW4MAdmin.Application
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var serverConfig in config.Servers)
|
foreach (var serverConfig in _appConfig.Servers)
|
||||||
{
|
{
|
||||||
Migration.ConfigurationMigration.ModifyLogPath020919(serverConfig);
|
Migration.ConfigurationMigration.ModifyLogPath020919(serverConfig);
|
||||||
|
|
||||||
@ -397,26 +414,27 @@ namespace IW4MAdmin.Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.Servers.Length == 0)
|
if (_appConfig.Servers.Length == 0)
|
||||||
{
|
{
|
||||||
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
|
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252");
|
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(_appConfig.CustomParserEncoding) ? _appConfig.CustomParserEncoding : "windows-1252");
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region DATABASE
|
#region DATABASE
|
||||||
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString,
|
_logger.LogInformation("Beginning database migration sync");
|
||||||
GetApplicationSettings().Configuration()?.DatabaseProvider))
|
Console.WriteLine(_translationLookup["MANAGER_MIGRATION_START"]);
|
||||||
{
|
await ContextSeed.Seed(_serviceProvider.GetRequiredService<IDatabaseContextFactory>(), _tokenSource.Token);
|
||||||
await new ContextSeed(db).Seed();
|
await DatabaseHousekeeping.RemoveOldRatings(_serviceProvider.GetRequiredService<IDatabaseContextFactory>(), _tokenSource.Token);
|
||||||
}
|
_logger.LogInformation("Finished database migration sync");
|
||||||
|
Console.WriteLine(_translationLookup["MANAGER_MIGRATION_END"]);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region COMMANDS
|
#region COMMANDS
|
||||||
if (ClientSvc.GetOwners().Result.Count > 0)
|
if (await ClientSvc.HasOwnerAsync(_tokenSource.Token))
|
||||||
{
|
{
|
||||||
_commands.RemoveAll(_cmd => _cmd.GetType() == typeof(OwnerCommand));
|
_commands.RemoveAll(_cmd => _cmd.GetType() == typeof(OwnerCommand));
|
||||||
}
|
}
|
||||||
@ -438,8 +456,8 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
// this is because I want to store the command prefix in IW4MAdminSettings, but can't easily
|
// this is because I want to store the command prefix in IW4MAdminSettings, but can't easily
|
||||||
// inject it to all the places that need it
|
// inject it to all the places that need it
|
||||||
cmdConfig.CommandPrefix = config.CommandPrefix;
|
cmdConfig.CommandPrefix = _appConfig.CommandPrefix;
|
||||||
cmdConfig.BroadcastCommandPrefix = config.BroadcastCommandPrefix;
|
cmdConfig.BroadcastCommandPrefix = _appConfig.BroadcastCommandPrefix;
|
||||||
|
|
||||||
foreach (var cmd in commandsToAddToConfig)
|
foreach (var cmd in commandsToAddToConfig)
|
||||||
{
|
{
|
||||||
@ -449,7 +467,8 @@ namespace IW4MAdmin.Application
|
|||||||
Name = cmd.Name,
|
Name = cmd.Name,
|
||||||
Alias = cmd.Alias,
|
Alias = cmd.Alias,
|
||||||
MinimumPermission = cmd.Permission,
|
MinimumPermission = cmd.Permission,
|
||||||
AllowImpersonation = cmd.AllowImpersonation
|
AllowImpersonation = cmd.AllowImpersonation,
|
||||||
|
SupportedGames = cmd.SupportedGames
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,6 +488,7 @@ namespace IW4MAdmin.Application
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]);
|
||||||
await InitializeServers();
|
await InitializeServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,13 +504,17 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
// 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()))
|
||||||
|
{
|
||||||
|
_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()));
|
||||||
|
_logger.LogInformation("Finishing initialization and now monitoring [{server}]", ServerInstance.Hostname, ServerInstance.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
Logger.WriteVerbose(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(ServerInstance.Hostname));
|
|
||||||
// add the start event for this server
|
// add the start event for this server
|
||||||
|
|
||||||
var e = new GameEvent()
|
var e = new GameEvent()
|
||||||
{
|
{
|
||||||
Type = GameEvent.EventType.Start,
|
Type = GameEvent.EventType.Start,
|
||||||
@ -504,13 +528,11 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
catch (ServerException e)
|
catch (ServerException e)
|
||||||
{
|
{
|
||||||
Logger.WriteError(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"].FormatExt($"[{Conf.IPAddress}:{Conf.Port}]"));
|
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"].FormatExt($"[{Conf.IPAddress}:{Conf.Port}]"));
|
||||||
|
using (LogContext.PushProperty("Server", $"{Conf.IPAddress}:{Conf.Port}"))
|
||||||
if (e.GetType() == typeof(DvarException))
|
|
||||||
{
|
{
|
||||||
Logger.WriteDebug($"{e.Message} {(e.GetType() == typeof(DvarException) ? $"({Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR_HELP"]})" : "")}");
|
_logger.LogError(e, "Unexpected exception occurred during initialization");
|
||||||
}
|
}
|
||||||
|
|
||||||
lastException = e;
|
lastException = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -545,20 +567,10 @@ namespace IW4MAdmin.Application
|
|||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILogger GetLogger(long serverId)
|
[Obsolete]
|
||||||
|
public ObsoleteLogger GetLogger(long serverId)
|
||||||
{
|
{
|
||||||
if (_loggers.ContainsKey(serverId))
|
return _serviceProvider.GetRequiredService<ObsoleteLogger>();
|
||||||
{
|
|
||||||
return _loggers[serverId];
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var newLogger = new Logger($"IW4MAdmin-Server-{serverId}");
|
|
||||||
|
|
||||||
_loggers.Add(serverId, newLogger);
|
|
||||||
return newLogger;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<MessageToken> GetMessageTokens()
|
public IList<MessageToken> GetMessageTokens()
|
||||||
@ -577,11 +589,6 @@ namespace IW4MAdmin.Application
|
|||||||
return ClientSvc;
|
return ClientSvc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AliasService GetAliasService()
|
|
||||||
{
|
|
||||||
return AliasSvc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PenaltyService GetPenaltyService()
|
public PenaltyService GetPenaltyService()
|
||||||
{
|
{
|
||||||
return PenaltySvc;
|
return PenaltySvc;
|
||||||
@ -604,7 +611,7 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
public IRConParser GenerateDynamicRConParser(string name)
|
public IRConParser GenerateDynamicRConParser(string name)
|
||||||
{
|
{
|
||||||
return new DynamicRConParser(_parserRegexFactory)
|
return new DynamicRConParser(_serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), _parserRegexFactory)
|
||||||
{
|
{
|
||||||
Name = name
|
Name = name
|
||||||
};
|
};
|
||||||
|
@ -4,11 +4,9 @@ set TargetDir=%3
|
|||||||
set OutDir=%4
|
set OutDir=%4
|
||||||
set Version=%5
|
set Version=%5
|
||||||
|
|
||||||
echo %Version% > "%SolutionDir%DEPLOY\version.txt"
|
|
||||||
|
|
||||||
echo Copying dependency configs
|
echo Copying dependency configs
|
||||||
copy "%SolutionDir%WebfrontCore\%OutDir%*.deps.json" "%TargetDir%"
|
copy "%SolutionDir%WebfrontCore\%OutDir%*.deps.json" "%TargetDir%"
|
||||||
copy "%SolutionDir%SharedLibaryCore\%OutDir%*.deps.json" "%TargetDir%"
|
copy "%SolutionDir%SharedLibraryCore\%OutDir%*.deps.json" "%TargetDir%"
|
||||||
|
|
||||||
if not exist "%TargetDir%Plugins" (
|
if not exist "%TargetDir%Plugins" (
|
||||||
echo "Making plugin dir"
|
echo "Making plugin dir"
|
||||||
@ -16,12 +14,3 @@ if not exist "%TargetDir%Plugins" (
|
|||||||
)
|
)
|
||||||
|
|
||||||
xcopy /y "%SolutionDir%Build\Plugins" "%TargetDir%Plugins\"
|
xcopy /y "%SolutionDir%Build\Plugins" "%TargetDir%Plugins\"
|
||||||
|
|
||||||
echo Copying plugins for publish
|
|
||||||
del %SolutionDir%BUILD\Plugins\Tests.dll
|
|
||||||
xcopy /Y "%SolutionDir%BUILD\Plugins" "%SolutionDir%Publish\Windows\Plugins\"
|
|
||||||
xcopy /Y "%SolutionDir%BUILD\Plugins" "%SolutionDir%Publish\WindowsPrerelease\Plugins\"
|
|
||||||
|
|
||||||
echo Copying script plugins for publish
|
|
||||||
xcopy /Y "%SolutionDir%Plugins\ScriptPlugins" "%SolutionDir%Publish\Windows\Plugins\"
|
|
||||||
xcopy /Y "%SolutionDir%Plugins\ScriptPlugins" "%SolutionDir%Publish\WindowsPrerelease\Plugins\"
|
|
43
Application/Configuration/LoggingConfiguration.json
Normal file
43
Application/Configuration/LoggingConfiguration.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"Serilog": {
|
||||||
|
"Using": [
|
||||||
|
"Serilog.Sinks.File"
|
||||||
|
],
|
||||||
|
"MinimumLevel": "Information",
|
||||||
|
"WriteTo": [
|
||||||
|
{
|
||||||
|
"Name": "File",
|
||||||
|
"Args": {
|
||||||
|
"path": "Log/IW4MAdmin-Application.log",
|
||||||
|
"rollingInterval": "Day",
|
||||||
|
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Enrich": [
|
||||||
|
"FromLogContext",
|
||||||
|
"WithMachineName",
|
||||||
|
"WithThreadId"
|
||||||
|
],
|
||||||
|
"Destructure": [
|
||||||
|
{
|
||||||
|
"Name": "ToMaximumDepth",
|
||||||
|
"Args": {
|
||||||
|
"maximumDestructuringDepth": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ToMaximumStringLength",
|
||||||
|
"Args": {
|
||||||
|
"maximumStringLength": 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ToMaximumCollectionCount",
|
||||||
|
"Args": {
|
||||||
|
"maximumCollectionCount": 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,9 @@ using SharedLibraryCore.Interfaces;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using static SharedLibraryCore.Server;
|
using static SharedLibraryCore.Server;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.EventParsers
|
namespace IW4MAdmin.Application.EventParsers
|
||||||
{
|
{
|
||||||
@ -91,16 +93,25 @@ namespace IW4MAdmin.Application.EventParsers
|
|||||||
public virtual GameEvent GenerateGameEvent(string logLine)
|
public virtual GameEvent GenerateGameEvent(string logLine)
|
||||||
{
|
{
|
||||||
var timeMatch = Configuration.Time.PatternMatcher.Match(logLine);
|
var timeMatch = Configuration.Time.PatternMatcher.Match(logLine);
|
||||||
int gameTime = 0;
|
var gameTime = 0L;
|
||||||
|
|
||||||
if (timeMatch.Success)
|
if (timeMatch.Success)
|
||||||
|
{
|
||||||
|
if (timeMatch.Values[0].Contains(":"))
|
||||||
{
|
{
|
||||||
gameTime = timeMatch
|
gameTime = timeMatch
|
||||||
.Values
|
.Values
|
||||||
.Skip(2)
|
.Skip(2)
|
||||||
// this converts the timestamp into seconds passed
|
// this converts the timestamp into seconds passed
|
||||||
.Select((_value, index) => int.Parse(_value.ToString()) * (index == 0 ? 60 : 1))
|
.Select((_value, index) => long.Parse(_value.ToString()) * (index == 0 ? 60 : 1))
|
||||||
.Sum();
|
.Sum();
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gameTime = long.Parse(timeMatch.Values[0]);
|
||||||
|
}
|
||||||
|
|
||||||
// we want to strip the time from the log line
|
// we want to strip the time from the log line
|
||||||
logLine = logLine.Substring(timeMatch.Values.First().Length);
|
logLine = logLine.Substring(timeMatch.Values.First().Length);
|
||||||
}
|
}
|
||||||
@ -255,6 +266,7 @@ namespace IW4MAdmin.Application.EventParsers
|
|||||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||||
State = EFClient.ClientState.Connecting,
|
State = EFClient.ClientState.Connecting,
|
||||||
},
|
},
|
||||||
|
Extra = originIdString,
|
||||||
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
||||||
IsBlocking = true,
|
IsBlocking = true,
|
||||||
GameTime = gameTime,
|
GameTime = gameTime,
|
||||||
@ -347,7 +359,7 @@ namespace IW4MAdmin.Application.EventParsers
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Could not handle custom event generation - {e.GetExceptionInfo()}");
|
_logger.LogError(e, $"Could not handle custom event generation");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.EventParsers
|
namespace IW4MAdmin.Application.EventParsers
|
||||||
{
|
{
|
||||||
|
101
Application/Extensions/StartupExtensions.cs
Normal file
101
Application/Extensions/StartupExtensions.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Events;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database.MigrationContext;
|
||||||
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Extensions
|
||||||
|
{
|
||||||
|
public static class StartupExtensions
|
||||||
|
{
|
||||||
|
private static ILogger _defaultLogger = null;
|
||||||
|
|
||||||
|
public static IServiceCollection AddBaseLogger(this IServiceCollection services,
|
||||||
|
ApplicationConfiguration appConfig)
|
||||||
|
{
|
||||||
|
if (_defaultLogger == null)
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddJsonFile(Path.Join(Utilities.OperatingDirectory, "Configuration", "LoggingConfiguration.json"))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var loggerConfig = new LoggerConfiguration()
|
||||||
|
.ReadFrom.Configuration(configuration)
|
||||||
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning);
|
||||||
|
|
||||||
|
|
||||||
|
if (Utilities.IsDevelopment)
|
||||||
|
{
|
||||||
|
loggerConfig = loggerConfig.WriteTo.Console(
|
||||||
|
outputTemplate:
|
||||||
|
"[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||||
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||||
|
.MinimumLevel.Debug();
|
||||||
|
}
|
||||||
|
|
||||||
|
_defaultLogger = loggerConfig.CreateLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
services.AddLogging(builder => builder.AddSerilog(_defaultLogger, dispose: true));
|
||||||
|
services.AddSingleton(new LoggerFactory()
|
||||||
|
.AddSerilog(_defaultLogger, true));
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddDatabaseContextOptions(this IServiceCollection services,
|
||||||
|
ApplicationConfiguration appConfig)
|
||||||
|
{
|
||||||
|
var activeProvider = appConfig.DatabaseProvider?.ToLower();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(appConfig.ConnectionString) || activeProvider == "sqlite")
|
||||||
|
{
|
||||||
|
var currentPath = Utilities.OperatingDirectory;
|
||||||
|
currentPath = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||||
|
? $"{Path.DirectorySeparatorChar}{currentPath}"
|
||||||
|
: currentPath;
|
||||||
|
|
||||||
|
var connectionStringBuilder = new SqliteConnectionStringBuilder
|
||||||
|
{DataSource = Path.Join(currentPath, "Database", "Database.db")};
|
||||||
|
var connectionString = connectionStringBuilder.ToString();
|
||||||
|
|
||||||
|
var builder = new DbContextOptionsBuilder<SqliteDatabaseContext>()
|
||||||
|
.UseSqlite(connectionString);
|
||||||
|
|
||||||
|
services.AddSingleton((DbContextOptions) builder.Options);
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (activeProvider)
|
||||||
|
{
|
||||||
|
case "mysql":
|
||||||
|
var appendTimeout = !appConfig.ConnectionString.Contains("default command timeout",
|
||||||
|
StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
services.AddSingleton(sp => (DbContextOptions) new DbContextOptionsBuilder<MySqlDatabaseContext>()
|
||||||
|
.UseMySql(appConfig.ConnectionString + (appendTimeout ? ";default command timeout=0" : ""),
|
||||||
|
mysqlOptions => mysqlOptions.EnableRetryOnFailure())
|
||||||
|
.UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options);
|
||||||
|
return services;
|
||||||
|
case "postgresql":
|
||||||
|
appendTimeout = !appConfig.ConnectionString.Contains("Command Timeout",
|
||||||
|
StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
services.AddSingleton(sp =>
|
||||||
|
(DbContextOptions) new DbContextOptionsBuilder<PostgresqlDatabaseContext>()
|
||||||
|
.UseNpgsql(appConfig.ConnectionString + (appendTimeout ? ";Command Timeout=0" : ""),
|
||||||
|
postgresqlOptions => postgresqlOptions.EnableRetryOnFailure())
|
||||||
|
.UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options);
|
||||||
|
return services;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException($"No context available for {appConfig.DatabaseProvider}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,8 @@
|
|||||||
using SharedLibraryCore.Database;
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database;
|
||||||
|
using SharedLibraryCore.Database.MigrationContext;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Factories
|
namespace IW4MAdmin.Application.Factories
|
||||||
@ -8,6 +12,15 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DatabaseContextFactory : IDatabaseContextFactory
|
public class DatabaseContextFactory : IDatabaseContextFactory
|
||||||
{
|
{
|
||||||
|
private readonly DbContextOptions _contextOptions;
|
||||||
|
private readonly string _activeProvider;
|
||||||
|
|
||||||
|
public DatabaseContextFactory(ApplicationConfiguration appConfig, DbContextOptions contextOptions)
|
||||||
|
{
|
||||||
|
_contextOptions = contextOptions;
|
||||||
|
_activeProvider = appConfig.DatabaseProvider?.ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// creates a new database context
|
/// creates a new database context
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -15,7 +28,35 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public DatabaseContext CreateContext(bool? enableTracking = true)
|
public DatabaseContext CreateContext(bool? enableTracking = true)
|
||||||
{
|
{
|
||||||
return enableTracking.HasValue ? new DatabaseContext(disableTracking: !enableTracking.Value) : new DatabaseContext();
|
var context = BuildContext();
|
||||||
|
|
||||||
|
enableTracking ??= true;
|
||||||
|
|
||||||
|
if (enableTracking.Value)
|
||||||
|
{
|
||||||
|
context.ChangeTracker.AutoDetectChangesEnabled = true;
|
||||||
|
context.ChangeTracker.LazyLoadingEnabled = true;
|
||||||
|
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.ChangeTracker.AutoDetectChangesEnabled = false;
|
||||||
|
context.ChangeTracker.LazyLoadingEnabled = false;
|
||||||
|
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseContext BuildContext()
|
||||||
|
{
|
||||||
|
return _activeProvider switch
|
||||||
|
{
|
||||||
|
"sqlite" => new SqliteDatabaseContext(_contextOptions),
|
||||||
|
"mysql" => new MySqlDatabaseContext(_contextOptions),
|
||||||
|
"postgresql" => new PostgresqlDatabaseContext(_contextOptions),
|
||||||
|
_ => throw new ArgumentException($"No context found for {_activeProvider}")
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System;
|
using System;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Factories
|
namespace IW4MAdmin.Application.Factories
|
||||||
{
|
{
|
||||||
@ -19,12 +20,12 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
var baseUri = logUris[0];
|
var baseUri = logUris[0];
|
||||||
if (baseUri.Scheme == Uri.UriSchemeHttp)
|
if (baseUri.Scheme == Uri.UriSchemeHttp)
|
||||||
{
|
{
|
||||||
return new GameLogReaderHttp(logUris, eventParser, _serviceProvider.GetRequiredService<ILogger>());
|
return new GameLogReaderHttp(logUris, eventParser, _serviceProvider.GetRequiredService<ILogger<GameLogReaderHttp>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (baseUri.Scheme == Uri.UriSchemeFile)
|
else if (baseUri.Scheme == Uri.UriSchemeFile)
|
||||||
{
|
{
|
||||||
return new GameLogReader(baseUri.LocalPath, eventParser, _serviceProvider.GetRequiredService<ILogger>());
|
return new GameLogReader(baseUri.LocalPath, eventParser, _serviceProvider.GetRequiredService<ILogger<GameLogReader>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotImplementedException($"No log reader implemented for Uri scheme \"{baseUri.Scheme}\"");
|
throw new NotImplementedException($"No log reader implemented for Uri scheme \"{baseUri.Scheme}\"");
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
using SharedLibraryCore;
|
using System;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System.Collections;
|
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Factories
|
namespace IW4MAdmin.Application.Factories
|
||||||
{
|
{
|
||||||
@ -11,21 +12,21 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
internal class GameServerInstanceFactory : IGameServerInstanceFactory
|
internal class GameServerInstanceFactory : IGameServerInstanceFactory
|
||||||
{
|
{
|
||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
private readonly IRConConnectionFactory _rconConnectionFactory;
|
|
||||||
private readonly IGameLogReaderFactory _gameLogReaderFactory;
|
|
||||||
private readonly IMetaService _metaService;
|
private readonly IMetaService _metaService;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// base constructor
|
/// base constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="translationLookup"></param>
|
/// <param name="translationLookup"></param>
|
||||||
/// <param name="rconConnectionFactory"></param>
|
/// <param name="rconConnectionFactory"></param>
|
||||||
public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory, IMetaService metaService)
|
public GameServerInstanceFactory(ITranslationLookup translationLookup,
|
||||||
|
IMetaService metaService,
|
||||||
|
IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
_rconConnectionFactory = rconConnectionFactory;
|
|
||||||
_gameLogReaderFactory = gameLogReaderFactory;
|
|
||||||
_metaService = metaService;
|
_metaService = metaService;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,7 +37,7 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Server CreateServer(ServerConfiguration config, IManager manager)
|
public Server CreateServer(ServerConfiguration config, IManager manager)
|
||||||
{
|
{
|
||||||
return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory, _gameLogReaderFactory, _metaService);
|
return new IW4MServer(config, _translationLookup, _metaService, _serviceProvider, _serviceProvider.GetRequiredService<IClientNoticeMessageFormatter>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using IW4MAdmin.Application.RCon;
|
using IW4MAdmin.Application.RCon;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Factories
|
namespace IW4MAdmin.Application.Factories
|
||||||
{
|
{
|
||||||
@ -10,13 +11,13 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
internal class RConConnectionFactory : IRConConnectionFactory
|
internal class RConConnectionFactory : IRConConnectionFactory
|
||||||
{
|
{
|
||||||
private static readonly Encoding gameEncoding = Encoding.GetEncoding("windows-1252");
|
private static readonly Encoding gameEncoding = Encoding.GetEncoding("windows-1252");
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<RConConnection> _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base constructor
|
/// Base constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger"></param>
|
/// <param name="logger"></param>
|
||||||
public RConConnectionFactory(ILogger logger)
|
public RConConnectionFactory(ILogger<RConConnection> logger)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ using SharedLibraryCore.Interfaces;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using static SharedLibraryCore.Database.Models.EFClient;
|
using static SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Factories
|
namespace IW4MAdmin.Application.Factories
|
||||||
@ -15,17 +17,20 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ScriptCommandFactory : IScriptCommandFactory
|
public class ScriptCommandFactory : IScriptCommandFactory
|
||||||
{
|
{
|
||||||
private CommandConfiguration _config;
|
private readonly CommandConfiguration _config;
|
||||||
private readonly ITranslationLookup _transLookup;
|
private readonly ITranslationLookup _transLookup;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
public ScriptCommandFactory(CommandConfiguration config, ITranslationLookup transLookup)
|
public ScriptCommandFactory(CommandConfiguration config, ITranslationLookup transLookup, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_transLookup = transLookup;
|
_transLookup = transLookup;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction)
|
public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission,
|
||||||
|
bool isTargetRequired, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction)
|
||||||
{
|
{
|
||||||
var permissionEnum = Enum.Parse<Permission>(permission);
|
var permissionEnum = Enum.Parse<Permission>(permission);
|
||||||
var argsArray = args.Select(_arg => new CommandArgument
|
var argsArray = args.Select(_arg => new CommandArgument
|
||||||
@ -34,7 +39,8 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
Required = _arg.Item2
|
Required = _arg.Item2
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
return new ScriptCommand(name, alias, description, permissionEnum, argsArray, executeAction, _config, _transLookup);
|
return new ScriptCommand(name, alias, description, isTargetRequired, permissionEnum, argsArray, executeAction,
|
||||||
|
_config, _transLookup, _serviceProvider.GetRequiredService<ILogger<ScriptCommand>>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
using IW4MAdmin.Application.Misc;
|
using IW4MAdmin.Application.Misc;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Events;
|
using SharedLibraryCore.Events;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application
|
namespace IW4MAdmin.Application
|
||||||
{
|
{
|
||||||
public class GameEventHandler : IEventHandler
|
public class GameEventHandler : IEventHandler
|
||||||
{
|
{
|
||||||
private readonly EventLog _eventLog;
|
private readonly EventLog _eventLog;
|
||||||
|
private readonly ILogger _logger;
|
||||||
private static readonly GameEvent.EventType[] overrideEvents = new[]
|
private static readonly GameEvent.EventType[] overrideEvents = new[]
|
||||||
{
|
{
|
||||||
GameEvent.EventType.Connect,
|
GameEvent.EventType.Connect,
|
||||||
@ -22,34 +21,23 @@ namespace IW4MAdmin.Application
|
|||||||
GameEvent.EventType.Stop
|
GameEvent.EventType.Stop
|
||||||
};
|
};
|
||||||
|
|
||||||
public GameEventHandler()
|
public GameEventHandler(ILogger<GameEventHandler> logger)
|
||||||
{
|
{
|
||||||
_eventLog = new EventLog();
|
_eventLog = new EventLog();
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleEvent(IManager manager, GameEvent gameEvent)
|
public void HandleEvent(IManager manager, GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
|
||||||
ThreadPool.GetMaxThreads(out int workerThreads, out int n);
|
|
||||||
ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
|
|
||||||
gameEvent.Owner.Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
|
|
||||||
|
|
||||||
#endif
|
|
||||||
if (manager.IsRunning || overrideEvents.Contains(gameEvent.Type))
|
if (manager.IsRunning || overrideEvents.Contains(gameEvent.Type))
|
||||||
{
|
{
|
||||||
#if DEBUG
|
|
||||||
gameEvent.Owner.Logger.WriteDebug($"Adding event with id {gameEvent.Id}");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
EventApi.OnGameEvent(gameEvent);
|
EventApi.OnGameEvent(gameEvent);
|
||||||
Task.Factory.StartNew(() => manager.ExecuteEvent(gameEvent));
|
Task.Factory.StartNew(() => manager.ExecuteEvent(gameEvent));
|
||||||
}
|
}
|
||||||
#if DEBUG
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
gameEvent.Owner.Logger.WriteDebug($"Skipping event as we're shutting down {gameEvent.Id}");
|
_logger.LogDebug("Skipping event as we're shutting down {eventId}", gameEvent.Id);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@ using SharedLibraryCore.Interfaces;
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog.Context;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.IO
|
namespace IW4MAdmin.Application.IO
|
||||||
{
|
{
|
||||||
@ -12,12 +15,14 @@ namespace IW4MAdmin.Application.IO
|
|||||||
private readonly Server _server;
|
private readonly Server _server;
|
||||||
private readonly IGameLogReader _reader;
|
private readonly IGameLogReader _reader;
|
||||||
private readonly bool _ignoreBots;
|
private readonly bool _ignoreBots;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public GameLogEventDetection(Server server, Uri[] gameLogUris, IGameLogReaderFactory gameLogReaderFactory)
|
public GameLogEventDetection(ILogger<GameLogEventDetection> logger, IW4MServer server, Uri[] gameLogUris, IGameLogReaderFactory gameLogReaderFactory)
|
||||||
{
|
{
|
||||||
_reader = gameLogReaderFactory.CreateGameLogReader(gameLogUris, server.EventParser);
|
_reader = gameLogReaderFactory.CreateGameLogReader(gameLogUris, server.EventParser);
|
||||||
_server = server;
|
_server = server;
|
||||||
_ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false;
|
_ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PollForChanges()
|
public async Task PollForChanges()
|
||||||
@ -33,15 +38,17 @@ namespace IW4MAdmin.Application.IO
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_server.Logger.WriteWarning($"Failed to update log event for {_server.EndPoint}");
|
using(LogContext.PushProperty("Server", _server.ToString()))
|
||||||
_server.Logger.WriteDebug(e.GetExceptionInfo());
|
{
|
||||||
|
_logger.LogError(e, "Failed to update log event for {endpoint}", _server.EndPoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(_reader.UpdateInterval, _server.Manager.CancellationToken);
|
await Task.Delay(_reader.UpdateInterval, _server.Manager.CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
_server.Logger.WriteDebug("Stopped polling for changes");
|
_logger.LogDebug("Stopped polling for changes");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateLogEvents()
|
public async Task UpdateLogEvents()
|
||||||
@ -68,9 +75,6 @@ namespace IW4MAdmin.Application.IO
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#if DEBUG
|
|
||||||
_server.Logger.WriteVerbose(gameEvent.Data);
|
|
||||||
#endif
|
|
||||||
gameEvent.Owner = _server;
|
gameEvent.Owner = _server;
|
||||||
|
|
||||||
// we don't want to add the event if ignoreBots is on and the event comes from a bot
|
// we don't want to add the event if ignoreBots is on and the event comes from a bot
|
||||||
@ -102,10 +106,14 @@ namespace IW4MAdmin.Application.IO
|
|||||||
|
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
{
|
{
|
||||||
if (!_ignoreBots)
|
if (_ignoreBots)
|
||||||
{
|
{
|
||||||
_server.Logger.WriteWarning("Could not find client in client list when parsing event line");
|
continue;
|
||||||
_server.Logger.WriteDebug(gameEvent.Data);
|
}
|
||||||
|
|
||||||
|
using(LogContext.PushProperty("Server", _server.ToString()))
|
||||||
|
{
|
||||||
|
_logger.LogError("Could not find client in client list when parsing event line {data}", gameEvent.Data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.IO
|
namespace IW4MAdmin.Application.IO
|
||||||
{
|
{
|
||||||
@ -19,7 +21,7 @@ namespace IW4MAdmin.Application.IO
|
|||||||
|
|
||||||
public int UpdateInterval => 300;
|
public int UpdateInterval => 300;
|
||||||
|
|
||||||
public GameLogReader(string logFile, IEventParser parser, ILogger logger)
|
public GameLogReader(string logFile, IEventParser parser, ILogger<GameLogReader> logger)
|
||||||
{
|
{
|
||||||
_logFile = logFile;
|
_logFile = logFile;
|
||||||
_parser = parser;
|
_parser = parser;
|
||||||
@ -73,9 +75,7 @@ namespace IW4MAdmin.Application.IO
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning("Could not properly parse event line");
|
_logger.LogError(e, "Could not properly parse event line {@eventLine}", eventLine);
|
||||||
_logger.WriteDebug(e.Message);
|
|
||||||
_logger.WriteDebug(eventLine);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.IO
|
namespace IW4MAdmin.Application.IO
|
||||||
{
|
{
|
||||||
@ -20,7 +22,7 @@ namespace IW4MAdmin.Application.IO
|
|||||||
private readonly string _safeLogPath;
|
private readonly string _safeLogPath;
|
||||||
private string lastKey = "next";
|
private string lastKey = "next";
|
||||||
|
|
||||||
public GameLogReaderHttp(Uri[] gameLogServerUris, IEventParser parser, ILogger logger)
|
public GameLogReaderHttp(Uri[] gameLogServerUris, IEventParser parser, ILogger<GameLogReaderHttp> logger)
|
||||||
{
|
{
|
||||||
_eventParser = parser;
|
_eventParser = parser;
|
||||||
_logServerApi = RestClient.For<IGameLogServer>(gameLogServerUris[0].ToString());
|
_logServerApi = RestClient.For<IGameLogServer>(gameLogServerUris[0].ToString());
|
||||||
@ -40,7 +42,7 @@ namespace IW4MAdmin.Application.IO
|
|||||||
|
|
||||||
if (!response.Success && string.IsNullOrEmpty(lastKey))
|
if (!response.Success && string.IsNullOrEmpty(lastKey))
|
||||||
{
|
{
|
||||||
_logger.WriteError($"Could not get log server info of {_safeLogPath}");
|
_logger.LogError("Could not get log server info of {logPath}", _safeLogPath);
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,9 +64,7 @@ namespace IW4MAdmin.Application.IO
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.WriteError("Could not properly parse event line from http");
|
_logger.LogError(e, "Could not properly parse event line from http {eventLine}", eventLine);
|
||||||
_logger.WriteDebug(e.Message);
|
|
||||||
_logger.WriteDebug(eventLine);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,12 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog.Context;
|
||||||
using static SharedLibraryCore.Database.Models.EFClient;
|
using static SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
|
||||||
namespace IW4MAdmin
|
namespace IW4MAdmin
|
||||||
@ -27,40 +29,51 @@ namespace IW4MAdmin
|
|||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
private readonly IMetaService _metaService;
|
private readonly IMetaService _metaService;
|
||||||
private const int REPORT_FLAG_COUNT = 4;
|
private const int REPORT_FLAG_COUNT = 4;
|
||||||
private int lastGameTime = 0;
|
private long lastGameTime = 0;
|
||||||
|
|
||||||
public int Id { get; private set; }
|
public int Id { get; private set; }
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly IClientNoticeMessageFormatter _messageFormatter;
|
||||||
|
|
||||||
public IW4MServer(IManager mgr, ServerConfiguration cfg, ITranslationLookup lookup,
|
public IW4MServer(
|
||||||
IRConConnectionFactory connectionFactory, IGameLogReaderFactory gameLogReaderFactory, IMetaService metaService) : base(cfg, mgr, connectionFactory, gameLogReaderFactory)
|
ServerConfiguration serverConfiguration,
|
||||||
|
ITranslationLookup lookup,
|
||||||
|
IMetaService metaService,
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
IClientNoticeMessageFormatter messageFormatter) : base(serviceProvider.GetRequiredService<ILogger<Server>>(),
|
||||||
|
serviceProvider.GetRequiredService<SharedLibraryCore.Interfaces.ILogger>(),
|
||||||
|
serverConfiguration,
|
||||||
|
serviceProvider.GetRequiredService<IManager>(),
|
||||||
|
serviceProvider.GetRequiredService<IRConConnectionFactory>(),
|
||||||
|
serviceProvider.GetRequiredService<IGameLogReaderFactory>())
|
||||||
{
|
{
|
||||||
_translationLookup = lookup;
|
_translationLookup = lookup;
|
||||||
_metaService = metaService;
|
_metaService = metaService;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_messageFormatter = messageFormatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
override public async Task<EFClient> OnClientConnected(EFClient clientFromLog)
|
public override async Task<EFClient> OnClientConnected(EFClient clientFromLog)
|
||||||
{
|
{
|
||||||
Logger.WriteDebug($"Client slot #{clientFromLog.ClientNumber} now reserved");
|
ServerLogger.LogDebug("Client slot #{clientNumber} now reserved", clientFromLog.ClientNumber);
|
||||||
|
|
||||||
EFClient client = await Manager.GetClientService().GetUnique(clientFromLog.NetworkId);
|
EFClient client = await Manager.GetClientService().GetUnique(clientFromLog.NetworkId);
|
||||||
|
|
||||||
// first time client is connecting to server
|
// first time client is connecting to server
|
||||||
if (client == null)
|
if (client == null)
|
||||||
{
|
{
|
||||||
Logger.WriteDebug($"Client {clientFromLog} first time connecting");
|
ServerLogger.LogDebug("Client {client} first time connecting", clientFromLog.ToString());
|
||||||
clientFromLog.CurrentServer = this;
|
clientFromLog.CurrentServer = this;
|
||||||
client = await Manager.GetClientService().Create(clientFromLog);
|
client = await Manager.GetClientService().Create(clientFromLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// this is only a temporary version until the IPAddress is transmitted
|
// this is only a temporary version until the IPAddress is transmitted
|
||||||
client.CurrentAlias = new EFAlias()
|
client.CurrentAlias = new EFAlias()
|
||||||
{
|
{
|
||||||
Name = clientFromLog.Name,
|
Name = clientFromLog.Name,
|
||||||
IPAddress = clientFromLog.IPAddress
|
IPAddress = clientFromLog.IPAddress
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.WriteInfo($"Client {client} connected...");
|
|
||||||
|
|
||||||
// Do the player specific stuff
|
// Do the player specific stuff
|
||||||
client.ClientNumber = clientFromLog.ClientNumber;
|
client.ClientNumber = clientFromLog.ClientNumber;
|
||||||
client.Score = clientFromLog.Score;
|
client.Score = clientFromLog.Score;
|
||||||
@ -69,9 +82,7 @@ namespace IW4MAdmin
|
|||||||
client.State = ClientState.Connecting;
|
client.State = ClientState.Connecting;
|
||||||
|
|
||||||
Clients[client.ClientNumber] = client;
|
Clients[client.ClientNumber] = client;
|
||||||
#if DEBUG == true
|
ServerLogger.LogDebug("End PreConnect for {client}", client.ToString());
|
||||||
Logger.WriteDebug($"End PreConnect for {client}");
|
|
||||||
#endif
|
|
||||||
var e = new GameEvent()
|
var e = new GameEvent()
|
||||||
{
|
{
|
||||||
Origin = client,
|
Origin = client,
|
||||||
@ -83,11 +94,14 @@ namespace IW4MAdmin
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
override public async Task OnClientDisconnected(EFClient client)
|
public override async Task OnClientDisconnected(EFClient client)
|
||||||
{
|
{
|
||||||
if (!GetClientsAsList().Any(_client => _client.NetworkId == client.NetworkId))
|
if (!GetClientsAsList().Any(_client => _client.NetworkId == client.NetworkId))
|
||||||
{
|
{
|
||||||
Logger.WriteInfo($"{client} disconnecting, but they are not connected");
|
using (LogContext.PushProperty("Server", ToString()))
|
||||||
|
{
|
||||||
|
ServerLogger.LogWarning("{client} disconnecting, but they are not connected", client.ToString());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +109,7 @@ namespace IW4MAdmin
|
|||||||
if (client.ClientNumber >= 0)
|
if (client.ClientNumber >= 0)
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
Logger.WriteInfo($"Client {client} [{client.State.ToString().ToLower()}] disconnecting...");
|
ServerLogger.LogDebug("Client {@client} disconnecting...", new { client=client.ToString(), client.State });
|
||||||
Clients[client.ClientNumber] = null;
|
Clients[client.ClientNumber] = null;
|
||||||
await client.OnDisconnect();
|
await client.OnDisconnect();
|
||||||
|
|
||||||
@ -114,19 +128,14 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
public override async Task ExecuteEvent(GameEvent E)
|
public override async Task ExecuteEvent(GameEvent E)
|
||||||
{
|
{
|
||||||
if (E == null)
|
using (LogContext.PushProperty("Server", ToString()))
|
||||||
{
|
{
|
||||||
Logger.WriteError("Received NULL event");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (E.IsBlocking)
|
if (E.IsBlocking)
|
||||||
{
|
{
|
||||||
await E.Origin?.Lock();
|
await E.Origin?.Lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool canExecuteCommand = true;
|
bool canExecuteCommand = true;
|
||||||
Exception lastException = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -145,7 +154,8 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
catch (CommandException e)
|
catch (CommandException e)
|
||||||
{
|
{
|
||||||
Logger.WriteInfo(e.Message);
|
ServerLogger.LogWarning(e, "Error validating command from event {@event}",
|
||||||
|
new { E.Type, E.Data, E.Message, E.Subtype, E.IsRemote, E.CorrelationId });
|
||||||
E.FailReason = GameEvent.EventFailReason.Invalid;
|
E.FailReason = GameEvent.EventFailReason.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,42 +185,20 @@ namespace IW4MAdmin
|
|||||||
if (E.Type == GameEvent.EventType.Command && E.Extra is Command command &&
|
if (E.Type == GameEvent.EventType.Command && E.Extra is Command command &&
|
||||||
(canExecuteCommand || E.Origin?.Level == Permission.Console))
|
(canExecuteCommand || E.Origin?.Level == Permission.Console))
|
||||||
{
|
{
|
||||||
|
ServerLogger.LogInformation("Executing command {comamnd} for {client}", command.Name, E.Origin.ToString());
|
||||||
await command.ExecuteAsync(E);
|
await command.ExecuteAsync(E);
|
||||||
}
|
}
|
||||||
|
|
||||||
var pluginTasks = Manager.Plugins.Where(_plugin => _plugin.Name != "Login").Select(async _plugin =>
|
var pluginTasks = Manager.Plugins
|
||||||
{
|
.Where(_plugin => _plugin.Name != "Login")
|
||||||
try
|
.Select(async plugin => await CreatePluginTask(plugin, E));
|
||||||
{
|
|
||||||
// we don't want to run the events on parser plugins
|
|
||||||
if (_plugin is ScriptPlugin scriptPlugin && scriptPlugin.IsParser)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
await Task.WhenAll(pluginTasks);
|
||||||
{
|
|
||||||
tokenSource.CancelAfter(Utilities.DefaultCommandTimeout);
|
|
||||||
await (_plugin.OnEventAsync(E, this)).WithWaitCancellation(tokenSource.Token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception Except)
|
|
||||||
{
|
|
||||||
Logger.WriteError($"{loc["SERVER_PLUGIN_ERROR"]} [{_plugin.Name}]");
|
|
||||||
Logger.WriteDebug(Except.GetExceptionInfo());
|
|
||||||
}
|
|
||||||
}).ToArray();
|
|
||||||
|
|
||||||
if (pluginTasks.Any())
|
|
||||||
{
|
|
||||||
await Task.WhenAny(pluginTasks);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
lastException = e;
|
ServerLogger.LogError(e, "Unexpected exception occurred processing event");
|
||||||
|
|
||||||
if (E.Origin != null && E.Type == GameEvent.EventType.Command)
|
if (E.Origin != null && E.Type == GameEvent.EventType.Command)
|
||||||
{
|
{
|
||||||
E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
||||||
@ -223,15 +211,30 @@ namespace IW4MAdmin
|
|||||||
{
|
{
|
||||||
E.Origin?.Unlock();
|
E.Origin?.Unlock();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (lastException != null)
|
private async Task CreatePluginTask(IPlugin plugin, GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
bool notifyDisconnects = !Manager.GetApplicationSettings().Configuration().IgnoreServerConnectionLost;
|
// we don't want to run the events on parser plugins
|
||||||
if (notifyDisconnects || (!notifyDisconnects && lastException as NetworkException == null))
|
if (plugin is ScriptPlugin scriptPlugin && scriptPlugin.IsParser)
|
||||||
{
|
{
|
||||||
throw lastException;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var tokenSource = new CancellationTokenSource();
|
||||||
|
tokenSource.CancelAfter(Utilities.DefaultCommandTimeout);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await (plugin.OnEventAsync(gameEvent, this)).WithWaitCancellation(tokenSource.Token);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(loc["SERVER_PLUGIN_ERROR"]);
|
||||||
|
ServerLogger.LogError(ex, "Could not execute {methodName} for plugin {plugin}",
|
||||||
|
nameof(plugin.OnEventAsync), plugin.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,41 +243,45 @@ namespace IW4MAdmin
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="E"></param>
|
/// <param name="E"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
override protected async Task<bool> ProcessEvent(GameEvent E)
|
protected override async Task<bool> ProcessEvent(GameEvent E)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
using (LogContext.PushProperty("Server", ToString()))
|
||||||
Logger.WriteDebug($"processing event of type {E.Type}");
|
using (LogContext.PushProperty("EventType", E.Type))
|
||||||
#endif
|
{
|
||||||
|
ServerLogger.LogDebug("processing event of type {type}", E.Type);
|
||||||
|
|
||||||
if (E.Type == GameEvent.EventType.ConnectionLost)
|
if (E.Type == GameEvent.EventType.ConnectionLost)
|
||||||
{
|
{
|
||||||
var exception = E.Extra as Exception;
|
var exception = E.Extra as Exception;
|
||||||
|
ServerLogger.LogError(exception,
|
||||||
|
"Connection lost with {server}", ToString());
|
||||||
|
|
||||||
if (!Manager.GetApplicationSettings().Configuration().IgnoreServerConnectionLost)
|
if (!Manager.GetApplicationSettings().Configuration().IgnoreServerConnectionLost)
|
||||||
{
|
{
|
||||||
Logger.WriteError(exception.Message);
|
Console.WriteLine(loc["SERVER_ERROR_COMMUNICATION"].FormatExt($"{IP}:{Port}"));
|
||||||
if (exception.Data["internal_exception"] != null)
|
|
||||||
{
|
|
||||||
Logger.WriteDebug($"Internal Exception: {exception.Data["internal_exception"]}");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Logger.WriteInfo("Connection lost to server, so we are throttling the poll rate");
|
|
||||||
Throttled = true;
|
Throttled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (E.Type == GameEvent.EventType.ConnectionRestored)
|
if (E.Type == GameEvent.EventType.ConnectionRestored)
|
||||||
{
|
{
|
||||||
if (Throttled && !Manager.GetApplicationSettings().Configuration().IgnoreServerConnectionLost)
|
ServerLogger.LogInformation(
|
||||||
|
"Connection restored with {server}", ToString());
|
||||||
|
|
||||||
|
if (!Manager.GetApplicationSettings().Configuration().IgnoreServerConnectionLost)
|
||||||
{
|
{
|
||||||
Logger.WriteVerbose(loc["MANAGER_CONNECTION_REST"].FormatExt($"[{IP}:{Port}]"));
|
Console.WriteLine(loc["MANAGER_CONNECTION_REST"].FormatExt($"[{IP}:{Port}]"));
|
||||||
}
|
}
|
||||||
Logger.WriteInfo("Connection restored to server, so we are no longer throttling the poll rate");
|
|
||||||
Throttled = false;
|
Throttled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (E.Type == GameEvent.EventType.ChangePermission)
|
if (E.Type == GameEvent.EventType.ChangePermission)
|
||||||
{
|
{
|
||||||
var newPermission = (Permission) E.Extra;
|
var newPermission = (Permission) E.Extra;
|
||||||
Logger.WriteInfo($"{E.Origin} is setting {E.Target} to permission level {newPermission}");
|
ServerLogger.LogInformation("{origin} is setting {target} to permission level {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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,12 +300,20 @@ namespace IW4MAdmin
|
|||||||
Time = DateTime.UtcNow
|
Time = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var clientTag = await _metaService.GetPersistentMeta(EFMeta.ClientTag, E.Origin);
|
||||||
|
|
||||||
|
if (clientTag?.LinkedMeta != null)
|
||||||
|
{
|
||||||
|
E.Origin.Tag = clientTag.LinkedMeta.Value;
|
||||||
|
}
|
||||||
|
|
||||||
await E.Origin.OnJoin(E.Origin.IPAddress);
|
await E.Origin.OnJoin(E.Origin.IPAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.PreConnect)
|
else if (E.Type == GameEvent.EventType.PreConnect)
|
||||||
{
|
{
|
||||||
|
ServerLogger.LogInformation("Detected PreConnect for {client} from {source}", E.Origin.ToString(), E.Source);
|
||||||
// we don't want to track bots in the database at all if ignore bots is requested
|
// we don't want to track bots in the database at all if ignore bots is requested
|
||||||
if (E.Origin.IsBot && Manager.GetApplicationSettings().Configuration().IgnoreBots)
|
if (E.Origin.IsBot && Manager.GetApplicationSettings().Configuration().IgnoreBots)
|
||||||
{
|
{
|
||||||
@ -307,16 +322,19 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (E.Origin.CurrentServer == null)
|
if (E.Origin.CurrentServer == null)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning($"preconnecting client {E.Origin} did not have a current server specified");
|
ServerLogger.LogWarning("Preconnecting client {client} did not have a current server specified",
|
||||||
|
E.Origin.ToString());
|
||||||
E.Origin.CurrentServer = this;
|
E.Origin.CurrentServer = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
var existingClient = GetClientsAsList().FirstOrDefault(_client => _client.Equals(E.Origin));
|
var existingClient = GetClientsAsList().FirstOrDefault(_client => _client.Equals(E.Origin));
|
||||||
|
|
||||||
// they're already connected
|
// they're already connected
|
||||||
if (existingClient != null && existingClient.ClientNumber == E.Origin.ClientNumber && !E.Origin.IsBot)
|
if (existingClient != null && existingClient.ClientNumber == E.Origin.ClientNumber &&
|
||||||
|
!E.Origin.IsBot)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning($"detected preconnect for {E.Origin}, but they are already connected");
|
ServerLogger.LogInformation("{client} is already connected, so we are ignoring their PreConnect",
|
||||||
|
E.Origin.ToString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,18 +342,19 @@ namespace IW4MAdmin
|
|||||||
// possible a connect/reconnect game event before we get to process it here
|
// possible a connect/reconnect game event before we get to process it here
|
||||||
// it appears that new games decide to switch client slots between maps (even if the clients aren't disconnecting)
|
// it appears that new games decide to switch client slots between maps (even if the clients aren't disconnecting)
|
||||||
// bots can have duplicate names which causes conflicting GUIDs
|
// bots can have duplicate names which causes conflicting GUIDs
|
||||||
else if (existingClient != null && existingClient.ClientNumber != E.Origin.ClientNumber && !E.Origin.IsBot)
|
if (existingClient != null && existingClient.ClientNumber != E.Origin.ClientNumber &&
|
||||||
|
!E.Origin.IsBot)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning($"client {E.Origin} is trying to connect in client slot {E.Origin.ClientNumber}, but they are already registed in client slot {existingClient.ClientNumber}, swapping...");
|
ServerLogger.LogWarning(
|
||||||
|
"client {client} is trying to connect in client slot {newClientSlot}, but they are already registered in client slot {oldClientSlot}, swapping...",
|
||||||
|
E.Origin.ToString(), E.Origin.ClientNumber, existingClient.ClientNumber);
|
||||||
// we need to remove them so the client spots can swap
|
// we need to remove them so the client spots can swap
|
||||||
await OnClientDisconnected(Clients[existingClient.ClientNumber]);
|
await OnClientDisconnected(Clients[existingClient.ClientNumber]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Clients[E.Origin.ClientNumber] == null)
|
if (Clients[E.Origin.ClientNumber] == null)
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
ServerLogger.LogDebug("Begin PreConnect for {origin}", E.Origin.ToString());
|
||||||
Logger.WriteDebug($"Begin PreConnect for {E.Origin}");
|
|
||||||
#endif
|
|
||||||
// we can go ahead and put them in so that they don't get re added
|
// we can go ahead and put them in so that they don't get re added
|
||||||
Clients[E.Origin.ClientNumber] = E.Origin;
|
Clients[E.Origin.ClientNumber] = E.Origin;
|
||||||
try
|
try
|
||||||
@ -346,9 +365,8 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.WriteError($"{loc["SERVER_ERROR_ADDPLAYER"]} {E.Origin}");
|
Console.WriteLine($"{loc["SERVER_ERROR_ADDPLAYER"]} {E.Origin}");
|
||||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
ServerLogger.LogError(ex, "Could not add player {player}", E.Origin.ToString());
|
||||||
|
|
||||||
Clients[E.Origin.ClientNumber] = null;
|
Clients[E.Origin.ClientNumber] = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -362,7 +380,9 @@ namespace IW4MAdmin
|
|||||||
// for some reason there's still a client in the spot
|
// for some reason there's still a client in the spot
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.WriteWarning($"{E.Origin} is connecting but {Clients[E.Origin.ClientNumber]} is currently in that client slot");
|
ServerLogger.LogWarning(
|
||||||
|
"{origin} is connecting but {existingClient} is currently in that client slot",
|
||||||
|
E.Origin.ToString(), Clients[E.Origin.ClientNumber].ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,13 +457,15 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (!E.Target.IsPrivileged() && reportNum >= REPORT_FLAG_COUNT && !isAutoFlagged)
|
if (!E.Target.IsPrivileged() && reportNum >= REPORT_FLAG_COUNT && !isAutoFlagged)
|
||||||
{
|
{
|
||||||
E.Target.Flag(Utilities.CurrentLocalization.LocalizationIndex["SERVER_AUTO_FLAG_REPORT"].FormatExt(reportNum), Utilities.IW4MAdminClient(E.Owner));
|
E.Target.Flag(
|
||||||
|
Utilities.CurrentLocalization.LocalizationIndex["SERVER_AUTO_FLAG_REPORT"]
|
||||||
|
.FormatExt(reportNum), Utilities.IW4MAdminClient(E.Owner));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.TempBan)
|
else if (E.Type == GameEvent.EventType.TempBan)
|
||||||
{
|
{
|
||||||
await TempBan(E.Data, (TimeSpan)E.Extra, E.Target, E.ImpersonationOrigin ?? E.Origin); ;
|
await TempBan(E.Data, (TimeSpan) E.Extra, E.Target, E.ImpersonationOrigin ?? E.Origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.Ban)
|
else if (E.Type == GameEvent.EventType.Ban)
|
||||||
@ -459,7 +481,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.Kick)
|
else if (E.Type == GameEvent.EventType.Kick)
|
||||||
{
|
{
|
||||||
await Kick(E.Data, E.Target, E.ImpersonationOrigin ?? E.Origin);
|
await Kick(E.Data, E.Target, E.ImpersonationOrigin ?? E.Origin, E.Extra as EFPenalty);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.Warn)
|
else if (E.Type == GameEvent.EventType.Warn)
|
||||||
@ -482,11 +504,15 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.PreDisconnect)
|
else if (E.Type == GameEvent.EventType.PreDisconnect)
|
||||||
{
|
{
|
||||||
|
ServerLogger.LogInformation("Detected PreDisconnect for {client} from {source}",
|
||||||
|
E.Origin.ToString(), E.Source);
|
||||||
bool isPotentialFalseQuit = E.GameTime.HasValue && E.GameTime.Value == lastGameTime;
|
bool isPotentialFalseQuit = E.GameTime.HasValue && E.GameTime.Value == lastGameTime;
|
||||||
|
|
||||||
if (isPotentialFalseQuit)
|
if (isPotentialFalseQuit)
|
||||||
{
|
{
|
||||||
Logger.WriteInfo($"Receive predisconnect event for {E.Origin}, but it occured at game time {E.GameTime.Value}, which is the same last map change, so we're ignoring");
|
ServerLogger.LogDebug(
|
||||||
|
"Received PreDisconnect event for {origin}, but it occured at game time {gameTime}, which is the same last map change, so we're ignoring",
|
||||||
|
E.Origin.ToString(), E.GameTime);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,34 +522,31 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (client == null)
|
if (client == null)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning($"Client {E.Origin} detected as disconnecting, but could not find them in the player list");
|
// this can happen when the status picks up the connect before the log does
|
||||||
|
ServerLogger.LogInformation(
|
||||||
|
"Ignoring PreDisconnect for {origin} because they are no longer on the client list",
|
||||||
|
E.Origin.ToString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (client.State != ClientState.Unknown)
|
else if (client.State != ClientState.Unknown)
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
|
||||||
Logger.WriteDebug($"Begin PreDisconnect for {client}");
|
|
||||||
#endif
|
|
||||||
await OnClientDisconnected(client);
|
await OnClientDisconnected(client);
|
||||||
#if DEBUG == true
|
|
||||||
Logger.WriteDebug($"End PreDisconnect for {client}");
|
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.WriteWarning($"Expected disconnecting client {client} to be in state {ClientState.Connected.ToString()}, but is in state {client.State}");
|
ServerLogger.LogWarning(
|
||||||
|
"Expected disconnecting client {client} to be in state {state}, but is in state {clientState}",
|
||||||
|
client.ToString(), ClientState.Connected.ToString(), client.State);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.Update)
|
else if (E.Type == GameEvent.EventType.Update)
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
ServerLogger.LogDebug("Begin Update for {origin}", E.Origin.ToString());
|
||||||
Logger.WriteDebug($"Begin Update for {E.Origin}");
|
|
||||||
#endif
|
|
||||||
await OnClientUpdate(E.Origin);
|
await OnClientUpdate(E.Origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,7 +582,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (E.Type == GameEvent.EventType.MapChange)
|
if (E.Type == GameEvent.EventType.MapChange)
|
||||||
{
|
{
|
||||||
Logger.WriteInfo($"New map loaded - {ClientNum} active players");
|
ServerLogger.LogInformation("New map loaded - {clientCount} active players", ClientNum);
|
||||||
|
|
||||||
// iw4 doesn't log the game info
|
// iw4 doesn't log the game info
|
||||||
if (E.Extra == null)
|
if (E.Extra == null)
|
||||||
@ -568,7 +591,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (dict == null)
|
if (dict == null)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning("Map change event response doesn't have any data");
|
ServerLogger.LogWarning("Map change event response doesn't have any data");
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
@ -600,7 +623,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (E.Type == GameEvent.EventType.MapEnd)
|
if (E.Type == GameEvent.EventType.MapEnd)
|
||||||
{
|
{
|
||||||
Logger.WriteInfo("Game ending...");
|
ServerLogger.LogInformation("Game ending...");
|
||||||
|
|
||||||
if (E.GameTime.HasValue)
|
if (E.GameTime.HasValue)
|
||||||
{
|
{
|
||||||
@ -638,6 +661,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnClientUpdate(EFClient origin)
|
private async Task OnClientUpdate(EFClient origin)
|
||||||
{
|
{
|
||||||
@ -645,7 +669,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (client == null)
|
if (client == null)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning($"{origin} expected to exist in client list for update, but they do not");
|
ServerLogger.LogWarning("{origin} expected to exist in client list for update, but they do not", origin.ToString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,15 +688,17 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning($"Could not execute on join for {origin}");
|
using(LogContext.PushProperty("Server", ToString()))
|
||||||
Logger.WriteDebug(e.GetExceptionInfo());
|
{
|
||||||
|
ServerLogger.LogError(e, "Could not execute on join for {origin}", origin.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ((client.IPAddress != null && client.State == ClientState.Disconnecting) ||
|
else if ((client.IPAddress != null && client.State == ClientState.Disconnecting) ||
|
||||||
client.Level == Permission.Banned)
|
client.Level == Permission.Banned)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning($"{client} state is Unknown (probably kicked), but they are still connected. trying to kick again...");
|
ServerLogger.LogWarning("{client} state is Unknown (probably kicked), but they are still connected. trying to kick again...", origin.ToString());
|
||||||
await client.CanConnect(client.IPAddress);
|
await client.CanConnect(client.IPAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -686,9 +712,6 @@ namespace IW4MAdmin
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
async Task<IList<EFClient>[]> PollPlayersAsync()
|
async Task<IList<EFClient>[]> PollPlayersAsync()
|
||||||
{
|
{
|
||||||
#if DEBUG
|
|
||||||
var now = DateTime.Now;
|
|
||||||
#endif
|
|
||||||
var currentClients = GetClientsAsList();
|
var currentClients = GetClientsAsList();
|
||||||
var statusResponse = (await this.GetStatusAsync());
|
var statusResponse = (await this.GetStatusAsync());
|
||||||
var polledClients = statusResponse.Item1.AsEnumerable();
|
var polledClients = statusResponse.Item1.AsEnumerable();
|
||||||
@ -697,9 +720,6 @@ namespace IW4MAdmin
|
|||||||
{
|
{
|
||||||
polledClients = polledClients.Where(c => !c.IsBot);
|
polledClients = polledClients.Where(c => !c.IsBot);
|
||||||
}
|
}
|
||||||
#if DEBUG
|
|
||||||
Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms");
|
|
||||||
#endif
|
|
||||||
var disconnectingClients = currentClients.Except(polledClients);
|
var disconnectingClients = currentClients.Except(polledClients);
|
||||||
var connectingClients = polledClients.Except(currentClients);
|
var connectingClients = polledClients.Except(currentClients);
|
||||||
var updatedClients = polledClients.Except(connectingClients).Except(disconnectingClients);
|
var updatedClients = polledClients.Except(connectingClients).Except(disconnectingClients);
|
||||||
@ -763,9 +783,8 @@ namespace IW4MAdmin
|
|||||||
DateTime playerCountStart = DateTime.Now;
|
DateTime playerCountStart = DateTime.Now;
|
||||||
DateTime lastCount = DateTime.Now;
|
DateTime lastCount = DateTime.Now;
|
||||||
|
|
||||||
override public async Task<bool> ProcessUpdatesAsync(CancellationToken cts)
|
public override async Task<bool> ProcessUpdatesAsync(CancellationToken cts)
|
||||||
{
|
{
|
||||||
bool notifyDisconnects = !Manager.GetApplicationSettings().Configuration().IgnoreServerConnectionLost;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (cts.IsCancellationRequested)
|
if (cts.IsCancellationRequested)
|
||||||
@ -776,12 +795,10 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#if DEBUG
|
if (Manager.GetApplicationSettings().Configuration().RConPollRate == int.MaxValue && Utilities.IsDevelopment)
|
||||||
if (Manager.GetApplicationSettings().Configuration().RConPollRate == int.MaxValue)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
var polledClients = await PollPlayersAsync();
|
var polledClients = await PollPlayersAsync();
|
||||||
|
|
||||||
@ -816,6 +833,7 @@ namespace IW4MAdmin
|
|||||||
Origin = client,
|
Origin = client,
|
||||||
Owner = this,
|
Owner = this,
|
||||||
IsBlocking = true,
|
IsBlocking = true,
|
||||||
|
Extra = client.GetAdditionalProperty<string>("BotGuid"),
|
||||||
Source = GameEvent.EventSource.Status
|
Source = GameEvent.EventSource.Status
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -837,7 +855,7 @@ namespace IW4MAdmin
|
|||||||
Manager.AddEvent(e);
|
Manager.AddEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConnectionErrors > 0)
|
if (Throttled)
|
||||||
{
|
{
|
||||||
var _event = new GameEvent()
|
var _event = new GameEvent()
|
||||||
{
|
{
|
||||||
@ -850,14 +868,12 @@ namespace IW4MAdmin
|
|||||||
Manager.AddEvent(_event);
|
Manager.AddEvent(_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionErrors = 0;
|
|
||||||
LastPoll = DateTime.Now;
|
LastPoll = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (NetworkException e)
|
catch (NetworkException e)
|
||||||
{
|
{
|
||||||
ConnectionErrors++;
|
if (!Throttled)
|
||||||
if (ConnectionErrors == 3)
|
|
||||||
{
|
{
|
||||||
var _event = new GameEvent()
|
var _event = new GameEvent()
|
||||||
{
|
{
|
||||||
@ -871,6 +887,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
Manager.AddEvent(_event);
|
Manager.AddEvent(_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -915,24 +932,22 @@ namespace IW4MAdmin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this one is ok
|
// this one is ok
|
||||||
catch (ServerException e)
|
catch (Exception e) when(e is ServerException || e is RConException)
|
||||||
{
|
{
|
||||||
if (e is NetworkException && !Throttled && notifyDisconnects)
|
using(LogContext.PushProperty("Server", ToString()))
|
||||||
{
|
{
|
||||||
Logger.WriteError(loc["SERVER_ERROR_COMMUNICATION"].FormatExt($"{IP}:{Port}"));
|
ServerLogger.LogWarning(e, "Undesirable exception occured during processing updates");
|
||||||
Logger.WriteDebug(e.GetExceptionInfo());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.WriteError(e.Message);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception E)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.WriteError(loc["SERVER_ERROR_EXCEPTION"].FormatExt($"[{IP}:{Port}]"));
|
using(LogContext.PushProperty("Server", ToString()))
|
||||||
Logger.WriteDebug(E.GetExceptionInfo());
|
{
|
||||||
|
ServerLogger.LogError(e, "Unexpected exception occured during processing updates");
|
||||||
|
}
|
||||||
|
Console.WriteLine(loc["SERVER_ERROR_EXCEPTION"].FormatExt($"[{IP}:{Port}]"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -948,7 +963,7 @@ namespace IW4MAdmin
|
|||||||
RconParser = RconParser ?? Manager.AdditionalRConParsers[0];
|
RconParser = RconParser ?? Manager.AdditionalRConParsers[0];
|
||||||
EventParser = EventParser ?? Manager.AdditionalEventParsers[0];
|
EventParser = EventParser ?? Manager.AdditionalEventParsers[0];
|
||||||
|
|
||||||
RemoteConnection.SetConfiguration(RconParser.Configuration);
|
RemoteConnection.SetConfiguration(RconParser);
|
||||||
|
|
||||||
var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version");
|
var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version");
|
||||||
Version = version.Value;
|
Version = version.Value;
|
||||||
@ -971,7 +986,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
|
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
|
||||||
{
|
{
|
||||||
throw new ServerException(loc["SERVER_ERROR_NOT_RUNNING"]);
|
throw new ServerException(loc["SERVER_ERROR_NOT_RUNNING"].FormatExt(this.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null;
|
var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null;
|
||||||
@ -1012,6 +1027,12 @@ namespace IW4MAdmin
|
|||||||
Website = loc["SERVER_WEBSITE_GENERIC"];
|
Website = loc["SERVER_WEBSITE_GENERIC"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: remove this once _website is weaned off
|
||||||
|
if (string.IsNullOrEmpty(Manager.GetApplicationSettings().Configuration().ContactUri))
|
||||||
|
{
|
||||||
|
Manager.GetApplicationSettings().Configuration().ContactUri = Website;
|
||||||
|
}
|
||||||
|
|
||||||
InitializeMaps();
|
InitializeMaps();
|
||||||
|
|
||||||
WorkingDirectory = basepath.Value;
|
WorkingDirectory = basepath.Value;
|
||||||
@ -1042,8 +1063,9 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (needsRestart)
|
if (needsRestart)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning("Game log file not properly initialized, restarting map...");
|
// disabling this for the time being
|
||||||
await this.ExecuteCommandAsync("map_restart");
|
/*Logger.WriteWarning("Game log file not properly initialized, restarting map...");
|
||||||
|
await this.ExecuteCommandAsync("map_restart");*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// this DVAR isn't set until the a map is loaded
|
// this DVAR isn't set until the a map is loaded
|
||||||
@ -1075,16 +1097,21 @@ namespace IW4MAdmin
|
|||||||
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||||
};
|
};
|
||||||
LogPath = GenerateLogPath(logInfo);
|
LogPath = GenerateLogPath(logInfo);
|
||||||
|
ServerLogger.LogInformation("Game log information {@logInfo}", logInfo);
|
||||||
|
|
||||||
|
|
||||||
if (!File.Exists(LogPath) && ServerConfig.GameLogServerUrl == null)
|
if (!File.Exists(LogPath) && ServerConfig.GameLogServerUrl == null)
|
||||||
{
|
{
|
||||||
Logger.WriteError(loc["SERVER_ERROR_DNE"].FormatExt(LogPath));
|
Console.WriteLine(loc["SERVER_ERROR_DNE"].FormatExt(LogPath));
|
||||||
|
ServerLogger.LogCritical("Game log path does not exist {logPath}", LogPath);
|
||||||
throw new ServerException(loc["SERVER_ERROR_DNE"].FormatExt(LogPath));
|
throw new ServerException(loc["SERVER_ERROR_DNE"].FormatExt(LogPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LogEvent = new GameLogEventDetection(this, GenerateUriForLog(LogPath, ServerConfig.GameLogServerUrl?.AbsoluteUri), gameLogReaderFactory);
|
ServerLogger.LogInformation("Generated game log path is {logPath}", LogPath);
|
||||||
Logger.WriteInfo($"Log file is {LogPath}");
|
LogEvent = new GameLogEventDetection( _serviceProvider.GetRequiredService<ILogger<GameLogEventDetection>>(),
|
||||||
|
this,
|
||||||
|
GenerateUriForLog(LogPath, ServerConfig.GameLogServerUrl?.AbsoluteUri), gameLogReaderFactory);
|
||||||
|
|
||||||
_ = Task.Run(() => LogEvent.PollForChanges());
|
_ = Task.Run(() => LogEvent.PollForChanges());
|
||||||
|
|
||||||
@ -1163,8 +1190,8 @@ namespace IW4MAdmin
|
|||||||
Link = targetClient.AliasLink
|
Link = targetClient.AliasLink
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.WriteDebug($"Creating warn penalty for {targetClient}");
|
ServerLogger.LogDebug("Creating warn penalty for {targetClient}", targetClient.ToString());
|
||||||
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), Manager.GetLogger(0));
|
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), ServerLogger);
|
||||||
|
|
||||||
if (targetClient.IsIngame)
|
if (targetClient.IsIngame)
|
||||||
{
|
{
|
||||||
@ -1174,12 +1201,13 @@ namespace IW4MAdmin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: move to translation sheet
|
||||||
string message = $"^1{loc["SERVER_WARNING"]} ^7[^3{targetClient.Warnings}^7]: ^3{targetClient.Name}^7, {reason}";
|
string message = $"^1{loc["SERVER_WARNING"]} ^7[^3{targetClient.Warnings}^7]: ^3{targetClient.Name}^7, {reason}";
|
||||||
targetClient.CurrentServer.Broadcast(message);
|
targetClient.CurrentServer.Broadcast(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task Kick(string Reason, EFClient targetClient, EFClient originClient)
|
public override async Task Kick(string reason, EFClient targetClient, EFClient originClient, EFPenalty previousPenalty)
|
||||||
{
|
{
|
||||||
targetClient = targetClient.ClientNumber < 0 ?
|
targetClient = targetClient.ClientNumber < 0 ?
|
||||||
Manager.GetActiveClients()
|
Manager.GetActiveClients()
|
||||||
@ -1191,13 +1219,13 @@ namespace IW4MAdmin
|
|||||||
Type = EFPenalty.PenaltyType.Kick,
|
Type = EFPenalty.PenaltyType.Kick,
|
||||||
Expires = DateTime.UtcNow,
|
Expires = DateTime.UtcNow,
|
||||||
Offender = targetClient,
|
Offender = targetClient,
|
||||||
Offense = Reason,
|
Offense = reason,
|
||||||
Punisher = originClient,
|
Punisher = originClient,
|
||||||
Link = targetClient.AliasLink
|
Link = targetClient.AliasLink
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.WriteDebug($"Creating kick penalty for {targetClient}");
|
ServerLogger.LogDebug("Creating kick penalty for {targetClient}", targetClient.ToString());
|
||||||
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), Manager.GetLogger(0));
|
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), ServerLogger);
|
||||||
|
|
||||||
if (targetClient.IsIngame)
|
if (targetClient.IsIngame)
|
||||||
{
|
{
|
||||||
@ -1210,7 +1238,11 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
Manager.AddEvent(e);
|
Manager.AddEvent(e);
|
||||||
|
|
||||||
string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_KICK_TEXT"]} - ^5{Reason}^7");
|
var formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||||
|
targetClient.ClientNumber,
|
||||||
|
_messageFormatter.BuildFormattedMessage(RconParser.Configuration,
|
||||||
|
newPenalty,
|
||||||
|
previousPenalty));
|
||||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1233,18 +1265,20 @@ namespace IW4MAdmin
|
|||||||
Link = targetClient.AliasLink
|
Link = targetClient.AliasLink
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.WriteDebug($"Creating tempban penalty for {targetClient}");
|
ServerLogger.LogDebug("Creating tempban penalty for {targetClient}", targetClient.ToString());
|
||||||
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), Manager.GetLogger(0));
|
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), ServerLogger);
|
||||||
|
|
||||||
if (targetClient.IsIngame)
|
if (targetClient.IsIngame)
|
||||||
{
|
{
|
||||||
string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"^7{loc["SERVER_TB_TEXT"]}- ^5{Reason}");
|
var formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||||
Logger.WriteDebug($"Executing tempban kick command for {targetClient}");
|
targetClient.ClientNumber,
|
||||||
|
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||||
|
ServerLogger.LogDebug("Executing tempban kick command for {targetClient}", targetClient.ToString());
|
||||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public 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)
|
||||||
{
|
{
|
||||||
// ensure player gets kicked if command not performed on them in the same server
|
// ensure player gets kicked if command not performed on them in the same server
|
||||||
targetClient = targetClient.ClientNumber < 0 ?
|
targetClient = targetClient.ClientNumber < 0 ?
|
||||||
@ -1263,14 +1297,16 @@ namespace IW4MAdmin
|
|||||||
IsEvadedOffense = isEvade
|
IsEvadedOffense = isEvade
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.WriteDebug($"Creating ban penalty for {targetClient}");
|
ServerLogger.LogDebug("Creating ban penalty for {targetClient}", targetClient.ToString());
|
||||||
targetClient.SetLevel(Permission.Banned, originClient);
|
targetClient.SetLevel(Permission.Banned, originClient);
|
||||||
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), Manager.GetLogger(0));
|
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), ServerLogger);
|
||||||
|
|
||||||
if (targetClient.IsIngame)
|
if (targetClient.IsIngame)
|
||||||
{
|
{
|
||||||
Logger.WriteDebug($"Attempting to kicking newly banned client {targetClient}");
|
ServerLogger.LogDebug("Attempting to kicking newly banned client {targetClient}", targetClient.ToString());
|
||||||
string formattedString = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{reason} ^7{loc["SERVER_BAN_APPEAL"].FormatExt(Website)}^7");
|
var formattedString = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||||
|
targetClient.ClientNumber,
|
||||||
|
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedString);
|
await targetClient.CurrentServer.ExecuteCommandAsync(formattedString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1289,6 +1325,7 @@ namespace IW4MAdmin
|
|||||||
Link = Target.AliasLink
|
Link = Target.AliasLink
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ServerLogger.LogDebug("Creating unban penalty for {targetClient}", Target.ToString());
|
||||||
Target.SetLevel(Permission.User, Origin);
|
Target.SetLevel(Permission.User, Origin);
|
||||||
await Manager.GetPenaltyService().RemoveActivePenalties(Target.AliasLink.AliasLinkId);
|
await Manager.GetPenaltyService().RemoveActivePenalties(Target.AliasLink.AliasLinkId);
|
||||||
await Manager.GetPenaltyService().Create(unbanPenalty);
|
await Manager.GetPenaltyService().Create(unbanPenalty);
|
||||||
|
@ -6,15 +6,22 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Localization
|
namespace IW4MAdmin.Application.Localization
|
||||||
{
|
{
|
||||||
public class Configure
|
public static class Configure
|
||||||
{
|
{
|
||||||
public static ITranslationLookup Initialize(bool useLocalTranslation, IMasterApi apiInstance, string customLocale = null)
|
public static ITranslationLookup Initialize(ILogger logger, IMasterApi apiInstance, ApplicationConfiguration applicationConfiguration)
|
||||||
{
|
{
|
||||||
string currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale;
|
var useLocalTranslation = applicationConfiguration?.UseLocalTranslations ?? true;
|
||||||
string[] localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");
|
var customLocale = applicationConfiguration?.EnableCustomLocale ?? false
|
||||||
|
? (applicationConfiguration.CustomLocale ?? "en-US")
|
||||||
|
: "en-US";
|
||||||
|
var currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale;
|
||||||
|
var localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");
|
||||||
|
|
||||||
if (!useLocalTranslation)
|
if (!useLocalTranslation)
|
||||||
{
|
{
|
||||||
@ -25,9 +32,10 @@ namespace IW4MAdmin.Application.Localization
|
|||||||
return localization.LocalizationIndex;
|
return localization.LocalizationIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// the online localization failed so will default to local files
|
// the online localization failed so will default to local files
|
||||||
|
logger.LogWarning(ex, "Could not download latest translations");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,18 +63,20 @@ namespace IW4MAdmin.Application.Localization
|
|||||||
{
|
{
|
||||||
var localizationContents = File.ReadAllText(filePath, Encoding.UTF8);
|
var localizationContents = File.ReadAllText(filePath, Encoding.UTF8);
|
||||||
var eachLocalizationFile = Newtonsoft.Json.JsonConvert.DeserializeObject<SharedLibraryCore.Localization.Layout>(localizationContents);
|
var eachLocalizationFile = Newtonsoft.Json.JsonConvert.DeserializeObject<SharedLibraryCore.Localization.Layout>(localizationContents);
|
||||||
|
if (eachLocalizationFile == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var item in eachLocalizationFile.LocalizationIndex.Set)
|
foreach (var item in eachLocalizationFile.LocalizationIndex.Set)
|
||||||
{
|
{
|
||||||
if (!localizationDict.TryAdd(item.Key, item.Value))
|
if (!localizationDict.TryAdd(item.Key, item.Value))
|
||||||
{
|
{
|
||||||
Program.ServerManager.GetLogger(0).WriteError($"Could not add locale string {item.Key} to localization");
|
logger.LogError("Could not add locale string {key} to localization", item.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string localizationFile = $"{Path.Join(Utilities.OperatingDirectory, "Localization")}{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocale}-{currentLocale.ToUpper()}.json";
|
|
||||||
|
|
||||||
Utilities.CurrentLocalization = new SharedLibraryCore.Localization.Layout(localizationDict)
|
Utilities.CurrentLocalization = new SharedLibraryCore.Localization.Layout(localizationDict)
|
||||||
{
|
{
|
||||||
LocalizationName = currentLocale,
|
LocalizationName = currentLocale,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using IW4MAdmin.Application.API.Master;
|
using IW4MAdmin.Application.API.Master;
|
||||||
using IW4MAdmin.Application.EventParsers;
|
using IW4MAdmin.Application.EventParsers;
|
||||||
using IW4MAdmin.Application.Factories;
|
using IW4MAdmin.Application.Factories;
|
||||||
using IW4MAdmin.Application.Helpers;
|
|
||||||
using IW4MAdmin.Application.Meta;
|
using IW4MAdmin.Application.Meta;
|
||||||
using IW4MAdmin.Application.Migration;
|
using IW4MAdmin.Application.Migration;
|
||||||
using IW4MAdmin.Application.Misc;
|
using IW4MAdmin.Application.Misc;
|
||||||
@ -24,6 +23,10 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using IW4MAdmin.Application.Extensions;
|
||||||
|
using IW4MAdmin.Application.Localization;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application
|
namespace IW4MAdmin.Application
|
||||||
{
|
{
|
||||||
@ -65,8 +68,11 @@ namespace IW4MAdmin.Application
|
|||||||
private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e)
|
private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e)
|
||||||
{
|
{
|
||||||
ServerManager?.Stop();
|
ServerManager?.Stop();
|
||||||
|
if (ApplicationTask != null)
|
||||||
|
{
|
||||||
await ApplicationTask;
|
await ApplicationTask;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// task that initializes application and starts the application monitoring and runtime tasks
|
/// task that initializes application and starts the application monitoring and runtime tasks
|
||||||
@ -76,29 +82,33 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
restart:
|
restart:
|
||||||
ITranslationLookup translationLookup = null;
|
ITranslationLookup translationLookup = null;
|
||||||
|
var logger = BuildDefaultLogger<Program>(new ApplicationConfiguration());
|
||||||
|
Utilities.DefaultLogger = logger;
|
||||||
|
IServiceCollection services = null;
|
||||||
|
logger.LogInformation("Begin IW4MAdmin startup. Version is {version} {@args}", Version, args);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// do any needed housekeeping file/folder migrations
|
// do any needed housekeeping file/folder migrations
|
||||||
ConfigurationMigration.MoveConfigFolder10518(null);
|
ConfigurationMigration.MoveConfigFolder10518(null);
|
||||||
ConfigurationMigration.CheckDirectories();
|
ConfigurationMigration.CheckDirectories();
|
||||||
|
logger.LogDebug("Configuring services...");
|
||||||
var services = ConfigureServices(args);
|
services = ConfigureServices(args);
|
||||||
serviceProvider = services.BuildServiceProvider();
|
serviceProvider = services.BuildServiceProvider();
|
||||||
var versionChecker = serviceProvider.GetRequiredService<IMasterCommunication>();
|
var versionChecker = serviceProvider.GetRequiredService<IMasterCommunication>();
|
||||||
ServerManager = (ApplicationManager)serviceProvider.GetRequiredService<IManager>();
|
ServerManager = (ApplicationManager)serviceProvider.GetRequiredService<IManager>();
|
||||||
translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
|
translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
|
||||||
|
|
||||||
ServerManager.Logger.WriteInfo(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_VERSION"].FormatExt(Version));
|
|
||||||
|
|
||||||
await versionChecker.CheckVersion();
|
await versionChecker.CheckVersion();
|
||||||
await ServerManager.Init();
|
await ServerManager.Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
string failMessage = translationLookup == null ? "Failed to initalize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"];
|
string failMessage = translationLookup == null ? "Failed to initialize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"];
|
||||||
string exitMessage = translationLookup == null ? "Press enter to exit..." : translationLookup["MANAGER_EXIT"];
|
string exitMessage = translationLookup == null ? "Press enter to exit..." : translationLookup["MANAGER_EXIT"];
|
||||||
|
|
||||||
|
logger.LogCritical(e, "Failed to initialize IW4MAdmin");
|
||||||
Console.WriteLine(failMessage);
|
Console.WriteLine(failMessage);
|
||||||
|
|
||||||
while (e.InnerException != null)
|
while (e.InnerException != null)
|
||||||
@ -131,13 +141,14 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ApplicationTask = RunApplicationTasksAsync();
|
ApplicationTask = RunApplicationTasksAsync(logger, services);
|
||||||
await ApplicationTask;
|
await ApplicationTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
string failMessage = translationLookup == null ? "Failed to initalize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"];
|
logger.LogCritical(e, "Failed to launch IW4MAdmin");
|
||||||
|
string failMessage = translationLookup == null ? "Failed to launch IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"];
|
||||||
Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}");
|
Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,15 +164,15 @@ namespace IW4MAdmin.Application
|
|||||||
/// runs the core application tasks
|
/// runs the core application tasks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static async Task RunApplicationTasksAsync()
|
private static async Task RunApplicationTasksAsync(ILogger logger, IServiceCollection services)
|
||||||
{
|
{
|
||||||
var webfrontTask = ServerManager.GetApplicationSettings().Configuration().EnableWebFront ?
|
var webfrontTask = ServerManager.GetApplicationSettings().Configuration().EnableWebFront ?
|
||||||
WebfrontCore.Program.Init(ServerManager, serviceProvider, ServerManager.CancellationToken) :
|
WebfrontCore.Program.Init(ServerManager, serviceProvider, services, ServerManager.CancellationToken) :
|
||||||
Task.CompletedTask;
|
Task.CompletedTask;
|
||||||
|
|
||||||
// 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
|
||||||
var inputThread = new Thread(async () => await ReadConsoleInput());
|
var inputThread = new Thread(async () => await ReadConsoleInput(logger));
|
||||||
inputThread.Start();
|
inputThread.Start();
|
||||||
|
|
||||||
var tasks = new[]
|
var tasks = new[]
|
||||||
@ -171,9 +182,11 @@ namespace IW4MAdmin.Application
|
|||||||
serviceProvider.GetRequiredService<IMasterCommunication>().RunUploadStatus(ServerManager.CancellationToken)
|
serviceProvider.GetRequiredService<IMasterCommunication>().RunUploadStatus(ServerManager.CancellationToken)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
logger.LogDebug("Starting webfront and input tasks");
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
|
|
||||||
ServerManager.Logger.WriteVerbose(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_SHUTDOWN_SUCCESS"]);
|
logger.LogInformation("Shutdown completed successfully");
|
||||||
|
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_SHUTDOWN_SUCCESS"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -181,11 +194,11 @@ namespace IW4MAdmin.Application
|
|||||||
/// reads input from the console and executes entered commands on the default server
|
/// reads input from the console and executes entered commands on the default server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static async Task ReadConsoleInput()
|
private static async Task ReadConsoleInput(ILogger logger)
|
||||||
{
|
{
|
||||||
if (Console.IsInputRedirected)
|
if (Console.IsInputRedirected)
|
||||||
{
|
{
|
||||||
ServerManager.Logger.WriteInfo("Disabling console input as it has been redirected");
|
logger.LogInformation("Disabling console input as it has been redirected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,67 +234,29 @@ namespace IW4MAdmin.Application
|
|||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private static IServiceCollection HandlePluginRegistration(ApplicationConfiguration appConfig,
|
||||||
/// Configures the dependency injection services
|
IServiceCollection serviceCollection,
|
||||||
/// </summary>
|
IMasterApi masterApi)
|
||||||
private static IServiceCollection ConfigureServices(string[] args)
|
|
||||||
{
|
{
|
||||||
var defaultLogger = new Logger("IW4MAdmin-Manager");
|
var defaultLogger = BuildDefaultLogger<Program>(appConfig);
|
||||||
var pluginImporter = new PluginImporter(defaultLogger);
|
var pluginServiceProvider = new ServiceCollection()
|
||||||
|
.AddBaseLogger(appConfig)
|
||||||
var serviceCollection = new ServiceCollection();
|
.AddSingleton(appConfig)
|
||||||
serviceCollection.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
|
.AddSingleton(masterApi)
|
||||||
.AddSingleton(new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings") as IConfigurationHandler<ApplicationConfiguration>)
|
.AddSingleton<IRemoteAssemblyHandler, RemoteAssemblyHandler>()
|
||||||
.AddSingleton(new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as IConfigurationHandler<CommandConfiguration>)
|
|
||||||
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration() ?? new ApplicationConfiguration())
|
|
||||||
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>().Configuration() ?? new CommandConfiguration())
|
|
||||||
.AddSingleton<ILogger>(_serviceProvider => defaultLogger)
|
|
||||||
.AddSingleton<IPluginImporter, PluginImporter>()
|
.AddSingleton<IPluginImporter, PluginImporter>()
|
||||||
.AddSingleton<IMiddlewareActionHandler, MiddlewareActionHandler>()
|
.BuildServiceProvider();
|
||||||
.AddSingleton<IRConConnectionFactory, RConConnectionFactory>()
|
|
||||||
.AddSingleton<IGameServerInstanceFactory, GameServerInstanceFactory>()
|
|
||||||
.AddSingleton<IConfigurationHandlerFactory, ConfigurationHandlerFactory>()
|
|
||||||
.AddSingleton<IParserRegexFactory, ParserRegexFactory>()
|
|
||||||
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>()
|
|
||||||
.AddSingleton<IGameLogReaderFactory, GameLogReaderFactory>()
|
|
||||||
.AddSingleton<IScriptCommandFactory, ScriptCommandFactory>()
|
|
||||||
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
|
|
||||||
.AddSingleton<IEntityService<EFClient>, ClientService>()
|
|
||||||
.AddSingleton<IMetaService, MetaService>()
|
|
||||||
.AddSingleton<IMetaRegistration, MetaRegistration>()
|
|
||||||
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>, ReceivedPenaltyResourceQueryHelper>()
|
|
||||||
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>, AdministeredPenaltyResourceQueryHelper>()
|
|
||||||
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>, UpdatedAliasResourceQueryHelper>()
|
|
||||||
.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>()
|
|
||||||
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
|
||||||
.AddSingleton(_serviceProvider =>
|
|
||||||
{
|
|
||||||
var config = _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration();
|
|
||||||
return Localization.Configure.Initialize(useLocalTranslation: config?.UseLocalTranslations ?? false,
|
|
||||||
apiInstance: _serviceProvider.GetRequiredService<IMasterApi>(),
|
|
||||||
customLocale: config?.EnableCustomLocale ?? false ? (config.CustomLocale ?? "en-US") : "en-US");
|
|
||||||
})
|
|
||||||
.AddSingleton<IManager, ApplicationManager>()
|
|
||||||
.AddSingleton(_serviceProvider => RestClient
|
|
||||||
.For<IMasterApi>(Utilities.IsDevelopment ? new Uri("http://127.0.0.1:8080") : _serviceProvider
|
|
||||||
.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration()?.MasterUrl ??
|
|
||||||
new ApplicationConfiguration().MasterUrl))
|
|
||||||
.AddSingleton<IMasterCommunication, MasterCommunication>();
|
|
||||||
|
|
||||||
if (args.Contains("serialevents"))
|
var pluginImporter = pluginServiceProvider.GetRequiredService<IPluginImporter>();
|
||||||
{
|
|
||||||
serviceCollection.AddSingleton<IEventHandler, SerialGameEventHandler>();
|
// we need to register the rest client with regular collection
|
||||||
}
|
serviceCollection.AddSingleton(masterApi);
|
||||||
else
|
|
||||||
{
|
|
||||||
serviceCollection.AddSingleton<IEventHandler, GameEventHandler>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// register the native commands
|
// register the native commands
|
||||||
foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
|
foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
|
||||||
.Where(_command => _command.BaseType == typeof(Command)))
|
.Where(_command => _command.BaseType == typeof(Command)))
|
||||||
{
|
{
|
||||||
defaultLogger.WriteInfo($"Registered native command type {commandType.Name}");
|
defaultLogger.LogDebug("Registered native command type {name}", commandType.Name);
|
||||||
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
|
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,14 +264,14 @@ namespace IW4MAdmin.Application
|
|||||||
var pluginImplementations = pluginImporter.DiscoverAssemblyPluginImplementations();
|
var pluginImplementations = pluginImporter.DiscoverAssemblyPluginImplementations();
|
||||||
foreach (var pluginType in pluginImplementations.Item1)
|
foreach (var pluginType in pluginImplementations.Item1)
|
||||||
{
|
{
|
||||||
defaultLogger.WriteInfo($"Registered plugin type {pluginType.FullName}");
|
defaultLogger.LogDebug("Registered plugin type {name}", pluginType.FullName);
|
||||||
serviceCollection.AddSingleton(typeof(IPlugin), pluginType);
|
serviceCollection.AddSingleton(typeof(IPlugin), pluginType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// register the plugin commands
|
// register the plugin commands
|
||||||
foreach (var commandType in pluginImplementations.Item2)
|
foreach (var commandType in pluginImplementations.Item2)
|
||||||
{
|
{
|
||||||
defaultLogger.WriteInfo($"Registered plugin command type {commandType.FullName}");
|
defaultLogger.LogDebug("Registered plugin command type {name}", commandType.FullName);
|
||||||
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
|
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,5 +295,105 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
return serviceCollection;
|
return serviceCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the dependency injection services
|
||||||
|
/// </summary>
|
||||||
|
private static IServiceCollection ConfigureServices(string[] args)
|
||||||
|
{
|
||||||
|
// setup the static resources (config/master api/translations)
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
||||||
|
var appConfig = appConfigHandler.Configuration();
|
||||||
|
var masterUri = Utilities.IsDevelopment
|
||||||
|
? new Uri("http://127.0.0.1:8080")
|
||||||
|
: appConfig?.MasterUrl ?? new ApplicationConfiguration().MasterUrl;
|
||||||
|
var masterRestClient = RestClient.For<IMasterApi>(masterUri);
|
||||||
|
var translationLookup = Configure.Initialize(Utilities.DefaultLogger, masterRestClient, appConfig);
|
||||||
|
|
||||||
|
if (appConfig == null)
|
||||||
|
{
|
||||||
|
appConfig = (ApplicationConfiguration) new ApplicationConfiguration().Generate();
|
||||||
|
appConfigHandler.Set(appConfig);
|
||||||
|
appConfigHandler.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// register override level names
|
||||||
|
foreach (var (key, value) in appConfig.OverridePermissionLevelNames)
|
||||||
|
{
|
||||||
|
if (!Utilities.PermissionLevelOverrides.ContainsKey(key))
|
||||||
|
{
|
||||||
|
Utilities.PermissionLevelOverrides.Add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the dependency list
|
||||||
|
HandlePluginRegistration(appConfig, serviceCollection, masterRestClient);
|
||||||
|
|
||||||
|
serviceCollection
|
||||||
|
.AddBaseLogger(appConfig)
|
||||||
|
.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
|
||||||
|
.AddSingleton((IConfigurationHandler<ApplicationConfiguration>) appConfigHandler)
|
||||||
|
.AddSingleton(
|
||||||
|
new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as
|
||||||
|
IConfigurationHandler<CommandConfiguration>)
|
||||||
|
.AddSingleton(appConfig)
|
||||||
|
.AddSingleton(_serviceProvider =>
|
||||||
|
_serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>()
|
||||||
|
.Configuration() ?? new CommandConfiguration())
|
||||||
|
.AddSingleton<IPluginImporter, PluginImporter>()
|
||||||
|
.AddSingleton<IMiddlewareActionHandler, MiddlewareActionHandler>()
|
||||||
|
.AddSingleton<IRConConnectionFactory, RConConnectionFactory>()
|
||||||
|
.AddSingleton<IGameServerInstanceFactory, GameServerInstanceFactory>()
|
||||||
|
.AddSingleton<IConfigurationHandlerFactory, ConfigurationHandlerFactory>()
|
||||||
|
.AddSingleton<IParserRegexFactory, ParserRegexFactory>()
|
||||||
|
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>()
|
||||||
|
.AddSingleton<IGameLogReaderFactory, GameLogReaderFactory>()
|
||||||
|
.AddSingleton<IScriptCommandFactory, ScriptCommandFactory>()
|
||||||
|
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
|
||||||
|
.AddSingleton<IEntityService<EFClient>, ClientService>()
|
||||||
|
.AddSingleton<IMetaService, MetaService>()
|
||||||
|
.AddSingleton<ClientService>()
|
||||||
|
.AddSingleton<PenaltyService>()
|
||||||
|
.AddSingleton<ChangeHistoryService>()
|
||||||
|
.AddSingleton<IMetaRegistration, MetaRegistration>()
|
||||||
|
.AddSingleton<IScriptPluginServiceResolver, ScriptPluginServiceResolver>()
|
||||||
|
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>,
|
||||||
|
ReceivedPenaltyResourceQueryHelper>()
|
||||||
|
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>,
|
||||||
|
AdministeredPenaltyResourceQueryHelper>()
|
||||||
|
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>,
|
||||||
|
UpdatedAliasResourceQueryHelper>()
|
||||||
|
.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>()
|
||||||
|
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
||||||
|
.AddSingleton<IRemoteAssemblyHandler, RemoteAssemblyHandler>()
|
||||||
|
.AddSingleton<IMasterCommunication, MasterCommunication>()
|
||||||
|
.AddSingleton<IManager, ApplicationManager>()
|
||||||
|
.AddSingleton<SharedLibraryCore.Interfaces.ILogger, Logger>()
|
||||||
|
.AddSingleton<IClientNoticeMessageFormatter, ClientNoticeMessageFormatter>()
|
||||||
|
.AddSingleton(translationLookup)
|
||||||
|
.AddDatabaseContextOptions(appConfig);
|
||||||
|
|
||||||
|
if (args.Contains("serialevents"))
|
||||||
|
{
|
||||||
|
serviceCollection.AddSingleton<IEventHandler, SerialGameEventHandler>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
serviceCollection.AddSingleton<IEventHandler, GameEventHandler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ILogger BuildDefaultLogger<T>(ApplicationConfiguration appConfig)
|
||||||
|
{
|
||||||
|
var collection = new ServiceCollection()
|
||||||
|
.AddBaseLogger(appConfig)
|
||||||
|
.BuildServiceProvider();
|
||||||
|
|
||||||
|
return collection.GetRequiredService<ILogger<T>>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Dtos.Meta.Responses;
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using SharedLibraryCore.QueryHelper;
|
using SharedLibraryCore.QueryHelper;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Meta
|
namespace IW4MAdmin.Application.Meta
|
||||||
{
|
{
|
||||||
@ -18,7 +20,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
public AdministeredPenaltyResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
|
public AdministeredPenaltyResourceQueryHelper(ILogger<AdministeredPenaltyResourceQueryHelper> logger, IDatabaseContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -26,7 +28,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
|
|
||||||
public async Task<ResourceQueryHelperResult<AdministeredPenaltyResponse>> QueryResource(ClientPaginationRequest query)
|
public async Task<ResourceQueryHelperResult<AdministeredPenaltyResponse>> QueryResource(ClientPaginationRequest query)
|
||||||
{
|
{
|
||||||
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
|
||||||
var iqPenalties = ctx.Penalties.AsNoTracking()
|
var iqPenalties = ctx.Penalties.AsNoTracking()
|
||||||
.Where(_penalty => query.ClientId == _penalty.PunisherId)
|
.Where(_penalty => query.ClientId == _penalty.PunisherId)
|
||||||
|
@ -6,6 +6,8 @@ using SharedLibraryCore.QueryHelper;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Meta
|
namespace IW4MAdmin.Application.Meta
|
||||||
{
|
{
|
||||||
@ -19,7 +21,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> _administeredPenaltyHelper;
|
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> _administeredPenaltyHelper;
|
||||||
private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper;
|
private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper;
|
||||||
|
|
||||||
public MetaRegistration(ILogger logger, IMetaService metaService, ITranslationLookup transLookup, IEntityService<EFClient> clientEntityService,
|
public MetaRegistration(ILogger<MetaRegistration> logger, IMetaService metaService, ITranslationLookup transLookup, IEntityService<EFClient> clientEntityService,
|
||||||
IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> receivedPenaltyHelper,
|
IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> receivedPenaltyHelper,
|
||||||
IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> administeredPenaltyHelper,
|
IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> administeredPenaltyHelper,
|
||||||
IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper)
|
IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper)
|
||||||
@ -82,7 +84,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
|
|
||||||
if (client == null)
|
if (client == null)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"No client found with id {request.ClientId} when generating profile meta");
|
_logger.LogWarning("No client found with id {clientId} when generating profile meta", request.ClientId);
|
||||||
return metaList;
|
return metaList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Dtos.Meta.Responses;
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using SharedLibraryCore.QueryHelper;
|
using SharedLibraryCore.QueryHelper;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Meta
|
namespace IW4MAdmin.Application.Meta
|
||||||
{
|
{
|
||||||
@ -20,7 +22,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
public ReceivedPenaltyResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
|
public ReceivedPenaltyResourceQueryHelper(ILogger<ReceivedPenaltyResourceQueryHelper> logger, IDatabaseContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -29,7 +31,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource(ClientPaginationRequest query)
|
public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource(ClientPaginationRequest query)
|
||||||
{
|
{
|
||||||
var linkedPenaltyType = Utilities.LinkedPenaltyTypes();
|
var linkedPenaltyType = Utilities.LinkedPenaltyTypes();
|
||||||
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
|
||||||
var linkId = await ctx.Clients.AsNoTracking()
|
var linkId = await ctx.Clients.AsNoTracking()
|
||||||
.Where(_client => _client.ClientId == query.ClientId)
|
.Where(_client => _client.ClientId == query.ClientId)
|
||||||
|
@ -6,6 +6,8 @@ using SharedLibraryCore.Interfaces;
|
|||||||
using SharedLibraryCore.QueryHelper;
|
using SharedLibraryCore.QueryHelper;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Meta
|
namespace IW4MAdmin.Application.Meta
|
||||||
{
|
{
|
||||||
@ -18,7 +20,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
public UpdatedAliasResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
|
public UpdatedAliasResourceQueryHelper(ILogger<UpdatedAliasResourceQueryHelper> logger, IDatabaseContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
@ -26,7 +28,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
|
|
||||||
public async Task<ResourceQueryHelperResult<UpdatedAliasResponse>> QueryResource(ClientPaginationRequest query)
|
public async Task<ResourceQueryHelperResult<UpdatedAliasResponse>> QueryResource(ClientPaginationRequest query)
|
||||||
{
|
{
|
||||||
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
int linkId = ctx.Clients.First(_client => _client.ClientId == query.ClientId).AliasLinkId;
|
int linkId = ctx.Clients.First(_client => _client.ClientId == query.ClientId).AliasLinkId;
|
||||||
|
|
||||||
var iqAliasUpdates = ctx.Aliases
|
var iqAliasUpdates = ctx.Aliases
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Interfaces;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Migration
|
namespace IW4MAdmin.Application.Migration
|
||||||
{
|
{
|
||||||
@ -56,7 +53,6 @@ namespace IW4MAdmin.Application.Migration
|
|||||||
|
|
||||||
if (!Directory.Exists(configDirectory))
|
if (!Directory.Exists(configDirectory))
|
||||||
{
|
{
|
||||||
log?.WriteDebug($"Creating directory for configs {configDirectory}");
|
|
||||||
Directory.CreateDirectory(configDirectory);
|
Directory.CreateDirectory(configDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +62,6 @@ namespace IW4MAdmin.Application.Migration
|
|||||||
|
|
||||||
foreach (var configFile in configurationFiles)
|
foreach (var configFile in configurationFiles)
|
||||||
{
|
{
|
||||||
log?.WriteDebug($"Moving config file {configFile}");
|
|
||||||
string destinationPath = Path.Join("Configuration", configFile);
|
string destinationPath = Path.Join("Configuration", configFile);
|
||||||
if (!File.Exists(destinationPath))
|
if (!File.Exists(destinationPath))
|
||||||
{
|
{
|
||||||
@ -77,7 +72,6 @@ namespace IW4MAdmin.Application.Migration
|
|||||||
if (!File.Exists(Path.Join("Database", "Database.db")) &&
|
if (!File.Exists(Path.Join("Database", "Database.db")) &&
|
||||||
File.Exists("Database.db"))
|
File.Exists("Database.db"))
|
||||||
{
|
{
|
||||||
log?.WriteDebug("Moving database file");
|
|
||||||
File.Move("Database.db", Path.Join("Database", "Database.db"));
|
File.Move("Database.db", Path.Join("Database", "Database.db"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
Application/Migration/DatabaseHousekeeping.cs
Normal file
24
Application/Migration/DatabaseHousekeeping.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using IW4MAdmin.Plugins.Stats.Models;
|
||||||
|
using SharedLibraryCore.Database;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Migration
|
||||||
|
{
|
||||||
|
public static class DatabaseHousekeeping
|
||||||
|
{
|
||||||
|
private static readonly DateTime CutoffDate = DateTime.UtcNow.AddMonths(-6);
|
||||||
|
|
||||||
|
public static async Task RemoveOldRatings(IDatabaseContextFactory contextFactory, CancellationToken token)
|
||||||
|
{
|
||||||
|
await using var context = contextFactory.CreateContext();
|
||||||
|
var dbSet = context.Set<EFRating>();
|
||||||
|
var itemsToDelete = dbSet.Where(rating => rating.When <= CutoffDate);
|
||||||
|
dbSet.RemoveRange(itemsToDelete);
|
||||||
|
await context.SaveChangesAsync(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
Application/Misc/ClientNoticeMessageFormatter.cs
Normal file
120
Application/Misc/ClientNoticeMessageFormatter.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// implementation of IClientNoticeMessageFormatter
|
||||||
|
/// </summary>
|
||||||
|
public class ClientNoticeMessageFormatter : IClientNoticeMessageFormatter
|
||||||
|
{
|
||||||
|
private readonly ITranslationLookup _transLookup;
|
||||||
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
|
||||||
|
public ClientNoticeMessageFormatter(ITranslationLookup transLookup, ApplicationConfiguration appConfig)
|
||||||
|
{
|
||||||
|
_transLookup = transLookup;
|
||||||
|
_appConfig = appConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildFormattedMessage(IRConParserConfiguration config, EFPenalty currentPenalty, EFPenalty originalPenalty = null)
|
||||||
|
{
|
||||||
|
var isNewLineSeparator = config.NoticeLineSeparator == Environment.NewLine;
|
||||||
|
var penalty = originalPenalty ?? currentPenalty;
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
// build the top level header
|
||||||
|
var header = _transLookup[$"SERVER_{penalty.Type.ToString().ToUpper()}_TEXT"];
|
||||||
|
builder.Append(header);
|
||||||
|
builder.Append(config.NoticeLineSeparator);
|
||||||
|
// build the reason
|
||||||
|
var reason = _transLookup["GAME_MESSAGE_PENALTY_REASON"].FormatExt(penalty.Offense);
|
||||||
|
|
||||||
|
if (isNewLineSeparator)
|
||||||
|
{
|
||||||
|
foreach (var splitReason in SplitOverMaxLength(reason, config.NoticeMaxCharactersPerLine))
|
||||||
|
{
|
||||||
|
builder.Append(splitReason);
|
||||||
|
builder.Append(config.NoticeLineSeparator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Append(reason);
|
||||||
|
builder.Append(config.NoticeLineSeparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (penalty.Type == EFPenalty.PenaltyType.TempBan)
|
||||||
|
{
|
||||||
|
// build the time remaining if temporary
|
||||||
|
var timeRemainingValue = penalty.Expires.HasValue
|
||||||
|
? (penalty.Expires - DateTime.UtcNow).Value.HumanizeForCurrentCulture()
|
||||||
|
: "--";
|
||||||
|
var timeRemaining = _transLookup["GAME_MESSAGE_PENALTY_TIME_REMAINING"].FormatExt(timeRemainingValue);
|
||||||
|
|
||||||
|
if (isNewLineSeparator)
|
||||||
|
{
|
||||||
|
foreach (var splitReason in SplitOverMaxLength(timeRemaining, config.NoticeMaxCharactersPerLine))
|
||||||
|
{
|
||||||
|
builder.Append(splitReason);
|
||||||
|
builder.Append(config.NoticeLineSeparator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Append(timeRemaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (penalty.Type == EFPenalty.PenaltyType.Ban)
|
||||||
|
{
|
||||||
|
// provide a place to appeal the ban (should always be specified but including a placeholder just incase)
|
||||||
|
builder.Append(_transLookup["GAME_MESSAGE_PENALTY_APPEAL"].FormatExt(_appConfig.ContactUri ?? "--"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// final format looks something like:
|
||||||
|
/*
|
||||||
|
* You are permanently banned
|
||||||
|
* Reason - toxic behavior
|
||||||
|
* Visit example.com to appeal
|
||||||
|
*/
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> SplitOverMaxLength(string source, int maxCharactersPerLine)
|
||||||
|
{
|
||||||
|
if (source.Length <= maxCharactersPerLine)
|
||||||
|
{
|
||||||
|
return new[] {source};
|
||||||
|
}
|
||||||
|
|
||||||
|
var segments = new List<string>();
|
||||||
|
var currentLocation = 0;
|
||||||
|
while (currentLocation < source.Length)
|
||||||
|
{
|
||||||
|
var nextLocation = currentLocation + maxCharactersPerLine;
|
||||||
|
// there's probably a more efficient way to do this but this is readable
|
||||||
|
segments.Add(string.Concat(
|
||||||
|
source
|
||||||
|
.Skip(currentLocation)
|
||||||
|
.Take(Math.Min(maxCharactersPerLine, source.Length - currentLocation))));
|
||||||
|
currentLocation = nextLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLocation < source.Length)
|
||||||
|
{
|
||||||
|
segments.Add(source.Substring(currentLocation, source.Length - currentLocation));
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
using SharedLibraryCore;
|
|
||||||
using SharedLibraryCore.Interfaces;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Misc
|
|
||||||
{
|
|
||||||
internal class EventPerformance
|
|
||||||
{
|
|
||||||
public long ExecutionTime { get; set; }
|
|
||||||
public GameEvent Event { get; set; }
|
|
||||||
public string EventInfo => $"{Event.Type}, {Event.FailReason}, {Event.IsBlocking}, {Event.Data}, {Event.Message}, {Event.Extra}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DuplicateKeyComparer<TKey> : IComparer<TKey> where TKey : IComparable
|
|
||||||
{
|
|
||||||
public int Compare(TKey x, TKey y)
|
|
||||||
{
|
|
||||||
int result = x.CompareTo(y);
|
|
||||||
|
|
||||||
if (result == 0)
|
|
||||||
return 1;
|
|
||||||
else
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class EventProfiler
|
|
||||||
{
|
|
||||||
public double AverageEventTime { get; private set; }
|
|
||||||
public double MaxEventTime => Events.Values.Last().ExecutionTime;
|
|
||||||
public double MinEventTime => Events.Values[0].ExecutionTime;
|
|
||||||
public int TotalEventCount => Events.Count;
|
|
||||||
public SortedList<long, EventPerformance> Events { get; private set; } = new SortedList<long, EventPerformance>(new DuplicateKeyComparer<long>());
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
public EventProfiler(ILogger logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Profile(DateTime start, DateTime end, GameEvent gameEvent)
|
|
||||||
{
|
|
||||||
_logger.WriteDebug($"Starting profile of event {gameEvent.Id}");
|
|
||||||
long executionTime = (long)Math.Round((end - start).TotalMilliseconds);
|
|
||||||
|
|
||||||
var perf = new EventPerformance()
|
|
||||||
{
|
|
||||||
Event = gameEvent,
|
|
||||||
ExecutionTime = executionTime
|
|
||||||
};
|
|
||||||
|
|
||||||
lock (Events)
|
|
||||||
{
|
|
||||||
Events.Add(executionTime, perf);
|
|
||||||
}
|
|
||||||
|
|
||||||
AverageEventTime = (AverageEventTime * (TotalEventCount - 1) + executionTime) / TotalEventCount;
|
|
||||||
_logger.WriteDebug($"Finished profile of event {gameEvent.Id}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,132 +1,47 @@
|
|||||||
using IW4MAdmin.Application.IO;
|
using System;
|
||||||
using SharedLibraryCore;
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore.Interfaces;
|
using ILogger = SharedLibraryCore.Interfaces.ILogger;
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace IW4MAdmin.Application
|
namespace IW4MAdmin.Application
|
||||||
{
|
{
|
||||||
|
[Obsolete]
|
||||||
public class Logger : ILogger
|
public class Logger : ILogger
|
||||||
{
|
{
|
||||||
enum LogType
|
private readonly Microsoft.Extensions.Logging.ILogger _logger;
|
||||||
|
|
||||||
|
public Logger(ILogger<Logger> logger)
|
||||||
{
|
{
|
||||||
Verbose,
|
_logger = logger;
|
||||||
Info,
|
|
||||||
Debug,
|
|
||||||
Warning,
|
|
||||||
Error,
|
|
||||||
Assert
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly string FileName;
|
|
||||||
readonly ReaderWriterLockSlim WritingLock;
|
|
||||||
static readonly short MAX_LOG_FILES = 10;
|
|
||||||
|
|
||||||
public Logger(string fn)
|
|
||||||
{
|
|
||||||
FileName = Path.Join(Utilities.OperatingDirectory, "Log", $"{fn}.log");
|
|
||||||
WritingLock = new ReaderWriterLockSlim();
|
|
||||||
RotateLogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
~Logger()
|
|
||||||
{
|
|
||||||
WritingLock.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// rotates logs when log is initialized
|
|
||||||
/// </summary>
|
|
||||||
private void RotateLogs()
|
|
||||||
{
|
|
||||||
string maxLog = FileName + MAX_LOG_FILES;
|
|
||||||
|
|
||||||
if (File.Exists(maxLog))
|
|
||||||
{
|
|
||||||
File.Delete(maxLog);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = MAX_LOG_FILES - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
string logToMove = i == 0 ? FileName : FileName + i;
|
|
||||||
string movedLogName = FileName + (i + 1);
|
|
||||||
|
|
||||||
if (File.Exists(logToMove))
|
|
||||||
{
|
|
||||||
File.Move(logToMove, movedLogName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Write(string msg, LogType type)
|
|
||||||
{
|
|
||||||
WritingLock.EnterWriteLock();
|
|
||||||
|
|
||||||
string stringType = type.ToString();
|
|
||||||
msg = msg.StripColors();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
stringType = Utilities.CurrentLocalization.LocalizationIndex[$"GLOBAL_{type.ToString().ToUpper()}"];
|
|
||||||
}
|
|
||||||
|
|
||||||
catch (Exception) { }
|
|
||||||
|
|
||||||
string LogLine = $"[{DateTime.Now.ToString("MM.dd.yyy HH:mm:ss.fff")}] - {stringType}: {msg}";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
|
|
||||||
Console.WriteLine(msg);
|
|
||||||
#else
|
|
||||||
if (type == LogType.Error || type == LogType.Verbose)
|
|
||||||
{
|
|
||||||
Console.WriteLine(LogLine);
|
|
||||||
}
|
|
||||||
File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Well.. It looks like your machine can't event write to the log file. That's something else...");
|
|
||||||
Console.WriteLine(ex.GetExceptionInfo());
|
|
||||||
}
|
|
||||||
|
|
||||||
WritingLock.ExitWriteLock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteVerbose(string msg)
|
public void WriteVerbose(string msg)
|
||||||
{
|
{
|
||||||
Write(msg, LogType.Verbose);
|
_logger.LogInformation(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteDebug(string msg)
|
public void WriteDebug(string msg)
|
||||||
{
|
{
|
||||||
Write(msg, LogType.Debug);
|
_logger.LogDebug(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteError(string msg)
|
public void WriteError(string msg)
|
||||||
{
|
{
|
||||||
Write(msg, LogType.Error);
|
_logger.LogError(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteInfo(string msg)
|
public void WriteInfo(string msg)
|
||||||
{
|
{
|
||||||
Write(msg, LogType.Info);
|
WriteVerbose(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteWarning(string msg)
|
public void WriteWarning(string msg)
|
||||||
{
|
{
|
||||||
Write(msg, LogType.Warning);
|
_logger.LogWarning(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteAssert(bool condition, string msg)
|
public void WriteAssert(bool condition, string msg)
|
||||||
{
|
{
|
||||||
if (!condition)
|
throw new NotImplementedException();
|
||||||
Write(msg, LogType.Assert);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Misc
|
namespace IW4MAdmin.Application.Misc
|
||||||
{
|
{
|
||||||
@ -24,10 +26,9 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
private readonly ApplicationConfiguration _appConfig;
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
private readonly BuildNumber _fallbackVersion = BuildNumber.Parse("99.99.99.99");
|
private readonly BuildNumber _fallbackVersion = BuildNumber.Parse("99.99.99.99");
|
||||||
private readonly int _apiVersion = 1;
|
private readonly int _apiVersion = 1;
|
||||||
|
|
||||||
private bool firstHeartBeat = true;
|
private bool firstHeartBeat = true;
|
||||||
|
|
||||||
public MasterCommunication(ILogger logger, ApplicationConfiguration appConfig, ITranslationLookup translationLookup, IMasterApi apiInstance, IManager manager)
|
public MasterCommunication(ILogger<MasterCommunication> logger, ApplicationConfiguration appConfig, ITranslationLookup translationLookup, IMasterApi apiInstance, IManager manager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_transLookup = translationLookup;
|
_transLookup = translationLookup;
|
||||||
@ -55,13 +56,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning(_transLookup["MANAGER_VERSION_FAIL"]);
|
_logger.LogWarning(e, "Unable to retrieve IW4MAdmin version information");
|
||||||
while (e.InnerException != null)
|
|
||||||
{
|
|
||||||
e = e.InnerException;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.WriteDebug(e.Message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version.CurrentVersionStable == _fallbackVersion)
|
if (version.CurrentVersionStable == _fallbackVersion)
|
||||||
@ -110,12 +105,12 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
catch (System.Net.Http.HttpRequestException e)
|
catch (System.Net.Http.HttpRequestException e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
_logger.LogWarning(e, "Could not send heartbeat");
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (AggregateException e)
|
catch (AggregateException e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
_logger.LogWarning(e, "Could not send heartbeat");
|
||||||
var exceptions = e.InnerExceptions.Where(ex => ex.GetType() == typeof(ApiException));
|
var exceptions = e.InnerExceptions.Where(ex => ex.GetType() == typeof(ApiException));
|
||||||
|
|
||||||
foreach (var ex in exceptions)
|
foreach (var ex in exceptions)
|
||||||
@ -129,7 +124,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
_logger.LogWarning(e, "Could not send heartbeat");
|
||||||
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
{
|
{
|
||||||
connected = false;
|
connected = false;
|
||||||
@ -138,7 +133,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
_logger.LogWarning(e, "Could not send heartbeat");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -202,7 +197,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK)
|
if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Response code from master is {response.ResponseMessage.StatusCode}, message is {response.StringContent}");
|
_logger.LogWarning("Non success response code from master is {statusCode}, message is {message}", response.ResponseMessage.StatusCode, response.StringContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Misc
|
namespace IW4MAdmin.Application.Misc
|
||||||
{
|
{
|
||||||
@ -20,14 +22,14 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public MetaService(ILogger logger, IDatabaseContextFactory contextFactory)
|
public MetaService(ILogger<MetaService> logger, IDatabaseContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_metaActions = new Dictionary<MetaType, List<dynamic>>();
|
_metaActions = new Dictionary<MetaType, List<dynamic>>();
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddPersistentMeta(string metaKey, string metaValue, EFClient client)
|
public async Task AddPersistentMeta(string metaKey, string metaValue, EFClient client, EFMeta linkedMeta = null)
|
||||||
{
|
{
|
||||||
// this seems to happen if the client disconnects before they've had time to authenticate and be added
|
// this seems to happen if the client disconnects before they've had time to authenticate and be added
|
||||||
if (client.ClientId < 1)
|
if (client.ClientId < 1)
|
||||||
@ -35,7 +37,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var ctx = _contextFactory.CreateContext();
|
await using var ctx = _contextFactory.CreateContext();
|
||||||
|
|
||||||
var existingMeta = await ctx.EFMeta
|
var existingMeta = await ctx.EFMeta
|
||||||
.Where(_meta => _meta.Key == metaKey)
|
.Where(_meta => _meta.Key == metaKey)
|
||||||
@ -46,6 +48,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
{
|
{
|
||||||
existingMeta.Value = metaValue;
|
existingMeta.Value = metaValue;
|
||||||
existingMeta.Updated = DateTime.UtcNow;
|
existingMeta.Updated = DateTime.UtcNow;
|
||||||
|
existingMeta.LinkedMetaId = linkedMeta?.MetaId;
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
@ -55,16 +58,101 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
ClientId = client.ClientId,
|
ClientId = client.ClientId,
|
||||||
Created = DateTime.UtcNow,
|
Created = DateTime.UtcNow,
|
||||||
Key = metaKey,
|
Key = metaKey,
|
||||||
Value = metaValue
|
Value = metaValue,
|
||||||
|
LinkedMetaId = linkedMeta?.MetaId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AddPersistentMeta(string metaKey, string metaValue)
|
||||||
|
{
|
||||||
|
await using var ctx = _contextFactory.CreateContext();
|
||||||
|
|
||||||
|
var existingMeta = await ctx.EFMeta
|
||||||
|
.Where(meta => meta.Key == metaKey)
|
||||||
|
.Where(meta => meta.ClientId == null)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var matchValues = existingMeta
|
||||||
|
.Where(meta => meta.Value == metaValue)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (matchValues.Any())
|
||||||
|
{
|
||||||
|
foreach (var meta in matchValues)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Updating existing meta with key {key} and id {id}", meta.Key, meta.MetaId);
|
||||||
|
meta.Value = metaValue;
|
||||||
|
meta.Updated = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Adding new meta with key {key}", metaKey);
|
||||||
|
|
||||||
|
ctx.EFMeta.Add(new EFMeta()
|
||||||
|
{
|
||||||
|
Created = DateTime.UtcNow,
|
||||||
|
Key = metaKey,
|
||||||
|
Value = metaValue
|
||||||
|
});
|
||||||
|
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemovePersistentMeta(string metaKey, EFClient client)
|
||||||
|
{
|
||||||
|
await using var context = _contextFactory.CreateContext();
|
||||||
|
|
||||||
|
var existingMeta = await context.EFMeta
|
||||||
|
.FirstOrDefaultAsync(meta => meta.Key == metaKey && meta.ClientId == client.ClientId);
|
||||||
|
|
||||||
|
if (existingMeta == null)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("No meta with key {key} found for client id {id}", metaKey, client.ClientId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Removing meta for key {key} with id {id}", metaKey, existingMeta.MetaId);
|
||||||
|
context.EFMeta.Remove(existingMeta);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemovePersistentMeta(string metaKey, string metaValue = null)
|
||||||
|
{
|
||||||
|
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
var existingMeta = await context.EFMeta
|
||||||
|
.Where(meta => meta.Key == metaKey)
|
||||||
|
.Where(meta => meta.ClientId == null)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (metaValue == null)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Removing all meta for key {key} with ids [{ids}] ", metaKey, string.Join(", ", existingMeta.Select(meta => meta.MetaId)));
|
||||||
|
existingMeta.ForEach(meta => context.Remove(existingMeta));
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundMeta = existingMeta.FirstOrDefault(meta => meta.Value == metaValue);
|
||||||
|
|
||||||
|
if (foundMeta != null)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Removing meta for key {key} with id {id}", metaKey, foundMeta.MetaId);
|
||||||
|
context.Remove(foundMeta);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client)
|
public async Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client)
|
||||||
{
|
{
|
||||||
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
|
||||||
return await ctx.EFMeta
|
return await ctx.EFMeta
|
||||||
.Where(_meta => _meta.Key == metaKey)
|
.Where(_meta => _meta.Key == metaKey)
|
||||||
@ -74,11 +162,34 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
MetaId = _meta.MetaId,
|
MetaId = _meta.MetaId,
|
||||||
Key = _meta.Key,
|
Key = _meta.Key,
|
||||||
ClientId = _meta.ClientId,
|
ClientId = _meta.ClientId,
|
||||||
Value = _meta.Value
|
Value = _meta.Value,
|
||||||
|
LinkedMetaId = _meta.LinkedMetaId,
|
||||||
|
LinkedMeta = _meta.LinkedMetaId != null ? new EFMeta()
|
||||||
|
{
|
||||||
|
MetaId = _meta.LinkedMeta.MetaId,
|
||||||
|
Key = _meta.LinkedMeta.Key,
|
||||||
|
Value = _meta.LinkedMeta.Value
|
||||||
|
} : null
|
||||||
})
|
})
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<EFMeta>> GetPersistentMeta(string metaKey)
|
||||||
|
{
|
||||||
|
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
return await context.EFMeta
|
||||||
|
.Where(meta => meta.Key == metaKey)
|
||||||
|
.Where(meta => meta.ClientId == null)
|
||||||
|
.Select(meta => new EFMeta
|
||||||
|
{
|
||||||
|
MetaId = meta.MetaId,
|
||||||
|
Key = meta.Key,
|
||||||
|
ClientId = meta.ClientId,
|
||||||
|
Value = meta.Value,
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public void AddRuntimeMeta<T, V>(MetaType metaKey, Func<T, Task<IEnumerable<V>>> metaAction) where T : PaginationRequest where V : IClientMeta
|
public void AddRuntimeMeta<T, V>(MetaType metaKey, Func<T, Task<IEnumerable<V>>> metaAction) where T : PaginationRequest where V : IClientMeta
|
||||||
{
|
{
|
||||||
if (!_metaActions.ContainsKey(metaKey))
|
if (!_metaActions.ContainsKey(metaKey))
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore.Interfaces;
|
||||||
using SharedLibraryCore.Interfaces;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Misc
|
namespace IW4MAdmin.Application.Misc
|
||||||
{
|
{
|
||||||
@ -11,7 +12,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
private readonly IDictionary<string, IList<object>> _actions;
|
private readonly IDictionary<string, IList<object>> _actions;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public MiddlewareActionHandler(ILogger logger)
|
public MiddlewareActionHandler(ILogger<MiddlewareActionHandler> logger)
|
||||||
{
|
{
|
||||||
_actions = new Dictionary<string, IList<object>>();
|
_actions = new Dictionary<string, IList<object>>();
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -38,8 +39,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Failed to invoke middleware action {name}");
|
_logger.LogWarning(e, "Failed to invoke middleware action {name}", name);
|
||||||
_logger.WriteDebug(e.GetExceptionInfo());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,9 +5,12 @@ using System.Reflection;
|
|||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using IW4MAdmin.Application.Misc;
|
using IW4MAdmin.Application.API.Master;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Helpers
|
namespace IW4MAdmin.Application.Misc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// implementation of IPluginImporter
|
/// implementation of IPluginImporter
|
||||||
@ -15,12 +18,19 @@ namespace IW4MAdmin.Application.Helpers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PluginImporter : IPluginImporter
|
public class PluginImporter : IPluginImporter
|
||||||
{
|
{
|
||||||
|
private IEnumerable<PluginSubscriptionContent> _pluginSubscription;
|
||||||
private static readonly string PLUGIN_DIR = "Plugins";
|
private static readonly string PLUGIN_DIR = "Plugins";
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly IRemoteAssemblyHandler _remoteAssemblyHandler;
|
||||||
|
private readonly IMasterApi _masterApi;
|
||||||
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
|
||||||
public PluginImporter(ILogger logger)
|
public PluginImporter(ILogger<PluginImporter> logger, ApplicationConfiguration appConfig, IMasterApi masterApi, IRemoteAssemblyHandler remoteAssemblyHandler)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_masterApi = masterApi;
|
||||||
|
_remoteAssemblyHandler = remoteAssemblyHandler;
|
||||||
|
_appConfig = appConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -33,16 +43,16 @@ namespace IW4MAdmin.Application.Helpers
|
|||||||
|
|
||||||
if (Directory.Exists(pluginDir))
|
if (Directory.Exists(pluginDir))
|
||||||
{
|
{
|
||||||
string[] scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js");
|
var scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts());
|
||||||
|
|
||||||
_logger.WriteInfo($"Discovered {scriptPluginFiles.Length} potential script plugins");
|
_logger.LogDebug("Discovered {count} potential script plugins", scriptPluginFiles.Count());
|
||||||
|
|
||||||
if (scriptPluginFiles.Length > 0)
|
if (scriptPluginFiles.Count() > 0)
|
||||||
{
|
{
|
||||||
foreach (string fileName in scriptPluginFiles)
|
foreach (string fileName in scriptPluginFiles)
|
||||||
{
|
{
|
||||||
_logger.WriteInfo($"Discovered script plugin {fileName}");
|
_logger.LogDebug("Discovered script plugin {fileName}", fileName);
|
||||||
var plugin = new ScriptPlugin(fileName);
|
var plugin = new ScriptPlugin(_logger, fileName);
|
||||||
yield return plugin;
|
yield return plugin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,27 +72,70 @@ namespace IW4MAdmin.Application.Helpers
|
|||||||
if (Directory.Exists(pluginDir))
|
if (Directory.Exists(pluginDir))
|
||||||
{
|
{
|
||||||
var dllFileNames = Directory.GetFiles(pluginDir, "*.dll");
|
var dllFileNames = Directory.GetFiles(pluginDir, "*.dll");
|
||||||
_logger.WriteInfo($"Discovered {dllFileNames.Length} potential plugin assemblies");
|
_logger.LogDebug("Discovered {count} potential plugin assemblies", dllFileNames.Length);
|
||||||
|
|
||||||
if (dllFileNames.Length > 0)
|
if (dllFileNames.Length > 0)
|
||||||
{
|
{
|
||||||
var assemblies = dllFileNames.Select(_name => Assembly.LoadFrom(_name));
|
// we only want to load the most recent assembly in case of duplicates
|
||||||
|
var assemblies = dllFileNames.Select(_name => Assembly.LoadFrom(_name))
|
||||||
|
.Union(GetRemoteAssemblies())
|
||||||
|
.GroupBy(_assembly => _assembly.FullName).Select(_assembly => _assembly.OrderByDescending(_assembly => _assembly.GetName().Version).First());
|
||||||
|
|
||||||
pluginTypes = assemblies
|
pluginTypes = assemblies
|
||||||
.SelectMany(_asm => _asm.GetTypes())
|
.SelectMany(_asm => _asm.GetTypes())
|
||||||
.Where(_assemblyType => _assemblyType.GetInterface(nameof(IPlugin), false) != null);
|
.Where(_assemblyType => _assemblyType.GetInterface(nameof(IPlugin), false) != null);
|
||||||
|
|
||||||
_logger.WriteInfo($"Discovered {pluginTypes.Count()} plugin implementations");
|
_logger.LogDebug("Discovered {count} plugin implementations", pluginTypes.Count());
|
||||||
|
|
||||||
commandTypes = assemblies
|
commandTypes = assemblies
|
||||||
.SelectMany(_asm => _asm.GetTypes())
|
.SelectMany(_asm => _asm.GetTypes())
|
||||||
.Where(_assemblyType => _assemblyType.IsClass && _assemblyType.BaseType == typeof(Command));
|
.Where(_assemblyType => _assemblyType.IsClass && _assemblyType.BaseType == typeof(Command));
|
||||||
|
|
||||||
_logger.WriteInfo($"Discovered {commandTypes.Count()} plugin commands");
|
_logger.LogDebug("Discovered {count} plugin commands", commandTypes.Count());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (pluginTypes, commandTypes);
|
return (pluginTypes, commandTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Assembly> GetRemoteAssemblies()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_pluginSubscription == null)
|
||||||
|
_pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
||||||
|
|
||||||
|
return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Could not load remote assemblies");
|
||||||
|
return Enumerable.Empty<Assembly>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<string> GetRemoteScripts()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_pluginSubscription == null)
|
||||||
|
_pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
||||||
|
|
||||||
|
return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex,"Could not load remote scripts");
|
||||||
|
return Enumerable.Empty<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PluginType
|
||||||
|
{
|
||||||
|
Binary,
|
||||||
|
Script
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
76
Application/Misc/RemoteAssemblyHandler.cs
Normal file
76
Application/Misc/RemoteAssemblyHandler.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc
|
||||||
|
{
|
||||||
|
public class RemoteAssemblyHandler : IRemoteAssemblyHandler
|
||||||
|
{
|
||||||
|
private const int keyLength = 32;
|
||||||
|
private const int tagLength = 16;
|
||||||
|
private const int nonceLength = 12;
|
||||||
|
private const int iterationCount = 10000;
|
||||||
|
|
||||||
|
private readonly ApplicationConfiguration _appconfig;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public RemoteAssemblyHandler(ILogger<RemoteAssemblyHandler> logger, ApplicationConfiguration appconfig)
|
||||||
|
{
|
||||||
|
_appconfig = appconfig;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Assembly> DecryptAssemblies(string[] encryptedAssemblies)
|
||||||
|
{
|
||||||
|
return DecryptContent(encryptedAssemblies)
|
||||||
|
.Select(decryptedAssembly => Assembly.Load(decryptedAssembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> DecryptScripts(string[] encryptedScripts)
|
||||||
|
{
|
||||||
|
return DecryptContent(encryptedScripts).Select(decryptedScript => Encoding.UTF8.GetString(decryptedScript));
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[][] DecryptContent(string[] content)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_appconfig.Id) || string.IsNullOrWhiteSpace(_appconfig.SubscriptionId))
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"{nameof(_appconfig.Id)} and {nameof(_appconfig.SubscriptionId)} must be provided to attempt loading remote assemblies/scripts");
|
||||||
|
return new byte[0][];
|
||||||
|
}
|
||||||
|
|
||||||
|
var assemblies = content.Select(piece =>
|
||||||
|
{
|
||||||
|
byte[] byteContent = Convert.FromBase64String(piece);
|
||||||
|
byte[] encryptedContent = byteContent.Take(byteContent.Length - (tagLength + nonceLength)).ToArray();
|
||||||
|
byte[] tag = byteContent.Skip(byteContent.Length - (tagLength + nonceLength)).Take(tagLength).ToArray();
|
||||||
|
byte[] nonce = byteContent.Skip(byteContent.Length - nonceLength).Take(nonceLength).ToArray();
|
||||||
|
byte[] decryptedContent = new byte[encryptedContent.Length];
|
||||||
|
|
||||||
|
var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id.ToString()), iterationCount, HashAlgorithmName.SHA512);
|
||||||
|
var encryption = new AesGcm(keyGen.GetBytes(keyLength));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
encryption.Decrypt(nonce, encryptedContent, tag, decryptedContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (CryptographicException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Could not decrypt remote plugin assemblies");
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptedContent;
|
||||||
|
});
|
||||||
|
|
||||||
|
return assemblies.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,9 @@ using SharedLibraryCore.Configuration;
|
|||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using static SharedLibraryCore.Database.Models.EFClient;
|
using static SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Misc
|
namespace IW4MAdmin.Application.Misc
|
||||||
{
|
{
|
||||||
@ -14,28 +16,38 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
public class ScriptCommand : Command
|
public class ScriptCommand : Command
|
||||||
{
|
{
|
||||||
private readonly Action<GameEvent> _executeAction;
|
private readonly Action<GameEvent> _executeAction;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public ScriptCommand(string name, string alias, string description, Permission permission,
|
public ScriptCommand(string name, string alias, string description, bool isTargetRequired, Permission permission,
|
||||||
CommandArgument[] args, Action<GameEvent> executeAction, CommandConfiguration config, ITranslationLookup layout)
|
CommandArgument[] args, Action<GameEvent> executeAction, CommandConfiguration config, ITranslationLookup layout, ILogger<ScriptCommand> logger)
|
||||||
: base(config, layout)
|
: base(config, layout)
|
||||||
{
|
{
|
||||||
|
|
||||||
_executeAction = executeAction;
|
_executeAction = executeAction;
|
||||||
|
_logger = logger;
|
||||||
Name = name;
|
Name = name;
|
||||||
Alias = alias;
|
Alias = alias;
|
||||||
Description = description;
|
Description = description;
|
||||||
|
RequiresTarget = isTargetRequired;
|
||||||
Permission = permission;
|
Permission = permission;
|
||||||
Arguments = args;
|
Arguments = args;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent e)
|
||||||
{
|
{
|
||||||
if (_executeAction == null)
|
if (_executeAction == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"No execute action defined for command \"{Name}\"");
|
throw new InvalidOperationException($"No execute action defined for command \"{Name}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.Run(() => _executeAction(E));
|
try
|
||||||
|
{
|
||||||
|
await Task.Run(() => _executeAction(e));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to execute ScriptCommand action for command {command} {@event}", Name, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Jint;
|
using System;
|
||||||
|
using Jint;
|
||||||
using Jint.Native;
|
using Jint.Native;
|
||||||
using Jint.Runtime;
|
using Jint.Runtime;
|
||||||
using Microsoft.CSharp.RuntimeBinder;
|
using Microsoft.CSharp.RuntimeBinder;
|
||||||
@ -12,6 +13,9 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog.Context;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Misc
|
namespace IW4MAdmin.Application.Misc
|
||||||
{
|
{
|
||||||
@ -39,9 +43,11 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
private readonly SemaphoreSlim _onProcessing;
|
private readonly SemaphoreSlim _onProcessing;
|
||||||
private bool successfullyLoaded;
|
private bool successfullyLoaded;
|
||||||
private readonly List<string> _registeredCommandNames;
|
private readonly List<string> _registeredCommandNames;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public ScriptPlugin(string filename, string workingDirectory = null)
|
public ScriptPlugin(ILogger logger, string filename, string workingDirectory = null)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_fileName = filename;
|
_fileName = filename;
|
||||||
Watcher = new FileSystemWatcher()
|
Watcher = new FileSystemWatcher()
|
||||||
{
|
{
|
||||||
@ -61,7 +67,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
_onProcessing.Dispose();
|
_onProcessing.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory)
|
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory, IScriptPluginServiceResolver serviceResolver)
|
||||||
{
|
{
|
||||||
await _onProcessing.WaitAsync();
|
await _onProcessing.WaitAsync();
|
||||||
|
|
||||||
@ -84,7 +90,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
foreach (string commandName in _registeredCommandNames)
|
foreach (string commandName in _registeredCommandNames)
|
||||||
{
|
{
|
||||||
manager.GetLogger(0).WriteDebug($"Removing plugin registered command \"{commandName}\"");
|
_logger.LogDebug("Removing plugin registered command {command}", commandName);
|
||||||
manager.RemoveCommandByName(commandName);
|
manager.RemoveCommandByName(commandName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,8 +118,30 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
})
|
})
|
||||||
.CatchClrExceptions());
|
.CatchClrExceptions());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
_scriptEngine.Execute(script);
|
_scriptEngine.Execute(script);
|
||||||
|
}
|
||||||
|
catch (JavaScriptException ex)
|
||||||
|
{
|
||||||
|
|
||||||
|
_logger.LogError(ex,
|
||||||
|
"Encountered JavaScript runtime error while executing {methodName} for script plugin {plugin} at {@locationInfo}",
|
||||||
|
nameof(Initialize), _fileName, ex.Location);
|
||||||
|
throw new PluginException($"A JavaScript parsing error occured while initializing script plugin");
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
|
||||||
|
_logger.LogError(e,
|
||||||
|
"Encountered unexpected error while running {methodName} for script plugin {plugin}",
|
||||||
|
nameof(Initialize), _fileName);
|
||||||
|
throw new PluginException($"An unexpected error occured while initialization script plugin");
|
||||||
|
}
|
||||||
|
|
||||||
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
|
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
|
||||||
|
_scriptEngine.SetValue("_serviceResolver", serviceResolver);
|
||||||
dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject();
|
dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject();
|
||||||
|
|
||||||
Author = pluginObject.author;
|
Author = pluginObject.author;
|
||||||
@ -128,7 +156,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
{
|
{
|
||||||
foreach (var command in GenerateScriptCommands(commands, scriptCommandFactory))
|
foreach (var command in GenerateScriptCommands(commands, scriptCommandFactory))
|
||||||
{
|
{
|
||||||
manager.GetLogger(0).WriteDebug($"Adding plugin registered command \"{command.Name}\"");
|
_logger.LogDebug("Adding plugin registered command {commandName}", command.Name);
|
||||||
manager.AddAdditionalCommand(command);
|
manager.AddAdditionalCommand(command);
|
||||||
_registeredCommandNames.Add(command.Name);
|
_registeredCommandNames.Add(command.Name);
|
||||||
}
|
}
|
||||||
@ -164,9 +192,22 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
successfullyLoaded = true;
|
successfullyLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch
|
catch (JavaScriptException ex)
|
||||||
{
|
{
|
||||||
throw;
|
_logger.LogError(ex,
|
||||||
|
"Encountered JavaScript runtime error while executing {methodName} for script plugin {plugin} initialization {@locationInfo}",
|
||||||
|
nameof(OnLoadAsync), _fileName, ex.Location);
|
||||||
|
|
||||||
|
throw new PluginException("An error occured while initializing script plugin");
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex,
|
||||||
|
"Encountered unexpected error while running {methodName} for script plugin {plugin}",
|
||||||
|
nameof(OnLoadAsync), _fileName);
|
||||||
|
|
||||||
|
throw new PluginException("An unexpected error occured while initializing script plugin");
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
@ -192,9 +233,28 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
_scriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue();
|
_scriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
catch
|
catch (JavaScriptException ex)
|
||||||
{
|
{
|
||||||
throw;
|
using (LogContext.PushProperty("Server", S.ToString()))
|
||||||
|
{
|
||||||
|
_logger.LogError(ex,
|
||||||
|
"Encountered JavaScript runtime error while executing {methodName} for script plugin {plugin} with event type {eventType} {@locationInfo}",
|
||||||
|
nameof(OnEventAsync), _fileName, E.Type, ex.Location);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PluginException($"An error occured while executing action for script plugin");
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
using (LogContext.PushProperty("Server", S.ToString()))
|
||||||
|
{
|
||||||
|
_logger.LogError(e,
|
||||||
|
"Encountered unexpected error while running {methodName} for script plugin {plugin} with event type {eventType}",
|
||||||
|
nameof(OnEventAsync), _fileName, E.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PluginException($"An error occured while executing action for script plugin");
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
@ -209,7 +269,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
public Task OnLoadAsync(IManager manager)
|
public Task OnLoadAsync(IManager manager)
|
||||||
{
|
{
|
||||||
manager.GetLogger(0).WriteDebug($"OnLoad executing for {Name}");
|
_logger.LogDebug("OnLoad executing for {name}", Name);
|
||||||
_scriptEngine.SetValue("_manager", manager);
|
_scriptEngine.SetValue("_manager", manager);
|
||||||
return Task.FromResult(_scriptEngine.Execute("plugin.onLoadAsync(_manager)").GetCompletionValue());
|
return Task.FromResult(_scriptEngine.Execute("plugin.onLoadAsync(_manager)").GetCompletionValue());
|
||||||
}
|
}
|
||||||
@ -246,6 +306,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
string alias = dynamicCommand.alias;
|
string alias = dynamicCommand.alias;
|
||||||
string description = dynamicCommand.description;
|
string description = dynamicCommand.description;
|
||||||
string permission = dynamicCommand.permission;
|
string permission = dynamicCommand.permission;
|
||||||
|
bool targetRequired = false;
|
||||||
|
|
||||||
List<(string, bool)> args = new List<(string, bool)>();
|
List<(string, bool)> args = new List<(string, bool)>();
|
||||||
dynamic arguments = null;
|
dynamic arguments = null;
|
||||||
@ -260,6 +321,16 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
// arguments are optional
|
// arguments are optional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
targetRequired = dynamicCommand.targetRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (RuntimeBinderException)
|
||||||
|
{
|
||||||
|
// arguments are optional
|
||||||
|
}
|
||||||
|
|
||||||
if (arguments != null)
|
if (arguments != null)
|
||||||
{
|
{
|
||||||
foreach (var arg in dynamicCommand.arguments)
|
foreach (var arg in dynamicCommand.arguments)
|
||||||
@ -284,7 +355,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, args, execute));
|
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, targetRequired, args, execute));
|
||||||
}
|
}
|
||||||
|
|
||||||
return commandList;
|
return commandList;
|
||||||
|
48
Application/Misc/ScriptPluginServiceResolver.cs
Normal file
48
Application/Misc/ScriptPluginServiceResolver.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// implementation of IScriptPluginServiceResolver
|
||||||
|
/// </summary>
|
||||||
|
public class ScriptPluginServiceResolver : IScriptPluginServiceResolver
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public ScriptPluginServiceResolver(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ResolveService(string serviceName)
|
||||||
|
{
|
||||||
|
var serviceType = DetermineRootType(serviceName);
|
||||||
|
return _serviceProvider.GetService(serviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ResolveService(string serviceName, string[] genericParameters)
|
||||||
|
{
|
||||||
|
var serviceType = DetermineRootType(serviceName, genericParameters.Length);
|
||||||
|
var genericTypes = genericParameters.Select(_genericTypeParam => DetermineRootType(_genericTypeParam));
|
||||||
|
var resolvedServiceType = serviceType.MakeGenericType(genericTypes.ToArray());
|
||||||
|
return _serviceProvider.GetService(resolvedServiceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type DetermineRootType(string serviceName, int genericParamCount = 0)
|
||||||
|
{
|
||||||
|
var typeCollection = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
.SelectMany(t => t.GetTypes());
|
||||||
|
string generatedName = $"{serviceName}{(genericParamCount == 0 ? "" : $"`{genericParamCount}")}".ToLower();
|
||||||
|
var serviceType = typeCollection.FirstOrDefault(_type => _type.Name.ToLower() == generatedName);
|
||||||
|
|
||||||
|
if (serviceType == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"No object type '{serviceName}' defined in loaded assemblies");
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int ConnectionAttempts { get; set; }
|
public int ConnectionAttempts { get; set; }
|
||||||
const int BufferSize = 4096;
|
private const int BufferSize = 16384;
|
||||||
public readonly byte[] ReceiveBuffer = new byte[BufferSize];
|
public readonly byte[] ReceiveBuffer = new byte[BufferSize];
|
||||||
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
|
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
|
||||||
public readonly ManualResetEventSlim OnSentData = new ManualResetEventSlim(false);
|
public readonly ManualResetEventSlim OnSentData = new ManualResetEventSlim(false);
|
||||||
|
@ -10,7 +10,11 @@ using System.Net;
|
|||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog.Context;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.RCon
|
namespace IW4MAdmin.Application.RCon
|
||||||
{
|
{
|
||||||
@ -23,11 +27,12 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
public IPEndPoint Endpoint { get; private set; }
|
public IPEndPoint Endpoint { get; private set; }
|
||||||
public string RConPassword { get; private set; }
|
public string RConPassword { get; private set; }
|
||||||
|
|
||||||
|
private IRConParser parser;
|
||||||
private IRConParserConfiguration config;
|
private IRConParserConfiguration config;
|
||||||
private readonly ILogger _log;
|
private readonly ILogger _log;
|
||||||
private readonly Encoding _gameEncoding;
|
private readonly Encoding _gameEncoding;
|
||||||
|
|
||||||
public RConConnection(string ipAddress, int port, string password, ILogger log, Encoding gameEncoding)
|
public RConConnection(string ipAddress, int port, string password, ILogger<RConConnection> log, Encoding gameEncoding)
|
||||||
{
|
{
|
||||||
Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
|
Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
|
||||||
_gameEncoding = gameEncoding;
|
_gameEncoding = gameEncoding;
|
||||||
@ -35,9 +40,10 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
_log = log;
|
_log = log;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetConfiguration(IRConParserConfiguration config)
|
public void SetConfiguration(IRConParser parser)
|
||||||
{
|
{
|
||||||
this.config = config;
|
this.parser = parser;
|
||||||
|
config = parser.Configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
|
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
|
||||||
@ -49,9 +55,8 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
var connectionState = ActiveQueries[this.Endpoint];
|
var connectionState = ActiveQueries[this.Endpoint];
|
||||||
|
|
||||||
#if DEBUG == true
|
_log.LogDebug("Waiting for semaphore to be released [{endpoint}]", Endpoint);
|
||||||
_log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]");
|
|
||||||
#endif
|
|
||||||
// enter the semaphore so only one query is sent at a time per server.
|
// enter the semaphore so only one query is sent at a time per server.
|
||||||
await connectionState.OnComplete.WaitAsync();
|
await connectionState.OnComplete.WaitAsync();
|
||||||
|
|
||||||
@ -64,10 +69,8 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
connectionState.LastQuery = DateTime.Now;
|
connectionState.LastQuery = DateTime.Now;
|
||||||
|
|
||||||
#if DEBUG == true
|
_log.LogDebug("Semaphore has been released [{endpoint}]", Endpoint);
|
||||||
_log.WriteDebug($"Semaphore has been released [{this.Endpoint}]");
|
_log.LogDebug("Query {@queryInfo}", new { endpoint=Endpoint.ToString(), type, parameters });
|
||||||
_log.WriteDebug($"Query [{this.Endpoint},{type.ToString()},{parameters}]");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
byte[] payload = null;
|
byte[] payload = null;
|
||||||
bool waitForResponse = config.WaitForResponse;
|
bool waitForResponse = config.WaitForResponse;
|
||||||
@ -112,18 +115,34 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
// this happens when someone tries to send something that can't be converted into a 7 bit character set
|
// this happens when someone tries to send something that can't be converted into a 7 bit character set
|
||||||
// e.g: emoji -> windows-1252
|
// e.g: emoji -> windows-1252
|
||||||
catch (OverflowException)
|
catch (OverflowException ex)
|
||||||
{
|
{
|
||||||
connectionState.OnComplete.Release(1);
|
connectionState.OnComplete.Release(1);
|
||||||
throw new NetworkException($"Invalid character encountered when converting encodings - {parameters}");
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Could not convert RCon data payload to desired encoding {encoding} {params}",
|
||||||
|
_gameEncoding.EncodingName, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RConException($"Invalid character encountered when converting encodings");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[][] response = null;
|
byte[][] response = null;
|
||||||
|
|
||||||
retrySend:
|
retrySend:
|
||||||
|
if (connectionState.ConnectionAttempts > 1)
|
||||||
|
{
|
||||||
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
|
{
|
||||||
|
_log.LogInformation(
|
||||||
|
"Retrying RCon message ({connectionAttempts}/{allowedConnectionFailures} attempts) with parameters {payload}",
|
||||||
|
connectionState.ConnectionAttempts,
|
||||||
|
StaticHelpers.AllowedConnectionFails, parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
|
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
|
||||||
{
|
{
|
||||||
DontFragment = true,
|
DontFragment = false,
|
||||||
Ttl = 100,
|
Ttl = 100,
|
||||||
ExclusiveAddressUse = true,
|
ExclusiveAddressUse = true,
|
||||||
})
|
})
|
||||||
@ -133,16 +152,19 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
connectionState.OnReceivedData.Reset();
|
connectionState.OnReceivedData.Reset();
|
||||||
connectionState.ConnectionAttempts++;
|
connectionState.ConnectionAttempts++;
|
||||||
connectionState.BytesReadPerSegment.Clear();
|
connectionState.BytesReadPerSegment.Clear();
|
||||||
#if DEBUG == true
|
bool exceptionCaught = false;
|
||||||
_log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
|
|
||||||
#endif
|
_log.LogDebug("Sending {payloadLength} bytes to [{endpoint}] ({connectionAttempts}/{allowedConnectionFailures})",
|
||||||
|
payload.Length, Endpoint, connectionState.ConnectionAttempts, StaticHelpers.AllowedConnectionFails);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
response = await SendPayloadAsync(payload, waitForResponse);
|
|
||||||
|
response = await SendPayloadAsync(payload, waitForResponse, parser.OverrideTimeoutForCommand(parameters));
|
||||||
|
|
||||||
if ((response.Length == 0 || response[0].Length == 0) && waitForResponse)
|
if ((response.Length == 0 || response[0].Length == 0) && waitForResponse)
|
||||||
{
|
{
|
||||||
throw new NetworkException("Expected response but got 0 bytes back");
|
throw new RConException("Expected response but got 0 bytes back");
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionState.ConnectionAttempts = 0;
|
connectionState.ConnectionAttempts = 0;
|
||||||
@ -150,18 +172,28 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
// we want to retry with a delay
|
||||||
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
|
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
|
||||||
{
|
{
|
||||||
await Task.Delay(StaticHelpers.FloodProtectionInterval);
|
exceptionCaught = true;
|
||||||
|
await Task.Delay(StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts));
|
||||||
goto retrySend;
|
goto retrySend;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"].FormatExt(Endpoint));
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
|
{
|
||||||
|
_log.LogWarning(
|
||||||
|
"Made {connectionAttempts} attempts to send RCon data to server, but received no response",
|
||||||
|
connectionState.ConnectionAttempts);
|
||||||
|
}
|
||||||
|
connectionState.ConnectionAttempts = 0;
|
||||||
|
throw new NetworkException("Reached maximum retry attempts to send RCon data to server");
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (connectionState.OnComplete.CurrentCount == 0)
|
// we don't want to release if we're going to retry the query
|
||||||
|
if (connectionState.OnComplete.CurrentCount == 0 && !exceptionCaught)
|
||||||
{
|
{
|
||||||
connectionState.OnComplete.Release(1);
|
connectionState.OnComplete.Release(1);
|
||||||
}
|
}
|
||||||
@ -170,23 +202,22 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (response.Length == 0)
|
if (response.Length == 0)
|
||||||
{
|
{
|
||||||
_log.WriteWarning($"Received empty response for request [{type.ToString()}, {parameters}, {Endpoint.ToString()}]");
|
_log.LogDebug("Received empty response for RCon request {@query}", new { endpoint=Endpoint.ToString(), type, parameters });
|
||||||
return new string[0];
|
return new string[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
||||||
ReassembleSegmentedStatus(response) :
|
ReassembleSegmentedStatus(response) : RecombineMessages(response);
|
||||||
_gameEncoding.GetString(response[0]) + '\n';
|
|
||||||
|
|
||||||
// note: not all games respond if the pasword is wrong or not set
|
// note: not all games respond if the pasword is wrong or not set
|
||||||
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
||||||
{
|
{
|
||||||
throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]);
|
throw new RConException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseString.Contains("rcon_password"))
|
if (responseString.Contains("rcon_password"))
|
||||||
{
|
{
|
||||||
throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_NOTSET"]);
|
throw new RConException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_NOTSET"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseString.Contains(config.ServerNotRunningResponse))
|
if (responseString.Contains(config.ServerNotRunningResponse))
|
||||||
@ -199,7 +230,13 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (headerSplit.Length != 2)
|
if (headerSplit.Length != 2)
|
||||||
{
|
{
|
||||||
throw new NetworkException("Unexpected response header from server");
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
|
{
|
||||||
|
_log.LogWarning("Invalid response header from server. Expected {expected}, but got {response}",
|
||||||
|
config.CommandPrefixes.RConResponse, headerSplit.FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RConException("Unexpected response header from server");
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] splitResponse = headerSplit.Last().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
string[] splitResponse = headerSplit.Last().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
@ -234,7 +271,36 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
return string.Join("", splitStatusStrings);
|
return string.Join("", splitStatusStrings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse)
|
/// <summary>
|
||||||
|
/// Recombines multiple game messages into one
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="payload"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private string RecombineMessages(byte[][] payload)
|
||||||
|
{
|
||||||
|
if (payload.Length == 1)
|
||||||
|
{
|
||||||
|
return _gameEncoding.GetString(payload[0]).TrimEnd('\n') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
for (int i = 0; i < payload.Length; i++)
|
||||||
|
{
|
||||||
|
string message = _gameEncoding.GetString(payload[i]).TrimEnd('\n') + '\n';
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
message = message.Replace(config.CommandPrefixes.RConResponse, "");
|
||||||
|
}
|
||||||
|
builder.Append(message);
|
||||||
|
}
|
||||||
|
builder.Append('\n');
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse, TimeSpan overrideTimeout)
|
||||||
{
|
{
|
||||||
var connectionState = ActiveQueries[this.Endpoint];
|
var connectionState = ActiveQueries[this.Endpoint];
|
||||||
var rconSocket = (Socket)connectionState.SendEventArgs.UserToken;
|
var rconSocket = (Socket)connectionState.SendEventArgs.UserToken;
|
||||||
@ -258,11 +324,17 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (sendDataPending)
|
if (sendDataPending)
|
||||||
{
|
{
|
||||||
// the send has not been completed asyncronously
|
// the send has not been completed asynchronously
|
||||||
if (!await Task.Run(() => connectionState.OnSentData.Wait(StaticHelpers.SocketTimeout)))
|
// this really shouldn't ever happen because it's UDP
|
||||||
|
if (!await Task.Run(() => connectionState.OnSentData.Wait(StaticHelpers.SocketTimeout(1))))
|
||||||
{
|
{
|
||||||
|
using(LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
|
{
|
||||||
|
_log.LogWarning("Socket timed out while sending RCon data on attempt {attempt}",
|
||||||
|
connectionState.ConnectionAttempts);
|
||||||
|
}
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
throw new NetworkException("Timed out sending data", rconSocket);
|
throw new NetworkException("Timed out sending RCon data", rconSocket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,15 +350,37 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (receiveDataPending)
|
if (receiveDataPending)
|
||||||
{
|
{
|
||||||
if (!await Task.Run(() => connectionState.OnReceivedData.Wait(10000)))
|
_log.LogDebug("Waiting to asynchronously receive data on attempt #{connectionAttempts}", connectionState.ConnectionAttempts);
|
||||||
|
if (!await Task.Run(() => connectionState.OnReceivedData.Wait(
|
||||||
|
new[]
|
||||||
{
|
{
|
||||||
|
StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts),
|
||||||
|
overrideTimeout
|
||||||
|
}.Max())))
|
||||||
|
{
|
||||||
|
if (connectionState.ConnectionAttempts > 1) // this reduces some spam for unstable connections
|
||||||
|
{
|
||||||
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
|
{
|
||||||
|
_log.LogWarning(
|
||||||
|
"Socket timed out while waiting for RCon response on attempt {attempt} with timeout delay of {timeout}",
|
||||||
|
connectionState.ConnectionAttempts,
|
||||||
|
StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
throw new NetworkException("Timed out waiting for response", rconSocket);
|
throw new NetworkException("Timed out receiving RCon response", rconSocket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
|
|
||||||
|
return GetResponseData(connectionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[][] GetResponseData(ConnectionState connectionState)
|
||||||
|
{
|
||||||
var responseList = new List<byte[]>();
|
var responseList = new List<byte[]>();
|
||||||
int totalBytesRead = 0;
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
@ -305,58 +399,72 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
_log.LogDebug("Read {bytesTransferred} bytes from {endpoint}", e.BytesTransferred, e.RemoteEndPoint);
|
||||||
_log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// this occurs when we close the socket
|
// this occurs when we close the socket
|
||||||
if (e.BytesTransferred == 0)
|
if (e.BytesTransferred == 0)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("No bytes were transmitted so the connection was probably closed");
|
||||||
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sender is Socket sock)
|
if (!(sender is Socket sock))
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var state = ActiveQueries[this.Endpoint];
|
var state = ActiveQueries[this.Endpoint];
|
||||||
state.BytesReadPerSegment.Add(e.BytesTransferred);
|
state.BytesReadPerSegment.Add(e.BytesTransferred);
|
||||||
|
|
||||||
|
// I don't even want to know why this works for getting more data from Cod4x
|
||||||
|
// but I'm leaving it in here as long as it doesn't break anything.
|
||||||
|
// it's very stupid...
|
||||||
|
Thread.Sleep(150);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var totalBytesTransferred = e.BytesTransferred;
|
||||||
|
_log.LogDebug("{total} total bytes transferred with {available} bytes remaining", totalBytesTransferred, sock.Available);
|
||||||
// we still have available data so the payload was segmented
|
// we still have available data so the payload was segmented
|
||||||
if (sock.Available > 0)
|
while (sock.Available > 0)
|
||||||
{
|
{
|
||||||
state.ReceiveEventArgs.SetBuffer(state.ReceiveBuffer, e.BytesTransferred, state.ReceiveBuffer.Length - e.BytesTransferred);
|
_log.LogDebug("{available} more bytes to be read", sock.Available);
|
||||||
|
|
||||||
if (!sock.ReceiveAsync(state.ReceiveEventArgs))
|
var bufferSpaceAvailable = sock.Available + totalBytesTransferred - state.ReceiveBuffer.Length;
|
||||||
|
if (bufferSpaceAvailable >= 0 )
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
_log.LogWarning("Not enough buffer space to store incoming data {bytesNeeded} additional bytes required", bufferSpaceAvailable);
|
||||||
_log.WriteDebug($"Read {state.ReceiveEventArgs.BytesTransferred} synchronous bytes from {e.RemoteEndPoint.ToString()}");
|
continue;
|
||||||
#endif
|
}
|
||||||
|
|
||||||
|
state.ReceiveEventArgs.SetBuffer(state.ReceiveBuffer, totalBytesTransferred, sock.Available);
|
||||||
|
|
||||||
|
if (sock.ReceiveAsync(state.ReceiveEventArgs))
|
||||||
|
{
|
||||||
|
_log.LogDebug("Remaining bytes are async");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_log.LogDebug("Read {bytesTransferred} synchronous bytes from {endpoint}", state.ReceiveEventArgs.BytesTransferred, e.RemoteEndPoint);
|
||||||
// we need to increment this here because the callback isn't executed if there's no pending IO
|
// we need to increment this here because the callback isn't executed if there's no pending IO
|
||||||
state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred);
|
state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred);
|
||||||
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
totalBytesTransferred += state.ReceiveEventArgs.BytesTransferred;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("Socket was disposed while receiving data");
|
||||||
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
_log.LogDebug("Sent {byteCount} bytes to {endpoint}", e.Buffer?.Length, e.ConnectSocket?.RemoteEndPoint);
|
||||||
_log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
|
|
||||||
#endif
|
|
||||||
ActiveQueries[this.Endpoint].OnSentData.Set();
|
ActiveQueries[this.Endpoint].OnSentData.Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,19 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using static SharedLibraryCore.Server;
|
using static SharedLibraryCore.Server;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.RconParsers
|
namespace IW4MAdmin.Application.RconParsers
|
||||||
{
|
{
|
||||||
public class BaseRConParser : IRConParser
|
public class BaseRConParser : IRConParser
|
||||||
{
|
{
|
||||||
public BaseRConParser(IParserRegexFactory parserRegexFactory)
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public BaseRConParser(ILogger<BaseRConParser> logger, IParserRegexFactory parserRegexFactory)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
Configuration = new DynamicRConParserConfiguration(parserRegexFactory)
|
Configuration = new DynamicRConParserConfiguration(parserRegexFactory)
|
||||||
{
|
{
|
||||||
CommandPrefixes = new CommandPrefix()
|
CommandPrefixes = new CommandPrefix()
|
||||||
@ -76,7 +81,22 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
|
|
||||||
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
||||||
{
|
{
|
||||||
string[] lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
|
string[] lineSplit;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (fallbackValue == null)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
lineSplit = new string[0];
|
||||||
|
}
|
||||||
|
|
||||||
string response = string.Join('\n', lineSplit).TrimEnd('\0');
|
string response = string.Join('\n', lineSplit).TrimEnd('\0');
|
||||||
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
||||||
|
|
||||||
@ -118,12 +138,7 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
public virtual async Task<(List<EFClient>, string, string)> GetStatusAsync(IRConConnection connection)
|
public virtual async Task<(List<EFClient>, string, string)> GetStatusAsync(IRConConnection connection)
|
||||||
{
|
{
|
||||||
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
|
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
|
||||||
#if DEBUG
|
_logger.LogDebug("Status Response {response}", string.Join(Environment.NewLine, response));
|
||||||
foreach (var line in response)
|
|
||||||
{
|
|
||||||
Console.WriteLine(line);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return (ClientsFromStatus(response), MapFromStatus(response), GameTypeFromStatus(response));
|
return (ClientsFromStatus(response), MapFromStatus(response), GameTypeFromStatus(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +205,12 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
|
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
|
if (match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]] == "ZMBI")
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Ignoring detected client {client} because they are zombie state", string.Join(",", match.Values));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int clientNumber = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]]);
|
int clientNumber = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]]);
|
||||||
int score = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]]);
|
int score = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]]);
|
||||||
|
|
||||||
@ -203,12 +224,14 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
|
|
||||||
long networkId;
|
long networkId;
|
||||||
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
||||||
|
string networkIdString;
|
||||||
|
var ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
|
networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
|
||||||
|
|
||||||
networkId = networkIdString.IsBotGuid() ?
|
networkId = networkIdString.IsBotGuid() || (ip == null && ping == 999) ?
|
||||||
name.GenerateGuidFromString() :
|
name.GenerateGuidFromString() :
|
||||||
networkIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
networkIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||||
}
|
}
|
||||||
@ -218,8 +241,6 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int? ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP();
|
|
||||||
|
|
||||||
var client = new EFClient()
|
var client = new EFClient()
|
||||||
{
|
{
|
||||||
CurrentAlias = new EFAlias()
|
CurrentAlias = new EFAlias()
|
||||||
@ -234,6 +255,8 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
State = EFClient.ClientState.Connecting
|
State = EFClient.ClientState.Connecting
|
||||||
};
|
};
|
||||||
|
|
||||||
|
client.SetAdditionalProperty("BotGuid", networkIdString);
|
||||||
|
|
||||||
StatusPlayers.Add(client);
|
StatusPlayers.Add(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,5 +283,16 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
public T GetDefaultDvarValue<T>(string dvarName) => Configuration.DefaultDvarValues.ContainsKey(dvarName) ?
|
public T GetDefaultDvarValue<T>(string dvarName) => Configuration.DefaultDvarValues.ContainsKey(dvarName) ?
|
||||||
(T)Convert.ChangeType(Configuration.DefaultDvarValues[dvarName], typeof(T)) :
|
(T)Convert.ChangeType(Configuration.DefaultDvarValues[dvarName], typeof(T)) :
|
||||||
default;
|
default;
|
||||||
|
|
||||||
|
public TimeSpan OverrideTimeoutForCommand(string command)
|
||||||
|
{
|
||||||
|
if (command.Contains("map_rotate", StringComparison.InvariantCultureIgnoreCase) ||
|
||||||
|
command.StartsWith("map ", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
return TimeSpan.FromSeconds(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimeSpan.Zero;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using SharedLibraryCore.Interfaces;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.RconParsers
|
namespace IW4MAdmin.Application.RconParsers
|
||||||
{
|
{
|
||||||
@ -8,7 +9,7 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
sealed internal class DynamicRConParser : BaseRConParser
|
sealed internal class DynamicRConParser : BaseRConParser
|
||||||
{
|
{
|
||||||
public DynamicRConParser(IParserRegexFactory parserRegexFactory) : base(parserRegexFactory)
|
public DynamicRConParser(ILogger<BaseRConParser> logger, IParserRegexFactory parserRegexFactory) : base(logger, parserRegexFactory)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using SharedLibraryCore.Interfaces;
|
using System;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
using SharedLibraryCore.RCon;
|
using SharedLibraryCore.RCon;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -22,6 +23,9 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
|
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
|
||||||
public IDictionary<string, string> OverrideDvarNameMapping { get; set; } = new Dictionary<string, string>();
|
public IDictionary<string, string> OverrideDvarNameMapping { get; set; } = new Dictionary<string, string>();
|
||||||
public IDictionary<string, string> DefaultDvarValues { get; set; } = new Dictionary<string, string>();
|
public IDictionary<string, string> DefaultDvarValues { get; set; } = new Dictionary<string, string>();
|
||||||
|
public int NoticeMaximumLines { get; set; } = 8;
|
||||||
|
public int NoticeMaxCharactersPerLine { get; set; } = 50;
|
||||||
|
public string NoticeLineSeparator { get; set; } = Environment.NewLine;
|
||||||
|
|
||||||
public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory)
|
public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory)
|
||||||
{
|
{
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
|
name: '$(Date:yyyy.MM.dd)$(Rev:.r)'
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
batch: true
|
batch: true
|
||||||
branches:
|
branches:
|
||||||
include:
|
include:
|
||||||
- releases/*
|
- release/pre
|
||||||
- 2.4-pr
|
- master
|
||||||
paths:
|
|
||||||
exclude:
|
|
||||||
- azure-pipelines.yml
|
|
||||||
- Master/*
|
|
||||||
|
|
||||||
pr: none
|
pr: none
|
||||||
|
|
||||||
@ -17,110 +15,132 @@ pool:
|
|||||||
variables:
|
variables:
|
||||||
solution: 'IW4MAdmin.sln'
|
solution: 'IW4MAdmin.sln'
|
||||||
buildPlatform: 'Any CPU'
|
buildPlatform: 'Any CPU'
|
||||||
buildConfiguration: 'Prerelease'
|
|
||||||
outputFolder: '$(Build.ArtifactStagingDirectory)\Publish\$(buildConfiguration)'
|
outputFolder: '$(Build.ArtifactStagingDirectory)\Publish\$(buildConfiguration)'
|
||||||
|
releaseType: verified
|
||||||
|
buildConfiguration: Stable
|
||||||
|
isPreRelease: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: NuGetToolInstaller@1
|
- task: PowerShell@2
|
||||||
|
displayName: 'Setup Pre-Release configuration'
|
||||||
|
condition: eq(variables['Build.SourceBranch'], 'refs/heads/release/pre')
|
||||||
|
inputs:
|
||||||
|
targetType: 'inline'
|
||||||
|
script: |
|
||||||
|
echo '##vso[task.setvariable variable=releaseType]prerelease'
|
||||||
|
echo '##vso[task.setvariable variable=buildConfiguration]Prerelease'
|
||||||
|
echo '##vso[task.setvariable variable=isPreRelease]true'
|
||||||
|
failOnStderr: true
|
||||||
|
|
||||||
- task: NuGetCommand@2
|
- task: NuGetCommand@2
|
||||||
|
displayName: 'Restore nuget packages'
|
||||||
inputs:
|
inputs:
|
||||||
restoreSolution: '$(solution)'
|
restoreSolution: '$(solution)'
|
||||||
|
|
||||||
- task: PowerShell@2
|
- task: PowerShell@2
|
||||||
|
displayName: 'Preload external resources'
|
||||||
inputs:
|
inputs:
|
||||||
targetType: 'inline'
|
targetType: 'inline'
|
||||||
script: |
|
script: |
|
||||||
|
Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)'
|
||||||
md -Force lib\open-iconic\font\css
|
md -Force lib\open-iconic\font\css
|
||||||
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap.scss
|
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap.scss
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
||||||
|
|
||||||
- task: projectversionasvariable@1
|
|
||||||
inputs:
|
|
||||||
path: '$(Build.Repository.LocalPath)\Application\Application.csproj'
|
|
||||||
|
|
||||||
- task: VSBuild@1
|
- task: VSBuild@1
|
||||||
|
displayName: 'Build projects'
|
||||||
inputs:
|
inputs:
|
||||||
solution: '$(solution)'
|
solution: '$(solution)'
|
||||||
msbuildArgs: '/p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)" /p:Version=$(Version.Major).$(Version.Minor).$(Version.Build).$(Build.BuildId)'
|
msbuildArgs: '/p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)" /p:Version=$(Build.BuildNumber)'
|
||||||
platform: '$(buildPlatform)'
|
platform: '$(buildPlatform)'
|
||||||
configuration: '$(buildConfiguration)'
|
configuration: '$(buildConfiguration)'
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
|
displayName: 'Publish projects'
|
||||||
inputs:
|
inputs:
|
||||||
command: 'publish'
|
command: 'publish'
|
||||||
publishWebProjects: false
|
publishWebProjects: false
|
||||||
projects: |
|
projects: |
|
||||||
**/WebfrontCore.csproj
|
**/WebfrontCore.csproj
|
||||||
**/Application.csproj
|
**/Application.csproj
|
||||||
arguments: '-c $(buildConfiguration) -o $(outputFolder) /p:Version=$(Version.Major).$(Version.Minor).$(Version.Build).$(Build.BuildId)'
|
arguments: '-c $(buildConfiguration) -o $(outputFolder) /p:Version=$(Build.BuildNumber)'
|
||||||
zipAfterPublish: false
|
zipAfterPublish: false
|
||||||
modifyOutputPath: false
|
modifyOutputPath: false
|
||||||
|
|
||||||
- task: PowerShell@2
|
- task: PowerShell@2
|
||||||
|
displayName: 'Run publish script 1'
|
||||||
inputs:
|
inputs:
|
||||||
targetType: 'inline'
|
filePath: 'DeploymentFiles/PostPublish.ps1'
|
||||||
script: 'wget https://raidmax.org/downloads/dos2unix.exe'
|
|
||||||
failOnStderr: true
|
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
|
||||||
|
|
||||||
- task: PowerShell@2
|
|
||||||
inputs:
|
|
||||||
filePath: 'PostPublish.ps1'
|
|
||||||
arguments: '$(outputFolder)'
|
arguments: '$(outputFolder)'
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||||
|
|
||||||
- task: BatchScript@1
|
- task: BatchScript@1
|
||||||
|
displayName: 'Run publish script 2'
|
||||||
inputs:
|
inputs:
|
||||||
filename: 'Application\BuildScripts\PostPublish.bat'
|
filename: 'Application\BuildScripts\PostPublish.bat'
|
||||||
workingFolder: '$(Build.Repository.LocalPath)'
|
workingFolder: '$(Build.Repository.LocalPath)'
|
||||||
arguments: '$(outputFolder)'
|
arguments: '$(outputFolder)'
|
||||||
failOnStandardError: true
|
failOnStandardError: true
|
||||||
|
|
||||||
|
- task: PowerShell@2
|
||||||
|
displayName: 'Download dos2unix for line endings'
|
||||||
|
inputs:
|
||||||
|
targetType: 'inline'
|
||||||
|
script: 'wget https://raidmax.org/downloads/dos2unix.exe'
|
||||||
|
failOnStderr: true
|
||||||
|
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
|
displayName: 'Convert Linux start script line endings'
|
||||||
inputs:
|
inputs:
|
||||||
script: |
|
script: |
|
||||||
echo changing to encoding for linux start script
|
echo changing to encoding for linux start script
|
||||||
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
||||||
echo creating website version filename
|
echo creating website version filename
|
||||||
@echo IW4MAdmin-$(Version.Major).$(Version.Minor)-$(buildConfiguration)$(Version.Build)b$(Build.BuildId) > $(Build.ArtifactStagingDirectory)\version_prerelease.txt
|
@echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||||
|
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2
|
||||||
|
displayName: 'Move script plugins into publish directory'
|
||||||
inputs:
|
inputs:
|
||||||
SourceFolder: '$(Build.Repository.LocalPath)\Plugins\ScriptPlugins'
|
SourceFolder: '$(Build.Repository.LocalPath)\Plugins\ScriptPlugins'
|
||||||
Contents: '*.js'
|
Contents: '*.js'
|
||||||
TargetFolder: '$(outputFolder)\Plugins'
|
TargetFolder: '$(outputFolder)\Plugins'
|
||||||
|
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2
|
||||||
|
displayName: 'Move binary plugins into publish directory'
|
||||||
inputs:
|
inputs:
|
||||||
SourceFolder: '$(Build.Repository.LocalPath)\BUILD\Plugins\'
|
SourceFolder: '$(Build.Repository.LocalPath)\BUILD\Plugins\'
|
||||||
Contents: '*.dll'
|
Contents: '*.dll'
|
||||||
TargetFolder: '$(outputFolder)\Plugins'
|
TargetFolder: '$(outputFolder)\Plugins'
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
|
displayName: 'Move webfront resources into publish directory'
|
||||||
inputs:
|
inputs:
|
||||||
script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot'
|
script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot'
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\BUILD\Plugins'
|
workingDirectory: '$(Build.Repository.LocalPath)\BUILD\Plugins'
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
|
displayName: 'Move gamescript files into publish directory'
|
||||||
inputs:
|
inputs:
|
||||||
script: 'echo d | xcopy /s /y /f GameFiles $(outputFolder)\GameFiles'
|
script: 'echo d | xcopy /s /y /f GameFiles $(outputFolder)\GameFiles'
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
|
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
|
displayName: 'Generate final zip file'
|
||||||
inputs:
|
inputs:
|
||||||
rootFolderOrFile: '$(outputFolder)'
|
rootFolderOrFile: '$(outputFolder)'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Version.Major).$(Version.Minor)-$(buildConfiguration)$(Version.Build)b$(Build.BuildId).zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
||||||
replaceExistingArchive: true
|
replaceExistingArchive: true
|
||||||
|
|
||||||
- task: FtpUpload@2
|
- task: FtpUpload@2
|
||||||
|
displayName: 'Upload zip file to website'
|
||||||
inputs:
|
inputs:
|
||||||
credentialsOption: 'inputs'
|
credentialsOption: 'inputs'
|
||||||
serverUrl: '$(FTPUrl)'
|
serverUrl: '$(FTPUrl)'
|
||||||
@ -135,13 +155,14 @@ steps:
|
|||||||
trustSSL: false
|
trustSSL: false
|
||||||
|
|
||||||
- task: FtpUpload@2
|
- task: FtpUpload@2
|
||||||
|
displayName: 'Upload version info to website'
|
||||||
inputs:
|
inputs:
|
||||||
credentialsOption: 'inputs'
|
credentialsOption: 'inputs'
|
||||||
serverUrl: '$(FTPUrl)'
|
serverUrl: '$(FTPUrl)'
|
||||||
username: '$(FTPUsername)'
|
username: '$(FTPUsername)'
|
||||||
password: '$(FTPPassword)'
|
password: '$(FTPPassword)'
|
||||||
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||||
filePatterns: 'version_prerelease.txt'
|
filePatterns: 'version_$(releaseType).txt'
|
||||||
remoteDirectory: 'IW4MAdmin'
|
remoteDirectory: 'IW4MAdmin'
|
||||||
clean: false
|
clean: false
|
||||||
cleanContents: false
|
cleanContents: false
|
||||||
@ -149,27 +170,29 @@ steps:
|
|||||||
trustSSL: false
|
trustSSL: false
|
||||||
|
|
||||||
- task: GitHubRelease@1
|
- task: GitHubRelease@1
|
||||||
|
displayName: 'Make GitHub release'
|
||||||
inputs:
|
inputs:
|
||||||
gitHubConnection: 'github.com_RaidMax'
|
gitHubConnection: 'github.com_RaidMax'
|
||||||
repositoryName: 'RaidMax/IW4M-Admin'
|
repositoryName: 'RaidMax/IW4M-Admin'
|
||||||
action: 'create'
|
action: 'create'
|
||||||
target: '$(Build.SourceVersion)'
|
target: '$(Build.SourceVersion)'
|
||||||
tagSource: 'userSpecifiedTag'
|
tagSource: 'userSpecifiedTag'
|
||||||
tag: '$(Version.Major).$(Version.Minor)-$(buildConfiguration)$(Version.Build)b$(Build.BuildId)'
|
tag: '$(Build.BuildNumber)-$(releaseType)'
|
||||||
title: 'Version $(Version.Major).$(Version.Minor) $(buildConfiguration) Feature $(Version.Build) Build $(Build.BuildId)'
|
title: 'IW4MAdmin $(Build.BuildNumber) ($(releaseType))'
|
||||||
assets: '$(Build.ArtifactStagingDirectory)/*.zip'
|
assets: '$(Build.ArtifactStagingDirectory)/*.zip'
|
||||||
isPreRelease: true
|
isPreRelease: $(isPreRelease)
|
||||||
releaseNotesSource: 'inline'
|
releaseNotesSource: 'inline'
|
||||||
releaseNotesInline: 'todo'
|
releaseNotesInline: 'todo'
|
||||||
changeLogCompareToRelease: 'lastNonDraftRelease'
|
changeLogCompareToRelease: 'lastNonDraftRelease'
|
||||||
changeLogType: 'commitBased'
|
changeLogType: 'commitBased'
|
||||||
|
|
||||||
- task: PowerShell@2
|
- task: PowerShell@2
|
||||||
|
displayName: 'Update master version'
|
||||||
inputs:
|
inputs:
|
||||||
targetType: 'inline'
|
targetType: 'inline'
|
||||||
script: |
|
script: |
|
||||||
$payload = @{
|
$payload = @{
|
||||||
'current-version-prerelease' = '$(Version.Major).$(Version.Minor).$(Version.Build).$(Build.BuildId)'
|
'current-version-$(releaseType)' = '$(Build.BuildNumber)'
|
||||||
'jwt-secret' = '$(JWTSecret)'
|
'jwt-secret' = '$(JWTSecret)'
|
||||||
} | ConvertTo-Json
|
} | ConvertTo-Json
|
||||||
|
|
||||||
@ -184,6 +207,7 @@ steps:
|
|||||||
Invoke-RestMethod @params
|
Invoke-RestMethod @params
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@1
|
- task: PublishPipelineArtifact@1
|
||||||
|
displayName: 'Publish artifact for analysis'
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: '$(outputFolder)'
|
targetPath: '$(outputFolder)'
|
||||||
artifact: 'IW4MAdmin.$(buildConfiguration)'
|
artifact: 'IW4MAdmin.$(buildConfiguration)'
|
@ -8,10 +8,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
GameFiles\IW4x\userraw\scripts\_commands.gsc = GameFiles\IW4x\userraw\scripts\_commands.gsc
|
GameFiles\IW4x\userraw\scripts\_commands.gsc = GameFiles\IW4x\userraw\scripts\_commands.gsc
|
||||||
GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc = GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc
|
GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc = GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc
|
||||||
PostPublish.ps1 = PostPublish.ps1
|
DeploymentFiles\deployment-pipeline.yml = DeploymentFiles\deployment-pipeline.yml
|
||||||
|
DeploymentFiles\PostPublish.ps1 = DeploymentFiles\PostPublish.ps1
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
RunPublishPre.cmd = RunPublishPre.cmd
|
|
||||||
RunPublishRelease.cmd = RunPublishRelease.cmd
|
|
||||||
version.txt = version.txt
|
version.txt = version.txt
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
@ -36,6 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
|||||||
Plugins\ScriptPlugins\ActionOnReport.js = Plugins\ScriptPlugins\ActionOnReport.js
|
Plugins\ScriptPlugins\ActionOnReport.js = Plugins\ScriptPlugins\ActionOnReport.js
|
||||||
Plugins\ScriptPlugins\ParserCoD4x.js = Plugins\ScriptPlugins\ParserCoD4x.js
|
Plugins\ScriptPlugins\ParserCoD4x.js = Plugins\ScriptPlugins\ParserCoD4x.js
|
||||||
Plugins\ScriptPlugins\ParserIW4x.js = Plugins\ScriptPlugins\ParserIW4x.js
|
Plugins\ScriptPlugins\ParserIW4x.js = Plugins\ScriptPlugins\ParserIW4x.js
|
||||||
|
Plugins\ScriptPlugins\ParserIW6x.js = Plugins\ScriptPlugins\ParserIW6x.js
|
||||||
Plugins\ScriptPlugins\ParserPIW5.js = Plugins\ScriptPlugins\ParserPIW5.js
|
Plugins\ScriptPlugins\ParserPIW5.js = Plugins\ScriptPlugins\ParserPIW5.js
|
||||||
Plugins\ScriptPlugins\ParserPT6.js = Plugins\ScriptPlugins\ParserPT6.js
|
Plugins\ScriptPlugins\ParserPT6.js = Plugins\ScriptPlugins\ParserPT6.js
|
||||||
Plugins\ScriptPlugins\ParserRektT5M.js = Plugins\ScriptPlugins\ParserRektT5M.js
|
Plugins\ScriptPlugins\ParserRektT5M.js = Plugins\ScriptPlugins\ParserRektT5M.js
|
||||||
@ -44,6 +44,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
|||||||
Plugins\ScriptPlugins\SampleScriptPluginCommand.js = Plugins\ScriptPlugins\SampleScriptPluginCommand.js
|
Plugins\ScriptPlugins\SampleScriptPluginCommand.js = Plugins\ScriptPlugins\SampleScriptPluginCommand.js
|
||||||
Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js
|
Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js
|
||||||
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
|
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
|
||||||
|
Plugins\ScriptPlugins\ParserT4.js = Plugins\ScriptPlugins\ParserT4.js
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{A848FCF1-8527-4AA8-A1AA-50D29695C678}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{A848FCF1-8527-4AA8-A1AA-50D29695C678}"
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2020.12.20.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2020.12.20.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -74,14 +74,14 @@ namespace LiveRadar.Web.Controllers
|
|||||||
[Route("Radar/Update")]
|
[Route("Radar/Update")]
|
||||||
public IActionResult Update(string payload)
|
public IActionResult Update(string payload)
|
||||||
{
|
{
|
||||||
var radarUpdate = RadarEvent.Parse(payload);
|
/*var radarUpdate = RadarEvent.Parse(payload);
|
||||||
var client = _manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
var client = _manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
||||||
|
|
||||||
if (client != null)
|
if (client != null)
|
||||||
{
|
{
|
||||||
radarUpdate.Name = client.Name.StripColors();
|
radarUpdate.Name = client.Name.StripColors();
|
||||||
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
33
Plugins/LiveRadar/Events/Script.cs
Normal file
33
Plugins/LiveRadar/Events/Script.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using EventGeneratorCallback = System.ValueTuple<string, string,
|
||||||
|
System.Func<string, SharedLibraryCore.Interfaces.IEventParserConfiguration,
|
||||||
|
SharedLibraryCore.GameEvent,
|
||||||
|
SharedLibraryCore.GameEvent>>;
|
||||||
|
|
||||||
|
namespace LiveRadar.Events
|
||||||
|
{
|
||||||
|
public class Script : IRegisterEvent
|
||||||
|
{
|
||||||
|
private const string EVENT_LIVERADAR = "LiveRadar";
|
||||||
|
private EventGeneratorCallback LiveRadar()
|
||||||
|
{
|
||||||
|
return (EVENT_LIVERADAR, EVENT_LIVERADAR, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
|
||||||
|
{
|
||||||
|
string[] lineSplit = eventLine.Split(";");
|
||||||
|
|
||||||
|
autoEvent.Type = GameEvent.EventType.Other;
|
||||||
|
autoEvent.Subtype = EVENT_LIVERADAR;
|
||||||
|
autoEvent.Origin = new EFClient() { NetworkId = 0 };
|
||||||
|
autoEvent.Extra = lineSplit[1]; // guid
|
||||||
|
|
||||||
|
return autoEvent;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<EventGeneratorCallback> Events => new[] { LiveRadar() };
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2020.12.20.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
using LiveRadar.Configuration;
|
using LiveRadar.Configuration;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Configuration;
|
|
||||||
using SharedLibraryCore.Interfaces;
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace LiveRadar
|
namespace LiveRadar
|
||||||
{
|
{
|
||||||
@ -17,12 +20,16 @@ namespace LiveRadar
|
|||||||
public string Author => "RaidMax";
|
public string Author => "RaidMax";
|
||||||
|
|
||||||
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
|
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
|
||||||
|
private readonly Dictionary<string, long> _botGuidLookups;
|
||||||
private bool addedPage;
|
private bool addedPage;
|
||||||
private readonly object lockObject = new object();
|
private readonly object lockObject = new object();
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
|
public Plugin(ILogger<Plugin> logger, IConfigurationHandlerFactory configurationHandlerFactory)
|
||||||
{
|
{
|
||||||
_configurationHandler = configurationHandlerFactory.GetConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration");
|
_configurationHandler = configurationHandlerFactory.GetConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration");
|
||||||
|
_botGuidLookups = new Dictionary<string, long>();
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnEventAsync(GameEvent E, Server S)
|
public Task OnEventAsync(GameEvent E, Server S)
|
||||||
@ -41,13 +48,33 @@ namespace LiveRadar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (E.Type == GameEvent.EventType.Unknown)
|
if (E.Type == GameEvent.EventType.PreConnect && E.Origin.IsBot)
|
||||||
{
|
{
|
||||||
if (E.Data?.StartsWith("LiveRadar") ?? false)
|
string botKey = $"BotGuid_{E.Extra}";
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
if (!_botGuidLookups.ContainsKey(botKey))
|
||||||
|
{
|
||||||
|
_botGuidLookups.Add(botKey, E.Origin.NetworkId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (E.Type == GameEvent.EventType.Other && E.Subtype == "LiveRadar")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var radarUpdate = RadarEvent.Parse(E.Data);
|
string botKey = $"BotGuid_{E.Extra}";
|
||||||
|
long generatedBotGuid;
|
||||||
|
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
generatedBotGuid = _botGuidLookups.ContainsKey(botKey)
|
||||||
|
? _botGuidLookups[botKey]
|
||||||
|
: (E.Extra.ToString() ?? "0").ConvertGuidToLong(NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
var radarUpdate = RadarEvent.Parse(E.Data, generatedBotGuid);
|
||||||
var client = S.Manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
var client = S.Manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
||||||
|
|
||||||
if (client != null)
|
if (client != null)
|
||||||
@ -59,9 +86,7 @@ namespace LiveRadar
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
S.Logger.WriteWarning($"Could not parse live radar output: {e.Data}");
|
_logger.LogError(e, "Could not parse live radar output: {data}", e.Data);
|
||||||
S.Logger.WriteDebug(e.GetExceptionInfo());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LiveRadar
|
namespace LiveRadar
|
||||||
{
|
{
|
||||||
@ -39,13 +37,13 @@ namespace LiveRadar
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RadarEvent Parse(string input)
|
public static RadarEvent Parse(string input, long generatedBotGuid)
|
||||||
{
|
{
|
||||||
var items = input.Split(';').Skip(1).ToList();
|
var items = input.Split(';').Skip(1).ToList();
|
||||||
|
|
||||||
var parsedEvent = new RadarEvent()
|
var parsedEvent = new RadarEvent()
|
||||||
{
|
{
|
||||||
Guid = items[0].ConvertGuidToLong(System.Globalization.NumberStyles.HexNumber),
|
Guid = generatedBotGuid,
|
||||||
Location = Vector3.Parse(items[1]),
|
Location = Vector3.Parse(items[1]),
|
||||||
ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(),
|
ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(),
|
||||||
Team = items[3],
|
Team = items[3],
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2020.12.20.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2020.12.20.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -15,18 +15,22 @@ var plugin = {
|
|||||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||||
|
|
||||||
rconParser.Configuration.StatusHeader.Pattern = 'num +score +ping +playerid +steamid +name +lastmsg +address +qport +rate *';
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +ping +playerid +steamid +name +lastmsg +address +qport +rate *';
|
||||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]{16,32})|bot[0-9]+) ([0-9]+) +(.{0,32}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$';
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]{16,32})|0) +([[0-9]+|0]) +(.{0,32}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|bot) +(-*[0-9]+) +([0-9]+) *$';
|
||||||
rconParser.Configuration.Status.AddMapping(104, 6); // RConName
|
rconParser.Configuration.Status.AddMapping(104, 6); // RConName
|
||||||
rconParser.Configuration.Status.AddMapping(105, 8); // RConIPAddress
|
rconParser.Configuration.Status.AddMapping(105, 8); // RConIPAddress
|
||||||
|
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
||||||
|
|
||||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" is: "(.+)?" default: "(.+)?" info: "(.+)?"$';
|
rconParser.Configuration.Dvar.Pattern = '^"(.+)" is: "(.+)?" default: "(.+)?" info: "(.+)?"$';
|
||||||
rconParser.Configuration.Dvar.AddMapping(109, 2); // DVAR latched value
|
rconParser.Configuration.Dvar.AddMapping(109, 2); // DVAR latched value
|
||||||
rconParser.Configuration.Dvar.AddMapping(110, 4); // dvar info
|
rconParser.Configuration.Dvar.AddMapping(110, 4); // dvar info
|
||||||
rconParser.Version = 'CoD4 X - win_mingw-x86 build 963 Mar 12 2019';
|
rconParser.Configuration.GuidNumberStyle = 7; // Integer
|
||||||
|
rconParser.Configuration.NoticeLineSeparator = '. '; // CoD4x does not support \n in the client notice
|
||||||
|
rconParser.Version = 'CoD4 X - win_mingw-x86 build 1056 Dec 12 2020';
|
||||||
rconParser.GameName = 1; // IW3
|
rconParser.GameName = 1; // IW3
|
||||||
|
|
||||||
eventParser.Configuration.GameDirectory = 'main';
|
eventParser.Configuration.GameDirectory = 'main';
|
||||||
eventParser.Version = 'CoD4 X - win_mingw-x86 build 963 Mar 12 2019';
|
eventParser.Configuration.GuidNumberStyle = 7; // Integer
|
||||||
|
eventParser.Version = 'CoD4 X - win_mingw-x86 build 1056 Dec 12 2020';
|
||||||
eventParser.GameName = 1; // IW3
|
eventParser.GameName = 1; // IW3
|
||||||
eventParser.URLProtocolFormat = 'cod4://{{ip}}:{{port}}';
|
eventParser.URLProtocolFormat = 'cod4://{{ip}}:{{port}}';
|
||||||
},
|
},
|
||||||
|
45
Plugins/ScriptPlugins/ParserIW6x.js
Normal file
45
Plugins/ScriptPlugins/ParserIW6x.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
var rconParser;
|
||||||
|
var eventParser;
|
||||||
|
|
||||||
|
var plugin = {
|
||||||
|
author: 'Xerxes, RaidMax',
|
||||||
|
version: 0.2,
|
||||||
|
name: 'IW6x Parser',
|
||||||
|
isParser: true,
|
||||||
|
|
||||||
|
onEventAsync: function (gameEvent, server) {
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadAsync: function (manager) {
|
||||||
|
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||||
|
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||||
|
|
||||||
|
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0} "{1}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0} "{1}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick {0} "{1}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
||||||
|
rconParser.Configuration.Dvar.Pattern = '^ *\\"(.+)\\" is: \\"(.+)?\\" default: \\"(.+)?\\"\\n(?:latched: \\"(.+)?\\"\\n)? *(.+)$';
|
||||||
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(Yes|No) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) *$';
|
||||||
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +address +qport *';
|
||||||
|
rconParser.Configuration.WaitForResponse = false;
|
||||||
|
rconParser.Configuration.Status.AddMapping(102, 4);
|
||||||
|
rconParser.Configuration.Status.AddMapping(103, 5);
|
||||||
|
rconParser.Configuration.Status.AddMapping(104, 6);
|
||||||
|
rconParser.Configuration.DefaultDvarValues.Add('g_log', 'games_mp.log'); // todo: remove this once proper log support is implemented
|
||||||
|
rconParser.Configuration.DefaultDvarValues.Add('g_logsync', '1'); // todo: remove this once proper log support is implemented
|
||||||
|
|
||||||
|
rconParser.Version = 'IW6 MP 3.15 build 2 Sat Sep 14 2013 03:58:30PM win64';
|
||||||
|
rconParser.GameName = 4; // IW6
|
||||||
|
eventParser.Version = 'IW6 MP 3.15 build 2 Sat Sep 14 2013 03:58:30PM win64';
|
||||||
|
eventParser.GameName = 4; // IW6
|
||||||
|
eventParser.Configuration.GameDirectory = 'iw6x';
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnloadAsync: function () {
|
||||||
|
},
|
||||||
|
|
||||||
|
onTickAsync: function (server) {
|
||||||
|
}
|
||||||
|
};
|
@ -3,7 +3,7 @@ var eventParser;
|
|||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: 0.5,
|
version: 0.6,
|
||||||
name: 'Plutonium IW5 Parser',
|
name: 'Plutonium IW5 Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ var plugin = {
|
|||||||
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
||||||
rconParser.Configuration.WaitForResponse = true;
|
rconParser.Configuration.WaitForResponse = true;
|
||||||
rconParser.Configuration.CanGenerateLogPath = true;
|
rconParser.Configuration.CanGenerateLogPath = true;
|
||||||
|
rconParser.Configuration.NoticeLineSeparator = '. ';
|
||||||
|
|
||||||
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *';
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *';
|
||||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(?:[0-1]{1}) +([0-9]{1,4}|[A-Z]{4}) +([a-f|A-F|0-9]{16}) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(?:[0-1]{1}) +([0-9]{1,4}|[A-Z]{4}) +([a-f|A-F|0-9]{16}) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
|
||||||
|
@ -3,7 +3,7 @@ var eventParser;
|
|||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'RaidMax, Xerxes',
|
author: 'RaidMax, Xerxes',
|
||||||
version: 0.8,
|
version: 0.9,
|
||||||
name: 'Plutonium T6 Parser',
|
name: 'Plutonium T6 Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ var plugin = {
|
|||||||
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
||||||
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
||||||
rconParser.Configuration.WaitForResponse = false;
|
rconParser.Configuration.WaitForResponse = false;
|
||||||
|
rconParser.Configuration.NoticeLineSeparator = '. ';
|
||||||
|
|
||||||
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *';
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *';
|
||||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(?:[0-1]{1}) +([0-9]+) +([A-F0-9]+|0) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(?:[0-1]{1}) +([0-9]+) +([A-F0-9]+|0) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
|
||||||
|
@ -3,7 +3,7 @@ var eventParser;
|
|||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: 0.2,
|
version: 0.3,
|
||||||
name: 'RektT5m Parser',
|
name: 'RektT5m Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ var plugin = {
|
|||||||
|
|
||||||
eventParser.Configuration.GameDirectory = 'data';
|
eventParser.Configuration.GameDirectory = 'data';
|
||||||
|
|
||||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff\x01print';
|
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff\x01print\n';
|
||||||
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
|
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
|
||||||
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
|
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
|
||||||
|
|
||||||
|
30
Plugins/ScriptPlugins/ParserT4.js
Normal file
30
Plugins/ScriptPlugins/ParserT4.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
var rconParser;
|
||||||
|
var eventParser;
|
||||||
|
|
||||||
|
var plugin = {
|
||||||
|
author: 'RaidMax',
|
||||||
|
version: 0.1,
|
||||||
|
name: 'Call of Duty 5: World at War Parser',
|
||||||
|
isParser: true,
|
||||||
|
|
||||||
|
onEventAsync: function (gameEvent, server) {
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadAsync: function (manager) {
|
||||||
|
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||||
|
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||||
|
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
||||||
|
rconParser.Configuration.GuidNumberStyle = 7; // Integer
|
||||||
|
rconParser.Version = 'Call of Duty Multiplayer COD_WaW MP build 1.7.1263 CL(350073) JADAMS2 Thu Oct 29 15:43:55 2009 win-x86';
|
||||||
|
|
||||||
|
eventParser.Configuration.GuidNumberStyle = 7; // Integer
|
||||||
|
eventParser.GameName = 5; // T4
|
||||||
|
eventParser.Version = 'Call of Duty Multiplayer COD_WaW MP build 1.7.1263 CL(350073) JADAMS2 Thu Oct 29 15:43:55 2009 win-x86';
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnloadAsync: function () {
|
||||||
|
},
|
||||||
|
|
||||||
|
onTickAsync: function (server) {
|
||||||
|
}
|
||||||
|
};
|
@ -3,7 +3,7 @@ var eventParser;
|
|||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: 0.7,
|
version: 0.8,
|
||||||
name: 'Tekno MW3 Parser',
|
name: 'Tekno MW3 Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ var plugin = {
|
|||||||
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0} "{1}"';
|
||||||
rconParser.Configuration.Dvar.AddMapping(107, 1); // RCon DvarValue
|
rconParser.Configuration.Dvar.AddMapping(107, 1); // RCon DvarValue
|
||||||
rconParser.Configuration.Dvar.Pattern = '^(.*)$';
|
rconParser.Configuration.Dvar.Pattern = '^(.*)$';
|
||||||
|
rconParser.Configuration.NoticeLineSeparator = '. ';
|
||||||
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
||||||
rconParser.Configuration.OverrideDvarNameMapping.Add('_website', 'sv_clanWebsite');
|
rconParser.Configuration.OverrideDvarNameMapping.Add('_website', 'sv_clanWebsite');
|
||||||
|
@ -7,6 +7,8 @@ let commands = [{
|
|||||||
alias: "pp",
|
alias: "pp",
|
||||||
// required
|
// required
|
||||||
permission: "User",
|
permission: "User",
|
||||||
|
// optional (defaults to false)
|
||||||
|
targetRequired: false,
|
||||||
// optional
|
// optional
|
||||||
arguments: [{
|
arguments: [{
|
||||||
name: "times to ping",
|
name: "times to ping",
|
||||||
@ -44,6 +46,8 @@ let plugin = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onLoadAsync: function (manager) {
|
onLoadAsync: function (manager) {
|
||||||
|
this.logger = _serviceResolver.ResolveService("ILogger");
|
||||||
|
this.logger.WriteDebug("sample plugin loaded");
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnloadAsync: function () {
|
onUnloadAsync: function () {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
using IW4MAdmin.Plugins.Stats.Models;
|
using IW4MAdmin.Plugins.Stats.Models;
|
||||||
using SharedLibraryCore;
|
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Cheat
|
namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||||
{
|
{
|
||||||
@ -20,7 +20,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
Offset,
|
Offset,
|
||||||
Strain,
|
Strain,
|
||||||
Recoil,
|
Recoil,
|
||||||
Snap
|
Snap,
|
||||||
|
Button
|
||||||
};
|
};
|
||||||
|
|
||||||
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
||||||
@ -38,11 +39,12 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
ILogger Log;
|
ILogger Log;
|
||||||
Strain Strain;
|
Strain Strain;
|
||||||
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
||||||
private double sessionAverageRecoilAmount;
|
private double mapAverageRecoilAmount;
|
||||||
private double sessionAverageSnapAmount;
|
private double sessionAverageSnapAmount;
|
||||||
private int sessionSnapHits;
|
private int sessionSnapHits;
|
||||||
private EFClientKill lastHit;
|
private EFClientKill lastHit;
|
||||||
private int validRecoilHitCount;
|
private int validRecoilHitCount;
|
||||||
|
private int validButtonHitCount;
|
||||||
|
|
||||||
private class HitInfo
|
private class HitInfo
|
||||||
{
|
{
|
||||||
@ -221,11 +223,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
if (weightedSessionAverage > Thresholds.MaxOffset(totalSessionHits) &&
|
if (weightedSessionAverage > Thresholds.MaxOffset(totalSessionHits) &&
|
||||||
totalSessionHits >= (Thresholds.MediumSampleMinKills * 2))
|
totalSessionHits >= (Thresholds.MediumSampleMinKills * 2))
|
||||||
{
|
{
|
||||||
Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
|
|
||||||
Log.WriteDebug($"Session Average = {weightedSessionAverage}");
|
|
||||||
Log.WriteDebug($"HitCount = {HitCount}");
|
|
||||||
Log.WriteDebug($"ID = {hit.AttackerId}");
|
|
||||||
|
|
||||||
results.Add(new DetectionPenaltyResult()
|
results.Add(new DetectionPenaltyResult()
|
||||||
{
|
{
|
||||||
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
||||||
@ -236,17 +233,13 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
Log.LogDebug("PredictVsReal={realAgainstPredict}", realAgainstPredict);
|
||||||
Log.WriteDebug($"PredictVsReal={realAgainstPredict}");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region STRAIN
|
#region STRAIN
|
||||||
double currentStrain = Strain.GetStrain(hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, LastOffset == 0 ? 50 : (hit.TimeOffset - LastOffset)));
|
double currentStrain = Strain.GetStrain(hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, LastOffset == 0 ? 50 : (hit.TimeOffset - LastOffset)));
|
||||||
#if DEBUG == true
|
Log.LogDebug("Current Strain: {currentStrain}", currentStrain);
|
||||||
Log.WriteDebug($"Current Strain: {currentStrain}");
|
|
||||||
#endif
|
|
||||||
LastOffset = hit.TimeOffset;
|
LastOffset = hit.TimeOffset;
|
||||||
|
|
||||||
if (currentStrain > ClientStats.MaxStrain)
|
if (currentStrain > ClientStats.MaxStrain)
|
||||||
@ -282,18 +275,30 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
|
|
||||||
#region RECOIL
|
#region RECOIL
|
||||||
float hitRecoilAverage = 0;
|
float hitRecoilAverage = 0;
|
||||||
if (!Plugin.Config.Configuration().RecoilessWeapons.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex)))
|
bool shouldIgnoreDetection = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Recoil]
|
||||||
|
.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldIgnoreDetection)
|
||||||
{
|
{
|
||||||
validRecoilHitCount++;
|
validRecoilHitCount++;
|
||||||
hitRecoilAverage = (hit.AnglesList.Sum(_angle => _angle.Z) + hit.ViewAngles.Z) / (hit.AnglesList.Count + 1);
|
hitRecoilAverage = (hit.AnglesList.Sum(_angle => _angle.Z) + hit.ViewAngles.Z) / (hit.AnglesList.Count + 1);
|
||||||
sessionAverageRecoilAmount = (sessionAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount;
|
mapAverageRecoilAmount = (mapAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount;
|
||||||
|
|
||||||
if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && sessionAverageRecoilAmount == 0)
|
if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && mapAverageRecoilAmount == 0)
|
||||||
{
|
{
|
||||||
results.Add(new DetectionPenaltyResult()
|
results.Add(new DetectionPenaltyResult()
|
||||||
{
|
{
|
||||||
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
||||||
Value = sessionAverageRecoilAmount,
|
Value = mapAverageRecoilAmount,
|
||||||
HitCount = HitCount,
|
HitCount = HitCount,
|
||||||
Type = DetectionType.Recoil
|
Type = DetectionType.Recoil
|
||||||
});
|
});
|
||||||
@ -301,6 +306,37 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region BUTTON
|
||||||
|
try
|
||||||
|
{
|
||||||
|
shouldIgnoreDetection = false;
|
||||||
|
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Button]
|
||||||
|
.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldIgnoreDetection)
|
||||||
|
{
|
||||||
|
validButtonHitCount++;
|
||||||
|
|
||||||
|
double lastDiff = hit.TimeOffset - hit.TimeSinceLastAttack;
|
||||||
|
if (validButtonHitCount > 0 && lastDiff <= 0)
|
||||||
|
{
|
||||||
|
results.Add(new DetectionPenaltyResult()
|
||||||
|
{
|
||||||
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
||||||
|
Value = lastDiff,
|
||||||
|
HitCount = HitCount,
|
||||||
|
Type = DetectionType.Button
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region SESSION_RATIOS
|
#region SESSION_RATIOS
|
||||||
if (Kills >= Thresholds.LowSampleMinKills)
|
if (Kills >= Thresholds.LowSampleMinKills)
|
||||||
{
|
{
|
||||||
@ -384,7 +420,19 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
#region CHEST_ABDOMEN_RATIO_SESSION
|
#region CHEST_ABDOMEN_RATIO_SESSION
|
||||||
int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper].Count;
|
int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper].Count;
|
||||||
|
|
||||||
if (chestHits >= Thresholds.MediumSampleMinKills)
|
try
|
||||||
|
{
|
||||||
|
shouldIgnoreDetection = false; // reset previous value
|
||||||
|
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Chest]
|
||||||
|
.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chestHits >= Thresholds.MediumSampleMinKills && !shouldIgnoreDetection)
|
||||||
{
|
{
|
||||||
double marginOfError = Thresholds.GetMarginOfError(chestHits);
|
double marginOfError = Thresholds.GetMarginOfError(chestHits);
|
||||||
double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
|
double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
|
||||||
@ -466,5 +514,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnMapChange()
|
||||||
|
{
|
||||||
|
mapAverageRecoilAmount = 0;
|
||||||
|
validRecoilHitCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,12 +27,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
double[] distance = Utilities.AngleStuff(newAngle, LastAngle);
|
double[] distance = Utilities.AngleStuff(newAngle, LastAngle);
|
||||||
LastDistance = distance[0] + distance[1];
|
LastDistance = distance[0] + distance[1];
|
||||||
|
|
||||||
#if DEBUG == true
|
|
||||||
Console.WriteLine($"Angle Between = {LastDistance}");
|
|
||||||
Console.WriteLine($"Distance From Target = {killDistance}");
|
|
||||||
Console.WriteLine($"Time Offset = {deltaTime}");
|
|
||||||
Console.WriteLine($"Decay Factor = {decayFactor} ");
|
|
||||||
#endif
|
|
||||||
// this happens on first kill
|
// this happens on first kill
|
||||||
if ((distance[0] == 0 && distance[1] == 0) ||
|
if ((distance[0] == 0 && distance[1] == 0) ||
|
||||||
deltaTime == 0 ||
|
deltaTime == 0 ||
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using IW4MAdmin.Plugins.Stats.Models;
|
using IW4MAdmin.Plugins.Stats.Models;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -19,7 +18,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
private readonly CommandConfiguration _config;
|
private readonly CommandConfiguration _config;
|
||||||
|
|
||||||
public MostKillsCommand(CommandConfiguration config, ITranslationLookup translationLookup, IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
public MostKillsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||||
|
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "mostkills";
|
Name = "mostkills";
|
||||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_DESC"];
|
Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_DESC"];
|
||||||
@ -32,7 +32,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
var mostKills = await GetMostKills(StatManager.GetIdForServer(E.Owner), Plugin.Config.Configuration(), _contextFactory, _translationLookup);
|
var mostKills = await GetMostKills(StatManager.GetIdForServer(E.Owner), Plugin.Config.Configuration(),
|
||||||
|
_contextFactory, _translationLookup);
|
||||||
if (!E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
if (!E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
||||||
{
|
{
|
||||||
foreach (var stat in mostKills)
|
foreach (var stat in mostKills)
|
||||||
@ -50,10 +51,10 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IEnumerable<string>> GetMostKills(long? serverId, StatsConfiguration config, IDatabaseContextFactory contextFactory, ITranslationLookup translationLookup)
|
public static async Task<IEnumerable<string>> GetMostKills(long? serverId, StatsConfiguration config,
|
||||||
{
|
IDatabaseContextFactory contextFactory, ITranslationLookup translationLookup)
|
||||||
using (var ctx = contextFactory.CreateContext(enableTracking: false))
|
|
||||||
{
|
{
|
||||||
|
await using var ctx = contextFactory.CreateContext(enableTracking: false);
|
||||||
var dayInPast = DateTime.UtcNow.AddDays(-config.MostKillsMaxInactivityDays);
|
var dayInPast = DateTime.UtcNow.AddDays(-config.MostKillsMaxInactivityDays);
|
||||||
|
|
||||||
var iqStats = (from stats in ctx.Set<EFClientStatistics>()
|
var iqStats = (from stats in ctx.Set<EFClientStatistics>()
|
||||||
@ -74,9 +75,9 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
|
|
||||||
var iqList = await iqStats.ToListAsync();
|
var iqList = await iqStats.ToListAsync();
|
||||||
|
|
||||||
return iqList.Select((stats, index) => translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_FORMAT"].FormatExt(index + 1, stats.Name, stats.Kills))
|
return iqList.Select((stats, index) => translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_FORMAT"]
|
||||||
|
.FormatExt(index + 1, stats.Name, stats.Kills))
|
||||||
.Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]);
|
.Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -16,7 +16,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
{
|
{
|
||||||
class MostPlayedCommand : Command
|
class MostPlayedCommand : Command
|
||||||
{
|
{
|
||||||
public static async Task<List<string>> GetMostPlayed(Server s, ITranslationLookup translationLookup)
|
public static async Task<List<string>> GetMostPlayed(Server s, ITranslationLookup translationLookup, IDatabaseContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
long serverId = StatManager.GetIdForServer(s);
|
long serverId = StatManager.GetIdForServer(s);
|
||||||
|
|
||||||
@ -25,17 +25,13 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
$"^5--{translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
|
$"^5--{translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var db = new DatabaseContext(true))
|
await using var context = contextFactory.CreateContext(false);
|
||||||
{
|
|
||||||
db.ChangeTracker.AutoDetectChangesEnabled = false;
|
|
||||||
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
|
||||||
|
|
||||||
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
|
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
|
||||||
|
|
||||||
var iqStats = (from stats in db.Set<EFClientStatistics>()
|
var iqStats = (from stats in context.Set<EFClientStatistics>()
|
||||||
join client in db.Clients
|
join client in context.Clients
|
||||||
on stats.ClientId equals client.ClientId
|
on stats.ClientId equals client.ClientId
|
||||||
join alias in db.Aliases
|
join alias in context.Aliases
|
||||||
on client.CurrentAliasId equals alias.AliasId
|
on client.CurrentAliasId equals alias.AliasId
|
||||||
where stats.ServerId == serverId
|
where stats.ServerId == serverId
|
||||||
where client.Level != EFClient.Permission.Banned
|
where client.Level != EFClient.Permission.Banned
|
||||||
@ -52,15 +48,16 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
var iqList = await iqStats.ToListAsync();
|
var iqList = await iqStats.ToListAsync();
|
||||||
|
|
||||||
mostPlayed.AddRange(iqList.Select(stats => translationLookup["COMMANDS_MOST_PLAYED_FORMAT"].FormatExt(stats.Name, stats.Kills, (DateTime.UtcNow - DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime)).HumanizeForCurrentCulture())));
|
mostPlayed.AddRange(iqList.Select(stats => translationLookup["COMMANDS_MOST_PLAYED_FORMAT"].FormatExt(stats.Name, stats.Kills, (DateTime.UtcNow - DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime)).HumanizeForCurrentCulture())));
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return mostPlayed;
|
return mostPlayed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CommandConfiguration _config;
|
private readonly CommandConfiguration _config;
|
||||||
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
public MostPlayedCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
public MostPlayedCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||||
|
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "mostplayed";
|
Name = "mostplayed";
|
||||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"];
|
Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"];
|
||||||
@ -69,11 +66,12 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
|
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_contextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
var topStats = await GetMostPlayed(E.Owner, _translationLookup);
|
var topStats = await GetMostPlayed(E.Owner, _translationLookup, _contextFactory);
|
||||||
if (!E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
if (!E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
||||||
{
|
{
|
||||||
foreach (var stat in topStats)
|
foreach (var stat in topStats)
|
||||||
|
@ -12,31 +12,36 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
{
|
{
|
||||||
public class ResetStats : Command
|
public class ResetStats : Command
|
||||||
{
|
{
|
||||||
public ResetStats(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
|
public ResetStats(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||||
|
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "resetstats";
|
Name = "resetstats";
|
||||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_RESET_DESC"];
|
Description = translationLookup["PLUGINS_STATS_COMMANDS_RESET_DESC"];
|
||||||
Alias = "rs";
|
Alias = "rs";
|
||||||
Permission = EFClient.Permission.User;
|
Permission = EFClient.Permission.User;
|
||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
//AllowImpersonation = true;
|
AllowImpersonation = true;
|
||||||
|
|
||||||
|
_contextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
if (E.Origin.ClientNumber >= 0)
|
if (gameEvent.Origin.ClientNumber >= 0)
|
||||||
{
|
{
|
||||||
|
var serverId = Helpers.StatManager.GetIdForServer(gameEvent.Owner);
|
||||||
|
|
||||||
long serverId = Helpers.StatManager.GetIdForServer(E.Owner);
|
await using var context = _contextFactory.CreateContext();
|
||||||
|
var clientStats = await context.Set<EFClientStatistics>()
|
||||||
EFClientStatistics clientStats;
|
.Where(s => s.ClientId == gameEvent.Origin.ClientId)
|
||||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
|
||||||
{
|
|
||||||
clientStats = await ctx.Set<EFClientStatistics>()
|
|
||||||
.Where(s => s.ClientId == E.Origin.ClientId)
|
|
||||||
.Where(s => s.ServerId == serverId)
|
.Where(s => s.ServerId == serverId)
|
||||||
.FirstAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
// want to prevent resetting stats before they've gotten any kills
|
||||||
|
if (clientStats != null)
|
||||||
|
{
|
||||||
clientStats.Deaths = 0;
|
clientStats.Deaths = 0;
|
||||||
clientStats.Kills = 0;
|
clientStats.Kills = 0;
|
||||||
clientStats.SPM = 0.0;
|
clientStats.SPM = 0.0;
|
||||||
@ -44,19 +49,18 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
clientStats.TimePlayed = 0;
|
clientStats.TimePlayed = 0;
|
||||||
// todo: make this more dynamic
|
// todo: make this more dynamic
|
||||||
clientStats.EloRating = 200.0;
|
clientStats.EloRating = 200.0;
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
// reset the cached version
|
// reset the cached version
|
||||||
Plugin.Manager.ResetStats(E.Origin);
|
Plugin.Manager.ResetStats(gameEvent.Origin);
|
||||||
|
|
||||||
// fixme: this doesn't work properly when another context exists
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
|
||||||
await ctx.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
E.Origin.Tell(_translationLookup["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
E.Origin.Tell(_translationLookup["PLUGINS_STATS_COMMANDS_RESET_FAIL"]);
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_STATS_COMMANDS_RESET_FAIL"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
{
|
{
|
||||||
class TopStats : Command
|
class TopStats : Command
|
||||||
{
|
{
|
||||||
public static async Task<List<string>> GetTopStats(Server s, ITranslationLookup translationLookup)
|
public static async Task<List<string>> GetTopStats(Server s, ITranslationLookup translationLookup, IDatabaseContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
long serverId = StatManager.GetIdForServer(s);
|
long serverId = StatManager.GetIdForServer(s);
|
||||||
var topStatsText = new List<string>()
|
var topStatsText = new List<string>()
|
||||||
@ -24,15 +24,14 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
$"^5--{translationLookup["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
|
$"^5--{translationLookup["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var db = new DatabaseContext(true))
|
await using var context = contextFactory.CreateContext(false);
|
||||||
{
|
|
||||||
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
|
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
|
||||||
int minPlayTime = Plugin.Config.Configuration().TopPlayersMinPlayTime;
|
int minPlayTime = Plugin.Config.Configuration().TopPlayersMinPlayTime;
|
||||||
|
|
||||||
var iqStats = (from stats in db.Set<EFClientStatistics>()
|
var iqStats = (from stats in context.Set<EFClientStatistics>()
|
||||||
join client in db.Clients
|
join client in context.Clients
|
||||||
on stats.ClientId equals client.ClientId
|
on stats.ClientId equals client.ClientId
|
||||||
join alias in db.Aliases
|
join alias in context.Aliases
|
||||||
on client.CurrentAliasId equals alias.AliasId
|
on client.CurrentAliasId equals alias.AliasId
|
||||||
where stats.ServerId == serverId
|
where stats.ServerId == serverId
|
||||||
where stats.TimePlayed >= minPlayTime
|
where stats.TimePlayed >= minPlayTime
|
||||||
@ -51,7 +50,6 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
.Select(stats => $"^3{stats.Name}^7 - ^5{stats.KDR} ^7{translationLookup["PLUGINS_STATS_TEXT_KDR"]} | ^5{stats.Performance} ^7{translationLookup["PLUGINS_STATS_COMMANDS_PERFORMANCE"]}");
|
.Select(stats => $"^3{stats.Name}^7 - ^5{stats.KDR} ^7{translationLookup["PLUGINS_STATS_TEXT_KDR"]} | ^5{stats.Performance} ^7{translationLookup["PLUGINS_STATS_COMMANDS_PERFORMANCE"]}");
|
||||||
|
|
||||||
topStatsText.AddRange(statsList);
|
topStatsText.AddRange(statsList);
|
||||||
}
|
|
||||||
|
|
||||||
// no one qualified
|
// no one qualified
|
||||||
if (topStatsText.Count == 1)
|
if (topStatsText.Count == 1)
|
||||||
@ -66,8 +64,10 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly CommandConfiguration _config;
|
private readonly CommandConfiguration _config;
|
||||||
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
public TopStats(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
public TopStats(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||||
|
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "topstats";
|
Name = "topstats";
|
||||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_TOP_DESC"];
|
Description = translationLookup["PLUGINS_STATS_COMMANDS_TOP_DESC"];
|
||||||
@ -76,11 +76,12 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
|
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_contextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
var topStats = await GetTopStats(E.Owner, _translationLookup);
|
var topStats = await GetTopStats(E.Owner, _translationLookup, _contextFactory);
|
||||||
if (!E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
if (!E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
||||||
{
|
{
|
||||||
foreach (var stat in topStats)
|
foreach (var stat in topStats)
|
||||||
|
@ -14,9 +14,11 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
{
|
{
|
||||||
public class ViewStatsCommand : Command
|
public class ViewStatsCommand : Command
|
||||||
{
|
{
|
||||||
public ViewStatsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
{
|
|
||||||
|
|
||||||
|
public ViewStatsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||||
|
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||||
|
{
|
||||||
Name = "stats";
|
Name = "stats";
|
||||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_VIEW_DESC"];
|
Description = translationLookup["PLUGINS_STATS_COMMANDS_VIEW_DESC"];
|
||||||
Alias = "xlrstats";
|
Alias = "xlrstats";
|
||||||
@ -31,15 +33,13 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_config = config;
|
_contextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CommandConfiguration _config;
|
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
string statLine;
|
string statLine;
|
||||||
EFClientStatistics pStats;
|
EFClientStatistics pStats = null;
|
||||||
|
|
||||||
if (E.Data.Length > 0 && E.Target == null)
|
if (E.Data.Length > 0 && E.Target == null)
|
||||||
{
|
{
|
||||||
@ -51,52 +51,67 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long serverId = StatManager.GetIdForServer(E.Owner);
|
var serverId = StatManager.GetIdForServer(E.Owner);
|
||||||
|
|
||||||
|
|
||||||
|
// getting stats for a particular client
|
||||||
if (E.Target != null)
|
if (E.Target != null)
|
||||||
{
|
{
|
||||||
int performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Target.ClientId);
|
var performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Target.ClientId);
|
||||||
string performanceRankingString = performanceRanking == 0 ? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
var performanceRankingString = performanceRanking == 0
|
||||||
|
? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"]
|
||||||
|
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
||||||
|
|
||||||
if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Target)))
|
// target is currently connected so we want their cached stats if they exist
|
||||||
|
if (E.Owner.GetClientsAsList().Any(client => client.Equals(E.Target)))
|
||||||
{
|
{
|
||||||
pStats = E.Target.GetAdditionalProperty<EFClientStatistics>(StatManager.CLIENT_STATS_KEY);
|
pStats = E.Target.GetAdditionalProperty<EFClientStatistics>(StatManager.CLIENT_STATS_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
// target is not connected so we want to look up via database
|
||||||
|
if (pStats == null)
|
||||||
{
|
{
|
||||||
using (var ctx = new DatabaseContext(true))
|
await using var context = _contextFactory.CreateContext(false);
|
||||||
{
|
pStats = (await context.Set<EFClientStatistics>()
|
||||||
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId));
|
.FirstOrDefaultAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId));
|
||||||
}
|
|
||||||
}
|
|
||||||
statLine = $"^5{pStats.Kills} ^7{_translationLookup["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{_translationLookup["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{_translationLookup["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if it's still null then they've not gotten a kill or death yet
|
||||||
|
statLine = pStats == null
|
||||||
|
? _translationLookup["PLUGINS_STATS_COMMANDS_NOTAVAILABLE"]
|
||||||
|
: $"^5{pStats.Kills} ^7{_translationLookup["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{_translationLookup["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{_translationLookup["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// getting self stats
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Origin.ClientId);
|
var performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Origin.ClientId);
|
||||||
string performanceRankingString = performanceRanking == 0 ? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
var performanceRankingString = performanceRanking == 0
|
||||||
|
? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"]
|
||||||
|
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
||||||
|
|
||||||
if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Origin)))
|
// check if current client is connected to the server
|
||||||
|
if (E.Owner.GetClientsAsList().Any(client => client.Equals(E.Origin)))
|
||||||
{
|
{
|
||||||
pStats = E.Origin.GetAdditionalProperty<EFClientStatistics>(StatManager.CLIENT_STATS_KEY);
|
pStats = E.Origin.GetAdditionalProperty<EFClientStatistics>(StatManager.CLIENT_STATS_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
// happens if the user has not gotten a kill/death since connecting
|
||||||
|
if (pStats == null)
|
||||||
{
|
{
|
||||||
using (var ctx = new DatabaseContext(true))
|
await using var context = _contextFactory.CreateContext(false);
|
||||||
{
|
pStats = (await context.Set<EFClientStatistics>()
|
||||||
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId));
|
.FirstOrDefaultAsync(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
statLine = $"^5{pStats.Kills} ^7{_translationLookup["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{_translationLookup["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{_translationLookup["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
|
// if it's still null then they've not gotten a kill or death yet
|
||||||
|
statLine = pStats == null
|
||||||
|
? _translationLookup["PLUGINS_STATS_COMMANDS_NOTAVAILABLE"]
|
||||||
|
: $"^5{pStats.Kills} ^7{_translationLookup["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{_translationLookup["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{_translationLookup["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
if (E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
||||||
{
|
{
|
||||||
string name = E.Target == null ? E.Origin.Name : E.Target.Name;
|
var name = E.Target == null ? E.Origin.Name : E.Target.Name;
|
||||||
E.Owner.Broadcast(_translationLookup["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"].FormatExt(name));
|
E.Owner.Broadcast(_translationLookup["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"].FormatExt(name));
|
||||||
E.Owner.Broadcast(statLine);
|
E.Owner.Broadcast(statLine);
|
||||||
}
|
}
|
||||||
|
24
Plugins/Stats/Config/AnticheatConfiguration.cs
Normal file
24
Plugins/Stats/Config/AnticheatConfiguration.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
|
namespace Stats.Config
|
||||||
|
{
|
||||||
|
public class AnticheatConfiguration
|
||||||
|
{
|
||||||
|
public bool Enable { get; set; }
|
||||||
|
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; } = new Dictionary<long, DetectionType[]>();
|
||||||
|
public IList<long> IgnoredClientIds { get; set; } = new List<long>();
|
||||||
|
public IDictionary<Game, IDictionary<DetectionType, string[]>> IgnoredDetectionSpecification{ get; set; } = new Dictionary<Game, IDictionary<DetectionType, string[]>>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Game.IW4, new Dictionary<DetectionType, string[]>
|
||||||
|
{
|
||||||
|
{ DetectionType.Chest, new[] { "m21.+" } },
|
||||||
|
{ DetectionType.Recoil, new[] { "ranger.*_mp", "model1887.*_mp", ".+shotgun.*_mp", "turret_minigun_mp" } },
|
||||||
|
{ DetectionType.Button, new[] { ".*akimbo.*" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||||
|
|
||||||
@ -8,21 +9,41 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
|||||||
{
|
{
|
||||||
public class StatsConfiguration : IBaseConfiguration
|
public class StatsConfiguration : IBaseConfiguration
|
||||||
{
|
{
|
||||||
public bool EnableAntiCheat { get; set; }
|
[Obsolete]
|
||||||
|
public bool? EnableAntiCheat { get; set; }
|
||||||
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }
|
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }
|
||||||
public List<StreakMessageConfiguration> DeathstreakMessages { get; set; }
|
public List<StreakMessageConfiguration> DeathstreakMessages { get; set; }
|
||||||
public List<string> RecoilessWeapons { get; set; }
|
|
||||||
public int TopPlayersMinPlayTime { get; set; }
|
public int TopPlayersMinPlayTime { get; set; }
|
||||||
public bool StoreClientKills { get; set; }
|
public bool StoreClientKills { get; set; }
|
||||||
public int MostKillsMaxInactivityDays { get; set; } = 30;
|
public int MostKillsMaxInactivityDays { get; set; } = 30;
|
||||||
public int MostKillsClientLimit { get; set; } = 5;
|
public int MostKillsClientLimit { get; set; } = 5;
|
||||||
public IDictionary<DetectionType, DistributionConfiguration> DetectionDistributions { get; set; }
|
[Obsolete]
|
||||||
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; }
|
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; }
|
||||||
|
public AnticheatConfiguration AnticheatConfiguration { get; set; } = new AnticheatConfiguration();
|
||||||
|
|
||||||
|
#pragma warning disable CS0612 // Type or member is obsolete
|
||||||
|
public void ApplyMigration()
|
||||||
|
{
|
||||||
|
if (ServerDetectionTypes != null)
|
||||||
|
{
|
||||||
|
AnticheatConfiguration.ServerDetectionTypes = ServerDetectionTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerDetectionTypes = null;
|
||||||
|
|
||||||
|
if (EnableAntiCheat != null)
|
||||||
|
{
|
||||||
|
AnticheatConfiguration.Enable = EnableAntiCheat.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnableAntiCheat = null;
|
||||||
|
}
|
||||||
|
#pragma warning restore CS0612 // Type or member is obsolete
|
||||||
|
|
||||||
public string Name() => "StatsPluginSettings";
|
public string Name() => "StatsPluginSettings";
|
||||||
public IBaseConfiguration Generate()
|
public IBaseConfiguration Generate()
|
||||||
{
|
{
|
||||||
EnableAntiCheat = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);
|
AnticheatConfiguration.Enable = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);
|
||||||
KillstreakMessages = new List<StreakMessageConfiguration>()
|
KillstreakMessages = new List<StreakMessageConfiguration>()
|
||||||
{
|
{
|
||||||
new StreakMessageConfiguration(){
|
new StreakMessageConfiguration(){
|
||||||
@ -57,13 +78,6 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
RecoilessWeapons = new List<string>()
|
|
||||||
{
|
|
||||||
"ranger.*_mp",
|
|
||||||
"model1887.*_mp",
|
|
||||||
".+shotgun.*_mp"
|
|
||||||
};
|
|
||||||
|
|
||||||
TopPlayersMinPlayTime = 3600 * 3;
|
TopPlayersMinPlayTime = 3600 * 3;
|
||||||
StoreClientKills = false;
|
StoreClientKills = false;
|
||||||
|
|
||||||
|
7
Plugins/Stats/Helpers/MigrationHelper.cs
Normal file
7
Plugins/Stats/Helpers/MigrationHelper.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Stats.Helpers
|
||||||
|
{
|
||||||
|
public class MigrationHelper
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@ using IW4MAdmin.Plugins.Stats.Models;
|
|||||||
using IW4MAdmin.Plugins.Stats.Web.Dtos;
|
using IW4MAdmin.Plugins.Stats.Web.Dtos;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Database;
|
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
@ -15,7 +14,9 @@ using System.Linq;
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Helpers
|
namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||||
{
|
{
|
||||||
@ -29,22 +30,26 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
private static List<EFServer> serverModels;
|
private static List<EFServer> serverModels;
|
||||||
public static string CLIENT_STATS_KEY = "ClientStats";
|
public static string CLIENT_STATS_KEY = "ClientStats";
|
||||||
public static string CLIENT_DETECTIONS_KEY = "ClientDetections";
|
public static string CLIENT_DETECTIONS_KEY = "ClientDetections";
|
||||||
|
private readonly SemaphoreSlim _addPlayerWaiter = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
public StatManager(IManager mgr, IDatabaseContextFactory contextFactory, IConfigurationHandler<StatsConfiguration> configHandler)
|
public StatManager(ILogger<StatManager> logger, IManager mgr, IDatabaseContextFactory contextFactory, IConfigurationHandler<StatsConfiguration> configHandler)
|
||||||
{
|
{
|
||||||
_servers = new ConcurrentDictionary<long, ServerStats>();
|
_servers = new ConcurrentDictionary<long, ServerStats>();
|
||||||
_log = mgr.GetLogger(0);
|
_log = logger;
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_configHandler = configHandler;
|
_configHandler = configHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~StatManager()
|
||||||
|
{
|
||||||
|
_addPlayerWaiter.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
private void SetupServerIds()
|
private void SetupServerIds()
|
||||||
{
|
{
|
||||||
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
|
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
{
|
|
||||||
serverModels = ctx.Set<EFServer>().ToList();
|
serverModels = ctx.Set<EFServer>().ToList();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Expression<Func<EFRating, bool>> GetRankingFunc(long? serverId = null)
|
public Expression<Func<EFRating, bool>> GetRankingFunc(long? serverId = null)
|
||||||
{
|
{
|
||||||
@ -63,8 +68,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<int> GetClientOverallRanking(int clientId)
|
public async Task<int> GetClientOverallRanking(int clientId)
|
||||||
{
|
{
|
||||||
using (var context = _contextFactory.CreateContext(enableTracking: false))
|
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||||
{
|
|
||||||
var clientPerformance = await context.Set<EFRating>()
|
var clientPerformance = await context.Set<EFRating>()
|
||||||
.Where(r => r.RatingHistory.ClientId == clientId)
|
.Where(r => r.RatingHistory.ClientId == clientId)
|
||||||
.Where(r => r.ServerId == null)
|
.Where(r => r.ServerId == null)
|
||||||
@ -84,12 +89,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<TopStatsInfo>> GetTopStats(int start, int count, long? serverId = null)
|
public async Task<List<TopStatsInfo>> GetTopStats(int start, int count, long? serverId = null)
|
||||||
{
|
{
|
||||||
using (var context = _contextFactory.CreateContext(enableTracking: false))
|
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||||
{
|
|
||||||
// setup the query for the clients within the given rating range
|
// setup the query for the clients within the given rating range
|
||||||
var iqClientRatings = (from rating in context.Set<EFRating>()
|
var iqClientRatings = (from rating in context.Set<EFRating>()
|
||||||
.Where(GetRankingFunc(serverId))
|
.Where(GetRankingFunc(serverId))
|
||||||
@ -178,7 +181,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
return finished;
|
return finished;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a server to the StatManager server pool
|
/// Add a server to the StatManager server pool
|
||||||
@ -197,8 +199,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
long serverId = GetIdForServer(sv);
|
long serverId = GetIdForServer(sv);
|
||||||
EFServer server;
|
EFServer server;
|
||||||
|
|
||||||
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
|
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
{
|
|
||||||
var serverSet = ctx.Set<EFServer>();
|
var serverSet = ctx.Set<EFServer>();
|
||||||
// get the server from the database if it exists, otherwise create and insert a new one
|
// get the server from the database if it exists, otherwise create and insert a new one
|
||||||
server = serverSet.FirstOrDefault(s => s.ServerId == serverId);
|
server = serverSet.FirstOrDefault(s => s.ServerId == serverId);
|
||||||
@ -252,7 +253,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
ctx.Entry(server).Property(_prop => _prop.IsPasswordProtected).IsModified = true;
|
ctx.Entry(server).Property(_prop => _prop.IsPasswordProtected).IsModified = true;
|
||||||
server.IsPasswordProtected = !string.IsNullOrEmpty(sv.GamePassword);
|
server.IsPasswordProtected = !string.IsNullOrEmpty(sv.GamePassword);
|
||||||
ctx.SaveChanges();
|
ctx.SaveChanges();
|
||||||
}
|
|
||||||
|
|
||||||
// check to see if the stats have ever been initialized
|
// check to see if the stats have ever been initialized
|
||||||
var serverStats = InitializeServerStats(server.ServerId);
|
var serverStats = InitializeServerStats(server.ServerId);
|
||||||
@ -265,8 +265,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}");
|
_log.LogError(e, "{message}", Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]);
|
||||||
_log.WriteDebug(e.GetExceptionInfo());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,13 +276,21 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
/// <returns>EFClientStatistic of specified player</returns>
|
/// <returns>EFClientStatistic of specified player</returns>
|
||||||
public async Task<EFClientStatistics> AddPlayer(EFClient pl)
|
public async Task<EFClientStatistics> AddPlayer(EFClient pl)
|
||||||
{
|
{
|
||||||
|
var existingStats = pl.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
|
||||||
|
|
||||||
|
if (existingStats != null)
|
||||||
|
{
|
||||||
|
return existingStats;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await _addPlayerWaiter.WaitAsync();
|
||||||
long serverId = GetIdForServer(pl.CurrentServer);
|
long serverId = GetIdForServer(pl.CurrentServer);
|
||||||
|
|
||||||
if (!_servers.ContainsKey(serverId))
|
if (!_servers.ContainsKey(serverId))
|
||||||
{
|
{
|
||||||
_log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found");
|
_log.LogError("[Stats::AddPlayer] Server with id {serverId} could not be found", serverId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,8 +299,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
EFClientStatistics clientStats;
|
EFClientStatistics clientStats;
|
||||||
|
|
||||||
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
|
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
{
|
|
||||||
var clientStatsSet = ctx.Set<EFClientStatistics>();
|
var clientStatsSet = ctx.Set<EFClientStatistics>();
|
||||||
clientStats = clientStatsSet
|
clientStats = clientStatsSet
|
||||||
.Include(cl => cl.HitLocations)
|
.Include(cl => cl.HitLocations)
|
||||||
@ -311,7 +317,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
Skill = 0.0,
|
Skill = 0.0,
|
||||||
SPM = 0.0,
|
SPM = 0.0,
|
||||||
EloRating = 200.0,
|
EloRating = 200.0,
|
||||||
HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
|
HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>()
|
||||||
|
.Select(hl => new EFHitLocationCount()
|
||||||
{
|
{
|
||||||
Active = true,
|
Active = true,
|
||||||
HitCount = 0,
|
HitCount = 0,
|
||||||
@ -329,7 +336,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
// migration for previous existing stats
|
// migration for previous existing stats
|
||||||
if (clientStats.HitLocations.Count == 0)
|
if (clientStats.HitLocations.Count == 0)
|
||||||
{
|
{
|
||||||
clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>()
|
clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation))
|
||||||
|
.OfType<IW4Info.HitLocation>()
|
||||||
.Select(hl => new EFHitLocationCount()
|
.Select(hl => new EFHitLocationCount()
|
||||||
{
|
{
|
||||||
Active = true,
|
Active = true,
|
||||||
@ -360,16 +368,22 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
clientStats.LastScore = pl.Score;
|
clientStats.LastScore = pl.Score;
|
||||||
|
|
||||||
pl.SetAdditionalProperty(CLIENT_DETECTIONS_KEY, new Detection(_log, clientStats));
|
pl.SetAdditionalProperty(CLIENT_DETECTIONS_KEY, new Detection(_log, clientStats));
|
||||||
pl.CurrentServer.Logger.WriteInfo($"Added {pl} to stats");
|
_log.LogDebug("Added {client} to stats", pl.ToString());
|
||||||
}
|
|
||||||
|
|
||||||
return clientStats;
|
return clientStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_log.WriteWarning("Could not add client to stats");
|
_log.LogError(ex, "Could not add client to stats {@client}", pl.ToString());
|
||||||
_log.WriteDebug(ex.GetExceptionInfo());
|
}
|
||||||
|
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_addPlayerWaiter.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
_addPlayerWaiter.Release(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -382,11 +396,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task RemovePlayer(EFClient pl)
|
public async Task RemovePlayer(EFClient pl)
|
||||||
{
|
{
|
||||||
pl.CurrentServer.Logger.WriteInfo($"Removing {pl} from stats");
|
_log.LogDebug("Removing {client} from stats", pl.ToString());
|
||||||
|
|
||||||
if (pl.CurrentServer == null)
|
if (pl.CurrentServer == null)
|
||||||
{
|
{
|
||||||
pl.CurrentServer.Logger.WriteWarning($"Disconnecting client {pl} is not on a server, state is {pl.State}");
|
_log.LogWarning("Disconnecting client {client} is not on a server", pl.ToString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,18 +421,16 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pl.CurrentServer.Logger.WriteWarning($"Disconnecting client {pl} has not been added to stats, state is {pl.State}");
|
_log.LogWarning("Disconnecting client {client} has not been added to stats", pl.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveClientStats(EFClientStatistics clientStats)
|
private async Task SaveClientStats(EFClientStatistics clientStats)
|
||||||
{
|
{
|
||||||
using (var ctx = _contextFactory.CreateContext())
|
await using var ctx = _contextFactory.CreateContext();
|
||||||
{
|
|
||||||
ctx.Update(clientStats);
|
ctx.Update(clientStats);
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId)
|
public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId)
|
||||||
{
|
{
|
||||||
@ -452,10 +464,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (FormatException)
|
catch (FormatException ex)
|
||||||
{
|
{
|
||||||
_log.WriteError("Could not parse vector data from hit");
|
_log.LogWarning(ex, "Could not parse vector data from hit");
|
||||||
_log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles} Snapshot - {string.Join(",", snapshotAngles.Select(_a => _a.ToString()))}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,7 +492,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
IsKill = !isDamage,
|
IsKill = !isDamage,
|
||||||
AnglesList = snapshotAngles,
|
AnglesList = snapshotAngles,
|
||||||
IsAlive = isAlive == "1",
|
IsAlive = isAlive == "1",
|
||||||
TimeSinceLastAttack = long.Parse(lastAttackTime)
|
TimeSinceLastAttack = long.Parse(lastAttackTime),
|
||||||
|
GameName = attacker.CurrentServer.GameName
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hit.HitLoc == IW4Info.HitLocation.shield)
|
if (hit.HitLoc == IW4Info.HitLocation.shield)
|
||||||
@ -526,8 +538,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_log.WriteError("Could not store client kills");
|
_log.LogError(e, "Could not store client kills");
|
||||||
_log.WriteDebug(e.GetExceptionInfo());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
@ -539,7 +550,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Plugin.Config.Configuration().EnableAntiCheat && !attacker.IsBot && attacker.ClientId != victim.ClientId)
|
if (Plugin.Config.Configuration().AnticheatConfiguration.Enable && !attacker.IsBot && attacker.ClientId != victim.ClientId)
|
||||||
{
|
{
|
||||||
clientDetection.TrackedHits.Add(hit);
|
clientDetection.TrackedHits.Add(hit);
|
||||||
|
|
||||||
@ -555,10 +566,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
if (oldestHit.IsAlive)
|
if (oldestHit.IsAlive)
|
||||||
{
|
{
|
||||||
var result = DeterminePenaltyResult(clientDetection.ProcessHit(oldestHit), attacker.CurrentServer.EndPoint);
|
var result = DeterminePenaltyResult(clientDetection.ProcessHit(oldestHit), attacker);
|
||||||
#if !DEBUG
|
|
||||||
|
if (!Utilities.IsDevelopment)
|
||||||
|
{
|
||||||
await ApplyPenalty(result, attacker);
|
await ApplyPenalty(result, attacker);
|
||||||
#endif
|
}
|
||||||
|
|
||||||
if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any)
|
if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any)
|
||||||
{
|
{
|
||||||
@ -580,9 +593,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
catch (TaskCanceledException) { }
|
catch (TaskCanceledException) { }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_log.WriteError("Could not save hit or AC info");
|
_log.LogError(ex, "Could not save hit or anti-cheat info {@attacker} {@victim} {server}", attacker, victim, serverId);
|
||||||
_log.WriteDebug(ex.GetExceptionInfo());
|
|
||||||
_log.WriteDebug($"Attacker: {attacker} Victim: {victim}, ServerId {serverId}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
@ -594,10 +605,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DetectionPenaltyResult DeterminePenaltyResult(IEnumerable<DetectionPenaltyResult> results, long serverId)
|
private DetectionPenaltyResult DeterminePenaltyResult(IEnumerable<DetectionPenaltyResult> results, EFClient client)
|
||||||
{
|
{
|
||||||
// allow disabling of certain detection types
|
// allow disabling of certain detection types
|
||||||
results = results.Where(_result => ShouldUseDetection(serverId, _result.Type));
|
results = results.Where(_result => ShouldUseDetection(client.CurrentServer, _result.Type, client.ClientId));
|
||||||
return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ??
|
return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ??
|
||||||
results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ??
|
results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ??
|
||||||
new DetectionPenaltyResult()
|
new DetectionPenaltyResult()
|
||||||
@ -608,32 +619,40 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
public async Task SaveHitCache(long serverId)
|
public async Task SaveHitCache(long serverId)
|
||||||
{
|
{
|
||||||
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
|
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
{
|
|
||||||
var server = _servers[serverId];
|
var server = _servers[serverId];
|
||||||
ctx.AddRange(server.HitCache.ToList());
|
ctx.AddRange(server.HitCache.ToList());
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
server.HitCache.Clear();
|
server.HitCache.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
|
||||||
|
{
|
||||||
|
var detectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.ServerDetectionTypes;
|
||||||
|
var ignoredClients = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredClientIds;
|
||||||
|
|
||||||
|
if (ignoredClients.Contains(clientId))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldUseDetection(long serverId, DetectionType detectionType)
|
|
||||||
{
|
|
||||||
var detectionTypes = Plugin.Config.Configuration().ServerDetectionTypes;
|
|
||||||
|
|
||||||
if (detectionTypes == null)
|
try
|
||||||
{
|
{
|
||||||
|
if (!detectionTypes[server.EndPoint].Contains(detectionType))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!detectionTypes.ContainsKey(serverId))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return detectionTypes[serverId].Contains(detectionType);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ApplyPenalty(DetectionPenaltyResult penalty, EFClient attacker)
|
async Task ApplyPenalty(DetectionPenaltyResult penalty, EFClient attacker)
|
||||||
{
|
{
|
||||||
var penaltyClient = Utilities.IW4MAdminClient(attacker.CurrentServer);
|
var penaltyClient = Utilities.IW4MAdminClient(attacker.CurrentServer);
|
||||||
@ -684,15 +703,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
{
|
{
|
||||||
EFACSnapshot change;
|
EFACSnapshot change;
|
||||||
|
|
||||||
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
|
await using var ctx = _contextFactory.CreateContext();
|
||||||
{
|
|
||||||
while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot))
|
while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot))
|
||||||
{
|
{
|
||||||
ctx.Add(change);
|
ctx.Add(change);
|
||||||
}
|
}
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async Task AddStandardKill(EFClient attacker, EFClient victim)
|
public async Task AddStandardKill(EFClient attacker, EFClient victim)
|
||||||
{
|
{
|
||||||
@ -701,9 +718,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
var attackerStats = attacker.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
|
var attackerStats = attacker.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
|
||||||
var victimStats = victim.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
|
var victimStats = victim.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
_log.WriteDebug("Processing standard kill");
|
|
||||||
#endif
|
|
||||||
// update the total stats
|
// update the total stats
|
||||||
_servers[serverId].ServerStatistics.TotalKills += 1;
|
_servers[serverId].ServerStatistics.TotalKills += 1;
|
||||||
|
|
||||||
@ -741,14 +755,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
// fixme: why?
|
// fixme: why?
|
||||||
if (double.IsNaN(victimStats.SPM) || double.IsNaN(victimStats.Skill))
|
if (double.IsNaN(victimStats.SPM) || double.IsNaN(victimStats.Skill))
|
||||||
{
|
{
|
||||||
_log.WriteDebug($"[StatManager::AddStandardKill] victim SPM/SKILL {victimStats.SPM} {victimStats.Skill}");
|
_log.LogWarning("victim SPM/SKILL {@victimStats}", victimStats);
|
||||||
victimStats.SPM = 0.0;
|
victimStats.SPM = 0.0;
|
||||||
victimStats.Skill = 0.0;
|
victimStats.Skill = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (double.IsNaN(attackerStats.SPM) || double.IsNaN(attackerStats.Skill))
|
if (double.IsNaN(attackerStats.SPM) || double.IsNaN(attackerStats.Skill))
|
||||||
{
|
{
|
||||||
_log.WriteDebug($"[StatManager::AddStandardKill] attacker SPM/SKILL {victimStats.SPM} {victimStats.Skill}");
|
_log.LogWarning("attacker SPM/SKILL {@attackerStats}", attackerStats);
|
||||||
attackerStats.SPM = 0.0;
|
attackerStats.SPM = 0.0;
|
||||||
attackerStats.Skill = 0.0;
|
attackerStats.Skill = 0.0;
|
||||||
}
|
}
|
||||||
@ -768,8 +782,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_log.WriteWarning($"Could not update stat history for {attacker}");
|
_log.LogWarning(e, "Could not update stat history for {attacker}", attacker.ToString());
|
||||||
_log.WriteDebug(e.GetExceptionInfo());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
@ -800,8 +813,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime;
|
int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime;
|
||||||
|
|
||||||
using (var ctx = _contextFactory.CreateContext(enableTracking: true))
|
await using var ctx = _contextFactory.CreateContext(enableTracking: true);
|
||||||
{
|
|
||||||
// select the rating history for client
|
// select the rating history for client
|
||||||
var iqHistoryLink = from history in ctx.Set<EFClientRatingHistory>()
|
var iqHistoryLink = from history in ctx.Set<EFClientRatingHistory>()
|
||||||
.Include(h => h.Ratings)
|
.Include(h => h.Ratings)
|
||||||
@ -954,7 +966,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs the incrementation of kills and deaths for client statistics
|
/// Performs the incrementation of kills and deaths for client statistics
|
||||||
@ -1086,8 +1097,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
if (clientStats.SPM < 0)
|
if (clientStats.SPM < 0)
|
||||||
{
|
{
|
||||||
_log.WriteWarning("[StatManager:UpdateStats] clientStats SPM < 0");
|
_log.LogWarning("clientStats SPM < 0 {scoreDifference} {@clientStats}", scoreDifference, clientStats);
|
||||||
_log.WriteDebug($"{scoreDifference}-{clientStats.RoundScore} - {clientStats.LastScore} - {clientStats.SessionScore}");
|
|
||||||
clientStats.SPM = 0;
|
clientStats.SPM = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1097,8 +1107,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
// fixme: how does this happen?
|
// fixme: how does this happen?
|
||||||
if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill))
|
if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill))
|
||||||
{
|
{
|
||||||
_log.WriteWarning("[StatManager::UpdateStats] clientStats SPM/Skill NaN");
|
_log.LogWarning("clientStats SPM/Skill NaN {@killInfo}", new {killSPM, KDRWeight, totalPlayTime, SPMAgainstPlayWeight, clientStats, scoreDifference});
|
||||||
_log.WriteDebug($"{killSPM}-{KDRWeight}-{totalPlayTime}-{SPMAgainstPlayWeight}-{clientStats.SPM}-{clientStats.Skill}-{scoreDifference}");
|
|
||||||
clientStats.SPM = 0;
|
clientStats.SPM = 0;
|
||||||
clientStats.Skill = 0;
|
clientStats.Skill = 0;
|
||||||
}
|
}
|
||||||
@ -1113,14 +1122,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
{
|
{
|
||||||
EFServerStatistics serverStats;
|
EFServerStatistics serverStats;
|
||||||
|
|
||||||
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
|
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
{
|
|
||||||
var serverStatsSet = ctx.Set<EFServerStatistics>();
|
var serverStatsSet = ctx.Set<EFServerStatistics>();
|
||||||
serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId);
|
serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId);
|
||||||
|
|
||||||
if (serverStats == null)
|
if (serverStats == null)
|
||||||
{
|
{
|
||||||
_log.WriteDebug($"Initializing server stats for {serverId}");
|
_log.LogDebug("Initializing server stats for {serverId}", serverId);
|
||||||
// server stats have never been generated before
|
// server stats have never been generated before
|
||||||
serverStats = new EFServerStatistics()
|
serverStats = new EFServerStatistics()
|
||||||
{
|
{
|
||||||
@ -1132,23 +1140,34 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
serverStats = serverStatsSet.Add(serverStats).Entity;
|
serverStats = serverStatsSet.Add(serverStats).Entity;
|
||||||
ctx.SaveChanges();
|
ctx.SaveChanges();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return serverStats;
|
return serverStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetKillstreaks(Server sv)
|
public void ResetKillstreaks(Server sv)
|
||||||
{
|
{
|
||||||
foreach (var stat in sv.GetClientsAsList()
|
foreach (var session in sv.GetClientsAsList()
|
||||||
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY)))
|
.Select(_client => new
|
||||||
{
|
{
|
||||||
stat?.StartNewSession();
|
stat = _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY),
|
||||||
|
detection = _client.GetAdditionalProperty<Detection>(CLIENT_DETECTIONS_KEY)
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
session.stat?.StartNewSession();
|
||||||
|
session.detection?.OnMapChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetStats(EFClient client)
|
public void ResetStats(EFClient client)
|
||||||
{
|
{
|
||||||
var stats = client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
|
var stats = client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
|
||||||
|
|
||||||
|
// the cached stats have not been loaded yet
|
||||||
|
if (stats == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
stats.Kills = 0;
|
stats.Kills = 0;
|
||||||
stats.Deaths = 0;
|
stats.Deaths = 0;
|
||||||
stats.SPM = 0;
|
stats.SPM = 0;
|
||||||
@ -1157,7 +1176,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
stats.EloRating = 200;
|
stats.EloRating = 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddMessageAsync(int clientId, long serverId, string message)
|
public async Task AddMessageAsync(int clientId, long serverId, bool sentIngame, string message)
|
||||||
{
|
{
|
||||||
// the web users can have no account
|
// the web users can have no account
|
||||||
if (clientId < 1)
|
if (clientId < 1)
|
||||||
@ -1165,19 +1184,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
|
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
{
|
|
||||||
ctx.Set<EFClientMessage>().Add(new EFClientMessage()
|
ctx.Set<EFClientMessage>().Add(new EFClientMessage()
|
||||||
{
|
{
|
||||||
ClientId = clientId,
|
ClientId = clientId,
|
||||||
Message = message,
|
Message = message,
|
||||||
ServerId = serverId,
|
ServerId = serverId,
|
||||||
TimeSent = DateTime.UtcNow
|
TimeSent = DateTime.UtcNow,
|
||||||
|
SentIngame = sentIngame
|
||||||
});
|
});
|
||||||
|
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Sync(Server sv)
|
public async Task Sync(Server sv)
|
||||||
{
|
{
|
||||||
@ -1188,12 +1206,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
{
|
{
|
||||||
await waiter.WaitAsync();
|
await waiter.WaitAsync();
|
||||||
|
|
||||||
using (var ctx = _contextFactory.CreateContext())
|
await using var ctx = _contextFactory.CreateContext();
|
||||||
{
|
|
||||||
var serverStatsSet = ctx.Set<EFServerStatistics>();
|
var serverStatsSet = ctx.Set<EFServerStatistics>();
|
||||||
serverStatsSet.Update(_servers[serverId].ServerStatistics);
|
serverStatsSet.Update(_servers[serverId].ServerStatistics);
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var stats in sv.GetClientsAsList()
|
foreach (var stats in sv.GetClientsAsList()
|
||||||
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))
|
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))
|
||||||
@ -1207,8 +1223,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_log.WriteError("There was a probably syncing server stats");
|
_log.LogError(e, "There was a problem syncing server stats");
|
||||||
_log.WriteDebug(e.GetExceptionInfo());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
@ -1232,6 +1247,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return 886229536;
|
return 886229536;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: this is not stable and will need to be migrated again...
|
||||||
long id = HashCode.Combine(server.IP, server.Port);
|
long id = HashCode.Combine(server.IP, server.Port);
|
||||||
id = id < 0 ? Math.Abs(id) : id;
|
id = id < 0 ? Math.Abs(id) : id;
|
||||||
long? serverId;
|
long? serverId;
|
||||||
|
@ -26,7 +26,7 @@ namespace Stats.Helpers
|
|||||||
public async Task<ResourceQueryHelperResult<StatsInfoResult>> QueryResource(StatsInfoRequest query)
|
public async Task<ResourceQueryHelperResult<StatsInfoResult>> QueryResource(StatsInfoRequest query)
|
||||||
{
|
{
|
||||||
var result = new ResourceQueryHelperResult<StatsInfoResult>();
|
var result = new ResourceQueryHelperResult<StatsInfoResult>();
|
||||||
using var context = _contextFactory.CreateContext(enableTracking: false);
|
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
|
||||||
// we need to get the ratings separately because there's not explicit FK
|
// we need to get the ratings separately because there's not explicit FK
|
||||||
var ratings = await context.Set<EFClientRatingHistory>()
|
var ratings = await context.Set<EFClientRatingHistory>()
|
||||||
|
@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Models
|
namespace IW4MAdmin.Plugins.Stats.Models
|
||||||
{
|
{
|
||||||
@ -44,6 +45,8 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
|||||||
public float AdsPercent { get; set; }
|
public float AdsPercent { get; set; }
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public List<Vector3> AnglesList { get; set; }
|
public List<Vector3> AnglesList { get; set; }
|
||||||
|
[NotMapped]
|
||||||
|
public Game GameName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the attacker was alive after last captured angle
|
/// Indicates if the attacker was alive after last captured angle
|
||||||
|
@ -17,5 +17,6 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
|||||||
public virtual EFClient Client { get; set; }
|
public virtual EFClient Client { get; set; }
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
public DateTime TimeSent { get; set; }
|
public DateTime TimeSent { get; set; }
|
||||||
|
public bool SentIngame { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,13 @@ namespace Stats.Models
|
|||||||
builder.Entity<EFRating>()
|
builder.Entity<EFRating>()
|
||||||
.HasIndex(p => new { p.Performance, p.Ranking, p.When });
|
.HasIndex(p => new { p.Performance, p.Ranking, p.When });
|
||||||
|
|
||||||
builder.Entity<EFClientMessage>()
|
builder.Entity<EFRating>()
|
||||||
.HasIndex(p => p.TimeSent);
|
.HasIndex(p => new { p.When, p.ServerId, p.Performance, p.ActivityAmount });
|
||||||
|
|
||||||
|
builder.Entity<EFClientMessage>(message =>
|
||||||
|
{
|
||||||
|
message.HasIndex(p => p.TimeSent);
|
||||||
|
});
|
||||||
|
|
||||||
// force pluralization
|
// force pluralization
|
||||||
builder.Entity<EFClientKill>().ToTable("EFClientKills");
|
builder.Entity<EFClientKill>().ToTable("EFClientKills");
|
||||||
|
@ -14,6 +14,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharedLibraryCore.Commands;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats
|
namespace IW4MAdmin.Plugins.Stats
|
||||||
{
|
{
|
||||||
@ -28,23 +30,24 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
public static StatManager Manager { get; private set; }
|
public static StatManager Manager { get; private set; }
|
||||||
public static IManager ServerManager;
|
public static IManager ServerManager;
|
||||||
public static IConfigurationHandler<StatsConfiguration> Config { get; private set; }
|
public static IConfigurationHandler<StatsConfiguration> Config { get; private set; }
|
||||||
#if DEBUG
|
|
||||||
int scriptDamageCount;
|
|
||||||
int scriptKillCount;
|
|
||||||
#endif
|
|
||||||
private readonly IDatabaseContextFactory _databaseContextFactory;
|
private readonly IDatabaseContextFactory _databaseContextFactory;
|
||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
private readonly IMetaService _metaService;
|
private readonly IMetaService _metaService;
|
||||||
private readonly IResourceQueryHelper<ChatSearchQuery, MessageResponse> _chatQueryHelper;
|
private readonly IResourceQueryHelper<ChatSearchQuery, MessageResponse> _chatQueryHelper;
|
||||||
|
private readonly ILogger<StatManager> _managerLogger;
|
||||||
|
private readonly ILogger<Plugin> _logger;
|
||||||
|
|
||||||
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory databaseContextFactory,
|
public Plugin(ILogger<Plugin> logger, IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory databaseContextFactory,
|
||||||
ITranslationLookup translationLookup, IMetaService metaService, IResourceQueryHelper<ChatSearchQuery, MessageResponse> chatQueryHelper)
|
ITranslationLookup translationLookup, IMetaService metaService, IResourceQueryHelper<ChatSearchQuery, MessageResponse> chatQueryHelper, ILogger<StatManager> managerLogger)
|
||||||
{
|
{
|
||||||
Config = configurationHandlerFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
|
Config = configurationHandlerFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
|
||||||
_databaseContextFactory = databaseContextFactory;
|
_databaseContextFactory = databaseContextFactory;
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
_metaService = metaService;
|
_metaService = metaService;
|
||||||
_chatQueryHelper = chatQueryHelper;
|
_chatQueryHelper = chatQueryHelper;
|
||||||
|
_managerLogger = managerLogger;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnEventAsync(GameEvent E, Server S)
|
public async Task OnEventAsync(GameEvent E, Server S)
|
||||||
@ -54,11 +57,6 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
case GameEvent.EventType.Start:
|
case GameEvent.EventType.Start:
|
||||||
Manager.AddServer(S);
|
Manager.AddServer(S);
|
||||||
break;
|
break;
|
||||||
case GameEvent.EventType.Stop:
|
|
||||||
break;
|
|
||||||
case GameEvent.EventType.PreConnect:
|
|
||||||
await Manager.AddPlayer(E.Origin);
|
|
||||||
break;
|
|
||||||
case GameEvent.EventType.Disconnect:
|
case GameEvent.EventType.Disconnect:
|
||||||
await Manager.RemovePlayer(E.Origin);
|
await Manager.RemovePlayer(E.Origin);
|
||||||
break;
|
break;
|
||||||
@ -66,7 +64,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
if (!string.IsNullOrEmpty(E.Data) &&
|
if (!string.IsNullOrEmpty(E.Data) &&
|
||||||
E.Origin.ClientId > 1)
|
E.Origin.ClientId > 1)
|
||||||
{
|
{
|
||||||
await Manager.AddMessageAsync(E.Origin.ClientId, StatManager.GetIdForServer(S), E.Data);
|
await Manager.AddMessageAsync(E.Origin.ClientId, StatManager.GetIdForServer(S), true, E.Data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GameEvent.EventType.MapChange:
|
case GameEvent.EventType.MapChange:
|
||||||
@ -77,21 +75,13 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
case GameEvent.EventType.MapEnd:
|
case GameEvent.EventType.MapEnd:
|
||||||
await Manager.Sync(S);
|
await Manager.Sync(S);
|
||||||
break;
|
break;
|
||||||
case GameEvent.EventType.JoinTeam:
|
case GameEvent.EventType.Command:
|
||||||
break;
|
var shouldPersist = !string.IsNullOrEmpty(E.Data) &&
|
||||||
case GameEvent.EventType.Broadcast:
|
E.Extra is SayCommand;
|
||||||
break;
|
if (shouldPersist)
|
||||||
case GameEvent.EventType.Tell:
|
{
|
||||||
break;
|
await Manager.AddMessageAsync(E.Origin.ClientId, StatManager.GetIdForServer(S), false, E.Data);
|
||||||
case GameEvent.EventType.Kick:
|
}
|
||||||
break;
|
|
||||||
case GameEvent.EventType.Ban:
|
|
||||||
break;
|
|
||||||
case GameEvent.EventType.Unknown:
|
|
||||||
break;
|
|
||||||
case GameEvent.EventType.Report:
|
|
||||||
break;
|
|
||||||
case GameEvent.EventType.Flag:
|
|
||||||
break;
|
break;
|
||||||
case GameEvent.EventType.ScriptKill:
|
case GameEvent.EventType.ScriptKill:
|
||||||
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
|
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
|
||||||
@ -103,22 +93,14 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
E.Origin = E.Target;
|
E.Origin = E.Target;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
await EnsureClientsAdded(E.Origin, E.Target);
|
||||||
scriptKillCount++;
|
|
||||||
S.Logger.WriteInfo($"Start ScriptKill {scriptKillCount}");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(S), S.CurrentMap.Name, killInfo[7], killInfo[8],
|
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(S), S.CurrentMap.Name, killInfo[7], killInfo[8],
|
||||||
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15], killInfo[16], killInfo[17]);
|
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15], killInfo[16], killInfo[17]);
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
S.Logger.WriteInfo($"End ScriptKill {scriptKillCount}");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
S.Logger.WriteDebug("Skipping script kill as it is ignored or data in customcallbacks is outdated/missing");
|
_logger.LogDebug("Skipping script kill as it is ignored or data in customcallbacks is outdated/missing");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GameEvent.EventType.Kill:
|
case GameEvent.EventType.Kill:
|
||||||
@ -130,6 +112,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
E.Origin = E.Target;
|
E.Origin = E.Target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await EnsureClientsAdded(E.Origin, E.Target);
|
||||||
await Manager.AddStandardKill(E.Origin, E.Target);
|
await Manager.AddStandardKill(E.Origin, E.Target);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -155,22 +138,14 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
E.Origin = E.Target;
|
E.Origin = E.Target;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
await EnsureClientsAdded(E.Origin, E.Target);
|
||||||
scriptDamageCount++;
|
|
||||||
S.Logger.WriteInfo($"Start ScriptDamage {scriptDamageCount}");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(S), S.CurrentMap.Name, killInfo[7], killInfo[8],
|
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, StatManager.GetIdForServer(S), S.CurrentMap.Name, killInfo[7], killInfo[8],
|
||||||
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15], killInfo[16], killInfo[17]);
|
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15], killInfo[16], killInfo[17]);
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
S.Logger.WriteInfo($"End ScriptDamage {scriptDamageCount}");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
S.Logger.WriteDebug("Skipping script damage as it is ignored or data in customcallbacks is outdated/missing");
|
_logger.LogDebug("Skipping script damage as it is ignored or data in customcallbacks is outdated/missing");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -182,8 +157,9 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
if (Config.Configuration() == null)
|
if (Config.Configuration() == null)
|
||||||
{
|
{
|
||||||
Config.Set((StatsConfiguration)new StatsConfiguration().Generate());
|
Config.Set((StatsConfiguration)new StatsConfiguration().Generate());
|
||||||
await Config.Save();
|
|
||||||
}
|
}
|
||||||
|
Config.Configuration().ApplyMigration();
|
||||||
|
await Config.Save();
|
||||||
|
|
||||||
// register the topstats page
|
// register the topstats page
|
||||||
// todo:generate the URL/Location instead of hardcoding
|
// todo:generate the URL/Location instead of hardcoding
|
||||||
@ -197,11 +173,9 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
{
|
{
|
||||||
IList<EFClientStatistics> clientStats;
|
IList<EFClientStatistics> clientStats;
|
||||||
int messageCount = 0;
|
int messageCount = 0;
|
||||||
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false))
|
await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
|
||||||
{
|
|
||||||
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync();
|
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync();
|
||||||
messageCount = await ctx.Set<EFClientMessage>().CountAsync(_message => _message.ClientId == request.ClientId);
|
messageCount = await ctx.Set<EFClientMessage>().CountAsync(_message => _message.ClientId == request.ClientId);
|
||||||
}
|
|
||||||
|
|
||||||
int kills = clientStats.Sum(c => c.Kills);
|
int kills = clientStats.Sum(c => c.Kills);
|
||||||
int deaths = clientStats.Sum(c => c.Deaths);
|
int deaths = clientStats.Sum(c => c.Deaths);
|
||||||
@ -276,13 +250,11 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
{
|
{
|
||||||
IList<EFClientStatistics> clientStats;
|
IList<EFClientStatistics> clientStats;
|
||||||
|
|
||||||
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false))
|
await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
|
||||||
{
|
|
||||||
clientStats = await ctx.Set<EFClientStatistics>()
|
clientStats = await ctx.Set<EFClientStatistics>()
|
||||||
.Include(c => c.HitLocations)
|
.Include(c => c.HitLocations)
|
||||||
.Where(c => c.ClientId == request.ClientId)
|
.Where(c => c.ClientId == request.ClientId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
|
||||||
|
|
||||||
double headRatio = 0;
|
double headRatio = 0;
|
||||||
double chestRatio = 0;
|
double chestRatio = 0;
|
||||||
@ -405,7 +377,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
return (await _chatQueryHelper.QueryResource(query)).Results;
|
return (await _chatQueryHelper.QueryResource(query)).Results;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.Configuration().EnableAntiCheat)
|
if (Config.Configuration().AnticheatConfiguration.Enable)
|
||||||
{
|
{
|
||||||
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, getAnticheatInfo);
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, getAnticheatInfo);
|
||||||
}
|
}
|
||||||
@ -415,32 +387,28 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
|
|
||||||
async Task<string> totalKills(Server server)
|
async Task<string> totalKills(Server server)
|
||||||
{
|
{
|
||||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
await using var context = _databaseContextFactory.CreateContext(false);
|
||||||
{
|
long kills = await context.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalKills);
|
||||||
long kills = await ctx.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalKills);
|
|
||||||
return kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
|
return kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async Task<string> totalPlayTime(Server server)
|
async Task<string> totalPlayTime(Server server)
|
||||||
{
|
{
|
||||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
await using var context = _databaseContextFactory.CreateContext(false);
|
||||||
{
|
long playTime = await context.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalPlayTime);
|
||||||
long playTime = await ctx.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalPlayTime);
|
|
||||||
return (playTime / 3600.0).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
|
return (playTime / 3600.0).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async Task<string> topStats(Server s)
|
async Task<string> topStats(Server s)
|
||||||
{
|
{
|
||||||
// todo: this needs to needs to be updated when we DI the lookup
|
// todo: this needs to needs to be updated when we DI the lookup
|
||||||
return string.Join(Environment.NewLine, await Commands.TopStats.GetTopStats(s, Utilities.CurrentLocalization.LocalizationIndex));
|
return string.Join(Environment.NewLine, await Commands.TopStats.GetTopStats(s, Utilities.CurrentLocalization.LocalizationIndex, _databaseContextFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<string> mostPlayed(Server s)
|
async Task<string> mostPlayed(Server s)
|
||||||
{
|
{
|
||||||
// todo: this needs to needs to be updated when we DI the lookup
|
// todo: this needs to needs to be updated when we DI the lookup
|
||||||
return string.Join(Environment.NewLine, await Commands.MostPlayedCommand.GetMostPlayed(s, Utilities.CurrentLocalization.LocalizationIndex));
|
return string.Join(Environment.NewLine, await Commands.MostPlayedCommand.GetMostPlayed(s, Utilities.CurrentLocalization.LocalizationIndex, _databaseContextFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<string> mostKills(Server gameServer)
|
async Task<string> mostKills(Server gameServer)
|
||||||
@ -456,7 +424,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
manager.GetMessageTokens().Add(new MessageToken("MOSTKILLS", mostKills));
|
manager.GetMessageTokens().Add(new MessageToken("MOSTKILLS", mostKills));
|
||||||
|
|
||||||
ServerManager = manager;
|
ServerManager = manager;
|
||||||
Manager = new StatManager(manager, _databaseContextFactory, Config);
|
Manager = new StatManager(_managerLogger, manager, _databaseContextFactory, Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnTickAsync(Server S)
|
public Task OnTickAsync(Server S)
|
||||||
@ -496,6 +464,22 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="s"></param>
|
/// <param name="s"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private bool ShouldOverrideAnticheatSetting(Server s) => Config.Configuration().EnableAntiCheat && s.GameName == Server.Game.IW5;
|
private bool ShouldOverrideAnticheatSetting(Server s) => Config.Configuration().AnticheatConfiguration.Enable && s.GameName == Server.Game.IW5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes sure both clients are added
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="origin"></param>
|
||||||
|
/// <param name="target"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task EnsureClientsAdded(EFClient origin, EFClient target)
|
||||||
|
{
|
||||||
|
await Manager.AddPlayer(origin);
|
||||||
|
|
||||||
|
if (!origin.Equals(target))
|
||||||
|
{
|
||||||
|
await Manager.AddPlayer(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,15 @@
|
|||||||
<Copyright>2018</Copyright>
|
<Copyright>2018</Copyright>
|
||||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>8.0</LangVersion>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2020.12.20.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SharedLibraryCore;
|
|
||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using Stats.Dtos;
|
using Stats.Dtos;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace StatsWeb.API
|
namespace StatsWeb.API
|
||||||
{
|
{
|
||||||
@ -16,7 +17,7 @@ namespace StatsWeb.API
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IResourceQueryHelper<StatsInfoRequest, StatsInfoResult> _statsQueryHelper;
|
private readonly IResourceQueryHelper<StatsInfoRequest, StatsInfoResult> _statsQueryHelper;
|
||||||
|
|
||||||
public StatsController(ILogger logger, IResourceQueryHelper<StatsInfoRequest, StatsInfoResult> statsQueryHelper)
|
public StatsController(ILogger<StatsController> logger, IResourceQueryHelper<StatsInfoRequest, StatsInfoResult> statsQueryHelper)
|
||||||
{
|
{
|
||||||
_statsQueryHelper = statsQueryHelper;
|
_statsQueryHelper = statsQueryHelper;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -56,8 +57,7 @@ namespace StatsWeb.API
|
|||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Could not get client stats for client id {clientId}");
|
_logger.LogWarning(e, "Could not get client stats for client id {clientId}", clientId);
|
||||||
_logger.WriteDebug(e.GetExceptionInfo());
|
|
||||||
|
|
||||||
return StatusCode(StatusCodes.Status500InternalServerError, new ErrorResponse
|
return StatusCode(StatusCodes.Status500InternalServerError, new ErrorResponse
|
||||||
{
|
{
|
||||||
|
@ -10,6 +10,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace StatsWeb
|
namespace StatsWeb
|
||||||
{
|
{
|
||||||
@ -23,7 +25,7 @@ namespace StatsWeb
|
|||||||
private readonly ApplicationConfiguration _appConfig;
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
private List<EFServer> serverCache;
|
private List<EFServer> serverCache;
|
||||||
|
|
||||||
public ChatResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory, ApplicationConfiguration appConfig)
|
public ChatResourceQueryHelper(ILogger<ChatResourceQueryHelper> logger, IDatabaseContextFactory contextFactory, ApplicationConfiguration appConfig)
|
||||||
{
|
{
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -39,7 +41,7 @@ namespace StatsWeb
|
|||||||
}
|
}
|
||||||
|
|
||||||
var result = new ResourceQueryHelperResult<MessageResponse>();
|
var result = new ResourceQueryHelperResult<MessageResponse>();
|
||||||
using var context = _contextFactory.CreateContext(enableTracking: false);
|
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
|
||||||
if (serverCache == null)
|
if (serverCache == null)
|
||||||
{
|
{
|
||||||
@ -79,7 +81,8 @@ namespace StatsWeb
|
|||||||
When = _message.TimeSent,
|
When = _message.TimeSent,
|
||||||
Message = _message.Message,
|
Message = _message.Message,
|
||||||
ServerName = query.IsProfileMeta ? "" : _message.Server.HostName,
|
ServerName = query.IsProfileMeta ? "" : _message.Server.HostName,
|
||||||
GameName = _message.Server.GameName == null ? Server.Game.IW4 : _message.Server.GameName.Value
|
GameName = _message.Server.GameName == null ? Server.Game.IW4 : _message.Server.GameName.Value,
|
||||||
|
SentIngame = _message.SentIngame
|
||||||
});
|
});
|
||||||
|
|
||||||
if (query.Direction == SharedLibraryCore.Dtos.SortDirection.Descending)
|
if (query.Direction == SharedLibraryCore.Dtos.SortDirection.Descending)
|
||||||
|
@ -10,8 +10,11 @@ using SharedLibraryCore.Interfaces;
|
|||||||
using Stats.Dtos;
|
using Stats.Dtos;
|
||||||
using StatsWeb.Extensions;
|
using StatsWeb.Extensions;
|
||||||
using System;
|
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 ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
||||||
{
|
{
|
||||||
@ -21,14 +24,17 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
private readonly IManager _manager;
|
private readonly IManager _manager;
|
||||||
private readonly IResourceQueryHelper<ChatSearchQuery, MessageResponse> _chatResourceQueryHelper;
|
private readonly IResourceQueryHelper<ChatSearchQuery, MessageResponse> _chatResourceQueryHelper;
|
||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
public StatsController(ILogger logger, IManager manager, IResourceQueryHelper<ChatSearchQuery, MessageResponse> resourceQueryHelper,
|
public StatsController(ILogger<StatsController> logger, IManager manager, IResourceQueryHelper<ChatSearchQuery,
|
||||||
ITranslationLookup translationLookup) : base(manager)
|
MessageResponse> resourceQueryHelper, ITranslationLookup translationLookup,
|
||||||
|
IDatabaseContextFactory contextFactory) : base(manager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_chatResourceQueryHelper = resourceQueryHelper;
|
_chatResourceQueryHelper = resourceQueryHelper;
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
|
_contextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -86,7 +92,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
SentAfter = whenLower
|
SentAfter = whenLower
|
||||||
});
|
});
|
||||||
|
|
||||||
return View("_MessageContext", messages.Results);
|
return View("_MessageContext", messages.Results.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("Message/Find")]
|
[HttpGet("Message/Find")]
|
||||||
@ -108,15 +114,13 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
|
|
||||||
catch (ArgumentException e)
|
catch (ArgumentException e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Could not parse chat message search query - {query}");
|
_logger.LogWarning(e, "Could not parse chat message search query {query}", query);
|
||||||
_logger.WriteDebug(e.GetExceptionInfo());
|
|
||||||
ViewBag.Error = e;
|
ViewBag.Error = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (FormatException e)
|
catch (FormatException e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Could not parse chat message search query filter format - {query}");
|
_logger.LogWarning(e, "Could not parse chat message search query filter format {query}", query);
|
||||||
_logger.WriteDebug(e.GetExceptionInfo());
|
|
||||||
ViewBag.Error = e;
|
ViewBag.Error = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,15 +140,13 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
|
|
||||||
catch (ArgumentException e)
|
catch (ArgumentException e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Could not parse chat message search query - {query}");
|
_logger.LogWarning(e, "Could not parse chat message search query {query}", query);
|
||||||
_logger.WriteDebug(e.GetExceptionInfo());
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (FormatException e)
|
catch (FormatException e)
|
||||||
{
|
{
|
||||||
_logger.WriteWarning($"Could not parse chat message search query filter format - {query}");
|
_logger.LogWarning(e, "Could not parse chat message search query filter format {query}", query);
|
||||||
_logger.WriteDebug(e.GetExceptionInfo());
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,9 +158,9 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IActionResult> GetAutomatedPenaltyInfoAsync(int penaltyId)
|
public async Task<IActionResult> GetAutomatedPenaltyInfoAsync(int penaltyId)
|
||||||
{
|
{
|
||||||
using (var ctx = new SharedLibraryCore.Database.DatabaseContext(true))
|
await using var context = _contextFactory.CreateContext(false);
|
||||||
{
|
|
||||||
var penalty = await ctx.Penalties
|
var penalty = await context.Penalties
|
||||||
.Select(_penalty => new { _penalty.OffenderId, _penalty.PenaltyId, _penalty.When, _penalty.AutomatedOffense })
|
.Select(_penalty => new { _penalty.OffenderId, _penalty.PenaltyId, _penalty.When, _penalty.AutomatedOffense })
|
||||||
.FirstOrDefaultAsync(_penalty => _penalty.PenaltyId == penaltyId);
|
.FirstOrDefaultAsync(_penalty => _penalty.PenaltyId == penaltyId);
|
||||||
|
|
||||||
@ -168,7 +170,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo: this can be optimized
|
// todo: this can be optimized
|
||||||
var iqSnapshotInfo = ctx.Set<Stats.Models.EFACSnapshot>()
|
var iqSnapshotInfo = context.Set<Stats.Models.EFACSnapshot>()
|
||||||
.Where(s => s.ClientId == penalty.OffenderId)
|
.Where(s => s.ClientId == penalty.OffenderId)
|
||||||
.Include(s => s.LastStrainAngle)
|
.Include(s => s.LastStrainAngle)
|
||||||
.Include(s => s.HitOrigin)
|
.Include(s => s.HitOrigin)
|
||||||
@ -189,17 +191,16 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
// we want to show anything related to the automated offense
|
// we want to show anything related to the automated offense
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return View("_MessageContext", new[]
|
return View("_MessageContext", new List<MessageResponse>
|
||||||
{
|
{
|
||||||
new ChatInfo()
|
new MessageResponse()
|
||||||
{
|
{
|
||||||
ClientId = penalty.OffenderId,
|
ClientId = penalty.OffenderId,
|
||||||
Message = penalty.AutomatedOffense,
|
Message = penalty.AutomatedOffense,
|
||||||
Time = penalty.When
|
When = penalty.When
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2020.12.20.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -6,21 +6,37 @@
|
|||||||
string rankIcon(double elo)
|
string rankIcon(double elo)
|
||||||
{
|
{
|
||||||
if (elo >= getDeviation(-0.75) && elo < getDeviation(1.25))
|
if (elo >= getDeviation(-0.75) && elo < getDeviation(1.25))
|
||||||
|
{
|
||||||
return "0_no-place/menu_div_no_place.png";
|
return "0_no-place/menu_div_no_place.png";
|
||||||
|
}
|
||||||
if (elo >= getDeviation(0.125) && elo < getDeviation(0.625))
|
if (elo >= getDeviation(0.125) && elo < getDeviation(0.625))
|
||||||
|
{
|
||||||
return "1_iron/menu_div_iron_sub03.png";
|
return "1_iron/menu_div_iron_sub03.png";
|
||||||
|
}
|
||||||
if (elo >= getDeviation(0.625) && elo < getDeviation(1.0))
|
if (elo >= getDeviation(0.625) && elo < getDeviation(1.0))
|
||||||
|
{
|
||||||
return "2_bronze/menu_div_bronze_sub03.png";
|
return "2_bronze/menu_div_bronze_sub03.png";
|
||||||
|
}
|
||||||
if (elo >= getDeviation(1.0) && elo < getDeviation(1.25))
|
if (elo >= getDeviation(1.0) && elo < getDeviation(1.25))
|
||||||
|
{
|
||||||
return "3_silver/menu_div_silver_sub03.png";
|
return "3_silver/menu_div_silver_sub03.png";
|
||||||
|
}
|
||||||
if (elo >= getDeviation(1.25) && elo < getDeviation(1.5))
|
if (elo >= getDeviation(1.25) && elo < getDeviation(1.5))
|
||||||
|
{
|
||||||
return "4_gold/menu_div_gold_sub03.png";
|
return "4_gold/menu_div_gold_sub03.png";
|
||||||
|
}
|
||||||
if (elo >= getDeviation(1.5) && elo < getDeviation(1.75))
|
if (elo >= getDeviation(1.5) && elo < getDeviation(1.75))
|
||||||
|
{
|
||||||
return "5_platinum/menu_div_platinum_sub03.png";
|
return "5_platinum/menu_div_platinum_sub03.png";
|
||||||
|
}
|
||||||
if (elo >= getDeviation(1.75) && elo < getDeviation(2.0))
|
if (elo >= getDeviation(1.75) && elo < getDeviation(2.0))
|
||||||
|
{
|
||||||
return "6_semipro/menu_div_semipro_sub03.png";
|
return "6_semipro/menu_div_semipro_sub03.png";
|
||||||
|
}
|
||||||
if (elo >= getDeviation(2.0))
|
if (elo >= getDeviation(2.0))
|
||||||
|
{
|
||||||
return "7_pro/menu_div_pro_sub03.png";
|
return "7_pro/menu_div_pro_sub03.png";
|
||||||
|
}
|
||||||
|
|
||||||
return "0_no-place/menu_div_no_place.png";
|
return "0_no-place/menu_div_no_place.png";
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
@using SharedLibraryCore.Dtos.Meta.Responses
|
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||||
@model IEnumerable<MessageResponse>
|
@model IList<MessageResponse>
|
||||||
@{
|
@{
|
||||||
Layout = null;
|
Layout = null;
|
||||||
}
|
}
|
||||||
|
@ -13,51 +13,12 @@ using System.Net;
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using static SharedLibraryCore.Database.Models.EFClient;
|
using static SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
using Humanizer;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Welcome
|
namespace IW4MAdmin.Plugins.Welcome
|
||||||
{
|
{
|
||||||
public class Plugin : IPlugin
|
public class Plugin : IPlugin
|
||||||
{
|
{
|
||||||
String TimesConnected(EFClient P)
|
|
||||||
{
|
|
||||||
int connection = P.Connections;
|
|
||||||
String Prefix = String.Empty;
|
|
||||||
if (connection % 10 > 3 || connection % 10 == 0 || (connection % 100 > 9 && connection % 100 < 19))
|
|
||||||
Prefix = "th";
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (connection % 10)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
Prefix = "st";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
Prefix = "nd";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
Prefix = "rd";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (connection)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
case 1:
|
|
||||||
return "first";
|
|
||||||
case 2:
|
|
||||||
return "second";
|
|
||||||
case 3:
|
|
||||||
return "third";
|
|
||||||
case 4:
|
|
||||||
return "fourth";
|
|
||||||
case 5:
|
|
||||||
return "fifth";
|
|
||||||
default:
|
|
||||||
return connection.ToString() + Prefix;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Author => "RaidMax";
|
public string Author => "RaidMax";
|
||||||
|
|
||||||
public float Version => 1.0f;
|
public float Version => 1.0f;
|
||||||
@ -65,10 +26,12 @@ namespace IW4MAdmin.Plugins.Welcome
|
|||||||
public string Name => "Welcome Plugin";
|
public string Name => "Welcome Plugin";
|
||||||
|
|
||||||
private readonly IConfigurationHandler<WelcomeConfiguration> _configHandler;
|
private readonly IConfigurationHandler<WelcomeConfiguration> _configHandler;
|
||||||
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
|
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
_configHandler = configurationHandlerFactory.GetConfigurationHandler<WelcomeConfiguration>("WelcomePluginSettings");
|
_configHandler = configurationHandlerFactory.GetConfigurationHandler<WelcomeConfiguration>("WelcomePluginSettings");
|
||||||
|
_contextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnLoadAsync(IManager manager)
|
public async Task OnLoadAsync(IManager manager)
|
||||||
@ -89,7 +52,7 @@ namespace IW4MAdmin.Plugins.Welcome
|
|||||||
if (E.Type == GameEvent.EventType.Join)
|
if (E.Type == GameEvent.EventType.Join)
|
||||||
{
|
{
|
||||||
EFClient newPlayer = E.Origin;
|
EFClient newPlayer = E.Origin;
|
||||||
if (newPlayer.Level >= Permission.Trusted && !E.Origin.Masked)
|
if (newPlayer.Level >= Permission.Trusted && !E.Origin.Masked || !string.IsNullOrEmpty(newPlayer.GetAdditionalProperty<string>("ClientTag")))
|
||||||
E.Owner.Broadcast(await ProcessAnnouncement(_configHandler.Configuration().PrivilegedAnnouncementMessage, newPlayer));
|
E.Owner.Broadcast(await ProcessAnnouncement(_configHandler.Configuration().PrivilegedAnnouncementMessage, newPlayer));
|
||||||
|
|
||||||
newPlayer.Tell(await ProcessAnnouncement(_configHandler.Configuration().UserWelcomeMessage, newPlayer));
|
newPlayer.Tell(await ProcessAnnouncement(_configHandler.Configuration().UserWelcomeMessage, newPlayer));
|
||||||
@ -98,9 +61,9 @@ namespace IW4MAdmin.Plugins.Welcome
|
|||||||
{
|
{
|
||||||
string penaltyReason;
|
string penaltyReason;
|
||||||
|
|
||||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
await using var context = _contextFactory.CreateContext(false);
|
||||||
{
|
{
|
||||||
penaltyReason = await ctx.Penalties
|
penaltyReason = await context.Penalties
|
||||||
.Where(p => p.OffenderId == newPlayer.ClientId && p.Type == EFPenalty.PenaltyType.Flag)
|
.Where(p => p.OffenderId == newPlayer.ClientId && p.Type == EFPenalty.PenaltyType.Flag)
|
||||||
.OrderByDescending(p => p.When)
|
.OrderByDescending(p => p.When)
|
||||||
.Select(p => p.AutomatedOffense ?? p.Offense)
|
.Select(p => p.AutomatedOffense ?? p.Offense)
|
||||||
@ -117,13 +80,13 @@ namespace IW4MAdmin.Plugins.Welcome
|
|||||||
private async Task<string> ProcessAnnouncement(string msg, EFClient joining)
|
private async Task<string> ProcessAnnouncement(string msg, EFClient joining)
|
||||||
{
|
{
|
||||||
msg = msg.Replace("{{ClientName}}", joining.Name);
|
msg = msg.Replace("{{ClientName}}", joining.Name);
|
||||||
msg = msg.Replace("{{ClientLevel}}", Utilities.ConvertLevelToColor(joining.Level, joining.ClientPermission.Name));
|
msg = msg.Replace("{{ClientLevel}}", $"{Utilities.ConvertLevelToColor(joining.Level, joining.ClientPermission.Name)}{(string.IsNullOrEmpty(joining.GetAdditionalProperty<string>("ClientTag")) ? "" : $" ^7({joining.GetAdditionalProperty<string>("ClientTag")}^7)")}");
|
||||||
// this prevents it from trying to evaluate it every message
|
// this prevents it from trying to evaluate it every message
|
||||||
if (msg.Contains("{{ClientLocation}}"))
|
if (msg.Contains("{{ClientLocation}}"))
|
||||||
{
|
{
|
||||||
msg = msg.Replace("{{ClientLocation}}", await GetCountryName(joining.IPAddressString));
|
msg = msg.Replace("{{ClientLocation}}", await GetCountryName(joining.IPAddressString));
|
||||||
}
|
}
|
||||||
msg = msg.Replace("{{TimesConnected}}", TimesConnected(joining));
|
msg = msg.Replace("{{TimesConnected}}", joining.Connections.Ordinalize());
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,11 @@
|
|||||||
<Description>Welcome plugin for IW4MAdmin welcomes clients to the server</Description>
|
<Description>Welcome plugin for IW4MAdmin welcomes clients to the server</Description>
|
||||||
<Copyright>2018</Copyright>
|
<Copyright>2018</Copyright>
|
||||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||||
<LangVersion>7.1</LangVersion>
|
<LangVersion>Latest</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2020.12.20.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
[](https://ko-fi.com/J3J821KUJ)
|
[](https://ko-fi.com/J3J821KUJ)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
**IW4MAdmin** is an administration tool for [IW4x](https://iw4x.org/), [Pluto T6](https://forum.plutonium.pw/category/6/plutonium-t6), [Pluto IW5](https://forum.plutonium.pw/category/14/plutonium-iw5), [CoD4x](https://cod4x.me/), [TeknoMW3](https://github.com/Musta1337/TeknoMW3), and most Call of Duty® dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze.
|
**IW4MAdmin** is an administration tool for [IW4x](https://iw4x.org/), [IW6x](https://xlabs.dev), [Pluto T6](https://forum.plutonium.pw/category/6/plutonium-t6), [Pluto IW5](https://forum.plutonium.pw/category/14/plutonium-iw5), [CoD4x](https://cod4x.me/), [TeknoMW3](https://github.com/Musta1337/TeknoMW3), and most Call of Duty® dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze.
|
||||||
### Download
|
### Download
|
||||||
Latest binary builds are always available at:
|
Latest binary builds are always available at:
|
||||||
- [GitHub](https://github.com/RaidMax/IW4M-Admin/releases)
|
- [GitHub](https://github.com/RaidMax/IW4M-Admin/releases)
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
dotnet publish WebfrontCore/WebfrontCore.csproj -c Prerelease -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\WindowsPrerelease /p:PublishProfile=Prerelease
|
|
||||||
dotnet publish Application/Application.csproj -c Prerelease -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\WindowsPrerelease /p:PublishProfile=Prerelease
|
|
||||||
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\WindowsPrerelease\GameLogServer
|
|
||||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
|
|
||||||
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=PreRelease /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\GameLogServer\
|
|
||||||
cd "X:\IW4MAdmin\DEPLOY\"
|
|
||||||
PowerShell ".\upload_prerelease.ps1"
|
|
@ -1,7 +0,0 @@
|
|||||||
dotnet publish WebfrontCore/WebfrontCore.csproj -c Release -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\Windows /p:PublishProfile=sTABLE
|
|
||||||
dotnet publish Application/Application.csproj -c Release -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\Windows /p:PublishProfile=Stable
|
|
||||||
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -f netcoreapp3.0 --force -o X:\IW4MAdmin\Publish\WindowsPrerelease\GameLogServer
|
|
||||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
|
|
||||||
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\GameLogServer\
|
|
||||||
cd "X:\IW4MAdmin\DEPLOY\"
|
|
||||||
PowerShell ".\upload_release.ps1"
|
|
@ -134,6 +134,7 @@ namespace SharedLibraryCore
|
|||||||
ViewBag.Localization = Utilities.CurrentLocalization.LocalizationIndex;
|
ViewBag.Localization = Utilities.CurrentLocalization.LocalizationIndex;
|
||||||
ViewBag.CustomBranding = Manager.GetApplicationSettings().Configuration().WebfrontCustomBranding ?? "IW4MAdmin";
|
ViewBag.CustomBranding = Manager.GetApplicationSettings().Configuration().WebfrontCustomBranding ?? "IW4MAdmin";
|
||||||
ViewBag.EnableColorCodes = Manager.GetApplicationSettings().Configuration().EnableColorCodes;
|
ViewBag.EnableColorCodes = Manager.GetApplicationSettings().Configuration().EnableColorCodes;
|
||||||
|
ViewBag.EnablePrivilegedUserPrivacy = Manager.GetApplicationSettings().Configuration().EnablePrivilegedUserPrivacy;
|
||||||
|
|
||||||
base.OnActionExecuting(context);
|
base.OnActionExecuting(context);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ using SharedLibraryCore.Commands;
|
|||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace SharedLibraryCore
|
namespace SharedLibraryCore
|
||||||
{
|
{
|
||||||
@ -13,7 +15,7 @@ namespace SharedLibraryCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Command : IManagerCommand
|
public abstract class Command : IManagerCommand
|
||||||
{
|
{
|
||||||
private readonly CommandConfiguration _config;
|
protected readonly CommandConfiguration _config;
|
||||||
protected readonly ITranslationLookup _translationLookup;
|
protected readonly ITranslationLookup _translationLookup;
|
||||||
protected ILogger logger;
|
protected ILogger logger;
|
||||||
|
|
||||||
@ -26,9 +28,9 @@ namespace SharedLibraryCore
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the command
|
/// Executes the command
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="E"></param>
|
/// <param name="gameEvent"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
abstract public Task ExecuteAsync(GameEvent E);
|
abstract public Task ExecuteAsync(GameEvent gameEvent);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies the name and string that triggers the command
|
/// Specifies the name and string that triggers the command
|
||||||
@ -113,6 +115,25 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
private EFClient.Permission permission;
|
private EFClient.Permission permission;
|
||||||
|
|
||||||
|
public Game[] SupportedGames
|
||||||
|
{
|
||||||
|
get => supportedGames;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var savedGames = _config?.Commands[GetType().Name].SupportedGames;
|
||||||
|
supportedGames = savedGames?.Length != 0 ? savedGames : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
supportedGames = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Game[] supportedGames;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Argument list for the command
|
/// Argument list for the command
|
||||||
|
37
SharedLibraryCore/Commands/AddClientTagCommand.cs
Normal file
37
SharedLibraryCore/Commands/AddClientTagCommand.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Commands
|
||||||
|
{
|
||||||
|
public class AddClientTagCommand : Command
|
||||||
|
{
|
||||||
|
private readonly IMetaService _metaService;
|
||||||
|
|
||||||
|
public AddClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : base(config, layout)
|
||||||
|
{
|
||||||
|
Name = "addclienttag";
|
||||||
|
Description = layout["COMMANDS_ADD_CLIENT_TAG_DESC"];
|
||||||
|
Alias = "act";
|
||||||
|
Permission = EFClient.Permission.Owner;
|
||||||
|
RequiresTarget = false;
|
||||||
|
Arguments = new[]
|
||||||
|
{
|
||||||
|
new CommandArgument()
|
||||||
|
{
|
||||||
|
Name = _translationLookup["COMMANDS_ARGUMENT_TAG"],
|
||||||
|
Required = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_metaService = metaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
|
{
|
||||||
|
await _metaService.AddPersistentMeta(EFMeta.ClientTagName, gameEvent.Data);
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_ADD_CLIENT_TAG_SUCCESS"].FormatExt(gameEvent.Data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
SharedLibraryCore/Commands/ListClientTags.cs
Normal file
33
SharedLibraryCore/Commands/ListClientTags.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Commands
|
||||||
|
{
|
||||||
|
|
||||||
|
public class ListClientTags : Command
|
||||||
|
{
|
||||||
|
private readonly IMetaService _metaService;
|
||||||
|
|
||||||
|
public ListClientTags(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : base(config, layout)
|
||||||
|
{
|
||||||
|
Name = "listclienttags";
|
||||||
|
Description = layout["COMMANDS_LIST_CLIENT_TAGS_DESC"];
|
||||||
|
Alias = "lct";
|
||||||
|
Permission = EFClient.Permission.Owner;
|
||||||
|
RequiresTarget = false;
|
||||||
|
|
||||||
|
_metaService = metaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
|
{
|
||||||
|
var tags = await _metaService.GetPersistentMeta(EFMeta.ClientTagName);
|
||||||
|
gameEvent.Origin.Tell(tags.Select(tag => tag.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,8 @@ using System.Security.Cryptography;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog.Context;
|
||||||
using static SharedLibraryCore.Database.Models.EFClient;
|
using static SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Commands
|
namespace SharedLibraryCore.Commands
|
||||||
@ -99,7 +101,8 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class WarnCommand : Command
|
public class WarnCommand : Command
|
||||||
{
|
{
|
||||||
public WarnCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
public WarnCommand(ApplicationConfiguration appConfig, CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "warn";
|
Name = "warn";
|
||||||
Description = _translationLookup["COMMANDS_WARN_DESC"];
|
Description = _translationLookup["COMMANDS_WARN_DESC"];
|
||||||
@ -119,13 +122,15 @@ namespace SharedLibraryCore.Commands
|
|||||||
Required = true
|
Required = true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
_appConfig = appConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task ExecuteAsync(GameEvent E)
|
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
if (E.Target.Warn(E.Data, E.Origin).Failed)
|
var reason = gameEvent.Data.FindRuleForReason(_appConfig, gameEvent.Owner);
|
||||||
|
if (gameEvent.Target.Warn(reason, gameEvent.Origin).Failed)
|
||||||
{
|
{
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_WARN_FAIL"].FormatExt(E.Target.Name));
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_WARN_FAIL"].FormatExt(gameEvent.Target.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@ -170,7 +175,9 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class KickCommand : Command
|
public class KickCommand : Command
|
||||||
{
|
{
|
||||||
public KickCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
|
||||||
|
public KickCommand(ApplicationConfiguration appConfig, CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "kick";
|
Name = "kick";
|
||||||
Description = _translationLookup["COMMANDS_KICK_DESC"];
|
Description = _translationLookup["COMMANDS_KICK_DESC"];
|
||||||
@ -190,20 +197,22 @@ namespace SharedLibraryCore.Commands
|
|||||||
Required = true
|
Required = true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
_appConfig = appConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
switch ((await E.Target.Kick(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason)
|
var reason = gameEvent.Data.FindRuleForReason(_appConfig, gameEvent.Owner);
|
||||||
|
switch ((await gameEvent.Target.Kick(reason, gameEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, gameEvent.Owner.Manager.CancellationToken)).FailReason)
|
||||||
{
|
{
|
||||||
case GameEvent.EventFailReason.None:
|
case GameEvent.EventFailReason.None:
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_KICK_SUCCESS"].FormatExt(E.Target.Name));
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_KICK_SUCCESS"].FormatExt(gameEvent.Target.Name));
|
||||||
break;
|
break;
|
||||||
case GameEvent.EventFailReason.Exception:
|
case GameEvent.EventFailReason.Exception:
|
||||||
E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
gameEvent.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_KICK_FAIL"].FormatExt(E.Target.Name));
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_KICK_FAIL"].FormatExt(gameEvent.Target.Name));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,12 +248,50 @@ namespace SharedLibraryCore.Commands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prints out a message to all clients on all servers
|
||||||
|
/// </summary>
|
||||||
|
public class SayAllCommand : Command
|
||||||
|
{
|
||||||
|
public SayAllCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
|
{
|
||||||
|
Name = "sayall";
|
||||||
|
Description = _translationLookup["COMMANDS_SAY_ALL_DESC"];
|
||||||
|
Alias = "sa";
|
||||||
|
Permission = Permission.Moderator;
|
||||||
|
RequiresTarget = false;
|
||||||
|
Arguments = new[]
|
||||||
|
{
|
||||||
|
new CommandArgument()
|
||||||
|
{
|
||||||
|
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
|
||||||
|
Required = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
|
{
|
||||||
|
string message = _translationLookup["COMMANDS_SAY_ALL_MESSAGE_FORMAT"].FormatExt(E.Origin.Name, E.Data);
|
||||||
|
|
||||||
|
foreach (var server in E.Owner.Manager.GetServers())
|
||||||
|
{
|
||||||
|
server.Broadcast(message, E.Origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
E.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Temporarily bans a client
|
/// Temporarily bans a client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TempBanCommand : Command
|
public class TempBanCommand : Command
|
||||||
{
|
{
|
||||||
public TempBanCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
|
||||||
|
public TempBanCommand(ApplicationConfiguration appConfig, CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "tempban";
|
Name = "tempban";
|
||||||
Description = _translationLookup["COMMANDS_TEMPBAN_DESC"];
|
Description = _translationLookup["COMMANDS_TEMPBAN_DESC"];
|
||||||
@ -269,35 +316,36 @@ namespace SharedLibraryCore.Commands
|
|||||||
Required = true
|
Required = true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
_appConfig = appConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly string TempBanRegex = @"([0-9]+\w+)\ (.+)";
|
private static readonly string TempBanRegex = @"([0-9]+\w+)\ (.+)";
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
var match = Regex.Match(E.Data, TempBanRegex);
|
var match = Regex.Match(gameEvent.Data, TempBanRegex);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
string tempbanReason = match.Groups[2].ToString();
|
var tempbanReason = match.Groups[2].ToString().FindRuleForReason(_appConfig, gameEvent.Owner);
|
||||||
var length = match.Groups[1].ToString().ParseTimespan();
|
var length = match.Groups[1].ToString().ParseTimespan();
|
||||||
|
|
||||||
if (length > E.Owner.Manager.GetApplicationSettings().Configuration().MaximumTempBanTime)
|
if (length > gameEvent.Owner.Manager.GetApplicationSettings().Configuration().MaximumTempBanTime)
|
||||||
{
|
{
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_FAIL_TOOLONG"]);
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_FAIL_TOOLONG"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch ((await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason)
|
switch ((await gameEvent.Target.TempBan(tempbanReason, length, gameEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, gameEvent.Owner.Manager.CancellationToken)).FailReason)
|
||||||
{
|
{
|
||||||
case GameEvent.EventFailReason.None:
|
case GameEvent.EventFailReason.None:
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(E.Target, length.HumanizeForCurrentCulture()));
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(gameEvent.Target, length.HumanizeForCurrentCulture()));
|
||||||
break;
|
break;
|
||||||
case GameEvent.EventFailReason.Exception:
|
case GameEvent.EventFailReason.Exception:
|
||||||
E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
gameEvent.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_FAIL"].FormatExt(E.Target.Name));
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_FAIL"].FormatExt(gameEvent.Target.Name));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,7 +358,8 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class BanCommand : Command
|
public class BanCommand : Command
|
||||||
{
|
{
|
||||||
public BanCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
public BanCommand(ApplicationConfiguration appConfig, CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "ban";
|
Name = "ban";
|
||||||
Description = _translationLookup["COMMANDS_BAN_DESC"];
|
Description = _translationLookup["COMMANDS_BAN_DESC"];
|
||||||
@ -330,20 +379,22 @@ namespace SharedLibraryCore.Commands
|
|||||||
Required = true
|
Required = true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
_appConfig = appConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
switch ((await E.Target.Ban(E.Data, E.Origin, false).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason)
|
var reason = gameEvent.Data.FindRuleForReason(_appConfig, gameEvent.Owner);
|
||||||
|
switch ((await gameEvent.Target.Ban(reason, gameEvent.Origin, false).WaitAsync(Utilities.DefaultCommandTimeout, gameEvent.Owner.Manager.CancellationToken)).FailReason)
|
||||||
{
|
{
|
||||||
case GameEvent.EventFailReason.None:
|
case GameEvent.EventFailReason.None:
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_BAN_SUCCESS"].FormatExt(E.Target.Name));
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_BAN_SUCCESS"].FormatExt(gameEvent.Target.Name));
|
||||||
break;
|
break;
|
||||||
case GameEvent.EventFailReason.Exception:
|
case GameEvent.EventFailReason.Exception:
|
||||||
E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
gameEvent.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_BAN_FAIL"].FormatExt(E.Target.Name));
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_BAN_FAIL"].FormatExt(gameEvent.Target.Name));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -414,10 +465,11 @@ namespace SharedLibraryCore.Commands
|
|||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task ExecuteAsync(GameEvent E)
|
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
string you = string.Format("{0} [^3#{1}^7] {2} ^7[^3@{3}^7] ^7[{4}^7] IP: {5}", E.Origin.Name, E.Origin.ClientNumber, E.Origin.NetworkId, E.Origin.ClientId, Utilities.ConvertLevelToColor(E.Origin.Level, E.Origin.ClientPermission.Name), E.Origin.IPAddressString);
|
var you = _translationLookup["COMMANDS_WHOAMI_FORMAT"].FormatExt(gameEvent.Origin.ClientNumber, gameEvent.Origin.ClientId, gameEvent.Origin.GuidString,
|
||||||
E.Origin.Tell(you);
|
gameEvent.Origin.IPAddressString, gameEvent.Origin.ClientPermission.Name, string.IsNullOrEmpty(gameEvent.Origin.Tag) ? "" : $" {gameEvent.Origin.Tag}^7", gameEvent.Origin.Name);
|
||||||
|
gameEvent.Origin.Tell(you);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@ -437,46 +489,14 @@ namespace SharedLibraryCore.Commands
|
|||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task ExecuteAsync(GameEvent E)
|
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
StringBuilder playerList = new StringBuilder();
|
var clientList = gameEvent.Owner.GetClientsAsList()
|
||||||
int count = 0;
|
.Select(client => _translationLookup["COMMANDS_LIST_FORMAT"]
|
||||||
for (int i = 0; i < E.Owner.Clients.Count; i++)
|
.FormatExt(client.ClientPermission.Name, string.IsNullOrEmpty(client.Tag) ? "" : $" {client.Tag}^7", client.ClientNumber, client.Name))
|
||||||
{
|
.ToArray();
|
||||||
var P = E.Owner.Clients[i];
|
|
||||||
|
|
||||||
if (P == null)
|
gameEvent.Origin.Tell(clientList);
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// todo: fix spacing
|
|
||||||
// todo: make this better :)
|
|
||||||
if (P.Masked)
|
|
||||||
{
|
|
||||||
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(EFClient.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(EFClient.Permission.SeniorAdmin.ToString().Length - EFClient.Permission.User.ToString().Length));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(P.Level, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(EFClient.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == 2 || E.Owner.GetClientsAsList().Count == 1)
|
|
||||||
{
|
|
||||||
E.Origin.Tell(playerList.ToString());
|
|
||||||
count = 0;
|
|
||||||
playerList = new StringBuilder();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playerList.Length > 0)
|
|
||||||
{
|
|
||||||
E.Origin.Tell(playerList.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: make no players response for webfront
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@ -596,11 +616,11 @@ namespace SharedLibraryCore.Commands
|
|||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
var _ = !E.Origin.Masked ?
|
_ = !E.Origin.Masked ?
|
||||||
E.Owner.Broadcast($"{_translationLookup["COMMANDS_MAPROTATE"]} [^5{E.Origin.Name}^7]", E.Origin) :
|
E.Owner.Broadcast($"{_translationLookup["COMMANDS_MAPROTATE"]} [^5{E.Origin.Name}^7]", E.Origin) :
|
||||||
E.Owner.Broadcast(_translationLookup["COMMANDS_MAPROTATE"], E.Origin);
|
E.Owner.Broadcast(_translationLookup["COMMANDS_MAPROTATE"], E.Origin);
|
||||||
|
|
||||||
await Task.Delay(5000);
|
await Task.Delay(E.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds * 1000);
|
||||||
await E.Owner.ExecuteCommandAsync("map_rotate");
|
await E.Owner.ExecuteCommandAsync("map_rotate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -610,7 +630,7 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SetLevelCommand : Command
|
public class SetLevelCommand : Command
|
||||||
{
|
{
|
||||||
public SetLevelCommand(CommandConfiguration config, ITranslationLookup translationLookup, ILogger logger) : base(config, translationLookup)
|
public SetLevelCommand(CommandConfiguration config, ITranslationLookup translationLookup, ILogger<SetLevelCommand> logger) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "setlevel";
|
Name = "setlevel";
|
||||||
Description = _translationLookup["COMMANDS_SETLEVEL_DESC"];
|
Description = _translationLookup["COMMANDS_SETLEVEL_DESC"];
|
||||||
@ -675,12 +695,18 @@ namespace SharedLibraryCore.Commands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (gameEvent.Target.Level == Permission.Flagged)
|
||||||
|
{
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_FLAGGED"].FormatExt(gameEvent.Target.Name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// stepped privilege is enabled, but the new level is too high
|
// stepped privilege is enabled, but the new level is too high
|
||||||
else if (steppedPrivileges && !canPromoteSteppedPriv)
|
else if (steppedPrivileges && !canPromoteSteppedPriv)
|
||||||
{
|
{
|
||||||
// can't promote a client to higher than your current perms
|
// can't promote a client to higher than your current perms
|
||||||
// or your peer
|
// or your peer
|
||||||
gameEvent.Origin.Tell(string.Format(_translationLookup["COMMANDS_SETLEVEL_LEVELTOOHIGH"], gameEvent.Target.Name, (gameEvent.Origin.Level - 1).ToString()));
|
gameEvent.Origin.Tell(string.Format(_translationLookup["COMMANDS_SETLEVEL_LEVELTOOHIGH"], gameEvent.Target.Name, (gameEvent.Origin.Level - 1).ToLocalizedLevelName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,13 +717,26 @@ namespace SharedLibraryCore.Commands
|
|||||||
gameEvent.Owner.Manager.GetActiveClients()
|
gameEvent.Owner.Manager.GetActiveClients()
|
||||||
.FirstOrDefault(c => c.ClientId == targetClient?.ClientId) ?? targetClient : targetClient;
|
.FirstOrDefault(c => c.ClientId == targetClient?.ClientId) ?? targetClient : targetClient;
|
||||||
|
|
||||||
logger.WriteInfo($"Beginning set level of client {gameEvent.Origin} to {newPerm}");
|
logger.LogDebug("Beginning set level of client {origin} to {newPermission}", gameEvent.Origin.ToString(), newPerm);
|
||||||
|
|
||||||
var result = await targetClient.SetLevel(newPerm, gameEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, gameEvent.Owner.Manager.CancellationToken);
|
var result = await targetClient.SetLevel(newPerm, gameEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, gameEvent.Owner.Manager.CancellationToken);
|
||||||
|
|
||||||
if (result.Failed)
|
if (result.Failed)
|
||||||
{
|
{
|
||||||
logger.WriteInfo($"Failed to set level of client {gameEvent.Origin}");
|
// user is the same level
|
||||||
|
if (result.FailReason == GameEvent.EventFailReason.Invalid)
|
||||||
|
{
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_INVALID"]
|
||||||
|
.FormatExt(gameEvent.Target.Name, newPerm.ToString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (LogContext.PushProperty("Server", gameEvent.Origin.CurrentServer?.ToString()))
|
||||||
|
{
|
||||||
|
logger.LogWarning("Failed to set level of client {origin} {reason}",
|
||||||
|
gameEvent.Origin.ToString(),
|
||||||
|
result.FailReason);
|
||||||
|
}
|
||||||
gameEvent.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
gameEvent.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -760,9 +799,9 @@ namespace SharedLibraryCore.Commands
|
|||||||
|
|
||||||
public override Task ExecuteAsync(GameEvent E)
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
TimeSpan uptime = DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime;
|
var uptime = DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime;
|
||||||
var loc = _translationLookup;
|
var loc = _translationLookup;
|
||||||
E.Origin.Tell(loc["COMMANDS_UPTIME_TEXT"].FormatExt(uptime.Days, uptime.Hours, uptime.Minutes));
|
E.Origin.Tell(loc["COMMANDS_UPTIME_TEXT"].FormatExt(uptime.HumanizeForCurrentCulture(4)));
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -772,8 +811,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ListAdminsCommand : Command
|
public class ListAdminsCommand : Command
|
||||||
{
|
{
|
||||||
private readonly CommandConfiguration _config;
|
|
||||||
|
|
||||||
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "admins";
|
Name = "admins";
|
||||||
@ -781,8 +818,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
Alias = "a";
|
Alias = "a";
|
||||||
Permission = Permission.User;
|
Permission = Permission.User;
|
||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
|
|
||||||
_config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string OnlineAdmins(Server S, ITranslationLookup lookup)
|
public static string OnlineAdmins(Server S, ITranslationLookup lookup)
|
||||||
@ -832,22 +867,18 @@ namespace SharedLibraryCore.Commands
|
|||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
string newMap = E.Data.Trim().ToLower();
|
string newMap = E.Data.Trim();
|
||||||
foreach (Map m in E.Owner.Maps)
|
int delay = E.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds * 1000;
|
||||||
{
|
|
||||||
if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap)
|
|
||||||
{
|
|
||||||
E.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(m.Alias));
|
|
||||||
await Task.Delay((int)(Utilities.DefaultCommandTimeout.TotalMilliseconds / 2.0));
|
|
||||||
await E.Owner.LoadMap(m.Name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: this can be moved into a single statement
|
var foundMap = E.Owner.Maps.FirstOrDefault(_map => _map.Name.Equals(newMap, StringComparison.InvariantCultureIgnoreCase) ||
|
||||||
E.Owner.Broadcast(_translationLookup["COMMANDS_MAP_UKN"].FormatExt(newMap));
|
_map.Alias.Equals(newMap, StringComparison.InvariantCultureIgnoreCase));
|
||||||
await Task.Delay(5000);
|
|
||||||
await E.Owner.LoadMap(newMap);
|
_ = foundMap == null ?
|
||||||
|
E.Owner.Broadcast(_translationLookup["COMMANDS_MAP_UKN"].FormatExt(newMap)) :
|
||||||
|
E.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(foundMap.Alias));
|
||||||
|
|
||||||
|
await Task.Delay(delay);
|
||||||
|
await E.Owner.LoadMap(foundMap?.Name ?? newMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -901,8 +932,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ListRulesCommands : Command
|
public class ListRulesCommands : Command
|
||||||
{
|
{
|
||||||
private readonly CommandConfiguration _config;
|
|
||||||
|
|
||||||
public ListRulesCommands(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
public ListRulesCommands(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "rules";
|
Name = "rules";
|
||||||
@ -910,8 +939,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
Alias = "r";
|
Alias = "r";
|
||||||
Permission = Permission.User;
|
Permission = Permission.User;
|
||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
|
|
||||||
_config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task ExecuteAsync(GameEvent E)
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
@ -933,9 +960,14 @@ namespace SharedLibraryCore.Commands
|
|||||||
rules.AddRange(E.Owner.ServerConfig.Rules);
|
rules.AddRange(E.Owner.ServerConfig.Rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string r in rules)
|
var ruleFomat = rules.Select(r => $"- {r}");
|
||||||
|
if (E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
||||||
{
|
{
|
||||||
var _ = E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix) ? E.Owner.Broadcast($"- {r}") : E.Origin.Tell($"- {r}");
|
E.Owner.Broadcast(ruleFomat);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
E.Origin.Tell(ruleFomat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1392,7 +1424,10 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PruneAdminsCommand : Command
|
public class PruneAdminsCommand : Command
|
||||||
{
|
{
|
||||||
public PruneAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
|
public PruneAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||||
|
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "prune";
|
Name = "prune";
|
||||||
Description = _translationLookup["COMMANDS_PRUNE_DESC"];
|
Description = _translationLookup["COMMANDS_PRUNE_DESC"];
|
||||||
@ -1434,8 +1469,7 @@ namespace SharedLibraryCore.Commands
|
|||||||
List<EFClient> inactiveUsers = null;
|
List<EFClient> inactiveUsers = null;
|
||||||
// todo: make an event for this
|
// todo: make an event for this
|
||||||
// update user roles
|
// update user roles
|
||||||
using (var context = new DatabaseContext())
|
await using var context = _contextFactory.CreateContext();
|
||||||
{
|
|
||||||
var lastActive = DateTime.UtcNow.AddDays(-inactiveDays);
|
var lastActive = DateTime.UtcNow.AddDays(-inactiveDays);
|
||||||
inactiveUsers = await context.Clients
|
inactiveUsers = await context.Clients
|
||||||
.Where(c => c.Level > Permission.Flagged && c.Level <= Permission.Moderator)
|
.Where(c => c.Level > Permission.Flagged && c.Level <= Permission.Moderator)
|
||||||
@ -1443,7 +1477,7 @@ namespace SharedLibraryCore.Commands
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
inactiveUsers.ForEach(c => c.SetLevel(Permission.User, E.Origin));
|
inactiveUsers.ForEach(c => c.SetLevel(Permission.User, E.Origin));
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_PRUNE_SUCCESS"].FormatExt(inactiveUsers.Count));
|
E.Origin.Tell(_translationLookup["COMMANDS_PRUNE_SUCCESS"].FormatExt(inactiveUsers.Count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
37
SharedLibraryCore/Commands/PrivateMessageAdminsCommand.cs
Normal file
37
SharedLibraryCore/Commands/PrivateMessageAdminsCommand.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Commands
|
||||||
|
{
|
||||||
|
public class PrivateMessageAdminsCommand : Command
|
||||||
|
{
|
||||||
|
public PrivateMessageAdminsCommand(CommandConfiguration config, ITranslationLookup lookup) : base(config, lookup)
|
||||||
|
{
|
||||||
|
Name = "privatemessageadmin";
|
||||||
|
Description = lookup["COMMANDS_PMADMINS_DESC"];
|
||||||
|
Alias = "pma";
|
||||||
|
Permission = EFClient.Permission.Moderator;
|
||||||
|
SupportedGames = new[] { Game.IW4, Game.IW5 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
|
{
|
||||||
|
bool isGameSupported = _config.Commands[nameof(PrivateMessageAdminsCommand)].SupportedGames.Length > 0 &&
|
||||||
|
_config.Commands[nameof(PrivateMessageAdminsCommand)].SupportedGames.Contains(E.Owner.GameName);
|
||||||
|
|
||||||
|
if (!isGameSupported)
|
||||||
|
{
|
||||||
|
E.Origin.Tell(_translationLookup["COMMANDS_GAME_NOT_SUPPORTED"].FormatExt(nameof(PrivateMessageAdminsCommand)));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
E.Owner.ToAdmins(E.Data);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
SharedLibraryCore/Commands/RemoveClientTagCommand.cs
Normal file
37
SharedLibraryCore/Commands/RemoveClientTagCommand.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Commands
|
||||||
|
{
|
||||||
|
public class RemoveClientTag : Command
|
||||||
|
{
|
||||||
|
private readonly IMetaService _metaService;
|
||||||
|
|
||||||
|
public RemoveClientTag(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : base(config, layout)
|
||||||
|
{
|
||||||
|
Name = "removeclienttag";
|
||||||
|
Description = layout["COMMANDS_REMOVE_CLIENT_TAG_DESC"];
|
||||||
|
Alias = "rct";
|
||||||
|
Permission = EFClient.Permission.Owner;
|
||||||
|
RequiresTarget = false;
|
||||||
|
Arguments = new[]
|
||||||
|
{
|
||||||
|
new CommandArgument()
|
||||||
|
{
|
||||||
|
Name = _translationLookup["COMMANDS_ARGUMENT_TAG"],
|
||||||
|
Required = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_metaService = metaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
|
{
|
||||||
|
await _metaService.RemovePersistentMeta(EFMeta.ClientTagName, gameEvent.Data);
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_REMOVE_CLIENT_TAG_SUCCESS"].FormatExt(gameEvent.Data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,40 +25,39 @@ namespace SharedLibraryCore.Commands
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
if (E.IsTargetingSelf())
|
if (gameEvent.IsTargetingSelf())
|
||||||
{
|
{
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_RUN_AS_SELF"]);
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_RUN_AS_SELF"]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!E.CanPerformActionOnTarget())
|
if (!gameEvent.CanPerformActionOnTarget())
|
||||||
{
|
{
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_RUN_AS_FAIL_PERM"]);
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_RUN_AS_FAIL_PERM"]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string cmd = $"{Utilities.CommandPrefix}{E.Data}";
|
var cmd = $"{Utilities.CommandPrefix}{gameEvent.Data}";
|
||||||
var impersonatedCommandEvent = new GameEvent()
|
var impersonatedCommandEvent = new GameEvent()
|
||||||
{
|
{
|
||||||
Type = GameEvent.EventType.Command,
|
Type = GameEvent.EventType.Command,
|
||||||
Origin = E.Target,
|
Origin = gameEvent.Target,
|
||||||
ImpersonationOrigin = E.Origin,
|
ImpersonationOrigin = gameEvent.Origin,
|
||||||
Message = cmd,
|
Message = cmd,
|
||||||
Data = cmd,
|
Data = cmd,
|
||||||
Owner = E.Owner
|
Owner = gameEvent.Owner,
|
||||||
|
CorrelationId = gameEvent.CorrelationId
|
||||||
};
|
};
|
||||||
E.Owner.Manager.AddEvent(impersonatedCommandEvent);
|
gameEvent.Owner.Manager.AddEvent(impersonatedCommandEvent);
|
||||||
|
|
||||||
var result = await impersonatedCommandEvent.WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken);
|
var result = await impersonatedCommandEvent.WaitAsync(Utilities.DefaultCommandTimeout, gameEvent.Owner.Manager.CancellationToken);
|
||||||
var response = E.Owner.CommandResult.Where(c => c.ClientId == E.Target.ClientId).ToList();
|
|
||||||
|
|
||||||
// remove the added command response
|
// remove the added command response
|
||||||
for (int i = 0; i < response.Count; i++)
|
foreach (var output in result.Output)
|
||||||
{
|
{
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_RUN_AS_SUCCESS"].FormatExt(response[i].Response));
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_RUN_AS_SUCCESS"].FormatExt(output));
|
||||||
E.Owner.CommandResult.Remove(response[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
49
SharedLibraryCore/Commands/SetClientTagCommand.cs
Normal file
49
SharedLibraryCore/Commands/SetClientTagCommand.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Commands
|
||||||
|
{
|
||||||
|
public class SetClientTagCommand : Command
|
||||||
|
{
|
||||||
|
private readonly IMetaService _metaService;
|
||||||
|
|
||||||
|
|
||||||
|
public SetClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : base(config, layout)
|
||||||
|
{
|
||||||
|
Name = "setclienttag";
|
||||||
|
Description = layout["COMMANDS_SET_CLIENT_TAG_DESC"];
|
||||||
|
Alias = "sct";
|
||||||
|
Permission = EFClient.Permission.Owner;
|
||||||
|
RequiresTarget = true;
|
||||||
|
Arguments = new[]
|
||||||
|
{
|
||||||
|
new CommandArgument()
|
||||||
|
{
|
||||||
|
Name = _translationLookup["COMMANDS_ARGUMENT_TAG"],
|
||||||
|
Required = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_metaService = metaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
|
{
|
||||||
|
var availableTags = await _metaService.GetPersistentMeta(EFMeta.ClientTagName);
|
||||||
|
var matchingTag = availableTags.FirstOrDefault(tag => tag.Value == gameEvent.Data);
|
||||||
|
|
||||||
|
if (matchingTag == null)
|
||||||
|
{
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SET_CLIENT_TAG_FAIL"].FormatExt(gameEvent.Data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameEvent.Target.Tag = matchingTag.Value;
|
||||||
|
await _metaService.AddPersistentMeta(EFMeta.ClientTag, string.Empty, gameEvent.Target, matchingTag);
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SET_CLIENT_TAG_SUCCESS"].FormatExt(matchingTag.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user