Compare commits
29 Commits
2.3-Prerel
...
2.3-Prerel
Author | SHA1 | Date | |
---|---|---|---|
e56f574af4 | |||
513f495304 | |||
910faf427b | |||
9117440566 | |||
ad6b6a6465 | |||
eb8145a168 | |||
a560e05df8 | |||
ac06b41a0b | |||
cce6482541 | |||
bc7dc3a71a | |||
2be719d8f9 | |||
8a8dec8bbd | |||
2b3e21d4ba | |||
4590d94d7d | |||
5842073f91 | |||
c783a04a52 | |||
4735864113 | |||
d70d8fd0ae | |||
0dc4e12d61 | |||
778e339a61 | |||
1ef2ba5344 | |||
126f2fcc47 | |||
d5789dac81 | |||
25e2438e7f | |||
19107f9e85 | |||
0e44fa10f7 | |||
ebb54ebfd7 | |||
03a27d113e | |||
39fb3b9966 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
ko_fi: raidmax
|
@ -25,20 +25,20 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Jint" Version="3.0.0-beta-1632" />
|
<PackageReference Include="Jint" Version="3.0.0-beta-1632" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.7" />
|
||||||
<PackageReference Include="RestEase" Version="1.4.10" />
|
<PackageReference Include="RestEase" Version="1.5.0" />
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.0" />
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ServerGarbageCollection>false</ServerGarbageCollection>
|
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||||
<TieredCompilation>true</TieredCompilation>
|
<TieredCompilation>true</TieredCompilation>
|
||||||
<LangVersion>7.1</LangVersion>
|
<LangVersion>Latest</LangVersion>
|
||||||
<StartupObject></StartupObject>
|
<StartupObject></StartupObject>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ using SharedLibraryCore.Dtos;
|
|||||||
using SharedLibraryCore.Exceptions;
|
using SharedLibraryCore.Exceptions;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using SharedLibraryCore.QueryHelper;
|
||||||
using SharedLibraryCore.Services;
|
using SharedLibraryCore.Services;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
@ -54,7 +55,7 @@ namespace IW4MAdmin.Application
|
|||||||
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
|
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
|
||||||
readonly IPageList PageList;
|
readonly IPageList PageList;
|
||||||
private readonly Dictionary<long, ILogger> _loggers = new Dictionary<long, ILogger>();
|
private readonly Dictionary<long, ILogger> _loggers = new Dictionary<long, ILogger>();
|
||||||
private readonly MetaService _metaService;
|
private readonly IMetaService _metaService;
|
||||||
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
|
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
|
||||||
private readonly CancellationTokenSource _tokenSource;
|
private readonly CancellationTokenSource _tokenSource;
|
||||||
private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>();
|
private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>();
|
||||||
@ -65,12 +66,15 @@ namespace IW4MAdmin.Application
|
|||||||
private readonly IEnumerable<IRegisterEvent> _customParserEvents;
|
private readonly IEnumerable<IRegisterEvent> _customParserEvents;
|
||||||
private readonly IEventHandler _eventHandler;
|
private readonly IEventHandler _eventHandler;
|
||||||
private readonly IScriptCommandFactory _scriptCommandFactory;
|
private readonly IScriptCommandFactory _scriptCommandFactory;
|
||||||
|
private readonly IMetaRegistration _metaRegistration;
|
||||||
|
private readonly IScriptPluginServiceResolver _scriptPluginServiceResolver;
|
||||||
|
|
||||||
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
|
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
|
||||||
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
|
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
|
||||||
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
|
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
|
||||||
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
|
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
|
||||||
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory)
|
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IMetaService metaService,
|
||||||
|
IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver)
|
||||||
{
|
{
|
||||||
MiddlewareActionHandler = actionHandler;
|
MiddlewareActionHandler = actionHandler;
|
||||||
_servers = new ConcurrentBag<Server>();
|
_servers = new ConcurrentBag<Server>();
|
||||||
@ -85,7 +89,7 @@ namespace IW4MAdmin.Application
|
|||||||
AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser(parserRegexFactory) };
|
AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser(parserRegexFactory) };
|
||||||
TokenAuthenticator = new TokenAuthentication();
|
TokenAuthenticator = new TokenAuthentication();
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_metaService = new MetaService();
|
_metaService = metaService;
|
||||||
_tokenSource = new CancellationTokenSource();
|
_tokenSource = new CancellationTokenSource();
|
||||||
_loggers.Add(0, logger);
|
_loggers.Add(0, logger);
|
||||||
_commands = commands.ToList();
|
_commands = commands.ToList();
|
||||||
@ -96,6 +100,8 @@ namespace IW4MAdmin.Application
|
|||||||
_customParserEvents = customParserEvents;
|
_customParserEvents = customParserEvents;
|
||||||
_eventHandler = eventHandler;
|
_eventHandler = eventHandler;
|
||||||
_scriptCommandFactory = scriptCommandFactory;
|
_scriptCommandFactory = scriptCommandFactory;
|
||||||
|
_metaRegistration = metaRegistration;
|
||||||
|
_scriptPluginServiceResolver = scriptPluginServiceResolver;
|
||||||
Plugins = plugins;
|
Plugins = plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,12 +279,12 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
if (plugin is ScriptPlugin scriptPlugin)
|
if (plugin is ScriptPlugin scriptPlugin)
|
||||||
{
|
{
|
||||||
await scriptPlugin.Initialize(this, _scriptCommandFactory);
|
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver);
|
||||||
scriptPlugin.Watcher.Changed += async (sender, e) =>
|
scriptPlugin.Watcher.Changed += async (sender, e) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await scriptPlugin.Initialize(this, _scriptCommandFactory);
|
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -445,7 +451,8 @@ namespace IW4MAdmin.Application
|
|||||||
Name = cmd.Name,
|
Name = cmd.Name,
|
||||||
Alias = cmd.Alias,
|
Alias = cmd.Alias,
|
||||||
MinimumPermission = cmd.Permission,
|
MinimumPermission = cmd.Permission,
|
||||||
AllowImpersonation = cmd.AllowImpersonation
|
AllowImpersonation = cmd.AllowImpersonation,
|
||||||
|
SupportedGames = cmd.SupportedGames
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,133 +460,7 @@ namespace IW4MAdmin.Application
|
|||||||
await _commandConfiguration.Save();
|
await _commandConfiguration.Save();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region META
|
_metaRegistration.Register();
|
||||||
async Task<List<ProfileMeta>> getProfileMeta(int clientId, int offset, int count, DateTime? startAt)
|
|
||||||
{
|
|
||||||
var metaList = new List<ProfileMeta>();
|
|
||||||
|
|
||||||
// we don't want to return anything because it means we're trying to retrieve paged meta data
|
|
||||||
if (count > 1)
|
|
||||||
{
|
|
||||||
return metaList;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastMapMeta = await _metaService.GetPersistentMeta("LastMapPlayed", new EFClient() { ClientId = clientId });
|
|
||||||
|
|
||||||
if (lastMapMeta != null)
|
|
||||||
{
|
|
||||||
metaList.Add(new ProfileMeta()
|
|
||||||
{
|
|
||||||
Id = lastMapMeta.MetaId,
|
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_MAP"],
|
|
||||||
Value = lastMapMeta.Value,
|
|
||||||
Show = true,
|
|
||||||
Type = ProfileMeta.MetaType.Information,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastServerMeta = await _metaService.GetPersistentMeta("LastServerPlayed", new EFClient() { ClientId = clientId });
|
|
||||||
|
|
||||||
if (lastServerMeta != null)
|
|
||||||
{
|
|
||||||
metaList.Add(new ProfileMeta()
|
|
||||||
{
|
|
||||||
Id = lastServerMeta.MetaId,
|
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_SERVER"],
|
|
||||||
Value = lastServerMeta.Value,
|
|
||||||
Show = true,
|
|
||||||
Type = ProfileMeta.MetaType.Information
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var client = await GetClientService().Get(clientId);
|
|
||||||
|
|
||||||
if (client == null)
|
|
||||||
{
|
|
||||||
_logger.WriteWarning($"No client found with id {clientId} when generating profile meta");
|
|
||||||
return metaList;
|
|
||||||
}
|
|
||||||
|
|
||||||
metaList.Add(new ProfileMeta()
|
|
||||||
{
|
|
||||||
Id = client.ClientId,
|
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]} {Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PLAYER"]}",
|
|
||||||
Value = Math.Round(client.TotalConnectionTime / 3600.0, 1).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
|
||||||
Show = true,
|
|
||||||
Column = 1,
|
|
||||||
Order = 0,
|
|
||||||
Type = ProfileMeta.MetaType.Information
|
|
||||||
});
|
|
||||||
|
|
||||||
metaList.Add(new ProfileMeta()
|
|
||||||
{
|
|
||||||
Id = client.ClientId,
|
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_FSEEN"],
|
|
||||||
Value = Utilities.GetTimePassed(client.FirstConnection, false),
|
|
||||||
Show = true,
|
|
||||||
Column = 1,
|
|
||||||
Order = 1,
|
|
||||||
Type = ProfileMeta.MetaType.Information
|
|
||||||
});
|
|
||||||
|
|
||||||
metaList.Add(new ProfileMeta()
|
|
||||||
{
|
|
||||||
Id = client.ClientId,
|
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_LSEEN"],
|
|
||||||
Value = Utilities.GetTimePassed(client.LastConnection, false),
|
|
||||||
Show = true,
|
|
||||||
Column = 1,
|
|
||||||
Order = 2,
|
|
||||||
Type = ProfileMeta.MetaType.Information
|
|
||||||
});
|
|
||||||
|
|
||||||
metaList.Add(new ProfileMeta()
|
|
||||||
{
|
|
||||||
Id = client.ClientId,
|
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"],
|
|
||||||
Value = client.Connections.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
|
||||||
Show = true,
|
|
||||||
Column = 1,
|
|
||||||
Order = 3,
|
|
||||||
Type = ProfileMeta.MetaType.Information
|
|
||||||
});
|
|
||||||
|
|
||||||
metaList.Add(new ProfileMeta()
|
|
||||||
{
|
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_MASKED"],
|
|
||||||
Value = client.Masked ? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"] : Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
|
|
||||||
Sensitive = true,
|
|
||||||
Column = 1,
|
|
||||||
Order = 4,
|
|
||||||
Type = ProfileMeta.MetaType.Information
|
|
||||||
});
|
|
||||||
|
|
||||||
return metaList;
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<List<ProfileMeta>> getPenaltyMeta(int clientId, int offset, int count, DateTime? startAt)
|
|
||||||
{
|
|
||||||
if (count <= 1)
|
|
||||||
{
|
|
||||||
return new List<ProfileMeta>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var penalties = await GetPenaltyService().GetClientPenaltyForMetaAsync(clientId, count, offset, startAt);
|
|
||||||
|
|
||||||
return penalties.Select(_penalty => new ProfileMeta()
|
|
||||||
{
|
|
||||||
Id = _penalty.Id,
|
|
||||||
Type = _penalty.PunisherId == clientId ? ProfileMeta.MetaType.Penalized : ProfileMeta.MetaType.ReceivedPenalty,
|
|
||||||
Value = _penalty,
|
|
||||||
When = _penalty.TimePunished,
|
|
||||||
Sensitive = _penalty.Sensitive
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
MetaService.AddRuntimeMeta(getProfileMeta);
|
|
||||||
MetaService.AddRuntimeMeta(getPenaltyMeta);
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region CUSTOM_EVENTS
|
#region CUSTOM_EVENTS
|
||||||
foreach (var customEvent in _customParserEvents.SelectMany(_events => _events.Events))
|
foreach (var customEvent in _customParserEvents.SelectMany(_events => _events.Events))
|
||||||
|
12
Application/BuildScripts/DownloadTranslations.ps1
Normal file
12
Application/BuildScripts/DownloadTranslations.ps1
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
param (
|
||||||
|
[string]$OutputDir = $(throw "-OutputDir is required.")
|
||||||
|
)
|
||||||
|
|
||||||
|
$localizations = @("en-US", "ru-RU", "es-EC", "pt-BR", "de-DE")
|
||||||
|
foreach($localization in $localizations)
|
||||||
|
{
|
||||||
|
$url = "http://api.raidmax.org:5000/localization/{0}" -f $localization
|
||||||
|
$filePath = "{0}Localization\IW4MAdmin.{1}.json" -f $OutputDir, $localization
|
||||||
|
$response = Invoke-WebRequest $url
|
||||||
|
Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8
|
||||||
|
}
|
@ -26,6 +26,10 @@ if not exist "%PublishDir%\Lib\" md "%PublishDir%\Lib\"
|
|||||||
move "%PublishDir%\*.dll" "%PublishDir%\Lib\"
|
move "%PublishDir%\*.dll" "%PublishDir%\Lib\"
|
||||||
move "%PublishDir%\*.json" "%PublishDir%\Lib\"
|
move "%PublishDir%\*.json" "%PublishDir%\Lib\"
|
||||||
move "%PublishDir%\runtimes" "%PublishDir%\Lib\runtimes"
|
move "%PublishDir%\runtimes" "%PublishDir%\Lib\runtimes"
|
||||||
|
move "%PublishDir%\ru" "%PublishDir%\Lib\ru"
|
||||||
|
move "%PublishDir%\de" "%PublishDir%\Lib\de"
|
||||||
|
move "%PublishDir%\pt" "%PublishDir%\Lib\pt"
|
||||||
|
move "%PublishDir%\es" "%PublishDir%\Lib\es"
|
||||||
if exist "%PublishDir%\refs" move "%PublishDir%\refs" "%PublishDir%\Lib\refs"
|
if exist "%PublishDir%\refs" move "%PublishDir%\refs" "%PublishDir%\Lib\refs"
|
||||||
|
|
||||||
echo making start scripts
|
echo making start scripts
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
set SolutionDir=%1
|
set SolutionDir=%1
|
||||||
set ProjectDir=%2
|
set ProjectDir=%2
|
||||||
set TargetDir=%3
|
set TargetDir=%3
|
||||||
|
|
||||||
|
echo D | xcopy "%SolutionDir%Plugins\ScriptPlugins\*.js" "%TargetDir%Plugins" /y
|
||||||
|
powershell -File "%ProjectDir%BuildScripts\DownloadTranslations.ps1" %TargetDir%
|
@ -130,7 +130,6 @@ namespace IW4MAdmin.Application.EventParsers
|
|||||||
|
|
||||||
int clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
int clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||||
|
|
||||||
// todo: these need to defined outside of here
|
|
||||||
if (message.StartsWith(_appConfig.CommandPrefix) || message.StartsWith(_appConfig.BroadcastCommandPrefix))
|
if (message.StartsWith(_appConfig.CommandPrefix) || message.StartsWith(_appConfig.BroadcastCommandPrefix))
|
||||||
{
|
{
|
||||||
return new GameEvent()
|
return new GameEvent()
|
||||||
@ -256,6 +255,7 @@ namespace IW4MAdmin.Application.EventParsers
|
|||||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||||
State = EFClient.ClientState.Connecting,
|
State = EFClient.ClientState.Connecting,
|
||||||
},
|
},
|
||||||
|
Extra = originIdString,
|
||||||
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
||||||
IsBlocking = true,
|
IsBlocking = true,
|
||||||
GameTime = gameTime,
|
GameTime = gameTime,
|
||||||
|
@ -13,17 +13,19 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
private readonly IRConConnectionFactory _rconConnectionFactory;
|
private readonly IRConConnectionFactory _rconConnectionFactory;
|
||||||
private readonly IGameLogReaderFactory _gameLogReaderFactory;
|
private readonly IGameLogReaderFactory _gameLogReaderFactory;
|
||||||
|
private readonly IMetaService _metaService;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// base constructor
|
/// base constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="translationLookup"></param>
|
/// <param name="translationLookup"></param>
|
||||||
/// <param name="rconConnectionFactory"></param>
|
/// <param name="rconConnectionFactory"></param>
|
||||||
public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory)
|
public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory, IMetaService metaService)
|
||||||
{
|
{
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
_rconConnectionFactory = rconConnectionFactory;
|
_rconConnectionFactory = rconConnectionFactory;
|
||||||
_gameLogReaderFactory = gameLogReaderFactory;
|
_gameLogReaderFactory = gameLogReaderFactory;
|
||||||
|
_metaService = metaService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -34,7 +36,7 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Server CreateServer(ServerConfiguration config, IManager manager)
|
public Server CreateServer(ServerConfiguration config, IManager manager)
|
||||||
{
|
{
|
||||||
return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory, _gameLogReaderFactory);
|
return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory, _gameLogReaderFactory, _metaService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction)
|
public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, bool isTargetRequired, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction)
|
||||||
{
|
{
|
||||||
var permissionEnum = Enum.Parse<Permission>(permission);
|
var permissionEnum = Enum.Parse<Permission>(permission);
|
||||||
var argsArray = args.Select(_arg => new CommandArgument
|
var argsArray = args.Select(_arg => new CommandArgument
|
||||||
@ -34,7 +34,7 @@ namespace IW4MAdmin.Application.Factories
|
|||||||
Required = _arg.Item2
|
Required = _arg.Item2
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
return new ScriptCommand(name, alias, description, permissionEnum, argsArray, executeAction, _config, _transLookup);
|
return new ScriptCommand(name, alias, description, isTargetRequired, permissionEnum, argsArray, executeAction, _config, _transLookup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ using SharedLibraryCore.Dtos;
|
|||||||
using SharedLibraryCore.Exceptions;
|
using SharedLibraryCore.Exceptions;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using SharedLibraryCore.Services;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -26,15 +25,17 @@ namespace IW4MAdmin
|
|||||||
private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex;
|
private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||||
public GameLogEventDetection LogEvent;
|
public GameLogEventDetection LogEvent;
|
||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
|
private readonly IMetaService _metaService;
|
||||||
private const int REPORT_FLAG_COUNT = 4;
|
private const int REPORT_FLAG_COUNT = 4;
|
||||||
private int lastGameTime = 0;
|
private int lastGameTime = 0;
|
||||||
|
|
||||||
public int Id { get; private set; }
|
public int Id { get; private set; }
|
||||||
|
|
||||||
public IW4MServer(IManager mgr, ServerConfiguration cfg, ITranslationLookup lookup,
|
public IW4MServer(IManager mgr, ServerConfiguration cfg, ITranslationLookup lookup,
|
||||||
IRConConnectionFactory connectionFactory, IGameLogReaderFactory gameLogReaderFactory) : base(cfg, mgr, connectionFactory, gameLogReaderFactory)
|
IRConConnectionFactory connectionFactory, IGameLogReaderFactory gameLogReaderFactory, IMetaService metaService) : base(cfg, mgr, connectionFactory, gameLogReaderFactory)
|
||||||
{
|
{
|
||||||
_translationLookup = lookup;
|
_translationLookup = lookup;
|
||||||
|
_metaService = metaService;
|
||||||
}
|
}
|
||||||
|
|
||||||
override public async Task<EFClient> OnClientConnected(EFClient clientFromLog)
|
override public async Task<EFClient> OnClientConnected(EFClient clientFromLog)
|
||||||
@ -475,8 +476,8 @@ namespace IW4MAdmin
|
|||||||
Time = DateTime.UtcNow
|
Time = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
await new MetaService().AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin);
|
await _metaService.AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin);
|
||||||
await new MetaService().AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin);
|
await _metaService.AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.PreDisconnect)
|
else if (E.Type == GameEvent.EventType.PreDisconnect)
|
||||||
@ -540,14 +541,18 @@ namespace IW4MAdmin
|
|||||||
.First(_qm => _qm.Game == GameName)
|
.First(_qm => _qm.Game == GameName)
|
||||||
.Messages[E.Data.Substring(1)];
|
.Messages[E.Data.Substring(1)];
|
||||||
}
|
}
|
||||||
catch { }
|
catch
|
||||||
|
{
|
||||||
|
message = E.Data.Substring(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatHistory.Add(new ChatInfo()
|
ChatHistory.Add(new ChatInfo()
|
||||||
{
|
{
|
||||||
Name = E.Origin.Name,
|
Name = E.Origin.Name,
|
||||||
Message = message,
|
Message = message,
|
||||||
Time = DateTime.UtcNow
|
Time = DateTime.UtcNow,
|
||||||
|
IsHidden = !string.IsNullOrEmpty(GamePassword)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -610,13 +615,10 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (E.Type == GameEvent.EventType.Broadcast)
|
if (E.Type == GameEvent.EventType.Broadcast)
|
||||||
{
|
{
|
||||||
#if DEBUG == false
|
if (!Utilities.IsDevelopment && E.Data != null) // hides broadcast when in development mode
|
||||||
// this is a little ugly but I don't want to change the abstract class
|
|
||||||
if (E.Data != null)
|
|
||||||
{
|
{
|
||||||
await E.Owner.ExecuteCommandAsync(E.Data);
|
await E.Owner.ExecuteCommandAsync(E.Data);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (ChatHistory)
|
lock (ChatHistory)
|
||||||
@ -783,7 +785,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
var polledClients = await PollPlayersAsync();
|
var polledClients = await PollPlayersAsync();
|
||||||
|
|
||||||
foreach (var disconnectingClient in polledClients[1])
|
foreach (var disconnectingClient in polledClients[1].Where(_client => !_client.IsZombieClient /* ignores "fake" zombie clients */))
|
||||||
{
|
{
|
||||||
disconnectingClient.CurrentServer = this;
|
disconnectingClient.CurrentServer = this;
|
||||||
var e = new GameEvent()
|
var e = new GameEvent()
|
||||||
@ -814,6 +816,7 @@ namespace IW4MAdmin
|
|||||||
Origin = client,
|
Origin = client,
|
||||||
Owner = this,
|
Owner = this,
|
||||||
IsBlocking = true,
|
IsBlocking = true,
|
||||||
|
Extra = client.GetAdditionalProperty<string>("BotGuid"),
|
||||||
Source = GameEvent.EventSource.Status
|
Source = GameEvent.EventSource.Status
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -984,6 +987,7 @@ namespace IW4MAdmin
|
|||||||
var logfile = await this.GetMappedDvarValueOrDefaultAsync<string>("g_log");
|
var logfile = await this.GetMappedDvarValueOrDefaultAsync<string>("g_log");
|
||||||
var logsync = await this.GetMappedDvarValueOrDefaultAsync<int>("g_logsync");
|
var logsync = await this.GetMappedDvarValueOrDefaultAsync<int>("g_logsync");
|
||||||
var ip = await this.GetMappedDvarValueOrDefaultAsync<string>("net_ip");
|
var ip = await this.GetMappedDvarValueOrDefaultAsync<string>("net_ip");
|
||||||
|
var gamePassword = await this.GetMappedDvarValueOrDefaultAsync("g_password", overrideDefault: "");
|
||||||
|
|
||||||
if (Manager.GetApplicationSettings().Configuration().EnableCustomSayName)
|
if (Manager.GetApplicationSettings().Configuration().EnableCustomSayName)
|
||||||
{
|
{
|
||||||
@ -1017,6 +1021,7 @@ namespace IW4MAdmin
|
|||||||
this.FSGame = game.Value;
|
this.FSGame = game.Value;
|
||||||
this.Gametype = gametype;
|
this.Gametype = gametype;
|
||||||
this.IP = ip.Value == "localhost" ? ServerConfig.IPAddress : ip.Value ?? ServerConfig.IPAddress;
|
this.IP = ip.Value == "localhost" ? ServerConfig.IPAddress : ip.Value ?? ServerConfig.IPAddress;
|
||||||
|
this.GamePassword = gamePassword.Value;
|
||||||
UpdateMap(mapname);
|
UpdateMap(mapname);
|
||||||
|
|
||||||
if (RconParser.CanGenerateLogPath)
|
if (RconParser.CanGenerateLogPath)
|
||||||
@ -1083,9 +1088,11 @@ namespace IW4MAdmin
|
|||||||
Logger.WriteInfo($"Log file is {LogPath}");
|
Logger.WriteInfo($"Log file is {LogPath}");
|
||||||
|
|
||||||
_ = Task.Run(() => LogEvent.PollForChanges());
|
_ = Task.Run(() => LogEvent.PollForChanges());
|
||||||
#if !DEBUG
|
|
||||||
Broadcast(loc["BROADCAST_ONLINE"]);
|
if (!Utilities.IsDevelopment)
|
||||||
#endif
|
{
|
||||||
|
Broadcast(loc["BROADCAST_ONLINE"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri[] GenerateUriForLog(string logPath, string gameLogServerUrl)
|
public Uri[] GenerateUriForLog(string logPath, string gameLogServerUrl)
|
||||||
@ -1233,6 +1240,7 @@ namespace IW4MAdmin
|
|||||||
if (targetClient.IsIngame)
|
if (targetClient.IsIngame)
|
||||||
{
|
{
|
||||||
string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"^7{loc["SERVER_TB_TEXT"]}- ^5{Reason}");
|
string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"^7{loc["SERVER_TB_TEXT"]}- ^5{Reason}");
|
||||||
|
Logger.WriteDebug($"Executing tempban kick command for {targetClient}");
|
||||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,23 @@
|
|||||||
using IW4MAdmin.Application.EventParsers;
|
using IW4MAdmin.Application.EventParsers;
|
||||||
using IW4MAdmin.Application.Factories;
|
using IW4MAdmin.Application.Factories;
|
||||||
using IW4MAdmin.Application.Helpers;
|
using IW4MAdmin.Application.Helpers;
|
||||||
|
using IW4MAdmin.Application.Meta;
|
||||||
using IW4MAdmin.Application.Migration;
|
using IW4MAdmin.Application.Migration;
|
||||||
using IW4MAdmin.Application.Misc;
|
using IW4MAdmin.Application.Misc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using RestEase;
|
using RestEase;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
using SharedLibraryCore.Exceptions;
|
using SharedLibraryCore.Exceptions;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using SharedLibraryCore.QueryHelper;
|
||||||
using SharedLibraryCore.Repositories;
|
using SharedLibraryCore.Repositories;
|
||||||
|
using SharedLibraryCore.Services;
|
||||||
|
using Stats.Dtos;
|
||||||
|
using StatsWeb;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -128,7 +135,11 @@ namespace IW4MAdmin.Application
|
|||||||
await ApplicationTask;
|
await ApplicationTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch { }
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
string failMessage = translationLookup == null ? "Failed to initalize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"];
|
||||||
|
Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}");
|
||||||
|
}
|
||||||
|
|
||||||
if (ServerManager.IsRestartRequested)
|
if (ServerManager.IsRestartRequested)
|
||||||
{
|
{
|
||||||
@ -222,7 +233,7 @@ namespace IW4MAdmin.Application
|
|||||||
serviceCollection.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
|
serviceCollection.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
|
||||||
.AddSingleton(new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings") as IConfigurationHandler<ApplicationConfiguration>)
|
.AddSingleton(new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings") as IConfigurationHandler<ApplicationConfiguration>)
|
||||||
.AddSingleton(new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as IConfigurationHandler<CommandConfiguration>)
|
.AddSingleton(new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as IConfigurationHandler<CommandConfiguration>)
|
||||||
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration())
|
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration() ?? new ApplicationConfiguration())
|
||||||
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>().Configuration() ?? new CommandConfiguration())
|
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>().Configuration() ?? new CommandConfiguration())
|
||||||
.AddSingleton<ILogger>(_serviceProvider => defaultLogger)
|
.AddSingleton<ILogger>(_serviceProvider => defaultLogger)
|
||||||
.AddSingleton<IPluginImporter, PluginImporter>()
|
.AddSingleton<IPluginImporter, PluginImporter>()
|
||||||
@ -235,6 +246,14 @@ namespace IW4MAdmin.Application
|
|||||||
.AddSingleton<IGameLogReaderFactory, GameLogReaderFactory>()
|
.AddSingleton<IGameLogReaderFactory, GameLogReaderFactory>()
|
||||||
.AddSingleton<IScriptCommandFactory, ScriptCommandFactory>()
|
.AddSingleton<IScriptCommandFactory, ScriptCommandFactory>()
|
||||||
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
|
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
|
||||||
|
.AddSingleton<IEntityService<EFClient>, ClientService>()
|
||||||
|
.AddSingleton<IMetaService, MetaService>()
|
||||||
|
.AddSingleton<IMetaRegistration, MetaRegistration>()
|
||||||
|
.AddSingleton<IScriptPluginServiceResolver, ScriptPluginServiceResolver>()
|
||||||
|
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>, ReceivedPenaltyResourceQueryHelper>()
|
||||||
|
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>, AdministeredPenaltyResourceQueryHelper>()
|
||||||
|
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>, UpdatedAliasResourceQueryHelper>()
|
||||||
|
.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>()
|
||||||
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
||||||
.AddSingleton(_serviceProvider =>
|
.AddSingleton(_serviceProvider =>
|
||||||
{
|
{
|
||||||
|
64
Application/Meta/AdministeredPenaltyResourceQueryHelper.cs
Normal file
64
Application/Meta/AdministeredPenaltyResourceQueryHelper.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
|
using SharedLibraryCore.Helpers;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using SharedLibraryCore.QueryHelper;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Meta
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// implementation of IResourceQueryHelper
|
||||||
|
/// query helper that retrieves administered penalties for provided client id
|
||||||
|
/// </summary>
|
||||||
|
public class AdministeredPenaltyResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
|
public AdministeredPenaltyResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
|
||||||
|
{
|
||||||
|
_contextFactory = contextFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ResourceQueryHelperResult<AdministeredPenaltyResponse>> QueryResource(ClientPaginationRequest query)
|
||||||
|
{
|
||||||
|
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
|
||||||
|
var iqPenalties = ctx.Penalties.AsNoTracking()
|
||||||
|
.Where(_penalty => query.ClientId == _penalty.PunisherId)
|
||||||
|
.Where(_penalty => _penalty.When < query.Before)
|
||||||
|
.OrderByDescending(_penalty => _penalty.When);
|
||||||
|
|
||||||
|
var penalties = await iqPenalties
|
||||||
|
.Take(query.Count)
|
||||||
|
.Select(_penalty => new AdministeredPenaltyResponse()
|
||||||
|
{
|
||||||
|
PenaltyId = _penalty.PenaltyId,
|
||||||
|
Offense = _penalty.Offense,
|
||||||
|
AutomatedOffense = _penalty.AutomatedOffense,
|
||||||
|
ClientId = _penalty.OffenderId,
|
||||||
|
OffenderName = _penalty.Offender.CurrentAlias.Name,
|
||||||
|
OffenderClientId = _penalty.Offender.ClientId,
|
||||||
|
PunisherClientId = _penalty.PunisherId,
|
||||||
|
PunisherName = _penalty.Punisher.CurrentAlias.Name,
|
||||||
|
PenaltyType = _penalty.Type,
|
||||||
|
When = _penalty.When,
|
||||||
|
ExpirationDate = _penalty.Expires,
|
||||||
|
IsLinked = _penalty.OffenderId != query.ClientId,
|
||||||
|
IsSensitive = _penalty.Type == EFPenalty.PenaltyType.Flag
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return new ResourceQueryHelperResult<AdministeredPenaltyResponse>
|
||||||
|
{
|
||||||
|
// todo: might need to do count at some point
|
||||||
|
RetrievedResultCount = penalties.Count,
|
||||||
|
Results = penalties
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
165
Application/Meta/MetaRegistration.cs
Normal file
165
Application/Meta/MetaRegistration.cs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using SharedLibraryCore.QueryHelper;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Meta
|
||||||
|
{
|
||||||
|
public class MetaRegistration : IMetaRegistration
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private ITranslationLookup _transLookup;
|
||||||
|
private readonly IMetaService _metaService;
|
||||||
|
private readonly IEntityService<EFClient> _clientEntityService;
|
||||||
|
private readonly IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> _receivedPenaltyHelper;
|
||||||
|
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> _administeredPenaltyHelper;
|
||||||
|
private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper;
|
||||||
|
|
||||||
|
public MetaRegistration(ILogger logger, IMetaService metaService, ITranslationLookup transLookup, IEntityService<EFClient> clientEntityService,
|
||||||
|
IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> receivedPenaltyHelper,
|
||||||
|
IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> administeredPenaltyHelper,
|
||||||
|
IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_transLookup = transLookup;
|
||||||
|
_metaService = metaService;
|
||||||
|
_clientEntityService = clientEntityService;
|
||||||
|
_receivedPenaltyHelper = receivedPenaltyHelper;
|
||||||
|
_administeredPenaltyHelper = administeredPenaltyHelper;
|
||||||
|
_updatedAliasHelper = updatedAliasHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register()
|
||||||
|
{
|
||||||
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, GetProfileMeta);
|
||||||
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, ReceivedPenaltyResponse>(MetaType.ReceivedPenalty, GetReceivedPenaltiesMeta);
|
||||||
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, AdministeredPenaltyResponse>(MetaType.Penalized, GetAdministeredPenaltiesMeta);
|
||||||
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, UpdatedAliasResponse>(MetaType.AliasUpdate, GetUpdatedAliasMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<InformationResponse>> GetProfileMeta(ClientPaginationRequest request)
|
||||||
|
{
|
||||||
|
var metaList = new List<InformationResponse>();
|
||||||
|
var lastMapMeta = await _metaService.GetPersistentMeta("LastMapPlayed", new EFClient() { ClientId = request.ClientId });
|
||||||
|
|
||||||
|
if (lastMapMeta != null)
|
||||||
|
{
|
||||||
|
metaList.Add(new InformationResponse()
|
||||||
|
{
|
||||||
|
ClientId = request.ClientId,
|
||||||
|
MetaId = lastMapMeta.MetaId,
|
||||||
|
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_MAP"],
|
||||||
|
Value = lastMapMeta.Value,
|
||||||
|
ShouldDisplay = true,
|
||||||
|
Type = MetaType.Information,
|
||||||
|
Column = 1,
|
||||||
|
Order = 6
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastServerMeta = await _metaService.GetPersistentMeta("LastServerPlayed", new EFClient() { ClientId = request.ClientId });
|
||||||
|
|
||||||
|
if (lastServerMeta != null)
|
||||||
|
{
|
||||||
|
metaList.Add(new InformationResponse()
|
||||||
|
{
|
||||||
|
ClientId = request.ClientId,
|
||||||
|
MetaId = lastServerMeta.MetaId,
|
||||||
|
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_SERVER"],
|
||||||
|
Value = lastServerMeta.Value,
|
||||||
|
ShouldDisplay = true,
|
||||||
|
Type = MetaType.Information,
|
||||||
|
Column = 0,
|
||||||
|
Order = 6
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = await _clientEntityService.Get(request.ClientId);
|
||||||
|
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
_logger.WriteWarning($"No client found with id {request.ClientId} when generating profile meta");
|
||||||
|
return metaList;
|
||||||
|
}
|
||||||
|
|
||||||
|
metaList.Add(new InformationResponse()
|
||||||
|
{
|
||||||
|
ClientId = client.ClientId,
|
||||||
|
Key = _transLookup["WEBFRONT_PROFILE_META_PLAY_TIME"],
|
||||||
|
Value = TimeSpan.FromHours(client.TotalConnectionTime / 3600.0).HumanizeForCurrentCulture(),
|
||||||
|
ShouldDisplay = true,
|
||||||
|
Column = 1,
|
||||||
|
Order = 0,
|
||||||
|
Type = MetaType.Information
|
||||||
|
});
|
||||||
|
|
||||||
|
metaList.Add(new InformationResponse()
|
||||||
|
{
|
||||||
|
ClientId = client.ClientId,
|
||||||
|
Key = _transLookup["WEBFRONT_PROFILE_META_FIRST_SEEN"],
|
||||||
|
Value = (DateTime.UtcNow - client.FirstConnection).HumanizeForCurrentCulture(),
|
||||||
|
ShouldDisplay = true,
|
||||||
|
Column = 1,
|
||||||
|
Order = 1,
|
||||||
|
Type = MetaType.Information
|
||||||
|
});
|
||||||
|
|
||||||
|
metaList.Add(new InformationResponse()
|
||||||
|
{
|
||||||
|
ClientId = client.ClientId,
|
||||||
|
Key = _transLookup["WEBFRONT_PROFILE_META_LAST_SEEN"],
|
||||||
|
Value = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
|
||||||
|
ShouldDisplay = true,
|
||||||
|
Column = 1,
|
||||||
|
Order = 2,
|
||||||
|
Type = MetaType.Information
|
||||||
|
});
|
||||||
|
|
||||||
|
metaList.Add(new InformationResponse()
|
||||||
|
{
|
||||||
|
ClientId = client.ClientId,
|
||||||
|
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"],
|
||||||
|
Value = client.Connections.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
|
ShouldDisplay = true,
|
||||||
|
Column = 1,
|
||||||
|
Order = 3,
|
||||||
|
Type = MetaType.Information
|
||||||
|
});
|
||||||
|
|
||||||
|
metaList.Add(new InformationResponse()
|
||||||
|
{
|
||||||
|
ClientId = client.ClientId,
|
||||||
|
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_MASKED"],
|
||||||
|
Value = client.Masked ? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"] : Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
|
||||||
|
IsSensitive = true,
|
||||||
|
Column = 1,
|
||||||
|
Order = 4,
|
||||||
|
Type = MetaType.Information
|
||||||
|
});
|
||||||
|
|
||||||
|
return metaList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<ReceivedPenaltyResponse>> GetReceivedPenaltiesMeta(ClientPaginationRequest request)
|
||||||
|
{
|
||||||
|
var penalties = await _receivedPenaltyHelper.QueryResource(request);
|
||||||
|
return penalties.Results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<AdministeredPenaltyResponse>> GetAdministeredPenaltiesMeta(ClientPaginationRequest request)
|
||||||
|
{
|
||||||
|
var penalties = await _administeredPenaltyHelper.QueryResource(request);
|
||||||
|
return penalties.Results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<UpdatedAliasResponse>> GetUpdatedAliasMeta(ClientPaginationRequest request)
|
||||||
|
{
|
||||||
|
var aliases = await _updatedAliasHelper.QueryResource(request);
|
||||||
|
return aliases.Results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
72
Application/Meta/ReceivedPenaltyResourceQueryHelper.cs
Normal file
72
Application/Meta/ReceivedPenaltyResourceQueryHelper.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
|
using SharedLibraryCore.Helpers;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using SharedLibraryCore.QueryHelper;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Meta
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// implementation of IResourceQueryHelper
|
||||||
|
/// used to pull in penalties applied to a given client id
|
||||||
|
/// </summary>
|
||||||
|
public class ReceivedPenaltyResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
|
public ReceivedPenaltyResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
|
||||||
|
{
|
||||||
|
_contextFactory = contextFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource(ClientPaginationRequest query)
|
||||||
|
{
|
||||||
|
var linkedPenaltyType = Utilities.LinkedPenaltyTypes();
|
||||||
|
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
|
||||||
|
var linkId = await ctx.Clients.AsNoTracking()
|
||||||
|
.Where(_client => _client.ClientId == query.ClientId)
|
||||||
|
.Select(_client => _client.AliasLinkId)
|
||||||
|
.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);
|
||||||
|
|
||||||
|
var penalties = await iqPenalties
|
||||||
|
.Take(query.Count)
|
||||||
|
.Select(_penalty => new ReceivedPenaltyResponse()
|
||||||
|
{
|
||||||
|
PenaltyId = _penalty.PenaltyId,
|
||||||
|
ClientId = query.ClientId,
|
||||||
|
Offense = _penalty.Offense,
|
||||||
|
AutomatedOffense = _penalty.AutomatedOffense,
|
||||||
|
OffenderClientId = _penalty.OffenderId,
|
||||||
|
OffenderName = _penalty.Offender.CurrentAlias.Name,
|
||||||
|
PunisherClientId = _penalty.PunisherId,
|
||||||
|
PunisherName = _penalty.Punisher.CurrentAlias.Name,
|
||||||
|
PenaltyType = _penalty.Type,
|
||||||
|
When = _penalty.When,
|
||||||
|
ExpirationDate = _penalty.Expires,
|
||||||
|
IsLinked = _penalty.OffenderId != query.ClientId,
|
||||||
|
IsSensitive = _penalty.Type == EFPenalty.PenaltyType.Flag
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return new ResourceQueryHelperResult<ReceivedPenaltyResponse>
|
||||||
|
{
|
||||||
|
// todo: maybe actually count
|
||||||
|
RetrievedResultCount = penalties.Count,
|
||||||
|
Results = penalties
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
Application/Meta/UpdatedAliasResourceQueryHelper.cs
Normal file
60
Application/Meta/UpdatedAliasResourceQueryHelper.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
|
using SharedLibraryCore.Helpers;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using SharedLibraryCore.QueryHelper;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Meta
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// implementation if IResrouceQueryHerlp
|
||||||
|
/// used to pull alias changes for given client id
|
||||||
|
/// </summary>
|
||||||
|
public class UpdatedAliasResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
|
public UpdatedAliasResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_contextFactory = contextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ResourceQueryHelperResult<UpdatedAliasResponse>> QueryResource(ClientPaginationRequest query)
|
||||||
|
{
|
||||||
|
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
int linkId = ctx.Clients.First(_client => _client.ClientId == query.ClientId).AliasLinkId;
|
||||||
|
|
||||||
|
var iqAliasUpdates = ctx.Aliases
|
||||||
|
.Where(_alias => _alias.LinkId == linkId)
|
||||||
|
.Where(_alias => _alias.DateAdded < query.Before)
|
||||||
|
.Where(_alias => _alias.IPAddress != null)
|
||||||
|
.OrderByDescending(_alias => _alias.DateAdded)
|
||||||
|
.Select(_alias => new UpdatedAliasResponse
|
||||||
|
{
|
||||||
|
MetaId = _alias.AliasId,
|
||||||
|
Name = _alias.Name,
|
||||||
|
IPAddress = _alias.IPAddress.ConvertIPtoString(),
|
||||||
|
When = _alias.DateAdded,
|
||||||
|
Type = MetaType.AliasUpdate,
|
||||||
|
IsSensitive = true
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = (await iqAliasUpdates
|
||||||
|
.Take(query.Count)
|
||||||
|
.ToListAsync())
|
||||||
|
.Distinct();
|
||||||
|
|
||||||
|
|
||||||
|
return new ResourceQueryHelperResult<UpdatedAliasResponse>
|
||||||
|
{
|
||||||
|
Results = result, // we can potentially have duplicates
|
||||||
|
RetrievedResultCount = result.Count()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
187
Application/Misc/MetaService.cs
Normal file
187
Application/Misc/MetaService.cs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Dtos;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using SharedLibraryCore.QueryHelper;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// implementation of IMetaService
|
||||||
|
/// used to add and retrieve runtime and persistent meta
|
||||||
|
/// </summary>
|
||||||
|
public class MetaService : IMetaService
|
||||||
|
{
|
||||||
|
private readonly IDictionary<MetaType, List<dynamic>> _metaActions;
|
||||||
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public MetaService(ILogger logger, IDatabaseContextFactory contextFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_metaActions = new Dictionary<MetaType, List<dynamic>>();
|
||||||
|
_contextFactory = contextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddPersistentMeta(string metaKey, string metaValue, EFClient client)
|
||||||
|
{
|
||||||
|
// this seems to happen if the client disconnects before they've had time to authenticate and be added
|
||||||
|
if (client.ClientId < 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var ctx = _contextFactory.CreateContext();
|
||||||
|
|
||||||
|
var existingMeta = await ctx.EFMeta
|
||||||
|
.Where(_meta => _meta.Key == metaKey)
|
||||||
|
.Where(_meta => _meta.ClientId == client.ClientId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (existingMeta != null)
|
||||||
|
{
|
||||||
|
existingMeta.Value = metaValue;
|
||||||
|
existingMeta.Updated = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ctx.EFMeta.Add(new EFMeta()
|
||||||
|
{
|
||||||
|
ClientId = client.ClientId,
|
||||||
|
Created = DateTime.UtcNow,
|
||||||
|
Key = metaKey,
|
||||||
|
Value = metaValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client)
|
||||||
|
{
|
||||||
|
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
|
||||||
|
return await ctx.EFMeta
|
||||||
|
.Where(_meta => _meta.Key == metaKey)
|
||||||
|
.Where(_meta => _meta.ClientId == client.ClientId)
|
||||||
|
.Select(_meta => new EFMeta()
|
||||||
|
{
|
||||||
|
MetaId = _meta.MetaId,
|
||||||
|
Key = _meta.Key,
|
||||||
|
ClientId = _meta.ClientId,
|
||||||
|
Value = _meta.Value
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRuntimeMeta<T, V>(MetaType metaKey, Func<T, Task<IEnumerable<V>>> metaAction) where T : PaginationRequest where V : IClientMeta
|
||||||
|
{
|
||||||
|
if (!_metaActions.ContainsKey(metaKey))
|
||||||
|
{
|
||||||
|
_metaActions.Add(metaKey, new List<dynamic>() { metaAction });
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_metaActions[metaKey].Add(metaAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request)
|
||||||
|
{
|
||||||
|
var meta = new List<IClientMeta>();
|
||||||
|
|
||||||
|
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)
|
||||||
|
.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));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessInformationMeta(allMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
meta = await _metaActions[metaType][0](request) as IEnumerable<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<T> ProcessInformationMeta<T>(IEnumerable<T> meta) where T : IClientMeta
|
||||||
|
{
|
||||||
|
var table = new List<List<T>>();
|
||||||
|
var metaWithColumn = meta
|
||||||
|
.Where(_meta => _meta.Column != null);
|
||||||
|
|
||||||
|
var columnGrouping = metaWithColumn
|
||||||
|
.GroupBy(_meta => _meta.Column);
|
||||||
|
|
||||||
|
var metaToSort = meta.Except(metaWithColumn).ToList();
|
||||||
|
|
||||||
|
foreach (var metaItem in columnGrouping)
|
||||||
|
{
|
||||||
|
table.Add(new List<T>(metaItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (metaToSort.Count > 0)
|
||||||
|
{
|
||||||
|
var sortingMeta = metaToSort.First();
|
||||||
|
|
||||||
|
int indexOfSmallestColumn()
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
int smallestColumnSize = int.MaxValue;
|
||||||
|
for (int i = 0; i < table.Count; i++)
|
||||||
|
{
|
||||||
|
if (table[i].Count < smallestColumnSize)
|
||||||
|
{
|
||||||
|
smallestColumnSize = table[i].Count;
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
int columnIndex = indexOfSmallestColumn();
|
||||||
|
|
||||||
|
sortingMeta.Column = columnIndex;
|
||||||
|
sortingMeta.Order = columnGrouping
|
||||||
|
.First(_group => _group.Key == columnIndex)
|
||||||
|
.Count();
|
||||||
|
|
||||||
|
table[columnIndex].Add(sortingMeta);
|
||||||
|
|
||||||
|
metaToSort.Remove(sortingMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
{
|
{
|
||||||
private readonly Action<GameEvent> _executeAction;
|
private readonly Action<GameEvent> _executeAction;
|
||||||
|
|
||||||
public ScriptCommand(string name, string alias, string description, Permission permission,
|
public ScriptCommand(string name, string alias, string description, bool isTargetRequired, Permission permission,
|
||||||
CommandArgument[] args, Action<GameEvent> executeAction, CommandConfiguration config, ITranslationLookup layout)
|
CommandArgument[] args, Action<GameEvent> executeAction, CommandConfiguration config, ITranslationLookup layout)
|
||||||
: base(config, layout)
|
: base(config, layout)
|
||||||
{
|
{
|
||||||
@ -24,6 +24,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
Name = name;
|
Name = name;
|
||||||
Alias = alias;
|
Alias = alias;
|
||||||
Description = description;
|
Description = description;
|
||||||
|
RequiresTarget = isTargetRequired;
|
||||||
Permission = permission;
|
Permission = permission;
|
||||||
Arguments = args;
|
Arguments = args;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
_onProcessing.Dispose();
|
_onProcessing.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory)
|
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory, IScriptPluginServiceResolver serviceResolver)
|
||||||
{
|
{
|
||||||
await _onProcessing.WaitAsync();
|
await _onProcessing.WaitAsync();
|
||||||
|
|
||||||
@ -114,6 +114,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
_scriptEngine.Execute(script);
|
_scriptEngine.Execute(script);
|
||||||
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
|
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
|
||||||
|
_scriptEngine.SetValue("_serviceResolver", serviceResolver);
|
||||||
dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject();
|
dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject();
|
||||||
|
|
||||||
Author = pluginObject.author;
|
Author = pluginObject.author;
|
||||||
@ -164,6 +165,11 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
successfullyLoaded = true;
|
successfullyLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
catch (JavaScriptException ex)
|
||||||
|
{
|
||||||
|
throw new PluginException($"An error occured while initializing script plugin: {ex.Error} (Line: {ex.Location.Start.Line}, Character: {ex.Location.Start.Column})") { PluginFile = _fileName };
|
||||||
|
}
|
||||||
|
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
@ -246,6 +252,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
string alias = dynamicCommand.alias;
|
string alias = dynamicCommand.alias;
|
||||||
string description = dynamicCommand.description;
|
string description = dynamicCommand.description;
|
||||||
string permission = dynamicCommand.permission;
|
string permission = dynamicCommand.permission;
|
||||||
|
bool targetRequired = false;
|
||||||
|
|
||||||
List<(string, bool)> args = new List<(string, bool)>();
|
List<(string, bool)> args = new List<(string, bool)>();
|
||||||
dynamic arguments = null;
|
dynamic arguments = null;
|
||||||
@ -260,6 +267,16 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
// arguments are optional
|
// arguments are optional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
targetRequired = dynamicCommand.targetRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (RuntimeBinderException)
|
||||||
|
{
|
||||||
|
// arguments are optional
|
||||||
|
}
|
||||||
|
|
||||||
if (arguments != null)
|
if (arguments != null)
|
||||||
{
|
{
|
||||||
foreach (var arg in dynamicCommand.arguments)
|
foreach (var arg in dynamicCommand.arguments)
|
||||||
@ -284,7 +301,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, args, execute));
|
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, targetRequired, args, execute));
|
||||||
}
|
}
|
||||||
|
|
||||||
return commandList;
|
return commandList;
|
||||||
|
48
Application/Misc/ScriptPluginServiceResolver.cs
Normal file
48
Application/Misc/ScriptPluginServiceResolver.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// implementation of IScriptPluginServiceResolver
|
||||||
|
/// </summary>
|
||||||
|
public class ScriptPluginServiceResolver : IScriptPluginServiceResolver
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public ScriptPluginServiceResolver(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ResolveService(string serviceName)
|
||||||
|
{
|
||||||
|
var serviceType = DetermineRootType(serviceName);
|
||||||
|
return _serviceProvider.GetService(serviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ResolveService(string serviceName, string[] genericParameters)
|
||||||
|
{
|
||||||
|
var serviceType = DetermineRootType(serviceName, genericParameters.Length);
|
||||||
|
var genericTypes = genericParameters.Select(_genericTypeParam => DetermineRootType(_genericTypeParam));
|
||||||
|
var resolvedServiceType = serviceType.MakeGenericType(genericTypes.ToArray());
|
||||||
|
return _serviceProvider.GetService(resolvedServiceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type DetermineRootType(string serviceName, int genericParamCount = 0)
|
||||||
|
{
|
||||||
|
var typeCollection = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
.SelectMany(t => t.GetTypes());
|
||||||
|
string generatedName = $"{serviceName}{(genericParamCount == 0 ? "" : $"`{genericParamCount}")}".ToLower();
|
||||||
|
var serviceType = typeCollection.FirstOrDefault(_type => _type.Name.ToLower() == generatedName);
|
||||||
|
|
||||||
|
if (serviceType == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"No object type '{serviceName}' defined in loaded assemblies");
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -203,10 +203,11 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
|
|
||||||
long networkId;
|
long networkId;
|
||||||
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
||||||
|
string networkIdString;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
|
networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
|
||||||
|
|
||||||
networkId = networkIdString.IsBotGuid() ?
|
networkId = networkIdString.IsBotGuid() ?
|
||||||
name.GenerateGuidFromString() :
|
name.GenerateGuidFromString() :
|
||||||
@ -234,6 +235,8 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
State = EFClient.ClientState.Connecting
|
State = EFClient.ClientState.Connecting
|
||||||
};
|
};
|
||||||
|
|
||||||
|
client.SetAdditionalProperty("BotGuid", networkIdString);
|
||||||
|
|
||||||
StatusPlayers.Add(client);
|
StatusPlayers.Add(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
GameFiles/PT6/t6r/data/maps/mp/gametypes/_clientids.gsc
Normal file
BIN
GameFiles/PT6/t6r/data/maps/mp/gametypes/_clientids.gsc
Normal file
Binary file not shown.
283
GameFiles/PT6/t6r/data/maps/mp/gametypes/_clientids.gsc.src
Normal file
283
GameFiles/PT6/t6r/data/maps/mp/gametypes/_clientids.gsc.src
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
#include maps\mp\_utility;
|
||||||
|
#include maps\mp\gametypes\_hud_util;
|
||||||
|
#include common_scripts\utility;
|
||||||
|
|
||||||
|
init()
|
||||||
|
{
|
||||||
|
level.clientid = 0;
|
||||||
|
level thread onplayerconnect();
|
||||||
|
level thread IW4MA_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
onplayerconnect()
|
||||||
|
{
|
||||||
|
for ( ;; )
|
||||||
|
{
|
||||||
|
level waittill( "connecting", player );
|
||||||
|
player.clientid = level.clientid;
|
||||||
|
level.clientid++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IW4MA_init()
|
||||||
|
{
|
||||||
|
SetDvarIfUninitialized( "sv_customcallbacks", true );
|
||||||
|
SetDvarIfUninitialized( "sv_framewaittime", 0.05 );
|
||||||
|
SetDvarIfUninitialized( "sv_additionalwaittime", 0.1 );
|
||||||
|
SetDvarIfUninitialized( "sv_maxstoredframes", 12 );
|
||||||
|
SetDvarIfUninitialized( "sv_printradarupdates", 0 );
|
||||||
|
SetDvarIfUninitialized( "sv_printradar_updateinterval", 500 );
|
||||||
|
SetDvarIfUninitialized( "sv_iw4madmin_url", "http://127.0.0.1:1624" );
|
||||||
|
|
||||||
|
level thread IW4MA_onPlayerConnect();
|
||||||
|
if (getDvarInt("sv_printradarupdates") == 1)
|
||||||
|
{
|
||||||
|
level thread runRadarUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
level waittill( "prematch_over" );
|
||||||
|
level.callbackPlayerKilled = ::Callback_PlayerKilled;
|
||||||
|
level.callbackPlayerDamage = ::Callback_PlayerDamage;
|
||||||
|
level.callbackPlayerDisconnect = ::Callback_PlayerDisconnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Does not exist in T6
|
||||||
|
SetDvarIfUninitialized(dvar, val)
|
||||||
|
{
|
||||||
|
curval = getDvar(dvar);
|
||||||
|
if (curval == "")
|
||||||
|
SetDvar(dvar,val);
|
||||||
|
}
|
||||||
|
|
||||||
|
IW4MA_onPlayerConnect( player )
|
||||||
|
{
|
||||||
|
for( ;; )
|
||||||
|
{
|
||||||
|
level waittill( "connected", player );
|
||||||
|
player thread waitForFrameThread();
|
||||||
|
//player thread waitForAttack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Does not work in T6
|
||||||
|
/*waitForAttack()
|
||||||
|
{
|
||||||
|
self endon( "disconnect" );
|
||||||
|
|
||||||
|
self.lastAttackTime = 0;
|
||||||
|
|
||||||
|
for( ;; )
|
||||||
|
{
|
||||||
|
self notifyOnPlayerCommand( "player_shot", "+attack" );
|
||||||
|
self waittill( "player_shot" );
|
||||||
|
|
||||||
|
self.lastAttackTime = getTime();
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
runRadarUpdates()
|
||||||
|
{
|
||||||
|
interval = int(getDvar("sv_printradar_updateinterval"));
|
||||||
|
|
||||||
|
for ( ;; )
|
||||||
|
{
|
||||||
|
for ( i = 0; i <= 17; i++ )
|
||||||
|
{
|
||||||
|
player = level.players[i];
|
||||||
|
|
||||||
|
if ( isDefined( player ) )
|
||||||
|
{
|
||||||
|
payload = player.guid + ";" + player.origin + ";" + player getPlayerAngles() + ";" + player.team + ";" + player.kills + ";" + player.deaths + ";" + player.score + ";" + player GetCurrentWeapon() + ";" + player.health + ";" + isAlive(player) + ";" + player.timePlayed["total"];
|
||||||
|
logPrint( "LiveRadar;" + payload + "\n" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wait( interval / 1000 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hitLocationToBone( hitloc )
|
||||||
|
{
|
||||||
|
switch( hitloc )
|
||||||
|
{
|
||||||
|
case "helmet":
|
||||||
|
return "j_helmet";
|
||||||
|
case "head":
|
||||||
|
return "j_head";
|
||||||
|
case "neck":
|
||||||
|
return "j_neck";
|
||||||
|
case "torso_upper":
|
||||||
|
return "j_spineupper";
|
||||||
|
case "torso_lower":
|
||||||
|
return "j_spinelower";
|
||||||
|
case "right_arm_upper":
|
||||||
|
return "j_shoulder_ri";
|
||||||
|
case "left_arm_upper":
|
||||||
|
return "j_shoulder_le";
|
||||||
|
case "right_arm_lower":
|
||||||
|
return "j_elbow_ri";
|
||||||
|
case "left_arm_lower":
|
||||||
|
return "j_elbow_le";
|
||||||
|
case "right_hand":
|
||||||
|
return "j_wrist_ri";
|
||||||
|
case "left_hand":
|
||||||
|
return "j_wrist_le";
|
||||||
|
case "right_leg_upper":
|
||||||
|
return "j_hip_ri";
|
||||||
|
case "left_leg_upper":
|
||||||
|
return "j_hip_le";
|
||||||
|
case "right_leg_lower":
|
||||||
|
return "j_knee_ri";
|
||||||
|
case "left_leg_lower":
|
||||||
|
return "j_knee_le";
|
||||||
|
case "right_foot":
|
||||||
|
return "j_ankle_ri";
|
||||||
|
case "left_foot":
|
||||||
|
return "j_ankle_le";
|
||||||
|
default:
|
||||||
|
return "tag_origin";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForFrameThread()
|
||||||
|
{
|
||||||
|
self endon( "disconnect" );
|
||||||
|
|
||||||
|
self.currentAnglePosition = 0;
|
||||||
|
self.anglePositions = [];
|
||||||
|
|
||||||
|
for (i = 0; i < getDvarInt( "sv_maxstoredframes" ); i++)
|
||||||
|
{
|
||||||
|
self.anglePositions[i] = self getPlayerAngles();
|
||||||
|
}
|
||||||
|
|
||||||
|
for( ;; )
|
||||||
|
{
|
||||||
|
self.anglePositions[self.currentAnglePosition] = self getPlayerAngles();
|
||||||
|
wait( getDvarFloat( "sv_framewaittime" ) );
|
||||||
|
self.currentAnglePosition = (self.currentAnglePosition + 1) % getDvarInt( "sv_maxstoredframes" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForAdditionalAngles( logString, beforeFrameCount, afterFrameCount )
|
||||||
|
{
|
||||||
|
currentIndex = self.currentAnglePosition;
|
||||||
|
wait( 0.05 * afterFrameCount );
|
||||||
|
|
||||||
|
self.angleSnapshot = [];
|
||||||
|
|
||||||
|
for( j = 0; j < self.anglePositions.size; j++ )
|
||||||
|
{
|
||||||
|
self.angleSnapshot[j] = self.anglePositions[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
anglesStr = "";
|
||||||
|
collectedFrames = 0;
|
||||||
|
i = currentIndex - beforeFrameCount;
|
||||||
|
|
||||||
|
while (collectedFrames < beforeFrameCount)
|
||||||
|
{
|
||||||
|
fixedIndex = i;
|
||||||
|
if (i < 0)
|
||||||
|
{
|
||||||
|
fixedIndex = self.angleSnapshot.size - abs(i);
|
||||||
|
}
|
||||||
|
anglesStr += self.angleSnapshot[int(fixedIndex)] + ":";
|
||||||
|
collectedFrames++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == currentIndex)
|
||||||
|
{
|
||||||
|
anglesStr += self.angleSnapshot[i] + ":";
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
collectedFrames = 0;
|
||||||
|
|
||||||
|
while (collectedFrames < afterFrameCount)
|
||||||
|
{
|
||||||
|
fixedIndex = i;
|
||||||
|
if (i > self.angleSnapshot.size - 1)
|
||||||
|
{
|
||||||
|
fixedIndex = i % self.angleSnapshot.size;
|
||||||
|
}
|
||||||
|
anglesStr += self.angleSnapshot[int(fixedIndex)] + ":";
|
||||||
|
collectedFrames++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAttack = 100;//int(getTime()) - int(self.lastAttackTime);
|
||||||
|
isAlive = isAlive(self);
|
||||||
|
|
||||||
|
logPrint(logString + ";" + anglesStr + ";" + isAlive + ";" + lastAttack + "\n" );
|
||||||
|
}
|
||||||
|
|
||||||
|
vectorScale( vector, scale )
|
||||||
|
{
|
||||||
|
return ( vector[0] * scale, vector[1] * scale, vector[2] * scale );
|
||||||
|
}
|
||||||
|
|
||||||
|
Process_Hit( type, attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon )
|
||||||
|
{
|
||||||
|
if (sMeansOfDeath == "MOD_FALLING" || !isPlayer(attacker))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
victim = self;
|
||||||
|
_attacker = attacker;
|
||||||
|
|
||||||
|
if ( !isPlayer( attacker ) && isDefined( attacker.owner ) )
|
||||||
|
{
|
||||||
|
_attacker = attacker.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if( !isPlayer( attacker ) && sMeansOfDeath == "MOD_FALLING" )
|
||||||
|
{
|
||||||
|
_attacker = victim;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = victim GetTagOrigin( hitLocationToBone( sHitLoc ) );
|
||||||
|
isKillstreakKill = false;
|
||||||
|
if(!isPlayer(attacker))
|
||||||
|
{
|
||||||
|
isKillstreakKill = true;
|
||||||
|
}
|
||||||
|
if(maps/mp/killstreaks/_killstreaks::iskillstreakweapon(sWeapon))
|
||||||
|
{
|
||||||
|
isKillstreakKill = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
logLine = "Script" + type + ";" + _attacker.guid + ";" + victim.guid + ";" + _attacker GetTagOrigin("tag_eye") + ";" + location + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + ";" + _attacker getPlayerAngles() + ";" + int(gettime()) + ";" + isKillstreakKill + ";" + _attacker playerADS() + ";0;0";
|
||||||
|
attacker thread waitForAdditionalAngles( logLine, 2, 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
Callback_PlayerDamage( eInflictor, attacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex )
|
||||||
|
{
|
||||||
|
if ( level.teamBased && isDefined( attacker ) && ( self != attacker ) && isDefined( attacker.team ) && ( self.pers[ "team" ] == attacker.team ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( self.health - iDamage > 0 )
|
||||||
|
{
|
||||||
|
self Process_Hit( "Damage", attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon );
|
||||||
|
}
|
||||||
|
|
||||||
|
self [[maps/mp/gametypes/_globallogic_player::callback_playerdamage]]( eInflictor, attacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex );
|
||||||
|
}
|
||||||
|
|
||||||
|
Callback_PlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration)
|
||||||
|
{
|
||||||
|
Process_Hit( "Kill", attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon );
|
||||||
|
self [[maps/mp/gametypes/_globallogic_player::callback_playerkilled]]( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration );
|
||||||
|
}
|
||||||
|
|
||||||
|
Callback_PlayerDisconnect()
|
||||||
|
{
|
||||||
|
level notify( "disconnected", self );
|
||||||
|
self [[maps/mp/gametypes/_globallogic_player::callback_playerdisconnect]]();
|
||||||
|
}
|
@ -8,8 +8,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
GameFiles\IW4x\userraw\scripts\_commands.gsc = GameFiles\IW4x\userraw\scripts\_commands.gsc
|
GameFiles\IW4x\userraw\scripts\_commands.gsc = GameFiles\IW4x\userraw\scripts\_commands.gsc
|
||||||
GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc = GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc
|
GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc = GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc
|
||||||
azure-pipelines.yml = azure-pipelines.yml
|
|
||||||
PostPublish.ps1 = PostPublish.ps1
|
PostPublish.ps1 = PostPublish.ps1
|
||||||
|
pre-release-pipeline.yml = pre-release-pipeline.yml
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
RunPublishPre.cmd = RunPublishPre.cmd
|
RunPublishPre.cmd = RunPublishPre.cmd
|
||||||
RunPublishRelease.cmd = RunPublishRelease.cmd
|
RunPublishRelease.cmd = RunPublishRelease.cmd
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -74,14 +74,14 @@ namespace LiveRadar.Web.Controllers
|
|||||||
[Route("Radar/Update")]
|
[Route("Radar/Update")]
|
||||||
public IActionResult Update(string payload)
|
public IActionResult Update(string payload)
|
||||||
{
|
{
|
||||||
var radarUpdate = RadarEvent.Parse(payload);
|
/*var radarUpdate = RadarEvent.Parse(payload);
|
||||||
var client = _manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
var client = _manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
||||||
|
|
||||||
if (client != null)
|
if (client != null)
|
||||||
{
|
{
|
||||||
radarUpdate.Name = client.Name.StripColors();
|
radarUpdate.Name = client.Name.StripColors();
|
||||||
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
33
Plugins/LiveRadar/Events/Script.cs
Normal file
33
Plugins/LiveRadar/Events/Script.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using EventGeneratorCallback = System.ValueTuple<string, string,
|
||||||
|
System.Func<string, SharedLibraryCore.Interfaces.IEventParserConfiguration,
|
||||||
|
SharedLibraryCore.GameEvent,
|
||||||
|
SharedLibraryCore.GameEvent>>;
|
||||||
|
|
||||||
|
namespace LiveRadar.Events
|
||||||
|
{
|
||||||
|
public class Script : IRegisterEvent
|
||||||
|
{
|
||||||
|
private const string EVENT_LIVERADAR = "LiveRadar";
|
||||||
|
private EventGeneratorCallback LiveRadar()
|
||||||
|
{
|
||||||
|
return (EVENT_LIVERADAR, EVENT_LIVERADAR, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
|
||||||
|
{
|
||||||
|
string[] lineSplit = eventLine.Split(";");
|
||||||
|
|
||||||
|
autoEvent.Type = GameEvent.EventType.Other;
|
||||||
|
autoEvent.Subtype = EVENT_LIVERADAR;
|
||||||
|
autoEvent.Origin = new EFClient() { NetworkId = 0 };
|
||||||
|
autoEvent.Extra = lineSplit[1]; // guid
|
||||||
|
|
||||||
|
return autoEvent;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<EventGeneratorCallback> Events => new[] { LiveRadar() };
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@ using SharedLibraryCore;
|
|||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -17,12 +18,14 @@ namespace LiveRadar
|
|||||||
public string Author => "RaidMax";
|
public string Author => "RaidMax";
|
||||||
|
|
||||||
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
|
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
|
||||||
|
private readonly Dictionary<string, long> _botGuidLookups;
|
||||||
private bool addedPage;
|
private bool addedPage;
|
||||||
private readonly object lockObject = new object();
|
private readonly object lockObject = new object();
|
||||||
|
|
||||||
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
|
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
|
||||||
{
|
{
|
||||||
_configurationHandler = configurationHandlerFactory.GetConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration");
|
_configurationHandler = configurationHandlerFactory.GetConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration");
|
||||||
|
_botGuidLookups = new Dictionary<string, long>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnEventAsync(GameEvent E, Server S)
|
public Task OnEventAsync(GameEvent E, Server S)
|
||||||
@ -41,28 +44,45 @@ namespace LiveRadar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (E.Type == GameEvent.EventType.Unknown)
|
if (E.Type == GameEvent.EventType.PreConnect && E.Origin.IsBot)
|
||||||
{
|
{
|
||||||
if (E.Data?.StartsWith("LiveRadar") ?? false)
|
string botKey = $"BotGuid_{E.Extra}";
|
||||||
|
lock (lockObject)
|
||||||
{
|
{
|
||||||
try
|
if (!_botGuidLookups.ContainsKey(botKey))
|
||||||
{
|
{
|
||||||
var radarUpdate = RadarEvent.Parse(E.Data);
|
_botGuidLookups.Add(botKey, E.Origin.NetworkId);
|
||||||
var client = S.Manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (client != null)
|
if (E.Type == GameEvent.EventType.Other && E.Subtype == "LiveRadar")
|
||||||
{
|
{
|
||||||
radarUpdate.Name = client.Name.StripColors();
|
try
|
||||||
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
{
|
||||||
}
|
string botKey = $"BotGuid_{E.Extra}";
|
||||||
|
long generatedBotGuid;
|
||||||
|
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
generatedBotGuid = _botGuidLookups.ContainsKey(botKey) ? _botGuidLookups[botKey] : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
var radarUpdate = RadarEvent.Parse(E.Data, generatedBotGuid);
|
||||||
|
var client = S.Manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
||||||
|
|
||||||
|
if (client != null)
|
||||||
{
|
{
|
||||||
S.Logger.WriteWarning($"Could not parse live radar output: {e.Data}");
|
radarUpdate.Name = client.Name.StripColors();
|
||||||
S.Logger.WriteDebug(e.GetExceptionInfo());
|
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
S.Logger.WriteWarning($"Could not parse live radar output: {e.Data}");
|
||||||
|
S.Logger.WriteDebug(e.GetExceptionInfo());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LiveRadar
|
namespace LiveRadar
|
||||||
{
|
{
|
||||||
@ -39,13 +37,13 @@ namespace LiveRadar
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RadarEvent Parse(string input)
|
public static RadarEvent Parse(string input, long generatedBotGuid)
|
||||||
{
|
{
|
||||||
var items = input.Split(';').Skip(1).ToList();
|
var items = input.Split(';').Skip(1).ToList();
|
||||||
|
|
||||||
var parsedEvent = new RadarEvent()
|
var parsedEvent = new RadarEvent()
|
||||||
{
|
{
|
||||||
Guid = items[0].ConvertGuidToLong(System.Globalization.NumberStyles.HexNumber),
|
Guid = generatedBotGuid,
|
||||||
Location = Vector3.Parse(items[1]),
|
Location = Vector3.Parse(items[1]),
|
||||||
ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(),
|
ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(),
|
||||||
Team = items[3],
|
Team = items[3],
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -20,6 +20,7 @@ var plugin = {
|
|||||||
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick_for_reason {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick_for_reason {0} "{1}"';
|
||||||
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick_for_reason {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick_for_reason {0} "{1}"';
|
||||||
rconParser.Configuration.CommandPrefixes.RConGetDvar = '\xff\xff\xff\xffrcon {0} get {1}';
|
rconParser.Configuration.CommandPrefixes.RConGetDvar = '\xff\xff\xff\xffrcon {0} get {1}';
|
||||||
|
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined; // adding this in here temporarily until getInfo is fixed in new T6 version
|
||||||
|
|
||||||
rconParser.Configuration.Dvar.Pattern = '^(.+) is "(.+)?"$';
|
rconParser.Configuration.Dvar.Pattern = '^(.+) is "(.+)?"$';
|
||||||
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
||||||
|
@ -3,7 +3,7 @@ var eventParser;
|
|||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: 0.6,
|
version: 0.7,
|
||||||
name: 'Tekno MW3 Parser',
|
name: 'Tekno MW3 Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ var plugin = {
|
|||||||
rconParser.Configuration.Dvar.Pattern = '^(.*)$';
|
rconParser.Configuration.Dvar.Pattern = '^(.*)$';
|
||||||
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
||||||
|
rconParser.Configuration.OverrideDvarNameMapping.Add('_website', 'sv_clanWebsite');
|
||||||
|
|
||||||
rconParser.Version = 'IW5 MP 1.4 build 382 latest Thu Jan 19 2012 11:09:49AM win-x86';
|
rconParser.Version = 'IW5 MP 1.4 build 382 latest Thu Jan 19 2012 11:09:49AM win-x86';
|
||||||
rconParser.GameName = 3; // IW5
|
rconParser.GameName = 3; // IW5
|
||||||
|
@ -7,6 +7,8 @@ let commands = [{
|
|||||||
alias: "pp",
|
alias: "pp",
|
||||||
// required
|
// required
|
||||||
permission: "User",
|
permission: "User",
|
||||||
|
// optional (defaults to false)
|
||||||
|
targetRequired: false,
|
||||||
// optional
|
// optional
|
||||||
arguments: [{
|
arguments: [{
|
||||||
name: "times to ping",
|
name: "times to ping",
|
||||||
@ -44,6 +46,8 @@ let plugin = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onLoadAsync: function (manager) {
|
onLoadAsync: function (manager) {
|
||||||
|
this.logger = _serviceResolver.ResolveService("ILogger");
|
||||||
|
this.logger.WriteDebug("sample plugin loaded");
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnloadAsync: function () {
|
onUnloadAsync: function () {
|
||||||
|
@ -20,7 +20,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
Offset,
|
Offset,
|
||||||
Strain,
|
Strain,
|
||||||
Recoil,
|
Recoil,
|
||||||
Snap
|
Snap,
|
||||||
|
Button
|
||||||
};
|
};
|
||||||
|
|
||||||
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
||||||
@ -38,11 +39,12 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
ILogger Log;
|
ILogger Log;
|
||||||
Strain Strain;
|
Strain Strain;
|
||||||
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
||||||
private double sessionAverageRecoilAmount;
|
private double mapAverageRecoilAmount;
|
||||||
private double sessionAverageSnapAmount;
|
private double sessionAverageSnapAmount;
|
||||||
private int sessionSnapHits;
|
private int sessionSnapHits;
|
||||||
private EFClientKill lastHit;
|
private EFClientKill lastHit;
|
||||||
private int validRecoilHitCount;
|
private int validRecoilHitCount;
|
||||||
|
private int validButtonHitCount;
|
||||||
|
|
||||||
private class HitInfo
|
private class HitInfo
|
||||||
{
|
{
|
||||||
@ -282,18 +284,30 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
|
|
||||||
#region RECOIL
|
#region RECOIL
|
||||||
float hitRecoilAverage = 0;
|
float hitRecoilAverage = 0;
|
||||||
if (!Plugin.Config.Configuration().RecoilessWeapons.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex)))
|
bool shouldIgnoreDetection = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Recoil]
|
||||||
|
.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldIgnoreDetection)
|
||||||
{
|
{
|
||||||
validRecoilHitCount++;
|
validRecoilHitCount++;
|
||||||
hitRecoilAverage = (hit.AnglesList.Sum(_angle => _angle.Z) + hit.ViewAngles.Z) / (hit.AnglesList.Count + 1);
|
hitRecoilAverage = (hit.AnglesList.Sum(_angle => _angle.Z) + hit.ViewAngles.Z) / (hit.AnglesList.Count + 1);
|
||||||
sessionAverageRecoilAmount = (sessionAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount;
|
mapAverageRecoilAmount = (mapAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount;
|
||||||
|
|
||||||
if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && sessionAverageRecoilAmount == 0)
|
if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && mapAverageRecoilAmount == 0)
|
||||||
{
|
{
|
||||||
results.Add(new DetectionPenaltyResult()
|
results.Add(new DetectionPenaltyResult()
|
||||||
{
|
{
|
||||||
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
||||||
Value = sessionAverageRecoilAmount,
|
Value = mapAverageRecoilAmount,
|
||||||
HitCount = HitCount,
|
HitCount = HitCount,
|
||||||
Type = DetectionType.Recoil
|
Type = DetectionType.Recoil
|
||||||
});
|
});
|
||||||
@ -301,6 +315,37 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region BUTTON
|
||||||
|
try
|
||||||
|
{
|
||||||
|
shouldIgnoreDetection = false;
|
||||||
|
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Button]
|
||||||
|
.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldIgnoreDetection)
|
||||||
|
{
|
||||||
|
validButtonHitCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
double lastDiff = hit.TimeOffset - hit.TimeSinceLastAttack;
|
||||||
|
if (validButtonHitCount > 0 && lastDiff <= 0)
|
||||||
|
{
|
||||||
|
results.Add(new DetectionPenaltyResult()
|
||||||
|
{
|
||||||
|
ClientPenalty = EFPenalty.PenaltyType.Ban,
|
||||||
|
Value = lastDiff,
|
||||||
|
HitCount = HitCount,
|
||||||
|
Type = DetectionType.Button
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region SESSION_RATIOS
|
#region SESSION_RATIOS
|
||||||
if (Kills >= Thresholds.LowSampleMinKills)
|
if (Kills >= Thresholds.LowSampleMinKills)
|
||||||
{
|
{
|
||||||
@ -384,7 +429,19 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
#region CHEST_ABDOMEN_RATIO_SESSION
|
#region CHEST_ABDOMEN_RATIO_SESSION
|
||||||
int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper].Count;
|
int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper].Count;
|
||||||
|
|
||||||
if (chestHits >= Thresholds.MediumSampleMinKills)
|
try
|
||||||
|
{
|
||||||
|
shouldIgnoreDetection = false; // reset previous value
|
||||||
|
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Chest]
|
||||||
|
.Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chestHits >= Thresholds.MediumSampleMinKills && !shouldIgnoreDetection)
|
||||||
{
|
{
|
||||||
double marginOfError = Thresholds.GetMarginOfError(chestHits);
|
double marginOfError = Thresholds.GetMarginOfError(chestHits);
|
||||||
double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
|
double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
|
||||||
@ -466,5 +523,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnMapChange()
|
||||||
|
{
|
||||||
|
mapAverageRecoilAmount = 0;
|
||||||
|
validRecoilHitCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
|
|
||||||
List<string> mostPlayed = new List<string>()
|
List<string> mostPlayed = new List<string>()
|
||||||
{
|
{
|
||||||
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
|
$"^5--{translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var db = new DatabaseContext(true))
|
using (var db = new DatabaseContext(true))
|
||||||
@ -51,8 +51,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
|
|
||||||
var iqList = await iqStats.ToListAsync();
|
var iqList = await iqStats.ToListAsync();
|
||||||
|
|
||||||
mostPlayed.AddRange(iqList.Select(stats =>
|
mostPlayed.AddRange(iqList.Select(stats => translationLookup["COMMANDS_MOST_PLAYED_FORMAT"].FormatExt(stats.Name, stats.Kills, (DateTime.UtcNow - DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime)).HumanizeForCurrentCulture())));
|
||||||
$"^3{stats.Name}^7 - ^5{stats.Kills} ^7{translationLookup["PLUGINS_STATS_TEXT_KILLS"]} | ^5{Utilities.GetTimePassed(DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime), false)} ^7{translationLookup["WEBFRONT_PROFILE_PLAYER"].ToLower()}"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
24
Plugins/Stats/Config/AnticheatConfiguration.cs
Normal file
24
Plugins/Stats/Config/AnticheatConfiguration.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
|
namespace Stats.Config
|
||||||
|
{
|
||||||
|
public class AnticheatConfiguration
|
||||||
|
{
|
||||||
|
public bool Enable { get; set; }
|
||||||
|
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; } = new Dictionary<long, DetectionType[]>();
|
||||||
|
public IList<long> IgnoredClientIds { get; set; } = new List<long>();
|
||||||
|
public IDictionary<Game, IDictionary<DetectionType, string[]>> IgnoredDetectionSpecification{ get; set; } = new Dictionary<Game, IDictionary<DetectionType, string[]>>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Game.IW4, new Dictionary<DetectionType, string[]>
|
||||||
|
{
|
||||||
|
{ DetectionType.Chest, new[] { "m21.+" } },
|
||||||
|
{ DetectionType.Recoil, new[] { "ranger.*_mp", "model1887.*_mp", ".+shotgun.*_mp" } },
|
||||||
|
{ DetectionType.Button, new[] { ".*akimbo.*" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||||
|
|
||||||
@ -8,21 +9,41 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
|||||||
{
|
{
|
||||||
public class StatsConfiguration : IBaseConfiguration
|
public class StatsConfiguration : IBaseConfiguration
|
||||||
{
|
{
|
||||||
public bool EnableAntiCheat { get; set; }
|
[Obsolete]
|
||||||
|
public bool? EnableAntiCheat { get; set; }
|
||||||
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }
|
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }
|
||||||
public List<StreakMessageConfiguration> DeathstreakMessages { get; set; }
|
public List<StreakMessageConfiguration> DeathstreakMessages { get; set; }
|
||||||
public List<string> RecoilessWeapons { get; set; }
|
|
||||||
public int TopPlayersMinPlayTime { get; set; }
|
public int TopPlayersMinPlayTime { get; set; }
|
||||||
public bool StoreClientKills { get; set; }
|
public bool StoreClientKills { get; set; }
|
||||||
public int MostKillsMaxInactivityDays { get; set; } = 30;
|
public int MostKillsMaxInactivityDays { get; set; } = 30;
|
||||||
public int MostKillsClientLimit { get; set; } = 5;
|
public int MostKillsClientLimit { get; set; } = 5;
|
||||||
public IDictionary<DetectionType, DistributionConfiguration> DetectionDistributions { get; set; }
|
[Obsolete]
|
||||||
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; }
|
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; }
|
||||||
|
public AnticheatConfiguration AnticheatConfiguration { get; set; } = new AnticheatConfiguration();
|
||||||
|
|
||||||
|
#pragma warning disable CS0612 // Type or member is obsolete
|
||||||
|
public void ApplyMigration()
|
||||||
|
{
|
||||||
|
if (ServerDetectionTypes != null)
|
||||||
|
{
|
||||||
|
AnticheatConfiguration.ServerDetectionTypes = ServerDetectionTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerDetectionTypes = null;
|
||||||
|
|
||||||
|
if (EnableAntiCheat != null)
|
||||||
|
{
|
||||||
|
AnticheatConfiguration.Enable = EnableAntiCheat.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnableAntiCheat = null;
|
||||||
|
}
|
||||||
|
#pragma warning restore CS0612 // Type or member is obsolete
|
||||||
|
|
||||||
public string Name() => "StatsPluginSettings";
|
public string Name() => "StatsPluginSettings";
|
||||||
public IBaseConfiguration Generate()
|
public IBaseConfiguration Generate()
|
||||||
{
|
{
|
||||||
EnableAntiCheat = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);
|
AnticheatConfiguration.Enable = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);
|
||||||
KillstreakMessages = new List<StreakMessageConfiguration>()
|
KillstreakMessages = new List<StreakMessageConfiguration>()
|
||||||
{
|
{
|
||||||
new StreakMessageConfiguration(){
|
new StreakMessageConfiguration(){
|
||||||
@ -57,16 +78,9 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
RecoilessWeapons = new List<string>()
|
|
||||||
{
|
|
||||||
"ranger.*_mp",
|
|
||||||
"model1887.*_mp",
|
|
||||||
".+shotgun.*_mp"
|
|
||||||
};
|
|
||||||
|
|
||||||
TopPlayersMinPlayTime = 3600 * 3;
|
TopPlayersMinPlayTime = 3600 * 3;
|
||||||
StoreClientKills = false;
|
StoreClientKills = false;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.QueryHelper;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace StatsWeb.Dtos
|
namespace Stats.Dtos
|
||||||
{
|
{
|
||||||
public class ChatSearchQuery : PaginationInfo
|
public class ChatSearchQuery : ClientPaginationRequest
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// specifies the partial content of the message to search for
|
/// specifies the partial content of the message to search for
|
||||||
@ -18,7 +18,7 @@ namespace StatsWeb.Dtos
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// identifier for the client
|
/// identifier for the client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? ClientId { get; set; }
|
public new int? ClientId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// only look for messages sent after this date
|
/// only look for messages sent after this date
|
||||||
@ -29,5 +29,10 @@ namespace StatsWeb.Dtos
|
|||||||
/// only look for messages sent before this date0
|
/// only look for messages sent before this date0
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime SentBefore { get; set; } = DateTime.UtcNow;
|
public DateTime SentBefore { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// indicates if the chat is on the meta page
|
||||||
|
/// </summary>
|
||||||
|
public bool IsProfileMeta { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -156,7 +156,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
Deaths = s.Deaths,
|
Deaths = s.Deaths,
|
||||||
Kills = s.Kills,
|
Kills = s.Kills,
|
||||||
KDR = Math.Round(s.KDR, 2),
|
KDR = Math.Round(s.KDR, 2),
|
||||||
LastSeen = Utilities.GetTimePassed(clientRatingsDict[s.ClientId].LastConnection, false),
|
LastSeen = (DateTime.UtcNow - clientRatingsDict[s.ClientId].LastConnection).HumanizeForCurrentCulture(),
|
||||||
Name = clientRatingsDict[s.ClientId].Name,
|
Name = clientRatingsDict[s.ClientId].Name,
|
||||||
Performance = Math.Round(clientRatingsDict[s.ClientId].Performance, 2),
|
Performance = Math.Round(clientRatingsDict[s.ClientId].Performance, 2),
|
||||||
RatingChange = ratingInfo.First(r => r.Key == s.ClientId).Ratings.First().Ranking - ratingInfo.First(r => r.Key == s.ClientId).Ratings.Last().Ranking,
|
RatingChange = ratingInfo.First(r => r.Key == s.ClientId).Ratings.First().Ranking - ratingInfo.First(r => r.Key == s.ClientId).Ratings.Last().Ranking,
|
||||||
@ -248,6 +248,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
ctx.Entry(server).Property(_prop => _prop.HostName).IsModified = true;
|
ctx.Entry(server).Property(_prop => _prop.HostName).IsModified = true;
|
||||||
ctx.SaveChanges();
|
ctx.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.Entry(server).Property(_prop => _prop.IsPasswordProtected).IsModified = true;
|
||||||
|
server.IsPasswordProtected = !string.IsNullOrEmpty(sv.GamePassword);
|
||||||
|
ctx.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
// check to see if the stats have ever been initialized
|
// check to see if the stats have ever been initialized
|
||||||
@ -477,7 +481,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
IsKill = !isDamage,
|
IsKill = !isDamage,
|
||||||
AnglesList = snapshotAngles,
|
AnglesList = snapshotAngles,
|
||||||
IsAlive = isAlive == "1",
|
IsAlive = isAlive == "1",
|
||||||
TimeSinceLastAttack = long.Parse(lastAttackTime)
|
TimeSinceLastAttack = long.Parse(lastAttackTime),
|
||||||
|
GameName = attacker.CurrentServer.GameName
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hit.HitLoc == IW4Info.HitLocation.shield)
|
if (hit.HitLoc == IW4Info.HitLocation.shield)
|
||||||
@ -535,7 +540,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Plugin.Config.Configuration().EnableAntiCheat && !attacker.IsBot && attacker.ClientId != victim.ClientId)
|
if (Plugin.Config.Configuration().AnticheatConfiguration.Enable && !attacker.IsBot && attacker.ClientId != victim.ClientId)
|
||||||
{
|
{
|
||||||
clientDetection.TrackedHits.Add(hit);
|
clientDetection.TrackedHits.Add(hit);
|
||||||
|
|
||||||
@ -551,10 +556,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
if (oldestHit.IsAlive)
|
if (oldestHit.IsAlive)
|
||||||
{
|
{
|
||||||
var result = DeterminePenaltyResult(clientDetection.ProcessHit(oldestHit), attacker.CurrentServer.EndPoint);
|
var result = DeterminePenaltyResult(clientDetection.ProcessHit(oldestHit), attacker);
|
||||||
#if !DEBUG
|
|
||||||
await ApplyPenalty(result, attacker);
|
if (!Utilities.IsDevelopment)
|
||||||
#endif
|
{
|
||||||
|
await ApplyPenalty(result, attacker);
|
||||||
|
}
|
||||||
|
|
||||||
if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any)
|
if (clientDetection.Tracker.HasChanges && result.ClientPenalty != EFPenalty.PenaltyType.Any)
|
||||||
{
|
{
|
||||||
@ -590,10 +597,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DetectionPenaltyResult DeterminePenaltyResult(IEnumerable<DetectionPenaltyResult> results, long serverId)
|
private DetectionPenaltyResult DeterminePenaltyResult(IEnumerable<DetectionPenaltyResult> results, EFClient client)
|
||||||
{
|
{
|
||||||
// allow disabling of certain detection types
|
// allow disabling of certain detection types
|
||||||
results = results.Where(_result => ShouldUseDetection(serverId, _result.Type));
|
results = results.Where(_result => ShouldUseDetection(client.CurrentServer, _result.Type, client.ClientId));
|
||||||
return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ??
|
return results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Ban) ??
|
||||||
results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ??
|
results.FirstOrDefault(_result => _result.ClientPenalty == EFPenalty.PenaltyType.Flag) ??
|
||||||
new DetectionPenaltyResult()
|
new DetectionPenaltyResult()
|
||||||
@ -613,21 +620,31 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldUseDetection(long serverId, DetectionType detectionType)
|
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
|
||||||
{
|
{
|
||||||
var detectionTypes = Plugin.Config.Configuration().ServerDetectionTypes;
|
var detectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.ServerDetectionTypes;
|
||||||
|
var ignoredClients = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredClientIds;
|
||||||
|
|
||||||
if (detectionTypes == null)
|
if (ignoredClients.Contains(clientId))
|
||||||
{
|
{
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!detectionTypes.ContainsKey(serverId))
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return true;
|
if (detectionTypes[server.EndPoint].Contains(detectionType))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return detectionTypes[serverId].Contains(detectionType);
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task ApplyPenalty(DetectionPenaltyResult penalty, EFClient attacker)
|
async Task ApplyPenalty(DetectionPenaltyResult penalty, EFClient attacker)
|
||||||
@ -663,6 +680,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
|
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
|
||||||
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}";
|
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}";
|
||||||
|
|
||||||
|
penaltyClient.AdministeredPenalties = new List<EFPenalty>()
|
||||||
|
{
|
||||||
|
new EFPenalty()
|
||||||
|
{
|
||||||
|
AutomatedOffense = flagReason
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
await attacker.Flag(flagReason, penaltyClient, new TimeSpan(168, 0, 0)).WaitAsync(Utilities.DefaultCommandTimeout, attacker.CurrentServer.Manager.CancellationToken);
|
await attacker.Flag(flagReason, penaltyClient, new TimeSpan(168, 0, 0)).WaitAsync(Utilities.DefaultCommandTimeout, attacker.CurrentServer.Manager.CancellationToken);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1127,10 +1152,15 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
public void ResetKillstreaks(Server sv)
|
public void ResetKillstreaks(Server sv)
|
||||||
{
|
{
|
||||||
foreach (var stat in sv.GetClientsAsList()
|
foreach (var session in sv.GetClientsAsList()
|
||||||
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY)))
|
.Select(_client => new
|
||||||
|
{
|
||||||
|
stat = _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY),
|
||||||
|
detection = _client.GetAdditionalProperty<Detection>(CLIENT_DETECTIONS_KEY)
|
||||||
|
}))
|
||||||
{
|
{
|
||||||
stat?.StartNewSession();
|
session.stat?.StartNewSession();
|
||||||
|
session.detection?.OnMapChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1220,6 +1250,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return 886229536;
|
return 886229536;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: this is not stable and will need to be migrated again...
|
||||||
long id = HashCode.Combine(server.IP, server.Port);
|
long id = HashCode.Combine(server.IP, server.Port);
|
||||||
id = id < 0 ? Math.Abs(id) : id;
|
id = id < 0 ? Math.Abs(id) : id;
|
||||||
long? serverId;
|
long? serverId;
|
||||||
|
@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Models
|
namespace IW4MAdmin.Plugins.Stats.Models
|
||||||
{
|
{
|
||||||
@ -44,6 +45,8 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
|||||||
public float AdsPercent { get; set; }
|
public float AdsPercent { get; set; }
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public List<Vector3> AnglesList { get; set; }
|
public List<Vector3> AnglesList { get; set; }
|
||||||
|
[NotMapped]
|
||||||
|
public Game GameName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the attacker was alive after last captured angle
|
/// Indicates if the attacker was alive after last captured angle
|
||||||
|
@ -16,5 +16,6 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
|||||||
public string EndPoint { get; set; }
|
public string EndPoint { get; set; }
|
||||||
public Game? GameName { get; set; }
|
public Game? GameName { get; set; }
|
||||||
public string HostName { get; set; }
|
public string HostName { get; set; }
|
||||||
|
public bool IsPasswordProtected { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,11 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Database;
|
using SharedLibraryCore.Database;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using SharedLibraryCore.Services;
|
using SharedLibraryCore.QueryHelper;
|
||||||
|
using Stats.Dtos;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -33,13 +34,17 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
#endif
|
#endif
|
||||||
private readonly IDatabaseContextFactory _databaseContextFactory;
|
private readonly IDatabaseContextFactory _databaseContextFactory;
|
||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
|
private readonly IMetaService _metaService;
|
||||||
|
private readonly IResourceQueryHelper<ChatSearchQuery, MessageResponse> _chatQueryHelper;
|
||||||
|
|
||||||
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory databaseContextFactory,
|
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory databaseContextFactory,
|
||||||
ITranslationLookup translationLookup)
|
ITranslationLookup translationLookup, IMetaService metaService, IResourceQueryHelper<ChatSearchQuery, MessageResponse> chatQueryHelper)
|
||||||
{
|
{
|
||||||
Config = configurationHandlerFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
|
Config = configurationHandlerFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
|
||||||
_databaseContextFactory = databaseContextFactory;
|
_databaseContextFactory = databaseContextFactory;
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
|
_metaService = metaService;
|
||||||
|
_chatQueryHelper = chatQueryHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnEventAsync(GameEvent E, Server S)
|
public async Task OnEventAsync(GameEvent E, Server S)
|
||||||
@ -177,8 +182,9 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
if (Config.Configuration() == null)
|
if (Config.Configuration() == null)
|
||||||
{
|
{
|
||||||
Config.Set((StatsConfiguration)new StatsConfiguration().Generate());
|
Config.Set((StatsConfiguration)new StatsConfiguration().Generate());
|
||||||
await Config.Save();
|
|
||||||
}
|
}
|
||||||
|
Config.Configuration().ApplyMigration();
|
||||||
|
await Config.Save();
|
||||||
|
|
||||||
// register the topstats page
|
// register the topstats page
|
||||||
// todo:generate the URL/Location instead of hardcoding
|
// todo:generate the URL/Location instead of hardcoding
|
||||||
@ -188,17 +194,14 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
"/Stats/TopPlayersAsync");
|
"/Stats/TopPlayersAsync");
|
||||||
|
|
||||||
// meta data info
|
// meta data info
|
||||||
async Task<List<ProfileMeta>> getStats(int clientId, int offset, int count, DateTime? startAt)
|
async Task<IEnumerable<InformationResponse>> getStats(ClientPaginationRequest request)
|
||||||
{
|
{
|
||||||
if (count > 1)
|
|
||||||
{
|
|
||||||
return new List<ProfileMeta>();
|
|
||||||
}
|
|
||||||
|
|
||||||
IList<EFClientStatistics> clientStats;
|
IList<EFClientStatistics> clientStats;
|
||||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
int messageCount = 0;
|
||||||
|
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false))
|
||||||
{
|
{
|
||||||
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == clientId).ToListAsync();
|
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync();
|
||||||
|
messageCount = await ctx.Set<EFClientMessage>().CountAsync(_message => _message.ClientId == request.ClientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
int kills = clientStats.Sum(c => c.Kills);
|
int kills = clientStats.Sum(c => c.Kills);
|
||||||
@ -209,73 +212,76 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
double performance = Math.Round(validPerformanceValues.Sum(c => c.Performance * c.TimePlayed / performancePlayTime), 2);
|
double performance = Math.Round(validPerformanceValues.Sum(c => c.Performance * c.TimePlayed / performancePlayTime), 2);
|
||||||
double spm = Math.Round(clientStats.Sum(c => c.SPM) / clientStats.Where(c => c.SPM > 0).Count(), 1);
|
double spm = Math.Round(clientStats.Sum(c => c.SPM) / clientStats.Where(c => c.SPM > 0).Count(), 1);
|
||||||
|
|
||||||
return new List<ProfileMeta>()
|
return new List<InformationResponse>()
|
||||||
{
|
{
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
|
||||||
Value = "#" + (await Manager.GetClientOverallRanking(clientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = "#" + (await Manager.GetClientOverallRanking(request.ClientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 0,
|
Order = 0,
|
||||||
Type = ProfileMeta.MetaType.Information
|
Type = MetaType.Information
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"],
|
||||||
Value = kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 1,
|
Order = 1,
|
||||||
Type = ProfileMeta.MetaType.Information
|
Type = MetaType.Information
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_DEATHS"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_DEATHS"],
|
||||||
Value = deaths.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = deaths.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 2,
|
Order = 2,
|
||||||
Type = ProfileMeta.MetaType.Information
|
Type = MetaType.Information
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"],
|
||||||
Value = kdr.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = kdr.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 3,
|
Order = 3,
|
||||||
Type = ProfileMeta.MetaType.Information
|
Type = MetaType.Information
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_PERFORMANCE"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PERFORMANCE"],
|
||||||
Value = performance.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = performance.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 4,
|
Order = 4,
|
||||||
Type = ProfileMeta.MetaType.Information
|
Type = MetaType.Information
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_META_SPM"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_META_SPM"],
|
||||||
Value = spm.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = spm.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 5,
|
Order = 5,
|
||||||
Type = ProfileMeta.MetaType.Information
|
Type = MetaType.Information
|
||||||
|
},
|
||||||
|
new InformationResponse()
|
||||||
|
{
|
||||||
|
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
|
||||||
|
Value = messageCount.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
|
Column = 1,
|
||||||
|
Order = 4,
|
||||||
|
Type = MetaType.Information
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<List<ProfileMeta>> getAnticheatInfo(int clientId, int offset, int count, DateTime? startAt)
|
async Task<IEnumerable<InformationResponse>> getAnticheatInfo(ClientPaginationRequest request)
|
||||||
{
|
{
|
||||||
if (count > 1)
|
|
||||||
{
|
|
||||||
return new List<ProfileMeta>();
|
|
||||||
}
|
|
||||||
|
|
||||||
IList<EFClientStatistics> clientStats;
|
IList<EFClientStatistics> clientStats;
|
||||||
|
|
||||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false))
|
||||||
{
|
{
|
||||||
clientStats = await ctx.Set<EFClientStatistics>()
|
clientStats = await ctx.Set<EFClientStatistics>()
|
||||||
.Include(c => c.HitLocations)
|
.Include(c => c.HitLocations)
|
||||||
.Where(c => c.ClientId == clientId)
|
.Where(c => c.ClientId == request.ClientId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,159 +316,103 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
averageSnapValue = clientStats.Any(_stats => _stats.AverageSnapValue > 0) ? clientStats.Where(_stats => _stats.AverageSnapValue > 0).Average(_stat => _stat.AverageSnapValue) : 0;
|
averageSnapValue = clientStats.Any(_stats => _stats.AverageSnapValue > 0) ? clientStats.Where(_stats => _stats.AverageSnapValue > 0).Average(_stat => _stat.AverageSnapValue) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new List<ProfileMeta>()
|
return new List<InformationResponse>()
|
||||||
{
|
{
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 1",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 1",
|
||||||
Value = chestRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = chestRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = ProfileMeta.MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Column = 2,
|
||||||
Order = 0,
|
Order = 0,
|
||||||
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"],
|
||||||
Sensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 2",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 2",
|
||||||
Value = abdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = abdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = ProfileMeta.MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Column = 2,
|
||||||
Order = 1,
|
Order = 1,
|
||||||
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"],
|
||||||
Sensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 3",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 3",
|
||||||
Value = chestAbdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = chestAbdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = ProfileMeta.MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Column = 2,
|
||||||
Order = 2,
|
Order = 2,
|
||||||
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"],
|
||||||
Sensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 4",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 4",
|
||||||
Value = headRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = headRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = ProfileMeta.MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Column = 2,
|
||||||
Order = 3,
|
Order = 3,
|
||||||
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"],
|
||||||
Sensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 5",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 5",
|
||||||
// todo: make sure this is wrapped somewhere else
|
// todo: make sure this is wrapped somewhere else
|
||||||
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
|
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
|
||||||
Type = ProfileMeta.MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Column = 2,
|
||||||
Order = 4,
|
Order = 4,
|
||||||
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"],
|
||||||
Sensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 6",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 6",
|
||||||
Value = Math.Round(maxStrain, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = Math.Round(maxStrain, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Type = ProfileMeta.MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Column = 2,
|
||||||
Order = 5,
|
Order = 5,
|
||||||
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"],
|
||||||
Sensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
new ProfileMeta()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 7",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 7",
|
||||||
Value = Math.Round(averageSnapValue, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = Math.Round(averageSnapValue, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Type = ProfileMeta.MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Column = 2,
|
||||||
Order = 6,
|
Order = 6,
|
||||||
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"],
|
||||||
Sensitive = true
|
IsSensitive = true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<List<ProfileMeta>> getMessages(int clientId, int offset, int count, DateTime? startAt)
|
async Task<IEnumerable<MessageResponse>> getMessages(ClientPaginationRequest request)
|
||||||
{
|
{
|
||||||
if (count <= 1)
|
var query = new ChatSearchQuery()
|
||||||
{
|
{
|
||||||
using (var ctx = new DatabaseContext(true))
|
ClientId = request.ClientId,
|
||||||
{
|
Before = request.Before,
|
||||||
return new List<ProfileMeta>
|
SentBefore = request.Before ?? DateTime.UtcNow,
|
||||||
{
|
Count = request.Count,
|
||||||
new ProfileMeta()
|
IsProfileMeta = true
|
||||||
{
|
};
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
|
|
||||||
Value = (await ctx.Set<EFClientMessage>()
|
|
||||||
.CountAsync(_message => _message.ClientId == clientId))
|
|
||||||
.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
|
||||||
Column = 1,
|
|
||||||
Order= 4,
|
|
||||||
Type = ProfileMeta.MetaType.Information
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ProfileMeta> messageMeta;
|
return (await _chatQueryHelper.QueryResource(query)).Results;
|
||||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
|
||||||
{
|
|
||||||
var messages = ctx.Set<EFClientMessage>()
|
|
||||||
.Where(m => m.ClientId == clientId)
|
|
||||||
.Where(_message => _message.TimeSent < startAt)
|
|
||||||
.OrderByDescending(_message => _message.TimeSent)
|
|
||||||
.Skip(offset)
|
|
||||||
.Take(count);
|
|
||||||
|
|
||||||
messageMeta = await messages.Select(m => new ProfileMeta()
|
|
||||||
{
|
|
||||||
Key = null,
|
|
||||||
Value = new { m.Message, m.Server.GameName },
|
|
||||||
When = m.TimeSent,
|
|
||||||
Extra = m.ServerId.ToString(),
|
|
||||||
Type = ProfileMeta.MetaType.ChatMessage
|
|
||||||
}).ToListAsync();
|
|
||||||
|
|
||||||
foreach (var message in messageMeta)
|
|
||||||
{
|
|
||||||
if ((message.Value.Message as string).IsQuickMessage())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var quickMessages = ServerManager.GetApplicationSettings().Configuration()
|
|
||||||
.QuickMessages
|
|
||||||
.First(_qm => _qm.Game == message.Value.GameName);
|
|
||||||
message.Value = quickMessages.Messages[(message.Value.Message as string).Substring(1)];
|
|
||||||
message.Type = ProfileMeta.MetaType.QuickMessage;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
message.Value = message.Value.Message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
message.Value = message.Value.Message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageMeta;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.Configuration().EnableAntiCheat)
|
if (Config.Configuration().AnticheatConfiguration.Enable)
|
||||||
{
|
{
|
||||||
MetaService.AddRuntimeMeta(getAnticheatInfo);
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, getAnticheatInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
MetaService.AddRuntimeMeta(getStats);
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, getStats);
|
||||||
MetaService.AddRuntimeMeta(getMessages);
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, MessageResponse>(MetaType.ChatMessage, getMessages);
|
||||||
|
|
||||||
async Task<string> totalKills(Server server)
|
async Task<string> totalKills(Server server)
|
||||||
{
|
{
|
||||||
@ -547,6 +497,6 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="s"></param>
|
/// <param name="s"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private bool ShouldOverrideAnticheatSetting(Server s) => Config.Configuration().EnableAntiCheat && s.GameName == Server.Game.IW5;
|
private bool ShouldOverrideAnticheatSetting(Server s) => Config.Configuration().AnticheatConfiguration.Enable && s.GameName == Server.Game.IW5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
using IW4MAdmin.Plugins.Stats.Models;
|
using IW4MAdmin.Plugins.Stats.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using StatsWeb.Dtos;
|
using Stats.Dtos;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -12,31 +16,44 @@ namespace StatsWeb
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// implementation of IResourceQueryHelper
|
/// implementation of IResourceQueryHelper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ChatResourceQueryHelper : IResourceQueryHelper<ChatSearchQuery, ChatSearchResult>
|
public class ChatResourceQueryHelper : IResourceQueryHelper<ChatSearchQuery, MessageResponse>
|
||||||
{
|
{
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
private List<EFServer> serverCache;
|
||||||
|
|
||||||
public ChatResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
|
public ChatResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory, ApplicationConfiguration appConfig)
|
||||||
{
|
{
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_appConfig = appConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<ResourceQueryHelperResult<ChatSearchResult>> QueryResource(ChatSearchQuery query)
|
public async Task<ResourceQueryHelperResult<MessageResponse>> QueryResource(ChatSearchQuery query)
|
||||||
{
|
{
|
||||||
if (query == null)
|
if (query == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Query must be specified");
|
throw new ArgumentException("Query must be specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new ResourceQueryHelperResult<ChatSearchResult>();
|
var result = new ResourceQueryHelperResult<MessageResponse>();
|
||||||
using var context = _contextFactory.CreateContext(enableTracking: false);
|
using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||||
|
|
||||||
|
if (serverCache == null)
|
||||||
|
{
|
||||||
|
serverCache = await context.Set<EFServer>().ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int.TryParse(query.ServerId, out int serverId))
|
||||||
|
{
|
||||||
|
query.ServerId = serverCache.FirstOrDefault(_server => _server.ServerId == serverId)?.EndPoint ?? query.ServerId;
|
||||||
|
}
|
||||||
|
|
||||||
var iqMessages = context.Set<EFClientMessage>()
|
var iqMessages = context.Set<EFClientMessage>()
|
||||||
.Where(_message => _message.TimeSent >= query.SentAfter)
|
.Where(_message => _message.TimeSent >= query.SentAfter)
|
||||||
.Where(_message => _message.TimeSent <= query.SentBefore);
|
.Where(_message => _message.TimeSent < query.SentBefore);
|
||||||
|
|
||||||
if (query.ClientId != null)
|
if (query.ClientId != null)
|
||||||
{
|
{
|
||||||
@ -50,27 +67,29 @@ namespace StatsWeb
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(query.MessageContains))
|
if (!string.IsNullOrEmpty(query.MessageContains))
|
||||||
{
|
{
|
||||||
iqMessages = iqMessages.Where(_message => EF.Functions.Like(_message.Message, $"%{query.MessageContains}%"));
|
iqMessages = iqMessages.Where(_message => EF.Functions.Like(_message.Message.ToLower(), $"%{query.MessageContains.ToLower()}%"));
|
||||||
}
|
}
|
||||||
|
|
||||||
var iqResponse = iqMessages
|
var iqResponse = iqMessages
|
||||||
.Select(_message => new ChatSearchResult
|
.Select(_message => new MessageResponse
|
||||||
{
|
{
|
||||||
ClientId = _message.ClientId,
|
ClientId = _message.ClientId,
|
||||||
ClientName = _message.Client.CurrentAlias.Name,
|
ClientName = query.IsProfileMeta ? "" : _message.Client.CurrentAlias.Name,
|
||||||
Date = _message.TimeSent,
|
ServerId = _message.ServerId,
|
||||||
|
When = _message.TimeSent,
|
||||||
Message = _message.Message,
|
Message = _message.Message,
|
||||||
ServerName = _message.Server.HostName
|
ServerName = query.IsProfileMeta ? "" : _message.Server.HostName,
|
||||||
|
GameName = _message.Server.GameName == null ? Server.Game.IW4 : _message.Server.GameName.Value
|
||||||
});
|
});
|
||||||
|
|
||||||
if (query.Direction == SharedLibraryCore.Dtos.SortDirection.Descending)
|
if (query.Direction == SharedLibraryCore.Dtos.SortDirection.Descending)
|
||||||
{
|
{
|
||||||
iqResponse = iqResponse.OrderByDescending(_message => _message.Date);
|
iqResponse = iqResponse.OrderByDescending(_message => _message.When);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
iqResponse = iqResponse.OrderBy(_message => _message.Date);
|
iqResponse = iqResponse.OrderBy(_message => _message.When);
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultList = await iqResponse
|
var resultList = await iqResponse
|
||||||
@ -78,6 +97,27 @@ namespace StatsWeb
|
|||||||
.Take(query.Count)
|
.Take(query.Count)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
foreach (var message in resultList)
|
||||||
|
{
|
||||||
|
message.IsHidden = serverCache.Any(server => server.ServerId == message.ServerId && server.IsPasswordProtected);
|
||||||
|
|
||||||
|
if (message.Message.IsQuickMessage())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var quickMessages = _appConfig
|
||||||
|
.QuickMessages
|
||||||
|
.First(_qm => _qm.Game == message.GameName);
|
||||||
|
message.Message = quickMessages.Messages[message.Message.Substring(1)];
|
||||||
|
message.IsQuickMessage = true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
message.Message = message.Message.Substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.TotalResultCount = await iqResponse.CountAsync();
|
result.TotalResultCount = await iqResponse.CountAsync();
|
||||||
result.Results = resultList;
|
result.Results = resultList;
|
||||||
result.RetrievedResultCount = resultList.Count;
|
result.RetrievedResultCount = resultList.Count;
|
||||||
|
@ -5,8 +5,9 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos;
|
||||||
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using StatsWeb.Dtos;
|
using Stats.Dtos;
|
||||||
using StatsWeb.Extensions;
|
using StatsWeb.Extensions;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -18,10 +19,10 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IManager _manager;
|
private readonly IManager _manager;
|
||||||
private readonly IResourceQueryHelper<ChatSearchQuery, ChatSearchResult> _chatResourceQueryHelper;
|
private readonly IResourceQueryHelper<ChatSearchQuery, MessageResponse> _chatResourceQueryHelper;
|
||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
|
|
||||||
public StatsController(ILogger logger, IManager manager, IResourceQueryHelper<ChatSearchQuery, ChatSearchResult> resourceQueryHelper,
|
public StatsController(ILogger logger, IManager manager, IResourceQueryHelper<ChatSearchQuery, MessageResponse> resourceQueryHelper,
|
||||||
ITranslationLookup translationLookup) : base(manager)
|
ITranslationLookup translationLookup) : base(manager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -36,6 +37,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"];
|
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"];
|
||||||
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"];
|
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"];
|
||||||
ViewBag.Servers = _manager.GetServers().Select(_server => new ServerInfo() { Name = _server.Hostname, ID = _server.EndPoint });
|
ViewBag.Servers = _manager.GetServers().Select(_server => new ServerInfo() { Name = _server.Hostname, ID = _server.EndPoint });
|
||||||
|
ViewBag.Localization = _translationLookup;
|
||||||
|
|
||||||
return View("Index");
|
return View("Index");
|
||||||
}
|
}
|
||||||
@ -71,51 +73,24 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> GetMessageAsync(int serverId, long when)
|
public async Task<IActionResult> GetMessageAsync(string serverId, long when)
|
||||||
{
|
{
|
||||||
var whenTime = DateTime.FromFileTimeUtc(when);
|
var whenTime = DateTime.FromFileTimeUtc(when);
|
||||||
var whenUpper = whenTime.AddMinutes(5);
|
var whenUpper = whenTime.AddMinutes(5);
|
||||||
var whenLower = whenTime.AddMinutes(-5);
|
var whenLower = whenTime.AddMinutes(-5);
|
||||||
|
|
||||||
using (var ctx = new SharedLibraryCore.Database.DatabaseContext(true))
|
var messages = await _chatResourceQueryHelper.QueryResource(new ChatSearchQuery()
|
||||||
{
|
{
|
||||||
var iqMessages = from message in ctx.Set<Stats.Models.EFClientMessage>()
|
ServerId = serverId,
|
||||||
where message.ServerId == serverId
|
SentBefore = whenUpper,
|
||||||
where message.TimeSent >= whenLower
|
SentAfter = whenLower
|
||||||
where message.TimeSent <= whenUpper
|
});
|
||||||
select new ChatInfo()
|
|
||||||
{
|
|
||||||
ClientId = message.ClientId,
|
|
||||||
Message = message.Message,
|
|
||||||
Name = message.Client.CurrentAlias.Name,
|
|
||||||
Time = message.TimeSent,
|
|
||||||
ServerGame = message.Server.GameName ?? Server.Game.IW4
|
|
||||||
};
|
|
||||||
|
|
||||||
var messages = await iqMessages.ToListAsync();
|
return View("_MessageContext", messages.Results);
|
||||||
|
|
||||||
foreach (var message in messages)
|
|
||||||
{
|
|
||||||
if (message.Message.IsQuickMessage())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var quickMessages = _manager.GetApplicationSettings().Configuration()
|
|
||||||
.QuickMessages
|
|
||||||
.First(_qm => _qm.Game == message.ServerGame);
|
|
||||||
message.Message = quickMessages.Messages[message.Message.Substring(1)];
|
|
||||||
message.IsQuickMessage = true;
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return View("_MessageContext", messages);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("Message/Find")]
|
[HttpGet("Message/Find")]
|
||||||
public async Task<IActionResult> FindMessage([FromQuery]string query)
|
public async Task<IActionResult> FindMessage([FromQuery] string query)
|
||||||
{
|
{
|
||||||
ViewBag.Localization = _translationLookup;
|
ViewBag.Localization = _translationLookup;
|
||||||
ViewBag.EnableColorCodes = _manager.GetApplicationSettings().Configuration().EnableColorCodes;
|
ViewBag.EnableColorCodes = _manager.GetApplicationSettings().Configuration().EnableColorCodes;
|
||||||
@ -150,7 +125,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("Message/FindNext")]
|
[HttpGet("Message/FindNext")]
|
||||||
public async Task<IActionResult> FindNextMessages([FromQuery]string query, [FromQuery]int count, [FromQuery]int offset)
|
public async Task<IActionResult> FindNextMessages([FromQuery] string query, [FromQuery] int count, [FromQuery] int offset)
|
||||||
{
|
{
|
||||||
ChatSearchQuery searchRequest;
|
ChatSearchQuery searchRequest;
|
||||||
|
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace StatsWeb.Dtos
|
|
||||||
{
|
|
||||||
public class ChatSearchResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// name of the client
|
|
||||||
/// </summary>
|
|
||||||
public string ClientName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// client id
|
|
||||||
/// </summary>
|
|
||||||
public int ClientId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// hostname of the server
|
|
||||||
/// </summary>
|
|
||||||
public string ServerName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// chat message
|
|
||||||
/// </summary>
|
|
||||||
public string Message { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// date the chat occured on
|
|
||||||
/// </summary>
|
|
||||||
public DateTime Date { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos;
|
||||||
using StatsWeb.Dtos;
|
using Stats.Dtos;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<ul class="nav nav-tabs border-top border-bottom nav-fill row" role="tablist" id="stats_top_players">
|
<ul class="nav nav-tabs border-top border-bottom nav-fill row" role="tablist" id="stats_top_players">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active top-players-link" href="#server_0" role="tab" data-toggle="tab" aria-selected="true" data-serverid="0">All Servers</a>
|
<a class="nav-link active top-players-link" href="#server_0" role="tab" data-toggle="tab" aria-selected="true" data-serverid="0">@ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"]</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@foreach (var server in ViewBag.Servers)
|
@foreach (var server in ViewBag.Servers)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@model SharedLibraryCore.Helpers.ResourceQueryHelperResult<StatsWeb.Dtos.ChatSearchResult>
|
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||||
|
@model SharedLibraryCore.Helpers.ResourceQueryHelperResult<MessageResponse>
|
||||||
|
|
||||||
@if (ViewBag.Error != null)
|
@if (ViewBag.Error != null)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@model IEnumerable<StatsWeb.Dtos.ChatSearchResult>
|
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||||
|
@model IEnumerable<MessageResponse>
|
||||||
|
|
||||||
@foreach (var message in Model)
|
@foreach (var message in Model)
|
||||||
{
|
{
|
||||||
@ -10,13 +11,20 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-light w-50 text-break">
|
<td class="text-light w-50 text-break">
|
||||||
<color-code value="@message.Message" allow="@ViewBag.EnableColorCodes"></color-code>
|
@if (message.IsHidden && !ViewBag.Authorized)
|
||||||
|
{
|
||||||
|
<color-code value="@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], message.HiddenMessage)" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<color-code value="@message.Message" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-light">
|
<td class="text-light">
|
||||||
<color-code value="@(message.ServerName ?? "--")" allow="@ViewBag.EnableColorCodes"></color-code>
|
<color-code value="@(message.ServerName ?? "--")" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right text-light">
|
<td class="text-right text-light">
|
||||||
@message.Date
|
@message.When
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -33,7 +41,14 @@
|
|||||||
<tr class="d-table-row d-lg-none bg-dark">
|
<tr class="d-table-row d-lg-none bg-dark">
|
||||||
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</th>
|
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</th>
|
||||||
<td class="text-light">
|
<td class="text-light">
|
||||||
<color-code value="@message.Message" allow="@ViewBag.EnableColorCodes"></color-code>
|
@if (message.IsHidden && !ViewBag.Authorized)
|
||||||
|
{
|
||||||
|
<color-code value="@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], message.HiddenMessage)" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<color-code value="@message.Message" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -47,7 +62,7 @@
|
|||||||
<tr class="d-table-row d-lg-none bg-dark">
|
<tr class="d-table-row d-lg-none bg-dark">
|
||||||
<th scope="row" class="bg-primary" style="border-bottom: 1px solid #222">@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
<th scope="row" class="bg-primary" style="border-bottom: 1px solid #222">@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
||||||
<td class="text-light mb-2 border-bottom">
|
<td class="text-light mb-2 border-bottom">
|
||||||
@message.Date
|
@message.When
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
@ -1,20 +1,21 @@
|
|||||||
@model IEnumerable<SharedLibraryCore.Dtos.ChatInfo>
|
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||||
|
@model IEnumerable<MessageResponse>
|
||||||
@{
|
@{
|
||||||
Layout = null;
|
Layout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="client-message-context">
|
<div class="client-message-context">
|
||||||
<h5 class="bg-primary pt-2 pb-2 pl-3 mb-0 mt-2 text-white">@Model.First().Time.ToString()</h5>
|
<h5 class="bg-primary pt-2 pb-2 pl-3 mb-0 mt-2 text-white">@Model.First().When.ToString()</h5>
|
||||||
<div class="bg-dark p-3 mb-2 border-bottom">
|
<div class="bg-dark p-3 mb-2 border-bottom">
|
||||||
@foreach (var message in Model)
|
@foreach (var message in Model)
|
||||||
{
|
{
|
||||||
<span class="text-white">
|
<span class="text-white">
|
||||||
<color-code value="@message.Name" allow="ViewBag.EnableColorCodes"></color-code>
|
<color-code value="@message.ClientName" allow="ViewBag.EnableColorCodes"></color-code>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
—
|
—
|
||||||
<span class="@(message.IsQuickMessage ? "font-italic" : "")">
|
<span class="@(message.IsQuickMessage ? "font-italic" : "")">
|
||||||
<color-code value="@message.Message" allow="ViewBag.EnableColorCodes"></color-code>
|
<color-code value="@(message.IsHidden ? message.HiddenMessage : message.Message)" allow="ViewBag.EnableColorCodes"></color-code>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.9" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
437
README.md
437
README.md
@ -1,26 +1,25 @@
|
|||||||
|
|
||||||
# IW4MAdmin
|
# IW4MAdmin [](https://github.com/RaidMax/IW4M-Admin/blob/2.4-pr/LICENSE) [](https://github.com/RaidMax/IW4M-Admin/stargazers)
|
||||||
### Quick Start Guide
|
[](https://ko-fi.com/J3J821KUJ)
|
||||||
### Version 2.4
|
|
||||||
_______
|
## About
|
||||||
### About
|
**IW4MAdmin** is an administration tool for [IW4x](https://iw4x.org/), [Pluto T6](https://forum.plutonium.pw/category/6/plutonium-t6), [Pluto IW5](https://forum.plutonium.pw/category/14/plutonium-iw5), [CoD4x](https://cod4x.me/), [TeknoMW3](https://github.com/Musta1337/TeknoMW3), and most Call of Duty® dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze.
|
||||||
**IW4MAdmin** is an administration tool for [IW4x](https://iw4xcachep26muba.onion.link/), [Pluto T6](https://forum.plutonium.pw/category/6/plutonium-t6), [Pluto IW5](https://forum.plutonium.pw/category/14/plutonium-iw5), [CoD4x](https://cod4x.me/), [TeknoMW3](https://www.teknomw3.pw/), and most Call of Duty® dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze.
|
|
||||||
### Download
|
### Download
|
||||||
Latest binary builds are always available at:
|
Latest binary builds are always available at:
|
||||||
- [GitHub](https://github.com/RaidMax/IW4M-Admin/releases)
|
- [GitHub](https://github.com/RaidMax/IW4M-Admin/releases)
|
||||||
- [RaidMax](https://raidmax.org/IW4MAdmin)
|
- [RaidMax](https://raidmax.org/IW4MAdmin)
|
||||||
|
|
||||||
---
|
|
||||||
### Setup
|
## Setup
|
||||||
**IW4MAdmin** requires minimal effort to get up and running.
|
**IW4MAdmin** requires minimal effort to get up and running.
|
||||||
#### Prerequisites
|
### Prerequisites
|
||||||
* [.NET Core 3.1.x Runtime](https://www.microsoft.com/net/download) *or newer*
|
* [.NET Core 3.1.x Runtime](https://www.microsoft.com/net/download) *or newer*
|
||||||
* [Direct Download (Windows)](https://dotnet.microsoft.com/download/dotnet-core/thank-you/runtime-aspnetcore-3.1.4-windows-hosting-bundle-installer)
|
* [Direct Download (Windows)](https://dotnet.microsoft.com/download/dotnet-core/thank-you/runtime-aspnetcore-3.1.4-windows-hosting-bundle-installer)
|
||||||
* [Package Installation (Linux)](https://docs.microsoft.com/en-us/dotnet/core/install/linux-package-manager-ubuntu-1910)
|
* [Package Installation (Linux)](https://docs.microsoft.com/en-us/dotnet/core/install/linux-package-manager-ubuntu-1910)
|
||||||
#### Installation
|
### Installation
|
||||||
1. Install .NET Core Runtime
|
1. Install .NET Core Runtime
|
||||||
2. Extract `IW4MAdmin-<version>.zip`
|
2. Extract `IW4MAdmin-<version>.zip`
|
||||||
#### Launching
|
### Launching
|
||||||
Windows
|
Windows
|
||||||
1. Run `StartIW4MAdmin.cmd`
|
1. Run `StartIW4MAdmin.cmd`
|
||||||
2. Configure **IW4MAdmin**
|
2. Configure **IW4MAdmin**
|
||||||
@ -41,421 +40,9 @@ Linux
|
|||||||
|
|
||||||
_Your configuration and database will be saved_
|
_Your configuration and database will be saved_
|
||||||
|
|
||||||
---
|
## Help
|
||||||
### Help
|
|
||||||
Feel free to join the **IW4MAdmin** [Discord](https://discord.gg/ZZFK5p3)
|
Feel free to join the **IW4MAdmin** [Discord](https://discord.gg/ZZFK5p3)
|
||||||
If you come across an issue, bug, or feature request please post an [issue](https://github.com/RaidMax/IW4M-Admin/issues)
|
If you come across an issue, bug, or feature request please post an [issue](https://github.com/RaidMax/IW4M-Admin/issues)
|
||||||
___
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
#### Initial Configuration
|
|
||||||
When **IW4MAdmin** is launched for the _first time_, you will be prompted to setup your configuration.
|
|
||||||
|
|
||||||
`Enable webfront`
|
#### Explore the [wiki](https://github.com/RaidMax/IW4M-Admin/wiki) to find more information.
|
||||||
* Enables you to monitor and control your server(s) through a web interface
|
|
||||||
* Default — `http://0.0.0.0:1624`
|
|
||||||
|
|
||||||
`Enable multiple owners`
|
|
||||||
* Enables more than one client to be promoted to level of `Owner`
|
|
||||||
* Default — `false`
|
|
||||||
|
|
||||||
`Enable stepped privilege hierarchy`
|
|
||||||
* Allows privileged clients to promote other clients to the level below their current level
|
|
||||||
* Default — `false`
|
|
||||||
|
|
||||||
`Enable custom say name`
|
|
||||||
* Shows a prefix to every message send by **IW4MAdmin** -- `[Admin] message`
|
|
||||||
* _This feature requires you specify a custom say name_
|
|
||||||
* _This feature only works on games that support the `sv_sayName` dvar_
|
|
||||||
* Default — `false`
|
|
||||||
|
|
||||||
`Enable social link`
|
|
||||||
* Shows a link to your community's social media/website on the webfront
|
|
||||||
* Default — `false`
|
|
||||||
|
|
||||||
`Use Custom Encoding Parser`
|
|
||||||
* Allows alternative encodings to be used for parsing game information and events
|
|
||||||
* **Russian users should use this and then specify** `windows-1251` **as the encoding string**
|
|
||||||
* Default — `false`
|
|
||||||
|
|
||||||
#### Server Configuration
|
|
||||||
After initial configuration is finished, you will be prompted to configure your servers for **IW4MAdmin**.
|
|
||||||
|
|
||||||
`Enter server IP Address`
|
|
||||||
* For almost all scenarios `127.0.0.1` is sufficient
|
|
||||||
* Default — `n/a`
|
|
||||||
|
|
||||||
`Enter server port`
|
|
||||||
* The port that your server is listening on (can be obtained via `net_port`)
|
|
||||||
* Default — `n/a`
|
|
||||||
|
|
||||||
`Enter server RCon password`
|
|
||||||
* The *\(R\)emote (Con)sole* password set in your server configuration (can be obtained via `rcon_password`)
|
|
||||||
* Default — `n/a`
|
|
||||||
|
|
||||||
`Enter number of reserved slots`
|
|
||||||
* The number of client slots reserved for privileged players (unavailable for regular users to occupy)
|
|
||||||
* For example, if you enter **2** reserved slots on an **18** slot server, you will have **16** publicly available slots
|
|
||||||
* Default — `0`
|
|
||||||
|
|
||||||
#### Advanced Configuration
|
|
||||||
If you wish to further customize your experience of **IW4MAdmin**, the following configuration file(s) will allow you to changes core options using any text-editor.
|
|
||||||
|
|
||||||
#### `IW4MAdminSettings.json`-- this file is created after initial setup
|
|
||||||
* This file uses the [JSON](https://en.wikipedia.org/wiki/JSON#JSON_sample) specification, so please validate your configuration before running **IW4MAdmin**
|
|
||||||
|
|
||||||
`WebfrontBindUrl`
|
|
||||||
* Specifies the address and port the webfront will listen on.
|
|
||||||
* The value can be an [IP Address](https://en.wikipedia.org/wiki/IP_address):port or [Domain Name](https://en.wikipedia.org/wiki/Domain_name):port
|
|
||||||
* Example http://gameserver.com:8080
|
|
||||||
* Default — `http://0.0.0.0:1624` (indicates that it will listen on all IP Addresses available to the default interface)
|
|
||||||
|
|
||||||
`CustomLocale`
|
|
||||||
* Specifies a [locale name](https://msdn.microsoft.com/en-us/library/39cwe7zf.aspx) to use instead of system default
|
|
||||||
* Locale must be from the `Equivalent Locale Name` column
|
|
||||||
* Default — `windows-1252`
|
|
||||||
|
|
||||||
`ConnectionString`
|
|
||||||
* Specifies the [connection string](https://www.connectionstrings.com/mysql/) to a MySQL server that is used instead of SQLite
|
|
||||||
* Default — `null`
|
|
||||||
|
|
||||||
`DatabaseProvider`
|
|
||||||
* Specifies the database provider **IW4MAdmin** should use
|
|
||||||
* Possible values — `sqlite`, `mysql`, `postgresql`
|
|
||||||
* Default — `sqlite`
|
|
||||||
|
|
||||||
`Ignore Bots`
|
|
||||||
* Disables bots from being registered and tracked by **IW4MAdmin**
|
|
||||||
|
|
||||||
`RConPollRate`
|
|
||||||
* Specifies (in milliseconds) how often to poll each server for updates
|
|
||||||
* Default — `5000`
|
|
||||||
|
|
||||||
`Servers`
|
|
||||||
* Specifies the list of servers **IW4MAdmin** will monitor
|
|
||||||
* Default — `[]`
|
|
||||||
* `IPAddress`
|
|
||||||
* Specifies the IP Address of the particular server
|
|
||||||
* Default — `n/a`
|
|
||||||
* `Port`
|
|
||||||
* Specifies the port of the particular server
|
|
||||||
* Default — `n/a`
|
|
||||||
* `Password`
|
|
||||||
* Specifies the `rcon_password` of the particular server
|
|
||||||
* Default — `n/a`
|
|
||||||
* `ManualLogPath`
|
|
||||||
* Specifies the log path to be used instead of the automatically generated one
|
|
||||||
* To use the `GameLogServer`, this should be set to the http address that the `GameLogServer` is listening on
|
|
||||||
* Example — http://gamelogserver.com/
|
|
||||||
* Default — `null`
|
|
||||||
* `AutoMessages`
|
|
||||||
* Specifies the list of messages that are broadcasted to the particular server
|
|
||||||
* Default — `[]`
|
|
||||||
* `Rules`
|
|
||||||
* Specifies the list of rules that apply to the particular server
|
|
||||||
* Default — `[]`
|
|
||||||
* `ReservedSlotNumber`
|
|
||||||
* Specifies the number of client slots to reserve for privileged users
|
|
||||||
* Default — `0`
|
|
||||||
* `GameLogServerUrl`
|
|
||||||
* Specifies the HTTP Url for the Game Log Server
|
|
||||||
* Default — `null`
|
|
||||||
|
|
||||||
`AutoMessagePeriod`
|
|
||||||
* Specifies (in seconds) how often messages should be broadcasted to each server
|
|
||||||
* Default — `60`
|
|
||||||
|
|
||||||
`AutoMessages`
|
|
||||||
* Specifies the list of messages that are broadcasted to **all** servers
|
|
||||||
* Specially formatted **tokens** can be used to broadcast dynamic information
|
|
||||||
* `{{TOTALPLAYERS}}` — displays how many players have connected
|
|
||||||
* `{{TOPSTATS}}` — displays the top 5 players on the server based on performance
|
|
||||||
* `{{MOSTPLAYED}}` — displays the top 5 players based on number of kills
|
|
||||||
* `{{TOTALPLAYTIME}}` — displays the cumulative play time (in man-hours) on all monitored servers
|
|
||||||
* `{{VERSION}}` — displays the version of **IW4MAdmin**
|
|
||||||
* `{{ADMINS}}` — displays the currently connected and *unmasked* privileged users online
|
|
||||||
* `{{NEXTMAP}}` — displays the next map and gametype in rotation
|
|
||||||
|
|
||||||
`GlobalRules`
|
|
||||||
* Specifies the list of rules that apply to **all** servers`
|
|
||||||
|
|
||||||
`Maps`
|
|
||||||
* Specifies the list of maps for each supported game
|
|
||||||
* `Name`
|
|
||||||
* Specifies the name of the map as returned by the game (usually the file name sans the file extension)
|
|
||||||
* `Alias`
|
|
||||||
* Specifies the display name of the map (as seen while loading in)
|
|
||||||
___
|
|
||||||
|
|
||||||
### Commands
|
|
||||||
|Name |Alias|Description |Requires Target|Syntax |Required Level|
|
|
||||||
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ---------------|
|
|
||||||
|prune|pa|demote any trusted clients that have not connected recently (defaults to 30 days)|False|!pa \<optional inactive days\>|Owner|
|
|
||||||
|quit|q|quit IW4MAdmin|False|!q |Owner|
|
|
||||||
|rcon|rcon|send rcon command to server|False|!rcon \<commands\>|Owner|
|
|
||||||
|ban|b|permanently ban a client from the server|True|!b \<player\> \<reason\>|SeniorAdmin|
|
|
||||||
|unban|ub|unban client by client id|True|!ub \<client id\> \<reason\>|SeniorAdmin|
|
|
||||||
|find|f|find client in database|False|!f \<player\>|Administrator|
|
|
||||||
|killserver|kill|kill the game server|False|!kill |Administrator|
|
|
||||||
|map|m|change to specified map|False|!m \<map\>|Administrator|
|
|
||||||
|maprotate|mr|cycle to the next map in rotation|False|!mr |Administrator|
|
|
||||||
|plugins|p|view all loaded plugins|False|!p |Administrator|
|
|
||||||
|tempban|tb|temporarily ban a client for specified time (defaults to 1 hour)|True|!tb \<player\> \<duration (m\|h\|d\|w\|y)\> \<reason\>|Administrator|
|
|
||||||
|alias|known|get past aliases and ips of a client|True|!known \<player\>|Moderator|
|
|
||||||
|baninfo|bi|get information about a ban for a client|True|!bi \<player\>|Moderator|
|
|
||||||
|fastrestart|fr|fast restart current map|False|!fr |Moderator|
|
|
||||||
|flag|fp|flag a suspicious client and announce to admins on join|True|!fp \<player\> \<reason\>|Moderator|
|
|
||||||
|kick|k|kick a client by name|True|!k \<player\> \<reason\>|Moderator|
|
|
||||||
|list|l|list active clients|False|!l |Moderator|
|
|
||||||
|mask|hide|hide your presence as a privileged client|False|!hide |Moderator|
|
|
||||||
|reports|reps|get or clear recent reports|False|!reps \<optional clear\>|Moderator|
|
|
||||||
|say|s|broadcast message to all clients|False|!s \<message\>|Moderator|
|
|
||||||
|setlevel|sl|set client to specified privilege level|True|!sl \<player\> \<level\>|Moderator|
|
|
||||||
|setpassword|sp|set your authentication password|False|!sp \<password\>|Moderator|
|
|
||||||
|unflag|uf|Remove flag for client|True|!uf \<player\>|Moderator|
|
|
||||||
|uptime|up|get current application running time|False|!up |Moderator|
|
|
||||||
|usage|us|get application memory usage|False|!us |Moderator|
|
|
||||||
|warn|w|warn client for infringing rules|True|!w \<player\> \<reason\>|Trusted|
|
|
||||||
|warnclear|wc|remove all warnings for a client|True|!wc \<player\>|Trusted|
|
|
||||||
|admins|a|list currently connected privileged clients|False|!a |User|
|
|
||||||
|getexternalip|ip|view your external IP address|False|!ip |User|
|
|
||||||
|help|h|list all available commands|False|!h \<optional commands\>|User|
|
|
||||||
|nextmap|nm|view next map in rotation|False|!nm |User|
|
|
||||||
|owner|iamgod|claim ownership of the server|False|!iamgod |User|
|
|
||||||
|ping|pi|get client's latency|False|!pi \<optional player\>|User|
|
|
||||||
|privatemessage|pm|send message to other client|True|!pm \<player\> \<message\>|User|
|
|
||||||
|report|rep|report a client for suspicious behavior|True|!rep \<player\> \<reason\>|User|
|
|
||||||
|rules|r|list server rules|False|!r |User|
|
|
||||||
|setgravatar|sg|set gravatar for webfront profile|False|!sg \<gravatar email\>|User|
|
|
||||||
|whoami|who|give information about yourself|False|!who |User|
|
|
||||||
|
|
||||||
|
|
||||||
_These commands include all shipped plugin commands._
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Player Identification
|
|
||||||
All players are identified 5 separate ways
|
|
||||||
1. `npID/GUID/XUID` - The ID corresponding to the player's hardware or forum account
|
|
||||||
2. `IP` - The player's IP Address
|
|
||||||
3. `Client ID` - The internal reference to a player, generated by **IW4MAdmin**
|
|
||||||
4. `Name` - The visible player name as it appears in game
|
|
||||||
5. `Client Number` - The slot the client occupies on a server. (The number ranges between 0 and the max number of clients allowed on the server)
|
|
||||||
|
|
||||||
For most commands players are identified by their `Name`
|
|
||||||
However, if they are currently offline, or their name contains un-typable characters, their `Client ID` must be used
|
|
||||||
|
|
||||||
The `Client ID` is specified by prefixing a player's reference number with `@`.
|
|
||||||
For example, `@123` would reference the player with a `Client ID` of 123.
|
|
||||||
|
|
||||||
**All commands that require a `target` look at the `first argument` for a form of player identification**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Additional Command Examples
|
|
||||||
`setlevel`
|
|
||||||
- _shortcut_ - `sl`
|
|
||||||
- _Parameter 1_ - Player to modify level of
|
|
||||||
- _Parameter 2_ - Level to set the player to ```[ User, Trusted, Moderator, Administrator, SeniorAdmin, Owner ]```
|
|
||||||
- _Example_ - `!setlevel Player1 SeniorAdmin`, `!sl @123 Moderator`
|
|
||||||
- **NOTE** - An `owner` cannot set another player's level to `owner` unless the configuration option is enabled during setup
|
|
||||||
|
|
||||||
`ban`
|
|
||||||
- _Shortcut_ - `b`
|
|
||||||
- _Parameter 1_ - Player to ban
|
|
||||||
- _Parameter 2_ - Reason for ban
|
|
||||||
- _Example_ - `!ban Player1 caught cheating`, `!b @123 GUID Spoofing`
|
|
||||||
|
|
||||||
`tempban`
|
|
||||||
- _Shortcut_ - `tb`
|
|
||||||
- _Parameter 1_ - Player to ban
|
|
||||||
- _Parameter 2_ - Ban length (minutes|hours|days|weeks|years)
|
|
||||||
- _Parameter 3_ - Reason for ban
|
|
||||||
- _Example_ - `!tempban Player1 3w racism`, `!tb @123 8h Abusive behaivor`
|
|
||||||
|
|
||||||
`reports`
|
|
||||||
- _Shortcut_ - `reps`
|
|
||||||
- _Optional Parameter 1_ - `clear` (erases reports for current server)
|
|
||||||
|
|
||||||
___
|
|
||||||
### Plugins
|
|
||||||
|
|
||||||
#### Welcome
|
|
||||||
- This plugin uses geo-location data to welcome a player based on their country of origin
|
|
||||||
- All privileged users ( Trusted or higher ) receive a specialized welcome message as well
|
|
||||||
- Welcome messages can be customized in `WelcomePluginSettings.json`
|
|
||||||
|
|
||||||
#### Stats
|
|
||||||
- This plugin calculates basic player performance, skill approximation, and kill/death ratio
|
|
||||||
- Skill is an number derived from an algorithmic processing of a player's Kill Death Ratio (KDR) and Score per Minute (SPM).
|
|
||||||
- Elo Rating is based off of the number of encounters a player wins.
|
|
||||||
- Performance is the average of Skill + Elo Rating
|
|
||||||
|
|
||||||
**Commands added by this plugin**
|
|
||||||
|
|
||||||
|Name |Alias|Description |Requires Target|Syntax |Required Level|
|
|
||||||
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|
|
|
||||||
|resetstats|rs|reset your stats to factory-new|False|!rs |User|
|
|
||||||
|stats|xlrstats|view your stats|False|!xlrstats \<optional player\>|User|
|
|
||||||
|topstats|ts|view the top 5 players on this server|False|!ts |User|
|
|
||||||
|mostplayed|mp|view the top 5 dedicated players on the server|False|!mp |User|
|
|
||||||
|
|
||||||
- To qualify for top stats, a client must have played for at least `3 hours` and connected within the past `15 days`.
|
|
||||||
|
|
||||||
#### Login
|
|
||||||
- This plugin deters GUID spoofing by requiring privileged users to login with their password before executing commands
|
|
||||||
- A password must be set using the `setpassword` command before logging in
|
|
||||||
|
|
||||||
**Commands added by this plugin**
|
|
||||||
|
|
||||||
|Name |Alias|Description |Requires Target|Syntax |Required Level|
|
|
||||||
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|
|
|
||||||
|login|l|login using password|False|!l \<password\>|Trusted|
|
|
||||||
|
|
||||||
#### Profanity Determent
|
|
||||||
- This plugin warns and kicks players for using profanity
|
|
||||||
- Profane words and warning message can be specified in `ProfanityDetermentSettings.json`
|
|
||||||
- If a client's name contains a word listed in the settings, they will immediately be kicked
|
|
||||||
|
|
||||||
#### IW4 Script Commands
|
|
||||||
- This plugin provides additional integration to IW4x
|
|
||||||
- In order to take advantage of it, copy the `userraw` folder into your IW4x server directory
|
|
||||||
|
|
||||||
#### VPN Detection [Script Plugin]
|
|
||||||
- This plugin detects if a client is using a VPN and kicks them if they are
|
|
||||||
- To disable this plugin, delete `Plugins\VPNDetection.js`
|
|
||||||
- Adding **Client IDs** to the `vpnExceptionIds` array will prevent a client from being kicked.
|
|
||||||
|
|
||||||
#### Shared GUID Kicker [Script Plugin]
|
|
||||||
- This plugin kicks users using a specific GUID
|
|
||||||
- GUID `F4D2C30B712AC6E3` on IW4x was packed into a torrent version of the game.
|
|
||||||
___
|
|
||||||
### Webfront
|
|
||||||
`Home`
|
|
||||||
* Shows an overview of the monitored server(s)
|
|
||||||
|
|
||||||
`Penalties`
|
|
||||||
* Shows a chronological ordered list of client penalties (scrolling down loads older penalties)
|
|
||||||
|
|
||||||
`Admins`
|
|
||||||
* Shows a list of privileged clients
|
|
||||||
|
|
||||||
`Login`
|
|
||||||
* Allows privileged users to login using their `Client ID` and password set via `setpassword`
|
|
||||||
* `ClientID` is a number that can be found by using `!find <client name>` or find the client on the webfront and copy the ID following `ProfileAsync/`
|
|
||||||
|
|
||||||
`Profile`
|
|
||||||
* Shows a client's information and history
|
|
||||||
|
|
||||||
`Web Console`
|
|
||||||
* Allows logged in privileged users to execute commands as if they are in-
|
|
||||||
|
|
||||||
`Search`
|
|
||||||
* Query clients and messages
|
|
||||||
|
|
||||||
Advanced filters can be constructed to search for resources using the following filter table.
|
|
||||||
| Filter | Description | Format | Example |
|
|
||||||
|-----------|--------------------------------------------------------|-----------------------|---------------------|
|
|
||||||
| before | include items occurring on or before the provided date | YYYY-MM-DD hh:mm:ss (UTC inferred) | 2020-05-21 23:00:00 |
|
|
||||||
| after | include items occurring on or after the provided date | YYYY-MM-DD hh:mm:ss (UTC inferred) | 2015-01-01 |
|
|
||||||
| server | include items matching the server id | ip:port | 127.0.0.1:28960 |
|
|
||||||
| client | include items matching the client id | integer | 8947 |
|
|
||||||
| contains | include items containing this substring | string | hack |
|
|
||||||
| sort | display results in this order | ascending\|descending | descending |
|
|
||||||
|
|
||||||
Any number of filters can be combined in any order.
|
|
||||||
Example — `chat|before 2020-05-21|after 2020-05-01|server 127.0.0.1:28960|client 444|contains cheating|sort descending`
|
|
||||||
|
|
||||||
---
|
|
||||||
### Game Log Server
|
|
||||||
The game log server provides a way to remotely host your server's log over a http rest-ful api.
|
|
||||||
This feature is useful if you plan on running IW4MAdmin on a different machine than the game server.
|
|
||||||
#### Requirements
|
|
||||||
- [Python 3.8.x](https://www.python.org/downloads/) or newer
|
|
||||||
|
|
||||||
#### Installation
|
|
||||||
1. With Python 3.x installed, open up a terminal/command prompt window in the `GameLogServer` folder and execute:
|
|
||||||
```console
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
If this fails, you can alternatively try installing with:
|
|
||||||
```console
|
|
||||||
python -m pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
2. Allow TCP port 1625 through firewall
|
|
||||||
* [Windows Instructions](https://www.tomshardware.com/news/how-to-open-firewall-ports-in-windows-10,36451.html)
|
|
||||||
* [Linux Instructions (iptables)](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-basic-iptables-firewall-on-centos-6#open-up-ports-for-selected-services)
|
|
||||||
#### Launching
|
|
||||||
With Python 3 installed, open a terminal/command prompt window open in the `GameServerLog` folder and execute:
|
|
||||||
```console
|
|
||||||
python runserver.py
|
|
||||||
```
|
|
||||||
The Game Log Server window will need to remain running/open as long as **IW4MAdmin** is running
|
|
||||||
|
|
||||||
#### Configuring
|
|
||||||
* Update your `IW4MAdminSettings.json` by changing the value of `GameLogServerUrl` to "http://<remote_server_ip>:1625"
|
|
||||||
* Example — `"GameLogServerUrl": "http://192.168.1.123:1625",`
|
|
||||||
---
|
|
||||||
### Extending Plugins
|
|
||||||
|
|
||||||
#### NuGet Package
|
|
||||||
The NuGet package for **IW4MAdmin's** "Shared Library" can be obtained from the [NuGet Gallery](https://www.nuget.org/packages/RaidMax.IW4MAdmin.SharedLibraryCore)
|
|
||||||
Referencing this package will give you the ability to write plugins against **IW4MAdmin's** core library.
|
|
||||||
#### Code
|
|
||||||
**IW4MAdmin's** functionality can be extended by writing additional plugins in C#.
|
|
||||||
Each class library must implement the `IPlugin` interface.
|
|
||||||
See the existing [plugins](https://github.com/RaidMax/IW4M-Admin/tree/master/Plugins) for examples.
|
|
||||||
#### JavaScript
|
|
||||||
**IW4MAdmin** functionality can also be extended using JavaScript.
|
|
||||||
The JavaScript parser supports [ECMA 5.1](https://ecma-international.org/ecma-262/5.1/) standards.
|
|
||||||
#### Plugin Object Template
|
|
||||||
In order to be properly parsed by the JavaScript engine, every plugin must conform to the following template.
|
|
||||||
```js
|
|
||||||
var plugin = {
|
|
||||||
author: 'YourHandle',
|
|
||||||
version: 1.0,
|
|
||||||
name: 'Sample JavaScript Plugin',
|
|
||||||
|
|
||||||
onEventAsync: function (gameEvent, server) {
|
|
||||||
},
|
|
||||||
|
|
||||||
onLoadAsync: function (manager) {
|
|
||||||
},
|
|
||||||
|
|
||||||
onUnloadAsync: function () {
|
|
||||||
},
|
|
||||||
|
|
||||||
onTickAsync: function (server) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
#### Required Properties
|
|
||||||
- `author` — [string] Author of the plugin (usually your name or online name/alias)
|
|
||||||
- `version` — [float] Version number of your plugin (useful if you release several different versions)
|
|
||||||
- `name` — [string] Name of your plugin (be descriptive!)
|
|
||||||
- `onEventAsync` — [function] Handler executed when an event occurs
|
|
||||||
- `gameEvent` — [parameter object] Object containing event type, origin, target, and other info (see the GameEvent class declaration)
|
|
||||||
- `server` — [parameter object] Object containing information and methods about the server the event occured on (see the Server class declaration)
|
|
||||||
- `onLoadAsync` — [function] Handler executed when the plugin is loaded by code
|
|
||||||
- `manager` — [parameter object] Object reference to the application manager (see the IManager interface definition)
|
|
||||||
- `onUnloadAsync` — [function] Handler executed when the plugin is unloaded by code (see live reloading)
|
|
||||||
- `onTickAsync` — [function] Handler executed approximately once per second by code *(unimplemented as of version 2.\*)*
|
|
||||||
- `server` — [parameter object] Object containing information and methods about the server the event occured on (see the Server class declaration)
|
|
||||||
### Live Reloading
|
|
||||||
Thanks to JavaScript's flexibility and parsability, the plugin importer scans the plugins folder and reloads the JavaScript plugins on demand as they're modified. This allows faster development/testing/debugging.
|
|
||||||
|
|
||||||
---
|
|
||||||
### Misc
|
|
||||||
#### Anti-cheat
|
|
||||||
This is an [IW4x](https://iw4xcachep26muba.onion.link/) only feature (wider game support planned), that uses analytics to detect aimbots and aim-assist tools.
|
|
||||||
To utilize anti-cheat, enable it during setup **and** copy `_customcallbacks.gsc` from `userraw` into your `IW4x Server\userraw\scripts` folder.
|
|
||||||
|
|
||||||
The anti-cheat feature is a work in progress and as such will be constantly tweaked and may not be 100% accurate, however the goal is to deter as many cheaters as possible from IW4x.
|
|
||||||
#### Database Storage
|
|
||||||
By default, all **IW4MAdmin** information is stored in `Database.db`.
|
|
||||||
|
|
||||||
Should you need to reset your database, this file can simply be deleted.
|
|
||||||
Additionally, this file should be preserved during updates to retain client information.
|
|
||||||
|
|
||||||
Setting the `ConnectionString` and `DatabaseProvider` properties in `IW4MAdminSettings.json`
|
|
||||||
will allow **IW4MAdmin** to use alternate methods for database storage
|
|
||||||
|
@ -108,7 +108,7 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
|
|
||||||
// give the local host full access
|
// give the local host full access
|
||||||
else
|
else if (!HttpContext.Request.Headers.ContainsKey("X-Forwarded-For"))
|
||||||
{
|
{
|
||||||
Client.ClientId = 1;
|
Client.ClientId = 1;
|
||||||
Client.Level = EFClient.Permission.Console;
|
Client.Level = EFClient.Permission.Console;
|
||||||
|
@ -5,6 +5,7 @@ using SharedLibraryCore.Commands;
|
|||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace SharedLibraryCore
|
namespace SharedLibraryCore
|
||||||
{
|
{
|
||||||
@ -13,7 +14,7 @@ namespace SharedLibraryCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Command : IManagerCommand
|
public abstract class Command : IManagerCommand
|
||||||
{
|
{
|
||||||
private readonly CommandConfiguration _config;
|
protected readonly CommandConfiguration _config;
|
||||||
protected readonly ITranslationLookup _translationLookup;
|
protected readonly ITranslationLookup _translationLookup;
|
||||||
protected ILogger logger;
|
protected ILogger logger;
|
||||||
|
|
||||||
@ -113,6 +114,25 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
private EFClient.Permission permission;
|
private EFClient.Permission permission;
|
||||||
|
|
||||||
|
public Game[] SupportedGames
|
||||||
|
{
|
||||||
|
get => supportedGames;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var savedGames = _config?.Commands[GetType().Name].SupportedGames;
|
||||||
|
supportedGames = savedGames?.Length != 0 ? savedGames : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
supportedGames = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Game[] supportedGames;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Argument list for the command
|
/// Argument list for the command
|
||||||
|
@ -53,7 +53,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
|
|
||||||
public override Task ExecuteAsync(GameEvent E)
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
MetaService.Clear();
|
|
||||||
E.Owner.Manager.Restart();
|
E.Owner.Manager.Restart();
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_RESTART_SUCCESS"]);
|
E.Origin.Tell(_translationLookup["COMMANDS_RESTART_SUCCESS"]);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@ -292,7 +291,7 @@ namespace SharedLibraryCore.Commands
|
|||||||
switch ((await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason)
|
switch ((await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason)
|
||||||
{
|
{
|
||||||
case GameEvent.EventFailReason.None:
|
case GameEvent.EventFailReason.None:
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(E.Target, length.TimeSpanText()));
|
E.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(E.Target, length.HumanizeForCurrentCulture()));
|
||||||
break;
|
break;
|
||||||
case GameEvent.EventFailReason.Exception:
|
case GameEvent.EventFailReason.Exception:
|
||||||
E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
|
||||||
@ -773,8 +772,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ListAdminsCommand : Command
|
public class ListAdminsCommand : Command
|
||||||
{
|
{
|
||||||
private readonly CommandConfiguration _config;
|
|
||||||
|
|
||||||
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "admins";
|
Name = "admins";
|
||||||
@ -782,8 +779,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
Alias = "a";
|
Alias = "a";
|
||||||
Permission = Permission.User;
|
Permission = Permission.User;
|
||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
|
|
||||||
_config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string OnlineAdmins(Server S, ITranslationLookup lookup)
|
public static string OnlineAdmins(Server S, ITranslationLookup lookup)
|
||||||
@ -890,14 +885,9 @@ namespace SharedLibraryCore.Commands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var P in db_players)
|
foreach (var client in db_players)
|
||||||
{
|
{
|
||||||
// they're not going by another alias
|
E.Origin.Tell(_translationLookup["COMMANDS_FIND_FORMAT"].FormatExt(client.Name, client.ClientId, Utilities.ConvertLevelToColor((Permission)client.LevelInt, client.Level), client.IPAddress, client.LastConnectionText));
|
||||||
// /*P.AliasLink.Children.FirstOrDefault(a => a.Name.ToLower().Contains(E.Data.ToLower()))?.Name*/
|
|
||||||
string msg = P.Name.ToLower().Contains(E.Data.ToLower()) ?
|
|
||||||
$"[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor((Permission)P.LevelInt, P.Level)}^7] - {P.IPAddress} | last seen {Utilities.GetTimePassed(P.LastConnection)}" :
|
|
||||||
$"()->[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor((Permission)P.LevelInt, P.Level)}^7] - {P.IPAddress} | last seen {Utilities.GetTimePassed(P.LastConnection)}";
|
|
||||||
E.Origin.Tell(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -907,8 +897,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ListRulesCommands : Command
|
public class ListRulesCommands : Command
|
||||||
{
|
{
|
||||||
private readonly CommandConfiguration _config;
|
|
||||||
|
|
||||||
public ListRulesCommands(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
public ListRulesCommands(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "rules";
|
Name = "rules";
|
||||||
@ -916,8 +904,6 @@ namespace SharedLibraryCore.Commands
|
|||||||
Alias = "r";
|
Alias = "r";
|
||||||
Permission = Permission.User;
|
Permission = Permission.User;
|
||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
|
|
||||||
_config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task ExecuteAsync(GameEvent E)
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
@ -1259,7 +1245,7 @@ namespace SharedLibraryCore.Commands
|
|||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string remainingTime = (penalty.Expires.Value - DateTime.UtcNow).TimeSpanText();
|
string remainingTime = (penalty.Expires.Value - DateTime.UtcNow).HumanizeForCurrentCulture();
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_BANINFO_TB_SUCCESS"].FormatExt(E.Target.Name, penalty.Offense, remainingTime));
|
E.Origin.Tell(_translationLookup["COMMANDS_BANINFO_TB_SUCCESS"].FormatExt(E.Target.Name, penalty.Offense, remainingTime));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1543,7 +1529,9 @@ namespace SharedLibraryCore.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SetGravatarCommand : Command
|
public class SetGravatarCommand : Command
|
||||||
{
|
{
|
||||||
public SetGravatarCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
|
private readonly IMetaService _metaService;
|
||||||
|
|
||||||
|
public SetGravatarCommand(CommandConfiguration config, ITranslationLookup translationLookup, IMetaService metaService) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "setgravatar";
|
Name = "setgravatar";
|
||||||
Description = _translationLookup["COMMANDS_GRAVATAR_DESC"];
|
Description = _translationLookup["COMMANDS_GRAVATAR_DESC"];
|
||||||
@ -1558,17 +1546,17 @@ namespace SharedLibraryCore.Commands
|
|||||||
Required = true
|
Required = true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_metaService = metaService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent E)
|
||||||
{
|
{
|
||||||
var metaSvc = new MetaService();
|
|
||||||
|
|
||||||
using (var md5 = MD5.Create())
|
using (var md5 = MD5.Create())
|
||||||
{
|
{
|
||||||
string gravatarEmail = string.Concat(md5.ComputeHash(E.Data.ToLower().Select(d => Convert.ToByte(d)).ToArray())
|
string gravatarEmail = string.Concat(md5.ComputeHash(E.Data.ToLower().Select(d => Convert.ToByte(d)).ToArray())
|
||||||
.Select(h => h.ToString("x2")));
|
.Select(h => h.ToString("x2")));
|
||||||
await metaSvc.AddPersistentMeta("GravatarEmail", gravatarEmail, E.Origin);
|
await _metaService.AddPersistentMeta("GravatarEmail", gravatarEmail, E.Origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
E.Origin.Tell(_translationLookup["COMMANDS_GRAVATAR_SUCCESS_NEW"]);
|
E.Origin.Tell(_translationLookup["COMMANDS_GRAVATAR_SUCCESS_NEW"]);
|
||||||
|
37
SharedLibraryCore/Commands/PrivateMessageAdminsCommand.cs
Normal file
37
SharedLibraryCore/Commands/PrivateMessageAdminsCommand.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Commands
|
||||||
|
{
|
||||||
|
public class PrivateMessageAdminsCommand : Command
|
||||||
|
{
|
||||||
|
public PrivateMessageAdminsCommand(CommandConfiguration config, ITranslationLookup lookup) : base(config, lookup)
|
||||||
|
{
|
||||||
|
Name = "privatemessageadmin";
|
||||||
|
Description = lookup["COMMANDS_PMADMINS_DESC"];
|
||||||
|
Alias = "pma";
|
||||||
|
Permission = EFClient.Permission.Moderator;
|
||||||
|
SupportedGames = new[] { Game.IW4, Game.IW5 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
|
{
|
||||||
|
bool isGameSupported = _config.Commands[nameof(PrivateMessageAdminsCommand)].SupportedGames.Length > 0 &&
|
||||||
|
_config.Commands[nameof(PrivateMessageAdminsCommand)].SupportedGames.Contains(E.Owner.GameName);
|
||||||
|
|
||||||
|
if (!isGameSupported)
|
||||||
|
{
|
||||||
|
E.Origin.Tell(_translationLookup["COMMANDS_GAME_NOT_SUPPORTED"].FormatExt(nameof(PrivateMessageAdminsCommand)));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
E.Owner.ToAdmins(E.Data);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json;
|
||||||
using System.Text.Json.Serialization;
|
using Newtonsoft.Json.Converters;
|
||||||
using static SharedLibraryCore.Database.Models.EFClient;
|
using static SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Configuration
|
namespace SharedLibraryCore.Configuration
|
||||||
{
|
{
|
||||||
@ -29,5 +30,11 @@ namespace SharedLibraryCore.Configuration
|
|||||||
/// Indicates if the command can be run by another user (impersonation)
|
/// Indicates if the command can be run by another user (impersonation)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllowImpersonation { get; set; }
|
public bool AllowImpersonation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the games supporting the functionality of the command
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
|
||||||
|
public Game[] SupportedGames { get; set; } = new Game[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ namespace SharedLibraryCore.Database
|
|||||||
|
|
||||||
static string _ConnectionString;
|
static string _ConnectionString;
|
||||||
static string _provider;
|
static string _provider;
|
||||||
private static readonly string _migrationPluginDirectory = @"X:\IW4MAdmin\BUILD\Plugins";
|
|
||||||
private static readonly ILoggerFactory _loggerFactory = LoggerFactory.Create(builder =>
|
private static readonly ILoggerFactory _loggerFactory = LoggerFactory.Create(builder =>
|
||||||
{
|
{
|
||||||
builder.AddConsole()
|
builder.AddConsole()
|
||||||
@ -72,7 +71,7 @@ namespace SharedLibraryCore.Database
|
|||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
//optionsBuilder.UseLoggerFactory(_loggerFactory)
|
//optionsBuilder.UseLoggerFactory(_loggerFactory)
|
||||||
// .EnableSensitiveDataLogging();
|
// .EnableSensitiveDataLogging();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_ConnectionString))
|
if (string.IsNullOrEmpty(_ConnectionString))
|
||||||
{
|
{
|
||||||
@ -198,11 +197,14 @@ namespace SharedLibraryCore.Database
|
|||||||
|
|
||||||
// adapted from
|
// adapted from
|
||||||
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
|
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
|
||||||
#if DEBUG
|
|
||||||
string pluginDir = _migrationPluginDirectory;
|
|
||||||
#else
|
|
||||||
string pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
|
string pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
|
||||||
#endif
|
|
||||||
|
if (Utilities.IsDevelopment)
|
||||||
|
{
|
||||||
|
pluginDir = Path.Join(Utilities.OperatingDirectory, "..", "..", "..", "..", "BUILD", "Plugins");
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerable<string> directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
|
IEnumerable<string> directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
|
||||||
|
|
||||||
foreach (string dllPath in directoryFiles)
|
foreach (string dllPath in directoryFiles)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using static SharedLibraryCore.Server;
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Dtos
|
namespace SharedLibraryCore.Dtos
|
||||||
@ -11,5 +12,7 @@ namespace SharedLibraryCore.Dtos
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public Game ServerGame { get; set; }
|
public Game ServerGame { get; set; }
|
||||||
public bool IsQuickMessage { get; set; }
|
public bool IsQuickMessage { get; set; }
|
||||||
|
public bool IsHidden { get; set; }
|
||||||
|
public string HiddenMessage => string.Concat(Enumerable.Repeat('●', Message.Length));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
namespace SharedLibraryCore.Dtos
|
namespace SharedLibraryCore.Dtos
|
||||||
{
|
{
|
||||||
public class FindClientRequest : PaginationInfo
|
public class FindClientRequest : PaginationRequest
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// name of client
|
/// name of client
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Dtos.Meta.Requests
|
||||||
|
{
|
||||||
|
public class BaseClientMetaRequest : PaginationRequest
|
||||||
|
{
|
||||||
|
public int ClientId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
using SharedLibraryCore.QueryHelper;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Dtos.Meta.Requests
|
||||||
|
{
|
||||||
|
public class ReceivedPenaltyRequest : BaseClientMetaRequest
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Dtos.Meta.Responses
|
||||||
|
{
|
||||||
|
public class AdministeredPenaltyResponse : ReceivedPenaltyResponse
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
19
SharedLibraryCore/Dtos/Meta/Responses/BaseMetaResponse.cs
Normal file
19
SharedLibraryCore/Dtos/Meta/Responses/BaseMetaResponse.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Dtos.Meta.Responses
|
||||||
|
{
|
||||||
|
public class BaseMetaResponse : IClientMeta, IClientMetaResponse
|
||||||
|
{
|
||||||
|
public int MetaId { get; set; }
|
||||||
|
public int ClientId { get; set; }
|
||||||
|
public MetaType Type { get; set; }
|
||||||
|
public DateTime When { get; set; }
|
||||||
|
public bool IsSensitive { get; set; }
|
||||||
|
public bool ShouldDisplay { get; set; }
|
||||||
|
public int? Column { get; set; }
|
||||||
|
public int? Order { get; set; }
|
||||||
|
}
|
||||||
|
}
|
14
SharedLibraryCore/Dtos/Meta/Responses/InformationResponse.cs
Normal file
14
SharedLibraryCore/Dtos/Meta/Responses/InformationResponse.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Dtos.Meta.Responses
|
||||||
|
{
|
||||||
|
public class InformationResponse : BaseMetaResponse
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public string Value { get; set; }
|
||||||
|
public string ToolTipText { get; set; }
|
||||||
|
}
|
||||||
|
}
|
33
SharedLibraryCore/Dtos/Meta/Responses/MessageResponse.cs
Normal file
33
SharedLibraryCore/Dtos/Meta/Responses/MessageResponse.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Dtos.Meta.Responses
|
||||||
|
{
|
||||||
|
public class MessageResponse : BaseMetaResponse
|
||||||
|
{
|
||||||
|
public long ServerId { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public bool IsHidden { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// name of the client
|
||||||
|
/// </summary>
|
||||||
|
public string ClientName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// hostname of the server
|
||||||
|
/// </summary>
|
||||||
|
public string ServerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// specifies the game the chat occured on
|
||||||
|
/// </summary>
|
||||||
|
public Server.Game GameName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// indicates if the chat message is a quick message phrase
|
||||||
|
/// </summary>
|
||||||
|
public bool IsQuickMessage { get; set; }
|
||||||
|
|
||||||
|
public string HiddenMessage => string.Concat(Enumerable.Repeat('●', Message.Length));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using static SharedLibraryCore.Database.Models.EFPenalty;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Dtos.Meta.Responses
|
||||||
|
{
|
||||||
|
public class ReceivedPenaltyResponse : BaseMetaResponse
|
||||||
|
{
|
||||||
|
public int PenaltyId { get; set; }
|
||||||
|
public int OffenderClientId { get; set; }
|
||||||
|
public string OffenderName { get; set; }
|
||||||
|
public string PunisherName { get; set; }
|
||||||
|
public int PunisherClientId { get; set; }
|
||||||
|
public PenaltyType PenaltyType { get; set; }
|
||||||
|
public string Offense { get; set; }
|
||||||
|
public string AutomatedOffense { get; set; }
|
||||||
|
public DateTime? ExpirationDate { get; set; }
|
||||||
|
public string ExpiresInText => ExpirationDate.HasValue && ExpirationDate.Value > DateTime.UtcNow ? (ExpirationDate - DateTime.UtcNow).Value.HumanizeForCurrentCulture() : "";
|
||||||
|
public string LengthText => ExpirationDate.HasValue ? (ExpirationDate.Value.AddMinutes(1) - When).HumanizeForCurrentCulture() : "";
|
||||||
|
public bool IsLinked { get; set; }
|
||||||
|
public int LinkedClientId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
namespace SharedLibraryCore.Dtos.Meta.Responses
|
||||||
|
{
|
||||||
|
public class UpdatedAliasResponse : BaseMetaResponse
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string IPAddress { get; set; } = "--";
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is UpdatedAliasResponse resp)
|
||||||
|
{
|
||||||
|
return resp.Name.StripColors() == Name.StripColors() && resp.IPAddress == IPAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() => $"{Name.StripColors()}{IPAddress}".GetStableHashCode();
|
||||||
|
}
|
||||||
|
}
|
13
SharedLibraryCore/Dtos/Meta/WebfrontTranslationHelper.cs
Normal file
13
SharedLibraryCore/Dtos/Meta/WebfrontTranslationHelper.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Dtos.Meta
|
||||||
|
{
|
||||||
|
public class WebfrontTranslationHelper
|
||||||
|
{
|
||||||
|
public bool IsInterpolation { get; set; }
|
||||||
|
public string MatchValue { get; set; }
|
||||||
|
public string TranslationValue { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
namespace SharedLibraryCore.Dtos
|
using System;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Dtos
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// pagination information holder class
|
/// pagination information holder class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PaginationInfo
|
public class PaginationRequest
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// how many items to skip
|
/// how many items to skip
|
||||||
@ -24,6 +26,8 @@
|
|||||||
/// direction of ordering
|
/// direction of ordering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SortDirection Direction { get; set; } = SortDirection.Descending;
|
public SortDirection Direction { get; set; } = SortDirection.Descending;
|
||||||
|
|
||||||
|
public DateTime? Before { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SortDirection
|
public enum SortDirection
|
@ -21,8 +21,8 @@ namespace SharedLibraryCore.Dtos
|
|||||||
public PenaltyType PenaltyType { get; set; }
|
public PenaltyType PenaltyType { get; set; }
|
||||||
public string PenaltyTypeText => PenaltyType.ToString();
|
public string PenaltyTypeText => PenaltyType.ToString();
|
||||||
public DateTime TimePunished { get; set; }
|
public DateTime TimePunished { get; set; }
|
||||||
public string TimePunishedString => Utilities.GetTimePassed(TimePunished, true);
|
public string TimePunishedString => TimePunished.HumanizeForCurrentCulture();
|
||||||
public string TimeRemaining => DateTime.UtcNow > Expires ? "" : $"{((Expires ?? DateTime.MaxValue).Year == DateTime.MaxValue.Year ? Utilities.GetTimePassed(TimePunished, true) : Utilities.TimeSpanText((Expires ?? DateTime.MaxValue) - DateTime.UtcNow))}";
|
public string TimeRemaining => DateTime.UtcNow > Expires ? "" : $"{((Expires ?? DateTime.MaxValue).Year == DateTime.MaxValue.Year ? TimePunishedString : ((Expires ?? DateTime.MaxValue) - DateTime.UtcNow).HumanizeForCurrentCulture())}";
|
||||||
public bool Expired => Expires.HasValue && Expires <= DateTime.UtcNow;
|
public bool Expired => Expires.HasValue && Expires <= DateTime.UtcNow;
|
||||||
public DateTime? Expires { get; set; }
|
public DateTime? Expires { get; set; }
|
||||||
public override bool Sensitive => PenaltyType == PenaltyType.Flag || PenaltyType == PenaltyType.Unflag;
|
public override bool Sensitive => PenaltyType == PenaltyType.Flag || PenaltyType == PenaltyType.Unflag;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharedLibraryCore.Dtos
|
namespace SharedLibraryCore.Dtos
|
||||||
{
|
{
|
||||||
@ -19,11 +19,13 @@ namespace SharedLibraryCore.Dtos
|
|||||||
public bool HasActivePenalty { get; set; }
|
public bool HasActivePenalty { get; set; }
|
||||||
public string ActivePenaltyType { get; set; }
|
public string ActivePenaltyType { get; set; }
|
||||||
public bool Authenticated { get; set; }
|
public bool Authenticated { get; set; }
|
||||||
public List<ProfileMeta> Meta { get; set; }
|
public List<InformationResponse> Meta { get; set; }
|
||||||
|
public EFPenalty ActivePenalty { get; set; }
|
||||||
public bool Online { get; set; }
|
public bool Online { get; set; }
|
||||||
public string TimeOnline { get; set; }
|
public string TimeOnline { get; set; }
|
||||||
public DateTime LastConnection { get; set; }
|
public DateTime LastConnection { get; set; }
|
||||||
public string LastConnectionText => Utilities.GetTimePassed(LastConnection, true);
|
public string LastConnectionText => (DateTime.UtcNow - LastConnection).HumanizeForCurrentCulture();
|
||||||
public IDictionary<int, long> LinkedAccounts { get; set; }
|
public IDictionary<int, long> LinkedAccounts { get; set; }
|
||||||
|
public MetaType? MetaFilterType { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharedLibraryCore.Dtos
|
|
||||||
{
|
|
||||||
public class ProfileMeta : SharedInfo
|
|
||||||
{
|
|
||||||
public enum MetaType
|
|
||||||
{
|
|
||||||
Other,
|
|
||||||
Information,
|
|
||||||
AliasUpdate,
|
|
||||||
ChatMessage,
|
|
||||||
Penalized,
|
|
||||||
ReceivedPenalty,
|
|
||||||
QuickMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTime When { get; set; }
|
|
||||||
public string WhenString => Utilities.GetTimePassed(When, false);
|
|
||||||
public string Key { get; set; }
|
|
||||||
public dynamic Value { get; set; }
|
|
||||||
public string Extra { get; set; }
|
|
||||||
public virtual string Class => Value.GetType().ToString();
|
|
||||||
public MetaType Type { get; set; }
|
|
||||||
public int? Column { get; set; }
|
|
||||||
public int? Order { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,5 +21,6 @@ namespace SharedLibraryCore.Dtos
|
|||||||
public bool Online { get; set; }
|
public bool Online { get; set; }
|
||||||
public string ConnectProtocolUrl { get; set; }
|
public string ConnectProtocolUrl { get; set; }
|
||||||
public string IPAddress { get; set; }
|
public string IPAddress { get; set; }
|
||||||
|
public bool IsPasswordProtected { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Helpers
|
namespace SharedLibraryCore.Helpers
|
||||||
{
|
{
|
||||||
|
@ -14,6 +14,6 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="paginationInfo">pagination info</param>
|
/// <param name="paginationInfo">pagination info</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<IList<AuditInfo>> ListAuditInformation(PaginationInfo paginationInfo);
|
Task<IList<AuditInfo>> ListAuditInformation(PaginationRequest paginationInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
SharedLibraryCore/Interfaces/IClientMeta.cs
Normal file
31
SharedLibraryCore/Interfaces/IClientMeta.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// describes all the base attributes of a client meta object
|
||||||
|
/// </summary>
|
||||||
|
public interface IClientMeta
|
||||||
|
{
|
||||||
|
MetaType Type { get; }
|
||||||
|
DateTime When { get; }
|
||||||
|
|
||||||
|
bool IsSensitive { get; }
|
||||||
|
bool ShouldDisplay { get; }
|
||||||
|
|
||||||
|
// sorting purposes
|
||||||
|
public int? Column { get; set; }
|
||||||
|
public int? Order { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MetaType
|
||||||
|
{
|
||||||
|
Other,
|
||||||
|
Information,
|
||||||
|
AliasUpdate,
|
||||||
|
ChatMessage,
|
||||||
|
Penalized,
|
||||||
|
ReceivedPenalty,
|
||||||
|
QuickMessage
|
||||||
|
}
|
||||||
|
}
|
12
SharedLibraryCore/Interfaces/IClientMetaResponse.cs
Normal file
12
SharedLibraryCore/Interfaces/IClientMetaResponse.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces
|
||||||
|
{
|
||||||
|
public interface IClientMetaResponse
|
||||||
|
{
|
||||||
|
int ClientId { get;}
|
||||||
|
int MetaId { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static SharedLibraryCore.Database.Models.EFClient;
|
using static SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Interfaces
|
namespace SharedLibraryCore.Interfaces
|
||||||
{
|
{
|
||||||
@ -35,6 +36,11 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Permission Permission { get; }
|
Permission Permission { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Games the command is supported on
|
||||||
|
/// </summary>
|
||||||
|
Game[] SupportedGames { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Syntax for using the command
|
/// Syntax for using the command
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
11
SharedLibraryCore/Interfaces/IMetaRegistration.cs
Normal file
11
SharedLibraryCore/Interfaces/IMetaRegistration.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces
|
||||||
|
{
|
||||||
|
public interface IMetaRegistration
|
||||||
|
{
|
||||||
|
void Register();
|
||||||
|
}
|
||||||
|
}
|
51
SharedLibraryCore/Interfaces/IMetaService.cs
Normal file
51
SharedLibraryCore/Interfaces/IMetaService.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Dtos;
|
||||||
|
using SharedLibraryCore.QueryHelper;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces
|
||||||
|
{
|
||||||
|
public interface IMetaService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// adds or updates meta key and value to the database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="metaKey">key of meta data</param>
|
||||||
|
/// <param name="metaValue">value of the meta data</param>
|
||||||
|
/// <param name="client">client to save the meta for</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task AddPersistentMeta(string metaKey, string metaValue, EFClient client);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// retrieves meta data for given client and key
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="metaKey">key to retrieve value for</param>
|
||||||
|
/// <param name="client">client to retrieve meta for</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// adds a meta task to the runtime meta list
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="metaKey">type of meta</param>
|
||||||
|
/// <param name="metaAction">action to perform</param>
|
||||||
|
void AddRuntimeMeta<T,V>(MetaType metaKey, Func<T, Task<IEnumerable<V>>> metaAction) where V : IClientMeta where T: PaginationRequest;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// retrieves all the runtime meta information for given client idea
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">request information</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// retreives all the runtime of provided type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">>request information</param>
|
||||||
|
/// <param name="metaType">type of meta to retreive</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta;
|
||||||
|
}
|
||||||
|
}
|
@ -15,9 +15,10 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="alias">alias of command</param>
|
/// <param name="alias">alias of command</param>
|
||||||
/// <param name="description">description of command</param>
|
/// <param name="description">description of command</param>
|
||||||
/// <param name="permission">minimum required permission</param>
|
/// <param name="permission">minimum required permission</param>
|
||||||
|
/// <param name="isTargetRequired">target required or not</param>
|
||||||
/// <param name="args">command arguments (name, is required)</param>
|
/// <param name="args">command arguments (name, is required)</param>
|
||||||
/// <param name="executeAction">action to peform when commmand is executed</param>
|
/// <param name="executeAction">action to peform when commmand is executed</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction);
|
IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, bool isTargetRequired, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs
Normal file
25
SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// interface used to dynamically resolve services by string name
|
||||||
|
/// </summary>
|
||||||
|
public interface IScriptPluginServiceResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// resolves a service with the given name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceName">class name of service</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
object ResolveService(string serviceName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// resolves a service with the given name and generic params
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceName">class name of service</param>
|
||||||
|
/// <param name="genericParams">generic class names</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
object ResolveService(string serviceName, string[] genericParameters);
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,23 @@
|
|||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Localization
|
namespace SharedLibraryCore.Localization
|
||||||
{
|
{
|
||||||
public class Layout
|
public class Layout
|
||||||
{
|
{
|
||||||
public string LocalizationName { get; set; }
|
private string localizationName;
|
||||||
|
public string LocalizationName
|
||||||
|
{
|
||||||
|
get => localizationName;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
localizationName = value;
|
||||||
|
Culture = new CultureInfo(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
public TranslationLookup LocalizationIndex { get; set; }
|
public TranslationLookup LocalizationIndex { get; set; }
|
||||||
|
public CultureInfo Culture { get; private set; }
|
||||||
|
|
||||||
public Layout(Dictionary<string, string> set)
|
public Layout(Dictionary<string, string> set)
|
||||||
{
|
{
|
||||||
|
925
SharedLibraryCore/Migrations/20200819224119_AddIsPasswordProtectedColumn.Designer.cs
generated
Normal file
925
SharedLibraryCore/Migrations/20200819224119_AddIsPasswordProtectedColumn.Designer.cs
generated
Normal file
@ -0,0 +1,925 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using SharedLibraryCore.Database;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(DatabaseContext))]
|
||||||
|
[Migration("20200819224119_AddIsPasswordProtectedColumn")]
|
||||||
|
partial class AddIsPasswordProtectedColumn
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "3.1.7");
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("SnapshotId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("CurrentSessionLength")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("CurrentStrain")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("CurrentViewAngleId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Deaths")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("Distance")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("EloRating")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("HitDestinationId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("HitLocation")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("HitOriginId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("HitType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Hits")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Kills")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("LastStrainAngleId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("RecoilOffset")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("SessionAngleOffset")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("SessionAverageSnapValue")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("SessionSPM")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("SessionScore")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SessionSnapHits")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("StrainAngleBetween")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("TimeSinceLastEvent")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("WeaponId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("When")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("SnapshotId");
|
||||||
|
|
||||||
|
b.HasIndex("ClientId");
|
||||||
|
|
||||||
|
b.HasIndex("CurrentViewAngleId");
|
||||||
|
|
||||||
|
b.HasIndex("HitDestinationId");
|
||||||
|
|
||||||
|
b.HasIndex("HitOriginId");
|
||||||
|
|
||||||
|
b.HasIndex("LastStrainAngleId");
|
||||||
|
|
||||||
|
b.ToTable("EFACSnapshot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ACSnapshotVector3Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SnapshotId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Vector3Id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("ACSnapshotVector3Id");
|
||||||
|
|
||||||
|
b.HasIndex("SnapshotId");
|
||||||
|
|
||||||
|
b.HasIndex("Vector3Id");
|
||||||
|
|
||||||
|
b.ToTable("EFACSnapshotVector3");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("KillId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AttackerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Damage")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("DeathOriginVector3Id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("DeathType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("Fraction")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("HitLoc")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("IsKill")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("KillOriginVector3Id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Map")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("ServerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("VictimId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("ViewAnglesVector3Id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("VisibilityPercentage")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("Weapon")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("When")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("KillId");
|
||||||
|
|
||||||
|
b.HasIndex("AttackerId");
|
||||||
|
|
||||||
|
b.HasIndex("DeathOriginVector3Id");
|
||||||
|
|
||||||
|
b.HasIndex("KillOriginVector3Id");
|
||||||
|
|
||||||
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
|
b.HasIndex("VictimId");
|
||||||
|
|
||||||
|
b.HasIndex("ViewAnglesVector3Id");
|
||||||
|
|
||||||
|
b.ToTable("EFClientKills");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("MessageId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Message")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("ServerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TimeSent")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("MessageId");
|
||||||
|
|
||||||
|
b.HasIndex("ClientId");
|
||||||
|
|
||||||
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
|
b.HasIndex("TimeSent");
|
||||||
|
|
||||||
|
b.ToTable("EFClientMessages");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("RatingHistoryId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("RatingHistoryId");
|
||||||
|
|
||||||
|
b.HasIndex("ClientId");
|
||||||
|
|
||||||
|
b.ToTable("EFClientRatingHistory");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("ServerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("AverageRecoilOffset")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("AverageSnapValue")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("Deaths")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("EloRating")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("Kills")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("MaxStrain")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("RollingWeightedKDR")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("SPM")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("Skill")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("SnapHitCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("TimePlayed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("VisionAverage")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.HasKey("ClientId", "ServerId");
|
||||||
|
|
||||||
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
|
b.ToTable("EFClientStatistics");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("HitLocationCountId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("EFClientStatisticsClientId")
|
||||||
|
.HasColumnName("EFClientStatisticsClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("EFClientStatisticsServerId")
|
||||||
|
.HasColumnName("EFClientStatisticsServerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("HitCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<float>("HitOffsetAverage")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("Location")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<float>("MaxAngleDistance")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.HasKey("HitLocationCountId");
|
||||||
|
|
||||||
|
b.HasIndex("EFClientStatisticsServerId");
|
||||||
|
|
||||||
|
b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId");
|
||||||
|
|
||||||
|
b.ToTable("EFHitLocationCounts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("RatingId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ActivityAmount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Newest")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("Performance")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("Ranking")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("RatingHistoryId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long?>("ServerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("When")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("RatingId");
|
||||||
|
|
||||||
|
b.HasIndex("RatingHistoryId");
|
||||||
|
|
||||||
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
|
b.HasIndex("Performance", "Ranking", "When");
|
||||||
|
|
||||||
|
b.ToTable("EFRating");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("ServerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("EndPoint")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("GameName")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("HostName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPasswordProtected")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Port")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("ServerId");
|
||||||
|
|
||||||
|
b.ToTable("EFServers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("StatisticId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("ServerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("TotalKills")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("TotalPlayTime")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("StatisticId");
|
||||||
|
|
||||||
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
|
b.ToTable("EFServerStatistics");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("AliasId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAdded")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("IPAddress")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("LinkId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(24);
|
||||||
|
|
||||||
|
b.Property<string>("SearchableName")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(24);
|
||||||
|
|
||||||
|
b.HasKey("AliasId");
|
||||||
|
|
||||||
|
b.HasIndex("IPAddress");
|
||||||
|
|
||||||
|
b.HasIndex("LinkId");
|
||||||
|
|
||||||
|
b.HasIndex("Name");
|
||||||
|
|
||||||
|
b.HasIndex("SearchableName");
|
||||||
|
|
||||||
|
b.HasIndex("Name", "IPAddress")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("EFAlias");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("AliasLinkId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("AliasLinkId");
|
||||||
|
|
||||||
|
b.ToTable("EFAliasLinks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ChangeHistoryId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Comment")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(128);
|
||||||
|
|
||||||
|
b.Property<string>("CurrentValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("ImpersonationEntityId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("OriginEntityId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("PreviousValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TargetEntityId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TimeChanged")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TypeOfChange")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("ChangeHistoryId");
|
||||||
|
|
||||||
|
b.ToTable("EFChangeHistory");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ClientId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AliasLinkId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Connections")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("CurrentAliasId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("FirstConnection")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastConnection")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Level")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Masked")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("NetworkId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordSalt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TotalConnectionTime")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("ClientId");
|
||||||
|
|
||||||
|
b.HasIndex("AliasLinkId");
|
||||||
|
|
||||||
|
b.HasIndex("CurrentAliasId");
|
||||||
|
|
||||||
|
b.HasIndex("NetworkId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("EFClients");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("MetaId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Extra")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(32);
|
||||||
|
|
||||||
|
b.Property<DateTime>("Updated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("MetaId");
|
||||||
|
|
||||||
|
b.HasIndex("ClientId");
|
||||||
|
|
||||||
|
b.HasIndex("Key");
|
||||||
|
|
||||||
|
b.ToTable("EFMeta");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("PenaltyId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Active")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AutomatedOffense")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Expires")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEvadedOffense")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("LinkId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("OffenderId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Offense")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("PunisherId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("When")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("PenaltyId");
|
||||||
|
|
||||||
|
b.HasIndex("LinkId");
|
||||||
|
|
||||||
|
b.HasIndex("OffenderId");
|
||||||
|
|
||||||
|
b.HasIndex("PunisherId");
|
||||||
|
|
||||||
|
b.ToTable("EFPenalties");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Vector3Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<float>("X")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<float>("Y")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<float>("Z")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.HasKey("Vector3Id");
|
||||||
|
|
||||||
|
b.ToTable("Vector3");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ClientId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CurrentViewAngleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("HitDestinationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("HitOriginId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("LastStrainAngleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", "Snapshot")
|
||||||
|
.WithMany("PredictedViewAngles")
|
||||||
|
.HasForeignKey("SnapshotId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Helpers.Vector3", "Vector")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("Vector3Id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AttackerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DeathOriginVector3Id");
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("KillOriginVector3Id");
|
||||||
|
|
||||||
|
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ServerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("VictimId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ViewAnglesVector3Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ClientId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ServerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ClientId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ClientId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ServerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("EFClientStatisticsClientId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("EFClientStatisticsServerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", null)
|
||||||
|
.WithMany("HitLocations")
|
||||||
|
.HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
|
||||||
|
.WithMany("Ratings")
|
||||||
|
.HasForeignKey("RatingHistoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ServerId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ServerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
|
||||||
|
.WithMany("Children")
|
||||||
|
.HasForeignKey("LinkId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AliasLinkId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CurrentAliasId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||||
|
.WithMany("Meta")
|
||||||
|
.HasForeignKey("ClientId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
|
||||||
|
.WithMany("ReceivedPenalties")
|
||||||
|
.HasForeignKey("LinkId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
|
||||||
|
.WithMany("ReceivedPenalties")
|
||||||
|
.HasForeignKey("OffenderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
|
||||||
|
.WithMany("AdministeredPenalties")
|
||||||
|
.HasForeignKey("PunisherId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddIsPasswordProtectedColumn : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
if (migrationBuilder.ActiveProvider == "Npgsql.EntityFrameworkCore.PostgreSQL")
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsPasswordProtected",
|
||||||
|
type: "bool",
|
||||||
|
table: "EFServers",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsPasswordProtected",
|
||||||
|
table: "EFServers",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsPasswordProtected",
|
||||||
|
table: "EFServers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ namespace SharedLibraryCore.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "3.1.3");
|
.HasAnnotation("ProductVersion", "3.1.7");
|
||||||
|
|
||||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
|
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
|
||||||
{
|
{
|
||||||
@ -408,6 +408,9 @@ namespace SharedLibraryCore.Migrations
|
|||||||
b.Property<string>("HostName")
|
b.Property<string>("HostName")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPasswordProtected")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("Port")
|
b.Property<int>("Port")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
@ -618,7 +618,7 @@ namespace SharedLibraryCore.Database.Models
|
|||||||
if (tempbanPenalty != null)
|
if (tempbanPenalty != null)
|
||||||
{
|
{
|
||||||
CurrentServer.Logger.WriteDebug($"Kicking {this} because their GUID is temporarily banned");
|
CurrentServer.Logger.WriteDebug($"Kicking {this} because their GUID is temporarily banned");
|
||||||
Kick($"{loc["SERVER_TB_REMAIN"]} ({(tempbanPenalty.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient);
|
Kick($"{loc["SERVER_TB_REMAIN"]} ({(tempbanPenalty.Expires.Value - DateTime.UtcNow).HumanizeForCurrentCulture()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -656,6 +656,12 @@ namespace SharedLibraryCore.Database.Models
|
|||||||
public int Score { get; set; }
|
public int Score { get; set; }
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public bool IsBot => NetworkId == Name.GenerateGuidFromString();
|
public bool IsBot => NetworkId == Name.GenerateGuidFromString();
|
||||||
|
[NotMapped]
|
||||||
|
public bool IsZombieClient => IsBot && Name == "Zombie";
|
||||||
|
[NotMapped]
|
||||||
|
public string XuidString => (NetworkId + 0x110000100000000).ToString("x");
|
||||||
|
[NotMapped]
|
||||||
|
public string GuidString => NetworkId.ToString("x");
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public ClientState State { get; set; }
|
public ClientState State { get; set; }
|
||||||
|
12
SharedLibraryCore/QueryHelper/ClientPaginationRequest.cs
Normal file
12
SharedLibraryCore/QueryHelper/ClientPaginationRequest.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using SharedLibraryCore.Dtos;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.QueryHelper
|
||||||
|
{
|
||||||
|
public class ClientPaginationRequest : PaginationRequest
|
||||||
|
{
|
||||||
|
public int ClientId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ namespace SharedLibraryCore.Repositories
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<IList<AuditInfo>> ListAuditInformation(PaginationInfo paginationInfo)
|
public async Task<IList<AuditInfo>> ListAuditInformation(PaginationRequest paginationInfo)
|
||||||
{
|
{
|
||||||
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
|
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
|
||||||
{
|
{
|
||||||
|
@ -293,6 +293,7 @@ namespace SharedLibraryCore
|
|||||||
public string Hostname { get => ServerConfig.CustomHostname ?? hostname; protected set => hostname = value; }
|
public string Hostname { get => ServerConfig.CustomHostname ?? hostname; protected set => hostname = value; }
|
||||||
public string Website { get; protected set; }
|
public string Website { get; protected set; }
|
||||||
public string Gametype { get; set; }
|
public string Gametype { get; set; }
|
||||||
|
public string GamePassword { get; protected set; }
|
||||||
public Map CurrentMap { get; set; }
|
public Map CurrentMap { get; set; }
|
||||||
public int ClientNum
|
public int ClientNum
|
||||||
{
|
{
|
||||||
|
@ -1,169 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using SharedLibraryCore.Database;
|
|
||||||
using SharedLibraryCore.Database.Models;
|
|
||||||
using SharedLibraryCore.Dtos;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharedLibraryCore.Services
|
|
||||||
{
|
|
||||||
public class MetaService
|
|
||||||
{
|
|
||||||
private static List<Func<int, int, int, DateTime?, Task<List<ProfileMeta>>>> _metaActions = new List<Func<int, int, int, DateTime?, Task<List<ProfileMeta>>>>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// adds or updates meta key and value to the database
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="metaKey">key of meta data</param>
|
|
||||||
/// <param name="metaValue">value of the meta data</param>
|
|
||||||
/// <param name="client">client to save the meta for</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task AddPersistentMeta(string metaKey, string metaValue, EFClient client)
|
|
||||||
{
|
|
||||||
// this seems to happen if the client disconnects before they've had time to authenticate and be added
|
|
||||||
if (client.ClientId < 1)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var ctx = new DatabaseContext())
|
|
||||||
{
|
|
||||||
var existingMeta = await ctx.EFMeta
|
|
||||||
.Where(_meta => _meta.Key == metaKey)
|
|
||||||
.Where(_meta => _meta.ClientId == client.ClientId)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
|
|
||||||
if (existingMeta != null)
|
|
||||||
{
|
|
||||||
existingMeta.Value = metaValue;
|
|
||||||
existingMeta.Updated = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ctx.EFMeta.Add(new EFMeta()
|
|
||||||
{
|
|
||||||
ClientId = client.ClientId,
|
|
||||||
Created = DateTime.UtcNow,
|
|
||||||
Key = metaKey,
|
|
||||||
Value = metaValue
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void Clear()
|
|
||||||
{
|
|
||||||
_metaActions.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// retrieves meta data for given client and key
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="metaKey">key to retrieve value for</param>
|
|
||||||
/// <param name="client">client to retrieve meta for</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client)
|
|
||||||
{
|
|
||||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
|
||||||
{
|
|
||||||
return await ctx.EFMeta
|
|
||||||
.Where(_meta => _meta.Key == metaKey)
|
|
||||||
.Where(_meta => _meta.ClientId == client.ClientId)
|
|
||||||
.Select(_meta => new EFMeta()
|
|
||||||
{
|
|
||||||
MetaId = _meta.MetaId,
|
|
||||||
Key = _meta.Key,
|
|
||||||
ClientId = _meta.ClientId,
|
|
||||||
Value = _meta.Value
|
|
||||||
})
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// aads a meta task to the runtime meta list
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="metaAction"></param>
|
|
||||||
public static void AddRuntimeMeta(Func<int, int, int, DateTime?, Task<List<ProfileMeta>>> metaAction)
|
|
||||||
{
|
|
||||||
_metaActions.Add(metaAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// retrieves all the runtime meta information for given client idea
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="clientId">id of the client</param>
|
|
||||||
/// <param name="count">number of meta items to retrieve</param>
|
|
||||||
/// <param name="offset">offset from the first item</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static async Task<List<ProfileMeta>> GetRuntimeMeta(int clientId, int offset = 0, int count = int.MaxValue, DateTime? startAt = null)
|
|
||||||
{
|
|
||||||
var meta = new List<ProfileMeta>();
|
|
||||||
|
|
||||||
foreach (var action in _metaActions)
|
|
||||||
{
|
|
||||||
var metaItems = await action(clientId, offset, count, startAt);
|
|
||||||
meta.AddRange(metaItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == 1)
|
|
||||||
{
|
|
||||||
var table = new List<List<ProfileMeta>>();
|
|
||||||
var metaWithColumn = meta
|
|
||||||
.Where(_meta => _meta.Column != null);
|
|
||||||
|
|
||||||
var columnGrouping = metaWithColumn
|
|
||||||
.GroupBy(_meta => _meta.Column);
|
|
||||||
|
|
||||||
var metaToSort = meta.Except(metaWithColumn).ToList();
|
|
||||||
|
|
||||||
foreach (var metaItem in columnGrouping)
|
|
||||||
{
|
|
||||||
table.Add(new List<ProfileMeta>(metaItem));
|
|
||||||
}
|
|
||||||
|
|
||||||
while (metaToSort.Count > 0)
|
|
||||||
{
|
|
||||||
var sortingMeta = metaToSort.First();
|
|
||||||
|
|
||||||
int indexOfSmallestColumn()
|
|
||||||
{
|
|
||||||
int index = 0;
|
|
||||||
int smallestColumnSize = int.MaxValue;
|
|
||||||
for (int i = 0; i < table.Count; i++)
|
|
||||||
{
|
|
||||||
if (table[i].Count < smallestColumnSize)
|
|
||||||
{
|
|
||||||
smallestColumnSize = table[i].Count;
|
|
||||||
index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
int columnIndex = indexOfSmallestColumn();
|
|
||||||
|
|
||||||
sortingMeta.Column = columnIndex;
|
|
||||||
sortingMeta.Order = columnGrouping
|
|
||||||
.First(_group => _group.Key == columnIndex)
|
|
||||||
.Count();
|
|
||||||
|
|
||||||
table[columnIndex].Add(sortingMeta);
|
|
||||||
|
|
||||||
metaToSort.Remove(sortingMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
return meta.OrderByDescending(_meta => _meta.When)
|
|
||||||
.Take(count)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
@ -6,7 +6,7 @@
|
|||||||
<ApplicationIcon />
|
<ApplicationIcon />
|
||||||
<StartupObject />
|
<StartupObject />
|
||||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||||
<Version>2.4.3</Version>
|
<Version>2.4.10</Version>
|
||||||
<Authors>RaidMax</Authors>
|
<Authors>RaidMax</Authors>
|
||||||
<Company>Forever None</Company>
|
<Company>Forever None</Company>
|
||||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||||
@ -18,10 +18,11 @@
|
|||||||
<Copyright>2020</Copyright>
|
<Copyright>2020</Copyright>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<Description>Shared Library for IW4MAdmin</Description>
|
<Description>Shared Library for IW4MAdmin</Description>
|
||||||
<AssemblyVersion>2.4.3.0</AssemblyVersion>
|
<AssemblyVersion>2.4.10.0</AssemblyVersion>
|
||||||
<FileVersion>2.4.3.0</FileVersion>
|
<FileVersion>2.4.10.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
||||||
@ -30,32 +31,36 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
<PackageReference Include="FluentValidation" Version="9.1.3" />
|
||||||
|
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
|
||||||
|
<PackageReference Include="Humanizer.Core.ru" Version="2.8.26" />
|
||||||
|
<PackageReference Include="Humanizer.Core.de" Version="2.8.26" />
|
||||||
|
<PackageReference Include="Humanizer.Core.es" Version="2.8.26" />
|
||||||
|
<PackageReference Include="Humanizer.Core.pt" Version="2.8.26" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.7">
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.3">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.7" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="Npgsql" Version="4.1.3.1" />
|
<PackageReference Include="Npgsql" Version="4.1.4" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.2" />
|
||||||
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||||
<Exec Command="if not exist "$(ProjectDir)..\BUILD" (
if $(ConfigurationName) == Debug (
md "$(ProjectDir)..\BUILD"
)
)
if not exist "$(ProjectDir)..\BUILD\Plugins" (
if $(ConfigurationName) == Debug (
md "$(ProjectDir)..\BUILD\Plugins"
)
)" />
|
<Exec Command="if not exist "$(ProjectDir)..\BUILD" (
if $(ConfigurationName) == Debug (
md "$(ProjectDir)..\BUILD"
)
)
if not exist "$(ProjectDir)..\BUILD\Plugins" (
if $(ConfigurationName) == Debug (
md "$(ProjectDir)..\BUILD\Plugins"
)
)" />
|
||||||
</Target>
|
</Target>
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
|
||||||
|
using Humanizer;
|
||||||
|
using Humanizer.Localisation;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Dtos.Meta;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System;
|
using System;
|
||||||
@ -366,7 +369,27 @@ namespace SharedLibraryCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">value string</param>
|
/// <param name="value">value string</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static long GenerateGuidFromString(this string value) => string.IsNullOrEmpty(value) ? -1 : HashCode.Combine(value.StripColors());
|
public static long GenerateGuidFromString(this string value) => string.IsNullOrEmpty(value) ? -1 : GetStableHashCode(value.StripColors());
|
||||||
|
|
||||||
|
/// https://stackoverflow.com/questions/36845430/persistent-hashcode-for-strings
|
||||||
|
public static int GetStableHashCode(this string str)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int hash1 = 5381;
|
||||||
|
int hash2 = hash1;
|
||||||
|
|
||||||
|
for (int i = 0; i < str.Length && str[i] != '\0'; i += 2)
|
||||||
|
{
|
||||||
|
hash1 = ((hash1 << 5) + hash1) ^ str[i];
|
||||||
|
if (i == str.Length - 1 || str[i + 1] == '\0')
|
||||||
|
break;
|
||||||
|
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash1 + (hash2 * 1566083941);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static int? ConvertToIP(this string str)
|
public static int? ConvertToIP(this string str)
|
||||||
{
|
{
|
||||||
@ -381,57 +404,6 @@ namespace SharedLibraryCore
|
|||||||
return !ip.HasValue ? "" : new IPAddress(BitConverter.GetBytes(ip.Value)).ToString();
|
return !ip.HasValue ? "" : new IPAddress(BitConverter.GetBytes(ip.Value)).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetTimePassed(DateTime start)
|
|
||||||
{
|
|
||||||
return GetTimePassed(start, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetTimePassed(DateTime start, bool includeAgo)
|
|
||||||
{
|
|
||||||
TimeSpan Elapsed = DateTime.UtcNow - start;
|
|
||||||
string ago = includeAgo ? $" {CurrentLocalization.LocalizationIndex["WEBFRONT_PENALTY_TEMPLATE_AGO"]}" : "";
|
|
||||||
|
|
||||||
if (Elapsed.TotalSeconds < 30)
|
|
||||||
{
|
|
||||||
return CurrentLocalization.LocalizationIndex["GLOBAL_TIME_JUSTNOW"] + ago;
|
|
||||||
}
|
|
||||||
if (Elapsed.TotalMinutes < 120)
|
|
||||||
{
|
|
||||||
if (Elapsed.TotalMinutes < 1.5)
|
|
||||||
{
|
|
||||||
return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MINUTES"]}{ago}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.Round(Elapsed.TotalMinutes, 0) + $" {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MINUTES"]}{ago}";
|
|
||||||
}
|
|
||||||
if (Elapsed.TotalHours <= 24)
|
|
||||||
{
|
|
||||||
if (Elapsed.TotalHours < 1.5)
|
|
||||||
{
|
|
||||||
return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]}{ago}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.Round(Elapsed.TotalHours, 0) + $" { CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]}{ago}";
|
|
||||||
}
|
|
||||||
if (Elapsed.TotalDays <= 90)
|
|
||||||
{
|
|
||||||
if (Elapsed.TotalDays < 1.5)
|
|
||||||
{
|
|
||||||
return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_DAYS"]}{ago}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.Round(Elapsed.TotalDays, 0) + $" {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_DAYS"]}{ago}";
|
|
||||||
}
|
|
||||||
if (Elapsed.TotalDays <= 365)
|
|
||||||
{
|
|
||||||
return $"{Math.Round(Elapsed.TotalDays / 7)} {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_WEEKS"]}{ago}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $"{Math.Round(Elapsed.TotalDays / 30, 0)} {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MONTHS"]}{ago}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Game GetGame(string gameName)
|
public static Game GetGame(string gameName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(gameName))
|
if (string.IsNullOrEmpty(gameName))
|
||||||
@ -519,42 +491,6 @@ namespace SharedLibraryCore
|
|||||||
return new TimeSpan(1, 0, 0);
|
return new TimeSpan(1, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string TimeSpanText(this TimeSpan span)
|
|
||||||
{
|
|
||||||
var loc = CurrentLocalization.LocalizationIndex;
|
|
||||||
|
|
||||||
if (span.TotalMinutes < 60)
|
|
||||||
{
|
|
||||||
return $"{span.Minutes} {loc["GLOBAL_TIME_MINUTES"]}";
|
|
||||||
}
|
|
||||||
else if (span.Hours >= 1 && span.TotalHours < 24)
|
|
||||||
{
|
|
||||||
return $"{span.Hours} {loc["GLOBAL_TIME_HOURS"]}";
|
|
||||||
}
|
|
||||||
else if (span.TotalDays >= 1 && span.TotalDays < 7)
|
|
||||||
{
|
|
||||||
return $"{span.Days} {loc["GLOBAL_TIME_DAYS"]}";
|
|
||||||
}
|
|
||||||
else if (span.TotalDays >= 7 && span.TotalDays < 90)
|
|
||||||
{
|
|
||||||
return $"{Math.Round(span.Days / 7.0, 0)} {loc["GLOBAL_TIME_WEEKS"]}";
|
|
||||||
}
|
|
||||||
else if (span.TotalDays >= 90 && span.TotalDays < 365)
|
|
||||||
{
|
|
||||||
return $"{Math.Round(span.Days / 30.0, 0)} {loc["GLOBAL_TIME_MONTHS"]}";
|
|
||||||
}
|
|
||||||
else if (span.TotalDays >= 365 && span.TotalDays < 36500)
|
|
||||||
{
|
|
||||||
return $"{Math.Round(span.Days / 365.0, 0)} {loc["GLOBAL_TIME_YEARS"]}";
|
|
||||||
}
|
|
||||||
else if (span.TotalDays >= 36500)
|
|
||||||
{
|
|
||||||
return loc["GLOBAL_TIME_FOREVER"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// returns a list of penalty types that should be shown across all profiles
|
/// returns a list of penalty types that should be shown across all profiles
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -756,11 +692,11 @@ namespace SharedLibraryCore
|
|||||||
return server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue);
|
return server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Dvar<T>> GetMappedDvarValueOrDefaultAsync<T>(this Server server, string dvarName, string infoResponseName = null, IDictionary<string, string> infoResponse = null)
|
public static async Task<Dvar<T>> GetMappedDvarValueOrDefaultAsync<T>(this Server server, string dvarName, string infoResponseName = null, IDictionary<string, string> infoResponse = null, T overrideDefault = default)
|
||||||
{
|
{
|
||||||
// todo: unit test this
|
// todo: unit test this
|
||||||
string mappedKey = server.RconParser.GetOverrideDvarName(dvarName);
|
string mappedKey = server.RconParser.GetOverrideDvarName(dvarName);
|
||||||
var defaultValue = server.RconParser.GetDefaultDvarValue<T>(mappedKey);
|
var defaultValue = server.RconParser.GetDefaultDvarValue<T>(mappedKey) ?? overrideDefault;
|
||||||
|
|
||||||
string foundKey = infoResponse?.Keys.Where(_key => new[] { mappedKey, dvarName, infoResponseName ?? dvarName }.Contains(_key)).FirstOrDefault();
|
string foundKey = infoResponse?.Keys.Where(_key => new[] { mappedKey, dvarName, infoResponseName ?? dvarName }.Contains(_key)).FirstOrDefault();
|
||||||
|
|
||||||
@ -857,6 +793,8 @@ namespace SharedLibraryCore
|
|||||||
byte[] bytes = toTest.GetAddressBytes();
|
byte[] bytes = toTest.GetAddressBytes();
|
||||||
switch (bytes[0])
|
switch (bytes[0])
|
||||||
{
|
{
|
||||||
|
case 0:
|
||||||
|
return bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 0;
|
||||||
case 10:
|
case 10:
|
||||||
return true;
|
return true;
|
||||||
case 172:
|
case 172:
|
||||||
@ -895,7 +833,7 @@ namespace SharedLibraryCore
|
|||||||
/// <returns>true if the </returns>
|
/// <returns>true if the </returns>
|
||||||
public static bool IsQuickMessage(this string message)
|
public static bool IsQuickMessage(this string message)
|
||||||
{
|
{
|
||||||
return Regex.IsMatch(message, @"^\u0014(?:[A-Z]|_)+$");
|
return Regex.IsMatch(message, @"^\u0014(?:\w|_|!|\s)+$");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -932,7 +870,7 @@ namespace SharedLibraryCore
|
|||||||
/// <param name="penalty"></param>
|
/// <param name="penalty"></param>
|
||||||
/// <param name="penaltyService"></param>
|
/// <param name="penaltyService"></param>
|
||||||
/// <param name="logger"></param>
|
/// <param name="logger"></param>
|
||||||
/// <returns>true of the creat succeeds, false otherwise</returns>
|
/// <returns>true of the create succeeds, false otherwise</returns>
|
||||||
public static async Task<bool> TryCreatePenalty(this EFPenalty penalty, IEntityService<EFPenalty> penaltyService, ILogger logger)
|
public static async Task<bool> TryCreatePenalty(this EFPenalty penalty, IEntityService<EFPenalty> penaltyService, ILogger logger)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -969,7 +907,50 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged;
|
public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// parses translation string into tokens that are able to be formatted by the webfront
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="translationKey">key for translation lookup</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static WebfrontTranslationHelper[] SplitTranslationTokens(string translationKey)
|
||||||
|
{
|
||||||
|
string translationString = CurrentLocalization.LocalizationIndex[translationKey];
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
var results = new List<WebfrontTranslationHelper>();
|
||||||
|
|
||||||
|
foreach (string word in translationString.Split(' '))
|
||||||
|
{
|
||||||
|
string finalWord = word;
|
||||||
|
|
||||||
|
if ((word.StartsWith("{{") && !word.EndsWith("}}")) ||
|
||||||
|
(builder.Length > 0 && !word.EndsWith("}}")))
|
||||||
|
{
|
||||||
|
builder.Append($"{word} ");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builder.Length > 0)
|
||||||
|
{
|
||||||
|
builder.Append(word);
|
||||||
|
finalWord = builder.ToString();
|
||||||
|
builder.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = Regex.Match(finalWord, @"{{([^}|^-]+)(?:->)([^}]+)}}|{{([^}]+)}}");
|
||||||
|
bool isInterpolation = match.Success;
|
||||||
|
|
||||||
|
results.Add(new WebfrontTranslationHelper
|
||||||
|
{
|
||||||
|
IsInterpolation = isInterpolation,
|
||||||
|
MatchValue = isInterpolation ? match.Groups[3].Length > 0 ? match.Groups[3].ToString() : match.Groups[1].ToString() : finalWord,
|
||||||
|
TranslationValue = isInterpolation && match.Groups[2].Length > 0 ? match.Groups[2].ToString() : ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// indicates if running in development mode
|
/// indicates if running in development mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -991,5 +972,27 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// wrapper method for humanizee that uses current current culture
|
||||||
|
/// </summary>
|
||||||
|
public static string HumanizeForCurrentCulture(this TimeSpan timeSpan, int precision = 1, TimeUnit maxUnit = TimeUnit.Week,
|
||||||
|
TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", bool toWords = false)
|
||||||
|
{
|
||||||
|
return timeSpan.Humanize(precision, CurrentLocalization.Culture, maxUnit, minUnit, collectionSeparator, toWords);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// wrapper method for humanizee that uses current current culture
|
||||||
|
/// </summary>
|
||||||
|
public static string HumanizeForCurrentCulture(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null, CultureInfo culture = null)
|
||||||
|
{
|
||||||
|
return input.Humanize(utcDate, dateToCompareAgainst, CurrentLocalization.Culture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToTranslatedName(this MetaType metaType)
|
||||||
|
{
|
||||||
|
return CurrentLocalization.LocalizationIndex[$"META_TYPE_{metaType.ToString().ToUpper()}_NAME"];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ using SharedLibraryCore.Dtos;
|
|||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using Stats.Dtos;
|
using Stats.Dtos;
|
||||||
using StatsWeb.Dtos;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FakeItEasy" Version="6.0.0" />
|
<PackageReference Include="FakeItEasy" Version="6.2.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1">
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user