Compare commits

..

40 Commits

Author SHA1 Message Date
b9c4a1b5f6 finish initial implementing application configuration editing through webfront
todo: server configuration
2019-04-12 22:25:18 -05:00
f0fd4c66e9 add configuration option to force local translations
fix silly bug with no being able to claim ownership
continue work on configuration via webfront
2019-04-11 20:43:05 -05:00
52fe8fc847 !setgravatar uses meta service now
update certain prompts to use interpolated strings from translation
update application version
2019-04-09 15:02:49 -05:00
9f8c35dbed fix bug with chat context timestamps not parsing is different machine locales
add disallowed client names to default config
fix ping not working for targets
2019-04-08 20:31:32 -05:00
9d9be7f8af few more small fixes
complete join button on webfront
update for 2.2.6.0
2019-04-08 12:29:48 -05:00
dd82a5e3fa start add of join button (still need to grab the external IP address)
finish up fixes for alias stuff
2019-04-07 20:14:59 -05:00
8667532d24 remove create proxy as it's not even used anymore
more fixes for alias stuff
hopefully fix rare bug where client activity cshtml loop goes oob
add URLProtocol format to event parsers to allow connecting through webfront
2019-04-06 21:48:49 -05:00
863ba8b096 strip drive letter on gamelog server if running on linux
strip undecodable chacters from gamelog server log file
finish re work on alias add/update ( I think)
2019-04-05 21:15:17 -05:00
8ab89e113d clean up log reader/make it output more useful message if things go wrong
add unflag as a penalty
show bans/tempbans even after they've expired on penalty list
continue making alias links great again
2019-04-05 13:34:03 -05:00
00634780d4 use "world client" when recieving fall damage/damage
fix rare bug with GetClientByName
refactor some alias/ef stuff. still more to do
2019-04-02 20:20:37 -05:00
6f80f1edbb refine webfront pages
finish refactor of penalty information/profile
optimize pull penalty query
start impl of quick message mapping
2019-03-31 19:56:31 -05:00
9393b35c39 start implementation of configuration via webfront 2019-03-30 22:04:15 -05:00
37d3f4f90d changes for latest release 2019-03-30 17:21:01 -05:00
8521df85f5 finish initial rework of profile page with meta pagination 2019-03-29 21:56:56 -05:00
7b8126d57e continue rework of profile
start moving profile info out of javascript into componentview
rework meta data to include count and offset
2019-03-27 19:40:26 -05:00
1e2e2218e3 finish initial rework of profile page 2019-03-26 21:02:11 -05:00
11e3235d5d fix issue with not loading last connection for admins
continue work on fixing profile
2019-03-25 21:12:16 -05:00
cae6d8389e fix bug with privileged users not always showing the most recent profile
temporary bans are now applied to all linked accounts instead of a per-guid basis
rework set level flow
add guid and ip address (if logged in) to public async endpoint
2019-03-24 21:34:20 -05:00
1056c7c335 fix for issue #70
update start script for windows
2019-03-18 10:36:31 -05:00
95e4ee672e Merge branch 'master' of https://github.com/RaidMax/IW4M-Admin 2019-03-17 17:38:17 -05:00
bf0a0befc6 fix top players row not fill full width
make stats web use view imports
add more games/maps to default settings (thanks to FryTechTiddys
#7622)
replace ConfigurationBuilder Config Handler implementation with Newtonsoft.Json as it wasn't deserializing "complex" classes properly
2019-03-17 17:37:50 -05:00
53c3ff6ce3 2 typos fixed
Plutoniun -> Plutonium
IW3 -> IW4
2019-03-17 08:06:15 -05:00
c8ec0eefa9 game log reader reads async now.
should have done that a long time ago
update profile page to have a bit better space usage
2019-03-09 10:28:04 -06:00
82bae772f0 most played command now ordered by play time
issue #68
2019-03-02 17:29:09 -06:00
af3aea77bc finish UI tweaks for issue #39 2019-03-02 14:29:09 -06:00
b3e5f468a1 finish implementation of per server top stats page 2019-02-27 20:13:15 -06:00
c21bf2ebf1 continue working on per servver topstats 2019-02-26 21:25:27 -06:00
d318a57830 start implementation for per server top stats
issue #39
2019-02-25 19:36:10 -06:00
61f1436faf prompt user to continue if not all servers can be connected
issue #58
2019-02-24 19:35:59 -06:00
0c527a5f65 Add lock menu icon for tempbanned players
issue #62
2019-02-24 19:07:56 -06:00
1457b843e2 one more small fix for meta service after testing live 2019-02-22 19:37:53 -06:00
de69bed792 small migration fix for MySql 2019-02-22 19:35:03 -06:00
4b1f44cc2a re-enable login to webfront with password
update cookie to last 3 months
add configuration option to limit # of rss feed items
prevent database tracking of bots if ignore bots requested
add last map and last server played to profile
2019-02-22 19:06:51 -06:00
74cdf8e885 Fix bug introduced with auto messages 2019-02-19 19:39:09 -06:00
5d41059641 accidentally copied a file to the wrong project 2019-02-18 19:48:28 -06:00
2e6889d9bb implement RSS feed in auto messages for issue #53
modified automessages to use async mesthods instead of synchronous
2019-02-18 19:30:38 -06:00
9c4d23f0b4 enhancement for issue #63 2019-02-17 18:48:40 -06:00
b4e3e8526a fix for issue #66 2019-02-17 13:16:48 -06:00
e3944fb8c2 add web project for stats to fix bug with pre compiled razor templates 2019-02-16 17:18:50 -06:00
40f1697c97 fix damage event not including log line
complete initiall implementation for "2FA"
issue #52
issue #66
2019-02-16 15:04:40 -06:00
250 changed files with 4452 additions and 1914 deletions

3
.gitignore vendored
View File

@ -223,7 +223,7 @@ global.min.js
bootstrap-custom.min.css bootstrap-custom.min.css
**/Master/static **/Master/static
**/Master/dev_env **/Master/dev_env
/WebfrontCore/Views/Plugins/Stats /WebfrontCore/Views/Plugins/*
/WebfrontCore/wwwroot/**/dds /WebfrontCore/wwwroot/**/dds
/DiscordWebhook/env /DiscordWebhook/env
@ -233,3 +233,4 @@ launchSettings.json
/VpnDetectionPrivate.js /VpnDetectionPrivate.js
/Plugins/ScriptPlugins/VpnDetectionPrivate.js /Plugins/ScriptPlugins/VpnDetectionPrivate.js
**/Master/env_master **/Master/env_master
/GameLogServer/log_env

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using RestEase; using RestEase;
using SharedLibraryCore; using SharedLibraryCore;
@ -11,6 +12,7 @@ namespace IW4MAdmin.Application.API.Master
public class HeartbeatState public class HeartbeatState
{ {
public bool Connected { get; set; } public bool Connected { get; set; }
public CancellationToken Token { get; set; }
} }
public class Heartbeat public class Heartbeat

View File

@ -6,11 +6,11 @@
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish> <MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId> <PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.2.5.1</Version> <Version>2.2.6.1</Version>
<Authors>RaidMax</Authors> <Authors>RaidMax</Authors>
<Company>Forever None</Company> <Company>Forever None</Company>
<Product>IW4MAdmin</Product> <Product>IW4MAdmin</Product>
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated server</Description> <Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated servers</Description>
<Copyright>2019</Copyright> <Copyright>2019</Copyright>
<PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl> <PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>
@ -31,8 +31,8 @@
<PropertyGroup> <PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection> <ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation> <TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.5.1</AssemblyVersion> <AssemblyVersion>2.2.6.1</AssemblyVersion>
<FileVersion>2.2.5.1</FileVersion> <FileVersion>2.2.6.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -45,21 +45,6 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="Properties\IW4MAdmin.en-US.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>IW4MAdmin.en-US.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\IW4MAdmin.en-US.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>IW4MAdmin.en-US.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="DefaultSettings.json"> <None Update="DefaultSettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>

View File

@ -7,10 +7,12 @@ using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Events; using SharedLibraryCore.Events;
using SharedLibraryCore.Exceptions; using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Services; using SharedLibraryCore.Services;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -39,10 +41,9 @@ namespace IW4MAdmin.Application
public IList<IRConParser> AdditionalRConParsers { get; } public IList<IRConParser> AdditionalRConParsers { get; }
public IList<IEventParser> AdditionalEventParsers { get; } public IList<IEventParser> AdditionalEventParsers { get; }
public ITokenAuthentication TokenAuthenticator => Authenticator; public ITokenAuthentication TokenAuthenticator => Authenticator;
public ITokenAuthentication Authenticator => _authenticator; public ITokenAuthentication Authenticator => _authenticator;
public string ExternalIPAddress { get; private set; }
static ApplicationManager Instance; static ApplicationManager Instance;
readonly List<AsyncStatus> TaskStatuses; readonly List<AsyncStatus> TaskStatuses;
@ -58,6 +59,7 @@ namespace IW4MAdmin.Application
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1); readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>(); readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
readonly ITokenAuthentication _authenticator; readonly ITokenAuthentication _authenticator;
private readonly MetaService _metaService;
private ApplicationManager() private ApplicationManager()
{ {
@ -77,6 +79,7 @@ namespace IW4MAdmin.Application
OnServerEvent += OnGameEvent; OnServerEvent += OnGameEvent;
OnServerEvent += EventApi.OnGameEvent; OnServerEvent += EventApi.OnGameEvent;
_authenticator = new TokenAuthentication(); _authenticator = new TokenAuthentication();
_metaService = new MetaService();
} }
private async void OnGameEvent(object sender, GameEventArgs args) private async void OnGameEvent(object sender, GameEventArgs args)
@ -154,7 +157,7 @@ namespace IW4MAdmin.Application
return Instance ?? (Instance = new ApplicationManager()); return Instance ?? (Instance = new ApplicationManager());
} }
public async Task UpdateServerStates() public async Task UpdateServerStates(CancellationToken token)
{ {
// store the server hash code and task for it // store the server hash code and task for it
var runningUpdateTasks = new Dictionary<long, Task>(); var runningUpdateTasks = new Dictionary<long, Task>();
@ -198,6 +201,11 @@ namespace IW4MAdmin.Application
Logger.WriteWarning($"Failed to update status for {server}"); Logger.WriteWarning($"Failed to update status for {server}");
Logger.WriteDebug(e.GetExceptionInfo()); Logger.WriteDebug(e.GetExceptionInfo());
} }
finally
{
server.IsInitialized = true;
}
})); }));
} }
#if DEBUG #if DEBUG
@ -206,17 +214,19 @@ namespace IW4MAdmin.Application
ThreadPool.GetAvailableThreads(out int availableThreads, out int m); ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks"); Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
#endif #endif
await Task.Delay(ConfigHandler.Configuration().RConPollRate); try
{
await Task.Delay(ConfigHandler.Configuration().RConPollRate, token);
}
// if a cancellation is received, we want to return immediately
catch { break; }
} }
// trigger the event processing loop to end
SetHasEvent();
} }
public async Task Init() public async Task Init()
{ {
Running = true; Running = true;
ExternalIPAddress = await Utilities.GetExternalIP();
#region PLUGINS #region PLUGINS
SharedLibraryCore.Plugins.PluginImporter.Load(this); SharedLibraryCore.Plugins.PluginImporter.Load(this);
@ -246,10 +256,11 @@ namespace IW4MAdmin.Application
ConfigHandler.Set((ApplicationConfiguration)new ApplicationConfiguration().Generate()); ConfigHandler.Set((ApplicationConfiguration)new ApplicationConfiguration().Generate());
var newConfig = ConfigHandler.Configuration(); var newConfig = ConfigHandler.Configuration();
newConfig.AutoMessagePeriod = defaultConfig.AutoMessagePeriod;
newConfig.AutoMessages = defaultConfig.AutoMessages; newConfig.AutoMessages = defaultConfig.AutoMessages;
newConfig.GlobalRules = defaultConfig.GlobalRules; newConfig.GlobalRules = defaultConfig.GlobalRules;
newConfig.Maps = defaultConfig.Maps; newConfig.Maps = defaultConfig.Maps;
newConfig.DisallowedClientNames = defaultConfig.DisallowedClientNames;
newConfig.QuickMessages = defaultConfig.QuickMessages;
if (newConfig.Servers == null) if (newConfig.Servers == null)
{ {
@ -277,7 +288,7 @@ namespace IW4MAdmin.Application
} }
} }
else if (config != null) else
{ {
if (string.IsNullOrEmpty(config.Id)) if (string.IsNullOrEmpty(config.Id))
{ {
@ -313,7 +324,7 @@ namespace IW4MAdmin.Application
} }
} }
else if (config.Servers.Count == 0) if (config.Servers.Count == 0)
{ {
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid"); throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
} }
@ -376,7 +387,7 @@ namespace IW4MAdmin.Application
Commands.Add(new CPing()); Commands.Add(new CPing());
Commands.Add(new CSetGravatar()); Commands.Add(new CSetGravatar());
Commands.Add(new CNextMap()); Commands.Add(new CNextMap());
Commands.Add(new GenerateTokenCommand()); Commands.Add(new RequestTokenCommand());
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands) foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
{ {
@ -384,11 +395,139 @@ namespace IW4MAdmin.Application
} }
#endregion #endregion
#region META
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);
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 INIT #region INIT
int failedServers = 0;
int successServers = 0;
Exception lastException = null;
async Task Init(ServerConfiguration Conf) async Task Init(ServerConfiguration Conf)
{ {
// setup the event handler after the class is initialized // setup the event handler after the class is initialized
Handler = new GameEventHandler(this); Handler = new GameEventHandler(this);
try try
{ {
var ServerInstance = new IW4MServer(this, Conf); var ServerInstance = new IW4MServer(this, Conf);
@ -399,7 +538,7 @@ namespace IW4MAdmin.Application
_servers.Add(ServerInstance); _servers.Add(ServerInstance);
} }
Logger.WriteVerbose($"{Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"]} {ServerInstance.Hostname}"); Logger.WriteVerbose(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(ServerInstance.Hostname));
// add the start event for this server // add the start event for this server
var e = new GameEvent() var e = new GameEvent()
@ -410,26 +549,37 @@ namespace IW4MAdmin.Application
}; };
Handler.AddEvent(e); Handler.AddEvent(e);
successServers++;
} }
catch (ServerException e) catch (ServerException e)
{ {
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"]} [{Conf.IPAddress}:{Conf.Port}]"); Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"]} [{Conf.IPAddress}:{Conf.Port}]");
if (e.GetType() == typeof(DvarException)) if (e.GetType() == typeof(DvarException))
{ {
Logger.WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"]} {(e as DvarException).Data["dvar_name"]} ({Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR_HELP"]})"); Logger.WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt((e as DvarException).Data["dvar_name"])} ({Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR_HELP"]})");
} }
else if (e.GetType() == typeof(NetworkException)) else if (e.GetType() == typeof(NetworkException))
{ {
Logger.WriteDebug(e.Message); Logger.WriteDebug(e.Message);
} }
// throw the exception to the main method to stop before instantly exiting failedServers++;
throw e; lastException = e;
} }
} }
await Task.WhenAll(config.Servers.Select(c => Init(c)).ToArray()); await Task.WhenAll(config.Servers.Select(c => Init(c)).ToArray());
if (failedServers > 0 && successServers > 0)
{
if (!Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_START_WITH_ERRORS"]))
{
throw lastException;
}
}
#endregion #endregion
} }
@ -494,27 +644,34 @@ namespace IW4MAdmin.Application
} }
} }
await Task.Delay(30000);
try
{
await Task.Delay(30000, heartbeatState.Token);
}
catch { break; }
} }
} }
public void Start() public void Start()
{ {
var tokenSource = new CancellationTokenSource();
// this needs to be run seperately from the main thread // this needs to be run seperately from the main thread
var _ = Task.Run(() => SendHeartbeat(new HeartbeatState())); _ = Task.Run(() => SendHeartbeat(new HeartbeatState() { Token = tokenSource.Token }));
_ = Task.Run(() => UpdateServerStates()); _ = Task.Run(() => UpdateServerStates(tokenSource.Token));
while (Running) while (Running)
{ {
OnQuit.Wait(); OnQuit.Wait();
tokenSource.Cancel();
OnQuit.Reset(); OnQuit.Reset();
} }
_servers.Clear();
} }
public void Stop() public void Stop()
{ {
Running = false; Running = false;
OnQuit.Set();
} }
public ILogger GetLogger(long serverId) public ILogger GetLogger(long serverId)
@ -589,12 +746,12 @@ namespace IW4MAdmin.Application
public void SetHasEvent() public void SetHasEvent()
{ {
OnQuit.Set();
} }
public IList<Assembly> GetPluginAssemblies() public IList<Assembly> GetPluginAssemblies()
{ {
return SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies; return SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies.Union(SharedLibraryCore.Plugins.PluginImporter.Assemblies).ToList();
} }
public IPageList GetPageList() public IPageList GetPageList()

