Compare commits

..

55 Commits

Author SHA1 Message Date
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
2bbf2988da update application version 2019-02-15 22:20:45 -06:00
5e36bf4316 begin implementation of token authentication
replacing password authentication ingame
precompile views for webfront
issue #66
issue #52
2019-02-15 22:19:59 -06:00
a362caebac fix for T6 guid length including sign 2019-02-14 08:42:14 -06:00
2260d8974d update master to allow IW5 to pass validation
include version set on manual parser selection
update projects to .NET Core 2.2
add middleware to support ip whitelisting
(EnableWebfrontConnectionWhitelist and WebfrontConnectionWhitelist)
issue #59
2019-02-12 20:34:29 -06:00
5e04274da6 actually fix the encoding issue 2019-02-10 20:05:50 -06:00
6d2e6aee4f update application version 2019-02-09 21:26:39 -06:00
9e74c42246 fix small bug with log paths 2019-02-09 21:24:54 -06:00
dea5b3f954 fix reading PT6 having signed decimal GUID in log
fix  alternative encoding character converting
allow more paths for game log server
add localization for unknown ips in welcome plugin
add gamelog server uri to support game log server on games that must supply manual log path
misc fixes
2019-02-09 15:35:13 -06:00
7c6419a16a finish cod4x parser
add IW3 map names
2019-02-06 20:12:35 -06:00
0194196a33 update application version 2019-02-05 18:06:14 -06:00
044991272f update parsers to include game name
prompt to enter log path if game doesn't generate
2019-02-05 18:02:45 -06:00
f3290cf066 move IW4x parser out of code
add CoD4x parser
2019-02-05 11:14:43 -06:00
29eedea093 fix IW4x regression error with alternative encodings
add parser selection to server config setup
2019-02-04 19:38:24 -06:00
ce02f5dd68 Move T6 parser to javascript parser 2019-02-03 20:47:05 -06:00
e6bfa408f8 move IW3 parser to javascript 2019-02-02 20:19:24 -06:00
0a1dc46760 Add commenting for parsers
rename IW4*Parser to Base*Parser
2019-02-02 19:40:37 -06:00
97ba6aae2e put parser in right location :P 2019-02-02 19:13:17 -06:00
a456fab0e5 Increment version #
Add TeknoMW3 parser file
2019-02-02 19:11:34 -06:00
3e5282df87 Finish preliminary parser for TeknoMW3 2019-02-02 18:54:30 -06:00
59e0072744 Finish dynamic dvar parsing for IW4x 2019-02-01 19:49:25 -06:00
f1dd4f7c7f Fix IP parsing bug introduced with IW4Parser
Additional fix for Webfront Index OoB on _ClientActivity
2019-01-28 18:21:56 -06:00
760d3026ce Fixes for PR 2.3.4.0 2019-01-27 19:45:35 -06:00
07df6dbf79 Update version number and small plugin fix 2019-01-27 18:54:18 -06:00
ca535019c6 Finish RCON dynamic parser impl
Fix configuration generation bug
2019-01-27 18:41:54 -06:00
e6154822f6 Implement more dynamic parser stuff 2019-01-27 16:40:08 -06:00
7a6dccc26a Fix bug with webfront spamming issues when running
Remove IW5 parser
Begin implementation of dynamic parsers
2019-01-26 20:33:37 -06:00
08c883e0ff fix duplicate bot welcomes
fix prompt bool incorrect default value
rename GameEvent.Remote to GameEvent.IsRemote
include NetworkId in webfront claims
fix non descript error message appearing when something fails and localization is not initialized
2019-01-03 14:39:22 -06:00
aaf9eb09b6 more alias changes :(
fix flag penalty coming from wrong user
2019-01-02 18:32:39 -06:00
7b75c35c9b fix aliases for real (hopefully)
fix bug with flag not being applied
fix level being set based on IP instead of IP and name
2018-12-31 20:52:19 -06:00
07ec5cf52f update assembly version 2018-12-30 20:52:26 -06:00
cf5ee8765d finish alias fixes
add manual webfront bind url
2018-12-30 20:48:07 -06:00
9494a17997 update prompt utility functions for issue #65
tweaks to alias stuff
fix bug with bots not showing
2018-12-30 18:13:13 -06:00
5f4171ccf4 hopefulyl fix aliasing issue
bans are applied to an account if the accounts are linked but penallty on a different accounts
2018-12-29 12:43:40 -06:00
a10746d5ff Fix small issue with log reading 2018-12-19 19:24:31 -06:00
8dca05a442 Small fixes 2018-12-17 13:45:16 -06:00
8aa0d204f4 Delete stupid 2018-12-16 21:52:11 -06:00
12cf2e8247 Add server version to master api
Add IsEvadedOffense to EFPenalty
Fix remote log reading in not Windows
2018-12-16 21:16:56 -06:00
b77bdbe793 minor fixed 2018-12-03 19:21:13 -06:00
4522992c0e fix remote commands
user clientkick_for_reason for T6 parsers
small bug fixes
2018-12-01 12:17:53 -06:00
9d6cbee69c update stats
change server id
fIx change log server complaining when empty read
2018-11-27 18:31:48 -06:00
abf0609e2e fix only one administrator showing on admins page
fix profanity determent not applying penalties.
2018-11-25 21:11:55 -06:00
5ac8a55c72 fixes for new polling setup
update database model for alias (nullable ip)
heartbeats now send ip to master server
2018-11-25 20:00:36 -06:00
9bdd7d1b8a More work modifying client stuff 2018-11-07 20:30:11 -06:00
ed83c4c011 started work on getting the restart functionality in the gamelogserver
fix bug with unbanned players still showing as banned via lock icon
move player based stuff into client class
finally renamed Player to EFClient via partial class
don't try to run this build because it's in between stages
2018-11-05 21:01:29 -06:00
d9d548ea18 Small anti-cheat update 2018-10-28 20:47:56 -05:00
1779bf821d more work on skill based team balance.
added on player disconnect to custom callbacks
2018-10-25 08:14:39 -05:00
d50e6c8030 change penalty expiration datetime to null for perm bans
add tempban max time
allow searching for GUID
stats returns ranking as well
fix for promotion/demotion text
2018-10-15 19:51:04 -05:00
a58726d872 add gsc api controller for communicating with gsc
add ignore bots option
fix first localization message not working
2018-10-13 18:51:07 -05:00
dded60a6ef add test to print out all commands 2018-10-12 21:32:30 -05:00
291 changed files with 7743 additions and 2975 deletions

3
.gitignore vendored
View File

@ -230,3 +230,6 @@ bootstrap-custom.min.css
/DiscordWebhook/config.dev.json /DiscordWebhook/config.dev.json
/GameLogServer/env /GameLogServer/env
launchSettings.json launchSettings.json
/VpnDetectionPrivate.js
/Plugins/ScriptPlugins/VpnDetectionPrivate.js
**/Master/env_master

View File

@ -8,9 +8,13 @@ namespace IW4MAdmin.Application.API.Master
public class ApiServer public class ApiServer
{ {
[JsonProperty("id")] [JsonProperty("id")]
public int Id { get; set; } public long Id { get; set; }
[JsonProperty("ip")]
public string IPAddress { get; set; }
[JsonProperty("port")] [JsonProperty("port")]
public short Port { get; set; } public short Port { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("gametype")] [JsonProperty("gametype")]
public string Gametype { get; set; } public string Gametype { get; set; }
[JsonProperty("map")] [JsonProperty("map")]

View File

@ -39,12 +39,14 @@ namespace IW4MAdmin.Application.API.Master
{ {
ClientNum = s.ClientNum, ClientNum = s.ClientNum,
Game = s.GameName.ToString(), Game = s.GameName.ToString(),
Version = s.Version,
Gametype = s.Gametype, Gametype = s.Gametype,
Hostname = s.Hostname, Hostname = s.Hostname,
Map = s.CurrentMap.Name, Map = s.CurrentMap.Name,
MaxClientNum = s.MaxClients, MaxClientNum = s.MaxClients,
Id = s.GetHashCode(), Id = s.EndPoint,
Port = (short)s.GetPort() Port = (short)s.GetPort(),
IPAddress = s.IP
}).ToList() }).ToList()
}; };

View File

@ -2,16 +2,16 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</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</Version> <Version>2.2.5.3</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>2018</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>
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin</RepositoryUrl> <RepositoryUrl>https://github.com/RaidMax/IW4M-Admin</RepositoryUrl>
@ -25,14 +25,14 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="RestEase" Version="1.4.7" /> <PackageReference Include="RestEase" Version="1.4.7" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection> <ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation> <TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.0.0</AssemblyVersion> <AssemblyVersion>2.2.5.3</AssemblyVersion>
<FileVersion>2.2.0.0</FileVersion> <FileVersion>2.2.5.3</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>
@ -79,7 +64,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" /> <PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
</ItemGroup> </ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">

View File

@ -1,24 +1,24 @@
using System; using IW4MAdmin.Application.API.Master;
using System.Collections.Generic; using IW4MAdmin.Application.EventParsers;
using System.Linq; using IW4MAdmin.Application.Misc;
using System.Threading; using IW4MAdmin.Application.RconParsers;
using System.Threading.Tasks;
using System.Text;
using System.Reflection;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Commands; using SharedLibraryCore.Commands;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Services;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Events; using SharedLibraryCore.Events;
using SharedLibraryCore.Exceptions;
using IW4MAdmin.Application.API.Master; using SharedLibraryCore.Helpers;
using IW4MAdmin.Application.Migration; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
@ -26,7 +26,7 @@ namespace IW4MAdmin.Application
{ {
private List<Server> _servers; private List<Server> _servers;
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList(); public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
public Dictionary<int, Player> PrivilegedClients { get; set; } public Dictionary<int, EFClient> PrivilegedClients { get; set; }
public ILogger Logger => GetLogger(0); public ILogger Logger => GetLogger(0);
public bool Running { get; private set; } public bool Running { get; private set; }
public bool IsInitialized { get; private set; } public bool IsInitialized { get; private set; }
@ -37,6 +37,13 @@ namespace IW4MAdmin.Application
public DateTime StartTime { get; private set; } public DateTime StartTime { get; private set; }
public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString(); public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString();
public IList<IRConParser> AdditionalRConParsers { get; }
public IList<IEventParser> AdditionalEventParsers { get; }
public ITokenAuthentication TokenAuthenticator => Authenticator;
public ITokenAuthentication Authenticator => _authenticator;
static ApplicationManager Instance; static ApplicationManager Instance;
readonly List<AsyncStatus> TaskStatuses; readonly List<AsyncStatus> TaskStatuses;
List<Command> Commands; List<Command> Commands;
@ -49,7 +56,8 @@ namespace IW4MAdmin.Application
ManualResetEventSlim OnQuit; ManualResetEventSlim OnQuit;
readonly IPageList PageList; readonly IPageList PageList;
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1); readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
readonly Dictionary<int, ILogger> Loggers = new Dictionary<int, ILogger>(); readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
readonly ITokenAuthentication _authenticator;
private ApplicationManager() private ApplicationManager()
{ {
@ -60,13 +68,15 @@ namespace IW4MAdmin.Application
ClientSvc = new ClientService(); ClientSvc = new ClientService();
AliasSvc = new AliasService(); AliasSvc = new AliasService();
PenaltySvc = new PenaltyService(); PenaltySvc = new PenaltyService();
PrivilegedClients = new Dictionary<int, Player>();
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings"); ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
StartTime = DateTime.UtcNow; StartTime = DateTime.UtcNow;
OnQuit = new ManualResetEventSlim(); OnQuit = new ManualResetEventSlim();
PageList = new PageList(); PageList = new PageList();
AdditionalEventParsers = new List<IEventParser>();
AdditionalRConParsers = new List<IRConParser>();
OnServerEvent += OnGameEvent; OnServerEvent += OnGameEvent;
OnServerEvent += EventApi.OnGameEvent; OnServerEvent += EventApi.OnGameEvent;
_authenticator = new TokenAuthentication();
} }
private async void OnGameEvent(object sender, GameEventArgs args) private async void OnGameEvent(object sender, GameEventArgs args)
@ -85,80 +95,11 @@ namespace IW4MAdmin.Application
try try
{ {
// if the origin client is not in an authorized state (detected by RCon) don't execute the event await newEvent.Owner.ExecuteEvent(newEvent);
if (GameEvent.ShouldOriginEventBeDelayed(newEvent))
{
Logger.WriteDebug($"Delaying origin execution of event type {newEvent.Type} for {newEvent.Origin} because they are not authed");
if (newEvent.Type == GameEvent.EventType.Command)
{
newEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_DELAYED_EVENT_WAIT"]);
}
// offload it to the player to keep // save the event info to the database
newEvent.Origin.DelayedEvents.Enqueue(newEvent); var changeHistorySvc = new ChangeHistoryService();
} await changeHistorySvc.Add(args.Event);
// if the target client is not in an authorized state (detected by RCon) don't execute the event
else if (GameEvent.ShouldTargetEventBeDelayed(newEvent))
{
Logger.WriteDebug($"Delaying target execution of event type {newEvent.Type} for {newEvent.Target} because they are not authed");
// offload it to the player to keep
newEvent.Target.DelayedEvents.Enqueue(newEvent);
}
else
{
await newEvent.Owner.ExecuteEvent(newEvent);
// save the event info to the database
var changeHistorySvc = new ChangeHistoryService();
await changeHistorySvc.Add(args.Event);
// todo: this is a hacky mess
if (newEvent.Origin?.DelayedEvents.Count > 0 &&
(//newEvent.Origin?.State == Player.ClientState.Connected ||
newEvent.Type == GameEvent.EventType.Connect))
{
var events = newEvent.Origin.DelayedEvents;
// add the delayed event to the queue
while (events.Count > 0)
{
var oldEvent = events.Dequeue();
var e = new GameEvent()
{
Type = oldEvent.Type,
Origin = newEvent.Origin,
Data = oldEvent.Data,
Extra = oldEvent.Extra,
Owner = oldEvent.Owner,
Message = oldEvent.Message,
Target = oldEvent.Target,
Remote = oldEvent.Remote
};
e.Origin = newEvent.Origin;
// check if the target was assigned
if (e.Target != null)
{
// update the target incase they left or have newer info
e.Target = newEvent.Owner.GetPlayersAsList()
.FirstOrDefault(p => p.NetworkId == e.Target.NetworkId);
// we have to throw out the event because they left
if (e.Target == null)
{
Logger.WriteWarning($"Delayed event for {e.Origin} was ignored because the target has left");
// hack: don't do anything with the event because the target is invalid
e.Type = GameEvent.EventType.Unknown;
}
}
Logger.WriteDebug($"Adding delayed event of type {e.Type} for {e.Origin} back for processing");
this.GetEventHandler().AddEvent(e);
}
}
}
#if DEBUG #if DEBUG
Logger.WriteDebug($"Processed event with id {newEvent.Id}"); Logger.WriteDebug($"Processed event with id {newEvent.Id}");
@ -192,7 +133,7 @@ namespace IW4MAdmin.Application
Logger.WriteDebug(ex.GetExceptionInfo()); Logger.WriteDebug(ex.GetExceptionInfo());
} }
skip: skip:
// tell anyone waiting for the output that we're done // tell anyone waiting for the output that we're done
newEvent.OnProcessed.Set(); newEvent.OnProcessed.Set();
@ -216,7 +157,7 @@ namespace IW4MAdmin.Application
public async Task UpdateServerStates() public async Task UpdateServerStates()
{ {
// store the server hash code and task for it // store the server hash code and task for it
var runningUpdateTasks = new Dictionary<int, Task>(); var runningUpdateTasks = new Dictionary<long, Task>();
while (Running) while (Running)
{ {
@ -236,16 +177,16 @@ namespace IW4MAdmin.Application
} }
// remove the update tasks as they have completd // remove the update tasks as they have completd
foreach (int serverId in serverTasksToRemove) foreach (long serverId in serverTasksToRemove)
{ {
runningUpdateTasks.Remove(serverId); runningUpdateTasks.Remove(serverId);
} }
// select the servers where the tasks have completed // select the servers where the tasks have completed
var serverIds = Servers.Select(s => s.GetHashCode()).Except(runningUpdateTasks.Select(r => r.Key)).ToList(); var serverIds = Servers.Select(s => s.EndPoint).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
foreach (var server in Servers.Where(s => serverIds.Contains(s.GetHashCode()))) foreach (var server in Servers.Where(s => serverIds.Contains(s.EndPoint)))
{ {
runningUpdateTasks.Add(server.GetHashCode(), Task.Run(async () => runningUpdateTasks.Add(server.EndPoint, Task.Run(async () =>
{ {
try try
{ {
@ -265,11 +206,7 @@ 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
#if DEBUG
await Task.Delay(10000);
#else
await Task.Delay(ConfigHandler.Configuration().RConPollRate); await Task.Delay(ConfigHandler.Configuration().RConPollRate);
#endif
} }
// trigger the event processing loop to end // trigger the event processing loop to end
@ -280,40 +217,21 @@ namespace IW4MAdmin.Application
{ {
Running = true; Running = true;
#region DATABASE
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString, GetApplicationSettings().Configuration()?.DatabaseProvider))
{
await new ContextSeed(db).Seed();
}
// todo: optimize this (or replace it) #region PLUGINS
var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted)) SharedLibraryCore.Plugins.PluginImporter.Load(this);
.Select(c => new
{
c.Password,
c.PasswordSalt,
c.ClientId,
c.Level,
c.Name
});
foreach (var a in ipList) foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
{ {
try try
{ {
PrivilegedClients.Add(a.ClientId, new Player() await Plugin.OnLoadAsync(this);
{
Name = a.Name,
ClientId = a.ClientId,
Level = a.Level,
PasswordSalt = a.PasswordSalt,
Password = a.Password
});
} }
catch (ArgumentException) catch (Exception ex)
{ {
continue; Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
Logger.WriteDebug(ex.GetExceptionInfo());
} }
} }
#endregion #endregion
@ -340,7 +258,18 @@ namespace IW4MAdmin.Application
do do
{ {
newConfig.Servers.Add((ServerConfiguration)new ServerConfiguration().Generate()); var serverConfig = new ServerConfiguration();
foreach (var parser in AdditionalRConParsers)
{
serverConfig.AddRConParser(parser);
}
foreach (var parser in AdditionalEventParsers)
{
serverConfig.AddEventParser(parser);
}
newConfig.Servers.Add((ServerConfiguration)serverConfig.Generate());
} while (Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["SETUP_SERVER_SAVE"])); } while (Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["SETUP_SERVER_SAVE"]));
config = newConfig; config = newConfig;
@ -361,36 +290,54 @@ namespace IW4MAdmin.Application
config.WebfrontBindUrl = "http://0.0.0.0:1624"; config.WebfrontBindUrl = "http://0.0.0.0:1624";
await ConfigHandler.Save(); await ConfigHandler.Save();
} }
foreach (var serverConfig in config.Servers)
{
Migration.ConfigurationMigration.ModifyLogPath020919(serverConfig);
if (serverConfig.RConParserVersion == null || serverConfig.EventParserVersion == null)
{
foreach (var parser in AdditionalRConParsers)
{
serverConfig.AddRConParser(parser);
}
foreach (var parser in AdditionalEventParsers)
{
serverConfig.AddEventParser(parser);
}
serverConfig.ModifyParsers();
}
await ConfigHandler.Save();
}
} }
else if (config.Servers.Count == 0) else 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");
}
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252"); Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252");
#endregion #endregion
#region PLUGINS
SharedLibraryCore.Plugins.PluginImporter.Load(this);
foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) #region DATABASE
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString,
GetApplicationSettings().Configuration()?.DatabaseProvider))
{ {
try await new ContextSeed(db).Seed();
{
await Plugin.OnLoadAsync(this);
}
catch (Exception ex)
{
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
Logger.WriteDebug(ex.GetExceptionInfo());
}
} }
PrivilegedClients = (await ClientSvc.GetPrivilegedClients()).ToDictionary(_client => _client.ClientId);
#endregion #endregion
#region COMMANDS #region COMMANDS
if (ClientSvc.GetOwners().Result.Count == 0) if (ClientSvc.GetOwners().Result.Count == 0)
{
Commands.Add(new COwner()); Commands.Add(new COwner());
}
Commands.Add(new CQuit()); Commands.Add(new CQuit());
Commands.Add(new CKick()); Commands.Add(new CKick());
@ -429,9 +376,12 @@ 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 RequestTokenCommand());
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands) foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
{
Commands.Add(C); Commands.Add(C);
}
#endregion #endregion
#region INIT #region INIT
@ -466,7 +416,9 @@ namespace IW4MAdmin.Application
{ {
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"]} {(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);
@ -565,7 +517,7 @@ namespace IW4MAdmin.Application
Running = false; Running = false;
} }
public ILogger GetLogger(int serverId) public ILogger GetLogger(long serverId)
{ {
if (Loggers.ContainsKey(serverId)) if (Loggers.ContainsKey(serverId))
{ {
@ -595,23 +547,69 @@ namespace IW4MAdmin.Application
return MessageTokens; return MessageTokens;
} }
public IList<Player> GetActiveClients() => _servers.SelectMany(s => s.Players).Where(p => p != null).ToList(); public IList<EFClient> GetActiveClients()
{
return _servers.SelectMany(s => s.Clients).Where(p => p != null).ToList();
}
public ClientService GetClientService() => ClientSvc; public ClientService GetClientService()
public AliasService GetAliasService() => AliasSvc; {
public PenaltyService GetPenaltyService() => PenaltySvc; return ClientSvc;
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings() => ConfigHandler; }
public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients;
public bool ShutdownRequested() => !Running; public AliasService GetAliasService()
public IEventHandler GetEventHandler() => Handler; {
return AliasSvc;
}
public PenaltyService GetPenaltyService()
{
return PenaltySvc;
}
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings()
{
return ConfigHandler;
}
public IDictionary<int, EFClient> GetPrivilegedClients()
{
return PrivilegedClients;
}
public bool ShutdownRequested()
{
return !Running;
}
public IEventHandler GetEventHandler()
{
return Handler;
}
public void SetHasEvent() public void SetHasEvent()
{ {
OnQuit.Set(); OnQuit.Set();
} }
public IList<Assembly> GetPluginAssemblies() => SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies; public IList<Assembly> GetPluginAssemblies()
{
return SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies.Union(SharedLibraryCore.Plugins.PluginImporter.Assemblies).ToList();
}
public IPageList GetPageList() => PageList; public IPageList GetPageList()
{
return PageList;
}
public IRConParser GenerateDynamicRConParser()
{
return new DynamicRConParser();
}
public IEventParser GenerateDynamicEventParser()
{
return new DynamicEventParser();
}
} }
} }

