Compare commits
188 Commits
2021.06.03
...
2022.02.23
Author | SHA1 | Date | |
---|---|---|---|
4ace476242 | |||
bb7215dbb6 | |||
88bd47f3ae | |||
39a1066c74 | |||
18f3c59b9b | |||
0d88b6293f | |||
a6b56ceded | |||
78ef977268 | |||
d527a86911 | |||
2e531c4a50 | |||
45059fcfd9 | |||
482cd9c339 | |||
51667159a2 | |||
ea18a286b2 | |||
9a6d7c6a20 | |||
adcb75319c | |||
037fac5786 | |||
f4b892d8f4 | |||
3640d1df54 | |||
f3c6b10a35 | |||
4dec284b31 | |||
c9cf7be341 | |||
aa6ae0ab8d | |||
12dfd8c558 | |||
07f675eadc | |||
576d7015fa | |||
b1a1aae6c0 | |||
a0f4ceccfe | |||
b7a76cc4a2 | |||
261da918c7 | |||
2ed5e00bcb | |||
6ca94f8da8 | |||
3b532cf1f7 | |||
40966ed74d | |||
45eacabc28 | |||
0b02b7627a | |||
fc3a24ca17 | |||
209cb6cdd0 | |||
cfd4296f5c | |||
b275fbaced | |||
b2a3625288 | |||
0d3e2cb0bc | |||
505a2c4c2d | |||
8730a3fab8 | |||
3539101a40 | |||
7ccdee7d1b | |||
f4b160b735 | |||
73036dc1c7 | |||
6cfcce23cc | |||
8649b0efe9 | |||
f554536b95 | |||
11efc039b5 | |||
916ea4163b | |||
0bed1c728a | |||
7171b3753e | |||
a602e8caed | |||
e4cb3abb20 | |||
686b297d32 | |||
fb11bf54a6 | |||
11d2b0da90 | |||
8bd0337168 | |||
74b565ebae | |||
2b467d6ef9 | |||
e90355307d | |||
d3962989b5 | |||
16831aaccb | |||
032753236b | |||
7fcb2202bd | |||
7910fc73a3 | |||
a8d581eab7 | |||
bd27977b1e | |||
092ca5f9bd | |||
3f0b1b892a | |||
c713fdacb0 | |||
f5854f8d03 | |||
67be4f8e7f | |||
9baad44ab4 | |||
76f5933074 | |||
4cce336fb9 | |||
5d12ff471b | |||
5d7ac7498f | |||
15cb114c15 | |||
e739c91b52 | |||
17c9944eef | |||
4a89744ee9 | |||
66010a2fa2 | |||
ce3119425f | |||
307ff3ddeb | |||
7f2fa390c7 | |||
a88b30562c | |||
08bcd23cbc | |||
072571d341 | |||
35e42516f1 | |||
2210ccea68 | |||
08b93fcc10 | |||
ab05b45016 | |||
825dd6f382 | |||
f99fdac4b0 | |||
f7897763e3 | |||
5b95cdaca8 | |||
c4e0c4c36a | |||
31d0dfc7d3 | |||
8f52714fb7 | |||
e4153e0c2f | |||
8d0c48614f | |||
761d156209 | |||
77f04058de | |||
1317102d00 | |||
a2c7d92162 | |||
b2afc410f2 | |||
5b3420b97a | |||
74bb3da459 | |||
3916278422 | |||
a01543c89b | |||
694431d789 | |||
d5f978858d | |||
e80753a4d3 | |||
d4fb75d07c | |||
e97119211f | |||
87985b3e68 | |||
33c63f01db | |||
68c1151191 | |||
54e39fabb1 | |||
a4f0726b32 | |||
05e228633d | |||
e267bd95da | |||
c7fab5d36c | |||
1f8b7cde3f | |||
c5f9a68102 | |||
eff8a29a39 | |||
0191c8b7a7 | |||
fa6524c3b1 | |||
5b11196b29 | |||
3b7a22edef | |||
deff4f2947 | |||
02e5e78f67 | |||
162006da29 | |||
27e9ecfd9d | |||
da301bef40 | |||
a815bcbff5 | |||
19a49504b8 | |||
3bb87dffb0 | |||
02942e5c03 | |||
8c5ff440db | |||
a0b7781e66 | |||
596272a3de | |||
b83ea57579 | |||
75f68b6385 | |||
d5e4d083c5 | |||
602ec66afe | |||
435b079b94 | |||
a4eec5981f | |||
0b6e261dbb | |||
7e1221f467 | |||
a6b0911af9 | |||
fa66381193 | |||
67c2406325 | |||
e2ea5c6ce0 | |||
5ef00d6dae | |||
5921098dce | |||
31ee71260a | |||
ed8067a4a2 | |||
e2116712e7 | |||
8b06da5783 | |||
33a427bb8a | |||
c9d7a957dc | |||
9c6ff6f353 | |||
7444cb6472 | |||
c7e5c9c8dd | |||
0256fc35d2 | |||
0019ed8dde | |||
56aec53e72 | |||
1b773f21c6 | |||
bccbcce3c1 | |||
fc0bed2405 | |||
16cfb33109 | |||
42979dc5ae | |||
95cbc85144 | |||
9cbca390fe | |||
38c0c81451 | |||
af4630ecb9 | |||
dbceb23823 | |||
e628ac0e9e | |||
3a1e8359c2 | |||
c397fd5479 | |||
16e1bbb1b5 | |||
8530444ffa | |||
2512b9f251 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -245,4 +245,6 @@ launchSettings.json
|
||||
/Tests/ApplicationTests/Files/replay.json
|
||||
/GameLogServer/game_log_server_env
|
||||
.idea/*
|
||||
*.db
|
||||
*.db
|
||||
/Data/IW4MAdmin_Migration.db-shm
|
||||
/Data/IW4MAdmin_Migration.db-wal
|
||||
|
@ -22,7 +22,7 @@ namespace IW4MAdmin.Application.API.Master
|
||||
public int Uptime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifices the version of the instance
|
||||
/// Specifies the version of the instance
|
||||
/// </summary>
|
||||
[JsonProperty("version")]
|
||||
[JsonConverter(typeof(BuildNumberJsonConverter))]
|
||||
@ -33,5 +33,11 @@ namespace IW4MAdmin.Application.API.Master
|
||||
/// </summary>
|
||||
[JsonProperty("servers")]
|
||||
public List<ApiServer> Servers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Url IW4MAdmin is listening on
|
||||
/// </summary>
|
||||
[JsonProperty("webfront_url")]
|
||||
public string WebfrontUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
||||
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
||||
<Version>2020.0.0.0</Version>
|
||||
@ -24,14 +24,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-1632" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.10">
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2037" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.10" />
|
||||
<PackageReference Include="RestEase" Version="1.5.1" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="RestEase" Version="1.5.5" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
@ -15,6 +15,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
@ -26,6 +27,7 @@ using IW4MAdmin.Application.Migration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using SharedLibraryCore.Formatting;
|
||||
using static SharedLibraryCore.GameEvent;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
using ObsoleteLogger = SharedLibraryCore.Interfaces.ILogger;
|
||||
@ -217,6 +219,8 @@ namespace IW4MAdmin.Application
|
||||
return _commands;
|
||||
}
|
||||
|
||||
public IReadOnlyList<IManagerCommand> Commands => _commands.ToImmutableList();
|
||||
|
||||
public async Task UpdateServerStates()
|
||||
{
|
||||
// store the server hash code and task for it
|
||||
@ -232,15 +236,8 @@ namespace IW4MAdmin.Application
|
||||
.Select(ut => ut.Key)
|
||||
.ToList();
|
||||
|
||||
// this is to prevent the log reader from starting before the initial
|
||||
// query of players on the server
|
||||
if (serverTasksToRemove.Count > 0)
|
||||
{
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
// remove the update tasks as they have completed
|
||||
foreach (var serverId in serverTasksToRemove)
|
||||
foreach (var serverId in serverTasksToRemove.Where(serverId => runningUpdateTasks.ContainsKey(serverId)))
|
||||
{
|
||||
if (!runningUpdateTasks[serverId].tokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
@ -259,7 +256,11 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
try
|
||||
{
|
||||
await server.ProcessUpdatesAsync(_tokenSource.Token).WithWaitCancellation(runningUpdateTasks[server.EndPoint].tokenSource.Token);
|
||||
if (runningUpdateTasks.ContainsKey(server.EndPoint))
|
||||
{
|
||||
await server.ProcessUpdatesAsync(_tokenSource.Token)
|
||||
.WithWaitCancellation(runningUpdateTasks[server.EndPoint].tokenSource.Token);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
@ -347,15 +348,13 @@ namespace IW4MAdmin.Application
|
||||
// copy over default config if it doesn't exist
|
||||
if (!_appConfig.Servers?.Any() ?? true)
|
||||
{
|
||||
var defaultConfig = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings").Configuration();
|
||||
//ConfigHandler.Set((ApplicationConfiguration)new ApplicationConfiguration().Generate());
|
||||
//var newConfig = ConfigHandler.Configuration();
|
||||
|
||||
var defaultHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings");
|
||||
await defaultHandler.BuildAsync();
|
||||
var defaultConfig = defaultHandler.Configuration();
|
||||
|
||||
_appConfig.AutoMessages = defaultConfig.AutoMessages;
|
||||
_appConfig.GlobalRules = defaultConfig.GlobalRules;
|
||||
_appConfig.Maps = defaultConfig.Maps;
|
||||
_appConfig.DisallowedClientNames = defaultConfig.DisallowedClientNames;
|
||||
_appConfig.QuickMessages = defaultConfig.QuickMessages;
|
||||
|
||||
//if (newConfig.Servers == null)
|
||||
{
|
||||
@ -396,12 +395,24 @@ namespace IW4MAdmin.Application
|
||||
await ConfigHandler.Save();
|
||||
}
|
||||
|
||||
#pragma warning disable 618
|
||||
if (_appConfig.Maps != null)
|
||||
{
|
||||
_appConfig.Maps = null;
|
||||
}
|
||||
|
||||
if (_appConfig.QuickMessages != null)
|
||||
{
|
||||
_appConfig.QuickMessages = null;
|
||||
}
|
||||
#pragma warning restore 618
|
||||
|
||||
var validator = new ApplicationConfigurationValidator();
|
||||
var validationResult = validator.Validate(_appConfig);
|
||||
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ConfigurationException("MANAGER_CONFIGURATION_ERROR")
|
||||
throw new ConfigurationException("Could not validate configuration")
|
||||
{
|
||||
Errors = validationResult.Errors.Select(_error => _error.ErrorMessage).ToArray(),
|
||||
ConfigurationFileName = ConfigHandler.FileName
|
||||
@ -410,7 +421,7 @@ namespace IW4MAdmin.Application
|
||||
|
||||
foreach (var serverConfig in _appConfig.Servers)
|
||||
{
|
||||
Migration.ConfigurationMigration.ModifyLogPath020919(serverConfig);
|
||||
ConfigurationMigration.ModifyLogPath020919(serverConfig);
|
||||
|
||||
if (serverConfig.RConParserVersion == null || serverConfig.EventParserVersion == null)
|
||||
{
|
||||
@ -438,6 +449,17 @@ namespace IW4MAdmin.Application
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(_appConfig.CustomParserEncoding) ? _appConfig.CustomParserEncoding : "windows-1252");
|
||||
|
||||
foreach (var parser in AdditionalRConParsers)
|
||||
{
|
||||
if (!parser.Configuration.ColorCodeMapping.ContainsKey(ColorCodes.Accent.ToString()))
|
||||
{
|
||||
parser.Configuration.ColorCodeMapping.Add(ColorCodes.Accent.ToString(),
|
||||
parser.Configuration.ColorCodeMapping.TryGetValue(_appConfig.IngameAccentColorKey, out var colorCode)
|
||||
? colorCode
|
||||
: "");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region COMMANDS
|
||||
@ -463,13 +485,17 @@ namespace IW4MAdmin.Application
|
||||
|
||||
// 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
|
||||
cmdConfig.CommandPrefix = _appConfig.CommandPrefix;
|
||||
cmdConfig.BroadcastCommandPrefix = _appConfig.BroadcastCommandPrefix;
|
||||
cmdConfig.CommandPrefix = _appConfig?.CommandPrefix ?? "!";
|
||||
cmdConfig.BroadcastCommandPrefix = _appConfig?.BroadcastCommandPrefix ?? "@";
|
||||
|
||||
foreach (var cmd in commandsToAddToConfig)
|
||||
{
|
||||
if (cmdConfig.Commands.ContainsKey(cmd.CommandConfigNameForType()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
cmdConfig.Commands.Add(cmd.CommandConfigNameForType(),
|
||||
new CommandProperties()
|
||||
new CommandProperties
|
||||
{
|
||||
Name = cmd.Name,
|
||||
Alias = cmd.Alias,
|
||||
@ -497,6 +523,7 @@ namespace IW4MAdmin.Application
|
||||
|
||||
Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]);
|
||||
await InitializeServers();
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
private async Task InitializeServers()
|
||||
@ -518,7 +545,7 @@ namespace IW4MAdmin.Application
|
||||
|
||||
_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.LogInformation("Finishing initialization and now monitoring [{Server}]", ServerInstance.Hostname);
|
||||
}
|
||||
|
||||
// add the start event for this server
|
||||
@ -591,6 +618,11 @@ namespace IW4MAdmin.Application
|
||||
return _servers.SelectMany(s => s.Clients).ToList().Where(p => p != null).ToList();
|
||||
}
|
||||
|
||||
public EFClient FindActiveClient(EFClient client) =>client.ClientNumber < 0 ?
|
||||
GetActiveClients()
|
||||
.FirstOrDefault(c => c.NetworkId == client.NetworkId) ?? client :
|
||||
client;
|
||||
|
||||
public ClientService GetClientService()
|
||||
{
|
||||
return ClientSvc;
|
||||
|
@ -13,4 +13,5 @@ if not exist "%TargetDir%Plugins" (
|
||||
md "%TargetDir%Plugins"
|
||||
)
|
||||
|
||||
xcopy /y "%SolutionDir%Build\Plugins" "%TargetDir%Plugins\"
|
||||
xcopy /y "%SolutionDir%Build\Plugins" "%TargetDir%Plugins\"
|
||||
del "%TargetDir%Plugins\SQLite*"
|
||||
|
@ -23,6 +23,7 @@ echo setting up default folders
|
||||
if not exist "%PublishDir%\Configuration" md "%PublishDir%\Configuration"
|
||||
move "%PublishDir%\DefaultSettings.json" "%PublishDir%\Configuration\"
|
||||
if not exist "%PublishDir%\Lib\" md "%PublishDir%\Lib\"
|
||||
del "%PublishDir%\Microsoft.CodeAnalysis*.dll" /F /Q
|
||||
move "%PublishDir%\*.dll" "%PublishDir%\Lib\"
|
||||
move "%PublishDir%\*.json" "%PublishDir%\Lib\"
|
||||
move "%PublishDir%\runtimes" "%PublishDir%\Lib\runtimes"
|
||||
@ -30,16 +31,37 @@ move "%PublishDir%\ru" "%PublishDir%\Lib\ru"
|
||||
move "%PublishDir%\de" "%PublishDir%\Lib\de"
|
||||
move "%PublishDir%\pt" "%PublishDir%\Lib\pt"
|
||||
move "%PublishDir%\es" "%PublishDir%\Lib\es"
|
||||
rmdir /Q /S "%PublishDir%\cs"
|
||||
rmdir /Q /S "%PublishDir%\fr"
|
||||
rmdir /Q /S "%PublishDir%\it"
|
||||
rmdir /Q /S "%PublishDir%\ja"
|
||||
rmdir /Q /S "%PublishDir%\ko"
|
||||
rmdir /Q /S "%PublishDir%\pl"
|
||||
rmdir /Q /S "%PublishDir%\pt-BR"
|
||||
rmdir /Q /S "%PublishDir%\tr"
|
||||
rmdir /Q /S "%PublishDir%\zh-Hans"
|
||||
rmdir /Q /S "%PublishDir%\zh-Hant"
|
||||
if exist "%PublishDir%\refs" move "%PublishDir%\refs" "%PublishDir%\Lib\refs"
|
||||
|
||||
echo making start scripts
|
||||
@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%PublishDir%\StartIW4MAdmin.cmd"
|
||||
@(echo #!/bin/bash&& echo export DOTNET_CLI_TELEMETRY_OPTOUT=1&& echo dotnet Lib/IW4MAdmin.dll) > "%PublishDir%\StartIW4MAdmin.sh"
|
||||
|
||||
echo copying update scripts
|
||||
copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.ps1" "%PublishDir%\UpdateIW4MAdmin.ps1"
|
||||
copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.sh" "%PublishDir%\UpdateIW4MAdmin.sh"
|
||||
|
||||
echo moving front-end library dependencies
|
||||
if not exist "%PublishDir%\wwwroot\font" mkdir "%PublishDir%\wwwroot\font"
|
||||
move "WebfrontCore\wwwroot\lib\open-iconic\font\fonts\*.*" "%PublishDir%\wwwroot\font\"
|
||||
if exist "%PublishDir%\wwwroot\lib" rd /s /q "%PublishDir%\wwwroot\lib"
|
||||
if not exist "%PublishDir%\wwwroot\css" mkdir "%PublishDir%\wwwroot\css"
|
||||
move "WebfrontCore\wwwroot\css\global.min.css" "%PublishDir%\wwwroot\css\global.min.css"
|
||||
if not exist "%PublishDir%\wwwroot\js" mkdir "%PublishDir%\wwwroot\js"
|
||||
move "%SourceDir%\WebfrontCore\wwwroot\js\global.min.js" "%PublishDir%\wwwroot\js\global.min.js"
|
||||
if not exist "%PublishDir%\wwwroot\images" mkdir "%PublishDir%\wwwroot\images"
|
||||
xcopy "%SourceDir%\WebfrontCore\wwwroot\images" "%PublishDir%\wwwroot\images" /E /H /C /I
|
||||
|
||||
|
||||
echo setting permissions...
|
||||
cacls "%PublishDir%" /t /e /p Everyone:F
|
||||
cacls "%PublishDir%" /t /e /p Everyone:F
|
||||
|
59
Application/Commands/FindPlayerCommand.cs
Normal file
59
Application/Commands/FindPlayerCommand.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds player by name
|
||||
/// </summary>
|
||||
public class FindPlayerCommand : Command
|
||||
{
|
||||
public FindPlayerCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "find";
|
||||
Description = _translationLookup["COMMANDS_FIND_DESC"];
|
||||
Alias = "f";
|
||||
Permission = EFClient.Permission.Administrator;
|
||||
RequiresTarget = false;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
if (gameEvent.Data.Length < 3)
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_FIND_MIN"]);
|
||||
return;
|
||||
}
|
||||
|
||||
var players = await gameEvent.Owner.Manager.GetClientService().FindClientsByIdentifier(gameEvent.Data);
|
||||
|
||||
if (!players.Any())
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_FIND_EMPTY"]);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var client in players)
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_FIND_FORMAT_V2"].FormatExt(client.Name,
|
||||
client.ClientId, Utilities.ConvertLevelToColor((EFClient.Permission) client.LevelInt, client.Level),
|
||||
client.IPAddress, (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
93
Application/Commands/HelpCommand.cs
Normal file
93
Application/Commands/HelpCommand.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Prints help information
|
||||
/// </summary>
|
||||
public class HelpCommand : Command
|
||||
{
|
||||
public HelpCommand(CommandConfiguration config, ITranslationLookup translationLookup) :
|
||||
base(config, translationLookup)
|
||||
{
|
||||
Name = "help";
|
||||
Description = translationLookup["COMMANDS_HELP_DESC"];
|
||||
Alias = "h";
|
||||
Permission = EFClient.Permission.User;
|
||||
RequiresTarget = false;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_COMMANDS"],
|
||||
Required = false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
var searchTerm = gameEvent.Data.Trim();
|
||||
var availableCommands = gameEvent.Owner.Manager.Commands.Distinct().Where(command =>
|
||||
command.SupportedGames == null || !command.SupportedGames.Any() ||
|
||||
command.SupportedGames.Contains(gameEvent.Owner.GameName))
|
||||
.Where(command => gameEvent.Origin.Level >= command.Permission);
|
||||
|
||||
if (searchTerm.Length > 2)
|
||||
{
|
||||
var matchingCommand = availableCommands.FirstOrDefault(command =>
|
||||
command.Name.Equals(searchTerm, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
command.Alias.Equals(searchTerm, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (matchingCommand != null)
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_HELP_SEARCH_RESULT"]
|
||||
.FormatExt(matchingCommand.Name, matchingCommand.Alias));
|
||||
gameEvent.Origin.Tell(matchingCommand.Syntax);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_HELP_NOTFOUND"]);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var commandStrings = availableCommands.Select((command, index) =>
|
||||
new
|
||||
{
|
||||
response = $" {_translationLookup["COMMANDS_HELP_LIST_FORMAT"].FormatExt(command.Name)} ",
|
||||
index
|
||||
});
|
||||
|
||||
var helpResponse = new StringBuilder();
|
||||
|
||||
foreach (var item in commandStrings)
|
||||
{
|
||||
helpResponse.Append(item.response);
|
||||
if (item.index == 0 || item.index % 4 != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
gameEvent.Origin.Tell(helpResponse.ToString());
|
||||
helpResponse = new StringBuilder();
|
||||
}
|
||||
|
||||
gameEvent.Origin.Tell(helpResponse.ToString());
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_HELP_MOREINFO"]);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
50
Application/Commands/ListAdminsCommand.cs
Normal file
50
Application/Commands/ListAdminsCommand.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Lists all unmasked admins
|
||||
/// </summary>
|
||||
public class ListAdminsCommand : Command
|
||||
{
|
||||
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "admins";
|
||||
Description = _translationLookup["COMMANDS_ADMINS_DESC"];
|
||||
Alias = "a";
|
||||
Permission = EFClient.Permission.User;
|
||||
RequiresTarget = false;
|
||||
}
|
||||
|
||||
public static string OnlineAdmins(Server server, ITranslationLookup lookup)
|
||||
{
|
||||
var onlineAdmins = server.GetClientsAsList()
|
||||
.Where(p => p.Level > EFClient.Permission.Flagged)
|
||||
.Where(p => !p.Masked)
|
||||
.Select(p =>
|
||||
$"[(Color::Yellow){Utilities.ConvertLevelToColor(p.Level, p.ClientPermission.Name)}(Color::White)] {p.Name}")
|
||||
.ToList();
|
||||
|
||||
return onlineAdmins.Any() ? string.Join(Environment.NewLine, onlineAdmins) : lookup["COMMANDS_ADMINS_NONE"];
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
foreach (var line in OnlineAdmins(gameEvent.Owner, _translationLookup).Split(Environment.NewLine))
|
||||
{
|
||||
var _ = gameEvent.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix)
|
||||
? gameEvent.Owner.Broadcast(line)
|
||||
: gameEvent.Origin.Tell(line);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
57
Application/Commands/ListAliasesCommand.cs
Normal file
57
Application/Commands/ListAliasesCommand.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Lists alises of specified client
|
||||
/// </summary>
|
||||
public class ListAliasesCommand : Command
|
||||
{
|
||||
public ListAliasesCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "alias";
|
||||
Description = _translationLookup["COMMANDS_ALIAS_DESC"];
|
||||
Alias = "known";
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = true;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
Required = true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
var message = new StringBuilder();
|
||||
var names = new List<string>(gameEvent.Target.AliasLink.Children.Select(a => a.Name));
|
||||
var ips = new List<string>(gameEvent.Target.AliasLink.Children.Select(a => a.IPAddress.ConvertIPtoString())
|
||||
.Distinct());
|
||||
|
||||
gameEvent.Origin.Tell($"[(Color::Accent){gameEvent.Target}(Color::White)]");
|
||||
|
||||
message.Append($"{_translationLookup["COMMANDS_ALIAS_ALIASES"]}: ");
|
||||
message.Append(string.Join(" | ", names));
|
||||
gameEvent.Origin.Tell(message.ToString());
|
||||
|
||||
message.Clear();
|
||||
message.Append($"{_translationLookup["COMMANDS_ALIAS_IPS"]}: ");
|
||||
message.Append(string.Join(" | ", ips));
|
||||
gameEvent.Origin.Tell(message.ToString());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
37
Application/Commands/ListClientsCommand.cs
Normal file
37
Application/Commands/ListClientsCommand.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// List online clients
|
||||
/// </summary>
|
||||
public class ListClientsCommand : Command
|
||||
{
|
||||
public ListClientsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "list";
|
||||
Description = _translationLookup["COMMANDS_LIST_DESC"];
|
||||
Alias = "l";
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = false;
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
var clientList = gameEvent.Owner.GetClientsAsList()
|
||||
.Select(client =>
|
||||
$"[(Color::Accent){client.ClientPermission.Name}(Color::White){(string.IsNullOrEmpty(client.Tag) ? "" : $" {client.Tag}")}(Color::White)][(Color::Yellow)#{client.ClientNumber}(Color::White)] {client.Name}")
|
||||
.ToArray();
|
||||
|
||||
gameEvent.Origin.Tell(clientList);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
41
Application/Commands/ListPluginsCommand.cs
Normal file
41
Application/Commands/ListPluginsCommand.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Lists the loaded plugins
|
||||
/// </summary>
|
||||
public class ListPluginsCommand : Command
|
||||
{
|
||||
private readonly IEnumerable<IPlugin> _plugins;
|
||||
|
||||
public ListPluginsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||
IEnumerable<IPlugin> plugins) : base(config, translationLookup)
|
||||
{
|
||||
Name = "plugins";
|
||||
Description = _translationLookup["COMMANDS_PLUGINS_DESC"];
|
||||
Alias = "p";
|
||||
Permission = EFClient.Permission.Administrator;
|
||||
RequiresTarget = false;
|
||||
_plugins = plugins;
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_PLUGINS_LOADED"]);
|
||||
foreach (var plugin in _plugins.Where(plugin => !plugin.IsParser))
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_LIST_PLUGINS_FORMAT"]
|
||||
.FormatExt(plugin.Name, plugin.Version, plugin.Author));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
59
Application/Commands/ListReportsCommand.cs
Normal file
59
Application/Commands/ListReportsCommand.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// List all reports on the server
|
||||
/// </summary>
|
||||
public class ListReportsCommand : Command
|
||||
{
|
||||
public ListReportsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "reports";
|
||||
Description = _translationLookup["COMMANDS_REPORTS_DESC"];
|
||||
Alias = "reps";
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = false;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument
|
||||
{
|
||||
Name = _translationLookup["COMMANDS_ARGS_CLEAR"],
|
||||
Required = false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
if (gameEvent.Data != null && gameEvent.Data.ToLower().Contains(_translationLookup["COMMANDS_ARGS_CLEAR"]))
|
||||
{
|
||||
gameEvent.Owner.Reports = new List<Report>();
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_REPORTS_CLEAR_SUCCESS"]);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (gameEvent.Owner.Reports.Count < 1)
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_REPORTS_NONE"]);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
foreach (var report in gameEvent.Owner.Reports)
|
||||
{
|
||||
gameEvent.Origin.Tell(
|
||||
$"(Color::Accent){report.Origin.Name}(Color::White) -> (Color::Red){report.Target.Name}(Color::White): {report.Reason}");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
116
Application/Commands/MapAndGameTypeCommand.cs
Normal file
116
Application/Commands/MapAndGameTypeCommand.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using IW4MAdmin.Application.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
public class MapAndGameTypeCommand : Command
|
||||
{
|
||||
private const string ArgumentRegexPattern = "(?:\"([^\"]+)\"|([^\\s]+)) (?:\"([^\"]+)\"|([^\\s]+))";
|
||||
private readonly ILogger _logger;
|
||||
private readonly DefaultSettings _defaultSettings;
|
||||
|
||||
public MapAndGameTypeCommand(ILogger<MapAndGameTypeCommand> logger, CommandConfiguration config,
|
||||
DefaultSettings defaultSettings, ITranslationLookup layout) : base(config, layout)
|
||||
{
|
||||
Name = "mapandgametype";
|
||||
Description = _translationLookup["COMMANDS_MAG_DESCRIPTION"];
|
||||
Alias = "mag";
|
||||
Permission = EFClient.Permission.Administrator;
|
||||
RequiresTarget = false;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument
|
||||
{
|
||||
Name = _translationLookup["COMMADS_MAG_ARG_1"],
|
||||
Required = true
|
||||
},
|
||||
new CommandArgument
|
||||
{
|
||||
Name = _translationLookup["COMMADS_MAG_ARG_2"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
_logger = logger;
|
||||
_defaultSettings = defaultSettings;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
var match = Regex.Match(gameEvent.Data.Trim(), ArgumentRegexPattern,
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
gameEvent.Origin.Tell(Syntax);
|
||||
return;
|
||||
}
|
||||
|
||||
var map = match.Groups[1].Length > 0 ? match.Groups[1].ToString() : match.Groups[2].ToString();
|
||||
var gametype = match.Groups[3].Length > 0 ? match.Groups[3].ToString() : match.Groups[4].ToString();
|
||||
|
||||
var matchingMaps = gameEvent.Owner.FindMap(map);
|
||||
var matchingGametypes = _defaultSettings.FindGametype(gametype, gameEvent.Owner.GameName);
|
||||
|
||||
if (matchingMaps.Count > 1)
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_MAG_MULTIPLE_MAPS"]);
|
||||
|
||||
foreach (var matchingMap in matchingMaps)
|
||||
{
|
||||
gameEvent.Origin.Tell(
|
||||
$"[(Color::Yellow){matchingMap.Alias}(Color::White)] [(Color::Yellow){matchingMap.Name}(Color::White)]");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchingGametypes.Count > 1)
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_MAG_MULTIPLE_GAMETYPES"]);
|
||||
|
||||
foreach (var matchingGametype in matchingGametypes)
|
||||
{
|
||||
gameEvent.Origin.Tell(
|
||||
$"[(Color::Yellow){matchingGametype.Alias}(Color::White)] [(Color::Yellow){matchingGametype.Name}(Color::White)]");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
map = matchingMaps.FirstOrDefault()?.Name ?? map;
|
||||
gametype = matchingGametypes.FirstOrDefault()?.Name ?? gametype;
|
||||
var hasMatchingGametype = matchingGametypes.Any();
|
||||
|
||||
_logger.LogDebug("Changing map to {Map} and gametype {Gametype}", map, gametype);
|
||||
|
||||
await gameEvent.Owner.SetDvarAsync("g_gametype", gametype);
|
||||
gameEvent.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(map));
|
||||
await Task.Delay(gameEvent.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds);
|
||||
|
||||
switch (gameEvent.Owner.GameName)
|
||||
{
|
||||
case Server.Game.IW5:
|
||||
await gameEvent.Owner.ExecuteCommandAsync(
|
||||
$"load_dsr {(hasMatchingGametype ? gametype.ToUpper() + "_default" : gametype)}");
|
||||
await gameEvent.Owner.ExecuteCommandAsync($"map {map}");
|
||||
break;
|
||||
case Server.Game.T6:
|
||||
await gameEvent.Owner.ExecuteCommandAsync($"exec {gametype}.cfg");
|
||||
await gameEvent.Owner.ExecuteCommandAsync($"map {map}");
|
||||
break;
|
||||
default:
|
||||
await gameEvent.Owner.ExecuteCommandAsync($"map {map}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
Application/Commands/OfflineMessageCommand.cs
Normal file
78
Application/Commands/OfflineMessageCommand.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Models.Client;
|
||||
using Data.Models.Misc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
public class OfflineMessageCommand : Command
|
||||
{
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
private readonly ILogger _logger;
|
||||
private const short MaxLength = 1024;
|
||||
|
||||
public OfflineMessageCommand(CommandConfiguration config, ITranslationLookup layout,
|
||||
IDatabaseContextFactory contextFactory, ILogger<IDatabaseContextFactory> logger) : base(config, layout)
|
||||
{
|
||||
Name = "offlinemessage";
|
||||
Description = _translationLookup["COMMANDS_OFFLINE_MESSAGE_DESC"];
|
||||
Alias = "om";
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = true;
|
||||
|
||||
_contextFactory = contextFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
if (gameEvent.Data.Length > MaxLength)
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_OFFLINE_MESSAGE_TOO_LONG"].FormatExt(MaxLength));
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameEvent.Target.ClientId == gameEvent.Origin.ClientId)
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_OFFLINE_MESSAGE_SELF"].FormatExt(MaxLength));
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameEvent.Target.IsIngame)
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_OFFLINE_MESSAGE_INGAME"].FormatExt(gameEvent.Target.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||
var server = await context.Servers.FirstAsync(srv => srv.EndPoint == gameEvent.Owner.ToString());
|
||||
|
||||
var newMessage = new EFInboxMessage()
|
||||
{
|
||||
SourceClientId = gameEvent.Origin.ClientId,
|
||||
DestinationClientId = gameEvent.Target.ClientId,
|
||||
ServerId = server.Id,
|
||||
Message = gameEvent.Data,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
context.Set<EFInboxMessage>().Add(newMessage);
|
||||
await context.SaveChangesAsync();
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_OFFLINE_MESSAGE_SUCCESS"]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not save offline message {@Message}", newMessage);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
Application/Commands/PrivateMessageCommand.cs
Normal file
45
Application/Commands/PrivateMessageCommand.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a private message to another player
|
||||
/// </summary>
|
||||
public class PrivateMessageCommand : Command
|
||||
{
|
||||
public PrivateMessageCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||
{
|
||||
Name = "privatemessage";
|
||||
Description = _translationLookup["COMMANDS_PM_DESC"];
|
||||
Alias = "pm";
|
||||
Permission = EFClient.Permission.User;
|
||||
RequiresTarget = true;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument
|
||||
{
|
||||
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
Required = true
|
||||
},
|
||||
new CommandArgument
|
||||
{
|
||||
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
gameEvent.Target.Tell(_translationLookup["COMMANDS_PRIVATE_MESSAGE_FORMAT"].FormatExt(gameEvent.Origin.Name, gameEvent.Data));
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_PRIVATE_MESSAGE_RESULT"]
|
||||
.FormatExt(gameEvent.Target.Name, gameEvent.Data));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
79
Application/Commands/ReadMessageCommand.cs
Normal file
79
Application/Commands/ReadMessageCommand.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using EFClient = Data.Models.Client.EFClient;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
public class ReadMessageCommand : Command
|
||||
{
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ReadMessageCommand(CommandConfiguration config, ITranslationLookup layout,
|
||||
IDatabaseContextFactory contextFactory, ILogger<IDatabaseContextFactory> logger) : base(config, layout)
|
||||
{
|
||||
Name = "readmessage";
|
||||
Description = _translationLookup["COMMANDS_READ_MESSAGE_DESC"];
|
||||
Alias = "rm";
|
||||
Permission = EFClient.Permission.Flagged;
|
||||
|
||||
_contextFactory = contextFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var context = _contextFactory.CreateContext();
|
||||
|
||||
var inboxItems = await context.InboxMessages
|
||||
.Include(message => message.SourceClient)
|
||||
.ThenInclude(client => client.CurrentAlias)
|
||||
.Where(message => message.DestinationClientId == gameEvent.Origin.ClientId)
|
||||
.Where(message => !message.IsDelivered)
|
||||
.ToListAsync();
|
||||
|
||||
if (!inboxItems.Any())
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_READ_MESSAGE_NONE"]);
|
||||
return;
|
||||
}
|
||||
|
||||
var index = 1;
|
||||
foreach (var inboxItem in inboxItems)
|
||||
{
|
||||
await gameEvent.Origin.Tell(_translationLookup["COMMANDS_READ_MESSAGE_SUCCESS"]
|
||||
.FormatExt($"{index}/{inboxItems.Count}", inboxItem.SourceClient.CurrentAlias.Name))
|
||||
.WaitAsync();
|
||||
|
||||
foreach (var messageFragment in inboxItem.Message.FragmentMessageForDisplay())
|
||||
{
|
||||
await gameEvent.Origin.Tell(messageFragment).WaitAsync();
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
inboxItems.ForEach(item => { item.IsDelivered = true; });
|
||||
|
||||
context.UpdateRange(inboxItems);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Could not retrieve offline messages for {Client}", gameEvent.Origin.ToString());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
Application/Commands/ReportClientCommand.cs
Normal file
77
Application/Commands/ReportClientCommand.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Report client for given reason
|
||||
/// </summary>
|
||||
public class ReportClientCommand : Command
|
||||
{
|
||||
public ReportClientCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "report";
|
||||
Description = _translationLookup["COMMANDS_REPORT_DESC"];
|
||||
Alias = "rep";
|
||||
Permission = EFClient.Permission.User;
|
||||
RequiresTarget = true;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument
|
||||
{
|
||||
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
Required = true
|
||||
},
|
||||
new CommandArgument
|
||||
{
|
||||
Name = _translationLookup["COMMANDS_ARGS_REASON"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent commandEvent)
|
||||
{
|
||||
if (commandEvent.Data.ToLower().Contains("camp"))
|
||||
{
|
||||
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_CAMP"]);
|
||||
return;
|
||||
}
|
||||
|
||||
var success = false;
|
||||
|
||||
switch ((await commandEvent.Target.Report(commandEvent.Data, commandEvent.Origin)
|
||||
.WaitAsync(Utilities.DefaultCommandTimeout, commandEvent.Owner.Manager.CancellationToken)).FailReason)
|
||||
{
|
||||
case GameEvent.EventFailReason.None:
|
||||
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_SUCCESS"]);
|
||||
success = true;
|
||||
break;
|
||||
case GameEvent.EventFailReason.Exception:
|
||||
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_DUPLICATE"]);
|
||||
break;
|
||||
case GameEvent.EventFailReason.Permission:
|
||||
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL"]
|
||||
.FormatExt(commandEvent.Target.Name));
|
||||
break;
|
||||
case GameEvent.EventFailReason.Invalid:
|
||||
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_SELF"]);
|
||||
break;
|
||||
case GameEvent.EventFailReason.Throttle:
|
||||
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_TOOMANY"]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
commandEvent.Owner.ToAdmins(
|
||||
$"(Color::Accent){commandEvent.Origin.Name}(Color::White) -> (Color::Red){commandEvent.Target.Name}(Color::White): {commandEvent.Data}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
Application/Commands/SayAllCommand.cs
Normal file
46
Application/Commands/SayAllCommand.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.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 = EFClient.Permission.Moderator;
|
||||
RequiresTarget = false;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument
|
||||
{
|
||||
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
var message = $"(Color::Accent){gameEvent.Origin.Name}(Color::White) - (Color::Red){gameEvent.Data}";
|
||||
|
||||
foreach (var server in gameEvent.Owner.Manager.GetServers())
|
||||
{
|
||||
server.Broadcast(message, gameEvent.Origin);
|
||||
}
|
||||
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
42
Application/Commands/SayCommand.cs
Normal file
42
Application/Commands/SayCommand.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using EFClient = Data.Models.Client.EFClient;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Prints out a message to all clients on the server
|
||||
/// </summary>
|
||||
public class SayCommand : Command
|
||||
{
|
||||
public SayCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "say";
|
||||
Description = _translationLookup["COMMANDS_SAY_DESC"];
|
||||
Alias = "s";
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = false;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument
|
||||
{
|
||||
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
gameEvent.Owner.Broadcast(
|
||||
_translationLookup["COMMANDS_SAY_FORMAT"].FormatExt(gameEvent.Origin.Name, gameEvent.Data),
|
||||
gameEvent.Origin);
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
38
Application/Commands/WhoAmICommand.cs
Normal file
38
Application/Commands/WhoAmICommand.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Prints client information
|
||||
/// </summary>
|
||||
public class WhoAmICommand : Command
|
||||
{
|
||||
public WhoAmICommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "whoami";
|
||||
Description = _translationLookup["COMMANDS_WHO_DESC"];
|
||||
Alias = "who";
|
||||
Permission = EFClient.Permission.User;
|
||||
RequiresTarget = false;
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
var you =
|
||||
"[(Color::Yellow)#{{clientNumber}}(Color::White)] [(Color::Yellow)@{{clientId}}(Color::White)] [{{networkId}}] [{{ip}}] [(Color::Cyan){{level}}(Color::White){{tag}}(Color::White)] {{name}}"
|
||||
.FormatExt(gameEvent.Origin.ClientNumber,
|
||||
gameEvent.Origin.ClientId, gameEvent.Origin.GuidString,
|
||||
gameEvent.Origin.IPAddressString, gameEvent.Origin.ClientPermission.Name,
|
||||
string.IsNullOrEmpty(gameEvent.Origin.Tag) ? "" : $" {gameEvent.Origin.Tag}",
|
||||
gameEvent.Origin.Name);
|
||||
gameEvent.Origin.Tell(you);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,13 @@
|
||||
"Using": [
|
||||
"Serilog.Sinks.File"
|
||||
],
|
||||
"MinimumLevel": "Information",
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"System": "Warning",
|
||||
"Microsoft": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "File",
|
||||
@ -12,6 +18,13 @@
|
||||
"rollingInterval": "Day",
|
||||
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}",
|
||||
"RestrictedToMinimumLevel": "Fatal"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Enrich": [
|
||||
|
@ -1,11 +1,11 @@
|
||||
{
|
||||
"AutoMessagePeriod": 60,
|
||||
"AutoMessages": [
|
||||
"This server uses ^5IW4M Admin v{{VERSION}} ^7get it at ^5raidmax.org/IW4MAdmin",
|
||||
"^5IW4M Admin ^7sees ^5YOU!",
|
||||
"This server uses (Color::Accent)IW4M Admin v{{VERSION}} (Color::White)get it at (Color::Accent)raidmax.org/IW4MAdmin",
|
||||
"(Color::Accent)IW4M Admin (Color::White)sees (Color::Accent)YOU!",
|
||||
"{{TOPSTATS}}",
|
||||
"This server has seen a total of ^5{{TOTALPLAYERS}} ^7players!",
|
||||
"Cheaters are ^1unwelcome ^7 on this server",
|
||||
"This server has seen a total of (Color::Accent){{TOTALPLAYERS}} (Color::White)players!",
|
||||
"Cheaters are (Color::Red)unwelcome (Color::White)on this server",
|
||||
"Did you know 8/10 people agree with unverified statistics?"
|
||||
],
|
||||
"GlobalRules": [
|
||||
@ -68,6 +68,504 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"Gametypes": [
|
||||
{
|
||||
"Game": "IW4",
|
||||
"Gametypes": [
|
||||
{
|
||||
"Name": "arena",
|
||||
"Alias": "Arena"
|
||||
},
|
||||
{
|
||||
"Name": "ctf",
|
||||
"Alias": "Capture The Flag"
|
||||
},
|
||||
{
|
||||
"Name": "dd",
|
||||
"Alias": "Demolition"
|
||||
},
|
||||
{
|
||||
"Name": "dm",
|
||||
"Alias": "Free For All"
|
||||
},
|
||||
{
|
||||
"Name": "dom",
|
||||
"Alias": "Domination"
|
||||
},
|
||||
{
|
||||
"Name": "gtnw",
|
||||
"Alias": "Global Thermo-Nuclear War"
|
||||
},
|
||||
{
|
||||
"Name": "koth",
|
||||
"Alias": "Headquarters"
|
||||
},
|
||||
{
|
||||
"Name": "oneflag",
|
||||
"Alias": "One-Flag CTF"
|
||||
},
|
||||
{
|
||||
"Name": "sab",
|
||||
"Alias": "Sabotage"
|
||||
},
|
||||
{
|
||||
"Name": "sd",
|
||||
"Alias": "Search & Destroy"
|
||||
},
|
||||
{
|
||||
"Name": "war",
|
||||
"Alias": "Team Deathmatch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "T4",
|
||||
"Gametypes": [
|
||||
{
|
||||
"Name": "ctf",
|
||||
"Alias": "Capture The Flag"
|
||||
},
|
||||
{
|
||||
"Name": "dom",
|
||||
"Alias": "Domination"
|
||||
},
|
||||
{
|
||||
"Name": "dm",
|
||||
"Alias": "Free For All"
|
||||
},
|
||||
{
|
||||
"Name": "koth",
|
||||
"Alias": "Headquarters"
|
||||
},
|
||||
{
|
||||
"Name": "tdm",
|
||||
"Alias": "Team Deathmatch"
|
||||
},
|
||||
{
|
||||
"Name": "sab",
|
||||
"Alias": "Sabotage"
|
||||
},
|
||||
{
|
||||
"Name": "sd",
|
||||
"Alias": "Search & Destroy"
|
||||
},
|
||||
{
|
||||
"Name": "twar",
|
||||
"Alias": "War"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "IW5",
|
||||
"Gametypes": [
|
||||
{
|
||||
"Name": "tdm",
|
||||
"Alias": "Team Deathmatch"
|
||||
},
|
||||
{
|
||||
"Name": "dom",
|
||||
"Alias": "Domination"
|
||||
},
|
||||
{
|
||||
"Name": "ctf",
|
||||
"Alias": "Capture The Flag"
|
||||
},
|
||||
{
|
||||
"Name": "dd",
|
||||
"Alias": "Demolition"
|
||||
},
|
||||
{
|
||||
"Name": "dz",
|
||||
"Alias": "Drop Zone"
|
||||
},
|
||||
{
|
||||
"Name": "ffa",
|
||||
"Alias": "Free For All"
|
||||
},
|
||||
{
|
||||
"Name": "gg",
|
||||
"Alias": "Gun Game"
|
||||
},
|
||||
{
|
||||
"Name": "hq",
|
||||
"Alias": "Headquarters"
|
||||
},
|
||||
{
|
||||
"Name": "koth",
|
||||
"Alias": "Headquarters"
|
||||
},
|
||||
{
|
||||
"Name": "inf",
|
||||
"Alias": "Infected"
|
||||
},
|
||||
{
|
||||
"Name": "jug",
|
||||
"Alias": "Juggernaut"
|
||||
},
|
||||
{
|
||||
"Name": "kc",
|
||||
"Alias": "Kill Confirmed"
|
||||
},
|
||||
{
|
||||
"Name": "oic",
|
||||
"Alias": "One In The Chamber"
|
||||
},
|
||||
{
|
||||
"Name": "sab",
|
||||
"Alias": "Sabotage"
|
||||
},
|
||||
{
|
||||
"Name": "sd",
|
||||
"Alias": "Search & Destroy"
|
||||
},
|
||||
{
|
||||
"Name": "tdef",
|
||||
"Alias": "Team Defender"
|
||||
},
|
||||
{
|
||||
"Name": "tj",
|
||||
"Alias": "Team Juggernaut"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "T5",
|
||||
"Gametypes": [
|
||||
{
|
||||
"Name": "ctf",
|
||||
"Alias": "Capture The Flag"
|
||||
},
|
||||
{
|
||||
"Name": "dem",
|
||||
"Alias": "Demolition"
|
||||
},
|
||||
{
|
||||
"Name": "dom",
|
||||
"Alias": "Domination"
|
||||
},
|
||||
{
|
||||
"Name": "dm",
|
||||
"Alias": "Free For All"
|
||||
},
|
||||
{
|
||||
"Name": "gun",
|
||||
"Alias": "Gun Game"
|
||||
},
|
||||
{
|
||||
"Name": "hlnd",
|
||||
"Alias": "Sticks & Stones"
|
||||
},
|
||||
{
|
||||
"Name": "koth",
|
||||
"Alias": "Headquarters"
|
||||
},
|
||||
{
|
||||
"Name": "oic",
|
||||
"Alias": "One In The Chamber"
|
||||
},
|
||||
{
|
||||
"Name": "sab",
|
||||
"Alias": "Sabotage"
|
||||
},
|
||||
{
|
||||
"Name": "sd",
|
||||
"Alias": "Search & Destroy"
|
||||
},
|
||||
{
|
||||
"Name": "shrp",
|
||||
"Alias": "Sharpshooter"
|
||||
},
|
||||
{
|
||||
"Name": "tdm",
|
||||
"Alias": "Team Deathmatch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "IW6",
|
||||
"Gametypes": [
|
||||
{
|
||||
"Name": "blitz",
|
||||
"Alias": "Blitz"
|
||||
},
|
||||
{
|
||||
"Name": "conf",
|
||||
"Alias": "Kill Confirmed"
|
||||
},
|
||||
{
|
||||
"Name": "cranked",
|
||||
"Alias": "Cranked"
|
||||
},
|
||||
{
|
||||
"Name": "dm",
|
||||
"Alias": "Free For All"
|
||||
},
|
||||
{
|
||||
"Name": "dom",
|
||||
"Alias": "Domination"
|
||||
},
|
||||
{
|
||||
"Name": "grind",
|
||||
"Alias": "Grind"
|
||||
},
|
||||
{
|
||||
"Name": "grnd",
|
||||
"Alias": "Drop Zone"
|
||||
},
|
||||
{
|
||||
"Name": "gun",
|
||||
"Alias": "Gun Game"
|
||||
},
|
||||
{
|
||||
"Name": "horde",
|
||||
"Alias": "Safeguard"
|
||||
},
|
||||
{
|
||||
"Name": "infect",
|
||||
"Alias": "Infected"
|
||||
},
|
||||
{
|
||||
"Name": "sd",
|
||||
"Alias": "Search & Destroy"
|
||||
},
|
||||
{
|
||||
"Name": "siege",
|
||||
"Alias": "Reinforce"
|
||||
},
|
||||
{
|
||||
"Name": "sotf",
|
||||
"Alias": "Hunted"
|
||||
},
|
||||
{
|
||||
"Name": "sotf_ffa",
|
||||
"Alias": "Hunted FFA"
|
||||
},
|
||||
{
|
||||
"Name": "sr",
|
||||
"Alias": "Search & Rescue"
|
||||
},
|
||||
{
|
||||
"Name": "war",
|
||||
"Alias": "Team Deathmatch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "T6",
|
||||
"Gametypes": [
|
||||
{
|
||||
"Name": "conf",
|
||||
"Alias": "Kill Confirmed"
|
||||
},
|
||||
{
|
||||
"Name": "ctf",
|
||||
"Alias": "Capture The Flag"
|
||||
},
|
||||
{
|
||||
"Name": "dem",
|
||||
"Alias": "Demolition"
|
||||
},
|
||||
{
|
||||
"Name": "dm",
|
||||
"Alias": "Free For All"
|
||||
},
|
||||
{
|
||||
"Name": "dom",
|
||||
"Alias": "Domination"
|
||||
},
|
||||
{
|
||||
"Name": "gun",
|
||||
"Alias": "Gun Game"
|
||||
},
|
||||
{
|
||||
"Name": "hq",
|
||||
"Alias": "Headquarters"
|
||||
},
|
||||
{
|
||||
"Name": "koth",
|
||||
"Alias": "Hardpoint"
|
||||
},
|
||||
{
|
||||
"Name": "oic",
|
||||
"Alias": "One In The Chamber"
|
||||
},
|
||||
{
|
||||
"Name": "oneflag",
|
||||
"Alias": "One-Flag CTF"
|
||||
},
|
||||
{
|
||||
"Name": "sas",
|
||||
"Alias": "Sticks & Stones"
|
||||
},
|
||||
{
|
||||
"Name": "sd",
|
||||
"Alias": "Search & Destroy"
|
||||
},
|
||||
{
|
||||
"Name": "shrp",
|
||||
"Alias": "Sharpshooter"
|
||||
},
|
||||
{
|
||||
"Name": "tdm",
|
||||
"Alias": "Team Deathmatch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "T7",
|
||||
"Gametypes": [
|
||||
{
|
||||
"Name": "ball",
|
||||
"Alias": "Uplink"
|
||||
},
|
||||
{
|
||||
"Name": "clean",
|
||||
"Alias": "Fracture"
|
||||
},
|
||||
{
|
||||
"Name": "conf",
|
||||
"Alias": "Kill Confirmed"
|
||||
},
|
||||
{
|
||||
"Name": "ctf",
|
||||
"Alias": "Capture The Flag"
|
||||
},
|
||||
{
|
||||
"Name": "dom",
|
||||
"Alias": "Domination"
|
||||
},
|
||||
{
|
||||
"Name": "dem",
|
||||
"Alias": "Demolition"
|
||||
},
|
||||
{
|
||||
"Name": "dm",
|
||||
"Alias": "Free For All"
|
||||
},
|
||||
{
|
||||
"Name": "escort",
|
||||
"Alias": "Safeguard"
|
||||
},
|
||||
{
|
||||
"Name": "gun",
|
||||
"Alias": "Gun Game"
|
||||
},
|
||||
{
|
||||
"Name": "koth",
|
||||
"Alias": "Hardpoint"
|
||||
},
|
||||
{
|
||||
"Name": "sd",
|
||||
"Alias": "Search & Destroy"
|
||||
},
|
||||
{
|
||||
"Name": "tdm",
|
||||
"Alias": "Team Deathmatch"
|
||||
},
|
||||
{
|
||||
"Name": "hc_ball",
|
||||
"Alias": "Hardcore Uplink"
|
||||
},
|
||||
{
|
||||
"Name": "hc_clean",
|
||||
"Alias": "Hardcore Fracture"
|
||||
},
|
||||
{
|
||||
"Name": "hc_conf",
|
||||
"Alias": "Hardcore Kill Confirmed"
|
||||
},
|
||||
{
|
||||
"Name": "hc_ctf",
|
||||
"Alias": "Hardcore Capture The Flag"
|
||||
},
|
||||
{
|
||||
"Name": "hc_dom",
|
||||
"Alias": "Hardcore Domination"
|
||||
},
|
||||
{
|
||||
"Name": "hc_dem",
|
||||
"Alias": "Hardcore Demolition"
|
||||
},
|
||||
{
|
||||
"Name": "hc_dm",
|
||||
"Alias": "Hardcore Free For All"
|
||||
},
|
||||
{
|
||||
"Name": "hc_escort",
|
||||
"Alias": "Hardcore Safeguard"
|
||||
},
|
||||
{
|
||||
"Name": "hc_gun",
|
||||
"Alias": "Hardcore Gun Game"
|
||||
},
|
||||
{
|
||||
"Name": "hc_koth",
|
||||
"Alias": "Hardcore Hardpoint"
|
||||
},
|
||||
{
|
||||
"Name": "hc_sd",
|
||||
"Alias": "Hardcore Search & Destroy"
|
||||
},
|
||||
{
|
||||
"Name": "hc_tdm",
|
||||
"Alias": "Hardcore Team Deathmatch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "SHG1",
|
||||
"Gametypes": [
|
||||
{
|
||||
"Name": "ball",
|
||||
"Alias": "Uplink"
|
||||
},
|
||||
{
|
||||
"Name": "conf",
|
||||
"Alias": "Kill Confirmed"
|
||||
},
|
||||
{
|
||||
"Name": "ctf",
|
||||
"Alias": "Capture The Flag"
|
||||
},
|
||||
{
|
||||
"Name": "dom",
|
||||
"Alias": "Domination"
|
||||
},
|
||||
{
|
||||
"Name": "dm",
|
||||
"Alias": "Free For All"
|
||||
},
|
||||
{
|
||||
"Name": "gun",
|
||||
"Alias": "Gun Game"
|
||||
},
|
||||
{
|
||||
"Name": "hp",
|
||||
"Alias": "Hardpoint"
|
||||
},
|
||||
{
|
||||
"Name": "infect",
|
||||
"Alias": "Infected"
|
||||
},
|
||||
{
|
||||
"Name": "sd",
|
||||
"Alias": "Search & Destroy"
|
||||
},
|
||||
{
|
||||
"Name": "sr",
|
||||
"Alias": "Search & Rescue"
|
||||
},
|
||||
{
|
||||
"Name": "war",
|
||||
"Alias": "Team Deathmatch"
|
||||
},
|
||||
{
|
||||
"Name": "twar",
|
||||
"Alias": "Momentum"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Maps": [
|
||||
{
|
||||
"Game": "IW3",
|
||||
@ -435,8 +933,8 @@
|
||||
"Name": "oilrig"
|
||||
},
|
||||
{
|
||||
"Name": "Village",
|
||||
"Alias": "co_hunted"
|
||||
"Alias": "Village",
|
||||
"Name": "co_hunted"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -699,6 +1197,22 @@
|
||||
{
|
||||
"Alias": "Rust",
|
||||
"Name": "mp_rust"
|
||||
},
|
||||
{
|
||||
"Alias": "Highrise",
|
||||
"Name": "mp_highrise"
|
||||
},
|
||||
{
|
||||
"Alias": "Favela",
|
||||
"Name": "mp_favela"
|
||||
},
|
||||
{
|
||||
"Alias": "Nuketown",
|
||||
"Name": "mp_nuked"
|
||||
},
|
||||
{
|
||||
"Alias": "Skidrow",
|
||||
"Name": "mp_nightshift"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -859,11 +1373,132 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "T7",
|
||||
"Maps": [
|
||||
{
|
||||
"Alias": "Evac",
|
||||
"Name": "mp_apartments"
|
||||
},
|
||||
{
|
||||
"Alias": "Aquarium",
|
||||
"Name": "mp_biodome"
|
||||
},
|
||||
{
|
||||
"Alias": "Exodus",
|
||||
"Name": "mp_chinatown"
|
||||
},
|
||||
{
|
||||
"Alias": "Hunted",
|
||||
"Name": "mp_ethiopia"
|
||||
},
|
||||
{
|
||||
"Alias": "Havoc",
|
||||
"Name": "mp_havoc"
|
||||
},
|
||||
{
|
||||
"Alias": "Infection",
|
||||
"Name": "mp_infection"
|
||||
},
|
||||
{
|
||||
"Alias": "Metro",
|
||||
"Name": "mp_metro"
|
||||
},
|
||||
{
|
||||
"Alias": "Redwood",
|
||||
"Name": "mp_redwood"
|
||||
},
|
||||
{
|
||||
"Alias": "Combine",
|
||||
"Name": "mp_sector"
|
||||
},
|
||||
{
|
||||
"Alias": "Breach",
|
||||
"Name": "mp_spire"
|
||||
},
|
||||
{
|
||||
"Alias": "Stronghold",
|
||||
"Name": "mp_stronghold"
|
||||
},
|
||||
{
|
||||
"Alias": "Fringe",
|
||||
"Name": "mp_veiled"
|
||||
},
|
||||
{
|
||||
"Alias": "Nuk3town",
|
||||
"Name": "mp_nuketown_x"
|
||||
},
|
||||
{
|
||||
"Alias": "Gauntlet",
|
||||
"Name": "mp_crucible"
|
||||
},
|
||||
{
|
||||
"Alias": "Rise",
|
||||
"Name": "mp_rise"
|
||||
},
|
||||
{
|
||||
"Alias": "Skyjacked",
|
||||
"Name": "mp_skyjacked"
|
||||
},
|
||||
{
|
||||
"Alias": "Splash",
|
||||
"Name": "mp_waterpark"
|
||||
},
|
||||
{
|
||||
"Alias": "Spire",
|
||||
"Name": "mp_aerospace"
|
||||
},
|
||||
{
|
||||
"Alias": "Verge",
|
||||
"Name": "mp_banzai"
|
||||
},
|
||||
{
|
||||
"Alias": "Rift",
|
||||
"Name": "mp_conduit"
|
||||
},
|
||||
{
|
||||
"Alias": "Knockout",
|
||||
"Name": "mp_kung_fu"
|
||||
},
|
||||
{
|
||||
"Alias": "Rumble",
|
||||
"Name": "mp_arena"
|
||||
},
|
||||
{
|
||||
"Alias": "Cyrogen",
|
||||
"Name": "mp_cryogen"
|
||||
},
|
||||
{
|
||||
"Alias": "Empire",
|
||||
"Name": "mp_rome"
|
||||
},
|
||||
{
|
||||
"Alias": "Berserk",
|
||||
"Name": "mp_shrine"
|
||||
},
|
||||
{
|
||||
"Alias": "Rupture",
|
||||
"Name": "mp_city"
|
||||
},
|
||||
{
|
||||
"Alias": "Micro",
|
||||
"Name": "mp_miniature"
|
||||
},
|
||||
{
|
||||
"Alias": "Citadel",
|
||||
"Name": "mp_ruins"
|
||||
},
|
||||
{
|
||||
"Alias": "Outlaw",
|
||||
"Name": "mp_western"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "IW6",
|
||||
"Maps": [
|
||||
{
|
||||
"Alias": "Prision Break",
|
||||
"Alias": "Prison Break",
|
||||
"Name": "mp_prisonbreak"
|
||||
},
|
||||
{
|
||||
@ -1008,7 +1643,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
"Game": "SHG1",
|
||||
"Maps": [
|
||||
{
|
||||
@ -1132,6 +1767,151 @@
|
||||
"Name": "mp_urban"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "CSGO",
|
||||
"Maps": [
|
||||
{
|
||||
"Name": "ar_baggage",
|
||||
"Alias": "Baggage"
|
||||
},
|
||||
{
|
||||
"Name": "ar_dizzy",
|
||||
"Alias": "Dizzy"
|
||||
},
|
||||
{
|
||||
"Name": "ar_lunacy",
|
||||
"Alias": "Lunacy"
|
||||
},
|
||||
{
|
||||
"Name": "ar_monastery",
|
||||
"Alias": "Monastery"
|
||||
},
|
||||
{
|
||||
"Name": "ar_shoots",
|
||||
"Alias": "Shoots"
|
||||
},
|
||||
{
|
||||
"Name": "cs_agency",
|
||||
"Alias": "Agency"
|
||||
},
|
||||
{
|
||||
"Name": "cs_assault",
|
||||
"Alias": "Assault"
|
||||
},
|
||||
{
|
||||
"Name": "cs_italy",
|
||||
"Alias": "Italy"
|
||||
},
|
||||
{
|
||||
"Name": "cs_militia",
|
||||
"Alias": "Militia"
|
||||
},
|
||||
{
|
||||
"Name": "cs_office",
|
||||
"Alias": "Office"
|
||||
},
|
||||
{
|
||||
"Name": "de_ancient",
|
||||
"Alias": "Ancient"
|
||||
},
|
||||
{
|
||||
"Name": "de_bank",
|
||||
"Alias": "Bank"
|
||||
},
|
||||
{
|
||||
"Name": "de_cache",
|
||||
"Alias": "Cache"
|
||||
},
|
||||
{
|
||||
"Name": "de_calavera",
|
||||
"Alias": "Calavera"
|
||||
},
|
||||
{
|
||||
"Name": "de_canals",
|
||||
"Alias": "Canals"
|
||||
},
|
||||
{
|
||||
"Name": "de_cbble",
|
||||
"Alias": "Cobblestone"
|
||||
},
|
||||
{
|
||||
"Name": "de_dust2",
|
||||
"Alias": "Dust II"
|
||||
},
|
||||
{
|
||||
"Name": "de_grind",
|
||||
"Alias": "Grind"
|
||||
},
|
||||
{
|
||||
"Name": "de_inferno",
|
||||
"Alias": "Inferno"
|
||||
},
|
||||
{
|
||||
"Name": "de_lake",
|
||||
"Alias": "Lake"
|
||||
},
|
||||
{
|
||||
"Name": "de_mirage",
|
||||
"Alias": "Mirage"
|
||||
},
|
||||
{
|
||||
"Name": "de_mocha",
|
||||
"Alias": "Mocha"
|
||||
},
|
||||
{
|
||||
"Name": "de_nuke",
|
||||
"Alias": "Nuke"
|
||||
},
|
||||
{
|
||||
"Name": "de_overpass",
|
||||
"Alias": "Overpass"
|
||||
},
|
||||
{
|
||||
"Name": "de_pitstop",
|
||||
"Alias": "Pitstop"
|
||||
},
|
||||
{
|
||||
"Name": "de_safehouse",
|
||||
"Alias": "Safehouse"
|
||||
},
|
||||
{
|
||||
"Name": "de_shortdust",
|
||||
"Alias": "Shortdust"
|
||||
},
|
||||
{
|
||||
"Name": "de_shortnuke",
|
||||
"Alias": "Shortnuke"
|
||||
},
|
||||
{
|
||||
"Name": "de_stmarc",
|
||||
"Alias": "St. Marc"
|
||||
},
|
||||
{
|
||||
"Name": "de_sugarcane",
|
||||
"Alias": "Sugarcane"
|
||||
},
|
||||
{
|
||||
"Name": "de_train",
|
||||
"Alias": "Train"
|
||||
},
|
||||
{
|
||||
"Name": "de_vertigo",
|
||||
"Alias": "Vertigo"
|
||||
},
|
||||
{
|
||||
"Name": "dz_blacksite",
|
||||
"Alias": "Blacksite"
|
||||
},
|
||||
{
|
||||
"Name": "dz_frostbite",
|
||||
"Alias": "Frostbite"
|
||||
},
|
||||
{
|
||||
"Name": "dz_sirocco",
|
||||
"Alias": "Sirocco"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"GameStrings": {
|
||||
@ -1247,62 +2027,62 @@
|
||||
"winchester1200": "W1200",
|
||||
"concussion": "Stun",
|
||||
"melee": "Knife",
|
||||
"Frag" : "Grenade",
|
||||
"Frag": "Grenade",
|
||||
"airstrike": "Airstrike",
|
||||
"helicopter": "Attack Helicopter",
|
||||
"player": "",
|
||||
"attach": ""
|
||||
},
|
||||
"T4": {
|
||||
"torso_upper": "Upper Torso",
|
||||
"torso_lower": "Lower Torso",
|
||||
"right_leg_upper": "Upper Right Leg",
|
||||
"right_leg_lower": "Lower Right Leg",
|
||||
"right_hand": "Right Hand",
|
||||
"right_foot": "Right Foot",
|
||||
"right_arm_upper": "Upper Right Arm",
|
||||
"right_arm_lower": "Lower Right Arm",
|
||||
"left_leg_upper": "Upper Left Leg",
|
||||
"left_leg_lower": "Lower Left Leg",
|
||||
"left_hand": "Left Hand",
|
||||
"left_foot": "Left Foot",
|
||||
"left_arm_upper": "Upper Left Arm",
|
||||
"left_arm_lower": "Lower Left Arm",
|
||||
"gl": "Rifle Grenade",
|
||||
"bigammo": "Round Drum",
|
||||
"scoped": "Sniper Scope",
|
||||
"telescopic": "Telescopic Sight",
|
||||
"aperture": "Aperture Sight",
|
||||
"flash": "Flash Hider",
|
||||
"silenced": "Silencer",
|
||||
"molotov": "Molotov Cocktail",
|
||||
"sticky": "N° 74 ST",
|
||||
"m2": "M2 Flamethrower",
|
||||
"artillery": "Artillery Strike",
|
||||
"dog": "Attack Dogs",
|
||||
"colt": "Colt M1911",
|
||||
"357magnum": ".357 Magnum",
|
||||
"walther": "Walther P38",
|
||||
"tokarev": "Tokarev TT-33",
|
||||
"shotgun": "M1897 Trench Gun",
|
||||
"doublebarreledshotgun": "Double-Barreled Shotgun",
|
||||
"mp40": "MP40",
|
||||
"type100smg": "Type 100",
|
||||
"ppsh": "PPSh-41",
|
||||
"svt40": "SVT-40",
|
||||
"gewehr43": "Gewehr 43",
|
||||
"m1garand": "M1 Garand",
|
||||
"stg44": "STG-44",
|
||||
"m1carbine": "M1A1 Carbine",
|
||||
"type99lmg": "Type 99",
|
||||
"bar": "BAR",
|
||||
"dp28": "DP-28",
|
||||
"mg42": "MG42",
|
||||
"fg42": "FG42",
|
||||
"30cal": "Browning M1919",
|
||||
"type99rifle": "Arisaka",
|
||||
"mosinrifle": "Mosin-Nagant",
|
||||
"ptrs41":"PTRS-41"
|
||||
}
|
||||
"T4": {
|
||||
"torso_upper": "Upper Torso",
|
||||
"torso_lower": "Lower Torso",
|
||||
"right_leg_upper": "Upper Right Leg",
|
||||
"right_leg_lower": "Lower Right Leg",
|
||||
"right_hand": "Right Hand",
|
||||
"right_foot": "Right Foot",
|
||||
"right_arm_upper": "Upper Right Arm",
|
||||
"right_arm_lower": "Lower Right Arm",
|
||||
"left_leg_upper": "Upper Left Leg",
|
||||
"left_leg_lower": "Lower Left Leg",
|
||||
"left_hand": "Left Hand",
|
||||
"left_foot": "Left Foot",
|
||||
"left_arm_upper": "Upper Left Arm",
|
||||
"left_arm_lower": "Lower Left Arm",
|
||||
"gl": "Rifle Grenade",
|
||||
"bigammo": "Round Drum",
|
||||
"scoped": "Sniper Scope",
|
||||
"telescopic": "Telescopic Sight",
|
||||
"aperture": "Aperture Sight",
|
||||
"flash": "Flash Hider",
|
||||
"silenced": "Silencer",
|
||||
"molotov": "Molotov Cocktail",
|
||||
"sticky": "N° 74 ST",
|
||||
"m2": "M2 Flamethrower",
|
||||
"artillery": "Artillery Strike",
|
||||
"dog": "Attack Dogs",
|
||||
"colt": "Colt M1911",
|
||||
"357magnum": ".357 Magnum",
|
||||
"walther": "Walther P38",
|
||||
"tokarev": "Tokarev TT-33",
|
||||
"shotgun": "M1897 Trench Gun",
|
||||
"doublebarreledshotgun": "Double-Barreled Shotgun",
|
||||
"mp40": "MP40",
|
||||
"type100smg": "Type 100",
|
||||
"ppsh": "PPSh-41",
|
||||
"svt40": "SVT-40",
|
||||
"gewehr43": "Gewehr 43",
|
||||
"m1garand": "M1 Garand",
|
||||
"stg44": "STG-44",
|
||||
"m1carbine": "M1A1 Carbine",
|
||||
"type99lmg": "Type 99",
|
||||
"bar": "BAR",
|
||||
"dp28": "DP-28",
|
||||
"mg42": "MG42",
|
||||
"fg42": "FG42",
|
||||
"30cal": "Browning M1919",
|
||||
"type99rifle": "Arisaka",
|
||||
"mosinrifle": "Mosin-Nagant",
|
||||
"ptrs41": "PTRS-41"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
|
||||
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4);
|
||||
|
||||
Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
|
||||
Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
|
||||
Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1);
|
||||
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
|
||||
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
|
||||
@ -65,7 +65,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
Configuration.Damage.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
|
||||
Configuration.Damage.AddMapping(ParserRegex.GroupType.HitLocation, 13);
|
||||
|
||||
Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
|
||||
Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
|
||||
Configuration.Kill.AddMapping(ParserRegex.GroupType.EventType, 1);
|
||||
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
|
||||
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
|
||||
@ -120,7 +120,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
if (lineSplit.Length > 1)
|
||||
{
|
||||
var type = lineSplit[0];
|
||||
return _eventTypeMap.ContainsKey(type) ? (_eventTypeMap[type], type): (GameEvent.EventType.Unknown, null);
|
||||
return _eventTypeMap.ContainsKey(type) ? (_eventTypeMap[type], type): (GameEvent.EventType.Unknown, lineSplit[0]);
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _regexMap)
|
||||
|
@ -1,6 +1,10 @@
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Linq;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
|
||||
namespace IW4MAdmin.Application.Extensions
|
||||
{
|
||||
@ -13,9 +17,19 @@ namespace IW4MAdmin.Application.Extensions
|
||||
/// <returns></returns>
|
||||
public static string CommandConfigNameForType(this IManagerCommand command)
|
||||
{
|
||||
return command.GetType() == typeof(ScriptCommand) ?
|
||||
$"{char.ToUpper(command.Name[0])}{command.Name.Substring(1)}Command" :
|
||||
command.GetType().Name;
|
||||
return command.GetType() == typeof(ScriptCommand)
|
||||
? $"{char.ToUpper(command.Name[0])}{command.Name.Substring(1)}Command"
|
||||
: command.GetType().Name;
|
||||
}
|
||||
|
||||
public static IList<Map> FindMap(this Server server, string mapName) => server.Maps.Where(map =>
|
||||
map.Name.Equals(mapName, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
map.Alias.Equals(mapName, StringComparison.InvariantCultureIgnoreCase)).ToList();
|
||||
|
||||
public static IList<Gametype> FindGametype(this DefaultSettings settings, string gameType, Server.Game? game = null) =>
|
||||
settings.Gametypes?.Where(gt => game == null || gt.Game == game)
|
||||
.SelectMany(gt => gt.Gametypes).Where(gt =>
|
||||
gt.Alias.Contains(gameType, StringComparison.CurrentCultureIgnoreCase) ||
|
||||
gt.Name.Contains(gameType, StringComparison.CurrentCultureIgnoreCase)).ToList();
|
||||
}
|
||||
}
|
||||
|
@ -78,8 +78,10 @@ namespace IW4MAdmin.Application.Extensions
|
||||
case "mysql":
|
||||
var appendTimeout = !appConfig.ConnectionString.Contains("default command timeout",
|
||||
StringComparison.InvariantCultureIgnoreCase);
|
||||
var connectionString =
|
||||
appConfig.ConnectionString + (appendTimeout ? ";default command timeout=0" : "");
|
||||
services.AddSingleton(sp => (DbContextOptions) new DbContextOptionsBuilder<MySqlDatabaseContext>()
|
||||
.UseMySql(appConfig.ConnectionString + (appendTimeout ? ";default command timeout=0" : ""),
|
||||
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString),
|
||||
mysqlOptions => mysqlOptions.EnableRetryOnFailure())
|
||||
.UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options);
|
||||
return services;
|
||||
@ -92,7 +94,7 @@ namespace IW4MAdmin.Application.Extensions
|
||||
postgresqlOptions =>
|
||||
{
|
||||
postgresqlOptions.EnableRetryOnFailure();
|
||||
postgresqlOptions.SetPostgresVersion(new Version("9.4"));
|
||||
postgresqlOptions.SetPostgresVersion(new Version("12.9"));
|
||||
})
|
||||
.UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options);
|
||||
return services;
|
||||
@ -101,4 +103,4 @@ namespace IW4MAdmin.Application.Extensions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using System.Threading.Tasks;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Factories
|
||||
@ -17,7 +18,17 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// <returns></returns>
|
||||
public IConfigurationHandler<T> GetConfigurationHandler<T>(string name) where T : IBaseConfiguration
|
||||
{
|
||||
return new BaseConfigurationHandler<T>(name);
|
||||
var handler = new BaseConfigurationHandler<T>(name);
|
||||
handler.BuildAsync().Wait();
|
||||
return handler;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IConfigurationHandler<T>> GetConfigurationHandlerAsync<T>(string name) where T : IBaseConfiguration
|
||||
{
|
||||
var handler = new BaseConfigurationHandler<T>(name);
|
||||
await handler.BuildAsync();
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,22 @@ namespace IW4MAdmin.Application.Factories
|
||||
public IGameLogReader CreateGameLogReader(Uri[] logUris, IEventParser eventParser)
|
||||
{
|
||||
var baseUri = logUris[0];
|
||||
if (baseUri.Scheme == Uri.UriSchemeHttp)
|
||||
if (baseUri.Scheme == Uri.UriSchemeHttp || baseUri.Scheme == Uri.UriSchemeHttps)
|
||||
{
|
||||
return new GameLogReaderHttp(logUris, eventParser, _serviceProvider.GetRequiredService<ILogger<GameLogReaderHttp>>());
|
||||
return new GameLogReaderHttp(logUris, eventParser,
|
||||
_serviceProvider.GetRequiredService<ILogger<GameLogReaderHttp>>());
|
||||
}
|
||||
|
||||
else if (baseUri.Scheme == Uri.UriSchemeFile)
|
||||
if (baseUri.Scheme == Uri.UriSchemeFile)
|
||||
{
|
||||
return new GameLogReader(baseUri.LocalPath, eventParser, _serviceProvider.GetRequiredService<ILogger<GameLogReader>>());
|
||||
return new GameLogReader(baseUri.LocalPath, eventParser,
|
||||
_serviceProvider.GetRequiredService<ILogger<GameLogReader>>());
|
||||
}
|
||||
|
||||
if (baseUri.Scheme == Uri.UriSchemeNetTcp)
|
||||
{
|
||||
return new NetworkGameLogReader(logUris, eventParser,
|
||||
_serviceProvider.GetRequiredService<ILogger<NetworkGameLogReader>>());
|
||||
}
|
||||
|
||||
throw new NotImplementedException($"No log reader implemented for Uri scheme \"{baseUri.Scheme}\"");
|
||||
|
@ -22,7 +22,7 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// </summary>
|
||||
/// <param name="translationLookup"></param>
|
||||
/// <param name="rconConnectionFactory"></param>
|
||||
public GameServerInstanceFactory(ITranslationLookup translationLookup,
|
||||
public GameServerInstanceFactory(ITranslationLookup translationLookup,
|
||||
IMetaService metaService,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
@ -39,7 +39,10 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// <returns></returns>
|
||||
public Server CreateServer(ServerConfiguration config, IManager manager)
|
||||
{
|
||||
return new IW4MServer(config, _translationLookup, _metaService, _serviceProvider, _serviceProvider.GetRequiredService<IClientNoticeMessageFormatter>(), _serviceProvider.GetRequiredService<ILookupCache<EFServer>>());
|
||||
return new IW4MServer(config,
|
||||
_serviceProvider.GetRequiredService<CommandConfiguration>(), _translationLookup, _metaService,
|
||||
_serviceProvider, _serviceProvider.GetRequiredService<IClientNoticeMessageFormatter>(),
|
||||
_serviceProvider.GetRequiredService<ILookupCache<EFServer>>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Text;
|
||||
using Integrations.Cod;
|
||||
@ -6,6 +7,7 @@ using Integrations.Source;
|
||||
using Integrations.Source.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Configuration;
|
||||
|
||||
namespace IW4MAdmin.Application.Factories
|
||||
{
|
||||
@ -26,21 +28,17 @@ namespace IW4MAdmin.Application.Factories
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// creates a new rcon connection instance
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">ip address of the server</param>
|
||||
/// <param name="port">port of the server</param>
|
||||
/// <param name="password">rcon password of the server</param>
|
||||
/// <returns></returns>
|
||||
public IRConConnection CreateConnection(string ipAddress, int port, string password, string rconEngine)
|
||||
/// <inheritdoc/>
|
||||
public IRConConnection CreateConnection(IPEndPoint ipEndpoint, string password, string rconEngine)
|
||||
{
|
||||
return rconEngine switch
|
||||
{
|
||||
"COD" => new CodRConConnection(ipAddress, port, password,
|
||||
_serviceProvider.GetRequiredService<ILogger<CodRConConnection>>(), GameEncoding),
|
||||
"Source" => new SourceRConConnection(_serviceProvider.GetRequiredService<ILogger<SourceRConConnection>>(),
|
||||
_serviceProvider.GetRequiredService<IRConClientFactory>(), ipAddress, port, password),
|
||||
"COD" => new CodRConConnection(ipEndpoint, password,
|
||||
_serviceProvider.GetRequiredService<ILogger<CodRConConnection>>(), GameEncoding,
|
||||
_serviceProvider.GetRequiredService<ApplicationConfiguration>()?.ServerConnectionAttempts ?? 6),
|
||||
"Source" => new SourceRConConnection(
|
||||
_serviceProvider.GetRequiredService<ILogger<SourceRConConnection>>(),
|
||||
_serviceProvider.GetRequiredService<IRConClientFactory>(), ipEndpoint, password),
|
||||
_ => throw new ArgumentException($"No supported RCon engine available for '{rconEngine}'")
|
||||
};
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -30,7 +31,7 @@ namespace IW4MAdmin.Application.Factories
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission,
|
||||
bool isTargetRequired, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction)
|
||||
bool isTargetRequired, IEnumerable<(string, bool)> args, Func<GameEvent, Task> executeAction, Server.Game[] supportedGames)
|
||||
{
|
||||
var permissionEnum = Enum.Parse<EFClient.Permission>(permission);
|
||||
var argsArray = args.Select(_arg => new CommandArgument
|
||||
@ -40,7 +41,7 @@ namespace IW4MAdmin.Application.Factories
|
||||
}).ToArray();
|
||||
|
||||
return new ScriptCommand(name, alias, description, isTargetRequired, permissionEnum, argsArray, executeAction,
|
||||
_config, _transLookup, _serviceProvider.GetRequiredService<ILogger<ScriptCommand>>());
|
||||
_config, _transLookup, _serviceProvider.GetRequiredService<ILogger<ScriptCommand>>(), supportedGames);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
private readonly EventLog _eventLog;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
private static readonly GameEvent.EventType[] overrideEvents = new[]
|
||||
{
|
||||
GameEvent.EventType.Connect,
|
||||
@ -21,10 +22,11 @@ namespace IW4MAdmin.Application
|
||||
GameEvent.EventType.Stop
|
||||
};
|
||||
|
||||
public GameEventHandler(ILogger<GameEventHandler> logger)
|
||||
public GameEventHandler(ILogger<GameEventHandler> logger, IEventPublisher eventPublisher)
|
||||
{
|
||||
_eventLog = new EventLog();
|
||||
_logger = logger;
|
||||
_eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
public void HandleEvent(IManager manager, GameEvent gameEvent)
|
||||
@ -32,6 +34,7 @@ namespace IW4MAdmin.Application
|
||||
if (manager.IsRunning || overrideEvents.Contains(gameEvent.Type))
|
||||
{
|
||||
EventApi.OnGameEvent(gameEvent);
|
||||
_eventPublisher.Publish(gameEvent);
|
||||
Task.Factory.StartNew(() => manager.ExecuteEvent(gameEvent));
|
||||
}
|
||||
else
|
||||
|
@ -21,7 +21,7 @@ namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
_reader = gameLogReaderFactory.CreateGameLogReader(gameLogUris, server.EventParser);
|
||||
_server = server;
|
||||
_ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false;
|
||||
_ignoreBots = server.Manager.GetApplicationSettings().Configuration()?.IgnoreBots ?? false;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ namespace IW4MAdmin.Application.IO
|
||||
return;
|
||||
}
|
||||
|
||||
var events = await _reader.ReadEventsFromLog(fileDiff, previousFileSize);
|
||||
var events = await _reader.ReadEventsFromLog(fileDiff, previousFileSize, _server);
|
||||
|
||||
foreach (var gameEvent in events)
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ namespace IW4MAdmin.Application.IO
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition)
|
||||
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition, Server server = null)
|
||||
{
|
||||
// allocate the bytes for the new log lines
|
||||
List<string> logLines = new List<string>();
|
||||
|
@ -34,7 +34,7 @@ namespace IW4MAdmin.Application.IO
|
||||
|
||||
public int UpdateInterval => 500;
|
||||
|
||||
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition)
|
||||
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition, Server server = null)
|
||||
{
|
||||
var events = new List<GameEvent>();
|
||||
var response = await _logServerApi.Log(_safeLogPath, lastKey);
|
||||
|
157
Application/IO/NetworkGameLogReader.cs
Normal file
157
Application/IO/NetworkGameLogReader.cs
Normal file
@ -0,0 +1,157 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Integrations.Cod;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// provides capability of reading log files over udp
|
||||
/// </summary>
|
||||
class NetworkGameLogReader : IGameLogReader
|
||||
{
|
||||
private readonly IEventParser _eventParser;
|
||||
private readonly ILogger _logger;
|
||||
private readonly Uri _uri;
|
||||
private static readonly NetworkLogState State = new();
|
||||
private bool _stateRegistered;
|
||||
private CancellationToken _token;
|
||||
|
||||
public NetworkGameLogReader(IReadOnlyList<Uri> uris, IEventParser parser, ILogger<NetworkGameLogReader> logger)
|
||||
{
|
||||
_eventParser = parser;
|
||||
_uri = uris[0];
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public long Length => -1;
|
||||
|
||||
public int UpdateInterval => 150;
|
||||
|
||||
public Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition,
|
||||
Server server = null)
|
||||
{
|
||||
// todo: other games might support this
|
||||
var serverEndpoint = (server?.RemoteConnection as CodRConConnection)?.Endpoint;
|
||||
|
||||
if (serverEndpoint is null)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<GameEvent>());
|
||||
}
|
||||
|
||||
if (!_stateRegistered && !State.EndPointExists(serverEndpoint))
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = State.RegisterEndpoint(serverEndpoint, BuildLocalEndpoint()).Client;
|
||||
|
||||
_stateRegistered = true;
|
||||
_token = server.Manager.CancellationToken;
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", server.ToString()))
|
||||
{
|
||||
_logger.LogInformation("Not registering {Name} socket because it is already bound",
|
||||
nameof(NetworkGameLogReader));
|
||||
}
|
||||
return Task.FromResult(Enumerable.Empty<GameEvent>());
|
||||
}
|
||||
|
||||
new Thread(() => ReadNetworkData(client)).Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not register {Name} endpoint {Endpoint}",
|
||||
nameof(NetworkGameLogReader), _uri);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
var events = new List<GameEvent>();
|
||||
|
||||
foreach (var logData in State.GetServerLogData(serverEndpoint)
|
||||
.Select(log => Utilities.EncodingType.GetString(log)))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(logData))
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<GameEvent>());
|
||||
}
|
||||
|
||||
var lines = logData
|
||||
.Split('\n')
|
||||
.Where(line => line.Length > 0);
|
||||
|
||||
foreach (var eventLine in lines)
|
||||
{
|
||||
try
|
||||
{
|
||||
// this trim end should hopefully fix the nasty runaway regex
|
||||
var gameEvent = _eventParser.GenerateGameEvent(eventLine.TrimEnd('\r'));
|
||||
events.Add(gameEvent);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not properly parse event line from http {EventLine}",
|
||||
eventLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult((IEnumerable<GameEvent>)events);
|
||||
}
|
||||
|
||||
private void ReadNetworkData(UdpClient client)
|
||||
{
|
||||
while (!_token.IsCancellationRequested)
|
||||
{
|
||||
// get more data
|
||||
IPEndPoint remoteEndpoint = null;
|
||||
byte[] bufferedData = null;
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
// we already have a socket listening on this port for data, so we don't need to run another thread
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bufferedData = client.Receive(ref remoteEndpoint);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not receive lines for {LogReader}", nameof(NetworkGameLogReader));
|
||||
}
|
||||
|
||||
if (bufferedData != null)
|
||||
{
|
||||
State.QueueServerLogData(remoteEndpoint, bufferedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IPEndPoint BuildLocalEndpoint()
|
||||
{
|
||||
try
|
||||
{
|
||||
return new IPEndPoint(Dns.GetHostAddresses(_uri.Host).First(), _uri.Port);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not setup {LogReader} endpoint", nameof(NetworkGameLogReader));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
138
Application/IO/NetworkLogState.cs
Normal file
138
Application/IO/NetworkLogState.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace IW4MAdmin.Application.IO;
|
||||
|
||||
public class NetworkLogState : Dictionary<IPEndPoint, UdpClientState>
|
||||
{
|
||||
public UdpClientState RegisterEndpoint(IPEndPoint serverEndpoint, IPEndPoint localEndpoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (!ContainsKey(serverEndpoint))
|
||||
{
|
||||
Add(serverEndpoint, new UdpClientState { Client = new UdpClient(localEndpoint) });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
// we don't add the udp client because it already exists (listening to multiple servers from one socket)
|
||||
Add(serverEndpoint, new UdpClientState());
|
||||
}
|
||||
}
|
||||
|
||||
return this[serverEndpoint];
|
||||
}
|
||||
|
||||
|
||||
public List<byte[]> GetServerLogData(IPEndPoint serverEndpoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
var state = this[serverEndpoint];
|
||||
|
||||
if (state == null)
|
||||
{
|
||||
return new List<byte[]>();
|
||||
}
|
||||
|
||||
// it's possible that we could be trying to read and write to the queue simultaneously so we need to wait
|
||||
this[serverEndpoint].OnAction.Wait();
|
||||
|
||||
var data = new List<byte[]>();
|
||||
|
||||
while (this[serverEndpoint].AvailableLogData.Count > 0)
|
||||
{
|
||||
data.Add(this[serverEndpoint].AvailableLogData.Dequeue());
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (this[serverEndpoint].OnAction.CurrentCount == 0)
|
||||
{
|
||||
this[serverEndpoint].OnAction.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void QueueServerLogData(IPEndPoint serverEndpoint, byte[] data)
|
||||
{
|
||||
var endpoint = Keys.FirstOrDefault(key =>
|
||||
Equals(key.Address, serverEndpoint.Address) && key.Port == serverEndpoint.Port);
|
||||
|
||||
try
|
||||
{
|
||||
if (endpoint == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// currently our expected start and end characters
|
||||
var startsWithPrefix = StartsWith(data, "ÿÿÿÿprint\n");
|
||||
var endsWithDelimiter = data[^1] == '\n';
|
||||
|
||||
// we have the data we expected
|
||||
if (!startsWithPrefix || !endsWithDelimiter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// it's possible that we could be trying to read and write to the queue simultaneously so we need to wait
|
||||
this[endpoint].OnAction.Wait();
|
||||
this[endpoint].AvailableLogData.Enqueue(data);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (endpoint != null && this[endpoint].OnAction.CurrentCount == 0)
|
||||
{
|
||||
this[endpoint].OnAction.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool EndPointExists(IPEndPoint serverEndpoint)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
return ContainsKey(serverEndpoint);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool StartsWith(byte[] sourceArray, string match)
|
||||
{
|
||||
if (sourceArray is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (match.Length > sourceArray.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !match.Where((t, i) => sourceArray[i] != (byte)t).Any();
|
||||
}
|
||||
}
|
||||
|
||||
public class UdpClientState
|
||||
{
|
||||
public UdpClient Client { get; set; }
|
||||
public Queue<byte[]> AvailableLogData { get; } = new();
|
||||
public SemaphoreSlim OnAction { get; } = new(1, 1);
|
||||
|
||||
~UdpClientState()
|
||||
{
|
||||
OnAction.Dispose();
|
||||
Client?.Dispose();
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@ -22,6 +24,8 @@ using Serilog.Context;
|
||||
using static SharedLibraryCore.Database.Models.EFClient;
|
||||
using Data.Models;
|
||||
using Data.Models.Server;
|
||||
using IW4MAdmin.Application.Commands;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using static Data.Models.Client.EFClient;
|
||||
|
||||
namespace IW4MAdmin
|
||||
@ -39,25 +43,30 @@ namespace IW4MAdmin
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IClientNoticeMessageFormatter _messageFormatter;
|
||||
private readonly ILookupCache<EFServer> _serverCache;
|
||||
private readonly CommandConfiguration _commandConfiguration;
|
||||
|
||||
public IW4MServer(
|
||||
ServerConfiguration serverConfiguration,
|
||||
CommandConfiguration commandConfiguration,
|
||||
ITranslationLookup lookup,
|
||||
IMetaService metaService,
|
||||
IServiceProvider serviceProvider,
|
||||
IClientNoticeMessageFormatter messageFormatter,
|
||||
ILookupCache<EFServer> serverCache) : base(serviceProvider.GetRequiredService<ILogger<Server>>(),
|
||||
#pragma warning disable CS0612
|
||||
serviceProvider.GetRequiredService<SharedLibraryCore.Interfaces.ILogger>(),
|
||||
#pragma warning restore CS0612
|
||||
serverConfiguration,
|
||||
serviceProvider.GetRequiredService<IManager>(),
|
||||
serviceProvider.GetRequiredService<IRConConnectionFactory>(),
|
||||
serviceProvider.GetRequiredService<IGameLogReaderFactory>())
|
||||
serviceProvider.GetRequiredService<IGameLogReaderFactory>(), serviceProvider)
|
||||
{
|
||||
_translationLookup = lookup;
|
||||
_metaService = metaService;
|
||||
_serviceProvider = serviceProvider;
|
||||
_messageFormatter = messageFormatter;
|
||||
_serverCache = serverCache;
|
||||
_commandConfiguration = commandConfiguration;
|
||||
}
|
||||
|
||||
public override async Task<EFClient> OnClientConnected(EFClient clientFromLog)
|
||||
@ -158,7 +167,7 @@ namespace IW4MAdmin
|
||||
{
|
||||
try
|
||||
{
|
||||
C = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E, Manager.GetApplicationSettings().Configuration());
|
||||
C = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E, Manager.GetApplicationSettings().Configuration(), _commandConfiguration);
|
||||
}
|
||||
|
||||
catch (CommandException e)
|
||||
@ -227,7 +236,7 @@ namespace IW4MAdmin
|
||||
private async Task CreatePluginTask(IPlugin plugin, GameEvent gameEvent)
|
||||
{
|
||||
// we don't want to run the events on parser plugins
|
||||
if (plugin is ScriptPlugin scriptPlugin && scriptPlugin.IsParser)
|
||||
if (plugin is ScriptPlugin { IsParser: true })
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -237,11 +246,16 @@ namespace IW4MAdmin
|
||||
|
||||
try
|
||||
{
|
||||
await (plugin.OnEventAsync(gameEvent, this)).WithWaitCancellation(tokenSource.Token);
|
||||
await plugin.OnEventAsync(gameEvent, this).WithWaitCancellation(tokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
ServerLogger.LogWarning("Timed out executing event {EventType} for {Plugin}", gameEvent.Type,
|
||||
plugin.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(loc["SERVER_PLUGIN_ERROR"]);
|
||||
Console.WriteLine(loc["SERVER_PLUGIN_ERROR"].FormatExt(plugin.Name, ex.GetType().Name));
|
||||
ServerLogger.LogError(ex, "Could not execute {methodName} for plugin {plugin}",
|
||||
nameof(plugin.OnEventAsync), plugin.Name);
|
||||
}
|
||||
@ -281,7 +295,7 @@ namespace IW4MAdmin
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.ConnectionLost)
|
||||
else if (E.Type == GameEvent.EventType.ConnectionLost)
|
||||
{
|
||||
var exception = E.Extra as Exception;
|
||||
ServerLogger.LogError(exception,
|
||||
@ -295,7 +309,7 @@ namespace IW4MAdmin
|
||||
Throttled = true;
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.ConnectionRestored)
|
||||
else if (E.Type == GameEvent.EventType.ConnectionRestored)
|
||||
{
|
||||
ServerLogger.LogInformation(
|
||||
"Connection restored with {server}", ToString());
|
||||
@ -305,10 +319,15 @@ namespace IW4MAdmin
|
||||
Console.WriteLine(loc["MANAGER_CONNECTION_REST"].FormatExt($"[{IP}:{Port}]"));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(CustomSayName))
|
||||
{
|
||||
await this.SetDvarAsync("sv_sayname", CustomSayName);
|
||||
}
|
||||
|
||||
Throttled = false;
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.ChangePermission)
|
||||
else if (E.Type == GameEvent.EventType.ChangePermission)
|
||||
{
|
||||
var newPermission = (Permission) E.Extra;
|
||||
ServerLogger.LogInformation("{origin} is setting {target} to permission level {newPermission}",
|
||||
@ -338,7 +357,26 @@ namespace IW4MAdmin
|
||||
E.Origin.Tag = clientTag.LinkedMeta.Value;
|
||||
}
|
||||
|
||||
await E.Origin.OnJoin(E.Origin.IPAddress);
|
||||
try
|
||||
{
|
||||
var factory = _serviceProvider.GetRequiredService<IDatabaseContextFactory>();
|
||||
await using var context = factory.CreateContext(enableTracking: false);
|
||||
|
||||
var messageCount = await context.InboxMessages
|
||||
.CountAsync(msg => msg.DestinationClientId == E.Origin.ClientId && !msg.IsDelivered);
|
||||
|
||||
if (messageCount > 0)
|
||||
{
|
||||
E.Origin.Tell(_translationLookup["SERVER_JOIN_OFFLINE_MESSAGES"]);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerLogger.LogError(ex, "Could not get offline message count for {Client}", E.Origin.ToString());
|
||||
throw;
|
||||
}
|
||||
|
||||
await E.Origin.OnJoin(E.Origin.IPAddress, Manager.GetApplicationSettings().Configuration().EnableImplicitAccountLinking);
|
||||
}
|
||||
}
|
||||
|
||||
@ -404,7 +442,7 @@ namespace IW4MAdmin
|
||||
|
||||
if (E.Origin.Level > Permission.Moderator)
|
||||
{
|
||||
E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
|
||||
E.Origin.Tell(loc["SERVER_REPORT_COUNT_V2"].FormatExt(E.Owner.Reports.Count));
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,13 +476,13 @@ namespace IW4MAdmin
|
||||
Link = E.Target.AliasLink
|
||||
};
|
||||
|
||||
var addedPenalty = await Manager.GetPenaltyService().Create(newPenalty);
|
||||
await Manager.GetPenaltyService().Create(newPenalty);
|
||||
E.Target.SetLevel(Permission.Flagged, E.Origin);
|
||||
}
|
||||
|
||||
else if (E.Type == GameEvent.EventType.Unflag)
|
||||
{
|
||||
var unflagPenalty = new EFPenalty()
|
||||
var unflagPenalty = new EFPenalty
|
||||
{
|
||||
Type = EFPenalty.PenaltyType.Unflag,
|
||||
Expires = DateTime.UtcNow,
|
||||
@ -456,7 +494,8 @@ namespace IW4MAdmin
|
||||
};
|
||||
|
||||
E.Target.SetLevel(Permission.User, E.Origin);
|
||||
await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId);
|
||||
await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId,
|
||||
E.Target.CurrentAlias?.IPAddress);
|
||||
await Manager.GetPenaltyService().Create(unflagPenalty);
|
||||
}
|
||||
|
||||
@ -581,7 +620,7 @@ namespace IW4MAdmin
|
||||
await OnClientUpdate(E.Origin);
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Say)
|
||||
else if (E.Type == GameEvent.EventType.Say)
|
||||
{
|
||||
if (E.Data?.Length > 0)
|
||||
{
|
||||
@ -590,7 +629,7 @@ namespace IW4MAdmin
|
||||
{
|
||||
try
|
||||
{
|
||||
message = Manager.GetApplicationSettings().Configuration()
|
||||
message = _serviceProvider.GetRequiredService<DefaultSettings>()
|
||||
.QuickMessages
|
||||
.First(_qm => _qm.Game == GameName)
|
||||
.Messages[E.Data.Substring(1)];
|
||||
@ -611,7 +650,7 @@ namespace IW4MAdmin
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.MapChange)
|
||||
else if (E.Type == GameEvent.EventType.MapChange)
|
||||
{
|
||||
ServerLogger.LogInformation("New map loaded - {clientCount} active players", ClientNum);
|
||||
|
||||
@ -652,7 +691,7 @@ namespace IW4MAdmin
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.MapEnd)
|
||||
else if (E.Type == GameEvent.EventType.MapEnd)
|
||||
{
|
||||
ServerLogger.LogInformation("Game ending...");
|
||||
|
||||
@ -662,12 +701,12 @@ namespace IW4MAdmin
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Tell)
|
||||
else if (E.Type == GameEvent.EventType.Tell)
|
||||
{
|
||||
await Tell(E.Message, E.Target);
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Broadcast)
|
||||
else if (E.Type == GameEvent.EventType.Broadcast)
|
||||
{
|
||||
if (!Utilities.IsDevelopment && E.Data != null) // hides broadcast when in development mode
|
||||
{
|
||||
@ -696,11 +735,11 @@ namespace IW4MAdmin
|
||||
|
||||
private async Task OnClientUpdate(EFClient origin)
|
||||
{
|
||||
var client = GetClientsAsList().FirstOrDefault(_client => _client.Equals(origin));
|
||||
var client = Manager.GetActiveClients().FirstOrDefault(c => c.NetworkId == origin.NetworkId);
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
ServerLogger.LogWarning("{origin} expected to exist in client list for update, but they do not", origin.ToString());
|
||||
ServerLogger.LogWarning("{Origin} expected to exist in client list for update, but they do not", origin.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -714,7 +753,7 @@ namespace IW4MAdmin
|
||||
{
|
||||
try
|
||||
{
|
||||
await client.OnJoin(origin.IPAddress);
|
||||
await client.OnJoin(origin.IPAddress, Manager.GetApplicationSettings().Configuration().EnableImplicitAccountLinking);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
@ -726,11 +765,11 @@ namespace IW4MAdmin
|
||||
}
|
||||
}
|
||||
|
||||
else if ((client.IPAddress != null && client.State == ClientState.Disconnecting) ||
|
||||
else if (client.IPAddress != null && client.State == ClientState.Disconnecting ||
|
||||
client.Level == Permission.Banned)
|
||||
{
|
||||
ServerLogger.LogWarning("{client} state is Unknown (probably kicked), but they are still connected. trying to kick again...", origin.ToString());
|
||||
await client.CanConnect(client.IPAddress);
|
||||
ServerLogger.LogWarning("{Client} state is Unknown (probably kicked), but they are still connected. trying to kick again...", origin.ToString());
|
||||
await client.CanConnect(client.IPAddress, Manager.GetApplicationSettings().Configuration().EnableImplicitAccountLinking);
|
||||
}
|
||||
}
|
||||
|
||||
@ -768,8 +807,10 @@ namespace IW4MAdmin
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<long> GetIdForServer(Server server)
|
||||
public override async Task<long> GetIdForServer(Server server = null)
|
||||
{
|
||||
server ??= this;
|
||||
|
||||
if ($"{server.IP}:{server.Port.ToString()}" == "66.150.121.184:28965")
|
||||
{
|
||||
return 886229536;
|
||||
@ -976,10 +1017,13 @@ namespace IW4MAdmin
|
||||
LastMessage = DateTime.Now - start;
|
||||
lastCount = DateTime.Now;
|
||||
|
||||
var appConfig = _serviceProvider.GetService<ApplicationConfiguration>();
|
||||
// update the player history
|
||||
if ((lastCount - playerCountStart).TotalMinutes >= PlayerHistory.UpdateInterval)
|
||||
if (lastCount - playerCountStart >= appConfig.ServerDataCollectionInterval)
|
||||
{
|
||||
while (ClientHistory.Count > ((60 / PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours
|
||||
var maxItems = Math.Ceiling(appConfig.MaxClientHistoryTime.TotalMinutes /
|
||||
appConfig.ServerDataCollectionInterval.TotalMinutes);
|
||||
while ( ClientHistory.Count > maxItems)
|
||||
{
|
||||
ClientHistory.Dequeue();
|
||||
}
|
||||
@ -1036,16 +1080,33 @@ namespace IW4MAdmin
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
ResolvedIpEndPoint =
|
||||
new IPEndPoint(
|
||||
(await Dns.GetHostAddressesAsync(IP)).First(address =>
|
||||
address.AddressFamily == AddressFamily.InterNetwork), Port);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerLogger.LogWarning(ex, "Could not resolve hostname or IP for RCon connection {IP}:{Port}", IP, Port);
|
||||
ResolvedIpEndPoint = new IPEndPoint(IPAddress.Parse(IP), Port);
|
||||
}
|
||||
|
||||
RconParser = Manager.AdditionalRConParsers
|
||||
.FirstOrDefault(_parser => _parser.Version == ServerConfig.RConParserVersion);
|
||||
.FirstOrDefault(parser =>
|
||||
parser.Version == ServerConfig.RConParserVersion ||
|
||||
parser.Name == ServerConfig.RConParserVersion);
|
||||
|
||||
EventParser = Manager.AdditionalEventParsers
|
||||
.FirstOrDefault(_parser => _parser.Version == ServerConfig.EventParserVersion);
|
||||
.FirstOrDefault(parser =>
|
||||
parser.Version == ServerConfig.EventParserVersion ||
|
||||
parser.Name == ServerConfig.RConParserVersion);
|
||||
|
||||
RconParser = RconParser ?? Manager.AdditionalRConParsers[0];
|
||||
EventParser = EventParser ?? Manager.AdditionalEventParsers[0];
|
||||
RconParser ??= Manager.AdditionalRConParsers[0];
|
||||
EventParser ??= Manager.AdditionalEventParsers[0];
|
||||
|
||||
RemoteConnection = RConConnectionFactory.CreateConnection(IP, Port, Password, RconParser.RConEngine);
|
||||
RemoteConnection = RConConnectionFactory.CreateConnection(ResolvedIpEndPoint, Password, RconParser.RConEngine);
|
||||
RemoteConnection.SetConfiguration(RconParser);
|
||||
|
||||
var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version");
|
||||
@ -1057,7 +1118,7 @@ namespace IW4MAdmin
|
||||
GameName = RconParser.GameName;
|
||||
}
|
||||
|
||||
if (version?.Value?.Length != 0)
|
||||
if (version.Value?.Length != 0)
|
||||
{
|
||||
var matchedRconParser = Manager.AdditionalRConParsers.FirstOrDefault(_parser => _parser.Version == version.Value);
|
||||
RconParser.Configuration = matchedRconParser != null ? matchedRconParser.Configuration : RconParser.Configuration;
|
||||
@ -1078,8 +1139,9 @@ namespace IW4MAdmin
|
||||
string mapname = (await this.GetMappedDvarValueOrDefaultAsync<string>("mapname", infoResponse: infoResponse)).Value;
|
||||
int maxplayers = (await this.GetMappedDvarValueOrDefaultAsync<int>("sv_maxclients", infoResponse: infoResponse)).Value;
|
||||
string gametype = (await this.GetMappedDvarValueOrDefaultAsync<string>("g_gametype", "gametype", infoResponse)).Value;
|
||||
var basepath = (await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basepath"));
|
||||
var basegame = (await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basegame"));
|
||||
var basepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basepath");
|
||||
var basegame = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basegame");
|
||||
var homepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_homepath");
|
||||
var game = (await this.GetMappedDvarValueOrDefaultAsync<string>("fs_game", infoResponse: infoResponse));
|
||||
var logfile = await this.GetMappedDvarValueOrDefaultAsync<string>("g_log");
|
||||
var logsync = await this.GetMappedDvarValueOrDefaultAsync<int>("g_logsync");
|
||||
@ -1115,8 +1177,14 @@ namespace IW4MAdmin
|
||||
{
|
||||
Manager.GetApplicationSettings().Configuration().ContactUri = Website;
|
||||
}
|
||||
|
||||
var defaultConfig = _serviceProvider.GetRequiredService<DefaultSettings>();
|
||||
var gameMaps = defaultConfig?.Maps?.FirstOrDefault(map => map.Game == GameName);
|
||||
|
||||
InitializeMaps();
|
||||
if (gameMaps != null)
|
||||
{
|
||||
Maps.AddRange(gameMaps.Maps);
|
||||
}
|
||||
|
||||
WorkingDirectory = basepath.Value;
|
||||
this.Hostname = hostname;
|
||||
@ -1174,15 +1242,16 @@ namespace IW4MAdmin
|
||||
{
|
||||
BaseGameDirectory = basegame.Value,
|
||||
BasePathDirectory = basepath.Value,
|
||||
HomePathDirectory = homepath.Value,
|
||||
GameDirectory = EventParser.Configuration.GameDirectory ?? "",
|
||||
ModDirectory = game.Value ?? "",
|
||||
LogFile = logfile.Value,
|
||||
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows),
|
||||
IsOneLog = RconParser.IsOneLog
|
||||
};
|
||||
LogPath = GenerateLogPath(logInfo);
|
||||
ServerLogger.LogInformation("Game log information {@logInfo}", logInfo);
|
||||
|
||||
|
||||
if (!File.Exists(LogPath) && ServerConfig.GameLogServerUrl == null)
|
||||
{
|
||||
Console.WriteLine(loc["SERVER_ERROR_DNE"].FormatExt(LogPath));
|
||||
@ -1223,21 +1292,31 @@ namespace IW4MAdmin
|
||||
public static string GenerateLogPath(LogPathGeneratorInfo logInfo)
|
||||
{
|
||||
string logPath;
|
||||
string workingDirectory = logInfo.BasePathDirectory;
|
||||
var workingDirectory = logInfo.BasePathDirectory;
|
||||
|
||||
bool IsValidGamePath (string path)
|
||||
{
|
||||
var baseGameIsDirectory = !string.IsNullOrWhiteSpace(path) &&
|
||||
path.IndexOfAny(Utilities.DirectorySeparatorChars) != -1;
|
||||
|
||||
bool baseGameIsDirectory = !string.IsNullOrWhiteSpace(logInfo.BaseGameDirectory) &&
|
||||
logInfo.BaseGameDirectory.IndexOfAny(Utilities.DirectorySeparatorChars) != -1;
|
||||
var baseGameIsRelative = path.FixDirectoryCharacters()
|
||||
.Equals(logInfo.GameDirectory.FixDirectoryCharacters(), StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
bool baseGameIsRelative = logInfo.BaseGameDirectory.FixDirectoryCharacters()
|
||||
.Equals(logInfo.GameDirectory.FixDirectoryCharacters(), StringComparison.InvariantCultureIgnoreCase);
|
||||
return baseGameIsDirectory && !baseGameIsRelative;
|
||||
}
|
||||
|
||||
// we want to see if base game is provided and it 'looks' like a directory
|
||||
if (baseGameIsDirectory && !baseGameIsRelative)
|
||||
if (IsValidGamePath(logInfo.HomePathDirectory))
|
||||
{
|
||||
workingDirectory = logInfo.HomePathDirectory;
|
||||
}
|
||||
|
||||
else if (IsValidGamePath(logInfo.BaseGameDirectory))
|
||||
{
|
||||
workingDirectory = logInfo.BaseGameDirectory;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(logInfo.ModDirectory))
|
||||
if (string.IsNullOrWhiteSpace(logInfo.ModDirectory) || logInfo.IsOneLog)
|
||||
{
|
||||
logPath = Path.Combine(workingDirectory, logInfo.GameDirectory, logInfo.LogFile);
|
||||
}
|
||||
@ -1259,12 +1338,9 @@ namespace IW4MAdmin
|
||||
public override async Task Warn(string reason, EFClient targetClient, EFClient targetOrigin)
|
||||
{
|
||||
// ensure player gets warned if command not performed on them in game
|
||||
targetClient = targetClient.ClientNumber < 0 ?
|
||||
Manager.GetActiveClients()
|
||||
.FirstOrDefault(c => c.ClientId == targetClient?.ClientId) ?? targetClient :
|
||||
targetClient;
|
||||
var activeClient = Manager.FindActiveClient(targetClient);
|
||||
|
||||
var newPenalty = new EFPenalty()
|
||||
var newPenalty = new EFPenalty
|
||||
{
|
||||
Type = EFPenalty.PenaltyType.Warning,
|
||||
Expires = DateTime.UtcNow,
|
||||
@ -1274,31 +1350,28 @@ namespace IW4MAdmin
|
||||
Link = targetClient.AliasLink
|
||||
};
|
||||
|
||||
ServerLogger.LogDebug("Creating warn penalty for {targetClient}", targetClient.ToString());
|
||||
ServerLogger.LogDebug("Creating warn penalty for {TargetClient}", targetClient.ToString());
|
||||
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), ServerLogger);
|
||||
|
||||
if (targetClient.IsIngame)
|
||||
if (activeClient.IsIngame)
|
||||
{
|
||||
if (targetClient.Warnings >= 4)
|
||||
if (activeClient.Warnings >= 4)
|
||||
{
|
||||
targetClient.Kick(loc["SERVER_WARNLIMT_REACHED"], Utilities.IW4MAdminClient(this));
|
||||
activeClient.Kick(loc["SERVER_WARNLIMT_REACHED"], Utilities.IW4MAdminClient(this));
|
||||
return;
|
||||
}
|
||||
|
||||
// todo: move to translation sheet
|
||||
string message = $"^1{loc["SERVER_WARNING"]} ^7[^3{targetClient.Warnings}^7]: ^3{targetClient.Name}^7, {reason}";
|
||||
targetClient.CurrentServer.Broadcast(message);
|
||||
var message = loc["COMMANDS_WARNING_FORMAT_V2"]
|
||||
.FormatExt(activeClient.Warnings, activeClient.Name, reason);
|
||||
activeClient.CurrentServer.Broadcast(message);
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task Kick(string reason, EFClient targetClient, EFClient originClient, EFPenalty previousPenalty)
|
||||
{
|
||||
targetClient = targetClient.ClientNumber < 0 ?
|
||||
Manager.GetActiveClients()
|
||||
.FirstOrDefault(c => c.ClientId == targetClient?.ClientId) ?? targetClient :
|
||||
targetClient;
|
||||
var activeClient = Manager.FindActiveClient(targetClient);
|
||||
|
||||
var newPenalty = new EFPenalty()
|
||||
var newPenalty = new EFPenalty
|
||||
{
|
||||
Type = EFPenalty.PenaltyType.Kick,
|
||||
Expires = DateTime.UtcNow,
|
||||
@ -1308,132 +1381,115 @@ namespace IW4MAdmin
|
||||
Link = targetClient.AliasLink
|
||||
};
|
||||
|
||||
ServerLogger.LogDebug("Creating kick penalty for {targetClient}", targetClient.ToString());
|
||||
ServerLogger.LogDebug("Creating kick penalty for {TargetClient}", targetClient.ToString());
|
||||
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), ServerLogger);
|
||||
|
||||
if (targetClient.IsIngame)
|
||||
if (activeClient.IsIngame)
|
||||
{
|
||||
var e = new GameEvent()
|
||||
var gameEvent = new GameEvent
|
||||
{
|
||||
Type = GameEvent.EventType.PreDisconnect,
|
||||
Origin = targetClient,
|
||||
Origin = activeClient,
|
||||
Owner = this
|
||||
};
|
||||
|
||||
Manager.AddEvent(e);
|
||||
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
|
||||
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
|
||||
Manager.AddEvent(gameEvent);
|
||||
|
||||
var formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
clientNumber,
|
||||
activeClient.TemporalClientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration,
|
||||
newPenalty,
|
||||
previousPenalty));
|
||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||
ServerLogger.LogDebug("Executing tempban kick command for {ActiveClient}", activeClient.ToString());
|
||||
await activeClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task TempBan(string Reason, TimeSpan length, EFClient targetClient, EFClient originClient)
|
||||
public override async Task TempBan(string reason, TimeSpan length, EFClient targetClient, EFClient originClient)
|
||||
{
|
||||
// ensure player gets kicked if command not performed on them in the same server
|
||||
targetClient = targetClient.ClientNumber < 0 ?
|
||||
Manager.GetActiveClients()
|
||||
.FirstOrDefault(c => c.ClientId == targetClient?.ClientId) ?? targetClient :
|
||||
targetClient;
|
||||
var activeClient = Manager.FindActiveClient(targetClient);
|
||||
|
||||
var newPenalty = new EFPenalty()
|
||||
var newPenalty = new EFPenalty
|
||||
{
|
||||
Type = EFPenalty.PenaltyType.TempBan,
|
||||
Expires = DateTime.UtcNow + length,
|
||||
Offender = targetClient,
|
||||
Offense = Reason,
|
||||
Offense = reason,
|
||||
Punisher = originClient,
|
||||
Link = targetClient.AliasLink
|
||||
};
|
||||
|
||||
ServerLogger.LogDebug("Creating tempban penalty for {targetClient}", targetClient.ToString());
|
||||
ServerLogger.LogDebug("Creating tempban penalty for {TargetClient}", targetClient.ToString());
|
||||
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), ServerLogger);
|
||||
|
||||
if (targetClient.IsIngame)
|
||||
if (activeClient.IsIngame)
|
||||
{
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
|
||||
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
|
||||
|
||||
var formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
clientNumber,
|
||||
activeClient.TemporalClientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||
ServerLogger.LogDebug("Executing tempban kick command for {targetClient}", targetClient.ToString());
|
||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||
ServerLogger.LogDebug("Executing tempban kick command for {ActiveClient}", activeClient.ToString());
|
||||
await activeClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
targetClient = targetClient.ClientNumber < 0 ?
|
||||
Manager.GetActiveClients()
|
||||
.FirstOrDefault(c => c.ClientId == targetClient?.ClientId) ?? targetClient :
|
||||
targetClient;
|
||||
var activeClient = Manager.FindActiveClient(targetClient);
|
||||
|
||||
EFPenalty newPenalty = new EFPenalty()
|
||||
var newPenalty = new EFPenalty
|
||||
{
|
||||
Type = EFPenalty.PenaltyType.Ban,
|
||||
Expires = null,
|
||||
Offender = targetClient,
|
||||
Offense = reason,
|
||||
Punisher = originClient,
|
||||
Link = targetClient.AliasLink,
|
||||
IsEvadedOffense = isEvade
|
||||
};
|
||||
|
||||
ServerLogger.LogDebug("Creating ban penalty for {targetClient}", targetClient.ToString());
|
||||
targetClient.SetLevel(Permission.Banned, originClient);
|
||||
ServerLogger.LogDebug("Creating ban penalty for {TargetClient}", targetClient.ToString());
|
||||
activeClient.SetLevel(Permission.Banned, originClient);
|
||||
await newPenalty.TryCreatePenalty(Manager.GetPenaltyService(), ServerLogger);
|
||||
|
||||
if (targetClient.IsIngame)
|
||||
if (activeClient.IsIngame)
|
||||
{
|
||||
ServerLogger.LogDebug("Attempting to kicking newly banned client {targetClient}", targetClient.ToString());
|
||||
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
|
||||
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
|
||||
ServerLogger.LogDebug("Attempting to kicking newly banned client {ActiveClient}", activeClient.ToString());
|
||||
|
||||
var formattedString = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
clientNumber,
|
||||
activeClient.TemporalClientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedString);
|
||||
await activeClient.CurrentServer.ExecuteCommandAsync(formattedString);
|
||||
}
|
||||
}
|
||||
|
||||
override public async Task Unban(string reason, EFClient Target, EFClient Origin)
|
||||
public override async Task Unban(string reason, EFClient targetClient, EFClient originClient)
|
||||
{
|
||||
var unbanPenalty = new EFPenalty()
|
||||
var unbanPenalty = new EFPenalty
|
||||
{
|
||||
Type = EFPenalty.PenaltyType.Unban,
|
||||
Expires = DateTime.Now,
|
||||
Offender = Target,
|
||||
Offender = targetClient,
|
||||
Offense = reason,
|
||||
Punisher = Origin,
|
||||
Punisher = originClient,
|
||||
When = DateTime.UtcNow,
|
||||
Active = true,
|
||||
Link = Target.AliasLink
|
||||
Link = targetClient.AliasLink
|
||||
};
|
||||
|
||||
ServerLogger.LogDebug("Creating unban penalty for {targetClient}", Target.ToString());
|
||||
Target.SetLevel(Permission.User, Origin);
|
||||
await Manager.GetPenaltyService().RemoveActivePenalties(Target.AliasLink.AliasLinkId);
|
||||
ServerLogger.LogDebug("Creating unban penalty for {targetClient}", targetClient.ToString());
|
||||
targetClient.SetLevel(Permission.User, originClient);
|
||||
await Manager.GetPenaltyService().RemoveActivePenalties(targetClient.AliasLink.AliasLinkId,
|
||||
targetClient.NetworkId, targetClient.CurrentAlias?.IPAddress);
|
||||
await Manager.GetPenaltyService().Create(unbanPenalty);
|
||||
}
|
||||
|
||||
override public void InitializeTokens()
|
||||
public override void InitializeTokens()
|
||||
{
|
||||
Manager.GetMessageTokens().Add(new MessageToken("TOTALPLAYERS", (Server s) => Task.Run(async () => (await Manager.GetClientService().GetTotalClientsAsync()).ToString())));
|
||||
Manager.GetMessageTokens().Add(new MessageToken("VERSION", (Server s) => Task.FromResult(Application.Program.Version.ToString())));
|
||||
Manager.GetMessageTokens().Add(new MessageToken("NEXTMAP", (Server s) => SharedLibraryCore.Commands.NextMapCommand.GetNextMap(s, _translationLookup)));
|
||||
Manager.GetMessageTokens().Add(new MessageToken("ADMINS", (Server s) => Task.FromResult(SharedLibraryCore.Commands.ListAdminsCommand.OnlineAdmins(s, _translationLookup))));
|
||||
Manager.GetMessageTokens().Add(new MessageToken("ADMINS", (Server s) => Task.FromResult(ListAdminsCommand.OnlineAdmins(s, _translationLookup))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ using SharedLibraryCore.Repositories;
|
||||
using SharedLibraryCore.Services;
|
||||
using Stats.Dtos;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -33,16 +35,17 @@ using IW4MAdmin.Plugins.Stats.Client.Abstractions;
|
||||
using IW4MAdmin.Plugins.Stats.Client;
|
||||
using Stats.Client.Abstractions;
|
||||
using Stats.Client;
|
||||
using Stats.Config;
|
||||
using Stats.Helpers;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static BuildNumber Version { get; private set; } = BuildNumber.Parse(Utilities.GetVersionAsString());
|
||||
public static ApplicationManager ServerManager;
|
||||
private static Task ApplicationTask;
|
||||
private static ServiceProvider serviceProvider;
|
||||
public static BuildNumber Version { get; } = BuildNumber.Parse(Utilities.GetVersionAsString());
|
||||
private static ApplicationManager _serverManager;
|
||||
private static Task _applicationTask;
|
||||
private static ServiceProvider _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// entrypoint of the application
|
||||
@ -55,7 +58,7 @@ namespace IW4MAdmin.Application
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
|
||||
Console.CancelKeyPress += OnCancelKey;
|
||||
|
||||
Console.WriteLine("=====================================================");
|
||||
Console.WriteLine(" IW4MAdmin");
|
||||
@ -74,10 +77,10 @@ namespace IW4MAdmin.Application
|
||||
/// <param name="e"></param>
|
||||
private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e)
|
||||
{
|
||||
ServerManager?.Stop();
|
||||
if (ApplicationTask != null)
|
||||
_serverManager?.Stop();
|
||||
if (_applicationTask != null)
|
||||
{
|
||||
await ApplicationTask;
|
||||
await _applicationTask;
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,9 +94,8 @@ namespace IW4MAdmin.Application
|
||||
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);
|
||||
|
||||
logger.LogInformation("Begin IW4MAdmin startup. Version is {Version} {@Args}", Version, args);
|
||||
|
||||
try
|
||||
{
|
||||
// do any needed housekeeping file/folder migrations
|
||||
@ -101,22 +103,30 @@ namespace IW4MAdmin.Application
|
||||
ConfigurationMigration.CheckDirectories();
|
||||
ConfigurationMigration.RemoveObsoletePlugins20210322();
|
||||
logger.LogDebug("Configuring services...");
|
||||
services = ConfigureServices(args);
|
||||
serviceProvider = services.BuildServiceProvider();
|
||||
var versionChecker = serviceProvider.GetRequiredService<IMasterCommunication>();
|
||||
ServerManager = (ApplicationManager) serviceProvider.GetRequiredService<IManager>();
|
||||
translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
|
||||
var services = await ConfigureServices(args);
|
||||
_serviceProvider = services.BuildServiceProvider();
|
||||
var versionChecker = _serviceProvider.GetRequiredService<IMasterCommunication>();
|
||||
_serverManager = (ApplicationManager) _serviceProvider.GetRequiredService<IManager>();
|
||||
translationLookup = _serviceProvider.GetRequiredService<ITranslationLookup>();
|
||||
|
||||
await versionChecker.CheckVersion();
|
||||
await ServerManager.Init();
|
||||
_applicationTask = RunApplicationTasksAsync(logger, services);
|
||||
var tasks = new[]
|
||||
{
|
||||
versionChecker.CheckVersion(),
|
||||
_applicationTask
|
||||
};
|
||||
|
||||
await _serverManager.Init();
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
string failMessage = translationLookup == null
|
||||
var failMessage = translationLookup == null
|
||||
? "Failed to initialize IW4MAdmin"
|
||||
: translationLookup["MANAGER_INIT_FAIL"];
|
||||
string exitMessage = translationLookup == null
|
||||
var exitMessage = translationLookup == null
|
||||
? "Press enter to exit..."
|
||||
: translationLookup["MANAGER_EXIT"];
|
||||
|
||||
@ -130,13 +140,10 @@ namespace IW4MAdmin.Application
|
||||
|
||||
if (e is ConfigurationException configException)
|
||||
{
|
||||
if (translationLookup != null)
|
||||
{
|
||||
Console.WriteLine(translationLookup[configException.Message]
|
||||
.FormatExt(configException.ConfigurationFileName));
|
||||
}
|
||||
Console.WriteLine("{{fileName}} contains an error."
|
||||
.FormatExt(Path.GetFileName(configException.ConfigurationFileName)));
|
||||
|
||||
foreach (string error in configException.Errors)
|
||||
foreach (var error in configException.Errors)
|
||||
{
|
||||
Console.WriteLine(error);
|
||||
}
|
||||
@ -152,27 +159,12 @@ namespace IW4MAdmin.Application
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ApplicationTask = RunApplicationTasksAsync(logger, services);
|
||||
await ApplicationTask;
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogCritical(e, "Failed to launch IW4MAdmin");
|
||||
string failMessage = translationLookup == null
|
||||
? "Failed to launch IW4MAdmin"
|
||||
: translationLookup["MANAGER_INIT_FAIL"];
|
||||
Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}");
|
||||
}
|
||||
|
||||
if (ServerManager.IsRestartRequested)
|
||||
if (_serverManager.IsRestartRequested)
|
||||
{
|
||||
goto restart;
|
||||
}
|
||||
|
||||
serviceProvider.Dispose();
|
||||
await _serviceProvider.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -181,21 +173,26 @@ namespace IW4MAdmin.Application
|
||||
/// <returns></returns>
|
||||
private static async Task RunApplicationTasksAsync(ILogger logger, IServiceCollection services)
|
||||
{
|
||||
var webfrontTask = ServerManager.GetApplicationSettings().Configuration().EnableWebFront
|
||||
? WebfrontCore.Program.Init(ServerManager, serviceProvider, services, ServerManager.CancellationToken)
|
||||
var webfrontTask = _serverManager.GetApplicationSettings().Configuration().EnableWebFront
|
||||
? WebfrontCore.Program.Init(_serverManager, _serviceProvider, services, _serverManager.CancellationToken)
|
||||
: Task.CompletedTask;
|
||||
|
||||
var collectionService = _serviceProvider.GetRequiredService<IServerDataCollector>();
|
||||
|
||||
// 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
|
||||
var inputThread = new Thread(async () => await ReadConsoleInput(logger));
|
||||
async void ReadInput() => await ReadConsoleInput(logger);
|
||||
|
||||
var inputThread = new Thread(ReadInput);
|
||||
inputThread.Start();
|
||||
|
||||
var tasks = new[]
|
||||
{
|
||||
ServerManager.Start(),
|
||||
webfrontTask,
|
||||
serviceProvider.GetRequiredService<IMasterCommunication>()
|
||||
.RunUploadStatus(ServerManager.CancellationToken)
|
||||
_serverManager.Start(),
|
||||
_serviceProvider.GetRequiredService<IMasterCommunication>()
|
||||
.RunUploadStatus(_serverManager.CancellationToken),
|
||||
collectionService.BeginCollectionAsync(cancellationToken: _serverManager.CancellationToken)
|
||||
};
|
||||
|
||||
logger.LogDebug("Starting webfront and input tasks");
|
||||
@ -204,8 +201,7 @@ namespace IW4MAdmin.Application
|
||||
logger.LogInformation("Shutdown completed successfully");
|
||||
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_SHUTDOWN_SUCCESS"]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// reads input from the console and executes entered commands on the default server
|
||||
/// </summary>
|
||||
@ -218,32 +214,41 @@ namespace IW4MAdmin.Application
|
||||
return;
|
||||
}
|
||||
|
||||
string lastCommand;
|
||||
var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
|
||||
EFClient origin = null;
|
||||
|
||||
try
|
||||
{
|
||||
while (!ServerManager.CancellationToken.IsCancellationRequested)
|
||||
while (!_serverManager.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
lastCommand = await Console.In.ReadLineAsync();
|
||||
|
||||
if (lastCommand?.Length > 0)
|
||||
if (!_serverManager.IsInitialized)
|
||||
{
|
||||
if (lastCommand?.Length > 0)
|
||||
{
|
||||
GameEvent E = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Command,
|
||||
Data = lastCommand,
|
||||
Origin = Origin,
|
||||
Owner = ServerManager.Servers[0]
|
||||
};
|
||||
|
||||
ServerManager.AddEvent(E);
|
||||
await E.WaitAsync(Utilities.DefaultCommandTimeout, ServerManager.CancellationToken);
|
||||
Console.Write('>');
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
var lastCommand = await Console.In.ReadLineAsync();
|
||||
|
||||
if (lastCommand == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!lastCommand.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var gameEvent = new GameEvent
|
||||
{
|
||||
Type = GameEvent.EventType.Command,
|
||||
Data = lastCommand,
|
||||
Origin = origin ??= Utilities.IW4MAdminClient(_serverManager.Servers.FirstOrDefault()),
|
||||
Owner = _serverManager.Servers[0]
|
||||
};
|
||||
|
||||
_serverManager.AddEvent(gameEvent);
|
||||
await gameEvent.WaitAsync(Utilities.DefaultCommandTimeout, _serverManager.CancellationToken);
|
||||
Console.Write('>');
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@ -271,9 +276,10 @@ namespace IW4MAdmin.Application
|
||||
|
||||
// register the native commands
|
||||
foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
|
||||
.Where(_command => _command.BaseType == typeof(Command)))
|
||||
.Concat(typeof(Program).Assembly.GetTypes().Where(type => type.Namespace == "IW4MAdmin.Application.Commands"))
|
||||
.Where(command => command.BaseType == typeof(Command)))
|
||||
{
|
||||
defaultLogger.LogDebug("Registered native command type {name}", commandType.Name);
|
||||
defaultLogger.LogDebug("Registered native command type {Name}", commandType.Name);
|
||||
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
|
||||
}
|
||||
|
||||
@ -281,40 +287,40 @@ namespace IW4MAdmin.Application
|
||||
var (plugins, commands, configurations) = pluginImporter.DiscoverAssemblyPluginImplementations();
|
||||
foreach (var pluginType in plugins)
|
||||
{
|
||||
defaultLogger.LogDebug("Registered plugin type {name}", pluginType.FullName);
|
||||
defaultLogger.LogDebug("Registered plugin type {Name}", pluginType.FullName);
|
||||
serviceCollection.AddSingleton(typeof(IPlugin), pluginType);
|
||||
}
|
||||
|
||||
// register the plugin commands
|
||||
foreach (var commandType in commands)
|
||||
{
|
||||
defaultLogger.LogDebug("Registered plugin command type {name}", commandType.FullName);
|
||||
defaultLogger.LogDebug("Registered plugin command type {Name}", commandType.FullName);
|
||||
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
|
||||
}
|
||||
|
||||
foreach (var configurationType in configurations)
|
||||
{
|
||||
defaultLogger.LogDebug("Registered plugin config type {name}", configurationType.Name);
|
||||
defaultLogger.LogDebug("Registered plugin config type {Name}", configurationType.Name);
|
||||
var configInstance = (IBaseConfiguration) Activator.CreateInstance(configurationType);
|
||||
var handlerType = typeof(BaseConfigurationHandler<>).MakeGenericType(configurationType);
|
||||
var handlerInstance = Activator.CreateInstance(handlerType, new[] {configInstance.Name()});
|
||||
var handlerInstance = Activator.CreateInstance(handlerType, configInstance.Name());
|
||||
var genericInterfaceType = typeof(IConfigurationHandler<>).MakeGenericType(configurationType);
|
||||
|
||||
serviceCollection.AddSingleton(genericInterfaceType, handlerInstance);
|
||||
}
|
||||
|
||||
// register any script plugins
|
||||
foreach (var scriptPlugin in pluginImporter.DiscoverScriptPlugins())
|
||||
foreach (var plugin in pluginImporter.DiscoverScriptPlugins())
|
||||
{
|
||||
serviceCollection.AddSingleton(scriptPlugin);
|
||||
serviceCollection.AddSingleton(plugin);
|
||||
}
|
||||
|
||||
// register any eventable types
|
||||
foreach (var assemblyType in typeof(Program).Assembly.GetTypes()
|
||||
.Where(_asmType => typeof(IRegisterEvent).IsAssignableFrom(_asmType))
|
||||
.Union(plugins.SelectMany(_asm => _asm.Assembly.GetTypes())
|
||||
.Where(asmType => typeof(IRegisterEvent).IsAssignableFrom(asmType))
|
||||
.Union(plugins.SelectMany(asm => asm.Assembly.GetTypes())
|
||||
.Distinct()
|
||||
.Where(_asmType => typeof(IRegisterEvent).IsAssignableFrom(_asmType))))
|
||||
.Where(asmType => typeof(IRegisterEvent).IsAssignableFrom(asmType))))
|
||||
{
|
||||
var instance = Activator.CreateInstance(assemblyType) as IRegisterEvent;
|
||||
serviceCollection.AddSingleton(instance);
|
||||
@ -327,23 +333,39 @@ namespace IW4MAdmin.Application
|
||||
/// <summary>
|
||||
/// Configures the dependency injection services
|
||||
/// </summary>
|
||||
private static IServiceCollection ConfigureServices(string[] args)
|
||||
private static async Task<IServiceCollection> ConfigureServices(string[] args)
|
||||
{
|
||||
// todo: this is a quick fix
|
||||
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
||||
|
||||
// setup the static resources (config/master api/translations)
|
||||
var serviceCollection = new ServiceCollection();
|
||||
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
||||
await appConfigHandler.BuildAsync();
|
||||
var defaultConfigHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings");
|
||||
await defaultConfigHandler.BuildAsync();
|
||||
var commandConfigHandler = new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration");
|
||||
await commandConfigHandler.BuildAsync();
|
||||
var statsCommandHandler = new BaseConfigurationHandler<StatsConfiguration>();
|
||||
await statsCommandHandler.BuildAsync();
|
||||
var defaultConfig = defaultConfigHandler.Configuration();
|
||||
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 httpClient = new HttpClient
|
||||
{
|
||||
BaseAddress = masterUri,
|
||||
Timeout = TimeSpan.FromSeconds(15)
|
||||
};
|
||||
var masterRestClient = RestClient.For<IMasterApi>(httpClient);
|
||||
var translationLookup = Configure.Initialize(Utilities.DefaultLogger, masterRestClient, appConfig);
|
||||
|
||||
if (appConfig == null)
|
||||
{
|
||||
appConfig = (ApplicationConfiguration) new ApplicationConfiguration().Generate();
|
||||
appConfigHandler.Set(appConfig);
|
||||
appConfigHandler.Save();
|
||||
await appConfigHandler.Save();
|
||||
}
|
||||
|
||||
// register override level names
|
||||
@ -360,15 +382,15 @@ namespace IW4MAdmin.Application
|
||||
|
||||
serviceCollection
|
||||
.AddBaseLogger(appConfig)
|
||||
.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
|
||||
.AddSingleton(defaultConfig)
|
||||
.AddSingleton<IServiceCollection>(serviceCollection)
|
||||
.AddSingleton<IConfigurationHandler<DefaultSettings>, BaseConfigurationHandler<DefaultSettings>>()
|
||||
.AddSingleton((IConfigurationHandler<ApplicationConfiguration>) appConfigHandler)
|
||||
.AddSingleton(
|
||||
new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as
|
||||
IConfigurationHandler<CommandConfiguration>)
|
||||
.AddSingleton<IConfigurationHandler<CommandConfiguration>>(commandConfigHandler)
|
||||
.AddSingleton(appConfig)
|
||||
.AddSingleton(_serviceProvider =>
|
||||
_serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>()
|
||||
.AddSingleton(statsCommandHandler.Configuration() ?? new StatsConfiguration())
|
||||
.AddSingleton(serviceProvider =>
|
||||
serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>()
|
||||
.Configuration() ?? new CommandConfiguration())
|
||||
.AddSingleton<IPluginImporter, PluginImporter>()
|
||||
.AddSingleton<IMiddlewareActionHandler, MiddlewareActionHandler>()
|
||||
@ -394,11 +416,15 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>,
|
||||
UpdatedAliasResourceQueryHelper>()
|
||||
.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>()
|
||||
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>, ConnectionsResourceQueryHelper>()
|
||||
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, PermissionLevelChangedResponse>, PermissionLevelChangedResourceQueryHelper>()
|
||||
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
||||
.AddSingleton<IRemoteAssemblyHandler, RemoteAssemblyHandler>()
|
||||
.AddSingleton<IMasterCommunication, MasterCommunication>()
|
||||
.AddSingleton<IManager, ApplicationManager>()
|
||||
#pragma warning disable CS0612
|
||||
.AddSingleton<SharedLibraryCore.Interfaces.ILogger, Logger>()
|
||||
#pragma warning restore CS0612
|
||||
.AddSingleton<IClientNoticeMessageFormatter, ClientNoticeMessageFormatter>()
|
||||
.AddSingleton<IClientStatisticCalculator, HitCalculator>()
|
||||
.AddSingleton<IServerDistributionCalculator, ServerDistributionCalculator>()
|
||||
@ -406,6 +432,10 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton<IHitInfoBuilder, HitInfoBuilder>()
|
||||
.AddSingleton(typeof(ILookupCache<>), typeof(LookupCache<>))
|
||||
.AddSingleton(typeof(IDataValueCache<,>), typeof(DataValueCache<,>))
|
||||
.AddSingleton<IServerDataViewer, ServerDataViewer>()
|
||||
.AddSingleton<IServerDataCollector, ServerDataCollector>()
|
||||
.AddSingleton<IEventPublisher, EventPublisher>()
|
||||
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
||||
.AddSingleton(translationLookup)
|
||||
.AddDatabaseContextOptions(appConfig);
|
||||
|
||||
@ -432,4 +462,4 @@ namespace IW4MAdmin.Application
|
||||
return collection.GetRequiredService<ILogger<T>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
60
Application/Meta/ConnectionsResourceQueryHelper.cs
Normal file
60
Application/Meta/ConnectionsResourceQueryHelper.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.QueryHelper;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.Meta
|
||||
{
|
||||
public class
|
||||
ConnectionsResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
|
||||
public ConnectionsResourceQueryHelper(ILogger<ConnectionsResourceQueryHelper> logger,
|
||||
IDatabaseContextFactory contextFactory)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<ResourceQueryHelperResult<ConnectionHistoryResponse>> QueryResource(
|
||||
ClientPaginationRequest query)
|
||||
{
|
||||
_logger.LogDebug("{Class} {@Request}", nameof(ConnectionsResourceQueryHelper), query);
|
||||
|
||||
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||
|
||||
var iqConnections = context.ConnectionHistory.AsNoTracking()
|
||||
.Where(history => query.ClientId == history.ClientId)
|
||||
.Where(history => history.CreatedDateTime < query.Before)
|
||||
.OrderByDescending(history => history.CreatedDateTime);
|
||||
|
||||
var connections = await iqConnections.Select(history => new ConnectionHistoryResponse
|
||||
{
|
||||
MetaId = history.ClientConnectionId,
|
||||
ClientId = history.ClientId,
|
||||
Type = MetaType.ConnectionHistory,
|
||||
ShouldDisplay = true,
|
||||
When = history.CreatedDateTime,
|
||||
ServerName = history.Server.HostName,
|
||||
ConnectionType = history.ConnectionType
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
_logger.LogDebug("{Class} retrieved {Number} items", nameof(ConnectionsResourceQueryHelper),
|
||||
connections.Count);
|
||||
|
||||
return new ResourceQueryHelperResult<ConnectionHistoryResponse>
|
||||
{
|
||||
Results = connections
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -20,11 +20,18 @@ namespace IW4MAdmin.Application.Meta
|
||||
private readonly IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> _receivedPenaltyHelper;
|
||||
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> _administeredPenaltyHelper;
|
||||
private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper;
|
||||
private readonly IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>
|
||||
_connectionHistoryHelper;
|
||||
private readonly IResourceQueryHelper<ClientPaginationRequest, PermissionLevelChangedResponse>
|
||||
_permissionLevelHelper;
|
||||
|
||||
public MetaRegistration(ILogger<MetaRegistration> 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, AdministeredPenaltyResponse> administeredPenaltyHelper,
|
||||
IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper)
|
||||
IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper,
|
||||
IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse> connectionHistoryHelper,
|
||||
IResourceQueryHelper<ClientPaginationRequest, PermissionLevelChangedResponse> permissionLevelHelper)
|
||||
{
|
||||
_logger = logger;
|
||||
_transLookup = transLookup;
|
||||
@ -33,6 +40,8 @@ namespace IW4MAdmin.Application.Meta
|
||||
_receivedPenaltyHelper = receivedPenaltyHelper;
|
||||
_administeredPenaltyHelper = administeredPenaltyHelper;
|
||||
_updatedAliasHelper = updatedAliasHelper;
|
||||
_connectionHistoryHelper = connectionHistoryHelper;
|
||||
_permissionLevelHelper = permissionLevelHelper;
|
||||
}
|
||||
|
||||
public void Register()
|
||||
@ -41,6 +50,9 @@ namespace IW4MAdmin.Application.Meta
|
||||
_metaService.AddRuntimeMeta<ClientPaginationRequest, ReceivedPenaltyResponse>(MetaType.ReceivedPenalty, GetReceivedPenaltiesMeta);
|
||||
_metaService.AddRuntimeMeta<ClientPaginationRequest, AdministeredPenaltyResponse>(MetaType.Penalized, GetAdministeredPenaltiesMeta);
|
||||
_metaService.AddRuntimeMeta<ClientPaginationRequest, UpdatedAliasResponse>(MetaType.AliasUpdate, GetUpdatedAliasMeta);
|
||||
_metaService.AddRuntimeMeta<ClientPaginationRequest, ConnectionHistoryResponse>(MetaType.ConnectionHistory, GetConnectionHistoryMeta);
|
||||
_metaService.AddRuntimeMeta<ClientPaginationRequest, PermissionLevelChangedResponse>(
|
||||
MetaType.PermissionLevel, GetPermissionLevelMeta);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<InformationResponse>> GetProfileMeta(ClientPaginationRequest request)
|
||||
@ -163,5 +175,18 @@ namespace IW4MAdmin.Application.Meta
|
||||
var aliases = await _updatedAliasHelper.QueryResource(request);
|
||||
return aliases.Results;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<ConnectionHistoryResponse>> GetConnectionHistoryMeta(ClientPaginationRequest request)
|
||||
{
|
||||
var connections = await _connectionHistoryHelper.QueryResource(request);
|
||||
return connections.Results;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<PermissionLevelChangedResponse>> GetPermissionLevelMeta(
|
||||
ClientPaginationRequest request)
|
||||
{
|
||||
var permissionChanges = await _permissionLevelHelper.QueryResource(request);
|
||||
return permissionChanges.Results;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.QueryHelper;
|
||||
|
||||
namespace IW4MAdmin.Application.Meta;
|
||||
|
||||
public class
|
||||
PermissionLevelChangedResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest,
|
||||
PermissionLevelChangedResponse>
|
||||
{
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
|
||||
public PermissionLevelChangedResourceQueryHelper(IDatabaseContextFactory contextFactory)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
}
|
||||
|
||||
public async Task<ResourceQueryHelperResult<PermissionLevelChangedResponse>> QueryResource(
|
||||
ClientPaginationRequest query)
|
||||
{
|
||||
await using var context = _contextFactory.CreateContext();
|
||||
|
||||
var auditEntries = context.EFChangeHistory.Where(change => change.TargetEntityId == query.ClientId)
|
||||
.Where(change => change.TypeOfChange == EFChangeHistory.ChangeType.Permission);
|
||||
|
||||
var audits = from change in auditEntries
|
||||
join client in context.Clients
|
||||
on change.OriginEntityId equals client.ClientId
|
||||
select new PermissionLevelChangedResponse
|
||||
{
|
||||
ChangedById = change.OriginEntityId,
|
||||
ChangedByName = client.CurrentAlias.Name,
|
||||
PreviousPermissionLevelValue = change.PreviousValue,
|
||||
CurrentPermissionLevelValue = change.CurrentValue,
|
||||
When = change.TimeChanged,
|
||||
ClientId = change.TargetEntityId,
|
||||
IsSensitive = true
|
||||
};
|
||||
|
||||
return new ResourceQueryHelperResult<PermissionLevelChangedResponse>
|
||||
{
|
||||
Results = await audits.Skip(query.Offset).Take(query.Count).ToListAsync()
|
||||
};
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
@ -21,11 +23,14 @@ namespace IW4MAdmin.Application.Meta
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
|
||||
public ReceivedPenaltyResourceQueryHelper(ILogger<ReceivedPenaltyResourceQueryHelper> logger, IDatabaseContextFactory contextFactory)
|
||||
public ReceivedPenaltyResourceQueryHelper(ILogger<ReceivedPenaltyResourceQueryHelper> logger,
|
||||
IDatabaseContextFactory contextFactory, ApplicationConfiguration appConfig)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
_logger = logger;
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
|
||||
public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource(ClientPaginationRequest query)
|
||||
@ -35,15 +40,40 @@ namespace IW4MAdmin.Application.Meta
|
||||
|
||||
var linkId = await ctx.Clients.AsNoTracking()
|
||||
.Where(_client => _client.ClientId == query.ClientId)
|
||||
.Select(_client => _client.AliasLinkId)
|
||||
.Select(_client => new {_client.AliasLinkId, _client.CurrentAliasId })
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
var iqPenalties = ctx.Penalties.AsNoTracking()
|
||||
.Where(_penalty => _penalty.OffenderId == query.ClientId || (linkedPenaltyType.Contains(_penalty.Type) && _penalty.LinkId == linkId))
|
||||
.Where(_penalty => _penalty.When < query.Before)
|
||||
.OrderByDescending(_penalty => _penalty.When);
|
||||
.Where(_penalty => _penalty.OffenderId == query.ClientId ||
|
||||
linkedPenaltyType.Contains(_penalty.Type) && _penalty.LinkId == linkId.AliasLinkId);
|
||||
|
||||
var penalties = await iqPenalties
|
||||
IQueryable<EFPenalty> iqIpLinkedPenalties = null;
|
||||
|
||||
if (!_appConfig.EnableImplicitAccountLinking)
|
||||
{
|
||||
var usedIps = await ctx.Aliases.AsNoTracking()
|
||||
.Where(alias => (alias.LinkId == linkId.AliasLinkId || alias.AliasId == linkId.CurrentAliasId) && alias.IPAddress != null)
|
||||
.Select(alias => alias.IPAddress).ToListAsync();
|
||||
|
||||
var aliasedIds = await ctx.Aliases.AsNoTracking().Where(alias => usedIps.Contains(alias.IPAddress))
|
||||
.Select(alias => alias.LinkId)
|
||||
.ToListAsync();
|
||||
|
||||
iqIpLinkedPenalties = ctx.Penalties.AsNoTracking()
|
||||
.Where(penalty =>
|
||||
linkedPenaltyType.Contains(penalty.Type) && aliasedIds.Contains(penalty.LinkId ?? -1));
|
||||
}
|
||||
|
||||
var iqAllPenalties = iqPenalties;
|
||||
|
||||
if (iqIpLinkedPenalties != null)
|
||||
{
|
||||
iqAllPenalties = iqPenalties.Union(iqIpLinkedPenalties);
|
||||
}
|
||||
|
||||
var penalties = await iqAllPenalties
|
||||
.Where(_penalty => _penalty.When < query.Before)
|
||||
.OrderByDescending(_penalty => _penalty.When)
|
||||
.Take(query.Count)
|
||||
.Select(_penalty => new ReceivedPenaltyResponse()
|
||||
{
|
||||
|
@ -4,8 +4,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Models.Client.Stats;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Migration
|
||||
{
|
||||
|
@ -1,10 +1,13 @@
|
||||
using Newtonsoft.Json;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
@ -14,27 +17,39 @@ namespace IW4MAdmin.Application.Misc
|
||||
/// <typeparam name="T">base configuration type</typeparam>
|
||||
public class BaseConfigurationHandler<T> : IConfigurationHandler<T> where T : IBaseConfiguration
|
||||
{
|
||||
T _configuration;
|
||||
private T _configuration;
|
||||
private readonly SemaphoreSlim _onSaving;
|
||||
private readonly JsonSerializerOptions _serializerOptions;
|
||||
|
||||
public BaseConfigurationHandler(string fn)
|
||||
|
||||
public BaseConfigurationHandler(string fileName)
|
||||
{
|
||||
FileName = Path.Join(Utilities.OperatingDirectory, "Configuration", $"{fn}.json");
|
||||
Build();
|
||||
_serializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
};
|
||||
_serializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
_onSaving = new SemaphoreSlim(1, 1);
|
||||
FileName = Path.Join(Utilities.OperatingDirectory, "Configuration", $"{fileName}.json");
|
||||
}
|
||||
|
||||
public BaseConfigurationHandler() : this(typeof(T).Name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~BaseConfigurationHandler()
|
||||
{
|
||||
_onSaving.Dispose();
|
||||
}
|
||||
|
||||
public string FileName { get; }
|
||||
|
||||
public void Build()
|
||||
public async Task BuildAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var configContent = File.ReadAllText(FileName);
|
||||
_configuration = JsonConvert.DeserializeObject<T>(configContent);
|
||||
await using var fileStream = File.OpenRead(FileName);
|
||||
_configuration = await JsonSerializer.DeserializeAsync<T>(fileStream, _serializerOptions);
|
||||
}
|
||||
|
||||
catch (FileNotFoundException)
|
||||
@ -44,7 +59,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ConfigurationException("MANAGER_CONFIGURATION_ERROR")
|
||||
throw new ConfigurationException("Could not load configuration")
|
||||
{
|
||||
Errors = new[] { e.Message },
|
||||
ConfigurationFileName = FileName
|
||||
@ -52,16 +67,23 @@ namespace IW4MAdmin.Application.Misc
|
||||
}
|
||||
}
|
||||
|
||||
public Task Save()
|
||||
public async Task Save()
|
||||
{
|
||||
var settings = new JsonSerializerSettings()
|
||||
try
|
||||
{
|
||||
Formatting = Formatting.Indented
|
||||
};
|
||||
settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
|
||||
await _onSaving.WaitAsync();
|
||||
|
||||
var appConfigJSON = JsonConvert.SerializeObject(_configuration, settings);
|
||||
return File.WriteAllTextAsync(FileName, appConfigJSON);
|
||||
await using var fileStream = File.Create(FileName);
|
||||
await JsonSerializer.SerializeAsync(fileStream, _configuration, _serializerOptions);
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (_onSaving.CurrentCount == 0)
|
||||
{
|
||||
_onSaving.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T Configuration()
|
||||
|
44
Application/Misc/EventPublisher.cs
Normal file
44
Application/Misc/EventPublisher.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
public class EventPublisher : IEventPublisher
|
||||
{
|
||||
public event EventHandler<GameEvent> OnClientDisconnect;
|
||||
public event EventHandler<GameEvent> OnClientConnect;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public EventPublisher(ILogger<EventPublisher> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Publish(GameEvent gameEvent)
|
||||
{
|
||||
_logger.LogDebug("Handling publishing event of type {EventType}", gameEvent.Type);
|
||||
|
||||
try
|
||||
{
|
||||
if (gameEvent.Type == GameEvent.EventType.Connect)
|
||||
{
|
||||
OnClientConnect?.Invoke(this, gameEvent);
|
||||
}
|
||||
|
||||
if (gameEvent.Type == GameEvent.EventType.Disconnect)
|
||||
{
|
||||
OnClientDisconnect?.Invoke(this, gameEvent);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not publish event of type {EventType}", gameEvent.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,6 +19,12 @@ namespace IW4MAdmin.Application.Misc
|
||||
/// </summary>
|
||||
public string BasePathDirectory { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// directory for local storage
|
||||
/// <remarks>fs_homepath</remarks>
|
||||
/// </summary>
|
||||
public string HomePathDirectory { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// overide game directory
|
||||
/// <remarks>plugin driven</remarks>
|
||||
@ -41,5 +47,11 @@ namespace IW4MAdmin.Application.Misc
|
||||
/// indicates if running on windows
|
||||
/// </summary>
|
||||
public bool IsWindows { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// indicates that the game does not log to the mods folder (when mod is loaded),
|
||||
/// but rather always to the fs_basegame directory
|
||||
/// </summary>
|
||||
public bool IsOneLog { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ namespace IW4MAdmin.Application.Misc
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
private readonly BuildNumber _fallbackVersion = BuildNumber.Parse("99.99.99.99");
|
||||
private readonly int _apiVersion = 1;
|
||||
private bool firstHeartBeat = true;
|
||||
private bool _firstHeartBeat = true;
|
||||
private static readonly TimeSpan Interval = TimeSpan.FromSeconds(30);
|
||||
|
||||
public MasterCommunication(ILogger<MasterCommunication> logger, ApplicationConfiguration appConfig, ITranslationLookup translationLookup, IMasterApi apiInstance, IManager manager)
|
||||
{
|
||||
@ -93,53 +94,24 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
public async Task RunUploadStatus(CancellationToken token)
|
||||
{
|
||||
// todo: clean up this logic
|
||||
bool connected;
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UploadStatus();
|
||||
}
|
||||
|
||||
catch (System.Net.Http.HttpRequestException e)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not send heartbeat");
|
||||
}
|
||||
|
||||
catch (AggregateException e)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not send heartbeat");
|
||||
var exceptions = e.InnerExceptions.Where(ex => ex.GetType() == typeof(ApiException));
|
||||
|
||||
foreach (var ex in exceptions)
|
||||
if (_manager.IsRunning)
|
||||
{
|
||||
if (((ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
connected = false;
|
||||
}
|
||||
await UploadStatus();
|
||||
}
|
||||
}
|
||||
|
||||
catch (ApiException e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not send heartbeat");
|
||||
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
connected = false;
|
||||
}
|
||||
_logger.LogWarning(ex, "Could not send heartbeat");
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not send heartbeat");
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(30000, token);
|
||||
await Task.Delay(Interval, token);
|
||||
}
|
||||
|
||||
catch
|
||||
@ -151,7 +123,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
private async Task UploadStatus()
|
||||
{
|
||||
if (firstHeartBeat)
|
||||
if (_firstHeartBeat)
|
||||
{
|
||||
var token = await _apiInstance.Authenticate(new AuthenticationId
|
||||
{
|
||||
@ -179,12 +151,13 @@ namespace IW4MAdmin.Application.Misc
|
||||
Id = s.EndPoint,
|
||||
Port = (short)s.Port,
|
||||
IPAddress = s.IP
|
||||
}).ToList()
|
||||
}).ToList(),
|
||||
WebfrontUrl = _appConfig.WebfrontUrl
|
||||
};
|
||||
|
||||
Response<ResultMessage> response = null;
|
||||
|
||||
if (firstHeartBeat)
|
||||
if (_firstHeartBeat)
|
||||
{
|
||||
response = await _apiInstance.AddInstance(instance);
|
||||
}
|
||||
@ -192,7 +165,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
else
|
||||
{
|
||||
response = await _apiInstance.UpdateInstance(instance.Id, instance);
|
||||
firstHeartBeat = false;
|
||||
_firstHeartBeat = false;
|
||||
}
|
||||
|
||||
if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK)
|
||||
|
@ -68,6 +68,29 @@ namespace IW4MAdmin.Application.Misc
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task SetPersistentMeta(string metaKey, string metaValue, int clientId)
|
||||
{
|
||||
await AddPersistentMeta(metaKey, metaValue, new EFClient { ClientId = clientId });
|
||||
}
|
||||
|
||||
public async Task IncrementPersistentMeta(string metaKey, int incrementAmount, int clientId)
|
||||
{
|
||||
var existingMeta = await GetPersistentMeta(metaKey, new EFClient { ClientId = clientId });
|
||||
|
||||
if (!long.TryParse(existingMeta?.Value ?? "0", out var existingValue))
|
||||
{
|
||||
existingValue = 0;
|
||||
}
|
||||
|
||||
var newValue = existingValue + incrementAmount;
|
||||
await SetPersistentMeta(metaKey, newValue.ToString(), clientId);
|
||||
}
|
||||
|
||||
public async Task DecrementPersistentMeta(string metaKey, int decrementAmount, int clientId)
|
||||
{
|
||||
await IncrementPersistentMeta(metaKey, -decrementAmount, clientId);
|
||||
}
|
||||
|
||||
public async Task AddPersistentMeta(string metaKey, string metaValue)
|
||||
{
|
||||
await using var ctx = _contextFactory.CreateContext();
|
||||
@ -207,42 +230,30 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
public async Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request)
|
||||
{
|
||||
var meta = new List<IClientMeta>();
|
||||
var metas = await Task.WhenAll(_metaActions.Where(kvp => kvp.Key != MetaType.Information)
|
||||
.Select(async kvp => await kvp.Value[0](request)));
|
||||
|
||||
foreach (var (type, actions) in _metaActions)
|
||||
{
|
||||
// information is not listed chronologically
|
||||
if (type != MetaType.Information)
|
||||
{
|
||||
var metaItems = await actions[0](request);
|
||||
meta.AddRange(metaItems);
|
||||
}
|
||||
}
|
||||
|
||||
return meta.OrderByDescending(_meta => _meta.When)
|
||||
return metas.SelectMany(m => (IEnumerable<IClientMeta>)m)
|
||||
.OrderByDescending(m => m.When)
|
||||
.Take(request.Count)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta
|
||||
{
|
||||
IEnumerable<T> meta;
|
||||
if (metaType == MetaType.Information)
|
||||
{
|
||||
var allMeta = new List<T>();
|
||||
|
||||
foreach (var individualMetaRegistration in _metaActions[metaType])
|
||||
{
|
||||
allMeta.AddRange(await individualMetaRegistration(request));
|
||||
}
|
||||
|
||||
var completedMeta = await Task.WhenAll(_metaActions[metaType].Select(async individualMetaRegistration =>
|
||||
(IEnumerable<T>)await individualMetaRegistration(request)));
|
||||
|
||||
allMeta.AddRange(completedMeta.SelectMany(meta => meta));
|
||||
|
||||
return ProcessInformationMeta(allMeta);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
meta = await _metaActions[metaType][0](request) as IEnumerable<T>;
|
||||
}
|
||||
var meta = await _metaActions[metaType][0](request) as IEnumerable<T>;
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using SharedLibraryCore.Interfaces;
|
||||
using System.Linq;
|
||||
using SharedLibraryCore;
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
@ -39,24 +40,23 @@ namespace IW4MAdmin.Application.Misc
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IPlugin> DiscoverScriptPlugins()
|
||||
{
|
||||
string pluginDir = $"{Utilities.OperatingDirectory}{PLUGIN_DIR}{Path.DirectorySeparatorChar}";
|
||||
var pluginDir = $"{Utilities.OperatingDirectory}{PLUGIN_DIR}{Path.DirectorySeparatorChar}";
|
||||
|
||||
if (Directory.Exists(pluginDir))
|
||||
if (!Directory.Exists(pluginDir))
|
||||
{
|
||||
var scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts());
|
||||
|
||||
_logger.LogDebug("Discovered {count} potential script plugins", scriptPluginFiles.Count());
|
||||
|
||||
if (scriptPluginFiles.Count() > 0)
|
||||
{
|
||||
foreach (string fileName in scriptPluginFiles)
|
||||
{
|
||||
_logger.LogDebug("Discovered script plugin {fileName}", fileName);
|
||||
var plugin = new ScriptPlugin(_logger, fileName);
|
||||
yield return plugin;
|
||||
}
|
||||
}
|
||||
return Enumerable.Empty<IPlugin>();
|
||||
}
|
||||
|
||||
var scriptPluginFiles =
|
||||
Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts()).ToList();
|
||||
|
||||
_logger.LogDebug("Discovered {count} potential script plugins", scriptPluginFiles.Count);
|
||||
|
||||
return scriptPluginFiles.Select(fileName =>
|
||||
{
|
||||
_logger.LogDebug("Discovered script plugin {fileName}", fileName);
|
||||
return new ScriptPlugin(_logger, fileName);
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -83,19 +83,47 @@ namespace IW4MAdmin.Application.Misc
|
||||
.GroupBy(_assembly => _assembly.FullName).Select(_assembly => _assembly.OrderByDescending(_assembly => _assembly.GetName().Version).First());
|
||||
|
||||
pluginTypes = assemblies
|
||||
.SelectMany(_asm => _asm.GetTypes())
|
||||
.SelectMany(_asm =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return _asm.GetTypes();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Enumerable.Empty<Type>();
|
||||
}
|
||||
})
|
||||
.Where(_assemblyType => _assemblyType.GetInterface(nameof(IPlugin), false) != null);
|
||||
|
||||
_logger.LogDebug("Discovered {count} plugin implementations", pluginTypes.Count());
|
||||
|
||||
commandTypes = assemblies
|
||||
.SelectMany(_asm => _asm.GetTypes())
|
||||
.SelectMany(_asm =>{
|
||||
try
|
||||
{
|
||||
return _asm.GetTypes();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Enumerable.Empty<Type>();
|
||||
}
|
||||
})
|
||||
.Where(_assemblyType => _assemblyType.IsClass && _assemblyType.BaseType == typeof(Command));
|
||||
|
||||
_logger.LogDebug("Discovered {count} plugin commands", commandTypes.Count());
|
||||
|
||||
configurationTypes = assemblies
|
||||
.SelectMany(asm => asm.GetTypes())
|
||||
.SelectMany(asm => {
|
||||
try
|
||||
{
|
||||
return asm.GetTypes();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Enumerable.Empty<Type>();
|
||||
}
|
||||
})
|
||||
.Where(asmType =>
|
||||
asmType.IsClass && asmType.GetInterface(nameof(IBaseConfiguration), false) != null);
|
||||
|
||||
|
@ -6,7 +6,6 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static SharedLibraryCore.Database.Models.EFClient;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
@ -16,14 +15,15 @@ namespace IW4MAdmin.Application.Misc
|
||||
/// </summary>
|
||||
public class ScriptCommand : Command
|
||||
{
|
||||
private readonly Action<GameEvent> _executeAction;
|
||||
private readonly Func<GameEvent, Task> _executeAction;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ScriptCommand(string name, string alias, string description, bool isTargetRequired, EFClient.Permission permission,
|
||||
CommandArgument[] args, Action<GameEvent> executeAction, CommandConfiguration config, ITranslationLookup layout, ILogger<ScriptCommand> logger)
|
||||
public ScriptCommand(string name, string alias, string description, bool isTargetRequired,
|
||||
EFClient.Permission permission,
|
||||
CommandArgument[] args, Func<GameEvent, Task> executeAction, CommandConfiguration config,
|
||||
ITranslationLookup layout, ILogger<ScriptCommand> logger, Server.Game[] supportedGames)
|
||||
: base(config, layout)
|
||||
{
|
||||
|
||||
_executeAction = executeAction;
|
||||
_logger = logger;
|
||||
Name = name;
|
||||
@ -32,6 +32,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
RequiresTarget = isTargetRequired;
|
||||
Permission = permission;
|
||||
Arguments = args;
|
||||
SupportedGames = supportedGames;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent e)
|
||||
@ -43,7 +44,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Run(() => _executeAction(e));
|
||||
await _executeAction(e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -13,6 +13,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jint.Runtime.Interop;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
@ -36,12 +37,12 @@ namespace IW4MAdmin.Application.Misc
|
||||
/// </summary>
|
||||
public bool IsParser { get; private set; }
|
||||
|
||||
public FileSystemWatcher Watcher { get; private set; }
|
||||
public FileSystemWatcher Watcher { get; }
|
||||
|
||||
private Engine _scriptEngine;
|
||||
private readonly string _fileName;
|
||||
private readonly SemaphoreSlim _onProcessing;
|
||||
private bool successfullyLoaded;
|
||||
private readonly SemaphoreSlim _onProcessing = new(1, 1);
|
||||
private bool _successfullyLoaded;
|
||||
private readonly List<string> _registeredCommandNames;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -49,15 +50,14 @@ namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
_logger = logger;
|
||||
_fileName = filename;
|
||||
Watcher = new FileSystemWatcher()
|
||||
Watcher = new FileSystemWatcher
|
||||
{
|
||||
Path = workingDirectory == null ? $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}" : workingDirectory,
|
||||
Path = workingDirectory ?? $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}",
|
||||
NotifyFilter = NotifyFilters.Size,
|
||||
Filter = _fileName.Split(Path.DirectorySeparatorChar).Last()
|
||||
};
|
||||
|
||||
Watcher.EnableRaisingEvents = true;
|
||||
_onProcessing = new SemaphoreSlim(1, 1);
|
||||
_registeredCommandNames = new List<string>();
|
||||
}
|
||||
|
||||
@ -67,12 +67,13 @@ namespace IW4MAdmin.Application.Misc
|
||||
_onProcessing.Dispose();
|
||||
}
|
||||
|
||||
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory, IScriptPluginServiceResolver serviceResolver)
|
||||
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory,
|
||||
IScriptPluginServiceResolver serviceResolver)
|
||||
{
|
||||
await _onProcessing.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
await _onProcessing.WaitAsync();
|
||||
|
||||
// for some reason we get an event trigger when the file is not finished being modified.
|
||||
// this must have been a change in .NET CORE 3.x
|
||||
// so if the new file is empty we can't process it yet
|
||||
@ -81,26 +82,27 @@ namespace IW4MAdmin.Application.Misc
|
||||
return;
|
||||
}
|
||||
|
||||
bool firstRun = _scriptEngine == null;
|
||||
var firstRun = _scriptEngine == null;
|
||||
|
||||
// it's been loaded before so we need to call the unload event
|
||||
if (!firstRun)
|
||||
{
|
||||
await OnUnloadAsync();
|
||||
|
||||
foreach (string commandName in _registeredCommandNames)
|
||||
foreach (var commandName in _registeredCommandNames)
|
||||
{
|
||||
_logger.LogDebug("Removing plugin registered command {command}", commandName);
|
||||
_logger.LogDebug("Removing plugin registered command {Command}", commandName);
|
||||
manager.RemoveCommandByName(commandName);
|
||||
}
|
||||
|
||||
_registeredCommandNames.Clear();
|
||||
}
|
||||
|
||||
successfullyLoaded = false;
|
||||
_successfullyLoaded = false;
|
||||
string script;
|
||||
|
||||
using (var stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
await using (var stream =
|
||||
new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
using (var reader = new StreamReader(stream, Encoding.Default))
|
||||
{
|
||||
@ -110,45 +112,34 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
_scriptEngine = new Engine(cfg =>
|
||||
cfg.AllowClr(new[]
|
||||
{
|
||||
typeof(System.Net.Http.HttpClient).Assembly,
|
||||
typeof(EFClient).Assembly,
|
||||
typeof(Utilities).Assembly,
|
||||
typeof(Encoding).Assembly
|
||||
})
|
||||
.CatchClrExceptions());
|
||||
|
||||
try
|
||||
{
|
||||
_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");
|
||||
}
|
||||
{
|
||||
typeof(System.Net.Http.HttpClient).Assembly,
|
||||
typeof(EFClient).Assembly,
|
||||
typeof(Utilities).Assembly,
|
||||
typeof(Encoding).Assembly
|
||||
})
|
||||
.CatchClrExceptions()
|
||||
.AddObjectConverter(new PermissionLevelToStringConverter()));
|
||||
|
||||
_scriptEngine.Execute(script);
|
||||
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
|
||||
_scriptEngine.SetValue("_serviceResolver", serviceResolver);
|
||||
dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject();
|
||||
_scriptEngine.SetValue("_lock", _onProcessing);
|
||||
dynamic pluginObject = _scriptEngine.Evaluate("plugin").ToObject();
|
||||
|
||||
Author = pluginObject.author;
|
||||
Name = pluginObject.name;
|
||||
Version = (float)pluginObject.version;
|
||||
|
||||
var commands = _scriptEngine.GetValue("commands");
|
||||
var commands = JsValue.Undefined;
|
||||
try
|
||||
{
|
||||
commands = _scriptEngine.Evaluate("commands");
|
||||
}
|
||||
catch (JavaScriptException)
|
||||
{
|
||||
// ignore because commands aren't defined;
|
||||
}
|
||||
|
||||
if (commands != JsValue.Undefined)
|
||||
{
|
||||
@ -156,7 +147,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
foreach (var command in GenerateScriptCommands(commands, scriptCommandFactory))
|
||||
{
|
||||
_logger.LogDebug("Adding plugin registered command {commandName}", command.Name);
|
||||
_logger.LogDebug("Adding plugin registered command {CommandName}", command.Name);
|
||||
manager.AddAdditionalCommand(command);
|
||||
_registeredCommandNames.Add(command.Name);
|
||||
}
|
||||
@ -164,51 +155,110 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
catch (RuntimeBinderException e)
|
||||
{
|
||||
throw new PluginException($"Not all required fields were found: {e.Message}") { PluginFile = _fileName };
|
||||
throw new PluginException($"Not all required fields were found: {e.Message}")
|
||||
{ PluginFile = _fileName };
|
||||
}
|
||||
}
|
||||
|
||||
_scriptEngine.SetValue("_configHandler", new ScriptPluginConfigurationWrapper(Name, _scriptEngine));
|
||||
await OnLoadAsync(manager);
|
||||
|
||||
try
|
||||
{
|
||||
if (pluginObject.isParser)
|
||||
{
|
||||
await OnLoadAsync(manager);
|
||||
IsParser = true;
|
||||
IEventParser eventParser = (IEventParser)_scriptEngine.GetValue("eventParser").ToObject();
|
||||
IRConParser rconParser = (IRConParser)_scriptEngine.GetValue("rconParser").ToObject();
|
||||
var eventParser = (IEventParser)_scriptEngine.Evaluate("eventParser").ToObject();
|
||||
var rconParser = (IRConParser)_scriptEngine.Evaluate("rconParser").ToObject();
|
||||
manager.AdditionalEventParsers.Add(eventParser);
|
||||
manager.AdditionalRConParsers.Add(rconParser);
|
||||
}
|
||||
}
|
||||
|
||||
catch (RuntimeBinderException) { }
|
||||
catch (RuntimeBinderException)
|
||||
{
|
||||
var configWrapper = new ScriptPluginConfigurationWrapper(Name, _scriptEngine);
|
||||
await configWrapper.InitializeAsync();
|
||||
_scriptEngine.SetValue("_configHandler", configWrapper);
|
||||
await OnLoadAsync(manager);
|
||||
}
|
||||
|
||||
if (!firstRun)
|
||||
{
|
||||
await OnLoadAsync(manager);
|
||||
}
|
||||
|
||||
successfullyLoaded = true;
|
||||
_successfullyLoaded = true;
|
||||
}
|
||||
catch (JavaScriptException ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} at {@LocationInfo}",
|
||||
nameof(Initialize), Path.GetFileName(_fileName), ex.Location);
|
||||
|
||||
throw new PluginException("An error occured while initializing script plugin");
|
||||
}
|
||||
catch (Exception ex) when (ex.InnerException is JavaScriptException jsEx)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} initialization {@LocationInfo}",
|
||||
nameof(Initialize), _fileName, jsEx.Location);
|
||||
|
||||
throw new PluginException("An error occured while initializing script plugin");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin}",
|
||||
nameof(OnLoadAsync), Path.GetFileName(_fileName));
|
||||
|
||||
throw new PluginException("An error occured while executing action for script plugin");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0)
|
||||
{
|
||||
_onProcessing.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnEventAsync(GameEvent gameEvent, Server server)
|
||||
{
|
||||
if (!_successfullyLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _onProcessing.WaitAsync();
|
||||
_scriptEngine.SetValue("_gameEvent", gameEvent);
|
||||
_scriptEngine.SetValue("_server", server);
|
||||
_scriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(server));
|
||||
_scriptEngine.Evaluate("plugin.onEventAsync(_gameEvent, _server)");
|
||||
}
|
||||
|
||||
catch (JavaScriptException ex)
|
||||
{
|
||||
_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");
|
||||
using (LogContext.PushProperty("Server", server.ToString()))
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} with event type {EventType} {@LocationInfo}",
|
||||
nameof(OnEventAsync), Path.GetFileName(_fileName), gameEvent.Type, ex.Location);
|
||||
}
|
||||
|
||||
throw new PluginException("An error occured while executing action for 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");
|
||||
using (LogContext.PushProperty("Server", server.ToString()))
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Encountered error while running {MethodName} for script plugin {Plugin} with event type {EventType}",
|
||||
nameof(OnEventAsync), _fileName, gameEvent.Type);
|
||||
}
|
||||
|
||||
throw new PluginException("An error occured while executing action for script plugin");
|
||||
}
|
||||
|
||||
finally
|
||||
@ -220,73 +270,69 @@ namespace IW4MAdmin.Application.Misc
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnEventAsync(GameEvent E, Server S)
|
||||
{
|
||||
if (successfullyLoaded)
|
||||
{
|
||||
await _onProcessing.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
_scriptEngine.SetValue("_gameEvent", E);
|
||||
_scriptEngine.SetValue("_server", S);
|
||||
_scriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(S));
|
||||
_scriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue();
|
||||
}
|
||||
|
||||
catch (JavaScriptException ex)
|
||||
{
|
||||
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
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0)
|
||||
{
|
||||
_onProcessing.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task OnLoadAsync(IManager manager)
|
||||
{
|
||||
_logger.LogDebug("OnLoad executing for {name}", Name);
|
||||
_scriptEngine.SetValue("_manager", manager);
|
||||
return Task.FromResult(_scriptEngine.Execute("plugin.onLoadAsync(_manager)").GetCompletionValue());
|
||||
}
|
||||
|
||||
public Task OnTickAsync(Server S)
|
||||
{
|
||||
_scriptEngine.SetValue("_server", S);
|
||||
return Task.FromResult(_scriptEngine.Execute("plugin.onTickAsync(_server)").GetCompletionValue());
|
||||
}
|
||||
|
||||
public async Task OnUnloadAsync()
|
||||
{
|
||||
if (successfullyLoaded)
|
||||
try
|
||||
{
|
||||
await Task.FromResult(_scriptEngine.Execute("plugin.onUnloadAsync()").GetCompletionValue());
|
||||
_logger.LogDebug("OnLoad executing for {Name}", Name);
|
||||
_scriptEngine.SetValue("_manager", manager);
|
||||
_scriptEngine.Evaluate("plugin.onLoadAsync(_manager)");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (JavaScriptException ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} at {@LocationInfo}",
|
||||
nameof(OnLoadAsync), Path.GetFileName(_fileName), ex.Location);
|
||||
|
||||
throw new PluginException("A runtime error occured while executing action for script plugin");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin}",
|
||||
nameof(OnLoadAsync), Path.GetFileName(_fileName));
|
||||
|
||||
throw new PluginException("An error occured while executing action for script plugin");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnTickAsync(Server server)
|
||||
{
|
||||
_scriptEngine.SetValue("_server", server);
|
||||
await Task.FromResult(_scriptEngine.Evaluate("plugin.onTickAsync(_server)"));
|
||||
}
|
||||
|
||||
public Task OnUnloadAsync()
|
||||
{
|
||||
if (!_successfullyLoaded)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_scriptEngine.Evaluate("plugin.onUnloadAsync()");
|
||||
}
|
||||
catch (JavaScriptException ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} at {@LocationInfo}",
|
||||
nameof(OnUnloadAsync), Path.GetFileName(_fileName), ex.Location);
|
||||
|
||||
throw new PluginException("A runtime error occured while executing action for script plugin");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin}",
|
||||
nameof(OnUnloadAsync), Path.GetFileName(_fileName));
|
||||
|
||||
throw new PluginException("An error occured while executing action for script plugin");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -295,9 +341,9 @@ namespace IW4MAdmin.Application.Misc
|
||||
/// <param name="commands">commands value from jint parser</param>
|
||||
/// <param name="scriptCommandFactory">factory to create the command from</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands, IScriptCommandFactory scriptCommandFactory)
|
||||
private IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands, IScriptCommandFactory scriptCommandFactory)
|
||||
{
|
||||
List<IManagerCommand> commandList = new List<IManagerCommand>();
|
||||
var commandList = new List<IManagerCommand>();
|
||||
|
||||
// go through each defined command
|
||||
foreach (var command in commands.AsArray())
|
||||
@ -307,9 +353,10 @@ namespace IW4MAdmin.Application.Misc
|
||||
string alias = dynamicCommand.alias;
|
||||
string description = dynamicCommand.description;
|
||||
string permission = dynamicCommand.permission;
|
||||
bool targetRequired = false;
|
||||
List<Server.Game> supportedGames = null;
|
||||
var targetRequired = false;
|
||||
|
||||
List<(string, bool)> args = new List<(string, bool)>();
|
||||
var args = new List<(string, bool)>();
|
||||
dynamic arguments = null;
|
||||
|
||||
try
|
||||
@ -340,26 +387,85 @@ namespace IW4MAdmin.Application.Misc
|
||||
}
|
||||
}
|
||||
|
||||
void execute(GameEvent e)
|
||||
try
|
||||
{
|
||||
_scriptEngine.SetValue("_event", e);
|
||||
var jsEventObject = _scriptEngine.GetValue("_event");
|
||||
foreach (var game in dynamicCommand.supportedGames)
|
||||
{
|
||||
supportedGames ??= new List<Server.Game>();
|
||||
supportedGames.Add(Enum.Parse(typeof(Server.Game), game.ToString()));
|
||||
}
|
||||
}
|
||||
catch (RuntimeBinderException)
|
||||
{
|
||||
// supported games is optional
|
||||
}
|
||||
|
||||
async Task Execute(GameEvent gameEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
dynamicCommand.execute.Target.Invoke(jsEventObject);
|
||||
await _onProcessing.WaitAsync();
|
||||
|
||||
_scriptEngine.SetValue("_event", gameEvent);
|
||||
var jsEventObject = _scriptEngine.Evaluate("_event");
|
||||
|
||||
dynamicCommand.execute.Target.Invoke(_scriptEngine, jsEventObject);
|
||||
}
|
||||
|
||||
catch (JavaScriptException ex)
|
||||
{
|
||||
throw new PluginException($"An error occured while executing action for script plugin: {ex.Error} (Line: {ex.Location.Start.Line}, Character: {ex.Location.Start.Column})") { PluginFile = _fileName };
|
||||
using (LogContext.PushProperty("Server", gameEvent.Owner?.ToString()))
|
||||
{
|
||||
_logger.LogError(ex, "Could not execute command action for {Filename} {@Location}",
|
||||
Path.GetFileName(_fileName), ex.Location);
|
||||
}
|
||||
|
||||
throw new PluginException("A runtime error occured while executing action for script plugin");
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", gameEvent.Owner?.ToString()))
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Could not execute command action for script plugin {FileName}",
|
||||
Path.GetFileName(_fileName));
|
||||
}
|
||||
|
||||
throw new PluginException("An error occured while executing action for script plugin");
|
||||
}
|
||||
|
||||
|
||||
finally
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0)
|
||||
{
|
||||
_onProcessing.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, targetRequired, args, execute));
|
||||
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission,
|
||||
targetRequired, args, Execute, supportedGames?.ToArray()));
|
||||
}
|
||||
|
||||
return commandList;
|
||||
}
|
||||
}
|
||||
|
||||
public class PermissionLevelToStringConverter : IObjectConverter
|
||||
{
|
||||
public bool TryConvert(Engine engine, object value, out JsValue result)
|
||||
{
|
||||
if (value is Data.Models.Client.EFClient.Permission)
|
||||
{
|
||||
result = value.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
result = JsValue.Null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,19 +12,24 @@ namespace IW4MAdmin.Application.Misc
|
||||
public class ScriptPluginConfigurationWrapper
|
||||
{
|
||||
private readonly BaseConfigurationHandler<ScriptPluginConfiguration> _handler;
|
||||
private readonly ScriptPluginConfiguration _config;
|
||||
private ScriptPluginConfiguration _config;
|
||||
private readonly string _pluginName;
|
||||
private readonly Engine _scriptEngine;
|
||||
|
||||
public ScriptPluginConfigurationWrapper(string pluginName, Engine scriptEngine)
|
||||
{
|
||||
_handler = new BaseConfigurationHandler<ScriptPluginConfiguration>("ScriptPluginSettings");
|
||||
_config = _handler.Configuration() ??
|
||||
(ScriptPluginConfiguration) new ScriptPluginConfiguration().Generate();
|
||||
_pluginName = pluginName;
|
||||
_scriptEngine = scriptEngine;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await _handler.BuildAsync();
|
||||
_config = _handler.Configuration() ??
|
||||
(ScriptPluginConfiguration) new ScriptPluginConfiguration().Generate();
|
||||
}
|
||||
|
||||
private static int? AsInteger(double d)
|
||||
{
|
||||
return int.TryParse(d.ToString(CultureInfo.InvariantCulture), out var parsed) ? parsed : (int?) null;
|
||||
@ -87,4 +92,4 @@ namespace IW4MAdmin.Application.Misc
|
||||
return JsValue.FromObject(_scriptEngine, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
159
Application/Misc/ScriptPluginTimerHelper.cs
Normal file
159
Application/Misc/ScriptPluginTimerHelper.cs
Normal file
@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Jint.Native;
|
||||
using Jint.Runtime;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc;
|
||||
|
||||
public class ScriptPluginTimerHelper : IScriptPluginTimerHelper
|
||||
{
|
||||
private Timer _timer;
|
||||
private Action _actions;
|
||||
private Delegate _jsAction;
|
||||
private string _actionName;
|
||||
private const int DefaultDelay = 0;
|
||||
private const int DefaultInterval = 1000;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ManualResetEventSlim _onRunningTick = new();
|
||||
private SemaphoreSlim _onDependentAction;
|
||||
|
||||
public ScriptPluginTimerHelper(ILogger<ScriptPluginTimerHelper> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
~ScriptPluginTimerHelper()
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
_onRunningTick.Dispose();
|
||||
}
|
||||
|
||||
public void Start(int delay, int interval)
|
||||
{
|
||||
if (_actions is null)
|
||||
{
|
||||
throw new InvalidOperationException("Timer action must be defined before starting");
|
||||
}
|
||||
|
||||
if (delay < 0)
|
||||
{
|
||||
throw new ArgumentException("Timer delay must be >= 0");
|
||||
}
|
||||
|
||||
if (interval < 20)
|
||||
{
|
||||
throw new ArgumentException("Timer interval must be at least 20ms");
|
||||
}
|
||||
|
||||
Stop();
|
||||
|
||||
_logger.LogDebug("Starting script timer...");
|
||||
|
||||
_onRunningTick.Set();
|
||||
_timer ??= new Timer(callback => _actions(), null, delay, interval);
|
||||
IsRunning = true;
|
||||
}
|
||||
|
||||
public void Start(int interval)
|
||||
{
|
||||
Start(DefaultDelay, interval);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Start(DefaultDelay, DefaultInterval);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_timer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Stopping script timer...");
|
||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
public void OnTick(Delegate action, string actionName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(actionName))
|
||||
{
|
||||
throw new ArgumentException("actionName must be provided", nameof(actionName));
|
||||
}
|
||||
|
||||
if (action is null)
|
||||
{
|
||||
throw new ArgumentException("action must be provided", nameof(action));
|
||||
}
|
||||
|
||||
_logger.LogDebug("Adding new action with name {ActionName}", actionName);
|
||||
|
||||
_jsAction = action;
|
||||
_actionName = actionName;
|
||||
_actions = OnTick;
|
||||
}
|
||||
|
||||
private void ReleaseThreads()
|
||||
{
|
||||
_onRunningTick.Set();
|
||||
|
||||
if (_onDependentAction?.CurrentCount != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_onDependentAction?.Release(1);
|
||||
}
|
||||
private void OnTick()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_onRunningTick.IsSet)
|
||||
{
|
||||
_logger.LogDebug("Previous {OnTick} is still running, so we are skipping this one",
|
||||
nameof(OnTick));
|
||||
return;
|
||||
}
|
||||
|
||||
_onRunningTick.Reset();
|
||||
|
||||
// the js engine is not thread safe so we need to ensure we're not executing OnTick and OnEventAsync simultaneously
|
||||
_onDependentAction?.WaitAsync().Wait();
|
||||
var start = DateTime.Now;
|
||||
_jsAction.DynamicInvoke(JsValue.Undefined, new[] { JsValue.Undefined });
|
||||
_logger.LogDebug("OnTick took {Time}ms", (DateTime.Now - start).TotalMilliseconds);
|
||||
ReleaseThreads();
|
||||
}
|
||||
|
||||
catch (Exception ex) when (ex.InnerException is JavaScriptException jsex)
|
||||
{
|
||||
_logger.LogError(jsex,
|
||||
"Could not execute timer tick for script action {ActionName} [@{LocationInfo}]", _actionName,
|
||||
jsex.Location);
|
||||
ReleaseThreads();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not execute timer tick for script action {ActionName}", _actionName);
|
||||
_onRunningTick.Set();
|
||||
ReleaseThreads();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDependency(SemaphoreSlim dependentSemaphore)
|
||||
{
|
||||
_onDependentAction = dependentSemaphore;
|
||||
}
|
||||
|
||||
public bool IsRunning { get; private set; }
|
||||
}
|
147
Application/Misc/ServerDataCollector.cs
Normal file
147
Application/Misc/ServerDataCollector.cs
Normal file
@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Models;
|
||||
using Data.Models.Client;
|
||||
using Data.Models.Client.Stats.Reference;
|
||||
using Data.Models.Server;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public class ServerDataCollector : IServerDataCollector
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IManager _manager;
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
|
||||
private bool _inProgress;
|
||||
private TimeSpan _period;
|
||||
|
||||
public ServerDataCollector(ILogger<ServerDataCollector> logger, ApplicationConfiguration appConfig,
|
||||
IManager manager, IDatabaseContextFactory contextFactory, IEventPublisher eventPublisher)
|
||||
{
|
||||
_logger = logger;
|
||||
_appConfig = appConfig;
|
||||
_manager = manager;
|
||||
_contextFactory = contextFactory;
|
||||
_eventPublisher = eventPublisher;
|
||||
|
||||
_eventPublisher.OnClientConnect += SaveConnectionInfo;
|
||||
_eventPublisher.OnClientDisconnect += SaveConnectionInfo;
|
||||
}
|
||||
|
||||
~ServerDataCollector()
|
||||
{
|
||||
_eventPublisher.OnClientConnect -= SaveConnectionInfo;
|
||||
_eventPublisher.OnClientDisconnect -= SaveConnectionInfo;
|
||||
}
|
||||
|
||||
public async Task BeginCollectionAsync(TimeSpan? period = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_inProgress)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(ServerDataCollector)} is already collecting data");
|
||||
}
|
||||
|
||||
_logger.LogDebug("Initializing data collection with {Name}", nameof(ServerDataCollector));
|
||||
_inProgress = true;
|
||||
_period = period ?? (Utilities.IsDevelopment
|
||||
? TimeSpan.FromMinutes(1)
|
||||
: _appConfig.ServerDataCollectionInterval);
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(_period, cancellationToken);
|
||||
_logger.LogDebug("{Name} is collecting server data", nameof(ServerDataCollector));
|
||||
|
||||
var data = await BuildCollectionData(cancellationToken);
|
||||
await SaveData(data, cancellationToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
_logger.LogInformation("Shutdown requested for {Name}", nameof(ServerDataCollector));
|
||||
return;
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error encountered collecting server data for {Name}",
|
||||
nameof(ServerDataCollector));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<EFServerSnapshot>> BuildCollectionData(CancellationToken token)
|
||||
{
|
||||
var data = await Task.WhenAll(_manager.GetServers()
|
||||
.Select(async server => new EFServerSnapshot
|
||||
{
|
||||
CapturedAt = DateTime.UtcNow,
|
||||
PeriodBlock = (int) (DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch).TotalMinutes,
|
||||
ServerId = await server.GetIdForServer(),
|
||||
MapId = await GetOrCreateMap(server.CurrentMap.Name, (Reference.Game) server.GameName, token),
|
||||
ClientCount = server.ClientNum
|
||||
}));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private async Task<int> GetOrCreateMap(string mapName, Reference.Game game, CancellationToken token)
|
||||
{
|
||||
await using var context = _contextFactory.CreateContext();
|
||||
var existingMap =
|
||||
await context.Maps.FirstOrDefaultAsync(map => map.Name == mapName && map.Game == game, token);
|
||||
|
||||
if (existingMap != null)
|
||||
{
|
||||
return existingMap.MapId;
|
||||
}
|
||||
|
||||
var newMap = new EFMap
|
||||
{
|
||||
Name = mapName,
|
||||
Game = game
|
||||
};
|
||||
|
||||
context.Maps.Add(newMap);
|
||||
await context.SaveChangesAsync(token);
|
||||
|
||||
return newMap.MapId;
|
||||
}
|
||||
|
||||
private async Task SaveData(IEnumerable<EFServerSnapshot> snapshots, CancellationToken token)
|
||||
{
|
||||
await using var context = _contextFactory.CreateContext();
|
||||
context.ServerSnapshots.AddRange(snapshots);
|
||||
await context.SaveChangesAsync(token);
|
||||
}
|
||||
|
||||
private void SaveConnectionInfo(object sender, GameEvent gameEvent)
|
||||
{
|
||||
using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||
context.ConnectionHistory.Add(new EFClientConnectionHistory
|
||||
{
|
||||
ClientId = gameEvent.Origin.ClientId,
|
||||
ServerId = gameEvent.Owner.GetIdForServer().Result,
|
||||
ConnectionType = gameEvent.Type == GameEvent.EventType.Connect
|
||||
? Reference.ConnectionType.Connect
|
||||
: Reference.ConnectionType.Disconnect
|
||||
});
|
||||
context.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
162
Application/Misc/ServerDataViewer.cs
Normal file
162
Application/Misc/ServerDataViewer.cs
Normal file
@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Models.Client;
|
||||
using Data.Models.Server;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public class ServerDataViewer : IServerDataViewer
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IDataValueCache<EFServerSnapshot, (int?, DateTime?)> _snapshotCache;
|
||||
private readonly IDataValueCache<EFClient, (int, int)> _serverStatsCache;
|
||||
private readonly IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> _clientHistoryCache;
|
||||
|
||||
private readonly TimeSpan? _cacheTimeSpan =
|
||||
Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?) TimeSpan.FromMinutes(10);
|
||||
|
||||
public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, (int?, DateTime?)> snapshotCache,
|
||||
IDataValueCache<EFClient, (int, int)> serverStatsCache,
|
||||
IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> clientHistoryCache)
|
||||
{
|
||||
_logger = logger;
|
||||
_snapshotCache = snapshotCache;
|
||||
_serverStatsCache = serverStatsCache;
|
||||
_clientHistoryCache = clientHistoryCache;
|
||||
}
|
||||
|
||||
public async Task<(int?, DateTime?)>
|
||||
MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
_snapshotCache.SetCacheItem(async (snapshots, cancellationToken) =>
|
||||
{
|
||||
var oldestEntry = overPeriod.HasValue
|
||||
? DateTime.UtcNow - overPeriod.Value
|
||||
: DateTime.UtcNow.AddDays(-1);
|
||||
|
||||
int? maxClients;
|
||||
DateTime? maxClientsTime;
|
||||
|
||||
if (serverId != null)
|
||||
{
|
||||
var clients = await snapshots.Where(snapshot => snapshot.ServerId == serverId)
|
||||
.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
||||
.OrderByDescending(snapshot => snapshot.ClientCount)
|
||||
.Select(snapshot => new
|
||||
{
|
||||
snapshot.ClientCount,
|
||||
snapshot.CapturedAt
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
maxClients = clients?.ClientCount;
|
||||
maxClientsTime = clients?.CapturedAt;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var clients = await snapshots.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
||||
.GroupBy(snapshot => snapshot.PeriodBlock)
|
||||
.Select(grp => new
|
||||
{
|
||||
ClientCount = grp.Sum(snapshot => (int?) snapshot.ClientCount),
|
||||
Time = grp.Max(snapshot => (DateTime?) snapshot.CapturedAt)
|
||||
})
|
||||
.OrderByDescending(snapshot => snapshot.ClientCount)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
maxClients = clients?.ClientCount;
|
||||
maxClientsTime = clients?.Time;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients);
|
||||
|
||||
return (maxClients, maxClientsTime);
|
||||
}, nameof(MaxConcurrentClientsAsync), _cacheTimeSpan, true);
|
||||
|
||||
try
|
||||
{
|
||||
return await _snapshotCache.GetCacheItem(nameof(MaxConcurrentClientsAsync), token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not retrieve data for {Name}", nameof(MaxConcurrentClientsAsync));
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, CancellationToken token = default)
|
||||
{
|
||||
_serverStatsCache.SetCacheItem(async (set, cancellationToken) =>
|
||||
{
|
||||
var count = await set.CountAsync(cancellationToken);
|
||||
var startOfPeriod =
|
||||
DateTime.UtcNow.AddHours(-overPeriod?.TotalHours ?? -24);
|
||||
var recentCount = await set.CountAsync(client => client.LastConnection >= startOfPeriod,
|
||||
cancellationToken);
|
||||
|
||||
return (count, recentCount);
|
||||
}, nameof(_serverStatsCache), _cacheTimeSpan, true);
|
||||
|
||||
try
|
||||
{
|
||||
return await _serverStatsCache.GetCacheItem(nameof(_serverStatsCache), token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not retrieve data for {Name}", nameof(ClientCountsAsync));
|
||||
return (0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ClientHistoryInfo>> ClientHistoryAsync(TimeSpan? overPeriod = null, CancellationToken token = default)
|
||||
{
|
||||
_clientHistoryCache.SetCacheItem(async (set, cancellationToken) =>
|
||||
{
|
||||
var oldestEntry = overPeriod.HasValue
|
||||
? DateTime.UtcNow - overPeriod.Value
|
||||
: DateTime.UtcNow.AddHours(-12);
|
||||
|
||||
var history = await set.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
||||
.Select(snapshot =>
|
||||
new
|
||||
{
|
||||
snapshot.ServerId,
|
||||
snapshot.CapturedAt,
|
||||
snapshot.ClientCount
|
||||
})
|
||||
.OrderBy(snapshot => snapshot.CapturedAt)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return history.GroupBy(snapshot => snapshot.ServerId).Select(byServer => new ClientHistoryInfo
|
||||
{
|
||||
ServerId = byServer.Key,
|
||||
ClientCounts = byServer.Select(snapshot => new ClientCountSnapshot()
|
||||
{Time = snapshot.CapturedAt, ClientCount = snapshot.ClientCount}).ToList()
|
||||
}).ToList();
|
||||
}, nameof(_clientHistoryCache), TimeSpan.MaxValue);
|
||||
|
||||
try
|
||||
{
|
||||
return await _clientHistoryCache.GetCacheItem(nameof(_clientHistoryCache), token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not retrieve data for {Name}", nameof(ClientHistoryAsync));
|
||||
return Enumerable.Empty<ClientHistoryInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,26 +7,26 @@ using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
class TokenAuthentication : ITokenAuthentication
|
||||
internal class TokenAuthentication : ITokenAuthentication
|
||||
{
|
||||
private readonly ConcurrentDictionary<long, TokenState> _tokens;
|
||||
private readonly RNGCryptoServiceProvider _random;
|
||||
private readonly static TimeSpan _timeoutPeriod = new TimeSpan(0, 0, 120);
|
||||
private const short TOKEN_LENGTH = 4;
|
||||
private readonly RandomNumberGenerator _random;
|
||||
private static readonly TimeSpan TimeoutPeriod = new TimeSpan(0, 0, 120);
|
||||
private const short TokenLength = 4;
|
||||
|
||||
public TokenAuthentication()
|
||||
{
|
||||
_tokens = new ConcurrentDictionary<long, TokenState>();
|
||||
_random = new RNGCryptoServiceProvider();
|
||||
_random = RandomNumberGenerator.Create();
|
||||
}
|
||||
|
||||
public bool AuthorizeToken(long networkId, string token)
|
||||
{
|
||||
bool authorizeSuccessful = _tokens.ContainsKey(networkId) && _tokens[networkId].Token == token;
|
||||
var authorizeSuccessful = _tokens.ContainsKey(networkId) && _tokens[networkId].Token == token;
|
||||
|
||||
if (authorizeSuccessful)
|
||||
{
|
||||
_tokens.TryRemove(networkId, out TokenState _);
|
||||
_tokens.TryRemove(networkId, out _);
|
||||
}
|
||||
|
||||
return authorizeSuccessful;
|
||||
@ -34,15 +34,15 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
public TokenState GenerateNextToken(long networkId)
|
||||
{
|
||||
TokenState state = null;
|
||||
TokenState state;
|
||||
|
||||
if (_tokens.ContainsKey(networkId))
|
||||
{
|
||||
state = _tokens[networkId];
|
||||
|
||||
if ((DateTime.Now - state.RequestTime) > _timeoutPeriod)
|
||||
if ((DateTime.Now - state.RequestTime) > TimeoutPeriod)
|
||||
{
|
||||
_tokens.TryRemove(networkId, out TokenState _);
|
||||
_tokens.TryRemove(networkId, out _);
|
||||
}
|
||||
|
||||
else
|
||||
@ -51,11 +51,11 @@ namespace IW4MAdmin.Application.Misc
|
||||
}
|
||||
}
|
||||
|
||||
state = new TokenState()
|
||||
state = new TokenState
|
||||
{
|
||||
NetworkId = networkId,
|
||||
Token = _generateToken(),
|
||||
TokenDuration = _timeoutPeriod
|
||||
TokenDuration = TimeoutPeriod
|
||||
};
|
||||
|
||||
_tokens.TryAdd(networkId, state);
|
||||
@ -63,31 +63,31 @@ namespace IW4MAdmin.Application.Misc
|
||||
// perform some housekeeping so we don't have built up tokens if they're not ever used
|
||||
foreach (var (key, value) in _tokens)
|
||||
{
|
||||
if ((DateTime.Now - value.RequestTime) > _timeoutPeriod)
|
||||
if ((DateTime.Now - value.RequestTime) > TimeoutPeriod)
|
||||
{
|
||||
_tokens.TryRemove(key, out TokenState _);
|
||||
_tokens.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public string _generateToken()
|
||||
private string _generateToken()
|
||||
{
|
||||
bool validCharacter(char c)
|
||||
bool ValidCharacter(char c)
|
||||
{
|
||||
// this ensure that the characters are 0-9, A-Z, a-z
|
||||
return (c > 47 && c < 58) || (c > 64 && c < 91) || (c > 96 && c < 123);
|
||||
}
|
||||
|
||||
StringBuilder token = new StringBuilder();
|
||||
var token = new StringBuilder();
|
||||
|
||||
while (token.Length < TOKEN_LENGTH)
|
||||
var charSet = new byte[1];
|
||||
while (token.Length < TokenLength)
|
||||
{
|
||||
byte[] charSet = new byte[1];
|
||||
_random.GetBytes(charSet);
|
||||
|
||||
if (validCharacter((char)charSet[0]))
|
||||
if (ValidCharacter((char)charSet[0]))
|
||||
{
|
||||
token.Append((char)charSet[0]);
|
||||
}
|
||||
|
@ -75,11 +75,12 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
public bool CanGenerateLogPath { get; set; } = true;
|
||||
public string Name { get; set; } = "Call of Duty";
|
||||
public string RConEngine { get; set; } = "COD";
|
||||
public bool IsOneLog { get; set; }
|
||||
|
||||
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command)
|
||||
{
|
||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
|
||||
return response.Skip(1).ToArray();
|
||||
return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray();
|
||||
}
|
||||
|
||||
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
||||
@ -216,10 +217,15 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
continue;
|
||||
}
|
||||
|
||||
int clientNumber = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]]);
|
||||
int score = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]]);
|
||||
var clientNumber = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]]);
|
||||
var score = 0;
|
||||
|
||||
if (Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore] > 0)
|
||||
{
|
||||
score = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]]);
|
||||
}
|
||||
|
||||
int ping = 999;
|
||||
var ping = 999;
|
||||
|
||||
// their state can be CNCT, ZMBI etc
|
||||
if (match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Length <= 3)
|
||||
@ -228,7 +234,7 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
}
|
||||
|
||||
long networkId;
|
||||
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
||||
var 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();
|
||||
|
||||
@ -261,10 +267,16 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
};
|
||||
|
||||
client.SetAdditionalProperty("BotGuid", networkIdString);
|
||||
var additionalGroupIndex = Configuration.Status.GroupMapping[ParserRegex.GroupType.AdditionalGroup];
|
||||
if (match.Values.Length > additionalGroupIndex)
|
||||
|
||||
if (Configuration.Status.GroupMapping.ContainsKey(ParserRegex.GroupType.AdditionalGroup))
|
||||
{
|
||||
client.SetAdditionalProperty("ConnectionClientId", match.Values[additionalGroupIndex]);
|
||||
var additionalGroupIndex =
|
||||
Configuration.Status.GroupMapping[ParserRegex.GroupType.AdditionalGroup];
|
||||
|
||||
if (match.Values.Length > additionalGroupIndex)
|
||||
{
|
||||
client.SetAdditionalProperty("ConnectionClientId", match.Values[additionalGroupIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
StatusPlayers.Add(client);
|
||||
|
@ -3,6 +3,7 @@ using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.RCon;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using SharedLibraryCore.Formatting;
|
||||
|
||||
namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
@ -28,6 +29,25 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
public int NoticeMaximumLines { get; set; } = 8;
|
||||
public int NoticeMaxCharactersPerLine { get; set; } = 50;
|
||||
public string NoticeLineSeparator { get; set; } = Environment.NewLine;
|
||||
public int? DefaultRConPort { get; set; }
|
||||
public string DefaultInstallationDirectoryHint { get; set; }
|
||||
public short FloodProtectInterval { get; set; } = 750;
|
||||
|
||||
public ColorCodeMapping ColorCodeMapping { get; set; } = new ColorCodeMapping
|
||||
{
|
||||
// this is the default mapping (IW4), but can be overridden as needed in the parsers
|
||||
{ColorCodes.Black.ToString(), "^0"},
|
||||
{ColorCodes.Red.ToString(), "^1"},
|
||||
{ColorCodes.Green.ToString(), "^2"},
|
||||
{ColorCodes.Yellow.ToString(), "^3"},
|
||||
{ColorCodes.Blue.ToString(), "^4"},
|
||||
{ColorCodes.Cyan.ToString(), "^5"},
|
||||
{ColorCodes.Pink.ToString(), "^6"},
|
||||
{ColorCodes.White.ToString(), "^7"},
|
||||
{ColorCodes.Map.ToString(), "^8"},
|
||||
{ColorCodes.Grey.ToString(), "^9"},
|
||||
{ColorCodes.Wildcard.ToString(), ":^"},
|
||||
};
|
||||
|
||||
public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory)
|
||||
{
|
||||
|
10
Data/Abstractions/IAuditFields.cs
Normal file
10
Data/Abstractions/IAuditFields.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Data.Abstractions
|
||||
{
|
||||
public class IAuditFields
|
||||
{
|
||||
DateTime CreatedDateTime { get; set; }
|
||||
DateTime? UpdatedDateTime { get; set; }
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Data.Abstractions
|
||||
{
|
||||
public interface IDataValueCache<T, V> where T : class
|
||||
public interface IDataValueCache<TEntityType, TReturnType> where TEntityType : class
|
||||
{
|
||||
void SetCacheItem(Func<DbSet<T>, Task<V>> itemGetter, string keyName, TimeSpan? expirationTime = null);
|
||||
Task<V> GetCacheItem(string keyName);
|
||||
void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> itemGetter, string keyName,
|
||||
TimeSpan? expirationTime = null, bool autoRefresh = false);
|
||||
Task<TReturnType> GetCacheItem(string keyName, CancellationToken token = default);
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ namespace Data.Context
|
||||
{
|
||||
var link = new EFAliasLink();
|
||||
|
||||
context.Clients.Add(new EFClient()
|
||||
context.Clients.Add(new EFClient
|
||||
{
|
||||
Active = false,
|
||||
Connections = 0,
|
||||
@ -33,7 +33,7 @@ namespace Data.Context
|
||||
Masked = true,
|
||||
NetworkId = 0,
|
||||
AliasLink = link,
|
||||
CurrentAlias = new EFAlias()
|
||||
CurrentAlias = new EFAlias
|
||||
{
|
||||
Link = link,
|
||||
Active = true,
|
||||
@ -46,4 +46,4 @@ namespace Data.Context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using Data.Models;
|
||||
using Data.Models.Client;
|
||||
using Data.Models.Client.Stats;
|
||||
using Data.Models.Client.Stats.Reference;
|
||||
using Data.Models.Misc;
|
||||
using Data.Models.Server;
|
||||
|
||||
namespace Data.Context
|
||||
@ -17,6 +18,7 @@ namespace Data.Context
|
||||
public DbSet<EFAlias> Aliases { get; set; }
|
||||
public DbSet<EFAliasLink> AliasLinks { get; set; }
|
||||
public DbSet<EFPenalty> Penalties { get; set; }
|
||||
public DbSet<EFPenaltyIdentifier> PenaltyIdentifiers { get; set; }
|
||||
public DbSet<EFMeta> EFMeta { get; set; }
|
||||
public DbSet<EFChangeHistory> EFChangeHistory { get; set; }
|
||||
|
||||
@ -35,6 +37,14 @@ namespace Data.Context
|
||||
public DbSet<EFWeapon> Weapons { get; set; }
|
||||
public DbSet<EFWeaponAttachment> WeaponAttachments { get; set; }
|
||||
public DbSet<EFMap> Maps { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region MISC
|
||||
|
||||
public DbSet<EFInboxMessage> InboxMessages { get; set; }
|
||||
public DbSet<EFServerSnapshot> ServerSnapshots { get;set; }
|
||||
public DbSet<EFClientConnectionHistory> ConnectionHistory { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@ -109,7 +119,7 @@ namespace Data.Context
|
||||
ent.HasIndex(a => a.Name);
|
||||
ent.Property(_alias => _alias.SearchableName).HasMaxLength(24);
|
||||
ent.HasIndex(_alias => _alias.SearchableName);
|
||||
ent.HasIndex(_alias => new {_alias.Name, _alias.IPAddress}).IsUnique();
|
||||
ent.HasIndex(_alias => new {_alias.Name, _alias.IPAddress});
|
||||
});
|
||||
|
||||
modelBuilder.Entity<EFMeta>(ent =>
|
||||
@ -121,15 +131,26 @@ namespace Data.Context
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<EFPenaltyIdentifier>(ent =>
|
||||
{
|
||||
ent.HasIndex(identifiers => identifiers.NetworkId);
|
||||
ent.HasIndex(identifiers => identifiers.IPv4Address);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<EFClientConnectionHistory>(ent => ent.HasIndex(history => history.CreatedDateTime));
|
||||
|
||||
// force full name for database conversion
|
||||
modelBuilder.Entity<EFClient>().ToTable("EFClients");
|
||||
modelBuilder.Entity<EFAlias>().ToTable("EFAlias");
|
||||
modelBuilder.Entity<EFAliasLink>().ToTable("EFAliasLinks");
|
||||
modelBuilder.Entity<EFPenalty>().ToTable("EFPenalties");
|
||||
modelBuilder.Entity<EFPenaltyIdentifier>().ToTable("EFPenaltyIdentifiers");
|
||||
modelBuilder.Entity<EFServerSnapshot>().ToTable(nameof(EFServerSnapshot));
|
||||
modelBuilder.Entity<EFClientConnectionHistory>().ToTable(nameof(EFClientConnectionHistory));
|
||||
|
||||
Models.Configuration.StatsModelConfiguration.Configure(modelBuilder);
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageId>RaidMax.IW4MAdmin.Data</PackageId>
|
||||
<Title>RaidMax.IW4MAdmin.Data</Title>
|
||||
<Authors />
|
||||
<PackageVersion>1.0.1</PackageVersion>
|
||||
<PackageVersion>1.2.0</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Migrations\MySql\20210210221342_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\MySql\20210210221342_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224014503_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224014503_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224030227_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224030227_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224031245_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224031245_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210227041237_AddPerformancePercentileToClientStats.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210227041237_AddPerformancePercentileToClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210227161333_AddPerformancePercentileToClientStats.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210227161333_AddPerformancePercentileToClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210307163752_AddRankingHistory.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210307163752_AddRankingHistory.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209205243_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209205243_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209205948_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209205948_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209211745_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209211745_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209212725_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209212725_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210020314_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210020314_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210140835_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210140835_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210154738_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210154738_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210163803_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210163803_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210193852_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210193852_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210211033835_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210211033835_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210219013429_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210219013429_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210220171950_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210220171950_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210223163022_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210223163022_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210226215929_AddPerformancePercentileToClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210226215929_AddPerformancePercentileToClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210227160800_AddPerformancePercentileToClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210227160800_AddPerformancePercentileToClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210305033616_AddRankingHistory.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210305033616_AddRankingHistory.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210305033846_AddRankingHistory.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210305033846_AddRankingHistory.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210306223712_AddRankingHistory.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210306223712_AddRankingHistory.Designer.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.10">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql" Version="4.1.7" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.2.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.10" />
|
||||
<PackageReference Include="Npgsql" Version="6.0.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.2" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,54 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Data.Helpers
|
||||
{
|
||||
public class DataValueCache<T, V> : IDataValueCache<T, V> where T : class
|
||||
public class DataValueCache<TEntityType, TReturnType> : IDataValueCache<TEntityType, TReturnType>
|
||||
where TEntityType : class
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
private readonly Dictionary<string, CacheState> _cacheStates = new Dictionary<string, CacheState>();
|
||||
private const int DefaultExpireMinutes = 15;
|
||||
|
||||
private class CacheState
|
||||
private readonly ConcurrentDictionary<string, CacheState<TReturnType>> _cacheStates =
|
||||
new ConcurrentDictionary<string, CacheState<TReturnType>>();
|
||||
|
||||
private bool _autoRefresh;
|
||||
private const int DefaultExpireMinutes = 15;
|
||||
private Timer _timer;
|
||||
|
||||
private class CacheState<TCacheType>
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public DateTime LastRetrieval { get; set; }
|
||||
public TimeSpan ExpirationTime { get; set; }
|
||||
public Func<DbSet<T>, Task<V>> Getter { get; set; }
|
||||
public V Value { get; set; }
|
||||
public bool IsExpired => (DateTime.Now - LastRetrieval.Add(ExpirationTime)).TotalSeconds > 0;
|
||||
public Func<DbSet<TEntityType>, CancellationToken, Task<TCacheType>> Getter { get; set; }
|
||||
public TCacheType Value { get; set; }
|
||||
public bool IsSet { get; set; }
|
||||
|
||||
public bool IsExpired => ExpirationTime != TimeSpan.MaxValue &&
|
||||
(DateTime.Now - LastRetrieval.Add(ExpirationTime)).TotalSeconds > 0;
|
||||
}
|
||||
|
||||
public DataValueCache(ILogger<DataValueCache<T, V>> logger, IDatabaseContextFactory contextFactory)
|
||||
|
||||
public DataValueCache(ILogger<DataValueCache<TEntityType, TReturnType>> logger,
|
||||
IDatabaseContextFactory contextFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_contextFactory = contextFactory;
|
||||
}
|
||||
|
||||
public void SetCacheItem(Func<DbSet<T>, Task<V>> getter, string key, TimeSpan? expirationTime = null)
|
||||
|
||||
~DataValueCache()
|
||||
{
|
||||
_timer?.Stop();
|
||||
_timer?.Dispose();
|
||||
}
|
||||
|
||||
public void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> getter, string key,
|
||||
TimeSpan? expirationTime = null, bool autoRefresh = false)
|
||||
{
|
||||
if (_cacheStates.ContainsKey(key))
|
||||
{
|
||||
_logger.LogDebug("Cache key {key} is already added", key);
|
||||
_logger.LogDebug("Cache key {Key} is already added", key);
|
||||
return;
|
||||
}
|
||||
|
||||
var state = new CacheState()
|
||||
|
||||
var state = new CacheState<TReturnType>
|
||||
{
|
||||
Key = key,
|
||||
Getter = getter,
|
||||
ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes)
|
||||
};
|
||||
|
||||
_cacheStates.Add(key, state);
|
||||
|
||||
_autoRefresh = autoRefresh;
|
||||
|
||||
_cacheStates.TryAdd(key, state);
|
||||
|
||||
if (!_autoRefresh || expirationTime == TimeSpan.MaxValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_timer = new Timer(state.ExpirationTime.TotalMilliseconds);
|
||||
_timer.Elapsed += async (sender, args) => await RunCacheUpdate(state, CancellationToken.None);
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
public async Task<V> GetCacheItem(string keyName)
|
||||
|
||||
public async Task<TReturnType> GetCacheItem(string keyName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_cacheStates.ContainsKey(keyName))
|
||||
{
|
||||
@ -57,27 +87,31 @@ namespace Data.Helpers
|
||||
|
||||
var state = _cacheStates[keyName];
|
||||
|
||||
if (state.IsExpired)
|
||||
// when auto refresh is off we want to check the expiration and value
|
||||
// when auto refresh is on, we want to only check the value, because it'll be refreshed automatically
|
||||
if ((state.IsExpired || !state.IsSet) && !_autoRefresh || _autoRefresh && !state.IsSet)
|
||||
{
|
||||
await RunCacheUpdate(state);
|
||||
await RunCacheUpdate(state, cancellationToken);
|
||||
}
|
||||
|
||||
return state.Value;
|
||||
}
|
||||
|
||||
private async Task RunCacheUpdate(CacheState state)
|
||||
private async Task RunCacheUpdate(CacheState<TReturnType> state, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Running update for {ClassName} {@State}", GetType().Name, state);
|
||||
await using var context = _contextFactory.CreateContext(false);
|
||||
var set = context.Set<T>();
|
||||
var value = await state.Getter(set);
|
||||
var set = context.Set<TEntityType>();
|
||||
var value = await state.Getter(set, token);
|
||||
state.Value = value;
|
||||
state.IsSet = true;
|
||||
state.LastRetrieval = DateTime.Now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not get cached value for {key}", state.Key);
|
||||
_logger.LogError(ex, "Could not get cached value for {Key}", state.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ namespace Data.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var context = _contextFactory.CreateContext();
|
||||
await using var context = _contextFactory.CreateContext(false);
|
||||
_cachedItems = await context.Set<T>().ToDictionaryAsync(item => item.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -111,4 +111,4 @@ namespace Data.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,10 +24,11 @@ namespace Data.MigrationContext
|
||||
{
|
||||
if (MigrationExtensions.IsMigration)
|
||||
{
|
||||
optionsBuilder.UseMySql("Server=127.0.0.1;Database=IW4MAdmin_Migration;Uid=root;Pwd=password;")
|
||||
.EnableDetailedErrors(true)
|
||||
.EnableSensitiveDataLogging(true);
|
||||
var connectionString = "Server=127.0.0.1;Database=IW4MAdmin_Migration;Uid=root;Pwd=password;";
|
||||
optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
|
||||
.EnableDetailedErrors()
|
||||
.EnableSensitiveDataLogging();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,12 +23,13 @@ namespace Data.MigrationContext
|
||||
{
|
||||
if (MigrationExtensions.IsMigration)
|
||||
{
|
||||
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
||||
optionsBuilder.UseNpgsql(
|
||||
"Host=127.0.0.1;Database=IW4MAdmin_Migration;Username=postgres;Password=password;",
|
||||
options => options.SetPostgresVersion(new Version("9.4")))
|
||||
options => options.SetPostgresVersion(new Version("12.9")))
|
||||
.EnableDetailedErrors(true)
|
||||
.EnableSensitiveDataLogging(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1283
Data/Migrations/MySql/20210628153649_AddWeaponReferenceToEFClientKill.Designer.cs
generated
Normal file
1283
Data/Migrations/MySql/20210628153649_AddWeaponReferenceToEFClientKill.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddWeaponReferenceToEFClientKill : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills");
|
||||
}
|
||||
}
|
||||
}
|
1295
Data/Migrations/MySql/20210628160144_AddWeaponReferenceAndServerIdToEFACSnapshot.Designer.cs
generated
Normal file
1295
Data/Migrations/MySql/20210628160144_AddWeaponReferenceAndServerIdToEFACSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,52 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddWeaponReferenceAndServerIdToEFACSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "ServerId",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFACSnapshot_ServerId",
|
||||
table: "EFACSnapshot",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_EFACSnapshot_EFServers_ServerId",
|
||||
table: "EFACSnapshot",
|
||||
column: "ServerId",
|
||||
principalTable: "EFServers",
|
||||
principalColumn: "ServerId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_EFACSnapshot_EFServers_ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_EFACSnapshot_ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WeaponReference",
|
||||
table: "EFACSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
1298
Data/Migrations/MySql/20210629022028_AddHitLocationReferenceToEFACSnapshot.Designer.cs
generated
Normal file
1298
Data/Migrations/MySql/20210629022028_AddHitLocationReferenceToEFACSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddHitLocationReferenceToEFACSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
1355
Data/Migrations/MySql/20210709010749_AddEFInboxMessage.Designer.cs
generated
Normal file
1355
Data/Migrations/MySql/20210709010749_AddEFInboxMessage.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
70
Data/Migrations/MySql/20210709010749_AddEFInboxMessage.cs
Normal file
70
Data/Migrations/MySql/20210709010749_AddEFInboxMessage.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddEFInboxMessage : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InboxMessages",
|
||||
columns: table => new
|
||||
{
|
||||
InboxMessageId = table.Column<int>(nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
CreatedDateTime = table.Column<DateTime>(nullable: false),
|
||||
UpdatedDateTime = table.Column<DateTime>(nullable: true),
|
||||
SourceClientId = table.Column<int>(nullable: false),
|
||||
DestinationClientId = table.Column<int>(nullable: false),
|
||||
ServerId = table.Column<long>(nullable: true),
|
||||
Message = table.Column<string>(nullable: true),
|
||||
IsDelivered = table.Column<bool>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InboxMessages", x => x.InboxMessageId);
|
||||
table.ForeignKey(
|
||||
name: "FK_InboxMessages_EFClients_DestinationClientId",
|
||||
column: x => x.DestinationClientId,
|
||||
principalTable: "EFClients",
|
||||
principalColumn: "ClientId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_InboxMessages_EFServers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalTable: "EFServers",
|
||||
principalColumn: "ServerId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_InboxMessages_EFClients_SourceClientId",
|
||||
column: x => x.SourceClientId,
|
||||
principalTable: "EFClients",
|
||||
principalColumn: "ClientId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InboxMessages_DestinationClientId",
|
||||
table: "InboxMessages",
|
||||
column: "DestinationClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InboxMessages_ServerId",
|
||||
table: "InboxMessages",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InboxMessages_SourceClientId",
|
||||
table: "InboxMessages",
|
||||
column: "SourceClientId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "InboxMessages");
|
||||
}
|
||||
}
|
||||
}
|
1403
Data/Migrations/MySql/20210826222452_AddEFServerSnapshot.Designer.cs
generated
Normal file
1403
Data/Migrations/MySql/20210826222452_AddEFServerSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
58
Data/Migrations/MySql/20210826222452_AddEFServerSnapshot.cs
Normal file
58
Data/Migrations/MySql/20210826222452_AddEFServerSnapshot.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddEFServerSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "EFServerSnapshot",
|
||||
columns: table => new
|
||||
{
|
||||
ServerSnapshotId = table.Column<long>(nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Active = table.Column<bool>(nullable: false),
|
||||
CapturedAt = table.Column<DateTime>(nullable: false),
|
||||
PeriodBlock = table.Column<int>(nullable: false),
|
||||
ServerId = table.Column<long>(nullable: false),
|
||||
MapId = table.Column<int>(nullable: false),
|
||||
ClientCount = table.Column<int>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_EFServerSnapshot", x => x.ServerSnapshotId);
|
||||
table.ForeignKey(
|
||||
name: "FK_EFServerSnapshot_EFMaps_MapId",
|
||||
column: x => x.MapId,
|
||||
principalTable: "EFMaps",
|
||||
principalColumn: "MapId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_EFServerSnapshot_EFServers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalTable: "EFServers",
|
||||
principalColumn: "ServerId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFServerSnapshot_MapId",
|
||||
table: "EFServerSnapshot",
|
||||
column: "MapId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFServerSnapshot_ServerId",
|
||||
table: "EFServerSnapshot",
|
||||
column: "ServerId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "EFServerSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
1450
Data/Migrations/MySql/20210831231006_AddEFClientConnectionHistory.Designer.cs
generated
Normal file
1450
Data/Migrations/MySql/20210831231006_AddEFClientConnectionHistory.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddEFClientConnectionHistory : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "EFClientConnectionHistory",
|
||||
columns: table => new
|
||||
{
|
||||
ClientConnectionId = table.Column<long>(nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
CreatedDateTime = table.Column<DateTime>(nullable: false),
|
||||
UpdatedDateTime = table.Column<DateTime>(nullable: true),
|
||||
ClientId = table.Column<int>(nullable: false),
|
||||
ServerId = table.Column<long>(nullable: false),
|
||||
ConnectionType = table.Column<int>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_EFClientConnectionHistory", x => x.ClientConnectionId);
|
||||
table.ForeignKey(
|
||||
name: "FK_EFClientConnectionHistory_EFClients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "EFClients",
|
||||
principalColumn: "ClientId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_EFClientConnectionHistory_EFServers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalTable: "EFServers",
|
||||
principalColumn: "ServerId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFClientConnectionHistory_ClientId",
|
||||
table: "EFClientConnectionHistory",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFClientConnectionHistory_CreatedDateTime",
|
||||
table: "EFClientConnectionHistory",
|
||||
column: "CreatedDateTime");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFClientConnectionHistory_ServerId",
|
||||
table: "EFClientConnectionHistory",
|
||||
column: "ServerId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "EFClientConnectionHistory");
|
||||
}
|
||||
}
|
||||
}
|
1449
Data/Migrations/MySql/20220102221729_RemoveUniqueAliasIndexConstraint.Designer.cs
generated
Normal file
1449
Data/Migrations/MySql/20220102221729_RemoveUniqueAliasIndexConstraint.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,32 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class RemoveUniqueAliasIndexConstraint : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_EFAlias_Name_IPAddress",
|
||||
table: "EFAlias");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFAlias_Name_IPAddress",
|
||||
table: "EFAlias",
|
||||
columns: new[] { "Name", "IPAddress" });
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_EFAlias_Name_IPAddress",
|
||||
table: "EFAlias");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFAlias_Name_IPAddress",
|
||||
table: "EFAlias",
|
||||
columns: new[] { "Name", "IPAddress" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
1622
Data/Migrations/MySql/20220222215149_AddEFPenaltyIdentifier.Designer.cs
generated
Normal file
1622
Data/Migrations/MySql/20220222215149_AddEFPenaltyIdentifier.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
250
Data/Migrations/MySql/20220222215149_AddEFPenaltyIdentifier.cs
Normal file
250
Data/Migrations/MySql/20220222215149_AddEFPenaltyIdentifier.cs
Normal file
@ -0,0 +1,250 @@
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddEFPenaltyIdentifier : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "EFPenaltyIdentifiers",
|
||||
columns: table => new
|
||||
{
|
||||
PenaltyIdentifierId = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
IPv4Address = table.Column<int>(type: "int", nullable: true),
|
||||
NetworkId = table.Column<long>(type: "bigint", nullable: false),
|
||||
PenaltyId = table.Column<int>(type: "int", nullable: false),
|
||||
Active = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_EFPenaltyIdentifiers", x => x.PenaltyIdentifierId);
|
||||
table.ForeignKey(
|
||||
name: "FK_EFPenaltyIdentifiers_EFPenalties_PenaltyId",
|
||||
column: x => x.PenaltyId,
|
||||
principalTable: "EFPenalties",
|
||||
principalColumn: "PenaltyId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFPenaltyIdentifiers_IPv4Address",
|
||||
table: "EFPenaltyIdentifiers",
|
||||
column: "IPv4Address");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFPenaltyIdentifiers_NetworkId",
|
||||
table: "EFPenaltyIdentifiers",
|
||||
column: "NetworkId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFPenaltyIdentifiers_PenaltyId",
|
||||
table: "EFPenaltyIdentifiers",
|
||||
column: "PenaltyId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "EFPenaltyIdentifiers");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Message",
|
||||
table: "InboxMessages",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "EFWeaponAttachments",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "HostName",
|
||||
table: "EFServers",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "EndPoint",
|
||||
table: "EFServers",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Offense",
|
||||
table: "EFPenalties",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "AutomatedOffense",
|
||||
table: "EFPenalties",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Value",
|
||||
table: "EFMeta",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Extra",
|
||||
table: "EFMeta",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "EFMeansOfDeath",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "EFMaps",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "PasswordSalt",
|
||||
table: "EFClients",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Password",
|
||||
table: "EFClients",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Message",
|
||||
table: "EFClientMessages",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "PreviousValue",
|
||||
table: "EFChangeHistory",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "CurrentValue",
|
||||
table: "EFChangeHistory",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFACSnapshot",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
1620
Data/Migrations/MySql/20220222230049_MakeEFPenaltyLinkIdNullable.Designer.cs
generated
Normal file
1620
Data/Migrations/MySql/20220222230049_MakeEFPenaltyLinkIdNullable.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,56 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class MakeEFPenaltyLinkIdNullable : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_EFPenalties_EFAliasLinks_LinkId",
|
||||
table: "EFPenalties");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "LinkId",
|
||||
table: "EFPenalties",
|
||||
type: "int",
|
||||
nullable: true,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_EFPenalties_EFAliasLinks_LinkId",
|
||||
table: "EFPenalties",
|
||||
column: "LinkId",
|
||||
principalTable: "EFAliasLinks",
|
||||
principalColumn: "AliasLinkId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_EFPenalties_EFAliasLinks_LinkId",
|
||||
table: "EFPenalties");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "LinkId",
|
||||
table: "EFPenalties",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_EFPenalties_EFAliasLinks_LinkId",
|
||||
table: "EFPenalties",
|
||||
column: "LinkId",
|
||||
principalTable: "EFAliasLinks",
|
||||
principalColumn: "AliasLinkId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
1623
Data/Migrations/MySql/20220223150028_AddAuditFieldsToEFPenaltyIdentifier.Designer.cs
generated
Normal file
1623
Data/Migrations/MySql/20220223150028_AddAuditFieldsToEFPenaltyIdentifier.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddAuditFieldsToEFPenaltyIdentifier : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Active",
|
||||
table: "EFPenaltyIdentifiers");
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedDateTime",
|
||||
table: "EFPenaltyIdentifiers",
|
||||
type: "datetime(6)",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "UpdatedDateTime",
|
||||
table: "EFPenaltyIdentifiers",
|
||||
type: "datetime(6)",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedDateTime",
|
||||
table: "EFPenaltyIdentifiers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UpdatedDateTime",
|
||||
table: "EFPenaltyIdentifiers");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Active",
|
||||
table: "EFPenaltyIdentifiers",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
[DbContext(typeof(MySqlDatabaseContext))]
|
||||
@ -14,7 +16,7 @@ namespace Data.Migrations.MySql
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "3.1.10")
|
||||
.HasAnnotation("ProductVersion", "6.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
|
||||
@ -38,7 +40,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("Vector3Id");
|
||||
|
||||
b.ToTable("EFACSnapshotVector3");
|
||||
b.ToTable("EFACSnapshotVector3", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFClient", b =>
|
||||
@ -75,10 +77,10 @@ namespace Data.Migrations.MySql
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("PasswordSalt")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("TotalConnectionTime")
|
||||
.HasColumnType("int");
|
||||
@ -92,7 +94,39 @@ namespace Data.Migrations.MySql
|
||||
b.HasIndex("NetworkId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("EFClients");
|
||||
b.ToTable("EFClients", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
|
||||
{
|
||||
b.Property<long>("ClientConnectionId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ConnectionType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<long>("ServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime?>("UpdatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("ClientConnectionId");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("CreatedDateTime");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("EFClientConnectionHistory", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
|
||||
@ -146,6 +180,9 @@ namespace Data.Migrations.MySql
|
||||
b.Property<int>("Weapon")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("WeaponReference")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@ -163,7 +200,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("ViewAnglesVector3Id");
|
||||
|
||||
b.ToTable("EFClientKills");
|
||||
b.ToTable("EFClientKills", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFClientMessage", b =>
|
||||
@ -179,7 +216,7 @@ namespace Data.Migrations.MySql
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("SentIngame")
|
||||
.HasColumnType("tinyint(1)");
|
||||
@ -198,7 +235,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("TimeSent");
|
||||
|
||||
b.ToTable("EFClientMessages");
|
||||
b.ToTable("EFClientMessages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
|
||||
@ -237,6 +274,9 @@ namespace Data.Migrations.MySql
|
||||
b.Property<int>("HitLocation")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("HitLocationReference")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("HitOriginId")
|
||||
.HasColumnType("int");
|
||||
|
||||
@ -255,6 +295,9 @@ namespace Data.Migrations.MySql
|
||||
b.Property<double>("RecoilOffset")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<long?>("ServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<double>("SessionAngleOffset")
|
||||
.HasColumnType("double");
|
||||
|
||||
@ -279,6 +322,9 @@ namespace Data.Migrations.MySql
|
||||
b.Property<int>("WeaponId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("WeaponReference")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@ -294,7 +340,9 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("LastStrainAngleId");
|
||||
|
||||
b.ToTable("EFACSnapshot");
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("EFACSnapshot", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
|
||||
@ -368,7 +416,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("WeaponId");
|
||||
|
||||
b.ToTable("EFClientHitStatistics");
|
||||
b.ToTable("EFClientHitStatistics", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b =>
|
||||
@ -413,7 +461,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("ZScore");
|
||||
|
||||
b.ToTable("EFClientRankingHistory");
|
||||
b.ToTable("EFClientRankingHistory", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
|
||||
@ -432,7 +480,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("EFClientRatingHistory");
|
||||
b.ToTable("EFClientRatingHistory", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
|
||||
@ -490,7 +538,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("ClientId", "TimePlayed", "ZScore");
|
||||
|
||||
b.ToTable("EFClientStatistics");
|
||||
b.ToTable("EFClientStatistics", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b =>
|
||||
@ -503,12 +551,12 @@ namespace Data.Migrations.MySql
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("EFClientStatisticsClientId")
|
||||
.HasColumnName("EFClientStatisticsClientId")
|
||||
.HasColumnType("int");
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("EFClientStatisticsClientId");
|
||||
|
||||
b.Property<long>("EFClientStatisticsServerId")
|
||||
.HasColumnName("EFClientStatisticsServerId")
|
||||
.HasColumnType("bigint");
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("EFClientStatisticsServerId");
|
||||
|
||||
b.Property<int>("HitCount")
|
||||
.HasColumnType("int");
|
||||
@ -528,7 +576,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId");
|
||||
|
||||
b.ToTable("EFHitLocationCounts");
|
||||
b.ToTable("EFHitLocationCounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b =>
|
||||
@ -571,7 +619,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("When", "ServerId", "Performance", "ActivityAmount");
|
||||
|
||||
b.ToTable("EFRating");
|
||||
b.ToTable("EFRating", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b =>
|
||||
@ -588,7 +636,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
@ -597,7 +645,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.ToTable("EFHitLocations");
|
||||
b.ToTable("EFHitLocations", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b =>
|
||||
@ -614,14 +662,14 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime?>("UpdatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("MapId");
|
||||
|
||||
b.ToTable("EFMaps");
|
||||
b.ToTable("EFMaps", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b =>
|
||||
@ -638,14 +686,14 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime?>("UpdatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("MeansOfDeathId");
|
||||
|
||||
b.ToTable("EFMeansOfDeath");
|
||||
b.ToTable("EFMeansOfDeath", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b =>
|
||||
@ -662,7 +710,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
@ -671,7 +719,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.ToTable("EFWeapons");
|
||||
b.ToTable("EFWeapons", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b =>
|
||||
@ -688,14 +736,14 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime?>("UpdatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("WeaponAttachmentId");
|
||||
|
||||
b.ToTable("EFWeaponAttachments");
|
||||
b.ToTable("EFWeaponAttachments", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b =>
|
||||
@ -730,7 +778,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("Attachment3Id");
|
||||
|
||||
b.ToTable("EFWeaponAttachmentCombos");
|
||||
b.ToTable("EFWeaponAttachmentCombos", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.EFAlias", b =>
|
||||
@ -753,12 +801,12 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(24) CHARACTER SET utf8mb4")
|
||||
.HasMaxLength(24);
|
||||
.HasMaxLength(24)
|
||||
.HasColumnType("varchar(24)");
|
||||
|
||||
b.Property<string>("SearchableName")
|
||||
.HasColumnType("varchar(24) CHARACTER SET utf8mb4")
|
||||
.HasMaxLength(24);
|
||||
.HasMaxLength(24)
|
||||
.HasColumnType("varchar(24)");
|
||||
|
||||
b.HasKey("AliasId");
|
||||
|
||||
@ -770,10 +818,9 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("SearchableName");
|
||||
|
||||
b.HasIndex("Name", "IPAddress")
|
||||
.IsUnique();
|
||||
b.HasIndex("Name", "IPAddress");
|
||||
|
||||
b.ToTable("EFAlias");
|
||||
b.ToTable("EFAlias", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.EFAliasLink", b =>
|
||||
@ -787,7 +834,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasKey("AliasLinkId");
|
||||
|
||||
b.ToTable("EFAliasLinks");
|
||||
b.ToTable("EFAliasLinks", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.EFChangeHistory", b =>
|
||||
@ -800,11 +847,11 @@ namespace Data.Migrations.MySql
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasColumnType("varchar(128) CHARACTER SET utf8mb4")
|
||||
.HasMaxLength(128);
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("varchar(128)");
|
||||
|
||||
b.Property<string>("CurrentValue")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("ImpersonationEntityId")
|
||||
.HasColumnType("int");
|
||||
@ -813,7 +860,7 @@ namespace Data.Migrations.MySql
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("PreviousValue")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("TargetEntityId")
|
||||
.HasColumnType("int");
|
||||
@ -845,12 +892,12 @@ namespace Data.Migrations.MySql
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Extra")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(32) CHARACTER SET utf8mb4")
|
||||
.HasMaxLength(32);
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("varchar(32)");
|
||||
|
||||
b.Property<int?>("LinkedMetaId")
|
||||
.HasColumnType("int");
|
||||
@ -860,7 +907,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("MetaId");
|
||||
|
||||
@ -883,7 +930,7 @@ namespace Data.Migrations.MySql
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("AutomatedOffense")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime?>("Expires")
|
||||
.HasColumnType("datetime(6)");
|
||||
@ -891,7 +938,7 @@ namespace Data.Migrations.MySql
|
||||
b.Property<bool>("IsEvadedOffense")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("LinkId")
|
||||
b.Property<int?>("LinkId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("OffenderId")
|
||||
@ -899,7 +946,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.Property<string>("Offense")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("PunisherId")
|
||||
.HasColumnType("int");
|
||||
@ -918,7 +965,77 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("PunisherId");
|
||||
|
||||
b.ToTable("EFPenalties");
|
||||
b.ToTable("EFPenalties", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b =>
|
||||
{
|
||||
b.Property<int>("PenaltyIdentifierId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int?>("IPv4Address")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("NetworkId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("PenaltyId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("UpdatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("PenaltyIdentifierId");
|
||||
|
||||
b.HasIndex("IPv4Address");
|
||||
|
||||
b.HasIndex("NetworkId");
|
||||
|
||||
b.HasIndex("PenaltyId");
|
||||
|
||||
b.ToTable("EFPenaltyIdentifiers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b =>
|
||||
{
|
||||
b.Property<int>("InboxMessageId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("DestinationClientId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IsDelivered")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<long?>("ServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("SourceClientId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("UpdatedDateTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("InboxMessageId");
|
||||
|
||||
b.HasIndex("DestinationClientId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.HasIndex("SourceClientId");
|
||||
|
||||
b.ToTable("InboxMessages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Server.EFServer", b =>
|
||||
@ -930,13 +1047,13 @@ namespace Data.Migrations.MySql
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("EndPoint")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("GameName")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("HostName")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsPasswordProtected")
|
||||
.HasColumnType("tinyint(1)");
|
||||
@ -946,7 +1063,40 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasKey("ServerId");
|
||||
|
||||
b.ToTable("EFServers");
|
||||
b.ToTable("EFServers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b =>
|
||||
{
|
||||
b.Property<long>("ServerSnapshotId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime>("CapturedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("ClientCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("MapId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PeriodBlock")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("ServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("ServerSnapshotId");
|
||||
|
||||
b.HasIndex("MapId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("EFServerSnapshot", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b =>
|
||||
@ -971,7 +1121,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("EFServerStatistics");
|
||||
b.ToTable("EFServerStatistics", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Vector3", b =>
|
||||
@ -991,7 +1141,7 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasKey("Vector3Id");
|
||||
|
||||
b.ToTable("Vector3");
|
||||
b.ToTable("Vector3", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
|
||||
@ -1007,6 +1157,10 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("Vector3Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Snapshot");
|
||||
|
||||
b.Navigation("Vector");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFClient", b =>
|
||||
@ -1022,6 +1176,29 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("CurrentAliasId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AliasLink");
|
||||
|
||||
b.Navigation("CurrentAlias");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
|
||||
{
|
||||
b.HasOne("Data.Models.Client.EFClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Data.Models.Server.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
|
||||
@ -1055,6 +1232,18 @@ namespace Data.Migrations.MySql
|
||||
b.HasOne("Data.Models.Vector3", "ViewAngles")
|
||||
.WithMany()
|
||||
.HasForeignKey("ViewAnglesVector3Id");
|
||||
|
||||
b.Navigation("Attacker");
|
||||
|
||||
b.Navigation("DeathOrigin");
|
||||
|
||||
b.Navigation("KillOrigin");
|
||||
|
||||
b.Navigation("Server");
|
||||
|
||||
b.Navigation("Victim");
|
||||
|
||||
b.Navigation("ViewAngles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFClientMessage", b =>
|
||||
@ -1070,6 +1259,10 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
|
||||
@ -1103,6 +1296,22 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("LastStrainAngleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Data.Models.Server.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId");
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("CurrentViewAngle");
|
||||
|
||||
b.Navigation("HitDestination");
|
||||
|
||||
b.Navigation("HitOrigin");
|
||||
|
||||
b.Navigation("LastStrainAngle");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
|
||||
@ -1132,6 +1341,18 @@ namespace Data.Migrations.MySql
|
||||
b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon")
|
||||
.WithMany()
|
||||
.HasForeignKey("WeaponId");
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("HitLocation");
|
||||
|
||||
b.Navigation("MeansOfDeath");
|
||||
|
||||
b.Navigation("Server");
|
||||
|
||||
b.Navigation("Weapon");
|
||||
|
||||
b.Navigation("WeaponAttachmentCombo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b =>
|
||||
@ -1145,6 +1366,10 @@ namespace Data.Migrations.MySql
|
||||
b.HasOne("Data.Models.Server.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId");
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
|
||||
@ -1154,6 +1379,8 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
|
||||
@ -1169,6 +1396,10 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b =>
|
||||
@ -1190,6 +1421,10 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b =>
|
||||
@ -1203,6 +1438,10 @@ namespace Data.Migrations.MySql
|
||||
b.HasOne("Data.Models.Server.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId");
|
||||
|
||||
b.Navigation("RatingHistory");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b =>
|
||||
@ -1220,6 +1459,12 @@ namespace Data.Migrations.MySql
|
||||
b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3")
|
||||
.WithMany()
|
||||
.HasForeignKey("Attachment3Id");
|
||||
|
||||
b.Navigation("Attachment1");
|
||||
|
||||
b.Navigation("Attachment2");
|
||||
|
||||
b.Navigation("Attachment3");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.EFAlias", b =>
|
||||
@ -1229,6 +1474,8 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("LinkId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Link");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.EFMeta", b =>
|
||||
@ -1241,15 +1488,17 @@ namespace Data.Migrations.MySql
|
||||
.WithMany()
|
||||
.HasForeignKey("LinkedMetaId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("LinkedMeta");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.EFPenalty", b =>
|
||||
{
|
||||
b.HasOne("Data.Models.EFAliasLink", "Link")
|
||||
.WithMany("ReceivedPenalties")
|
||||
.HasForeignKey("LinkId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.HasForeignKey("LinkId");
|
||||
|
||||
b.HasOne("Data.Models.Client.EFClient", "Offender")
|
||||
.WithMany("ReceivedPenalties")
|
||||
@ -1262,6 +1511,67 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("PunisherId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Link");
|
||||
|
||||
b.Navigation("Offender");
|
||||
|
||||
b.Navigation("Punisher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b =>
|
||||
{
|
||||
b.HasOne("Data.Models.EFPenalty", "Penalty")
|
||||
.WithMany()
|
||||
.HasForeignKey("PenaltyId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Penalty");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b =>
|
||||
{
|
||||
b.HasOne("Data.Models.Client.EFClient", "DestinationClient")
|
||||
.WithMany()
|
||||
.HasForeignKey("DestinationClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Data.Models.Server.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId");
|
||||
|
||||
b.HasOne("Data.Models.Client.EFClient", "SourceClient")
|
||||
.WithMany()
|
||||
.HasForeignKey("SourceClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("DestinationClient");
|
||||
|
||||
b.Navigation("Server");
|
||||
|
||||
b.Navigation("SourceClient");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b =>
|
||||
{
|
||||
b.HasOne("Data.Models.Client.Stats.Reference.EFMap", "Map")
|
||||
.WithMany()
|
||||
.HasForeignKey("MapId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Data.Models.Server.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Map");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b =>
|
||||
@ -1271,6 +1581,39 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.EFClient", b =>
|
||||
{
|
||||
b.Navigation("AdministeredPenalties");
|
||||
|
||||
b.Navigation("Meta");
|
||||
|
||||
b.Navigation("ReceivedPenalties");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
|
||||
{
|
||||
b.Navigation("PredictedViewAngles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
|
||||
{
|
||||
b.Navigation("Ratings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
|
||||
{
|
||||
b.Navigation("HitLocations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.EFAliasLink", b =>
|
||||
{
|
||||
b.Navigation("Children");
|
||||
|
||||
b.Navigation("ReceivedPenalties");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
1308
Data/Migrations/Postgresql/20210628153932_AddWeaponReferenceToEFClientKill.Designer.cs
generated
Normal file
1308
Data/Migrations/Postgresql/20210628153932_AddWeaponReferenceToEFClientKill.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.Postgresql
|
||||
{
|
||||
public partial class AddWeaponReferenceToEFClientKill : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills");
|
||||
}
|
||||
}
|
||||
}
|
1320
Data/Migrations/Postgresql/20210628160226_AddWeaponReferenceAndServerIdToEFACSnapshot.Designer.cs
generated
Normal file
1320
Data/Migrations/Postgresql/20210628160226_AddWeaponReferenceAndServerIdToEFACSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,52 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.Postgresql
|
||||
{
|
||||
public partial class AddWeaponReferenceAndServerIdToEFACSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "ServerId",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFACSnapshot_ServerId",
|
||||
table: "EFACSnapshot",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_EFACSnapshot_EFServers_ServerId",
|
||||
table: "EFACSnapshot",
|
||||
column: "ServerId",
|
||||
principalTable: "EFServers",
|
||||
principalColumn: "ServerId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_EFACSnapshot_EFServers_ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_EFACSnapshot_ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WeaponReference",
|
||||
table: "EFACSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
1323
Data/Migrations/Postgresql/20210629022117_AddHitLocationReferenceToEFACSnapshot.Designer.cs
generated
Normal file
1323
Data/Migrations/Postgresql/20210629022117_AddHitLocationReferenceToEFACSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.Postgresql
|
||||
{
|
||||
public partial class AddHitLocationReferenceToEFACSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
1381
Data/Migrations/Postgresql/20210709010920_AddEFInboxMessage.Designer.cs
generated
Normal file
1381
Data/Migrations/Postgresql/20210709010920_AddEFInboxMessage.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Data.Migrations.Postgresql
|
||||
{
|
||||
public partial class AddEFInboxMessage : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InboxMessages",
|
||||
columns: table => new
|
||||
{
|
||||
InboxMessageId = table.Column<int>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
|
||||
CreatedDateTime = table.Column<DateTime>(nullable: false),
|
||||
UpdatedDateTime = table.Column<DateTime>(nullable: true),
|
||||
SourceClientId = table.Column<int>(nullable: false),
|
||||
DestinationClientId = table.Column<int>(nullable: false),
|
||||
ServerId = table.Column<long>(nullable: true),
|
||||
Message = table.Column<string>(nullable: true),
|
||||
IsDelivered = table.Column<bool>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InboxMessages", x => x.InboxMessageId);
|
||||
table.ForeignKey(
|
||||
name: "FK_InboxMessages_EFClients_DestinationClientId",
|
||||
column: x => x.DestinationClientId,
|
||||
principalTable: "EFClients",
|
||||
principalColumn: "ClientId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_InboxMessages_EFServers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalTable: "EFServers",
|
||||
principalColumn: "ServerId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_InboxMessages_EFClients_SourceClientId",
|
||||
column: x => x.SourceClientId,
|
||||
principalTable: "EFClients",
|
||||
principalColumn: "ClientId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InboxMessages_DestinationClientId",
|
||||
table: "InboxMessages",
|
||||
column: "DestinationClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InboxMessages_ServerId",
|
||||
table: "InboxMessages",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InboxMessages_SourceClientId",
|
||||
table: "InboxMessages",
|
||||
column: "SourceClientId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "InboxMessages");
|
||||
}
|
||||
}
|
||||
}
|
1430
Data/Migrations/Postgresql/20210826222523_AddEFServerSnapshot.Designer.cs
generated
Normal file
1430
Data/Migrations/Postgresql/20210826222523_AddEFServerSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Data.Migrations.Postgresql
|
||||
{
|
||||
public partial class AddEFServerSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "EFServerSnapshot",
|
||||
columns: table => new
|
||||
{
|
||||
ServerSnapshotId = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
|
||||
Active = table.Column<bool>(nullable: false),
|
||||
CapturedAt = table.Column<DateTime>(nullable: false),
|
||||
PeriodBlock = table.Column<int>(nullable: false),
|
||||
ServerId = table.Column<long>(nullable: false),
|
||||
MapId = table.Column<int>(nullable: false),
|
||||
ClientCount = table.Column<int>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_EFServerSnapshot", x => x.ServerSnapshotId);
|
||||
table.ForeignKey(
|
||||
name: "FK_EFServerSnapshot_EFMaps_MapId",
|
||||
column: x => x.MapId,
|
||||
principalTable: "EFMaps",
|
||||
principalColumn: "MapId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_EFServerSnapshot_EFServers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalTable: "EFServers",
|
||||
principalColumn: "ServerId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFServerSnapshot_MapId",
|
||||
table: "EFServerSnapshot",
|
||||
column: "MapId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFServerSnapshot_ServerId",
|
||||
table: "EFServerSnapshot",
|
||||
column: "ServerId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "EFServerSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
1478
Data/Migrations/Postgresql/20210831231031_AddEFClientConnectionHistory.Designer.cs
generated
Normal file
1478
Data/Migrations/Postgresql/20210831231031_AddEFClientConnectionHistory.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user