View File

@ -98,10 +98,8 @@ if "%CurrentConfiguration%" == "Release" (
) )
echo making start scripts echo making start scripts
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd" @(echo @echo off && echo @title IW4MAdmin && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd" @(echo @echo off && echo @title IW4MAdmin && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd"
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh" @(echo #!/bin/bash && echo dotnet Lib/IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh"
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh" @(echo #!/bin/bash && echo dotnet Lib/IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh"
eCHO "%CurrentConfiguration%"

View File

@ -16,7 +16,231 @@
"Keep grenade launcher use to a minimum", "Keep grenade launcher use to a minimum",
"Balance teams at ALL times" "Balance teams at ALL times"
], ],
"DisallowedClientNames": ["Unknown Soldier", "VickNet", "UnknownSoldier", "CHEATER"],
"QuickMessages": [
{
"Game": "IW4",
"Messages": {
"QUICKMESSAGE_AREA_SECURE": "Area secure!",
"QUICKMESSAGE_ARE_YOU_CRAZY": "Are you crazy?",
"QUICKMESSAGE_ATTACK_LEFT_FLANK": "Attack left flank!",
"QUICKMESSAGE_ATTACK_RIGHT_FLANK": "Attack right flank!",
"QUICKMESSAGE_COME_ON": "Come on.",
"QUICKMESSAGE_ENEMIES_SPOTTED": "Multiple contacts!",
"QUICKMESSAGE_ENEMY_DOWN": "Enemy down!",
"QUICKMESSAGE_ENEMY_GRENADE": "Enemy grenade!",
"QUICKMESSAGE_ENEMY_SPOTTED": "Contact!",
"QUICKMESSAGE_FALL_BACK": "Fall back!",
"QUICKMESSAGE_FOLLOW_ME": "On me!",
"QUICKMESSAGE_GREAT_SHOT": "Nice shot!",
"QUICKMESSAGE_GRENADE": "Grenade!",
"QUICKMESSAGE_HOLD_THIS_POSITION": "Hold this position!",
"QUICKMESSAGE_HOLD_YOUR_FIRE": "Hold your fire!",
"QUICKMESSAGE_IM_IN_POSITION": "In position.",
"QUICKMESSAGE_IM_ON_MY_WAY": "Moving.",
"QUICKMESSAGE_MOVE_IN": "Move in!",
"QUICKMESSAGE_NEED_REINFORCEMENTS": "Need reinforcements!",
"QUICKMESSAGE_NO_SIR": "Negative.",
"QUICKMESSAGE_ON_MY_WAY": "On my way.",
"QUICKMESSAGE_REGROUP": "Regroup!",
"QUICKMESSAGE_SNIPER": "Sniper!",
"QUICKMESSAGE_SORRY": "Sorry.",
"QUICKMESSAGE_SQUAD_ATTACK_LEFT_FLANK": "Squad, attack left flank!",
"QUICKMESSAGE_SQUAD_ATTACK_RIGHT_FLANK": "Squad, attack right flank!",
"QUICKMESSAGE_SQUAD_HOLD_THIS_POSITION": "Squad, hold this position!",
"QUICKMESSAGE_SQUAD_REGROUP": "Squad, regroup!",
"QUICKMESSAGE_SQUAD_STICK_TOGETHER": "Squad, stick together!",
"QUICKMESSAGE_STICK_TOGETHER": "Stick together!",
"QUICKMESSAGE_SUPPRESSING_FIRE": "Base of fire!",
"QUICKMESSAGE_TOOK_LONG_ENOUGH": "Took long enough!",
"QUICKMESSAGE_TOOK_YOU_LONG_ENOUGH": "Took you long enough!",
"QUICKMESSAGE_WATCH_SIX": "Watch your six!",
"QUICKMESSAGE_YES_SIR": "Roger.",
"QUICKMESSAGE_YOURE_CRAZY": "You're crazy!",
"QUICKMESSAGE_YOURE_NUTS": "You're nuts!",
"QUICKMESSAGE_YOU_OUTTA_YOUR_MIND": "You outta your mind?"
}
}
],
"Maps": [ "Maps": [
{
"Game": "IW3",
"Maps": [
{
"Alias": "Ambush",
"Name": "mp_convoy"
},
{
"Alias": "Backlot",
"Name": "mp_backlot"
},
{
"Alias": "Bloc",
"Name": "mp_bloc"
},
{
"Alias": "Bog",
"Name": "mp_bog"
},
{
"Alias": "Countdown",
"Name": "mp_countdown"
},
{
"Alias": "Crash",
"Name": "mp_crash"
},
{
"Alias": "Crossfire",
"Name": "mp_crossfire"
},
{
"Alias": "District",
"Name": "mp_citystreets"
},
{
"Alias": "Downpour",
"Name": "mp_farm"
},
{
"Alias": "Overgrown",
"Name": "mp_overgrown"
},
{
"Alias": "Pipeline",
"Name": "mp_pipeline"
},
{
"Alias": "Shipment",
"Name": "mp_shipment"
},
{
"Alias": "Showdown",
"Name": "mp_showdown"
},
{
"Alias": "Strike",
"Name": "mp_strike"
},
{
"Alias": "Vacant",
"Name": "mp_vacant"
},
{
"Alias": "Wet Work",
"Name": "mp_cargoship"
},
{
"Alias": "Winter Crash",
"Name": "mp_crash_snow"
},
{
"Alias": "Broadcast",
"Name": "mp_broadcast"
},
{
"Alias": "Creek",
"Name": "mp_creek"
},
{
"Alias": "Chinatown",
"Name": "mp_carentan"
},
{
"Alias": "Killhouse",
"Name": "mp_killhouse"
}
]
},
{
"Game": "T4",
"Maps": [
{
"Alias": "Airfield",
"Name": "mp_airfield"
},
{
"Alias": "Asylum",
"Name": "mp_asylum"
},
{
"Alias": "Castle",
"Name": "mp_castle"
},
{
"Alias": "Cliffside",
"Name": "mp_shrine"
},
{
"Alias": "Courtyard",
"Name": "mp_courtyard"
},
{
"Alias": "Dome",
"Name": "mp_dome"
},
{
"Alias": "Downfall",
"Name": "mp_downfall"
},
{
"Alias": "Hanger",
"Name": "mp_hangar"
},
{
"Alias": "Makin",
"Name": "mp_makin"
},
{
"Alias": "Outskirts",
"Name": "mp_outskirts"
},
{
"Alias": "Roundhouse",
"Name": "mp_roundhouse"
},
{
"Alias": "Upheaval",
"Name": "mp_suburban"
},
{
"Alias": "Knee Deep",
"Name": "mp_kneedeep"
},
{
"Alias": "Nightfire",
"Name": "mp_nachtfeuer"
},
{
"Alias": "Station",
"Name": "mp_subway"
},
{
"Alias": "Banzai",
"Name": "mp_kwai"
},
{
"Alias": "Corrosion",
"Name": "mp_stalingrad"
},
{
"Alias": "Sub Pens",
"Name": "mp_docks"
},
{
"Alias": "Battery",
"Name": "mp_drum"
},
{
"Alias": "Breach",
"Name": "mp_bgate"
},
{
"Alias": "Revolution",
"Name": "mp_vodka"
}
]
},
{ {
"Game": "IW4", "Game": "IW4",
"Maps": [ "Maps": [
@ -246,6 +470,260 @@
} }
] ]
}, },
{
"Game": "T5",
"Maps": [
{
"Alias": "Array",
"Name": "mp_array"
},
{
"Alias": "Berlin Wall",
"Name": "mp_berlinwall2"
},
{
"Alias": "Convoy",
"Name": "mp_gridlock"
},
{
"Alias": "Cracked",
"Name": "mp_cracked"
},
{
"Alias": "Crisis",
"Name": "mp_crisis"
},
{
"Alias": "Discovery",
"Name": "mp_discovery"
},
{
"Alias": "Drive-In",
"Name": "mp_drivein"
},
{
"Alias": "Firing Range",
"Name": "mp_firingrange"
},
{
"Alias": "Grid",
"Name": "mp_duga"
},
{
"Alias": "Hangar 18",
"Name": "mp_area51"
},
{
"Alias": "Hanoi",
"Name": "mp_hanoi"
},
{
"Alias": "Hazard",
"Name": "mp_golfcourse"
},
{
"Alias": "Hotel",
"Name": "mp_hotel"
},
{
"Alias": "Jungle",
"Name": "mp_havoc"
},
{
"Alias": "Kowloon",
"Name": "mp_kowloon"
},
{
"Alias": "Launch",
"Name": "mp_cosmodrome"
},
{
"Alias": "Nuketown",
"Name": "mp_nuked"
},
{
"Alias": "Radiation",
"Name": "mp_radiation"
},
{
"Alias": "Silo",
"Name": "mp_silo"
},
{
"Alias": "Stadium",
"Name": "mp_stadium"
},
{
"Alias": "Stockpile",
"Name": "mp_outskirts"
},
{
"Alias": "Summit",
"Name": "mp_mountain"
},
{
"Alias": "Villa",
"Name": "mp_villa"
},
{
"Alias": "WMD",
"Name": "mp_russianbase"
},
{
"Alias": "Zoo",
"Name": "mp_zoo"
}
]
},
{
"Game": "IW5",
"Maps": [
{
"Alias": "Seatown",
"Name": "mp_seatown"
},
{
"Alias": "Lockdown",
"Name": "mp_alpha"
},
{
"Alias": "Mission",
"Name": "mp_bravo"
},
{
"Alias": "Carbon",
"Name": "mp_carbon"
},
{
"Alias": "Dome",
"Name": "mp_dome"
},
{
"Alias": "Arkaden",
"Name": "mp_plaza2"
},
{
"Alias": "Downturn",
"Name": "mp_exchange"
},
{
"Alias": "Bootleg",
"Name": "mp_bootleg"
},
{
"Alias": "Hardhat",
"Name": "mp_hardhat"
},
{
"Alias": "Interchange",
"Name": "mp_interchange"
},
{
"Alias": "Fallen",
"Name": "mp_lambeth"
},
{
"Alias": "Outpost",
"Name": "mp_radar"
},
{
"Alias": "Bakaara",
"Name": "mp_mogadishu"
},
{
"Alias": "Resistance",
"Name": "mp_paris"
},
{
"Alias": "Underground",
"Name": "mp_underground"
},
{
"Alias": "Village",
"Name": "mp_village"
},
{
"Alias": "Aground",
"Name": "mp_aground_ss"
},
{
"Alias": "Boardwalk",
"Name": "mp_boardwalk"
},
{
"Alias": "U-turn",
"Name": "mp_burn_ss"
},
{
"Alias": "Foundation",
"Name": "mp_cement"
},
{
"Alias": "Erosion",
"Name": "mp_courtyard_ss"
},
{
"Alias": "Intersection",
"Name": "mp_crosswalk_ss"
},
{
"Alias": "Getaway",
"Name": "mp_hillside_ss"
},
{
"Alias": "Piazza",
"Name": "mp_italy"
},
{
"Alias": "Sanctuary",
"Name": "mp_meteora"
},
{
"Alias": "Gulch",
"Name": "mp_moab"
},
{
"Alias": "Black Box",
"Name": "mp_morningwood"
},
{
"Alias": "Parish",
"Name": "mp_nola"
},
{
"Alias": "Overwatch",
"Name": "mp_overwatch"
},
{
"Alias": "Liberation",
"Name": "mp_park"
},
{
"Alias": "Oasis",
"Name": "mp_qadeem"
},
{
"Alias": "Lookout",
"Name": "mp_restrepo_ss"
},
{
"Alias": "Off Shore",
"Name": "mp_roughneck"
},
{
"Alias": "Decommission",
"Name": "mp_shipbreaker"
},
{
"Alias": "Vortex",
"Name": "mp_six_ss"
},
{
"Alias": "Terminal",
"Name": "mp_terminal_cls"
}
]
},
{ {
"Game": "T6", "Game": "T6",
"Maps": [ "Maps": [
@ -402,95 +880,6 @@
"Name": "zm_transit" "Name": "zm_transit"
} }
] ]
},
{
"Game": "IW3",
"Maps": [
{
"Alias": "Ambush",
"Name": "mp_convoy"
},
{
"Alias": "Backlot",
"Name": "mp_backlot"
},
{
"Alias": "Bloc",
"Name": "mp_bloc"
},
{
"Alias": "Bog",
"Name": "mp_bog"
},
{
"Alias": "Countdown",
"Name": "mp_countdown"
},
{
"Alias": "Crash",
"Name": "mp_crash"
},
{
"Alias": "Crossfire",
"Name": "mp_crossfire"
},
{
"Alias": "District",
"Name": "mp_citystreets"
},
{
"Alias": "Downpour",
"Name": "mp_farm"
},
{
"Alias": "Overgrown",
"Name": "mp_overgrown"
},
{
"Alias": "Pipeline",
"Name": "mp_pipeline"
},
{
"Alias": "Shipment",
"Name": "mp_shipment"
},
{
"Alias": "Showdown",
"Name": "mp_showdown"
},
{
"Alias": "Strike",
"Name": "mp_strike"
},
{
"Alias": "Vacant",
"Name": "mp_vacant"
},
{
"Alias": "Wet Work",
"Name": "mp_cargoship"
},
{
"Alias": "Winter Crash",
"Name": "mp_crash_snow"
},
{
"Alias": "Broadcast",
"Name": "mp_broadcast"
},
{
"Alias": "Creek",
"Name": "mp_creek"
},
{
"Alias": "Chinatown",
"Name": "mp_carentan"
},
{
"Alias": "Killhouse",
"Name": "mp_killhouse"
}
]
} }
] ]
} }