View File

@ -1,82 +0,0 @@
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace IW4MAdmin.Application.Core
{
class ClientAuthentication : IClientAuthentication
{
private Queue<Player> ClientAuthenticationQueue;
private Dictionary<long, Player> AuthenticatedClients;
public ClientAuthentication()
{
ClientAuthenticationQueue = new Queue<Player>();
AuthenticatedClients = new Dictionary<long, Player>();
}
public void AuthenticateClients(IList<Player> clients)
{
// we need to un-auth all the clients that have disconnected
var clientNetworkIds = clients.Select(c => c.NetworkId);
var clientsToRemove = AuthenticatedClients.Keys.Where(c => !clientNetworkIds.Contains(c));
// remove them
foreach (long Id in clientsToRemove.ToList())
{
AuthenticatedClients.Remove(Id);
}
// loop through the polled clients to see if they've been authenticated yet
foreach (var client in clients)
{
// they've not been authenticated
if (!AuthenticatedClients.TryGetValue(client.NetworkId, out Player value))
{
// authenticate them
client.State = Player.ClientState.Authenticated;
AuthenticatedClients.Add(client.NetworkId, client);
}
else
{
// this update their ping
// todo: this seems kinda hacky
value.Ping = client.Ping;
value.Score = client.Score;
}
}
// empty out the queue of clients detected through log
while (ClientAuthenticationQueue.Count > 0)
{
// grab each client that's connected via log
var clientToAuthenticate = ClientAuthenticationQueue.Dequeue();
// if they're not already authed, auth them
if (!AuthenticatedClients.TryGetValue(clientToAuthenticate.NetworkId, out Player value))
{
// authenticate them
clientToAuthenticate.State = Player.ClientState.Authenticated;
AuthenticatedClients.Add(clientToAuthenticate.NetworkId, clientToAuthenticate);
}
}
}
public IList<Player> GetAuthenticatedClients()
{
if (AuthenticatedClients.Values.Count > 18)
{
Program.ServerManager.GetLogger(0).WriteError($"auth client count is {AuthenticatedClients.Values.Count}, this is bad");
return AuthenticatedClients.Values.Take(18).ToList();
}
return AuthenticatedClients.Values.ToList();
}
public void RequestClientAuthentication(Player client)
{
ClientAuthenticationQueue.Enqueue(client);
}
}
}

View File

@ -242,12 +242,12 @@
{ {
"Name": "Village", "Name": "Village",
"Alias": "co_hunted" "Alias": "co_hunted"
} }
] ]
}, },
{ {
"Game": "T6M", "Game": "T6",
"Maps": [ "Maps": [
{ {
"Alias": "Aftermath", "Alias": "Aftermath",
@ -402,6 +402,95 @@
"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

@ -0,0 +1,306 @@
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using System;
using System.Linq;
using System.Text.RegularExpressions;
using static SharedLibraryCore.Server;
namespace IW4MAdmin.Application.EventParsers
{
class BaseEventParser : IEventParser
{
public BaseEventParser()
{
Configuration = new DynamicEventParserConfiguration()
{
GameDirectory = "main",
};
Configuration.Say.Pattern = @"^(say|sayteam);(-?[A-Fa-f0-9_]{8,32});([0-9]+);(.+);(.*)$";
Configuration.Say.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Say.AddMapping(ParserRegex.GroupType.Message, 5);
Configuration.Quit.Pattern = @"^(Q);(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+);([0-9]+);(.*)$";
Configuration.Quit.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Join.Pattern = @"^(J);(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+);([0-9]+);(.*)$";
Configuration.Join.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world);(.{1,24});(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+)?;-?([0-9]+);(axis|allies|world);(.{1,24})?;((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetTeam, 4);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetName, 5);
Configuration.Damage.AddMapping(ParserRegex.GroupType.OriginNetworkId, 6);
Configuration.Damage.AddMapping(ParserRegex.GroupType.OriginClientNumber, 7);
Configuration.Damage.AddMapping(ParserRegex.GroupType.OriginTeam, 8);
Configuration.Damage.AddMapping(ParserRegex.GroupType.OriginName, 9);
Configuration.Damage.AddMapping(ParserRegex.GroupType.Weapon, 10);
Configuration.Damage.AddMapping(ParserRegex.GroupType.Damage, 11);
Configuration.Damage.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
Configuration.Damage.AddMapping(ParserRegex.GroupType.HitLocation, 13);
Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world);(.{1,24});(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+)?;-?([0-9]+);(axis|allies|world);(.{1,24})?;((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Kill.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetTeam, 4);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetName, 5);
Configuration.Kill.AddMapping(ParserRegex.GroupType.OriginNetworkId, 6);
Configuration.Kill.AddMapping(ParserRegex.GroupType.OriginClientNumber, 7);
Configuration.Kill.AddMapping(ParserRegex.GroupType.OriginTeam, 8);
Configuration.Kill.AddMapping(ParserRegex.GroupType.OriginName, 9);
Configuration.Kill.AddMapping(ParserRegex.GroupType.Weapon, 10);
Configuration.Kill.AddMapping(ParserRegex.GroupType.Damage, 11);
Configuration.Kill.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
Configuration.Kill.AddMapping(ParserRegex.GroupType.HitLocation, 13);
}
public IEventParserConfiguration Configuration { get; set; }
public string Version { get; set; } = "CoD";
public Game GameName { get; set; } = Game.COD;
public virtual GameEvent GetEvent(Server server, string logLine)
{
logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim();
string[] lineSplit = logLine.Split(';');
string eventType = lineSplit[0];
if (eventType == "JoinTeam")
{
var origin = server.GetClientsAsList()
.FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.JoinTeam,
Data = logLine,
Origin = origin,
Owner = server
};
}
if (eventType == "say" || eventType == "sayteam")
{
var matchResult = Regex.Match(logLine, Configuration.Say.Pattern);
if (matchResult.Success)
{
string message = matchResult
.Groups[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]]
.ToString()
.Replace("\x15", "")
.Trim();
var origin = server.GetClientsAsList()
.First(c => c.NetworkId == matchResult.Groups[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong());
if (message[0] == '!' || message[0] == '@')
{
return new GameEvent()
{
Type = GameEvent.EventType.Command,
Data = message,
Origin = origin,
Owner = server,
Message = message
};
}
return new GameEvent()
{
Type = GameEvent.EventType.Say,
Data = message,
Origin = origin,
Owner = server,
Message = message
};
}
}
if (eventType == "K")
{
if (!server.CustomCallback)
{
var match = Regex.Match(logLine, Configuration.Kill.Pattern);
if (match.Success)
{
var origin = server.GetClientsAsList()
.First(c => c.NetworkId == match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong());
var target = server.GetClientsAsList()
.First(c => c.NetworkId == match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.Kill,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
}
}
if (eventType == "ScriptKill")
{
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.ScriptKill,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
if (eventType == "ScriptDamage")
{
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.ScriptDamage,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
// damage
if (eventType == "D")
{
if (!server.CustomCallback)
{
var regexMatch = Regex.Match(logLine, Configuration.Damage.Pattern);
if (regexMatch.Success)
{
var origin = server.GetClientsAsList()
.First(c => c.NetworkId == regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong());
var target = server.GetClientsAsList()
.First(c => c.NetworkId == regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.Damage,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
}
}
// join
if (eventType == "J")
{
var regexMatch = Regex.Match(logLine, Configuration.Join.Pattern);
if (regexMatch.Success)
{
bool isBot = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().Contains("bot");
return new GameEvent()
{
Type = GameEvent.EventType.PreConnect,
Data = logLine,
Owner = server,
Origin = new EFClient()
{
CurrentAlias = new EFAlias()
{
Active = false,
Name = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors(),
},
NetworkId = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(),
ClientNumber = Convert.ToInt32(regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
State = EFClient.ClientState.Connecting,
CurrentServer = server,
IsBot = isBot
}
};
}
}
if (eventType == "Q")
{
var regexMatch = Regex.Match(logLine, Configuration.Quit.Pattern);
if (regexMatch.Success)
{
return new GameEvent()
{
Type = GameEvent.EventType.PreDisconnect,
Data = logLine,
Owner = server,
Origin = new EFClient()
{
CurrentAlias = new EFAlias()
{
Active = false,
Name = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors()
},
NetworkId = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(),
ClientNumber = Convert.ToInt32(regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
State = EFClient.ClientState.Disconnecting
}
};
}
}
if (eventType.Contains("ExitLevel"))
{
return new GameEvent()
{
Type = GameEvent.EventType.MapEnd,
Data = lineSplit[0],
Origin = Utilities.IW4MAdminClient(server),
Target = Utilities.IW4MAdminClient(server),
Owner = server
};
}
if (eventType.Contains("InitGame"))
{
string dump = eventType.Replace("InitGame: ", "");
return new GameEvent()
{
Type = GameEvent.EventType.MapChange,
Data = lineSplit[0],
Origin = Utilities.IW4MAdminClient(server),
Target = Utilities.IW4MAdminClient(server),
Owner = server,
Extra = dump.DictionaryFromKeyValue()
};
}
return new GameEvent()
{
Type = GameEvent.EventType.Unknown,
Origin = Utilities.IW4MAdminClient(server),
Target = Utilities.IW4MAdminClient(server),
Owner = server
};
}
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
using static SharedLibraryCore.Server;
namespace IW4MAdmin.Application.EventParsers
{
/// <summary>
/// empty generic implementation of the IEventParserConfiguration
/// allows script plugins to generate dynamic event parsers
/// </summary>
sealed internal class DynamicEventParser : BaseEventParser
{
}
}

View File

@ -0,0 +1,19 @@
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.EventParsers
{
/// <summary>
/// generic implementation of the IEventParserConfiguration
/// allows script plugins to generate dynamic configurations
/// </summary>
sealed internal class DynamicEventParserConfiguration : IEventParserConfiguration
{
public string GameDirectory { get; set; }
public ParserRegex Say { get; set; } = new ParserRegex();
public ParserRegex Join { get; set; } = new ParserRegex();
public ParserRegex Quit { get; set; } = new ParserRegex();
public ParserRegex Kill { get; set; } = new ParserRegex();
public ParserRegex Damage { get; set; } = new ParserRegex();
public ParserRegex Action { get; set; } = new ParserRegex();
}
}

View File

@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IW4MAdmin.Application.EventParsers
{
class IW3EventParser : IW4EventParser
{
public override string GetGameDir() => "main";
}
}

View File

@ -1,239 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Application.EventParsers
{
class IW4EventParser : IEventParser
{
private const string SayRegex = @"(say|sayteam);(.{1,32});([0-9]+)(.*);(.*)";
public virtual GameEvent GetEvent(Server server, string logLine)
{
logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim();
string[] lineSplit = logLine.Split(';');
string eventType = lineSplit[0];
if (eventType == "JoinTeam")
{
var origin = server.GetPlayersAsList().FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.JoinTeam,
Data = eventType,
Origin = origin,
Owner = server
};
}
if (eventType == "say" || eventType == "sayteam")
{
var matchResult = Regex.Match(logLine, SayRegex);
if (matchResult.Success)
{
string message = matchResult.Groups[5].ToString()
.Replace("\x15", "")
.Trim();
var origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2));
if (message[0] == '!' || message[0] == '@')
{
return new GameEvent()
{
Type = GameEvent.EventType.Command,
Data = message,
Origin = origin,
Owner = server,
Message = message
};
}
return new GameEvent()
{
Type = GameEvent.EventType.Say,
Data = message,
Origin = origin,
Owner = server,
Message = message
};
}
}
if (eventType == "K")
{
if (!server.CustomCallback)
{
var origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6));
var target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2));
return new GameEvent()
{
Type = GameEvent.EventType.Kill,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
}
if (eventType == "ScriptKill")
{
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.ScriptKill,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
if (eventType == "ScriptDamage")
{
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.ScriptDamage,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
// damage
if (eventType == "D")
{
if (!server.CustomCallback)
{
if (Regex.Match(eventType, @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$").Success)
{
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.Damage,
Data = eventType,
Origin = origin,
Target = target,
Owner = server
};
}
}
}
// join
if (eventType == "J")
{
var regexMatch = Regex.Match(logLine, @"^(J;)(.{1,32});([0-9]+);(.*)$");
if (regexMatch.Success)
{
return new GameEvent()
{
Type = GameEvent.EventType.Join,
Data = logLine,
Owner = server,
Origin = new Player()
{
Name = regexMatch.Groups[4].ToString().StripColors(),
NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
State = Player.ClientState.Connecting,
CurrentServer = server
}
};
}
}
//if (eventType == "Q")
//{
// var regexMatch = Regex.Match(logLine, @"^(Q;)(.{1,32});([0-9]+);(.*)$");
// if (regexMatch.Success)
// {
// return new GameEvent()
// {
// Type = GameEvent.EventType.Quit,
// Data = logLine,
// Owner = server,
// Origin = new Player()
// {
// Name = regexMatch.Groups[4].ToString().StripColors(),
// NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
// ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
// State = Player.ClientState.Connecting
// }
// };
// }
//}
if (eventType.Contains("ExitLevel"))
{
return new GameEvent()
{
Type = GameEvent.EventType.MapEnd,
Data = lineSplit[0],
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server
};
}
if (eventType.Contains("InitGame"))
{
string dump = eventType.Replace("InitGame: ", "");
return new GameEvent()
{
Type = GameEvent.EventType.MapChange,
Data = lineSplit[0],
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server,
Extra = dump.DictionaryFromKeyValue()
};
}
return new GameEvent()
{
Type = GameEvent.EventType.Unknown,
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server
};
}
// other parsers can derive from this parser so we make it virtual
public virtual string GetGameDir() => "userraw";
}
}

View File

@ -1,53 +0,0 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace IW4MAdmin.Application.EventParsers
{
class IW5EventParser : IW4EventParser
{
public override string GetGameDir() => "logs";
public override GameEvent GetEvent(Server server, string logLine)
{
string cleanedEventLine = Regex.Replace(logLine, @"[0-9]+:[0-9]+\ ", "").Trim();
if (cleanedEventLine.Contains("J;"))
{
string[] lineSplit = cleanedEventLine.Split(';');
int clientNum = Int32.Parse(lineSplit[2]);
var player = new Player()
{
NetworkId = lineSplit[1].ConvertLong(),
ClientNumber = clientNum,
Name = lineSplit[3]
};
return new GameEvent()
{
Type = GameEvent.EventType.Join,
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server,
Extra = player
};
}
else
return base.GetEvent(server, logLine);
}
}
}

View File

@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IW4MAdmin.Application.EventParsers
{
class T5MEventParser : IW4EventParser
{
public override string GetGameDir() => "v2";
}
}

View File

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Application.EventParsers
{
class T6MEventParser : IW4EventParser
{
public override string GetGameDir() => $"t6r{Path.DirectorySeparatorChar}data";
}
}

View File

@ -1,8 +1,6 @@
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -21,23 +19,14 @@ namespace IW4MAdmin.Application.IO
public string ServerId { get; set; } public string ServerId { get; set; }
} }
public GameLogEventDetection(Server server, string gameLogPath, string gameLogName) public GameLogEventDetection(Server server, string gameLogPath, Uri gameLogServerUri)
{ {
GameLogFile = gameLogPath; GameLogFile = gameLogPath;
// todo: abtract this more Reader = gameLogServerUri != null ? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser) : Reader = new GameLogReader(gameLogPath, server.EventParser);
if (gameLogPath.StartsWith("http"))
{
Reader = new GameLogReaderHttp(gameLogPath, server.EventParser);
}
else
{
Reader = new GameLogReader(gameLogPath, server.EventParser);
}
Server = server; Server = server;
} }
public void PollForChanges() public async Task PollForChanges()
{ {
while (!Server.Manager.ShutdownRequested()) while (!Server.Manager.ShutdownRequested())
{ {
@ -45,12 +34,12 @@ namespace IW4MAdmin.Application.IO
{ {
try try
{ {
UpdateLogEvents(); await UpdateLogEvents();
} }
catch (Exception e) catch (Exception e)
{ {
Server.Logger.WriteWarning($"Failed to update log event for {Server.GetHashCode()}"); Server.Logger.WriteWarning($"Failed to update log event for {Server.EndPoint}");
Server.Logger.WriteDebug($"Exception: {e.Message}"); Server.Logger.WriteDebug($"Exception: {e.Message}");
Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}"); Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}");
} }
@ -59,7 +48,7 @@ namespace IW4MAdmin.Application.IO
} }
} }
private void UpdateLogEvents() private async Task UpdateLogEvents()
{ {
long fileSize = Reader.Length; long fileSize = Reader.Length;
@ -74,11 +63,12 @@ namespace IW4MAdmin.Application.IO
PreviousFileSize = fileSize; PreviousFileSize = fileSize;
var events = Reader.ReadEventsFromLog(Server, fileDiff, 0); var events = await Reader.ReadEventsFromLog(Server, fileDiff, 0);
foreach (var ev in events) foreach (var ev in events)
{ {
Server.Manager.GetEventHandler().AddEvent(ev); Server.Manager.GetEventHandler().AddEvent(ev);
await ev.WaitAsync();
} }
PreviousFileSize = fileSize; PreviousFileSize = fileSize;

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.IO namespace IW4MAdmin.Application.IO
{ {
@ -22,7 +23,7 @@ namespace IW4MAdmin.Application.IO
Parser = parser; Parser = parser;
} }
public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{ {
// allocate the bytes for the new log lines // allocate the bytes for the new log lines
List<string> logLines = new List<string>(); List<string> logLines = new List<string>();
@ -30,6 +31,7 @@ namespace IW4MAdmin.Application.IO
// open the file as a stream // open the file as a stream
using (var rd = new StreamReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType)) using (var rd = new StreamReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType))
{ {
// 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 // the difference should be in the range of a int :P

View File

@ -5,6 +5,7 @@ using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks;
using static SharedLibraryCore.Utilities; using static SharedLibraryCore.Utilities;
namespace IW4MAdmin.Application.IO namespace IW4MAdmin.Application.IO
@ -16,31 +17,32 @@ namespace IW4MAdmin.Application.IO
{ {
readonly IEventParser Parser; readonly IEventParser Parser;
readonly IGameLogServer Api; readonly IGameLogServer Api;
readonly string LogFile; readonly string logPath;
public GameLogReaderHttp(string logFile, IEventParser parser) public GameLogReaderHttp(Uri gameLogServerUri, string logPath, IEventParser parser)
{ {
LogFile = logFile; this.logPath = logPath.ToBase64UrlSafeString(); ;
Parser = parser; Parser = parser;
Api = RestClient.For<IGameLogServer>(logFile); Api = RestClient.For<IGameLogServer>(gameLogServerUri);
} }
public long Length => -1; public long Length => -1;
public int UpdateInterval => 1000; public int UpdateInterval => 350;
public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{ {
#if DEBUG == true #if DEBUG == true
server.Logger.WriteDebug($"Begin reading {fileSizeDiff} from http log"); server.Logger.WriteDebug($"Begin reading from http log");
#endif #endif
var events = new List<GameEvent>(); var events = new List<GameEvent>();
string b64Path = server.LogPath.ToBase64UrlSafeString(); string b64Path = logPath;
var response = Api.Log(b64Path).Result; var response = await Api.Log(b64Path);
if (!response.Success) if (!response.Success)
{ {
server.Logger.WriteError($"Could not get log server info of {LogFile}/{b64Path} ({server.LogPath})"); server.Logger.WriteError($"Could not get log server info of {logPath}/{b64Path} ({server.LogPath})");
return events;
} }
// parse each line // parse each line

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@ namespace IW4MAdmin.Application.Localization
{ {
public class Configure public class Configure
{ {
public static void Initialize(string customLocale) public static void Initialize(string customLocale = null)
{ {
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");

View File

@ -1,16 +1,11 @@
using System; using IW4MAdmin.Application.Migration;
using System.Threading.Tasks;
using System.IO;
using System.Reflection;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Objects; using SharedLibraryCore.Localization;
using SharedLibraryCore.Database; using System;
using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Collections.Generic; using System.Threading.Tasks;
using SharedLibraryCore.Localization;
using IW4MAdmin.Application.Migration;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
@ -23,7 +18,6 @@ namespace IW4MAdmin.Application
public static void Main(string[] args) public static void Main(string[] args)
{ {
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory); AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
Console.OutputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8;
Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = ConsoleColor.Gray;
@ -39,6 +33,18 @@ namespace IW4MAdmin.Application
try try
{ {
ServerManager = ApplicationManager.GetInstance();
if (ServerManager.GetApplicationSettings().Configuration() != null)
{
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration().CustomLocale);
}
else
{
Localization.Configure.Initialize();
}
loc = Utilities.CurrentLocalization.LocalizationIndex; loc = Utilities.CurrentLocalization.LocalizationIndex;
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey); Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
@ -47,10 +53,6 @@ namespace IW4MAdmin.Application
// todo: move out // todo: move out
ConfigurationMigration.MoveConfigFolder10518(null); ConfigurationMigration.MoveConfigFolder10518(null);
ServerManager = ApplicationManager.GetInstance();
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
ServerManager.Logger.WriteInfo($"Version is {Version}"); ServerManager.Logger.WriteInfo($"Version is {Version}");
var api = API.Master.Endpoint.Get(); var api = API.Master.Endpoint.Get();
@ -111,15 +113,17 @@ namespace IW4MAdmin.Application
var consoleTask = Task.Run(async () => var consoleTask = Task.Run(async () =>
{ {
String userInput; string userInput;
Player Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]); var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
do do
{ {
userInput = Console.ReadLine(); userInput = Console.ReadLine();
if (userInput?.ToLower() == "quit") if (userInput?.ToLower() == "quit")
{
ServerManager.Stop(); ServerManager.Stop();
}
if (ServerManager.Servers.Count == 0) if (ServerManager.Servers.Count == 0)
{ {
@ -148,13 +152,16 @@ namespace IW4MAdmin.Application
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(loc["MANAGER_INIT_FAIL"]); string failMessage = loc == null ? "Failed to initalize IW4MAdmin" : loc["MANAGER_INIT_FAIL"];
string exitMessage = loc == null ? "Press any key to exit..." : loc["MANAGER_EXIT"];
Console.WriteLine(failMessage);
while (e.InnerException != null) while (e.InnerException != null)
{ {
e = e.InnerException; e = e.InnerException;
} }
Console.WriteLine(e.Message); Console.WriteLine(e.Message);
Console.WriteLine(loc["MANAGER_EXIT"]); Console.WriteLine(exitMessage);
Console.ReadKey(); Console.ReadKey();
return; return;
} }

View File

@ -56,5 +56,14 @@ namespace IW4MAdmin.Application.Migration
} }
} }
} }
public static void ModifyLogPath020919(SharedLibraryCore.Configuration.ServerConfiguration config)
{
if (config.ManualLogPath.IsRemoteLog())
{
config.GameLogServerUrl = new Uri(config.ManualLogPath);
config.ManualLogPath = null;
}
}
} }
} }

View File

@ -0,0 +1,100 @@
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
namespace IW4MAdmin.Application.Misc
{
class TokenAuthentication : ITokenAuthentication
{
private readonly ConcurrentDictionary<long, TokenState> _tokens;
private readonly RNGCryptoServiceProvider _random;
private readonly static TimeSpan _timeoutPeriod = new TimeSpan(0, 0, 120);
private const short TOKEN_LENGTH = 4;
public TokenAuthentication()
{
_tokens = new ConcurrentDictionary<long, TokenState>();
_random = new RNGCryptoServiceProvider();
}
public bool AuthorizeToken(long networkId, string token)
{
bool authorizeSuccessful = _tokens.ContainsKey(networkId) && _tokens[networkId].Token == token;
if (authorizeSuccessful)
{
_tokens.TryRemove(networkId, out TokenState _);
}
return authorizeSuccessful;
}
public TokenState GenerateNextToken(long networkId)
{
TokenState state = null;
if (_tokens.ContainsKey(networkId))
{
state = _tokens[networkId];
if ((DateTime.Now - state.RequestTime) > _timeoutPeriod)
{
_tokens.TryRemove(networkId, out TokenState _);
}
else
{
return state;
}
}
state = new TokenState()
{
NetworkId = networkId,
Token = _generateToken(),
TokenDuration = _timeoutPeriod
};
_tokens.TryAdd(networkId, state);
// 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()
{
bool validCharacter(char c)
{
// this ensure that the characters are 0-9, A-Z, a-z
return (c > 47 && c < 58) || (c > 64 && c < 91) || (c > 96 && c < 123);
}
StringBuilder token = new StringBuilder();
while (token.Length < TOKEN_LENGTH)
{
byte[] charSet = new byte[1];
_random.GetBytes(charSet);
if (validCharacter((char)charSet[0]))
{
token.Append((char)charSet[0]);
}
}
_random.Dispose();
return token.ToString();
}
}
}

View File

@ -0,0 +1,180 @@
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.RCon;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static SharedLibraryCore.Server;
namespace IW4MAdmin.Application.RconParsers
{
class BaseRConParser : IRConParser
{
public BaseRConParser()
{
Configuration = new DynamicRConParserConfiguration()
{
CommandPrefixes = new CommandPrefix()
{
Tell = "tell {0} {1}",
Say = "say {0}",
Kick = "clientkick {0} \"{1}\"",
Ban = "clientkick {0} \"{1}\"",
TempBan = "tempbanclient {0} \"{1}\"",
RConCommand = "ÿÿÿÿrcon {0} {1}",
RConGetDvar = "ÿÿÿÿrcon {0} {1}",
RConSetDvar = "ÿÿÿÿrcon {0} set {1}",
RConGetStatus = "ÿÿÿÿgetstatus",
RConGetInfo = "ÿÿÿÿgetinfo",
RConResponse = "ÿÿÿÿprint",
},
};
Configuration.Status.Pattern = @"^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) +(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$";
Configuration.Status.AddMapping(ParserRegex.GroupType.RConClientNumber, 1);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConScore, 2);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConPing, 3);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConNetworkId, 4);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConName, 5);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConIpAddress, 7);
Configuration.Dvar.Pattern = "^\"(.+)\" is: \"(.+)?\" default: \"(.+)?\"\n(?:latched: \"(.+)?\"\n)? *(.+)$";
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarName, 1);
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarValue, 2);
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDefaultValue, 3);
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarLatchedValue, 4);
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDomain, 5);
}
public IRConParserConfiguration Configuration { get; set; }
public string Version { get; set; } = "CoD";
public Game GameName { get; set; } = Game.COD;
public bool CanGenerateLogPath { get; set; } = true;
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
return response.Skip(1).ToArray();
}
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
{
string[] lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
string response = string.Join('\n', lineSplit.Skip(1));
if (!lineSplit[0].Contains(Configuration.CommandPrefixes.RConResponse))
{
throw new DvarException($"Could not retrieve DVAR \"{dvarName}\"");
}
if (response.Contains("Unknown command"))
{
throw new DvarException($"DVAR \"{dvarName}\" does not exist");
}
var match = Regex.Match(response, Configuration.Dvar.Pattern);
if (!match.Success)
{
throw new DvarException($"Could not retrieve DVAR \"{dvarName}\"");
}
string value = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarValue]].Value.StripColors();
string defaultValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDefaultValue]].Value.StripColors();
string latchedValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarLatchedValue]].Value.StripColors();
return new Dvar<T>()
{
Name = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarName]].Value.StripColors(),
Value = string.IsNullOrEmpty(value) ? default(T) : (T)Convert.ChangeType(value, typeof(T)),
DefaultValue = string.IsNullOrEmpty(defaultValue) ? default(T) : (T)Convert.ChangeType(defaultValue, typeof(T)),
LatchedValue = string.IsNullOrEmpty(latchedValue) ? default(T) : (T)Convert.ChangeType(latchedValue, typeof(T)),
Domain = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDomain]].Value.StripColors()
};
}
public async Task<List<EFClient>> GetStatusAsync(Connection connection)
{
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
return ClientsFromStatus(response);
}
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
{
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, $"{dvarName} {dvarValue}")).Length > 0;
}
private List<EFClient> ClientsFromStatus(string[] Status)
{
List<EFClient> StatusPlayers = new List<EFClient>();
if (Status.Length < 4)
{
throw new ServerException("Unexpected status response received");
}
int validMatches = 0;
foreach (string S in Status)
{
string responseLine = S.Trim();
var regex = Regex.Match(responseLine, Configuration.Status.Pattern, RegexOptions.IgnoreCase);
if (regex.Success)
{
validMatches++;
int clientNumber = int.Parse(regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]].Value);
int score = int.Parse(regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]].Value);
int ping = 999;
// their state can be CNCT, ZMBI etc
if (regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Value.Length <= 3)
{
ping = int.Parse(regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Value);
}
long networkId = regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]].Value.ConvertLong();
string name = regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].Value.StripColors().Trim();
int? ip = regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Value.Split(':')[0].ConvertToIP();
var client = new EFClient()
{
CurrentAlias = new EFAlias()
{
Name = name
},
NetworkId = networkId,
ClientNumber = clientNumber,
IPAddress = ip,
Ping = ping,
Score = score,
IsBot = ip == null,
State = EFClient.ClientState.Connecting
};
// they've not fully connected yet
if (!client.IsBot && ping == 999)
{
continue;
}
StatusPlayers.Add(client);
}
}
// this happens if status is requested while map is rotating
if (Status.Length > 5 && validMatches == 0)
{
throw new ServerException("Server is rotating map");
}
return StatusPlayers;
}
}
}

View File