View File

@ -73,6 +73,8 @@ namespace IW4MAdmin.Application.EventParsers
public Game GameName { get; set; } = Game.COD; public Game GameName { get; set; } = Game.COD;
public string URLProtocolFormat { get; set; } = "CoD://{{ip}}:{{port}}";
public virtual GameEvent GetEvent(Server server, string logLine) public virtual GameEvent GetEvent(Server server, string logLine)
{ {
logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim(); logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim();
@ -87,7 +89,7 @@ namespace IW4MAdmin.Application.EventParsers
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.JoinTeam, Type = GameEvent.EventType.JoinTeam,
Data = eventType, Data = logLine,
Origin = origin, Origin = origin,
Owner = server Owner = server
}; };
@ -139,11 +141,16 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success) if (match.Success)
{ {
var origin = server.GetClientsAsList() string originId = match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].Value.ToString();
.First(c => c.NetworkId == match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong()); string targetId = match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].Value.ToString();
var target = server.GetClientsAsList()
.First(c => c.NetworkId == match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertLong());
var origin = !string.IsNullOrEmpty(originId) ? server.GetClientsAsList()
.First(c => c.NetworkId == originId.ConvertLong()) :
Utilities.IW4MAdminClient(server);
var target = !string.IsNullOrEmpty(targetId) ? server.GetClientsAsList()
.First(c => c.NetworkId == targetId.ConvertLong()) :
Utilities.IW4MAdminClient(server);
return new GameEvent() return new GameEvent()
{ {
@ -159,8 +166,14 @@ namespace IW4MAdmin.Application.EventParsers
if (eventType == "ScriptKill") if (eventType == "ScriptKill")
{ {
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); long originId = lineSplit[1].ConvertLong();
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()); long targetId = lineSplit[2].ConvertLong();
var origin = originId == long.MinValue ? Utilities.IW4MAdminClient(server) :
server.GetClientsAsList().First(c => c.NetworkId == originId);
var target = targetId == long.MinValue ? Utilities.IW4MAdminClient(server) :
server.GetClientsAsList().FirstOrDefault(c => c.NetworkId == targetId) ?? Utilities.IW4MAdminClient(server);
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.ScriptKill, Type = GameEvent.EventType.ScriptKill,
@ -173,8 +186,13 @@ namespace IW4MAdmin.Application.EventParsers
if (eventType == "ScriptDamage") if (eventType == "ScriptDamage")
{ {
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); long originId = lineSplit[1].ConvertLong();
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()); long targetId = lineSplit[2].ConvertLong();
var origin = originId == long.MinValue ? Utilities.IW4MAdminClient(server) :
server.GetClientsAsList().First(c => c.NetworkId == originId);
var target = targetId == long.MinValue ? Utilities.IW4MAdminClient(server) :
server.GetClientsAsList().FirstOrDefault(c => c.NetworkId == targetId) ?? Utilities.IW4MAdminClient(server);
return new GameEvent() return new GameEvent()
{ {
@ -195,15 +213,21 @@ namespace IW4MAdmin.Application.EventParsers
if (regexMatch.Success) if (regexMatch.Success)
{ {
var origin = server.GetClientsAsList() string originId = regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
.First(c => c.NetworkId == regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong()); string targetId = regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString();
var target = server.GetClientsAsList()
.First(c => c.NetworkId == regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertLong()); var origin = !string.IsNullOrEmpty(originId) ? server.GetClientsAsList()
.First(c => c.NetworkId == originId.ConvertLong()) :
Utilities.IW4MAdminClient(server);
var target = !string.IsNullOrEmpty(targetId) ? server.GetClientsAsList()
.First(c => c.NetworkId == targetId.ConvertLong()) :
Utilities.IW4MAdminClient(server);
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.Damage, Type = GameEvent.EventType.Damage,
Data = eventType, Data = logLine,
Origin = origin, Origin = origin,
Target = target, Target = target,
Owner = server Owner = server
@ -229,7 +253,6 @@ namespace IW4MAdmin.Application.EventParsers
{ {
CurrentAlias = new EFAlias() CurrentAlias = new EFAlias()
{ {
Active = false,
Name = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors(), Name = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors(),
}, },
NetworkId = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(), NetworkId = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(),
@ -256,7 +279,6 @@ namespace IW4MAdmin.Application.EventParsers
{ {
CurrentAlias = new EFAlias() CurrentAlias = new EFAlias()
{ {
Active = false,
Name = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors() Name = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors()
}, },
NetworkId = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(), NetworkId = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(),

View File

@ -30,7 +30,7 @@ namespace IW4MAdmin.Application.IO
{ {
while (!Server.Manager.ShutdownRequested()) while (!Server.Manager.ShutdownRequested())
{ {
if ((Server.Manager as ApplicationManager).IsInitialized) if (Server.IsInitialized)
{ {
try try
{ {

View File

@ -34,9 +34,9 @@ namespace IW4MAdmin.Application.IO
// todo: max async // todo: max async
// take the old start position and go back the number of new characters // take the old start position and go back the number of new characters
rd.BaseStream.Seek(-fileSizeDiff, SeekOrigin.End); rd.BaseStream.Seek(-fileSizeDiff, SeekOrigin.End);
// the difference should be in the range of a int :P
string newLine; string newLine;
while (!String.IsNullOrEmpty(newLine = rd.ReadLine())) while (!string.IsNullOrEmpty(newLine = await rd.ReadLineAsync()))
{ {
logLines.Add(newLine); logLines.Add(newLine);
} }

View File

@ -9,6 +9,7 @@ using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Localization; using SharedLibraryCore.Localization;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using SharedLibraryCore.Services;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -17,6 +18,7 @@ using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
namespace IW4MAdmin namespace IW4MAdmin
{ {
@ -34,7 +36,6 @@ namespace IW4MAdmin
override public async Task OnClientConnected(EFClient clientFromLog) override public async Task OnClientConnected(EFClient clientFromLog)
{ {
Logger.WriteDebug($"Client slot #{clientFromLog.ClientNumber} now reserved"); Logger.WriteDebug($"Client slot #{clientFromLog.ClientNumber} now reserved");
Clients[clientFromLog.ClientNumber] = new EFClient();
try try
{ {
@ -44,19 +45,16 @@ namespace IW4MAdmin
if (client == null) if (client == null)
{ {
Logger.WriteDebug($"Client {clientFromLog} first time connecting"); Logger.WriteDebug($"Client {clientFromLog} first time connecting");
clientFromLog.CurrentServer = this;
client = await Manager.GetClientService().Create(clientFromLog); client = await Manager.GetClientService().Create(clientFromLog);
} }
// client has connected in the past /// this is only a temporary version until the IPAddress is transmitted
else client.CurrentAlias = new EFAlias()
{
// this is only a temporary version until the IPAddress is transmitted
client.CurrentAlias = new EFAlias
{ {
Name = clientFromLog.Name, Name = clientFromLog.Name,
IPAddress = clientFromLog.IPAddress IPAddress = clientFromLog.IPAddress
}; };
}
Logger.WriteInfo($"Client {client} connected..."); Logger.WriteInfo($"Client {client} connected...");
@ -68,8 +66,6 @@ namespace IW4MAdmin
client.CurrentServer = this; client.CurrentServer = this;
Clients[client.ClientNumber] = client; Clients[client.ClientNumber] = client;
client.State = EFClient.ClientState.Connected;
#if DEBUG == true #if DEBUG == true
Logger.WriteDebug($"End PreConnect for {client}"); Logger.WriteDebug($"End PreConnect for {client}");
#endif #endif
@ -81,11 +77,8 @@ namespace IW4MAdmin
}; };
Manager.GetEventHandler().AddEvent(e); Manager.GetEventHandler().AddEvent(e);
if (client.IPAddress != null)
{
await client.OnJoin(client.IPAddress); await client.OnJoin(client.IPAddress);
} client.State = ClientState.Connected;
} }
catch (Exception ex) catch (Exception ex)
@ -97,12 +90,14 @@ namespace IW4MAdmin
override public async Task OnClientDisconnected(EFClient client) override public async Task OnClientDisconnected(EFClient client)
{ {
Logger.WriteInfo($"Client {client} [{client.State.ToString().ToLower()}] disconnecting...");
await client.OnDisconnect();
Clients[client.ClientNumber] = null;
#if DEBUG == true #if DEBUG == true
Logger.WriteDebug($"End PreDisconnect for {client}"); if (client.ClientNumber >= 0)
{
#endif #endif
Logger.WriteInfo($"Client {client} [{client.State.ToString().ToLower()}] disconnecting...");
Clients[client.ClientNumber] = null;
await client.OnDisconnect();
var e = new GameEvent() var e = new GameEvent()
{ {
Origin = client, Origin = client,
@ -111,6 +106,9 @@ namespace IW4MAdmin
}; };
Manager.GetEventHandler().AddEvent(e); Manager.GetEventHandler().AddEvent(e);
#if DEBUG == true
}
#endif
} }
public override async Task ExecuteEvent(GameEvent E) public override async Task ExecuteEvent(GameEvent E)
@ -178,7 +176,9 @@ namespace IW4MAdmin
{ {
if (E.Type == GameEvent.EventType.ChangePermission) if (E.Type == GameEvent.EventType.ChangePermission)
{ {
if (!E.Target.IsPrivileged()) var newPermission = (Permission)E.Extra;
if (newPermission < Permission.Moderator)
{ {
// remove banned or demoted privileged user // remove banned or demoted privileged user
Manager.GetPrivilegedClients().Remove(E.Target.ClientId); Manager.GetPrivilegedClients().Remove(E.Target.ClientId);
@ -188,15 +188,36 @@ namespace IW4MAdmin
{ {
Manager.GetPrivilegedClients()[E.Target.ClientId] = E.Target; Manager.GetPrivilegedClients()[E.Target.ClientId] = E.Target;
} }
Logger.WriteInfo($"{E.Origin} is setting {E.Target} to permission level {newPermission}");
await Manager.GetClientService().UpdateLevel(newPermission, E.Target, E.Origin);
} }
else if (E.Type == GameEvent.EventType.PreConnect) else if (E.Type == GameEvent.EventType.PreConnect)
{ {
// we don't want to track bots in the database at all if ignore bots is requested
if (E.Origin.IsBot && Manager.GetApplicationSettings().Configuration().IgnoreBots)
{
return false;
}
var existingClient = GetClientsAsList().FirstOrDefault(_client => _client.Equals(E.Origin));
// they're already connected
if (existingClient != null)
{
Logger.WriteWarning($"detected preconnect for {E.Origin}, but they are already connected");
return false;
}
CONNECT:
if (Clients[E.Origin.ClientNumber] == null) if (Clients[E.Origin.ClientNumber] == null)
{ {
#if DEBUG == true #if DEBUG == true
Logger.WriteDebug($"Begin PreConnect for {E.Origin}"); Logger.WriteDebug($"Begin PreConnect for {E.Origin}");
#endif #endif
// we can go ahead and put them in so that they don't get re added
Clients[E.Origin.ClientNumber] = E.Origin;
await OnClientConnected(E.Origin); await OnClientConnected(E.Origin);
ChatHistory.Add(new ChatInfo() ChatHistory.Add(new ChatInfo()
@ -212,9 +233,12 @@ namespace IW4MAdmin
} }
} }
// for some reason there's still a client in the spot
else else
{ {
return false; Logger.WriteWarning($"{E.Origin} is connecteding but {Clients[E.Origin.ClientNumber]} is currently in that client slot");
await OnClientDisconnected(Clients[E.Origin.ClientNumber]);
goto CONNECT;
} }
} }
@ -233,17 +257,29 @@ namespace IW4MAdmin
}; };
var addedPenalty = await Manager.GetPenaltyService().Create(newPenalty); var addedPenalty = await Manager.GetPenaltyService().Create(newPenalty);
await Manager.GetClientService().Update(E.Target); E.Target.SetLevel(Permission.Flagged, E.Origin);
} }
else if (E.Type == GameEvent.EventType.Unflag) else if (E.Type == GameEvent.EventType.Unflag)
{ {
await Manager.GetClientService().Update(E.Target); var unflagPenalty = new Penalty()
{
Type = Penalty.PenaltyType.Unflag,
Expires = DateTime.UtcNow,
Offender = E.Target,
Offense = E.Data,
Punisher = E.Origin,
When = DateTime.UtcNow,
Link = E.Target.AliasLink
};
await Manager.GetPenaltyService().Create(unflagPenalty);
E.Target.SetLevel(Permission.User, E.Origin);
} }
else if (E.Type == GameEvent.EventType.Report) else if (E.Type == GameEvent.EventType.Report)
{ {
this.Reports.Add(new Report() Reports.Add(new Report()
{ {
Origin = E.Origin, Origin = E.Origin,
Target = E.Target, Target = E.Target,
@ -277,37 +313,21 @@ namespace IW4MAdmin
await Warn(E.Data, E.Target, E.Origin); await Warn(E.Data, E.Target, E.Origin);
} }
else if (E.Type == GameEvent.EventType.Quit) else if (E.Type == GameEvent.EventType.Disconnect)
{ {
var origin = GetClientsAsList().FirstOrDefault(_client => _client.NetworkId.Equals(E.Origin)); ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = "DISCONNECTED",
Time = DateTime.UtcNow
});
if (origin != null) await new MetaService().AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin);
{ await new MetaService().AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin);
var e = new GameEvent()
{
Type = GameEvent.EventType.Disconnect,
Origin = origin,
Owner = this
};
Manager.GetEventHandler().AddEvent(e);
}
else
{
return false;
}
} }
else if (E.Type == GameEvent.EventType.PreDisconnect) else if (E.Type == GameEvent.EventType.PreDisconnect)
{ {
if ((DateTime.UtcNow - SessionStart).TotalSeconds < 30)
{
Logger.WriteInfo($"Cancelling pre disconnect for {E.Origin} as it occured too close to map end");
E.FailReason = GameEvent.EventFailReason.Invalid;
return false;
}
// predisconnect comes from minimal rcon polled players and minimal log players // predisconnect comes from minimal rcon polled players and minimal log players
// so we need to disconnect the "full" version of the client // so we need to disconnect the "full" version of the client
var client = GetClientsAsList().FirstOrDefault(_client => _client.Equals(E.Origin)); var client = GetClientsAsList().FirstOrDefault(_client => _client.Equals(E.Origin));
@ -317,18 +337,16 @@ namespace IW4MAdmin
#if DEBUG == true #if DEBUG == true
Logger.WriteDebug($"Begin PreDisconnect for {client}"); Logger.WriteDebug($"Begin PreDisconnect for {client}");
#endif #endif
ChatHistory.Add(new ChatInfo()
{
Name = client.Name,
Message = "DISCONNECTED",
Time = DateTime.UtcNow
});
await OnClientDisconnected(client); await OnClientDisconnected(client);
#if DEBUG == true
Logger.WriteDebug($"End PreDisconnect for {client}");
#endif
} }
else else if (client?.State != ClientState.Disconnecting)
{ {
Logger.WriteWarning($"Client {E.Origin} detected as disconnecting, but could not find them in the player list");
Logger.WriteDebug($"Expected {E.Origin} but found {GetClientsAsList().FirstOrDefault(_client => _client.ClientNumber == E.Origin.ClientNumber)}");
return false; return false;
} }
} }
@ -389,7 +407,7 @@ namespace IW4MAdmin
var dict = (Dictionary<string, string>)E.Extra; var dict = (Dictionary<string, string>)E.Extra;
Gametype = dict["g_gametype"].StripColors(); Gametype = dict["g_gametype"].StripColors();
Hostname = dict["sv_hostname"].StripColors(); Hostname = dict["sv_hostname"].StripColors();
MaxClients = Int32.Parse(dict["sv_maxclients"]); MaxClients = int.Parse(dict["sv_maxclients"]);
string mapname = dict["mapname"].StripColors(); string mapname = dict["mapname"].StripColors();
CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map()
@ -440,9 +458,9 @@ namespace IW4MAdmin
return true; return true;
} }
private Task OnClientUpdate(EFClient origin) private async Task OnClientUpdate(EFClient origin)
{ {
var client = Clients[origin.ClientNumber]; var client = GetClientsAsList().FirstOrDefault(_client => _client.Equals(origin));
if (client != null) if (client != null)
{ {
@ -450,13 +468,22 @@ namespace IW4MAdmin
client.Score = origin.Score; client.Score = origin.Score;
// update their IP if it hasn't been set yet // update their IP if it hasn't been set yet
if (client.IPAddress == null && !client.IsBot) if (client.IPAddress == null &&
!client.IsBot &&
client.State == ClientState.Connected)
{ {
return client.OnJoin(origin.IPAddress); try
} {
await client.OnJoin(origin.IPAddress);
} }
return Task.CompletedTask; catch (Exception e)
{
origin.CurrentServer.Logger.WriteWarning($"Could not execute on join for {origin}");
origin.CurrentServer.Logger.WriteDebug(e.GetExceptionInfo());
}
}
}
} }
/// <summary> /// <summary>
@ -472,6 +499,7 @@ namespace IW4MAdmin
#endif #endif
var currentClients = GetClientsAsList(); var currentClients = GetClientsAsList();
var polledClients = (await this.GetStatusAsync()).AsEnumerable(); var polledClients = (await this.GetStatusAsync()).AsEnumerable();
if (Manager.GetApplicationSettings().Configuration().IgnoreBots) if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
{ {
polledClients = polledClients.Where(c => !c.IsBot); polledClients = polledClients.Where(c => !c.IsBot);
@ -533,7 +561,7 @@ namespace IW4MAdmin
foreach (var disconnectingClient in polledClients[1]) foreach (var disconnectingClient in polledClients[1])
{ {
if (disconnectingClient.State == EFClient.ClientState.Disconnecting) if (disconnectingClient.State == ClientState.Disconnecting)
{ {
continue; continue;
} }
@ -557,6 +585,12 @@ namespace IW4MAdmin
// this are our new connecting clients // this are our new connecting clients
foreach (var client in polledClients[0]) foreach (var client in polledClients[0])
{ {
// note: this prevents players in ZMBI state from being registered with no name
if (string.IsNullOrEmpty(client.Name))
{
continue;
}
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.PreConnect, Type = GameEvent.EventType.PreConnect,
@ -590,7 +624,18 @@ namespace IW4MAdmin
if (ConnectionErrors > 0) if (ConnectionErrors > 0)
{ {
Logger.WriteVerbose($"{loc["MANAGER_CONNECTION_REST"]} {IP}:{Port}"); Logger.WriteVerbose(loc["MANAGER_CONNECTION_REST"].FormatExt($"{IP}:{Port}"));
var _event = new GameEvent()
{
Type = GameEvent.EventType.ConnectionRestored,
Owner = this,
Origin = Utilities.IW4MAdminClient(this),
Target = Utilities.IW4MAdminClient(this)
};
Manager.GetEventHandler().AddEvent(_event);
Throttled = false; Throttled = false;
} }
@ -605,6 +650,19 @@ namespace IW4MAdmin
{ {
Logger.WriteError($"{e.Message} {IP}:{Port}, {loc["SERVER_ERROR_POLLING"]}"); Logger.WriteError($"{e.Message} {IP}:{Port}, {loc["SERVER_ERROR_POLLING"]}");
Logger.WriteDebug($"Internal Exception: {e.Data["internal_exception"]}"); Logger.WriteDebug($"Internal Exception: {e.Data["internal_exception"]}");
var _event = new GameEvent()
{
Type = GameEvent.EventType.ConnectionLost,
Owner = this,
Origin = Utilities.IW4MAdminClient(this),
Target = Utilities.IW4MAdminClient(this),
Extra = e,
Data = ConnectionErrors.ToString()
};
Manager.GetEventHandler().AddEvent(_event);
Throttled = true; Throttled = true;
} }
return true; return true;
@ -630,7 +688,7 @@ namespace IW4MAdmin
&& BroadcastMessages.Count > 0 && BroadcastMessages.Count > 0
&& ClientNum > 0) && ClientNum > 0)
{ {
string[] messages = this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage]).Split(Environment.NewLine); string[] messages = (await this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])).Split(Environment.NewLine);
foreach (string message in messages) foreach (string message in messages)
{ {
@ -774,8 +832,8 @@ namespace IW4MAdmin
if (!File.Exists(LogPath) && ServerConfig.GameLogServerUrl == null) if (!File.Exists(LogPath) && ServerConfig.GameLogServerUrl == null)
{ {
Logger.WriteError($"{LogPath} {loc["SERVER_ERROR_DNE"]}"); Logger.WriteError(loc["SERVER_ERROR_DNE"].FormatExt(LogPath));
throw new ServerException($"{loc["SERVER_ERROR_LOG"]} {LogPath}"); throw new ServerException(loc["SERVER_ERROR_DNE"].FormatExt(LogPath));
} }
} }
@ -925,11 +983,9 @@ namespace IW4MAdmin
else else
{ {
// this is set only because they're still in the server.
targetClient.Level = EFClient.Permission.Banned;
#if !DEBUG #if !DEBUG
string formattedString = String.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{reason} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7"); string formattedString = String.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{reason} ^7{loc["SERVER_BAN_APPEAL"].FormatExt(Website)}^7");
await targetClient.CurrentServer.ExecuteCommandAsync(formattedString); await targetClient.CurrentServer.ExecuteCommandAsync(formattedString);
#else #else
await targetClient.CurrentServer.OnClientDisconnected(targetClient); await targetClient.CurrentServer.OnClientDisconnected(targetClient);
@ -949,6 +1005,7 @@ namespace IW4MAdmin
}; };
await Manager.GetPenaltyService().Create(newPenalty); await Manager.GetPenaltyService().Create(newPenalty);
targetClient.SetLevel(Permission.Banned, originClient);
} }
override public async Task Unban(string reason, EFClient Target, EFClient Origin) override public async Task Unban(string reason, EFClient Target, EFClient Origin)
@ -965,16 +1022,17 @@ namespace IW4MAdmin
Link = Target.AliasLink Link = Target.AliasLink
}; };
await Manager.GetPenaltyService().RemoveActivePenalties(Target.AliasLink.AliasLinkId); await Manager.GetPenaltyService().RemoveActivePenalties(Target.AliasLink.AliasLinkId, Origin);
await Manager.GetPenaltyService().Create(unbanPenalty); await Manager.GetPenaltyService().Create(unbanPenalty);
Target.SetLevel(Permission.User, Origin);
} }
override public void InitializeTokens() override public void InitializeTokens()
{ {
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("TOTALPLAYERS", (Server s) => Manager.GetClientService().GetTotalClientsAsync().Result.ToString())); Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("TOTALPLAYERS", (Server s) => Task.Run(async () => (await Manager.GetClientService().GetTotalClientsAsync()).ToString())));
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("VERSION", (Server s) => Application.Program.Version.ToString())); Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("VERSION", (Server s) => Task.FromResult(Application.Program.Version.ToString())));
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("NEXTMAP", (Server s) => SharedLibraryCore.Commands.CNextMap.GetNextMap(s).Result)); Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("NEXTMAP", (Server s) => SharedLibraryCore.Commands.CNextMap.GetNextMap(s)));
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("ADMINS", (Server s) => SharedLibraryCore.Commands.CListAdmins.OnlineAdmins(s))); Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("ADMINS", (Server s) => Task.FromResult(SharedLibraryCore.Commands.CListAdmins.OnlineAdmins(s))));
} }
} }
} }

View File

@ -17,6 +17,8 @@ namespace IW4MAdmin.Application.Localization
string currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale; string currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale;
string[] localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json"); string[] localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");
if (!Program.ServerManager.GetApplicationSettings()?.Configuration()?.UseLocalTranslations ?? false)
{
try try
{ {
var api = Endpoint.Get(); var api = Endpoint.Get();
@ -29,6 +31,7 @@ namespace IW4MAdmin.Application.Localization
{ {
// the online localization failed so will default to local files // the online localization failed so will default to local files
} }
}
// culture doesn't exist so we just want language // culture doesn't exist so we just want language
if (localizationFiles.Length == 0) if (localizationFiles.Length == 0)

View File

@ -90,7 +90,7 @@ namespace IW4MAdmin.Application
{ {
Console.ForegroundColor = ConsoleColor.DarkYellow; Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]"); Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]");
Console.WriteLine($"{loc["MANAGER_VERSION_CURRENT"]} [v{Version.ToString("0.0")}]"); Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}]"));
Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = ConsoleColor.Gray;
} }
#else #else
@ -98,7 +98,7 @@ namespace IW4MAdmin.Application
{ {
Console.ForegroundColor = ConsoleColor.DarkYellow; Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"IW4MAdmin-Prerelease {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionPrerelease.ToString("0.0")}-pr]"); Console.WriteLine($"IW4MAdmin-Prerelease {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionPrerelease.ToString("0.0")}-pr]");
Console.WriteLine($"{loc["MANAGER_VERSION_CURRENT"]} [v{Version.ToString("0.0")}-pr]"); Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}-pr]"));
Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = ConsoleColor.Gray;
} }
#endif #endif