@ -0,0 +1,10 @@
namespace IW4MAdmin.Application.RconParsers
{
/// <summary>
/// empty implementation of the IW4RConParser
/// allows script plugins to generate dynamic RCon parsers
/// </summary>
sealed internal class DynamicRConParser : BaseRConParser
{
}
}

View File

@ -0,0 +1,18 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.RCon;
namespace IW4MAdmin.Application.RconParsers
{
/// <summary>
/// generic implementation of the IRConParserConfiguration
/// allows script plugins to generate dynamic RCon configurations
/// </summary>
sealed internal class DynamicRConParserConfiguration : IRConParserConfiguration
{
public CommandPrefix CommandPrefixes { get; set; }
public ParserRegex Status { get; set; } = new ParserRegex();
public ParserRegex Dvar { get; set; } = new ParserRegex();
public bool WaitForResponse { get; set; } = true;
}
}

View File

@ -1,21 +0,0 @@
using SharedLibraryCore.RCon;
using System;
using System.Collections.Generic;
using System.Text;
namespace IW4MAdmin.Application.RconParsers
{
class IW3RConParser : IW4RConParser
{
private static readonly CommandPrefix Prefixes = new CommandPrefix()
{
Tell = "tell {0} {1}",
Say = "say {0}",
Kick = "clientkick {0} \"{1}\"",
Ban = "clientkick {0} \"{1}\"",
TempBan = "tempbanclient {0} \"{1}\""
};
public override CommandPrefix GetCommandPrefixes() => Prefixes;
}
}

View File

@ -1,133 +0,0 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Text;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
namespace IW4MAdmin.Application.RconParsers
{
class IW4RConParser : IRConParser
{
private static readonly CommandPrefix Prefixes = new CommandPrefix()
{
Tell = "tellraw {0} {1}",
Say = "sayraw {0}",
Kick = "clientkick {0} \"{1}\"",
Ban = "clientkick {0} \"{1}\"",
TempBan = "tempbanclient {0} \"{1}\""
};
private static readonly string StatusRegex = @"^( *[0-9]+) +-*([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|(?:[a-z]|[0-9]){32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$";
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
return response.Skip(1).ToArray();
}
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
{
string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, dvarName);
if (LineSplit.Length < 3)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }, StringSplitOptions.RemoveEmptyEntries);
if (ValueSplit.Length < 5)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string DvarName = Regex.Replace(ValueSplit[0], @"\^[0-9]", "");
string DvarCurrentValue = Regex.Replace(ValueSplit[2], @"\^[0-9]", "");
string DvarDefaultValue = Regex.Replace(ValueSplit[4], @"\^[0-9]", "");
return new Dvar<T>(DvarName)
{
Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T))
};
}
public async Task<List<Player>> GetStatusAsync(Connection connection)
{
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response);
}
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
{
return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0;
}
public virtual CommandPrefix GetCommandPrefixes() => Prefixes;
private List<Player> ClientsFromStatus(string[] Status)
{
List<Player> StatusPlayers = new List<Player>();
if (Status.Length < 4)
throw new ServerException("Unexpected status response received");
int validMatches = 0;
foreach (String S in Status)
{
String responseLine = S.Trim();
var regex = Regex.Match(responseLine, StatusRegex, RegexOptions.IgnoreCase);
if (regex.Success)
{
validMatches++;
int clientNumber = int.Parse(regex.Groups[1].Value);
int score = int.Parse(regex.Groups[2].Value);
int ping = 999;
// their state can be CNCT, ZMBI etc
if (regex.Groups[3].Value.Length <= 3)
{
ping = int.Parse(regex.Groups[3].Value);
}
long networkId = regex.Groups[4].Value.ConvertLong();
string name = regex.Groups[5].Value.StripColors().Trim();
int ip = regex.Groups[7].Value.Split(':')[0].ConvertToIP();
Player P = new Player()
{
Name = name,
NetworkId = networkId,
ClientNumber = clientNumber,
IPAddress = ip == 0 ? int.MinValue : ip,
Ping = ping,
Score = score,
IsBot = ip == 0,
State = Player.ClientState.Connecting
};
StatusPlayers.Add(P);
}
}
// this happens if status is requested while map is rotating
if (Status.Length > 5 && validMatches == 0)
{
throw new ServerException("Server is rotating map");
}
return StatusPlayers;
}
}
}

View File

@ -1,170 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Text;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
namespace IW4MAdmin.Application.RconParsers
{
public class IW5MRConParser : IRConParser
{
private static readonly CommandPrefix Prefixes = new CommandPrefix()
{
Tell = "tell {0} {1}",
Say = "say {0}",
Kick = "dropClient {0} \"{1}\"",
Ban = "dropClient {0} \"{1}\"",
TempBan = "dropClient {0} \"{1}\""
};
public CommandPrefix GetCommandPrefixes() => Prefixes;
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{
await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, false);
return new string[] { "Command Executed" };
}
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
{
// why can't this be real :(
if (dvarName == "version")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("IW5 MP 1.9 build 461 Fri Sep 14 00:04:28 2012 win-x86", typeof(T))
};
if (dvarName == "shortversion")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("1.9", typeof(T))
};
if (dvarName == "mapname")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("Unknown", typeof(T))
};
if (dvarName == "g_gametype")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("Unknown", typeof(T))
};
if (dvarName == "fs_game")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("", typeof(T))
};
if (dvarName == "g_logsync")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType(1, typeof(T))
};
if (dvarName == "fs_basepath")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("", typeof(T))
};
string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, dvarName);
if (LineSplit.Length < 4)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string[] ValueSplit = LineSplit[1].Split(new char[] { '"' });
if (ValueSplit.Length == 0)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string DvarName = dvarName;
string DvarCurrentValue = Regex.Replace(ValueSplit[3].StripColors(), @"\^[0-9]", "");
return new Dvar<T>(DvarName)
{
Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T))
};
}
public async Task<List<Player>> GetStatusAsync(Connection connection)
{
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response);
}
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
{
// T6M doesn't respond with anything when a value is set, so we can only hope for the best :c
await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, $"set {dvarName} {dvarValue}", false);
return true;
}
private List<Player> ClientsFromStatus(string[] status)
{
List<Player> StatusPlayers = new List<Player>();
foreach (string statusLine in status)
{
String responseLine = statusLine;
if (Regex.Matches(responseLine, @"^ *\d+", RegexOptions.IgnoreCase).Count > 0) // its a client line!
{
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
// this happens when the client is in a zombie state
if (playerInfo.Length < 5)
continue;
int clientId = -1;
int Ping = -1;
Int32.TryParse(playerInfo[2], out Ping);
string name = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(responseLine.Substring(23, 15).StripColors().Trim())));
long networkId = 0;//playerInfo[4].ConvertLong();
int.TryParse(playerInfo[0], out clientId);
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @" +(\d+ +){3}");
int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]);
var p = new Player()
{
Name = name,
NetworkId = networkId,
ClientNumber = clientId,
IPAddress = ipAddress,
Ping = Ping,
Score = score,
IsBot = false,
State = Player.ClientState.Connecting
};
StatusPlayers.Add(p);
if (p.IsBot)
p.NetworkId = -p.ClientNumber;
}
}
return StatusPlayers;
}
}
}

View File

@ -1,123 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
using System.Text;
namespace IW4MAdmin.Application.RconParsers
{
public class T6MRConParser : IRConParser
{
private static readonly CommandPrefix Prefixes = new CommandPrefix()
{
Tell = "tell {0} {1}",
Say = "say {0}",
Kick = "clientKick {0}",
Ban = "clientKick {0}",
TempBan = "clientKick {0}"
};
public CommandPrefix GetCommandPrefixes() => Prefixes;
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{
await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, false);
return new string[] { "Command Executed" };
}
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
{
string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"get {dvarName}");
if (LineSplit.Length < 2)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string[] ValueSplit = LineSplit[1].Split(new char[] { '"' });
if (ValueSplit.Length == 0)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string DvarName = dvarName;
string DvarCurrentValue = Regex.Replace(ValueSplit[1], @"\^[0-9]", "");
return new Dvar<T>(DvarName)
{
Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T))
};
}
public async Task<List<Player>> GetStatusAsync(Connection connection)
{
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response);
}
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
{
// T6M doesn't respond with anything when a value is set, so we can only hope for the best :c
await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, $"set {dvarName} {dvarValue}", false);
return true;
}
private List<Player> ClientsFromStatus(string[] status)
{
List<Player> StatusPlayers = new List<Player>();
foreach (string statusLine in status)
{
String responseLine = statusLine;
if (Regex.Matches(responseLine, @"^ *\d+", RegexOptions.IgnoreCase).Count > 0) // its a client line!
{
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int clientId = -1;
int Ping = -1;
Int32.TryParse(playerInfo[3], out Ping);
var regex = Regex.Match(responseLine, @"\^7.*\ +0 ");
string name = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(regex.Value.Substring(0, regex.Value.Length - 2).StripColors().Trim())));
long networkId = playerInfo[4].ConvertLong();
int.TryParse(playerInfo[0], out clientId);
regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
#if DEBUG
Ping = 1;
#endif
int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
var p = new Player()
{
Name = name,
NetworkId = networkId,
ClientNumber = clientId,
IPAddress = ipAddress,
Ping = Ping,
Score = 0,
State = Player.ClientState.Connecting,
IsBot = networkId == 0
};
if (p.IsBot)
p.NetworkId = -p.ClientNumber;
StatusPlayers.Add(p);
}
}
return StatusPlayers;
}
}
}

View File

@ -27,6 +27,11 @@
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging> <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<OutputPath>bin\Prerelease\</OutputPath>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="DiscordWebhook.py" /> <Compile Include="DiscordWebhook.py" />
</ItemGroup> </ItemGroup>
@ -42,7 +47,6 @@
</Interpreter> </Interpreter>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="config.dev.json" />
<Content Include="config.json"> <Content Include="config.json">
<Publish>True</Publish> <Publish>True</Publish>
</Content> </Content>
@ -50,7 +54,7 @@
<Publish>True</Publish> <Publish>True</Publish>
</Content> </Content>
</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" />
<!-- Uncomment the CoreCompile target to enable the Build command in <!-- Uncomment the CoreCompile target to enable the Build command in
Visual Studio and specify your pre- and post-build commands in Visual Studio and specify your pre- and post-build commands in
the BeforeBuild and AfterBuild targets below. --> the BeforeBuild and AfterBuild targets below. -->
@ -59,7 +63,7 @@
</Target> </Target>
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
<ProjectExtensions> <ProjectExtensions>
<VisualStudio> <VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}"> <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties> <WebProjectProperties>

View File

@ -11,13 +11,15 @@
<SearchPath> <SearchPath>
</SearchPath> </SearchPath>
<WorkingDirectory>.</WorkingDirectory> <WorkingDirectory>.</WorkingDirectory>
<LaunchProvider>Web launcher</LaunchProvider> <LaunchProvider>Standard Python launcher</LaunchProvider>
<WebBrowserUrl>http://localhost</WebBrowserUrl> <WebBrowserUrl>http://localhost</WebBrowserUrl>
<OutputPath>.</OutputPath> <OutputPath>.</OutputPath>
<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|env|$(MSBuildProjectFullPath)</InterpreterId>
<EnableNativeCodeDebugging>False</EnableNativeCodeDebugging>
<Environment>DEBUG=True</Environment>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -27,10 +29,16 @@
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging> <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<OutputPath>bin\Prerelease\</OutputPath>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="GameLogServer\log_reader.py"> <Compile Include="GameLogServer\log_reader.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="GameLogServer\restart_resource.py" />
<Compile Include="GameLogServer\server.py"> <Compile Include="GameLogServer\server.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
@ -42,8 +50,8 @@
<Folder Include="GameLogServer\" /> <Folder Include="GameLogServer\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="FolderProfile.pubxml" />
<Content Include="requirements.txt" /> <Content Include="requirements.txt" />
<None Include="Stable.pubxml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Interpreter Include="env\"> <Interpreter Include="env\">

View File

@ -5,26 +5,27 @@ 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 ) - 1 MB # (if the file changes more than this, ignore ) - 0.125 MB
self.max_file_size_change = 1000000 self.max_file_size_change = 125000
# (if the time between checks is greater, ignore ) - 5 minutes # (if the time between checks is greater, ignore ) - 5 minutes
self.max_file_time_change = 1000 self.max_file_time_change = 60
def read_file(self, path): def read_file(self, 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'^.+[\\|\/](userraw|mods)[\\|\/].+.log$', path): if not re.search(r'^.+[\\|\/](.+)[\\|\/].+.log$', path):
return False return False
# set the initialze size to the current file size # set the initialze size to the current file size
file_size = 0 file_size = 0
if path not in self.log_file_sizes: if path not in self.log_file_sizes:
self.log_file_sizes[path] = { self.log_file_sizes[path] = {
'length' : self.file_length(path), 'length' : self.file_length(path),
'read': time.time() 'read': time.time()
} }
return '' return True
# grab the previous values # grab the previous values
last_length = self.log_file_sizes[path]['length'] last_length = self.log_file_sizes[path]['length']
@ -50,8 +51,8 @@ class LogReader(object):
# if it's been too long since we read and the amount changed is too great, discard it # 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" # todo: do we really want old events? maybe make this an "or"
if file_size_difference > self.max_file_size_change and time_difference > self.max_file_time_change: if file_size_difference > self.max_file_size_change or time_difference > self.max_file_time_change:
return '' 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

View File

@ -10,8 +10,10 @@ class LogResource(Resource):
if log_info is False: if log_info is False:
print('could not read log file ' + path) 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 log_info is False else len(log_info), 'length': -1 if empty_read else len(log_info),
'data': log_info 'data': log_info
} }

View File

@ -0,0 +1,29 @@
from flask_restful import Resource
from flask import request
import requests
import os
import subprocess
import re
def get_pid_of_server_windows(port):
process = subprocess.Popen('netstat -aon', shell=True, stdout=subprocess.PIPE)
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'))
if matches is not None:
return matches.group(3)
else:
return 0
class RestartResource(Resource):
def get(self):
try:
response = requests.get('http://' + request.remote_addr + ':1624/api/restartapproved')
if response.status_code == 200:
pid = get_pid_of_server_windows(response.json()['port'])
subprocess.check_output("Taskkill /PID %s /F" % pid)
else:
return {}, 400
except Exception as e:
print(e)
return {}, 500
return {}, 200

View File

@ -1,9 +1,11 @@
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
app = Flask(__name__) app = Flask(__name__)
def init(): def init():
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')

View File

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

View File

@ -12,4 +12,4 @@ if __name__ == '__main__':
except ValueError: except ValueError:
PORT = 5555 PORT = 5555
init() init()
app.run(HOST, PORT, debug=True) app.run('0.0.0.0', PORT, debug=False)

View File

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26730.16 VisualStudioVersion = 15.0.26730.16
@ -39,12 +38,22 @@ Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "DiscordWebhook", "DiscordWe
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlugins", "{3F9ACC27-26DB-49FA-BCD2-50C54A49C9FA}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlugins", "{3F9ACC27-26DB-49FA-BCD2-50C54A49C9FA}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
Plugins\ScriptPlugins\ParserCoD4x.js = Plugins\ScriptPlugins\ParserCoD4x.js
Plugins\ScriptPlugins\ParserIW4x.js = Plugins\ScriptPlugins\ParserIW4x.js
Plugins\ScriptPlugins\ParserPT6.js = Plugins\ScriptPlugins\ParserPT6.js
Plugins\ScriptPlugins\ParserTeknoMW3.js = Plugins\ScriptPlugins\ParserTeknoMW3.js
Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
EndProjectSection EndProjectSection
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
@ -283,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
@ -303,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
@ -312,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
@ -335,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
@ -347,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

@ -11,13 +11,13 @@
<SearchPath> <SearchPath>
</SearchPath> </SearchPath>
<WorkingDirectory>.</WorkingDirectory> <WorkingDirectory>.</WorkingDirectory>
<LaunchProvider>Web launcher</LaunchProvider> <LaunchProvider>Standard Python launcher</LaunchProvider>
<WebBrowserUrl>http://localhost</WebBrowserUrl> <WebBrowserUrl>http://localhost</WebBrowserUrl>
<OutputPath>.</OutputPath> <OutputPath>.</OutputPath>
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles> <SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
<Name>Master</Name> <Name>Master</Name>
<RootNamespace>Master</RootNamespace> <RootNamespace>Master</RootNamespace>
<InterpreterId>MSBuild|dev_env|$(MSBuildProjectFullPath)</InterpreterId> <InterpreterId>MSBuild|env_master|$(MSBuildProjectFullPath)</InterpreterId>
<IsWindowsApplication>False</IsWindowsApplication> <IsWindowsApplication>False</IsWindowsApplication>
<PythonRunWebServerCommand> <PythonRunWebServerCommand>
</PythonRunWebServerCommand> </PythonRunWebServerCommand>
@ -25,6 +25,7 @@
</PythonDebugWebServerCommand> </PythonDebugWebServerCommand>
<PythonRunWebServerCommandType>script</PythonRunWebServerCommandType> <PythonRunWebServerCommandType>script</PythonRunWebServerCommandType>
<PythonDebugWebServerCommandType>script</PythonDebugWebServerCommandType> <PythonDebugWebServerCommandType>script</PythonDebugWebServerCommandType>
<EnableNativeCodeDebugging>False</EnableNativeCodeDebugging>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -111,18 +112,18 @@
<Folder Include="master\templates\" /> <Folder Include="master\templates\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="FolderProfile.pubxml" />
<Content Include="master\config\master.json" /> <Content Include="master\config\master.json" />
<Content Include="master\templates\serverlist.html" /> <Content Include="master\templates\serverlist.html" />
<None Include="Release.pubxml" />
<Content Include="requirements.txt" /> <Content Include="requirements.txt" />
<Content Include="master\templates\index.html" /> <Content Include="master\templates\index.html" />
<Content Include="master\templates\layout.html" /> <Content Include="master\templates\layout.html" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Interpreter Include="dev_env\"> <Interpreter Include="env_master\">
<Id>dev_env</Id> <Id>env_master</Id>
<Version>3.6</Version> <Version>3.6</Version>
<Description>dev_env (Python 3.6 (64-bit))</Description> <Description>env_master (Python 3.6 (64-bit))</Description>
<InterpreterPath>Scripts\python.exe</InterpreterPath> <InterpreterPath>Scripts\python.exe</InterpreterPath>
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath> <WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable> <PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>

View File

@ -1,8 +1,9 @@
class ServerModel(object): class ServerModel(object):
def __init__(self, id, port, game, hostname, clientnum, maxclientnum, map, gametype, ip): def __init__(self, id, port, game, hostname, clientnum, maxclientnum, map, gametype, ip, version):
self.id = id self.id = id
self.port = port self.port = port
self.version = version
self.game = game self.game = game
self.hostname = hostname self.hostname = hostname
self.clientnum = clientnum self.clientnum = clientnum

View File

@ -5,6 +5,7 @@ from marshmallow import ValidationError
from master.schema.instanceschema import InstanceSchema from master.schema.instanceschema import InstanceSchema
from master import ctx from master import ctx
import json import json
from netaddr import IPAddress
class Instance(Resource): class Instance(Resource):
def get(self, id=None): def get(self, id=None):
@ -23,7 +24,10 @@ class Instance(Resource):
def put(self, id): def put(self, id):
try: try:
for server in request.json['servers']: for server in request.json['servers']:
server['ip'] = request.remote_addr if 'ip' not in server or IPAddress(server['ip']).is_private() or IPAddress(server['ip']).is_loopback():
server['ip'] = request.remote_addr
if 'version' not in server:
server['version'] = 'Unknown'
instance = InstanceSchema().load(request.json) instance = InstanceSchema().load(request.json)
except ValidationError as err: except ValidationError as err:
return {'message' : err.messages }, 400 return {'message' : err.messages }, 400
@ -34,7 +38,10 @@ class Instance(Resource):
def post(self): def post(self):
try: try:
for server in request.json['servers']: for server in request.json['servers']:
server['ip'] = request.remote_addr if 'ip' not in server or server['ip'] == 'localhost':
server['ip'] = request.remote_addr
if 'version' not in server:
server['version'] = 'Unknown'
instance = InstanceSchema().load(request.json) instance = InstanceSchema().load(request.json)
except ValidationError as err: except ValidationError as err:
return {'message' : err.messages }, 400 return {'message' : err.messages }, 400

View File

@ -4,7 +4,7 @@ from master.models.servermodel import ServerModel
class ServerSchema(Schema): class ServerSchema(Schema):
id = fields.Int( id = fields.Int(
required=True, required=True,
validate=validate.Range(1, 2147483647, 'invalid id') validate=validate.Range(0, 25525525525565535, 'invalid id')
) )
ip = fields.Str( ip = fields.Str(
required=True required=True
@ -13,6 +13,10 @@ class ServerSchema(Schema):
required=True, required=True,
validate=validate.Range(1, 65535, 'invalid port') validate=validate.Range(1, 65535, 'invalid port')
) )
version = fields.String(
required=False,
validate=validate.Length(0, 128, 'invalid server version')
)
game = fields.String( game = fields.String(
required=True, required=True,
validate=validate.Length(1, 5, 'invalid game name') validate=validate.Length(1, 5, 'invalid game name')
@ -31,7 +35,7 @@ class ServerSchema(Schema):
) )
map = fields.String( map = fields.String(
required=True, required=True,
validate=validate.Length(1, 32, 'invalid map name') validate=validate.Length(0, 64, 'invalid map name')
) )
gametype = fields.String( gametype = fields.String(
required=True, required=True,

View File

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

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,24 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace AutomessageFeed
{
class Configuration : IBaseConfiguration
{
public bool EnableFeed { get; set; }
public string FeedUrl { get; set; }
public IBaseConfiguration Generate()
{
EnableFeed = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_PROMPT_ENABLE"]);
FeedUrl = Utilities.PromptString(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_URL"]);
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<object> 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)
{
_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,197 +1,200 @@
//using SharedLibraryCore; using SharedLibraryCore;
//using SharedLibraryCore.Objects; using SharedLibraryCore.Database.Models;
//using System; using SharedLibraryCore.Objects;
//using System.Collections.Generic; using System;
//using System.Linq; using System.Collections.Generic;
//using System.Text; using System.Linq;
//using System.Threading.Tasks; using System.Text;
using System.Threading.Tasks;
//namespace IW4ScriptCommands.Commands namespace IW4ScriptCommands.Commands
//{ {
// class Balance : Command class Balance
// { {
// private class TeamAssignment private class TeamAssignment
// { {
// public IW4MAdmin.Plugins.Stats.IW4Info.Team CurrentTeam { get; set; } public IW4MAdmin.Plugins.Stats.IW4Info.Team CurrentTeam { get; set; }
// public int Num { get; set; } public int Num { get; set; }
// public IW4MAdmin.Plugins.Stats.Models.EFClientStatistics Stats { get; set; } public IW4MAdmin.Plugins.Stats.Models.EFClientStatistics Stats { get; set; }
// } }
// public Balance() : base("balance", "balance teams", "bal", Player.Permission.Trusted, false, null)
// {
// }
// public override async Task ExecuteAsync(GameEvent E) public static string GetTeamAssignments(EFClient client, bool isDisconnect, Server server, string teamsString = "")
// { {
// string teamsString = (await E.Owner.GetDvarAsync<string>("sv_iw4madmin_teams")).Value; var scriptClientTeams = teamsString.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Split(','))
.Select(c => new TeamAssignment()
{
CurrentTeam = (IW4MAdmin.Plugins.Stats.IW4Info.Team)Enum.Parse(typeof(IW4MAdmin.Plugins.Stats.IW4Info.Team), c[1]),
Num = server.GetClientsAsList().FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0]))?.ClientNumber ?? -1,
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(server.Clients.FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0])).ClientId, server.EndPoint)
})
.ToList();
// var scriptClientTeams = teamsString.Split(';', StringSplitOptions.RemoveEmptyEntries) // at least one team is full so we can't balance
// .Select(c => c.Split(',')) if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(server.MaxClients / 2.0)
// .Select(c => new TeamAssignment() || scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(server.MaxClients / 2.0))
// { {
// CurrentTeam = (IW4MAdmin.Plugins.Stats.IW4Info.Team)Enum.Parse(typeof(IW4MAdmin.Plugins.Stats.IW4Info.Team), c[1]), // E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
// Num = E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong())?.ClientNumber ?? -1, return string.Empty;
// Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong()).ClientId, E.Owner.GetHashCode()) }
// })
// .ToList();
// // at least one team is full so we can't balance List<string> teamAssignments = new List<string>();
// if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(E.Owner.MaxClients / 2.0)
// || scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(E.Owner.MaxClients / 2.0))
// {
// await E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
// return;
// }
// List<string> teamAssignments = new List<string>(); var _c = server.GetClientsAsList();
if (isDisconnect && client != null)
{
_c = _c.Where(c => c.ClientNumber != client.ClientNumber).ToList();
}
// var activeClients = E.Owner.GetPlayersAsList().Select(c => new TeamAssignment() var activeClients = _c.Select(c => new TeamAssignment()
// { {
// Num = c.ClientNumber, Num = c.ClientNumber,
// Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()), Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.EndPoint),
// CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()).Team CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.EndPoint).Team
// }) })
// .Where(c => scriptClientTeams.FirstOrDefault(sc => sc.Num == c.Num)?.CurrentTeam != IW4MAdmin.Plugins.Stats.IW4Info.Team.Spectator) .Where(c => scriptClientTeams.FirstOrDefault(sc => sc.Num == c.Num)?.CurrentTeam != IW4MAdmin.Plugins.Stats.IW4Info.Team.Spectator)
// .Where(c => c.CurrentTeam != scriptClientTeams.FirstOrDefault(p => p.Num == c.Num)?.CurrentTeam) .Where(c => c.CurrentTeam != scriptClientTeams.FirstOrDefault(p => p.Num == c.Num)?.CurrentTeam)
// .OrderByDescending(c => c.Stats.Performance) .OrderByDescending(c => c.Stats.Performance)
// .ToList(); .ToList();
// var alliesTeam = scriptClientTeams var alliesTeam = scriptClientTeams
// .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies)
// .Where(c => activeClients.Count(t => t.Num == c.Num) == 0) .Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
// .ToList(); .ToList();
// var axisTeam = scriptClientTeams var axisTeam = scriptClientTeams
// .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis)
// .Where(c => activeClients.Count(t => t.Num == c.Num) == 0) .Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
// .ToList(); .ToList();
// while (activeClients.Count() > 0) while (activeClients.Count() > 0)
// { {
// int teamSizeDifference = alliesTeam.Count - axisTeam.Count; int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
// double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 - double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
// axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0; axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
// if (teamSizeDifference == 0) if (teamSizeDifference == 0)
// { {
// if (performanceDisparity == 0) if (performanceDisparity == 0)
// { {
// alliesTeam.Add(activeClients.First()); alliesTeam.Add(activeClients.First());
// activeClients.RemoveAt(0); activeClients.RemoveAt(0);
// } }
// else else
// { {
// if (performanceDisparity > 0) if (performanceDisparity > 0)
// { {
// axisTeam.Add(activeClients.First()); axisTeam.Add(activeClients.First());
// activeClients.RemoveAt(0); activeClients.RemoveAt(0);
// } }
// else else
// { {
// alliesTeam.Add(activeClients.First()); alliesTeam.Add(activeClients.First());
// activeClients.RemoveAt(0); activeClients.RemoveAt(0);
// } }
// } }
// } }
// else if (teamSizeDifference > 0) else if (teamSizeDifference > 0)
// { {
// if (performanceDisparity > 0) if (performanceDisparity > 0)
// { {
// axisTeam.Add(activeClients.First()); axisTeam.Add(activeClients.First());
// activeClients.RemoveAt(0); activeClients.RemoveAt(0);
// } }
// else else
// { {
// axisTeam.Add(activeClients.Last()); axisTeam.Add(activeClients.Last());
// activeClients.RemoveAt(activeClients.Count - 1); activeClients.RemoveAt(activeClients.Count - 1);
// } }
// } }
// else else
// { {
// if (performanceDisparity > 0) if (performanceDisparity > 0)
// { {
// alliesTeam.Add(activeClients.First()); alliesTeam.Add(activeClients.First());
// activeClients.RemoveAt(0); activeClients.RemoveAt(0);
// } }
// else else
// { {
// alliesTeam.Add(activeClients.Last()); alliesTeam.Add(activeClients.Last());
// activeClients.RemoveAt(activeClients.Count - 1); activeClients.RemoveAt(activeClients.Count - 1);
// } }
// } }
// } }
// alliesTeam = alliesTeam.OrderByDescending(t => t.Stats.Performance) alliesTeam = alliesTeam.OrderByDescending(t => t.Stats.Performance)
// .ToList(); .ToList();
// axisTeam = axisTeam.OrderByDescending(t => t.Stats.Performance) axisTeam = axisTeam.OrderByDescending(t => t.Stats.Performance)
// .ToList(); .ToList();
// while (Math.Abs(alliesTeam.Count - axisTeam.Count) > 1) while (Math.Abs(alliesTeam.Count - axisTeam.Count) > 1)
// { {
// int teamSizeDifference = alliesTeam.Count - axisTeam.Count; int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
// double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 - double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
// axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0; axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
// if (teamSizeDifference > 0) if (teamSizeDifference > 0)
// { {
// if (performanceDisparity > 0) if (performanceDisparity > 0)
// { {
// axisTeam.Add(alliesTeam.First()); axisTeam.Add(alliesTeam.First());
// alliesTeam.RemoveAt(0); alliesTeam.RemoveAt(0);
// } }
// else else
// { {
// axisTeam.Add(alliesTeam.Last()); axisTeam.Add(alliesTeam.Last());
// alliesTeam.RemoveAt(axisTeam.Count - 1); alliesTeam.RemoveAt(axisTeam.Count - 1);
// } }
// } }
// else else
// { {
// if (performanceDisparity > 0) if (performanceDisparity > 0)
// { {
// alliesTeam.Add(axisTeam.Last()); alliesTeam.Add(axisTeam.Last());
// axisTeam.RemoveAt(axisTeam.Count - 1); axisTeam.RemoveAt(axisTeam.Count - 1);
// } }
// else else
// { {
// alliesTeam.Add(axisTeam.First()); alliesTeam.Add(axisTeam.First());
// axisTeam.RemoveAt(0); axisTeam.RemoveAt(0);
// } }
// } }
// } }
// foreach (var assignment in alliesTeam) foreach (var assignment in alliesTeam)
// { {
// teamAssignments.Add($"{assignment.Num},2"); teamAssignments.Add($"{assignment.Num},2");
// assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies; assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies;
// } }
// foreach (var assignment in axisTeam)
// {
// teamAssignments.Add($"{assignment.Num},3");
// assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis;
// }
// if (alliesTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0 && foreach (var assignment in axisTeam)
// axisTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0) {
// { teamAssignments.Add($"{assignment.Num},3");
// await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]); assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis;
// return; }
// }
// if (E.Origin?.Level > Player.Permission.Administrator) //if (alliesTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0 &&
// { // axisTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0)
// await E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}"); //{
// await E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}"); // //E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
// } // return string.Empty;
//}
// string args = string.Join(",", teamAssignments); //if (E.Origin?.Level > Player.Permission.Administrator)
// await E.Owner.ExecuteCommandAsync($"sv_iw4madmin_command \"balance:{args}\""); //{
// await E.Origin.Tell("Balance command sent"); // E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}");
// } // E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}");
// } //}
//}
//E.Origin.Tell("Balance command sent");
string args = string.Join(",", teamAssignments);
return args;
}
}
}

View File

@ -0,0 +1,54 @@
using IW4ScriptCommands.Commands;
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WebfrontCore.Controllers.API
{
[Route("api/gsc/[action]")]
public class GscApiController : ApiController
{
[HttpGet("{networkId}")]
public IActionResult ClientInfo(string networkId)
{
var clientInfo = Manager.GetActiveClients()
.FirstOrDefault(c => c.NetworkId == networkId.ConvertLong());
if (clientInfo != null)
{
var sb = new StringBuilder();
sb.AppendLine($"admin={clientInfo.IsPrivileged()}");
sb.AppendLine($"level={(int)clientInfo.Level}");
sb.AppendLine($"levelstring={clientInfo.Level.ToLocalizedLevelName()}");
sb.AppendLine($"connections={clientInfo.Connections}");
sb.AppendLine($"authenticated={clientInfo.GetAdditionalProperty<bool>("IsLoggedIn") == true}");
return Content(sb.ToString());
}
return Content("");
}
[HttpGet("{networkId}")]
public IActionResult GetTeamAssignments(string networkId, int serverId, string teams = "", bool isDisconnect = false)
{
return Unauthorized();
var client = Manager.GetActiveClients()
.FirstOrDefault(c => c.NetworkId == networkId.ConvertLong());
var server = Manager.GetServers().First(c => c.EndPoint == serverId);
teams = teams ?? string.Empty;
string assignments = Balance.GetTeamAssignments(client, isDisconnect, server, teams);
return Content(assignments);
}
}
}

View File