View File

@ -1,4 +1,5 @@
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Security.Cryptography; using System.Security.Cryptography;
@ -10,16 +11,9 @@ namespace IW4MAdmin.Application.Misc
{ {
private readonly ConcurrentDictionary<long, TokenState> _tokens; private readonly ConcurrentDictionary<long, TokenState> _tokens;
private readonly RNGCryptoServiceProvider _random; private readonly RNGCryptoServiceProvider _random;
private readonly static TimeSpan _timeoutPeriod = new TimeSpan(0, 0, 30); private readonly static TimeSpan _timeoutPeriod = new TimeSpan(0, 0, 120);
private const short TOKEN_LENGTH = 4; private const short TOKEN_LENGTH = 4;
private class TokenState
{
public long NetworkId { get; set; }
public DateTime RequestTime { get; set; } = DateTime.Now;
public string Token { get; set; }
}
public TokenAuthentication() public TokenAuthentication()
{ {
_tokens = new ConcurrentDictionary<long, TokenState>(); _tokens = new ConcurrentDictionary<long, TokenState>();
@ -38,32 +32,44 @@ namespace IW4MAdmin.Application.Misc
return authorizeSuccessful; return authorizeSuccessful;
} }
public string GenerateNextToken(long networkId) public TokenState GenerateNextToken(long networkId)
{ {
TokenState state = null; TokenState state = null;
if (_tokens.ContainsKey(networkId)) if (_tokens.ContainsKey(networkId))
{ {
state = _tokens[networkId]; state = _tokens[networkId];
if ((DateTime.Now - state.RequestTime) < _timeoutPeriod) if ((DateTime.Now - state.RequestTime) > _timeoutPeriod)
{ {
return null; _tokens.TryRemove(networkId, out TokenState _);
} }
else else
{ {
_tokens.TryRemove(networkId, out TokenState _); return state;
} }
} }
state = new TokenState() state = new TokenState()
{ {
NetworkId = networkId, NetworkId = networkId,
Token = _generateToken() Token = _generateToken(),
TokenDuration = _timeoutPeriod
}; };
_tokens.TryAdd(networkId, state); _tokens.TryAdd(networkId, state);
return state.Token;
// perform some housekeeping so we don't have built up tokens if they're not ever used
foreach (var (key, value) in _tokens)
{
if ((DateTime.Now - value.RequestTime) > _timeoutPeriod)
{
_tokens.TryRemove(key, out TokenState _);
}
}
return state;
} }
public string _generateToken() public string _generateToken()

View File

@ -119,9 +119,9 @@ namespace IW4MAdmin.Application.RconParsers
} }
int validMatches = 0; int validMatches = 0;
foreach (string S in Status) foreach (string statusLine in Status)
{ {
string responseLine = S.Trim(); string responseLine = statusLine.Trim();
var regex = Regex.Match(responseLine, Configuration.Status.Pattern, RegexOptions.IgnoreCase); var regex = Regex.Match(responseLine, Configuration.Status.Pattern, RegexOptions.IgnoreCase);
@ -158,11 +158,11 @@ namespace IW4MAdmin.Application.RconParsers
State = EFClient.ClientState.Connecting State = EFClient.ClientState.Connecting
}; };
// they've not fully connected yet //// they've not fully connected yet
if (!client.IsBot && ping == 999) //if (!client.IsBot && ping == 999)
{ //{
continue; // continue;
} //}
StatusPlayers.Add(client); StatusPlayers.Add(client);
} }

View File

@ -17,7 +17,7 @@
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles> <SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
<Name>GameLogServer</Name> <Name>GameLogServer</Name>
<RootNamespace>GameLogServer</RootNamespace> <RootNamespace>GameLogServer</RootNamespace>
<InterpreterId>MSBuild|env|$(MSBuildProjectFullPath)</InterpreterId> <InterpreterId>MSBuild|log_env|$(MSBuildProjectFullPath)</InterpreterId>
<EnableNativeCodeDebugging>False</EnableNativeCodeDebugging> <EnableNativeCodeDebugging>False</EnableNativeCodeDebugging>
<Environment>DEBUG=True</Environment> <Environment>DEBUG=True</Environment>
</PropertyGroup> </PropertyGroup>
@ -50,7 +50,6 @@
<Folder Include="GameLogServer\" /> <Folder Include="GameLogServer\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="requirements.txt" />
<None Include="Stable.pubxml" /> <None Include="Stable.pubxml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -63,6 +62,18 @@
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable> <PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
<Architecture>X64</Architecture> <Architecture>X64</Architecture>
</Interpreter> </Interpreter>
<Interpreter Include="log_env\">
<Id>log_env</Id>
<Version>3.6</Version>
<Description>log_env (Python 3.6 (64-bit))</Description>
<InterpreterPath>Scripts\python.exe</InterpreterPath>
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
<Architecture>X64</Architecture>
</Interpreter>
</ItemGroup>
<ItemGroup>
<Content Include="requirements.txt" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
<!-- Specify pre- and post-build commands in the BeforeBuild and <!-- Specify pre- and post-build commands in the BeforeBuild and

View File

@ -5,55 +5,53 @@ import time
class LogReader(object): class LogReader(object):
def __init__(self): def __init__(self):
self.log_file_sizes = {} self.log_file_sizes = {}
# (if the file changes more than this, ignore ) - 0.125 MB # (if the time between checks is greater, ignore ) - in seconds
self.max_file_size_change = 125000 self.max_file_time_change = 30
# (if the time between checks is greater, ignore ) - 5 minutes
self.max_file_time_change = 60
def read_file(self, path): def read_file(self, path):
# this removes old entries that are no longer valid
try:
self._clear_old_logs()
except Exception as e:
print('could not clear old logs')
print(e)
if os.name != 'nt':
path = re.sub(r'^[A-Z]\:', '', path)
path = re.sub(r'\\+', '/', path)
# prevent traversing directories # prevent traversing directories
if re.search('r^.+\.\.\\.+$', path): if re.search('r^.+\.\.\\.+$', path):
return False return False
# must be a valid log path and log file # must be a valid log path and log file
if not re.search(r'^.+[\\|\/](.+)[\\|\/].+.log$', path): if not re.search(r'^.+[\\|\/](.+)[\\|\/].+.log$', path):
return False return False
# set the initialze size to the current file size
file_size = 0
if path not in self.log_file_sizes: # get the new file size
self.log_file_sizes[path] = {
'length' : self.file_length(path),
'read': time.time()
}
return True
# grab the previous values
last_length = self.log_file_sizes[path]['length']
last_read = self.log_file_sizes[path]['read']
# the file is being tracked already
new_file_size = self.file_length(path) new_file_size = self.file_length(path)
# the log size was unable to be read (probably the wrong path) # the log size was unable to be read (probably the wrong path)
if new_file_size < 0: if new_file_size < 0:
return False return False
now = time.time() # this is the first time the log has been requested
if path not in self.log_file_sizes:
self.log_file_sizes[path] = {
'length' : new_file_size,
'read': time.time()
}
return ''
# grab the previous values
last_length = self.log_file_sizes[path]['length']
file_size_difference = new_file_size - last_length file_size_difference = new_file_size - last_length
time_difference = now - last_read
# update the new size and actually read the data # update the new size and actually read the data
self.log_file_sizes[path] = { self.log_file_sizes[path] = {
'length': new_file_size, 'length': new_file_size,
'read': now 'read': time.time()
} }
# if it's been too long since we read and the amount changed is too great, discard it
# todo: do we really want old events? maybe make this an "or"
if file_size_difference > self.max_file_size_change or time_difference > self.max_file_time_change:
return True
new_log_info = self.get_file_lines(path, file_size_difference) new_log_info = self.get_file_lines(path, file_size_difference)
return new_log_info return new_log_info
@ -63,14 +61,25 @@ class LogReader(object):
file_handle.seek(-length, 2) file_handle.seek(-length, 2)
file_data = file_handle.read(length) file_data = file_handle.read(length)
file_handle.close() file_handle.close()
return file_data.decode('utf-8') # using ignore errors omits the pesky 0xb2 bytes we're reading in for some reason
except: return file_data.decode('utf-8', errors='ignore')
except Exception as e:
print('could not read the log file at {0}, wanted to read {1} bytes'.format(path, length))
print(e)
return False return False
def _clear_old_logs(self):
expired_logs = [path for path in self.log_file_sizes if int(time.time() - self.log_file_sizes[path]['read']) > self.max_file_time_change]
for log in expired_logs:
print('removing expired log {0}'.format(log))
del self.log_file_sizes[log]
def file_length(self, path): def file_length(self, path):
try: try:
return os.stat(path).st_size return os.stat(path).st_size
except: except Exception as e:
print('could not get the size of the log file at {0}'.format(path))
print(e)
return -1 return -1
reader = LogReader() reader = LogReader()