@ -2,10 +2,11 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup> </PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">
@ -18,7 +19,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" /> <PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -17,15 +17,10 @@ namespace IW4ScriptCommands
public Task OnEventAsync(GameEvent E, Server S) public Task OnEventAsync(GameEvent E, Server S)
{ {
//if (E.Type == GameEvent.EventType.JoinTeam || E.Type == GameEvent.EventType.Disconnect) if (E.Type == GameEvent.EventType.Start)
//{ {
// E.Origin = new SharedLibraryCore.Objects.Player() return S.SetDvarAsync("sv_iw4madmin_serverid", S.EndPoint);
// { }
// ClientId = 1,
// CurrentServer = E.Owner
// };
// return new Commands.Balance().ExecuteAsync(E);
//}
if (E.Type == GameEvent.EventType.Warn) if (E.Type == GameEvent.EventType.Warn)
{ {

View File

@ -1,14 +1,12 @@
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Objects; using SharedLibraryCore.Database.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Login.Commands namespace IW4MAdmin.Plugins.Login.Commands
{ {
public class CLogin : Command public class CLogin : Command
{ {
public CLogin() : base("login", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_DESC"], "li", Player.Permission.Trusted, false, new CommandArgument[] public CLogin() : base("login", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_DESC"], "li", EFClient.Permission.Trusted, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -21,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];
string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, client.PasswordSalt)); bool success = E.Owner.Manager.TokenAuthenticator.AuthorizeToken(E.Origin.NetworkId, E.Data);
if (hashedPassword[0] == client.Password) if (!success)
{ {
Plugin.AuthorizedClients[E.Origin.ClientId] = true; string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, client.PasswordSalt));
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]);
if (hashedPassword[0] == client.Password)
{
success = true;
Plugin.AuthorizedClients[E.Origin.ClientId] = true;
}
} }
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

@ -2,8 +2,8 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.Login</PackageId> <PackageId>RaidMax.IW4MAdmin.Plugins.Login</PackageId>
@ -22,7 +22,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" /> <PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -3,9 +3,9 @@ using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions; using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Plugins.Login namespace IW4MAdmin.Plugins.Login
{ {
@ -22,12 +22,13 @@ namespace IW4MAdmin.Plugins.Login
public Task OnEventAsync(GameEvent E, Server S) public Task OnEventAsync(GameEvent E, Server S)
{ {
if (E.Remote || Config.RequirePrivilegedClientLogin == false) if (E.IsRemote || Config.RequirePrivilegedClientLogin == false)
return Task.CompletedTask; return Task.CompletedTask;
if (E.Type == GameEvent.EventType.Connect) if (E.Type == GameEvent.EventType.Connect)
{ {
AuthorizedClients.TryAdd(E.Origin.ClientId, false); AuthorizedClients.TryAdd(E.Origin.ClientId, false);
E.Origin.SetAdditionalProperty("IsLoggedIn", false);
} }
if (E.Type == GameEvent.EventType.Disconnect) if (E.Type == GameEvent.EventType.Disconnect)
@ -37,11 +38,11 @@ namespace IW4MAdmin.Plugins.Login
if (E.Type == GameEvent.EventType.Command) if (E.Type == GameEvent.EventType.Command)
{ {
if (E.Origin.Level < Player.Permission.Moderator || if (E.Origin.Level < EFClient.Permission.Moderator ||
E.Origin.Level == Player.Permission.Console) E.Origin.Level == EFClient.Permission.Console)
return Task.CompletedTask; return Task.CompletedTask;
E.Owner.Manager.GetPrivilegedClients().TryGetValue(E.Origin.ClientId, out Player client); E.Owner.Manager.GetPrivilegedClients().TryGetValue(E.Origin.ClientId, out EFClient client);
if (((Command)E.Extra).Name == new SharedLibraryCore.Commands.CSetPassword().Name && if (((Command)E.Extra).Name == new SharedLibraryCore.Commands.CSetPassword().Name &&
client?.Password == null) client?.Password == null)
@ -51,7 +52,14 @@ namespace IW4MAdmin.Plugins.Login
return Task.CompletedTask; return Task.CompletedTask;
if (!AuthorizedClients[E.Origin.ClientId]) if (!AuthorizedClients[E.Origin.ClientId])
{
throw new AuthorizationException(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_AUTH"]); throw new AuthorizationException(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_AUTH"]);
}
else
{
E.Origin.SetAdditionalProperty("IsLoggedIn", true);
}
} }
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -5,6 +5,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
@ -48,7 +49,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
if (containsObjectionalWord) if (containsObjectionalWord)
{ {
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player() E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new EFClient()
{ {
ClientId = 1, ClientId = 1,
CurrentServer = E.Owner CurrentServer = E.Owner
@ -85,22 +86,14 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
var clientProfanity = ProfanityCounts[E.Origin.ClientId]; var clientProfanity = ProfanityCounts[E.Origin.ClientId];
if (clientProfanity.Infringements >= Settings.Configuration().KickAfterInfringementCount) if (clientProfanity.Infringements >= Settings.Configuration().KickAfterInfringementCount)
{ {
clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, new Player() clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, Utilities.IW4MAdminClient(E.Owner));
{
ClientId = 1,
CurrentServer = E.Owner
});
} }
else if (clientProfanity.Infringements < Settings.Configuration().KickAfterInfringementCount) else if (clientProfanity.Infringements < Settings.Configuration().KickAfterInfringementCount)
{ {
clientProfanity.Infringements++; clientProfanity.Infringements++;
clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, new Player() clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, Utilities.IW4MAdminClient(E.Owner));
{
ClientId = 1,
CurrentServer = E.Owner
});
} }
} }
} }

View File

@ -2,8 +2,8 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId> <PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId>
@ -20,7 +20,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" /> <PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -1,16 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
namespace IW4MAdmin.Plugins.ProfanityDeterment namespace IW4MAdmin.Plugins.ProfanityDeterment
{ {
class Tracking class Tracking
{ {
public Player Client { get; private set; } public EFClient Client { get; private set; }
public int Infringements { get; set; } public int Infringements { get; set; }
public Tracking(Player client) public Tracking(EFClient client)
{ {
Client = client; Client = client;
Infringements = 0; Infringements = 0;

View File

@ -0,0 +1,37 @@
var rconParser;
var eventParser;
var plugin = {
author: 'FrenchFry, RaidMax',
version: 0.2,
name: 'CoD4x Parser',
isParser: true,
onEventAsync: function (gameEvent, server) {
},
onLoadAsync: function (manager) {
rconParser = manager.GenerateDynamicRConParser();
eventParser = manager.GenerateDynamicEventParser();
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|(?:[a-z]|[0-9]){32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$'
rconParser.Configuration.Status.AddMapping(104, 6); // RConName
rconParser.Configuration.Status.AddMapping(105, 8); // RConIPAddress
rconParser.Configuration.Dvar.Pattern = '^"(.+)" is: "(.+)?" default: "(.+)?" info: "(.+)?"$';
rconParser.Configuration.Dvar.AddMapping(109, 2); // DVAR latched value
rconParser.Configuration.Dvar.AddMapping(110, 4); // dvar info
rconParser.Version = 'CoD4 X 1.8 win_mingw-x86 build 2055 May 2 2017';
rconParser.GameName = 1; // IW3
eventParser.Configuration.GameDirectory = 'main';
eventParser.Version = 'CoD4 X 1.8 win_mingw-x86 build 2055 May 2 2017';
eventParser.GameName = 1; // IW3
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -0,0 +1,35 @@
var rconParser;
var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.2,
name: 'IW3 Parser',
isParser: true,
onEventAsync: function (gameEvent, server) {
},
onLoadAsync: function (manager) {
rconParser = manager.GenerateDynamicRConParser();
eventParser = manager.GenerateDynamicEventParser();
rconParser.Configuration.CommandPrefixes.Tell = 'tellraw {0} {1}';
rconParser.Configuration.CommandPrefixes.Say = 'sayraw {0}';
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0} "{1}"';
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0} "{1}"';
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0} "{1}"';
eventParser.Configuration.GameDirectory = 'userraw';
rconParser.Version = 'IW4x (v0.6.0)';
rconParser.GameName = 2; // IW4x
eventParser.Version = 'IW4x (v0.6.0)';
eventParser.GameName = 2; // IW4x
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -0,0 +1,50 @@
var rconParser;
var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.2,
name: 'Plutoniun T6 Parser',
isParser: true,
onEventAsync: function (gameEvent, server) {
},
onLoadAsync: function (manager) {
rconParser = manager.GenerateDynamicRConParser();
eventParser = manager.GenerateDynamicEventParser();
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick_for_reason {0} "{1}"';
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick_for_reason {0} "{1}"';
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick_for_reason {0} "{1}"';
rconParser.Configuration.CommandPrefixes.RConGetDvar = '\xff\xff\xff\xffrcon {0} get {1}';
rconParser.Configuration.Dvar.Pattern = '^(.+) is "(.+)?"$';
rconParser.Configuration.Dvar.AddMapping(106, 1);
rconParser.Configuration.Dvar.AddMapping(107, 2);
rconParser.Configuration.WaitForResponse = false;
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(.+) +((?:[A-Z]+|[0-9]+)) +((?:[A-Z]|[0-9]){8,16}) +(.{0,16}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(-?[0-9]+) +([0-9]+) *$'
rconParser.Configuration.Status.AddMapping(100, 1);
rconParser.Configuration.Status.AddMapping(101, 2);
rconParser.Configuration.Status.AddMapping(102, 4);
rconParser.Configuration.Status.AddMapping(103, 5);
rconParser.Configuration.Status.AddMapping(104, 6);
rconParser.Configuration.Status.AddMapping(105, 8);
eventParser.Configuration.GameDirectory = 't6r\\data';
rconParser.Version = 'Call of Duty Multiplayer - Ship COD_T6_S MP build 1.0.44 CL(1759941) CODPCAB2 CEG Fri May 9 19:19:19 2014 win-x86 813e66d5';
rconParser.GameName = 7; // T6
eventParser.Version = 'Call of Duty Multiplayer - Ship COD_T6_S MP build 1.0.44 CL(1759941) CODPCAB2 CEG Fri May 9 19:19:19 2014 win-x86 813e66d5';
eventParser.GameName = 7; // T6
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -0,0 +1,43 @@
var rconParser;
var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.2,
name: 'Tekno MW3 Parser',
isParser: true,
onEventAsync: function (gameEvent, server) {
},
onLoadAsync: function (manager) {
rconParser = manager.GenerateDynamicRConParser();
eventParser = manager.GenerateDynamicEventParser();
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[A-Z]|[0-9]){16,32})\t +(.{0,16}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+\\:-?\\d{1,5}|loopback) *$';
rconParser.Configuration.Status.AddMapping(104, 5); // RConName
rconParser.Configuration.Status.AddMapping(103, 4); // RConNetworkId
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff';
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
rconParser.Configuration.CommandPrefixes.Kick = 'dropclient {0} "{1}"';
rconParser.Configuration.CommandPrefixes.Ban = 'dropclient {0} "{1}"';
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0} "{1}"';
rconParser.Configuration.Dvar.AddMapping(107, 1); // RCon DvarValue
rconParser.Configuration.Dvar.Pattern = '^(.*)$';
rconParser.Version = 'IW5 MP 1.4 build 382 latest Thu Jan 19 2012 11:09:49AM win-x86';
rconParser.GameName = 3; // IW5
rconParser.CanGenerateLogPath = false;
eventParser.Configuration.GameDirectory = 'scripts';
eventParser.Version = 'IW5 MP 1.4 build 382 latest Thu Jan 19 2012 11:09:49AM win-x86';
eventParser.GameName = 3; // IW5
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -1,7 +1,7 @@
using SharedLibraryCore.Helpers; using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -20,7 +20,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
}; };
public ChangeTracking<EFACSnapshot> Tracker { get; private set; } public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
public const int QUEUE_COUNT = 10;
public List<EFClientKill> QueuedHits { get; set; }
int Kills; int Kills;
int HitCount; int HitCount;
Dictionary<IW4Info.HitLocation, int> HitLocationCount; Dictionary<IW4Info.HitLocation, int> HitLocationCount;
@ -37,49 +39,55 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Log = log; Log = log;
HitLocationCount = new Dictionary<IW4Info.HitLocation, int>(); HitLocationCount = new Dictionary<IW4Info.HitLocation, int>();
foreach (var loc in Enum.GetValues(typeof(IW4Info.HitLocation))) foreach (var loc in Enum.GetValues(typeof(IW4Info.HitLocation)))
{
HitLocationCount.Add((IW4Info.HitLocation)loc, 0); HitLocationCount.Add((IW4Info.HitLocation)loc, 0);
}
ClientStats = clientStats; ClientStats = clientStats;
Strain = new Strain(); Strain = new Strain();
Tracker = new ChangeTracking<EFACSnapshot>(); Tracker = new ChangeTracking<EFACSnapshot>();
QueuedHits = new List<EFClientKill>();
} }
/// <summary> /// <summary>
/// Analyze kill and see if performed by a cheater /// Analyze kill and see if performed by a cheater
/// </summary> /// </summary>
/// <param name="kill">kill performed by the player</param> /// <param name="hit">kill performed by the player</param>
/// <returns>true if detection reached thresholds, false otherwise</returns> /// <returns>true if detection reached thresholds, false otherwise</returns>
public DetectionPenaltyResult ProcessKill(EFClientKill kill, bool isDamage) public DetectionPenaltyResult ProcessHit(EFClientKill hit, bool isDamage)
{ {
if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && if ((hit.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET && hit.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) || hit.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
kill.HitLoc == IW4Info.HitLocation.none || kill.TimeOffset - LastOffset < 0 || hit.HitLoc == IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 ||
// hack: prevents false positives // hack: prevents false positives
(LastWeapon != kill.Weapon && (kill.TimeOffset - LastOffset) == 50)) (LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50))
{
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Any, ClientPenalty = Penalty.PenaltyType.Any,
}; };
}
DetectionPenaltyResult result = null; DetectionPenaltyResult result = null;
LastWeapon = kill.Weapon; LastWeapon = hit.Weapon;
HitLocationCount[hit.HitLoc]++;
HitCount++;
HitLocationCount[kill.HitLoc]++;
if (!isDamage) if (!isDamage)
{ {
Kills++; Kills++;
} }
HitCount++;
#region VIEWANGLES #region VIEWANGLES
if (kill.AnglesList.Count >= 2) if (hit.AnglesList.Count >= 2)
{ {
double realAgainstPredict = Vector3.ViewAngleDistance(kill.AnglesList[0], kill.AnglesList[1], kill.ViewAngles); double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles);
// LIFETIME // LIFETIME
var hitLoc = ClientStats.HitLocations var hitLoc = ClientStats.HitLocations
.First(hl => hl.Location == kill.HitLoc); .First(hl => hl.Location == hit.HitLoc);
float previousAverage = hitLoc.HitOffsetAverage; float previousAverage = hitLoc.HitOffsetAverage;
double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount; double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount;
@ -88,11 +96,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset(hitLoc.HitCount) && if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset(hitLoc.HitCount) &&
hitLoc.HitCount > 100) hitLoc.HitCount > 100)
{ {
Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***"); //Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
Log.WriteDebug($"Lifetime Average = {newAverage}"); //Log.WriteDebug($"Lifetime Average = {newAverage}");
Log.WriteDebug($"Bone = {hitLoc.Location}"); //Log.WriteDebug($"Bone = {hitLoc.Location}");
Log.WriteDebug($"HitCount = {hitLoc.HitCount}"); //Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
Log.WriteDebug($"ID = {kill.AttackerId}"); //Log.WriteDebug($"ID = {hit.AttackerId}");
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -110,10 +118,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (sessAverage > Thresholds.MaxOffset(HitCount) && if (sessAverage > Thresholds.MaxOffset(HitCount) &&
HitCount > 30) HitCount > 30)
{ {
Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***"); //Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
Log.WriteDebug($"Session Average = {sessAverage}"); //Log.WriteDebug($"Session Average = {sessAverage}");
Log.WriteDebug($"HitCount = {HitCount}"); //Log.WriteDebug($"HitCount = {HitCount}");
Log.WriteDebug($"ID = {kill.AttackerId}"); //Log.WriteDebug($"ID = {hit.AttackerId}");
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -130,8 +138,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
#endif #endif
} }
double currentStrain = Strain.GetStrain(isDamage, kill.Damage, kill.Distance / 0.0254, kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset)); double currentStrain = Strain.GetStrain(isDamage, hit.Damage, hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, hit.TimeOffset - LastOffset));
LastOffset = kill.TimeOffset; #if DEBUG == true
Log.WriteDebug($"Current Strain: {currentStrain}");
#endif
LastOffset = hit.TimeOffset;
if (currentStrain > ClientStats.MaxStrain) if (currentStrain > ClientStats.MaxStrain)
{ {
@ -139,8 +150,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
// flag // flag
if (currentStrain > Thresholds.MaxStrainFlag && if (currentStrain > Thresholds.MaxStrainFlag)
HitCount >= 10)
{ {
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -153,7 +163,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
// ban // ban
if (currentStrain > Thresholds.MaxStrainBan && if (currentStrain > Thresholds.MaxStrainBan &&
HitCount >= 15) HitCount >= 5)
{ {
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -163,11 +173,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Type = DetectionType.Strain Type = DetectionType.Strain
}; };
} }
#if DEBUG
Log.WriteDebug($"Current Strain: {currentStrain}");
#endif
#endregion #endregion
#region SESSION_RATIOS #region SESSION_RATIOS
@ -197,13 +202,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentHeadshotRatio > maxHeadshotLerpValueForBan) if (currentHeadshotRatio > maxHeadshotLerpValueForBan)
{ {
Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**"); Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
@ -218,13 +226,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
else else
{ {
Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**"); Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
@ -247,13 +258,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan) if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
{ {
Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**"); Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
@ -268,13 +282,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
else else
{ {
Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**"); Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
@ -308,15 +325,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills + 30) if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills + 30)
{ {
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**"); //Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); //Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**Chest Hits: {chestHits}"); //Log.WriteDebug($"**Chest Hits: {chestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); //Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); //Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder(); //var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) //foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); // sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); //Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -329,16 +346,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
else else
{ {
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**"); //Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); //Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**Chest Hits: {chestHits}"); //Log.WriteDebug($"**Chest Hits: {chestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); //Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); //Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder(); //var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) //foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); // sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); //Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -356,31 +372,31 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Tracker.OnChange(new EFACSnapshot() Tracker.OnChange(new EFACSnapshot()
{ {
When = kill.When, When = hit.When,
ClientId = ClientStats.ClientId, ClientId = ClientStats.ClientId,
SessionAngleOffset = AngleDifferenceAverage, SessionAngleOffset = AngleDifferenceAverage,
CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds, CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds,
CurrentStrain = currentStrain, CurrentStrain = currentStrain,
CurrentViewAngle = kill.ViewAngles, CurrentViewAngle = hit.ViewAngles,
Hits = HitCount, Hits = HitCount,
Kills = Kills, Kills = Kills,
Deaths = ClientStats.SessionDeaths, Deaths = ClientStats.SessionDeaths,
HitDestinationId = kill.DeathOrigin.Vector3Id, HitDestinationId = hit.DeathOrigin.Vector3Id,
HitDestination = kill.DeathOrigin, HitDestination = hit.DeathOrigin,
HitOriginId = kill.KillOrigin.Vector3Id, HitOriginId = hit.KillOrigin.Vector3Id,
HitOrigin = kill.KillOrigin, HitOrigin = hit.KillOrigin,
EloRating = ClientStats.EloRating, EloRating = ClientStats.EloRating,
HitLocation = kill.HitLoc, HitLocation = hit.HitLoc,
LastStrainAngle = Strain.LastAngle, LastStrainAngle = Strain.LastAngle,
PredictedViewAngles = kill.AnglesList, PredictedViewAngles = hit.AnglesList,
// this is in "meters" // this is in "meters"
Distance = kill.Distance, Distance = hit.Distance,
SessionScore = ClientStats.SessionScore, SessionScore = ClientStats.SessionScore,
HitType = kill.DeathType, HitType = hit.DeathType,
SessionSPM = ClientStats.SessionSPM, SessionSPM = ClientStats.SessionSPM,
StrainAngleBetween = Strain.LastDistance, StrainAngleBetween = Strain.LastDistance,
TimeSinceLastEvent = (int)Strain.LastDeltaTime, TimeSinceLastEvent = (int)Strain.LastDeltaTime,
WeaponId = kill.Weapon WeaponId = hit.Weapon
}); });
return result ?? new DetectionPenaltyResult() return result ?? new DetectionPenaltyResult()
@ -409,15 +425,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan) if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan)
{ {
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**"); //Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {stats.ClientId}"); //Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Hits: {totalChestHits}"); //Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); //Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); //Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder(); //var sb = new StringBuilder();
foreach (var location in stats.HitLocations) //foreach (var location in stats.HitLocations)
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n"); // sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
Log.WriteDebug(sb.ToString()); //Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
@ -430,15 +446,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
else else
{ {
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**"); //Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {stats.ClientId}"); //Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Hits: {totalChestHits}"); //Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); //Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); //Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder(); //var sb = new StringBuilder();
foreach (var location in stats.HitLocations) //foreach (var location in stats.HitLocations)
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n"); // sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
Log.WriteDebug(sb.ToString()); //Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {

View File

@ -14,8 +14,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public Vector3 LastAngle { get; private set; } public Vector3 LastAngle { get; private set; }
public double LastDeltaTime { get; private set; } public double LastDeltaTime { get; private set; }
public int TimesReachedMaxStrain { get; private set; }
public double GetStrain(bool isDamage, int damage, double killDistance, Vector3 newAngle, double deltaTime) public double GetStrain(bool isDamage, int damage, double killDistance, Vector3 newAngle, double deltaTime)
{ {
if (LastAngle == null) if (LastAngle == null)
@ -25,14 +23,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
double decayFactor = GetDecay(deltaTime); double decayFactor = GetDecay(deltaTime);
CurrentStrain *= decayFactor; CurrentStrain *= decayFactor;
#if DEBUG
Console.WriteLine($"Decay Factor = {decayFactor} ");
#endif
double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle); double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle);
LastDistance = distance[0] + distance[1]; LastDistance = distance[0] + distance[1];
#if DEBUG == true
Console.WriteLine($"Angle Between = {LastDistance}");
Console.WriteLine($"Distance From Target = {killDistance}");
Console.WriteLine($"Time Offset = {deltaTime}");
Console.WriteLine($"Decay Factor = {decayFactor} ");
#endif
// this happens on first kill // this happens on first kill
if ((distance[0] == 0 && distance[1] == 0) || if ((distance[0] == 0 && distance[1] == 0) ||
deltaTime == 0 || deltaTime == 0 ||
@ -41,14 +40,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return CurrentStrain; return CurrentStrain;
} }
double newStrain = Math.Pow(distance[0] + distance[1], 0.99) / deltaTime; double newStrain = Math.Pow(LastDistance, 0.99) / deltaTime;
newStrain *= killDistance / 1000.0; newStrain *= killDistance / 1000.0;
CurrentStrain += newStrain; CurrentStrain += newStrain;
if (CurrentStrain > Thresholds.MaxStrainBan)
TimesReachedMaxStrain++;
LastAngle = newAngle; LastAngle = newAngle;
return CurrentStrain; return CurrentStrain;
} }

View File

@ -27,9 +27,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public const int HighSampleMinKills = 100; public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2; public const double KillTimeThreshold = 0.2;
public const double MaxStrainBan = 1.12; public const double MaxStrainBan = 0.9;
//=exp((MAX(-3.07+(-3.07/sqrt(J20)),-3.07-(-3.07/sqrt(J20))))+(4*(0.869)))
public static double MaxOffset(int sampleSize) => Math.Exp(Math.Max(-3.07 + (-3.07 / Math.Sqrt(sampleSize)), -3.07 - (-3.07 / Math.Sqrt(sampleSize))) + 4 * (0.869)); public static double MaxOffset(int sampleSize) => Math.Exp(Math.Max(-3.07 + (-3.07 / Math.Sqrt(sampleSize)), -3.07 - (-3.07 / Math.Sqrt(sampleSize))) + 4 * (0.869));
public const double MaxStrainFlag = 0.36; public const double MaxStrainFlag = 0.36;

View File

@ -8,6 +8,8 @@ using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models; using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using System.Collections.Generic; using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
using IW4MAdmin.Plugins.Stats.Helpers;
namespace IW4MAdmin.Plugins.Stats.Commands namespace IW4MAdmin.Plugins.Stats.Commands
{ {
@ -15,7 +17,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands
{ {
public static async Task<List<string>> GetMostPlayed(Server s) public static async Task<List<string>> GetMostPlayed(Server s)
{ {
int serverId = s.GetHashCode(); long serverId = await StatManager.GetIdForServer(s);
List<string> mostPlayed = new List<string>() List<string> mostPlayed = new List<string>()
{ {
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--" $"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
@ -34,7 +37,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
join alias in db.Aliases join alias in db.Aliases
on client.CurrentAliasId equals alias.AliasId on client.CurrentAliasId equals alias.AliasId
where stats.ServerId == serverId where stats.ServerId == serverId
where client.Level != Player.Permission.Banned where client.Level != EFClient.Permission.Banned
where client.LastConnection >= thirtyDaysAgo where client.LastConnection >= thirtyDaysAgo
orderby stats.Kills descending orderby stats.Kills descending
select new select new
@ -55,7 +58,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
return mostPlayed; return mostPlayed;
} }
public MostPlayed() : base("mostplayed", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"], "mp", Player.Permission.User, false) { } public MostPlayed() : base("mostplayed", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"], "mp", EFClient.Permission.User, false) { }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {

View File

@ -1,32 +1,30 @@
using SharedLibraryCore; using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using System.Linq;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats.Commands namespace IW4MAdmin.Plugins.Stats.Commands
{ {
public class ResetStats : Command public class ResetStats : Command
{ {
public ResetStats() : base("resetstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_DESC"], "rs", Player.Permission.User, false) { } public ResetStats() : base("resetstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_DESC"], "rs", EFClient.Permission.User, false) { }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {
if (E.Origin.ClientNumber >= 0) if (E.Origin.ClientNumber >= 0)
{ {
int serverId = E.Owner.GetHashCode(); long serverId = await Helpers.StatManager.GetIdForServer(E.Owner);
EFClientStatistics clientStats; 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>()
.Where(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId) .Where(s => s.ClientId == E.Origin.ClientId)
.Where(s => s.ServerId == serverId)
.FirstAsync(); .FirstAsync();
clientStats.Deaths = 0; clientStats.Deaths = 0;
@ -38,7 +36,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
clientStats.EloRating = 200.0; clientStats.EloRating = 200.0;
// reset the cached version // reset the cached version
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode()); Plugin.Manager.ResetStats(E.Origin.ClientId, serverId);
// fixme: this doesn't work properly when another context exists // fixme: this doesn't work properly when another context exists
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();

View File

@ -9,15 +9,16 @@ using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Models; using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using System.Collections.Generic; using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
using IW4MAdmin.Plugins.Stats.Helpers;
namespace IW4MAdmin.Plugins.Stats.Commands namespace IW4MAdmin.Plugins.Stats.Commands
{ {
class TopStats : Command class TopStats : Command
{ {
public static async Task<List<string>> GetTopStats(Server s) public static async Task<List<string>> GetTopStats(Server s)
{ {
int serverId = s.GetHashCode(); long serverId = await StatManager.GetIdForServer(s);
List<string> topStatsText = new List<string>() List<string> topStatsText = new List<string>()
{ {
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--" $"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
@ -34,7 +35,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
on client.CurrentAliasId equals alias.AliasId on client.CurrentAliasId equals alias.AliasId
where stats.ServerId == serverId where stats.ServerId == serverId
where stats.TimePlayed >= Plugin.Config.Configuration().TopPlayersMinPlayTime where stats.TimePlayed >= Plugin.Config.Configuration().TopPlayersMinPlayTime
where client.Level != Player.Permission.Banned where client.Level != EFClient.Permission.Banned
where client.LastConnection >= fifteenDaysAgo where client.LastConnection >= fifteenDaysAgo
orderby stats.Performance descending orderby stats.Performance descending
select new select new
@ -67,7 +68,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
return topStatsText; return topStatsText;
} }
public TopStats() : base("topstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_DESC"], "ts", Player.Permission.User, false) { } public TopStats() : base("topstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_DESC"], "ts", EFClient.Permission.User, false) { }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {

View File

@ -9,12 +9,14 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using IW4MAdmin.Plugins.Stats.Helpers;
using SharedLibraryCore.Database.Models;
namespace IW4MAdmin.Plugins.Stats.Commands namespace IW4MAdmin.Plugins.Stats.Commands
{ {
public class CViewStats : Command public class CViewStats : Command
{ {
public CViewStats() : base("stats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_VIEW_DESC"], "xlrstats", Player.Permission.User, false, new CommandArgument[] public CViewStats() : base("stats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_VIEW_DESC"], "xlrstats", EFClient.Permission.User, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -41,20 +43,26 @@ namespace IW4MAdmin.Plugins.Stats.Commands
} }
} }
int serverId = E.Owner.GetHashCode(); long serverId = await StatManager.GetIdForServer(E.Owner);
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
if (E.Target != null) if (E.Target != null)
{ {
int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId)); pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId));
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}"; statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
} }
else else
{ {
int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync((c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId))); pStats = (await ctx.Set<EFClientStatistics>().FirstAsync((c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId)));
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}"; statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
} }
} }

View File

@ -1,53 +1,53 @@
using System; using IW4MAdmin.Plugins.Stats.Cheat;
using System.Collections.Concurrent; using IW4MAdmin.Plugins.Stats.Models;
using System.Collections.Generic; using IW4MAdmin.Plugins.Stats.Web.Dtos;
using System.Linq; using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using SharedLibraryCore.Commands; using System;
using IW4MAdmin.Plugins.Stats.Models; using System.Collections.Concurrent;
using System.Text.RegularExpressions; using System.Collections.Generic;
using IW4MAdmin.Plugins.Stats.Web.Dtos; using System.Linq;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Services;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats.Helpers namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
public class StatManager public class StatManager
{ {
private ConcurrentDictionary<int, ServerStats> Servers; private ConcurrentDictionary<long, ServerStats> Servers;
private ILogger Log; private ILogger Log;
private readonly IManager Manager; private readonly IManager Manager;
private readonly SemaphoreSlim OnProcessingPenalty; private readonly SemaphoreSlim OnProcessingPenalty;
private readonly SemaphoreSlim OnProcessingSensitive; private readonly SemaphoreSlim OnProcessingSensitive;
public StatManager(IManager mgr) public StatManager(IManager mgr)
{ {
Servers = new ConcurrentDictionary<int, ServerStats>(); Servers = new ConcurrentDictionary<long, ServerStats>();
Log = mgr.GetLogger(0); Log = mgr.GetLogger(0);
Manager = mgr; Manager = mgr;
OnProcessingPenalty = new SemaphoreSlim(1, 1); OnProcessingPenalty = new SemaphoreSlim(1, 1);
OnProcessingSensitive = new SemaphoreSlim(1, 1); OnProcessingSensitive = new SemaphoreSlim(1, 1);
} }
public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId]; public EFClientStatistics GetClientStats(int clientId, long serverId)
{
return Servers[serverId].PlayerStats[clientId];
}
public static Expression<Func<EFRating, bool>> GetRankingFunc(int? serverId = null) public static Expression<Func<EFRating, bool>> GetRankingFunc(long? serverId = null)
{ {
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15); var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
return (r) => r.ServerId == serverId && return (r) => r.ServerId == serverId &&
r.When > fifteenDaysAgo && r.When > fifteenDaysAgo &&
r.RatingHistory.Client.Level != Player.Permission.Banned && r.RatingHistory.Client.Level != EFClient.Permission.Banned &&
r.Newest && r.Newest &&
r.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime; r.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime;
} }
@ -57,7 +57,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary> /// </summary>
/// <param name="clientId">client id of the player</param> /// <param name="clientId">client id of the player</param>
/// <returns></returns> /// <returns></returns>
public async Task<int> GetClientOverallRanking(int clientId) public static async Task<int> GetClientOverallRanking(int clientId)
{ {
using (var context = new DatabaseContext(true)) using (var context = new DatabaseContext(true))
{ {
@ -191,21 +191,36 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// insert the server if it does not exist // insert the server if it does not exist
try try
{ {
int serverId = sv.GetHashCode(); long serverId = GetIdForServer(sv).Result;
EFServer server; EFServer server;
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
var serverSet = ctx.Set<EFServer>(); var serverSet = ctx.Set<EFServer>();
// get the server from the database if it exists, otherwise create and insert a new one // get the server from the database if it exists, otherwise create and insert a new one
server = serverSet.FirstOrDefault(c => c.ServerId == serverId); server = serverSet.FirstOrDefault(s => s.ServerId == serverId);
// the server might be using legacy server id
if (server == null)
{
server = serverSet.FirstOrDefault(s => s.EndPoint == sv.ToString());
if (server != null)
{
// this provides a way to identify legacy server entries
server.EndPoint = sv.ToString();
ctx.Update(server);
ctx.SaveChanges();
}
}
// server has never been added before
if (server == null) if (server == null)
{ {
server = new EFServer() server = new EFServer()
{ {
Port = sv.GetPort(), Port = sv.GetPort(),
Active = true, EndPoint = sv.ToString(),
ServerId = serverId ServerId = serverId
}; };
@ -216,7 +231,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
// check to see if the stats have ever been initialized // check to see if the stats have ever been initialized
var serverStats = InitializeServerStats(sv); var serverStats = InitializeServerStats(server.ServerId);
Servers.TryAdd(serverId, new ServerStats(server, serverStats) Servers.TryAdd(serverId, new ServerStats(server, serverStats)
{ {
@ -227,6 +242,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
catch (Exception e) catch (Exception e)
{ {
Log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}"); Log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}");
Log.WriteDebug(e.GetExceptionInfo());
} }
} }
@ -235,14 +251,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary> /// </summary>
/// <param name="pl">Player to add/retrieve stats for</param> /// <param name="pl">Player to add/retrieve stats for</param>
/// <returns>EFClientStatistic of specified player</returns> /// <returns>EFClientStatistic of specified player</returns>
public async Task<EFClientStatistics> AddPlayer(Player pl) public async Task<EFClientStatistics> AddPlayer(EFClient pl)
{ {
await OnProcessingSensitive.WaitAsync(); await OnProcessingSensitive.WaitAsync();
try try
{ {
int serverId = pl.CurrentServer.GetHashCode(); long serverId = await GetIdForServer(pl.CurrentServer);
if (!Servers.ContainsKey(serverId)) if (!Servers.ContainsKey(serverId))
{ {
@ -299,7 +314,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
Log.WriteWarning("Adding new client to stats failed"); Log.WriteWarning("Adding new client to stats failed");
} }
} }
else else
@ -348,7 +362,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Log.WriteWarning("Could not add client to detection"); Log.WriteWarning("Could not add client to detection");
} }
Log.WriteInfo($"Adding {pl} to stats"); pl.CurrentServer.Logger.WriteInfo($"Adding {pl} to stats");
} }
return clientStats; return clientStats;
@ -373,21 +387,21 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary> /// </summary>
/// <param name="pl">Disconnecting client</param> /// <param name="pl">Disconnecting client</param>
/// <returns></returns> /// <returns></returns>
public async Task RemovePlayer(Player pl) public async Task RemovePlayer(EFClient pl)
{ {
Log.WriteInfo($"Removing {pl} from stats"); pl.CurrentServer.Logger.WriteInfo($"Removing {pl} from stats");
int serverId = pl.CurrentServer.GetHashCode(); long serverId = await GetIdForServer(pl.CurrentServer);
var playerStats = Servers[serverId].PlayerStats; var playerStats = Servers[serverId].PlayerStats;
var detectionStats = Servers[serverId].PlayerDetections; var detectionStats = Servers[serverId].PlayerDetections;
var serverStats = Servers[serverId].ServerStatistics; var serverStats = Servers[serverId].ServerStatistics;
if (!playerStats.ContainsKey(pl.ClientId)) if (!playerStats.ContainsKey(pl.ClientId))
{ {
Log.WriteWarning($"Client disconnecting not in stats {pl}"); pl.CurrentServer.Logger.WriteWarning($"Client disconnecting not in stats {pl}");
// remove the client from the stats dictionary as they're leaving // remove the client from the stats dictionary as they're leaving
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue1); playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue1);
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2); detectionStats.TryRemove(pl.ClientId, out Detection removedValue2);
return; return;
} }
@ -396,7 +410,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// remove the client from the stats dictionary as they're leaving // remove the client from the stats dictionary as they're leaving
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3); playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3);
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4); detectionStats.TryRemove(pl.ClientId, out Detection removedValue4);
// sync their stats before they leave // sync their stats before they leave
clientStats = UpdateStats(clientStats); clientStats = UpdateStats(clientStats);
@ -408,31 +422,32 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
// increment the total play time // increment the total play time
serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds; serverStats.TotalPlayTime += pl.ConnectionLength;
} }
public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, int 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>
/// Process stats for kill event /// Process stats for kill event
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public async Task AddScriptHit(bool isDamage, DateTime time, Player attacker, Player victim, int serverId, string map, string hitLoc, string type, public async Task AddScriptHit(bool isDamage, DateTime time, EFClient attacker, EFClient victim, long serverId, string map, string hitLoc, string type,
string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads, string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads,
string fraction, string visibilityPercentage, string snapAngles) string fraction, string visibilityPercentage, string snapAngles)
{ {
@ -548,7 +563,22 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (Plugin.Config.Configuration().EnableAntiCheat) if (Plugin.Config.Configuration().EnableAntiCheat)
{ {
ApplyPenalty(clientDetection.ProcessKill(hit, isDamage), clientDetection, attacker, ctx); if (clientDetection.QueuedHits.Count > Detection.QUEUE_COUNT)
{
while (clientDetection.QueuedHits.Count > 0)
{
clientDetection.QueuedHits = clientDetection.QueuedHits.OrderBy(_hits => _hits.TimeOffset).ToList();
var oldestHit = clientDetection.QueuedHits.First();
clientDetection.QueuedHits.RemoveAt(0);
ApplyPenalty(clientDetection.ProcessHit(oldestHit, isDamage), clientDetection, attacker, ctx);
}
}
else
{
clientDetection.QueuedHits.Add(hit);
}
ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx); ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx);
} }
@ -567,16 +597,16 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
} }
void ApplyPenalty(Cheat.DetectionPenaltyResult penalty, Cheat.Detection clientDetection, Player attacker, DatabaseContext ctx) void ApplyPenalty(DetectionPenaltyResult penalty, Detection clientDetection, EFClient attacker, DatabaseContext ctx)
{ {
switch (penalty.ClientPenalty) switch (penalty.ClientPenalty)
{ {
case Penalty.PenaltyType.Ban: case Penalty.PenaltyType.Ban:
if (attacker.Level == Player.Permission.Banned) if (attacker.Level == EFClient.Permission.Banned)
{ {
break; break;
} }
attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], new Player() attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], new EFClient()
{ {
ClientId = 1, ClientId = 1,
AdministeredPenalties = new List<EFPenalty>() AdministeredPenalties = new List<EFPenalty>()
@ -588,16 +618,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}", $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
} }
}, },
Level = Player.Permission.Console, Level = EFClient.Permission.Console,
CurrentServer = attacker.CurrentServer CurrentServer = attacker.CurrentServer,
});
}, false);
if (clientDetection.Tracker.HasChanges) if (clientDetection.Tracker.HasChanges)
{ {
SaveTrackedSnapshots(clientDetection, ctx); SaveTrackedSnapshots(clientDetection, ctx);
} }
break; break;
case Penalty.PenaltyType.Flag: case Penalty.PenaltyType.Flag:
if (attacker.Level != Player.Permission.User) if (attacker.Level != EFClient.Permission.User)
{ {
break; break;
} }
@ -606,10 +637,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" : $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}"; $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}";
attacker.Flag(flagReason, new Player() attacker.Flag(flagReason, new EFClient()
{ {
ClientId = 1, ClientId = 1,
Level = Player.Permission.Console, Level = EFClient.Permission.Console,
CurrentServer = attacker.CurrentServer, CurrentServer = attacker.CurrentServer,
}); });
@ -677,9 +708,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
} }
public async Task AddStandardKill(Player attacker, Player victim) public async Task AddStandardKill(EFClient attacker, EFClient victim)
{ {
int serverId = attacker.CurrentServer.GetHashCode(); long serverId = await GetIdForServer(attacker.CurrentServer);
EFClientStatistics attackerStats = null; EFClientStatistics attackerStats = null;
if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId))
@ -709,13 +740,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// update the total stats // update the total stats
Servers[serverId].ServerStatistics.TotalKills += 1; Servers[serverId].ServerStatistics.TotalKills += 1;
await Sync(attacker.CurrentServer);
// this happens when the round has changed // this happens when the round has changed
if (attackerStats.SessionScore == 0) if (attackerStats.SessionScore == 0)
{
attackerStats.LastScore = 0; attackerStats.LastScore = 0;
}
if (victimStats.SessionScore == 0) if (victimStats.SessionScore == 0)
{
victimStats.LastScore = 0; victimStats.LastScore = 0;
}
attackerStats.SessionScore = attacker.Score; attackerStats.SessionScore = attacker.Score;
victimStats.SessionScore = victim.Score; victimStats.SessionScore = victim.Score;
@ -768,8 +804,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
var clientStatsSet = ctx.Set<EFClientStatistics>(); var clientStatsSet = ctx.Set<EFClientStatistics>();
clientStatsSet.Update(attackerStats); clientStatsSet.Attach(attackerStats).State = EntityState.Modified;
clientStatsSet.Update(victimStats); clientStatsSet.Attach(victimStats).State = EntityState.Modified;
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
} }
@ -780,7 +816,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <param name="client">client to update</param> /// <param name="client">client to update</param>
/// <param name="clientStats">stats of client that is being updated</param> /// <param name="clientStats">stats of client that is being updated</param>
/// <returns></returns> /// <returns></returns>
private async Task UpdateStatHistory(Player client, EFClientStatistics clientStats) private async Task UpdateStatHistory(EFClient client, EFClientStatistics clientStats)
{ {
int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds; int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds;
@ -979,6 +1015,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// calulate elo // calulate elo
if (Servers[attackerStats.ServerId].PlayerStats.Count > 1) if (Servers[attackerStats.ServerId].PlayerStats.Count > 1)
{ {
#region DEPRECATED
/* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats /* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != attackerStats.ClientId) .Where(cs => cs.Value.ClientId != attackerStats.ClientId)
.Where(cs => .Where(cs =>
@ -1002,6 +1039,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ? double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ?
validVictimLobbyRatings.Average(cs => cs.Value.EloRating) : validVictimLobbyRatings.Average(cs => cs.Value.EloRating) :
victimStats.EloRating;*/ victimStats.EloRating;*/
#endregion
double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating)); double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating));
double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E)); double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E));
@ -1104,9 +1142,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return clientStats; return clientStats;
} }
public EFServerStatistics InitializeServerStats(Server sv) public EFServerStatistics InitializeServerStats(long serverId)
{ {
int serverId = sv.GetHashCode();
EFServerStatistics serverStats; EFServerStatistics serverStats;
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
@ -1116,7 +1153,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (serverStats == null) if (serverStats == null)
{ {
Log.WriteDebug($"Initializing server stats for {sv}"); Log.WriteDebug($"Initializing server stats for {serverId}");
// server stats have never been generated before // server stats have never been generated before
serverStats = new EFServerStatistics() serverStats = new EFServerStatistics()
{ {
@ -1133,7 +1170,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return serverStats; return serverStats;
} }
public void ResetKillstreaks(int serverId) public void ResetKillstreaks(long serverId)
{ {
var serverStats = Servers[serverId]; var serverStats = Servers[serverId];
@ -1143,7 +1180,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
} }
public void ResetStats(int clientId, int serverId) public void ResetStats(int clientId, long serverId)
{ {
var stats = Servers[serverId].PlayerStats[clientId]; var stats = Servers[serverId].PlayerStats[clientId];
stats.Kills = 0; stats.Kills = 0;
@ -1154,11 +1191,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
stats.EloRating = 200; stats.EloRating = 200;
} }
public async Task AddMessageAsync(int clientId, int serverId, string message) public async Task AddMessageAsync(int clientId, long serverId, string message)
{ {
// the web users can have no account // the web users can have no account
if (clientId < 1) if (clientId < 1)
{
return; return;
}
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
@ -1176,19 +1215,64 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public async Task Sync(Server sv) public async Task Sync(Server sv)
{ {
int serverId = sv.GetHashCode(); long serverId = await GetIdForServer(sv);
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
var serverSet = ctx.Set<EFServer>(); var serverSet = ctx.Set<EFServer>();
serverSet.Update(Servers[serverId].Server); serverSet.Update(Servers[serverId].Server);
var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStatsSet.Update(Servers[serverId].ServerStatistics);
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
} }
public void SetTeamBased(int serverId, bool isTeamBased) public void SetTeamBased(long serverId, bool isTeamBased)
{ {
Servers[serverId].IsTeamBased = isTeamBased; Servers[serverId].IsTeamBased = isTeamBased;
} }
public static async Task<long> GetIdForServer(Server server)
{
// hack: my laziness
if ($"{server.IP}:{server.GetPort().ToString()}" == "66.150.121.184:28965")
{
return 886229536;
}
else if ($"{server.IP}:{server.GetPort().ToString()}" == "66.150.121.184:28960")
{
return 1645744423;
}
else if ($"{server.IP}:{server.GetPort().ToString()}" == "66.150.121.184:28970")
{
return 1645809959;
}
else
{
long id = HashCode.Combine(server.IP, server.GetPort());
id = id < 0 ? Math.Abs(id) : id;
long? serverId;
// todo: cache this eventually, as it shouldn't change
using (var ctx = new DatabaseContext(disableTracking: true))
{
serverId = (await ctx.Set<EFServer>().FirstOrDefaultAsync(_server => _server.ServerId == server.EndPoint ||
_server.EndPoint == server.ToString() ||
_server.ServerId == id))?.ServerId;
}
if (!serverId.HasValue)
{
return id;
}
return serverId.Value;
}
}
} }
} }

View File

@ -18,7 +18,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int AttackerId { get; set; } public int AttackerId { get; set; }
[ForeignKey("AttackerId")] [ForeignKey("AttackerId")]
public virtual EFClient Attacker { get; set; } public virtual EFClient Attacker { get; set; }
public int ServerId { get; set; } public long ServerId { get; set; }
[ForeignKey("ServerId")] [ForeignKey("ServerId")]
public virtual EFServer Server { get; set; } public virtual EFServer Server { get; set; }
public IW4Info.HitLocation HitLoc { get; set; } public IW4Info.HitLocation HitLoc { get; set; }

View File

@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
{ {
[Key] [Key]
public long MessageId { get; set; } public long MessageId { get; set; }
public int ServerId { get; set; } public long ServerId { get; set; }
[ForeignKey("ServerId")] [ForeignKey("ServerId")]
public virtual EFServer Server { get; set; } public virtual EFServer Server { get; set; }
public int ClientId { get; set; } public int ClientId { get; set; }

View File

@ -15,7 +15,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int ClientId { get; set; } public int ClientId { get; set; }
[ForeignKey("ClientId")] [ForeignKey("ClientId")]
public virtual EFClient Client { get; set; } public virtual EFClient Client { get; set; }
public int ServerId { get; set; } public long ServerId { get; set; }
[ForeignKey("ServerId")] [ForeignKey("ServerId")]
public virtual EFServer Server { get; set; } public virtual EFServer Server { get; set; }
[Required] [Required]

View File

@ -20,7 +20,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int ClientId { get; set; } public int ClientId { get; set; }
[ForeignKey("ClientId"), Column(Order = 0 )] [ForeignKey("ClientId"), Column(Order = 0 )]
public EFClient Client { get; set; } public EFClient Client { get; set; }
public int ServerId { get; set; } public long ServerId { get; set; }
[ForeignKey("ServerId"), Column(Order = 1)] [ForeignKey("ServerId"), Column(Order = 1)]
public EFServer Server { get; set; } public EFServer Server { get; set; }
} }

View File

@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
[ForeignKey("RatingHistoryId")] [ForeignKey("RatingHistoryId")]
public virtual EFClientRatingHistory RatingHistory { get; set; } public virtual EFClientRatingHistory RatingHistory { get; set; }
// if null, indicates that the rating is an average rating // if null, indicates that the rating is an average rating
public int? ServerId { get; set; } public long? ServerId { get; set; }
// [ForeignKey("ServerId")] can't make this nullable if this annotation is set // [ForeignKey("ServerId")] can't make this nullable if this annotation is set
public virtual EFServer Server { get; set; } public virtual EFServer Server { get; set; }
[Required] [Required]

View File

@ -9,8 +9,9 @@ namespace IW4MAdmin.Plugins.Stats.Models
{ {
[Key] [Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)] [DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ServerId { get; set; } public long ServerId { get; set; }
[Required] [Required]
public int Port { get; set; } public int Port { get; set; }
public string EndPoint { get; set; }
} }
} }