View File

@ -7,13 +7,8 @@ class LogResource(Resource):
path = urlsafe_b64decode(path).decode('utf-8') path = urlsafe_b64decode(path).decode('utf-8')
log_info = reader.read_file(path) log_info = reader.read_file(path)
if log_info is False:
print('could not read log file ' + path)
empty_read = (log_info == False) or (log_info == True)
return { return {
'success' : log_info is not False, 'success' : log_info is not False,
'length': -1 if empty_read else len(log_info), 'length': 0 if log_info is False else len(log_info),
'data': log_info 'data': log_info
} }

View File

@ -1,29 +1,29 @@
from flask_restful import Resource #from flask_restful import Resource
from flask import request #from flask import request
import requests #import requests
import os #import os
import subprocess #import subprocess
import re #import re
def get_pid_of_server_windows(port): #def get_pid_of_server_windows(port):
process = subprocess.Popen('netstat -aon', shell=True, stdout=subprocess.PIPE) # process = subprocess.Popen('netstat -aon', shell=True, stdout=subprocess.PIPE)
output = process.communicate()[0] # output = process.communicate()[0]
matches = re.search(' *(UDP) +([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}):'+ str(port) + ' +[^\w]*([0-9]+)', output.decode('utf-8')) # matches = re.search(' *(UDP) +([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}):'+ str(port) + ' +[^\w]*([0-9]+)', output.decode('utf-8'))
if matches is not None: # if matches is not None:
return matches.group(3) # return matches.group(3)
else: # else:
return 0 # return 0
class RestartResource(Resource): #class RestartResource(Resource):
def get(self): # def get(self):
try: # try:
response = requests.get('http://' + request.remote_addr + ':1624/api/restartapproved') # response = requests.get('http://' + request.remote_addr + ':1624/api/restartapproved')
if response.status_code == 200: # if response.status_code == 200:
pid = get_pid_of_server_windows(response.json()['port']) # pid = get_pid_of_server_windows(response.json()['port'])
subprocess.check_output("Taskkill /PID %s /F" % pid) # subprocess.check_output("Taskkill /PID %s /F" % pid)
else: # else:
return {}, 400 # return {}, 400
except Exception as e: # except Exception as e:
print(e) # print(e)
return {}, 500 # return {}, 500
return {}, 200 # return {}, 200

View File

@ -1,11 +1,14 @@
from flask import Flask from flask import Flask
from flask_restful import Api from flask_restful import Api
from .log_resource import LogResource from .log_resource import LogResource
from .restart_resource import RestartResource #from .restart_resource import RestartResource
import logging
app = Flask(__name__) app = Flask(__name__)
def init(): def init():
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
api = Api(app) api = Api(app)
api.add_resource(LogResource, '/log/<string:path>') api.add_resource(LogResource, '/log/<string:path>')
api.add_resource(RestartResource, '/restart') #api.add_resource(RestartResource, '/restart')

View File

@ -1,26 +1,12 @@
aniso8601==3.0.2 aniso8601==6.0.0
APScheduler==3.5.3 Click==7.0
certifi==2018.10.15
chardet==3.0.4
click==6.7
Flask==1.0.2 Flask==1.0.2
Flask-JWT==0.3.2 Flask-RESTful==0.3.7
Flask-JWT-Extended==3.8.1 itsdangerous==1.1.0
Flask-RESTful==0.3.6
idna==2.7
itsdangerous==0.24
Jinja2==2.10 Jinja2==2.10
MarkupSafe==1.0 MarkupSafe==1.1.1
marshmallow==3.0.0b8 pip==10.0.1
pip==9.0.3 pytz==2018.9
psutil==5.4.8 setuptools==39.0.1
pygal==2.4.0 six==1.12.0
PyJWT==1.4.2 Werkzeug==0.15.2
pytz==2018.7
requests==2.20.0
setuptools==40.5.0
six==1.11.0
timeago==1.0.8
tzlocal==1.5.1
urllib3==1.24
Werkzeug==0.14.1

View File

@ -48,6 +48,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
EndProject EndProject
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "GameLogServer", "GameLogServer\GameLogServer.pyproj", "{42EFDA12-10D3-4C40-A210-9483520116BC}" Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "GameLogServer", "GameLogServer\GameLogServer.pyproj", "{42EFDA12-10D3-4C40-A210-9483520116BC}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{A848FCF1-8527-4AA8-A1AA-50D29695C678}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StatsWeb", "Plugins\Web\StatsWeb\StatsWeb.csproj", "{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -286,8 +292,8 @@ Global
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x64.Build.0 = Debug|Any CPU {6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x64.Build.0 = Debug|Any CPU
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x86.ActiveCfg = Debug|Any CPU {6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x86.ActiveCfg = Debug|Any CPU
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x86.Build.0 = Debug|Any CPU {6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x86.Build.0 = Debug|Any CPU
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU {6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Any CPU.Build.0 = Debug|Any CPU {6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU {6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU {6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|x64.ActiveCfg = Debug|Any CPU {6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|x64.ActiveCfg = Debug|Any CPU
@ -306,7 +312,7 @@ Global
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Debug|x64.ActiveCfg = Debug|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Debug|x64.ActiveCfg = Debug|Any CPU
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Debug|x86.ActiveCfg = Debug|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Debug|x86.ActiveCfg = Debug|Any CPU
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|Any CPU.ActiveCfg = Release|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|Mixed Platforms.ActiveCfg = Release|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|Mixed Platforms.ActiveCfg = Release|Any CPU
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|x64.ActiveCfg = Release|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|x64.ActiveCfg = Release|Any CPU
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|x86.ActiveCfg = Release|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|x86.ActiveCfg = Release|Any CPU
@ -315,15 +321,13 @@ Global
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|x64.ActiveCfg = Release|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|x64.ActiveCfg = Release|Any CPU
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|x86.ActiveCfg = Release|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|x86.ActiveCfg = Release|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x64.ActiveCfg = Debug|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x64.ActiveCfg = Debug|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x64.Build.0 = Debug|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x64.Build.0 = Debug|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x86.ActiveCfg = Debug|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x86.ActiveCfg = Debug|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x86.Build.0 = Debug|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x86.Build.0 = Debug|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Any CPU.ActiveCfg = Release|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Any CPU.Build.0 = Release|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Mixed Platforms.ActiveCfg = Release|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Mixed Platforms.ActiveCfg = Release|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Mixed Platforms.Build.0 = Release|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Mixed Platforms.Build.0 = Release|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|x64.ActiveCfg = Release|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|x64.ActiveCfg = Release|Any CPU
@ -338,6 +342,54 @@ Global
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x64.Build.0 = Release|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x64.Build.0 = Release|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x86.ActiveCfg = Release|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x86.ActiveCfg = Release|Any CPU
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x86.Build.0 = Release|Any CPU {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x86.Build.0 = Release|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|x64.ActiveCfg = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|x64.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|x86.ActiveCfg = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|x86.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|x64.ActiveCfg = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|x64.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|x86.ActiveCfg = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|x86.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|Any CPU.Build.0 = Release|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|x64.ActiveCfg = Release|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|x64.Build.0 = Release|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|x86.ActiveCfg = Release|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|x86.Build.0 = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|x64.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|x64.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|x86.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|x86.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|Any CPU.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|x64.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|x64.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|x86.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|x86.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|Any CPU.Build.0 = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|x64.ActiveCfg = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|x64.Build.0 = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|x86.ActiveCfg = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -350,6 +402,9 @@ Global
{B72DEBFB-9D48-4076-8FF5-1FD72A830845} = {26E8B310-269E-46D4-A612-24601F16065F} {B72DEBFB-9D48-4076-8FF5-1FD72A830845} = {26E8B310-269E-46D4-A612-24601F16065F}
{6C706CE5-A206-4E46-8712-F8C48D526091} = {26E8B310-269E-46D4-A612-24601F16065F} {6C706CE5-A206-4E46-8712-F8C48D526091} = {26E8B310-269E-46D4-A612-24601F16065F}
{3F9ACC27-26DB-49FA-BCD2-50C54A49C9FA} = {26E8B310-269E-46D4-A612-24601F16065F} {3F9ACC27-26DB-49FA-BCD2-50C54A49C9FA} = {26E8B310-269E-46D4-A612-24601F16065F}
{A848FCF1-8527-4AA8-A1AA-50D29695C678} = {26E8B310-269E-46D4-A612-24601F16065F}
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B} = {A848FCF1-8527-4AA8-A1AA-50D29695C678}
{F5815359-CFC7-44B4-9A3B-C04BACAD5836} = {26E8B310-269E-46D4-A612-24601F16065F}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87} SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy &quot;$(TargetPath)&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;"/>
<Exec Command="copy &quot;$(TargetDir)Microsoft.SyndicationFeed.ReaderWriter.dll&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;"/>
</Target>
</Project>

View File

@ -0,0 +1,29 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
namespace AutomessageFeed
{
class Configuration : IBaseConfiguration
{
public bool EnableFeed { get; set; }
public string FeedUrl { get; set; }
public int MaxFeedItems { get; set; }
public IBaseConfiguration Generate()
{
EnableFeed = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_PROMPT_ENABLE"]);
if (EnableFeed)
{
FeedUrl = Utilities.PromptString(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_URL"]);
MaxFeedItems = Utilities.PromptInt(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_PROMPT_MAXITEMS"],
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_PROMPT_MAXITEMS_DESC"],
0, int.MaxValue, 0);
}
return this;
}
public string Name() => "AutomessageFeedConfiguration";
}
}

View File

@ -0,0 +1,85 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Threading.Tasks;
using Microsoft.SyndicationFeed.Rss;
using SharedLibraryCore.Configuration;
using System.Xml;
using Microsoft.SyndicationFeed;
using System.Collections.Generic;
using SharedLibraryCore.Helpers;
using System.Text.RegularExpressions;
namespace AutomessageFeed
{
public class Plugin : IPlugin
{
public string Name => "Automessage Feed";
public float Version => (float)Utilities.GetVersionAsDouble();
public string Author => "RaidMax";
private Configuration _configuration;
private int _currentFeedItem;
private async Task<string> GetNextFeedItem(Server server)
{
var items = new List<string>();
using (var reader = XmlReader.Create(_configuration.FeedUrl, new XmlReaderSettings() { Async = true }))
{
var feedReader = new RssFeedReader(reader);
while (await feedReader.Read())
{
switch (feedReader.ElementType)
{
case SyndicationElementType.Item:
var item = await feedReader.ReadItem();
items.Add(Regex.Replace(item.Title, @"\<.+\>.*\</.+\>", ""));
break;
}
}
}
if (_currentFeedItem < items.Count && (_configuration.MaxFeedItems == 0 || _currentFeedItem < _configuration.MaxFeedItems))
{
_currentFeedItem++;
return items[_currentFeedItem - 1];
}
_currentFeedItem = 0;
return Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_NO_ITEMS"];
}
public Task OnEventAsync(GameEvent E, Server S)
{
return Task.CompletedTask;
}
public async Task OnLoadAsync(IManager manager)
{
var cfg = new BaseConfigurationHandler<Configuration>("AutomessageFeedPluginSettings");
if (cfg.Configuration() == null)
{
cfg.Set((Configuration)new Configuration().Generate());
await cfg.Save();
}
_configuration = cfg.Configuration();
manager.GetMessageTokens().Add(new MessageToken("FEED", GetNextFeedItem));
}
public Task OnTickAsync(Server S)
{
throw new NotImplementedException();
}
public Task OnUnloadAsync()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,8 +1,5 @@
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Login.Commands namespace IW4MAdmin.Plugins.Login.Commands
@ -22,18 +19,22 @@ namespace IW4MAdmin.Plugins.Login.Commands
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {
var client = E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId]; var client = E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId];
bool success = E.Owner.Manager.TokenAuthenticator.AuthorizeToken(E.Origin.NetworkId, E.Data);
if (!success)
{
string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, client.PasswordSalt)); string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, client.PasswordSalt));
if (hashedPassword[0] == client.Password) if (hashedPassword[0] == client.Password)
{ {
success = true;
Plugin.AuthorizedClients[E.Origin.ClientId] = true; Plugin.AuthorizedClients[E.Origin.ClientId] = true;
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]); }
} }
else _ = success ?
{ E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]) :
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]); E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]);
} }
} }
} }
}