View File

@ -8,7 +8,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
{ {
[Key] [Key]
public int StatisticId { get; set; } public int StatisticId { get; set; }
public int ServerId { get; set; } public long ServerId { get; set; }
[ForeignKey("ServerId")] [ForeignKey("ServerId")]
public virtual EFServer Server { get; set; } public virtual EFServer Server { get; set; }
public long TotalKills { get; set; } public long TotalKills { get; set; }

View File

@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore; using IW4MAdmin.Plugins.Stats.Models;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using IW4MAdmin.Plugins.Stats.Models;
namespace Stats.Models namespace Stats.Models
{ {

View File

@ -1,20 +1,19 @@
using System; using IW4MAdmin.Plugins.Stats.Config;
using System.Collections.Generic; using IW4MAdmin.Plugins.Stats.Helpers;
using System.Linq; using IW4MAdmin.Plugins.Stats.Models;
using System.Threading.Tasks; using Microsoft.EntityFrameworkCore;
using System.Reflection;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database;
using SharedLibraryCore.Dtos; using SharedLibraryCore.Dtos;
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services; using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Config; using System;
using IW4MAdmin.Plugins.Stats.Helpers; using System.Collections.Generic;
using IW4MAdmin.Plugins.Stats.Models; using System.Linq;
using SharedLibraryCore.Database; using System.Reflection;
using Microsoft.EntityFrameworkCore; using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats namespace IW4MAdmin.Plugins.Stats
{ {
@ -44,16 +43,19 @@ namespace IW4MAdmin.Plugins.Stats
break; break;
case GameEvent.EventType.Disconnect: case GameEvent.EventType.Disconnect:
await Manager.RemovePlayer(E.Origin); await Manager.RemovePlayer(E.Origin);
await Manager.Sync(S);
break; break;
case GameEvent.EventType.Say: case GameEvent.EventType.Say:
if (!string.IsNullOrEmpty(E.Data) && if (!string.IsNullOrEmpty(E.Data) &&
E.Origin.ClientId > 1) E.Origin.ClientId > 1)
await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), 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(E.Owner.GetHashCode(), E.Owner.Gametype != "dm"); Manager.SetTeamBased(await StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm");
Manager.ResetKillstreaks(S.GetHashCode()); Manager.ResetKillstreaks(await StatManager.GetIdForServer(E.Owner));
await Manager.Sync(S);
break; break;
case GameEvent.EventType.MapEnd: case GameEvent.EventType.MapEnd:
break; break;
@ -77,7 +79,7 @@ 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)
{ {
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, S.GetHashCode(), 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]);
} }
break; break;
@ -90,14 +92,14 @@ namespace IW4MAdmin.Plugins.Stats
case GameEvent.EventType.Damage: case GameEvent.EventType.Damage:
if (!E.Owner.CustomCallback) if (!E.Owner.CustomCallback)
{ {
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, E.Owner.GetHashCode()); Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, await StatManager.GetIdForServer(E.Owner));
} }
break; break;
case GameEvent.EventType.ScriptDamage: case GameEvent.EventType.ScriptDamage:
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)
{ {
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, S.GetHashCode(), 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]);
} }
break; break;
@ -143,7 +145,7 @@ 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 Manager.GetClientOverallRanking(clientId), Value = "#" + await StatManager.GetClientOverallRanking(clientId),
}, },
new ProfileMeta() new ProfileMeta()
{ {
@ -289,32 +291,32 @@ namespace IW4MAdmin.Plugins.Stats
MetaService.AddMeta(getMessages); MetaService.AddMeta(getMessages);
string totalKills(Server server) async Task<object> 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");
} }
} }
string totalPlayTime(Server server) async Task<object> 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");
} }
} }
string topStats(Server s) async Task<object> 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<object> 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));
@ -327,12 +329,17 @@ namespace IW4MAdmin.Plugins.Stats
Manager = new StatManager(manager); Manager = new StatManager(manager);
} }
public Task OnTickAsync(Server S) => Task.CompletedTask; public Task OnTickAsync(Server S)
{
return Task.CompletedTask;
}
public async Task OnUnloadAsync() public async Task OnUnloadAsync()
{ {
foreach (var sv in ServerManager.GetServers()) foreach (var sv in ServerManager.GetServers())
{
await Manager.Sync(sv); await Manager.Sync(sv);
}
} }
} }
} }

View File

@ -2,8 +2,8 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.Stats</PackageId> <PackageId>RaidMax.IW4MAdmin.Plugins.Stats</PackageId>
@ -15,27 +15,16 @@
<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" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" /> <PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
</ItemGroup> </ItemGroup>
<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>

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