View File

@ -7,7 +7,6 @@ using SharedLibraryCore;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Plugins.ProfanityDeterment namespace IW4MAdmin.Plugins.ProfanityDeterment
{ {

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = { var plugin = {
author: 'FrenchFry, RaidMax', author: 'FrenchFry, RaidMax',
version: 0.2, version: 0.3,
name: 'CoD4x Parser', name: 'CoD4x Parser',
isParser: true, isParser: true,
@ -27,6 +27,7 @@ var plugin = {
eventParser.Configuration.GameDirectory = 'main'; eventParser.Configuration.GameDirectory = 'main';
eventParser.Version = 'CoD4 X 1.8 win_mingw-x86 build 2055 May 2 2017'; eventParser.Version = 'CoD4 X 1.8 win_mingw-x86 build 2055 May 2 2017';
eventParser.GameName = 1; // IW3 eventParser.GameName = 1; // IW3
eventParser.URLProtocolFormat = 'cod4://{{ip}}:{{port}}';
}, },
onUnloadAsync: function () { onUnloadAsync: function () {

View File

@ -3,8 +3,8 @@ var eventParser;
var plugin = { var plugin = {
author: 'RaidMax', author: 'RaidMax',
version: 0.2, version: 0.3,
name: 'IW3 Parser', name: 'IW4 Parser',
isParser: true, isParser: true,
onEventAsync: function (gameEvent, server) { onEventAsync: function (gameEvent, server) {
@ -25,6 +25,7 @@ var plugin = {
rconParser.GameName = 2; // IW4x rconParser.GameName = 2; // IW4x
eventParser.Version = 'IW4x (v0.6.0)'; eventParser.Version = 'IW4x (v0.6.0)';
eventParser.GameName = 2; // IW4x eventParser.GameName = 2; // IW4x
eventParser.URLProtocolFormat = 'iw4x://{{ip}}:{{port}}';
}, },
onUnloadAsync: function () { onUnloadAsync: function () {

View File

@ -4,7 +4,7 @@ var eventParser;
var plugin = { var plugin = {
author: 'RaidMax', author: 'RaidMax',
version: 0.2, version: 0.2,
name: 'Plutoniun T6 Parser', name: 'Plutonium T6 Parser',
isParser: true, isParser: true,
onEventAsync: function (gameEvent, server) { onEventAsync: function (gameEvent, server) {

View File

@ -39,7 +39,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
where stats.ServerId == serverId where stats.ServerId == serverId
where client.Level != EFClient.Permission.Banned where client.Level != EFClient.Permission.Banned
where client.LastConnection >= thirtyDaysAgo where client.LastConnection >= thirtyDaysAgo
orderby stats.Kills descending orderby stats.TimePlayed descending
select new select new
{ {
alias.Name, alias.Name,

View File

@ -69,7 +69,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
if (E.Message.IsBroadcastCommand()) if (E.Message.IsBroadcastCommand())
{ {
string name = E.Target == null ? E.Origin.Name : E.Target.Name; string name = E.Target == null ? E.Origin.Name : E.Target.Name;
E.Owner.Broadcast($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{name}^7"); E.Owner.Broadcast(loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"].FormatExt(name));
E.Owner.Broadcast(statLine); E.Owner.Broadcast(statLine);
} }
@ -77,7 +77,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
{ {
if (E.Target != null) if (E.Target != null)
{ {
E.Origin.Tell($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{E.Target.Name}^7"); E.Origin.Tell(loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"].FormatExt(E.Target.Name));
} }
E.Origin.Tell(statLine); E.Origin.Tell(statLine);

View File

@ -1,7 +1,9 @@
using Microsoft.AspNetCore.Authorization; using IW4MAdmin.Plugins.Stats.Helpers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Dtos;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -12,25 +14,51 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
public class StatsController : BaseController public class StatsController : BaseController
{ {
[HttpGet] [HttpGet]
public async Task<IActionResult> TopPlayersAsync() public IActionResult TopPlayersAsync()
{ {
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_TITLE"]; ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"];
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex.Set["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 });
return View("Index", await Plugin.Manager.GetTopStats(0, 50)); return View("Index");
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> GetTopPlayersAsync(int count, int offset) public async Task<IActionResult> GetTopPlayersAsync(int count, int offset, long? serverId = null)
{ {
return View("_List", await Plugin.Manager.GetTopStats(offset, count)); // this prevents empty results when we really want aggregate
if (serverId == 0)
{
serverId = null;
}
var server = Manager.GetServers().FirstOrDefault(_server => _server.EndPoint == serverId);
if (server != null)
{
serverId = await StatManager.GetIdForServer(server);
}
var results = await Plugin.Manager.GetTopStats(offset, count, serverId);
// this returns an empty result so we know to stale the loader
if (results.Count == 0 && offset > 0)
{
return Ok();
}
else
{
return View("Components/TopPlayers/_List", results);
}
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> GetMessageAsync(int serverId, DateTime when) public async Task<IActionResult> GetMessageAsync(int serverId, long when)
{ {
var whenUpper = when.AddMinutes(5); var whenTime = DateTime.FromFileTimeUtc(when);
var whenLower = when.AddMinutes(-5); var whenUpper = whenTime.AddMinutes(5);
var whenLower = whenTime.AddMinutes(-5);
using (var ctx = new SharedLibraryCore.Database.DatabaseContext(true)) using (var ctx = new SharedLibraryCore.Database.DatabaseContext(true))
{ {
@ -38,7 +66,7 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
where message.ServerId == serverId where message.ServerId == serverId
where message.TimeSent >= whenLower where message.TimeSent >= whenLower
where message.TimeSent <= whenUpper where message.TimeSent <= whenUpper
select new SharedLibraryCore.Dtos.ChatInfo() select new ChatInfo()
{ {
ClientId = message.ClientId, ClientId = message.ClientId,
Message = message.Message, Message = message.Message,
@ -62,16 +90,30 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
{ {
using (var ctx = new SharedLibraryCore.Database.DatabaseContext(true)) using (var ctx = new SharedLibraryCore.Database.DatabaseContext(true))
{ {
var penaltyInfo = await ctx.Set<Models.EFACSnapshot>() int linkId = await ctx.Clients
.Where(s => s.ClientId == clientId) .Where(_client => _client.ClientId == clientId)
.Select(_client => _client.AliasLinkId)
.FirstOrDefaultAsync();
var clientIds = await ctx.Clients.Where(_client => _client.AliasLinkId == linkId)
.Select(_client => _client.ClientId)
.ToListAsync();
var iqPenaltyInfo = ctx.Set<Models.EFACSnapshot>()
.Where(s => clientIds.Contains(s.ClientId))
.Include(s => s.LastStrainAngle) .Include(s => s.LastStrainAngle)
.Include(s => s.HitOrigin) .Include(s => s.HitOrigin)
.Include(s => s.HitDestination) .Include(s => s.HitDestination)
.Include(s => s.CurrentViewAngle) .Include(s => s.CurrentViewAngle)
.Include(s => s.PredictedViewAngles) .Include(s => s.PredictedViewAngles)
.OrderBy(s => s.When) .OrderBy(s => s.When)
.ThenBy(s => s.Hits) .ThenBy(s => s.Hits);
.ToListAsync();
#if DEBUG == true
var sql = iqPenaltyInfo.ToSql();
#endif
var penaltyInfo = await iqPenaltyInfo.ToListAsync();
return View("_PenaltyInfo", penaltyInfo); return View("_PenaltyInfo", penaltyInfo);
} }

View File

@ -82,13 +82,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
} }
public async Task<List<TopStatsInfo>> GetTopStats(int start, int count) public async Task<List<TopStatsInfo>> GetTopStats(int start, int count, long? serverId = null)
{ {
using (var context = new DatabaseContext(true)) using (var context = new DatabaseContext(true))
{ {
// setup the query for the clients within the given rating range // setup the query for the clients within the given rating range
var iqClientRatings = (from rating in context.Set<EFRating>() var iqClientRatings = (from rating in context.Set<EFRating>()
.Where(GetRankingFunc()) .Where(GetRankingFunc(serverId))
select new select new
{ {
rating.RatingHistory.ClientId, rating.RatingHistory.ClientId,
@ -113,7 +113,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var iqRatingInfo = from rating in context.Set<EFRating>() var iqRatingInfo = from rating in context.Set<EFRating>()
where clientIds.Contains(rating.RatingHistory.ClientId) where clientIds.Contains(rating.RatingHistory.ClientId)
where rating.ServerId == null where rating.ServerId == serverId
select new select new
{ {
rating.Ranking, rating.Ranking,
@ -155,6 +155,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var finished = topPlayers.Select(s => new TopStatsInfo() var finished = topPlayers.Select(s => new TopStatsInfo()
{ {
ClientId = s.ClientId, ClientId = s.ClientId,
Id = (int?)serverId ?? 0,
Deaths = s.Deaths, Deaths = s.Deaths,
Kills = s.Kills, Kills = s.Kills,
KDR = Math.Round(s.KDR, 2), KDR = Math.Round(s.KDR, 2),
@ -427,19 +428,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId) public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId)
{ {
string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$"; // todo: maybe do something with this
var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase); //string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$";
//var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase);
if (match.Success) //if (match.Success)
{ //{
// this gives us what team the player is on // // this gives us what team the player is on
var attackerStats = Servers[serverId].PlayerStats[attackerClientId]; // var attackerStats = Servers[serverId].PlayerStats[attackerClientId];
var victimStats = Servers[serverId].PlayerStats[victimClientId]; // var victimStats = Servers[serverId].PlayerStats[victimClientId];
IW4Info.Team victimTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[4].ToString()); // IW4Info.Team victimTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[4].ToString(), true);
IW4Info.Team attackerTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[7].ToString()); // IW4Info.Team attackerTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[7].ToString(), true);
attackerStats.Team = attackerTeam; // attackerStats.Team = attackerTeam;
victimStats.Team = victimTeam; // victimStats.Team = victimTeam;
} //}
} }
/// <summary> /// <summary>

View File

@ -26,7 +26,7 @@ namespace IW4MAdmin.Plugins.Stats
public string Author => "RaidMax"; public string Author => "RaidMax";
public static StatManager Manager { get; private set; } public static StatManager Manager { get; private set; }
private IManager ServerManager; public static IManager ServerManager;
public static BaseConfigurationHandler<StatsConfiguration> Config { get; private set; } public static BaseConfigurationHandler<StatsConfiguration> Config { get; private set; }
public async Task OnEventAsync(GameEvent E, Server S) public async Task OnEventAsync(GameEvent E, Server S)
@ -51,7 +51,6 @@ namespace IW4MAdmin.Plugins.Stats
{ {
await Manager.AddMessageAsync(E.Origin.ClientId, await StatManager.GetIdForServer(E.Owner), E.Data); await Manager.AddMessageAsync(E.Origin.ClientId, await StatManager.GetIdForServer(E.Owner), E.Data);
} }
break; break;
case GameEvent.EventType.MapChange: case GameEvent.EventType.MapChange:
Manager.SetTeamBased(await StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm"); Manager.SetTeamBased(await StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm");
@ -79,6 +78,22 @@ namespace IW4MAdmin.Plugins.Stats
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 14) if (killInfo.Length >= 14)
{ {
if (E.Origin.ClientId <= 1 && E.Target.ClientId <= 1)
{
return;
}
// this treats "world" damage as self damage
if (E.Origin.ClientId <= 1)
{
E.Origin = E.Target;
}
if (E.Target.ClientId <= 1)
{
E.Target = E.Origin;
}
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8], await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]); killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
} }
@ -86,12 +101,44 @@ namespace IW4MAdmin.Plugins.Stats
case GameEvent.EventType.Kill: case GameEvent.EventType.Kill:
if (!E.Owner.CustomCallback) if (!E.Owner.CustomCallback)
{ {
if (E.Origin.ClientId <= 1 && E.Target.ClientId <= 1)
{
return;
}
// this treats "world" damage as self damage
if (E.Origin.ClientId <= 1)
{
E.Origin = E.Target;
}
if (E.Target.ClientId <= 1)
{
E.Target = E.Origin;
}
await Manager.AddStandardKill(E.Origin, E.Target); await Manager.AddStandardKill(E.Origin, E.Target);
} }
break; break;
case GameEvent.EventType.Damage: case GameEvent.EventType.Damage:
if (!E.Owner.CustomCallback) if (!E.Owner.CustomCallback)
{ {
if (E.Origin.ClientId <= 1 && E.Target.ClientId <= 1)
{
return;
}
// this treats "world" damage as self damage
if (E.Origin.ClientId <= 1)
{
E.Origin = E.Target;
}
if (E.Target.ClientId <= 1)
{
E.Target = E.Origin;
}
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, await StatManager.GetIdForServer(E.Owner)); Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, await StatManager.GetIdForServer(E.Owner));
} }
break; break;
@ -99,6 +146,22 @@ namespace IW4MAdmin.Plugins.Stats
killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 14) if (killInfo.Length >= 14)
{ {
if (E.Origin.ClientId <= 1 && E.Target.ClientId <= 1)
{
return;
}
// this treats "world" damage as self damage
if (E.Origin.ClientId <= 1)
{
E.Origin = E.Target;
}
if (E.Target.ClientId <= 1)
{
E.Target = E.Origin;
}
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8], await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]); killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
} }
@ -124,8 +187,13 @@ namespace IW4MAdmin.Plugins.Stats
"/Stats/TopPlayersAsync"); "/Stats/TopPlayersAsync");
// meta data info // meta data info
async Task<List<ProfileMeta>> getStats(int clientId) async Task<List<ProfileMeta>> getStats(int clientId, int offset, int count, DateTime? startAt)
{ {
if (count > 1)
{
return new List<ProfileMeta>();
}
IList<EFClientStatistics> clientStats; IList<EFClientStatistics> clientStats;
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
@ -145,39 +213,63 @@ namespace IW4MAdmin.Plugins.Stats
new ProfileMeta() new ProfileMeta()
{ {
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"], Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
Value = "#" + await StatManager.GetClientOverallRanking(clientId), Value = "#" + (await StatManager.GetClientOverallRanking(clientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 0,
Type = ProfileMeta.MetaType.Information
}, },
new ProfileMeta() new ProfileMeta()
{ {
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"], Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"],
Value = kills Value = kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 1,
Type = ProfileMeta.MetaType.Information
}, },
new ProfileMeta() new ProfileMeta()
{ {
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_DEATHS"], Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_DEATHS"],
Value = deaths Value = deaths.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 2,
Type = ProfileMeta.MetaType.Information
}, },
new ProfileMeta() new ProfileMeta()
{ {
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"], Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"],
Value = kdr Value = kdr.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 3,
Type = ProfileMeta.MetaType.Information
}, },
new ProfileMeta() new ProfileMeta()
{ {
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_PERFORMANCE"], Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_PERFORMANCE"],
Value = performance Value = performance.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 4,
Type = ProfileMeta.MetaType.Information
}, },
new ProfileMeta() new ProfileMeta()
{ {
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_META_SPM"], Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_META_SPM"],
Value = spm Value = spm.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 5,
Type = ProfileMeta.MetaType.Information
} }
}; };
} }
async Task<List<ProfileMeta>> getAnticheatInfo(int clientId) async Task<List<ProfileMeta>> getAnticheatInfo(int clientId, int offset, int count, DateTime? startAt)
{ {
if (count > 1)
{
return new List<ProfileMeta>();
}
IList<EFClientStatistics> clientStats; IList<EFClientStatistics> clientStats;
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
clientStats = await ctx.Set<EFClientStatistics>() clientStats = await ctx.Set<EFClientStatistics>()
@ -219,104 +311,141 @@ namespace IW4MAdmin.Plugins.Stats
{ {
new ProfileMeta() new ProfileMeta()
{ {
Key = "Chest Ratio", Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 1",
Value = chestRatio, Value = chestRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 0,
Sensitive = true Sensitive = true
}, },
new ProfileMeta() new ProfileMeta()
{ {
Key = "Abdomen Ratio", Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 2",
Value = abdomenRatio, Value = abdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 1,
Sensitive = true Sensitive = true
}, },
new ProfileMeta() new ProfileMeta()
{ {
Key = "Chest To Abdomen Ratio", Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 3",
Value = chestAbdomenRatio, Value = chestAbdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 2,
Sensitive = true Sensitive = true
}, },
new ProfileMeta() new ProfileMeta()
{ {
Key = "Headshot Ratio", Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 4",
Value = headRatio, Value = headRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 3,
Sensitive = true Sensitive = true
}, },
new ProfileMeta() new ProfileMeta()
{ {
Key = "Hit Offset Average", 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,
Column = 2,
Order = 4,
Sensitive = true Sensitive = true
}, },
new ProfileMeta() new ProfileMeta()
{ {
Key = "Max Strain", Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 6",
Value = Math.Round(maxStrain, 3), Value = Math.Round(maxStrain, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 5,
Sensitive = true Sensitive = true
}, },
}; };
} }
async Task<List<ProfileMeta>> getMessages(int clientId) async Task<List<ProfileMeta>> getMessages(int clientId, int offset, int count, DateTime? startAt)
{ {
if (count <= 1)
{
using (var ctx = new DatabaseContext(true))
{
return new List<ProfileMeta>
{
new ProfileMeta()
{
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; List<ProfileMeta> messageMeta;
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
var messages = ctx.Set<EFClientMessage>().Where(m => m.ClientId == clientId); 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() messageMeta = await messages.Select(m => new ProfileMeta()
{ {
Key = "EventMessage", Key = null,
Value = m.Message, Value = m.Message,
When = m.TimeSent, When = m.TimeSent,
Extra = m.ServerId.ToString() Extra = m.ServerId.ToString(),
Type = ProfileMeta.MetaType.ChatMessage
}).ToListAsync(); }).ToListAsync();
} }
messageMeta.Add(new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
Value = messageMeta.Count
});
return messageMeta; return messageMeta;
} }
MetaService.AddMeta(getStats);
if (Config.Configuration().EnableAntiCheat) if (Config.Configuration().EnableAntiCheat)
{ {
MetaService.AddMeta(getAnticheatInfo); MetaService.AddRuntimeMeta(getAnticheatInfo);
} }
MetaService.AddMeta(getMessages); MetaService.AddRuntimeMeta(getStats);
MetaService.AddRuntimeMeta(getMessages);
string totalKills(Server server) async Task<string> totalKills(Server server)
{ {
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
long kills = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalKills); long kills = await ctx.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalKills);
return kills.ToString("#,##0"); return kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
} }
} }
string totalPlayTime(Server server) async Task<string> totalPlayTime(Server server)
{ {
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
long playTime = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalPlayTime); long playTime = await ctx.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalPlayTime);
return (playTime / 3600.0).ToString("#,##0"); return (playTime / 3600.0).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
} }
} }
string topStats(Server s) async Task<string> topStats(Server s)
{ {
return String.Join(Environment.NewLine, Commands.TopStats.GetTopStats(s).Result); return string.Join(Environment.NewLine, await Commands.TopStats.GetTopStats(s));
} }
string mostPlayed(Server s) async Task<string> mostPlayed(Server s)
{ {
return String.Join(Environment.NewLine, Commands.MostPlayed.GetMostPlayed(s).Result); return string.Join(Environment.NewLine, await Commands.MostPlayed.GetMostPlayed(s));
} }
manager.GetMessageTokens().Add(new MessageToken("TOTALKILLS", totalKills)); manager.GetMessageTokens().Add(new MessageToken("TOTALKILLS", totalKills));
@ -325,7 +454,6 @@ namespace IW4MAdmin.Plugins.Stats
manager.GetMessageTokens().Add(new MessageToken("MOSTPLAYED", mostPlayed)); manager.GetMessageTokens().Add(new MessageToken("MOSTPLAYED", mostPlayed));
ServerManager = manager; ServerManager = manager;
Manager = new StatManager(manager); Manager = new StatManager(manager);
} }

View File

@ -15,12 +15,6 @@
<Configurations>Debug;Release;Prerelease</Configurations> <Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Content Include="Web\Views\Stats\_MessageContext.cshtml">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" /> <ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
<ProjectReference Include="..\..\WebfrontCore\WebfrontCore.csproj" /> <ProjectReference Include="..\..\WebfrontCore\WebfrontCore.csproj" />
@ -33,9 +27,4 @@
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy &quot;$(TargetPath)&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;" /> <Exec Command="copy &quot;$(TargetPath)&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;" />
</Target> </Target>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="xcopy /E /K /Y /C /I &quot;$(ProjectDir)Web\Views&quot; &quot;$(SolutionDir)WebfrontCore\Views\Plugins&quot;&#xD;&#xA;xcopy /E /K /Y /C /I &quot;$(ProjectDir)Web\wwwroot\images&quot; &quot;$(SolutionDir)WebfrontCore\wwwroot\images&quot;" />
</Target>
</Project> </Project>

View File

@ -0,0 +1,28 @@
using IW4MAdmin.Plugins.Stats;
using IW4MAdmin.Plugins.Stats.Helpers;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Threading.Tasks;
namespace Stats.ViewComponents
{
public class TopPlayersViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(int count, int offset, long? serverId = null)
{
if (serverId == 0)
{
serverId = null;
}
var server = Plugin.ServerManager.GetServers().FirstOrDefault(_server => _server.EndPoint == serverId);
if (server != null)
{
serverId = await StatManager.GetIdForServer(server);
}
return View("_List", await Plugin.Manager.GetTopStats(offset, count, serverId));
}
}
}

View File

@ -1,14 +0,0 @@
@model List<dynamic>
<h4 class="pb-2 text-center ">@ViewBag.Title</h4>
<div id="stats_top_players" class="striped border-top border-bottom">
@await Html.PartialAsync("_List", Model)
</div>
@section scripts {
<environment include="Development">
<script type="text/javascript" src="~/js/loader.js"></script>
<script type="text/javascript" src="~/js/stats.js"></script>
</environment>
<script>initLoader('/Stats/GetTopPlayersAsync', '#stats_top_players', 50);</script>
}

View File

@ -1,12 +0,0 @@
@model IEnumerable<SharedLibraryCore.Dtos.ChatInfo>
@{
Layout = null;
}
<div class="client-message-context bg-dark p-2 mt-2 mb-2 border-top border-bottom">
<h5>@Model.First().Time.ToString()</h5>
@foreach (var message in Model)
{
<span class="text-white">@Html.ActionLink(@message.Name, "ProfileAsync", "Client", new { id = message.ClientId})</span><span> &mdash; @message.Message</span><br />
}
</div>

View File

@ -56,10 +56,10 @@ namespace Tests
var warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer }); var warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(); warnEvent.OnProcessed.Wait();
Assert.True((client.Warnings == 1 || //Assert.True((client.Warnings == 1 ||
warnEvent.Failed) && // warnEvent.Failed) &&
Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1, // Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1,
"warning did not get applied"); // "warning did not get applied");
warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer }); warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(); warnEvent.OnProcessed.Wait();

View File

@ -18,7 +18,7 @@ namespace Tests
{ {
File.WriteAllText("test_mp.log", "test_log_file"); File.WriteAllText("test_mp.log", "test_log_file");
IW4MAdmin.Application.Localization.Configure.Initialize("en-US"); //IW4MAdmin.Application.Localization.Configure.Initialize("en-US");
Manager = ApplicationManager.GetInstance(); Manager = ApplicationManager.GetInstance();
@ -41,7 +41,7 @@ namespace Tests
Maps = new List<MapConfiguration>(), Maps = new List<MapConfiguration>(),
RConPollRate = 10000 RConPollRate = 10000
}; };
Manager.ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("test.json"); Manager.ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("test");
Manager.ConfigHandler.Set(config); Manager.ConfigHandler.Set(config);
Manager.Init().Wait(); Manager.Init().Wait();

View File

@ -34,7 +34,7 @@ namespace Tests
[Fact] [Fact]
public void AreCommandAliasesUnique() public void AreCommandAliasesUnique()
{ {
var mgr = IW4MAdmin.Application.Program.ServerManager; var mgr = Program.ServerManager;
bool test = mgr.GetCommands().Count == mgr.GetCommands().Select(c => c.Alias).Distinct().Count(); bool test = mgr.GetCommands().Count == mgr.GetCommands().Select(c => c.Alias).Distinct().Count();
Assert.True(test, "command aliases are not unique"); Assert.True(test, "command aliases are not unique");

Some files were not shown because too many files have changed in this diff Show More