Compare commits
65 Commits
2.2-prerel
...
2.3-prerel
Author | SHA1 | Date | |
---|---|---|---|
5d41059641 | |||
2e6889d9bb | |||
9c4d23f0b4 | |||
b4e3e8526a | |||
e3944fb8c2 | |||
40f1697c97 | |||
2bbf2988da | |||
5e36bf4316 | |||
a362caebac | |||
2260d8974d | |||
5e04274da6 | |||
6d2e6aee4f | |||
9e74c42246 | |||
dea5b3f954 | |||
7c6419a16a | |||
0194196a33 | |||
044991272f | |||
f3290cf066 | |||
29eedea093 | |||
ce02f5dd68 | |||
e6bfa408f8 | |||
0a1dc46760 | |||
97ba6aae2e | |||
a456fab0e5 | |||
3e5282df87 | |||
59e0072744 | |||
f1dd4f7c7f | |||
760d3026ce | |||
07df6dbf79 | |||
ca535019c6 | |||
e6154822f6 | |||
7a6dccc26a | |||
08c883e0ff | |||
aaf9eb09b6 | |||
7b75c35c9b | |||
07ec5cf52f | |||
cf5ee8765d | |||
9494a17997 | |||
5f4171ccf4 | |||
a10746d5ff | |||
8dca05a442 | |||
8aa0d204f4 | |||
12cf2e8247 | |||
b77bdbe793 | |||
4522992c0e | |||
9d6cbee69c | |||
abf0609e2e | |||
5ac8a55c72 | |||
9bdd7d1b8a | |||
ed83c4c011 | |||
d9d548ea18 | |||
1779bf821d | |||
d50e6c8030 | |||
a58726d872 | |||
dded60a6ef | |||
305817d00c | |||
65cf3566db | |||
e91d076b41 | |||
b5e9519f0c | |||
7caee55a7e | |||
b289917319 | |||
c8366a22e5 | |||
de902a58ac | |||
c7547f1ad5 | |||
9d946d1bad |
4
.gitignore
vendored
4
.gitignore
vendored
@ -229,3 +229,7 @@ bootstrap-custom.min.css
|
||||
/DiscordWebhook/env
|
||||
/DiscordWebhook/config.dev.json
|
||||
/GameLogServer/env
|
||||
launchSettings.json
|
||||
/VpnDetectionPrivate.js
|
||||
/Plugins/ScriptPlugins/VpnDetectionPrivate.js
|
||||
**/Master/env_master
|
@ -8,9 +8,13 @@ namespace IW4MAdmin.Application.API.Master
|
||||
public class ApiServer
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
public long Id { get; set; }
|
||||
[JsonProperty("ip")]
|
||||
public string IPAddress { get; set; }
|
||||
[JsonProperty("port")]
|
||||
public short Port { get; set; }
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; set; }
|
||||
[JsonProperty("gametype")]
|
||||
public string Gametype { get; set; }
|
||||
[JsonProperty("map")]
|
||||
|
@ -39,12 +39,14 @@ namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
ClientNum = s.ClientNum,
|
||||
Game = s.GameName.ToString(),
|
||||
Version = s.Version,
|
||||
Gametype = s.Gametype,
|
||||
Hostname = s.Hostname,
|
||||
Map = s.CurrentMap.Name,
|
||||
MaxClientNum = s.MaxClients,
|
||||
Id = s.GetHashCode(),
|
||||
Port = (short)s.GetPort()
|
||||
Id = s.EndPoint,
|
||||
Port = (short)s.GetPort(),
|
||||
IPAddress = s.IP
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
|
@ -2,15 +2,16 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
||||
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
||||
<Version>2.1.9.2</Version>
|
||||
<Version>2.2.5.3</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Product>IW4MAdmin</Product>
|
||||
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated server</Description>
|
||||
<Copyright>2018</Copyright>
|
||||
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated servers</Description>
|
||||
<Copyright>2019</Copyright>
|
||||
<PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl>
|
||||
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin</RepositoryUrl>
|
||||
@ -24,12 +25,14 @@
|
||||
|
||||
<ItemGroup>
|
||||
<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>
|
||||
|
||||
<PropertyGroup>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<TieredCompilation>true</TieredCompilation>
|
||||
<AssemblyVersion>2.2.5.3</AssemblyVersion>
|
||||
<FileVersion>2.2.5.3</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -42,21 +45,6 @@
|
||||
</ProjectReference>
|
||||
</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>
|
||||
<None Update="DefaultSettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
@ -76,7 +64,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" />
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
@ -91,6 +79,6 @@
|
||||
</Target>
|
||||
|
||||
<Target Name="PostPublish" AfterTargets="Publish">
|
||||
<Exec Command="call $(ProjectDir)BuildScripts\PostPublish.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(OutDir)" />
|
||||
<Exec Command="call $(ProjectDir)BuildScripts\PostPublish.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(ConfigurationName)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
@ -1,23 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
using IW4MAdmin.Application.EventParsers;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using IW4MAdmin.Application.RconParsers;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.Services;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Events;
|
||||
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Helpers;
|
||||
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
|
||||
{
|
||||
@ -25,8 +26,8 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
private List<Server> _servers;
|
||||
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
|
||||
public Dictionary<int, Player> PrivilegedClients { get; set; }
|
||||
public ILogger Logger { get; private set; }
|
||||
public Dictionary<int, EFClient> PrivilegedClients { get; set; }
|
||||
public ILogger Logger => GetLogger(0);
|
||||
public bool Running { get; private set; }
|
||||
public bool IsInitialized { get; private set; }
|
||||
// define what the delagate function looks like
|
||||
@ -36,6 +37,13 @@ namespace IW4MAdmin.Application
|
||||
public DateTime StartTime { get; private set; }
|
||||
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;
|
||||
readonly List<AsyncStatus> TaskStatuses;
|
||||
List<Command> Commands;
|
||||
@ -48,10 +56,11 @@ namespace IW4MAdmin.Application
|
||||
ManualResetEventSlim OnQuit;
|
||||
readonly IPageList PageList;
|
||||
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
|
||||
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
|
||||
readonly ITokenAuthentication _authenticator;
|
||||
|
||||
private ApplicationManager()
|
||||
{
|
||||
Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log");
|
||||
_servers = new List<Server>();
|
||||
Commands = new List<Command>();
|
||||
TaskStatuses = new List<AsyncStatus>();
|
||||
@ -59,13 +68,15 @@ namespace IW4MAdmin.Application
|
||||
ClientSvc = new ClientService();
|
||||
AliasSvc = new AliasService();
|
||||
PenaltySvc = new PenaltyService();
|
||||
PrivilegedClients = new Dictionary<int, Player>();
|
||||
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
||||
StartTime = DateTime.UtcNow;
|
||||
OnQuit = new ManualResetEventSlim();
|
||||
PageList = new PageList();
|
||||
AdditionalEventParsers = new List<IEventParser>();
|
||||
AdditionalRConParsers = new List<IRConParser>();
|
||||
OnServerEvent += OnGameEvent;
|
||||
OnServerEvent += EventApi.OnGameEvent;
|
||||
_authenticator = new TokenAuthentication();
|
||||
}
|
||||
|
||||
private async void OnGameEvent(object sender, GameEventArgs args)
|
||||
@ -84,80 +95,11 @@ namespace IW4MAdmin.Application
|
||||
|
||||
try
|
||||
{
|
||||
// if the origin client is not in an authorized state (detected by RCon) don't execute the event
|
||||
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"]);
|
||||
}
|
||||
await newEvent.Owner.ExecuteEvent(newEvent);
|
||||
|
||||
// offload it to the player to keep
|
||||
newEvent.Origin.DelayedEvents.Enqueue(newEvent);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
// save the event info to the database
|
||||
var changeHistorySvc = new ChangeHistoryService();
|
||||
await changeHistorySvc.Add(args.Event);
|
||||
|
||||
#if DEBUG
|
||||
Logger.WriteDebug($"Processed event with id {newEvent.Id}");
|
||||
@ -191,7 +133,7 @@ namespace IW4MAdmin.Application
|
||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
skip:
|
||||
skip:
|
||||
|
||||
// tell anyone waiting for the output that we're done
|
||||
newEvent.OnProcessed.Set();
|
||||
@ -215,7 +157,7 @@ namespace IW4MAdmin.Application
|
||||
public async Task UpdateServerStates()
|
||||
{
|
||||
// store the server hash code and task for it
|
||||
var runningUpdateTasks = new Dictionary<int, Task>();
|
||||
var runningUpdateTasks = new Dictionary<long, Task>();
|
||||
|
||||
while (Running)
|
||||
{
|
||||
@ -235,16 +177,16 @@ namespace IW4MAdmin.Application
|
||||
}
|
||||
|
||||
// remove the update tasks as they have completd
|
||||
foreach (int serverId in serverTasksToRemove)
|
||||
foreach (long serverId in serverTasksToRemove)
|
||||
{
|
||||
runningUpdateTasks.Remove(serverId);
|
||||
}
|
||||
|
||||
// select the servers where the tasks have completed
|
||||
var serverIds = Servers.Select(s => s.GetHashCode()).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
|
||||
foreach (var server in Servers.Where(s => serverIds.Contains(s.GetHashCode())))
|
||||
var serverIds = Servers.Select(s => s.EndPoint).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
|
||||
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
|
||||
{
|
||||
@ -264,11 +206,7 @@ namespace IW4MAdmin.Application
|
||||
ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
|
||||
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
|
||||
#endif
|
||||
#if DEBUG
|
||||
await Task.Delay(10000);
|
||||
#else
|
||||
await Task.Delay(ConfigHandler.Configuration().RConPollRate);
|
||||
#endif
|
||||
}
|
||||
|
||||
// trigger the event processing loop to end
|
||||
@ -279,40 +217,21 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
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)
|
||||
var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
|
||||
.Select(c => new
|
||||
{
|
||||
c.Password,
|
||||
c.PasswordSalt,
|
||||
c.ClientId,
|
||||
c.Level,
|
||||
c.Name
|
||||
});
|
||||
#region PLUGINS
|
||||
SharedLibraryCore.Plugins.PluginImporter.Load(this);
|
||||
|
||||
foreach (var a in ipList)
|
||||
foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
|
||||
{
|
||||
try
|
||||
{
|
||||
PrivilegedClients.Add(a.ClientId, new Player()
|
||||
{
|
||||
Name = a.Name,
|
||||
ClientId = a.ClientId,
|
||||
Level = a.Level,
|
||||
PasswordSalt = a.PasswordSalt,
|
||||
Password = a.Password
|
||||
});
|
||||
await Plugin.OnLoadAsync(this);
|
||||
}
|
||||
|
||||
catch (ArgumentException)
|
||||
catch (Exception ex)
|
||||
{
|
||||
continue;
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
|
||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@ -339,7 +258,18 @@ namespace IW4MAdmin.Application
|
||||
|
||||
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"]));
|
||||
|
||||
config = newConfig;
|
||||
@ -360,37 +290,54 @@ namespace IW4MAdmin.Application
|
||||
config.WebfrontBindUrl = "http://0.0.0.0:1624";
|
||||
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)
|
||||
{
|
||||
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
|
||||
}
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252");
|
||||
|
||||
#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 Plugin.OnLoadAsync(this);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
|
||||
Logger.WriteDebug($"Exception: {e.Message}");
|
||||
Logger.WriteDebug($"Stack Trace: {e.StackTrace}");
|
||||
}
|
||||
await new ContextSeed(db).Seed();
|
||||
}
|
||||
|
||||
PrivilegedClients = (await ClientSvc.GetPrivilegedClients()).ToDictionary(_client => _client.ClientId);
|
||||
#endregion
|
||||
|
||||
#region COMMANDS
|
||||
if (ClientSvc.GetOwners().Result.Count == 0)
|
||||
{
|
||||
Commands.Add(new COwner());
|
||||
}
|
||||
|
||||
Commands.Add(new CQuit());
|
||||
Commands.Add(new CKick());
|
||||
@ -429,9 +376,12 @@ namespace IW4MAdmin.Application
|
||||
Commands.Add(new CPing());
|
||||
Commands.Add(new CSetGravatar());
|
||||
Commands.Add(new CNextMap());
|
||||
Commands.Add(new RequestTokenCommand());
|
||||
|
||||
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
|
||||
{
|
||||
Commands.Add(C);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region INIT
|
||||
@ -466,7 +416,9 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"]} [{Conf.IPAddress}:{Conf.Port}]");
|
||||
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"]})");
|
||||
}
|
||||
else if (e.GetType() == typeof(NetworkException))
|
||||
{
|
||||
Logger.WriteDebug(e.Message);
|
||||
@ -565,9 +517,29 @@ namespace IW4MAdmin.Application
|
||||
Running = false;
|
||||
}
|
||||
|
||||
public ILogger GetLogger()
|
||||
public ILogger GetLogger(long serverId)
|
||||
{
|
||||
return Logger;
|
||||
if (Loggers.ContainsKey(serverId))
|
||||
{
|
||||
return Loggers[serverId];
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Logger newLogger;
|
||||
|
||||
if (serverId == 0)
|
||||
{
|
||||
newLogger = new Logger("IW4MAdmin-Manager");
|
||||
}
|
||||
else
|
||||
{
|
||||
newLogger = new Logger($"IW4MAdmin-Server-{serverId}");
|
||||
}
|
||||
|
||||
Loggers.Add(serverId, newLogger);
|
||||
return newLogger;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<MessageToken> GetMessageTokens()
|
||||
@ -575,23 +547,69 @@ namespace IW4MAdmin.Application
|
||||
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 AliasService GetAliasService() => AliasSvc;
|
||||
public PenaltyService GetPenaltyService() => PenaltySvc;
|
||||
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings() => ConfigHandler;
|
||||
public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients;
|
||||
public bool ShutdownRequested() => !Running;
|
||||
public IEventHandler GetEventHandler() => Handler;
|
||||
public ClientService GetClientService()
|
||||
{
|
||||
return ClientSvc;
|
||||
}
|
||||
|
||||
public AliasService GetAliasService()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
set SolutionDir=%1
|
||||
set ProjectDir=%2
|
||||
set TargetDir=%3
|
||||
set CurrentConfiguration=%4
|
||||
SET COPYCMD=/Y
|
||||
|
||||
echo Deleting extra language files
|
||||
|
||||
@ -54,9 +56,52 @@ del "%SolutionDir%Publish\Windows\*pdb"
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\web.config" del "%SolutionDir%Publish\WindowsPrerelease\web.config"
|
||||
del "%SolutionDir%Publish\WindowsPrerelease\*pdb"
|
||||
|
||||
echo making start scripts
|
||||
@echo dotnet IW4MAdmin.dll > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
|
||||
@echo dotnet IW4MAdmin.dll > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd"
|
||||
echo setting up library folders
|
||||
|
||||
@(echo #!/bin/bash && echo dotnet IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh"
|
||||
@(echo #!/bin/bash && echo dotnet IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh"
|
||||
if "%CurrentConfiguration%" == "Prerelease" (
|
||||
echo PR-Config
|
||||
if not exist "%SolutionDir%Publish\WindowsPrerelease\Configuration" md "%SolutionDir%Publish\WindowsPrerelease\Configuration"
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\DefaultSettings.json" "%SolutionDir%Publish\WindowsPrerelease\Configuration\"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Release" (
|
||||
echo R-Config
|
||||
if not exist "%SolutionDir%Publish\Windows\Configuration" md "%SolutionDir%Publish\Windows\Configuration"
|
||||
if exist "%SolutionDir%Publish\Windows\DefaultSettings.json" move "%SolutionDir%Publish\Windows\DefaultSettings.json" "%SolutionDir%Publish\Windows\Configuration\DefaultSettings.json"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Prerelease" (
|
||||
echo PR-LIB
|
||||
if not exist "%SolutionDir%Publish\WindowsPrerelease\Lib\" md "%SolutionDir%Publish\WindowsPrerelease\Lib\"
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\*.dll" "%SolutionDir%Publish\WindowsPrerelease\Lib\"
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\*.json" "%SolutionDir%Publish\WindowsPrerelease\Lib\"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Release" (
|
||||
echo R-LIB
|
||||
if not exist "%SolutionDir%Publish\Windows\Lib\" md "%SolutionDir%Publish\Windows\Lib\"
|
||||
move "%SolutionDir%Publish\Windows\*.dll" "%SolutionDir%Publish\Windows\Lib\"
|
||||
move "%SolutionDir%Publish\Windows\*.json" "%SolutionDir%Publish\Windows\Lib\"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Prerelease" (
|
||||
echo PR-RT
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\runtimes" "%SolutionDir%Publish\WindowsPrerelease\Lib\runtimes"
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\refs" move "%SolutionDir%Publish\WindowsPrerelease\refs" "%SolutionDir%Publish\WindowsPrerelease\Lib\refs"
|
||||
)
|
||||
|
||||
|
||||
if "%CurrentConfiguration%" == "Release" (
|
||||
echo R-RT
|
||||
move "%SolutionDir%Publish\Windows\runtimes" "%SolutionDir%Publish\Windows\Lib\runtimes"
|
||||
if exist "%SolutionDir%Publish\Windows\refs" move "%SolutionDir%Publish\Windows\refs" "%SolutionDir%Publish\Windows\Lib\refs"
|
||||
)
|
||||
|
||||
echo making start scripts
|
||||
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
|
||||
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd"
|
||||
|
||||
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh"
|
||||
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh"
|
||||
|
||||
eCHO "%CurrentConfiguration%"
|
||||
|
@ -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().WriteWarning($"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);
|
||||
}
|
||||
}
|
||||
}
|
@ -25,11 +25,6 @@
|
||||
"Name": "mp_rust"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Highrise",
|
||||
"Name": "mp_highrise"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Terminal",
|
||||
"Name": "mp_terminal"
|
||||
@ -40,16 +35,6 @@
|
||||
"Name": "mp_crash"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Skidrow",
|
||||
"Name": "mp_nightshift"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Quarry",
|
||||
"Name": "mp_quarry"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Afghan",
|
||||
"Name": "mp_afghan"
|
||||
@ -257,12 +242,12 @@
|
||||
|
||||
{
|
||||
"Name": "Village",
|
||||
"Alias": "co_hunted"
|
||||
"Alias": "co_hunted"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "T6M",
|
||||
"Game": "T6",
|
||||
"Maps": [
|
||||
{
|
||||
"Alias": "Aftermath",
|
||||
@ -417,6 +402,95 @@
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
306
Application/EventParsers/BaseEventParser.cs
Normal file
306
Application/EventParsers/BaseEventParser.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
15
Application/EventParsers/DynamicEventParser.cs
Normal file
15
Application/EventParsers/DynamicEventParser.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
19
Application/EventParsers/DynamicEventParserConfiguration.cs
Normal file
19
Application/EventParsers/DynamicEventParserConfiguration.cs
Normal 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();
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -21,23 +19,14 @@ namespace IW4MAdmin.Application.IO
|
||||
public string ServerId { get; set; }
|
||||
}
|
||||
|
||||
public GameLogEventDetection(Server server, string gameLogPath, string gameLogName)
|
||||
public GameLogEventDetection(Server server, string gameLogPath, Uri gameLogServerUri)
|
||||
{
|
||||
GameLogFile = gameLogPath;
|
||||
// todo: abtract this more
|
||||
if (gameLogPath.StartsWith("http"))
|
||||
{
|
||||
Reader = new GameLogReaderHttp(gameLogPath, server.EventParser);
|
||||
}
|
||||
else
|
||||
{
|
||||
Reader = new GameLogReader(gameLogPath, server.EventParser);
|
||||
}
|
||||
|
||||
Reader = gameLogServerUri != null ? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser) : Reader = new GameLogReader(gameLogPath, server.EventParser);
|
||||
Server = server;
|
||||
}
|
||||
|
||||
public void PollForChanges()
|
||||
public async Task PollForChanges()
|
||||
{
|
||||
while (!Server.Manager.ShutdownRequested())
|
||||
{
|
||||
@ -45,12 +34,12 @@ namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateLogEvents();
|
||||
await UpdateLogEvents();
|
||||
}
|
||||
|
||||
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($"StackTrace: {e.StackTrace}");
|
||||
}
|
||||
@ -59,7 +48,7 @@ namespace IW4MAdmin.Application.IO
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLogEvents()
|
||||
private async Task UpdateLogEvents()
|
||||
{
|
||||
long fileSize = Reader.Length;
|
||||
|
||||
@ -74,11 +63,12 @@ namespace IW4MAdmin.Application.IO
|
||||
|
||||
PreviousFileSize = fileSize;
|
||||
|
||||
var events = Reader.ReadEventsFromLog(Server, fileDiff, 0);
|
||||
var events = await Reader.ReadEventsFromLog(Server, fileDiff, 0);
|
||||
|
||||
foreach (var ev in events)
|
||||
{
|
||||
Server.Manager.GetEventHandler().AddEvent(ev);
|
||||
await ev.WaitAsync();
|
||||
}
|
||||
|
||||
PreviousFileSize = fileSize;
|
||||
|
@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
@ -22,7 +23,7 @@ namespace IW4MAdmin.Application.IO
|
||||
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
|
||||
List<string> logLines = new List<string>();
|
||||
@ -30,6 +31,7 @@ namespace IW4MAdmin.Application.IO
|
||||
// open the file as a stream
|
||||
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
|
||||
rd.BaseStream.Seek(-fileSizeDiff, SeekOrigin.End);
|
||||
// the difference should be in the range of a int :P
|
||||
@ -55,9 +57,9 @@ namespace IW4MAdmin.Application.IO
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line");
|
||||
Program.ServerManager.GetLogger().WriteDebug(e.Message);
|
||||
Program.ServerManager.GetLogger().WriteDebug(eventLine);
|
||||
server.Logger.WriteWarning("Could not properly parse event line");
|
||||
server.Logger.WriteDebug(e.Message);
|
||||
server.Logger.WriteDebug(eventLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using static SharedLibraryCore.Utilities;
|
||||
|
||||
namespace IW4MAdmin.Application.IO
|
||||
@ -16,31 +17,32 @@ namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
readonly IEventParser Parser;
|
||||
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;
|
||||
Api = RestClient.For<IGameLogServer>(logFile);
|
||||
Api = RestClient.For<IGameLogServer>(gameLogServerUri);
|
||||
}
|
||||
|
||||
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
|
||||
server.Logger.WriteDebug($"Begin reading {fileSizeDiff} from http log");
|
||||
server.Logger.WriteDebug($"Begin reading from http log");
|
||||
#endif
|
||||
var events = new List<GameEvent>();
|
||||
string b64Path = server.LogPath.ToBase64UrlSafeString();
|
||||
var response = Api.Log(b64Path).Result;
|
||||
string b64Path = logPath;
|
||||
var response = await Api.Log(b64Path);
|
||||
|
||||
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
|
||||
@ -59,9 +61,9 @@ namespace IW4MAdmin.Application.IO
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line");
|
||||
Program.ServerManager.GetLogger().WriteDebug(e.Message);
|
||||
Program.ServerManager.GetLogger().WriteDebug(eventLine);
|
||||
server.Logger.WriteWarning("Could not properly parse event line");
|
||||
server.Logger.WriteDebug(e.Message);
|
||||
server.Logger.WriteDebug(eventLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,10 +12,10 @@ namespace IW4MAdmin.Application.Localization
|
||||
{
|
||||
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[] localizationFiles = Directory.GetFiles("Localization", $"*.{currentLocale}.json");
|
||||
string[] localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");
|
||||
|
||||
try
|
||||
{
|
||||
@ -33,13 +33,13 @@ namespace IW4MAdmin.Application.Localization
|
||||
// culture doesn't exist so we just want language
|
||||
if (localizationFiles.Length == 0)
|
||||
{
|
||||
localizationFiles = Directory.GetFiles("Localization", $"*.{currentLocale.Substring(0, 2)}*.json");
|
||||
localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale.Substring(0, 2)}*.json");
|
||||
}
|
||||
|
||||
// language doesn't exist either so defaulting to english
|
||||
if (localizationFiles.Length == 0)
|
||||
{
|
||||
localizationFiles = Directory.GetFiles("Localization", "*.en-US.json");
|
||||
localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), "*.en-US.json");
|
||||
}
|
||||
|
||||
// this should never happen unless the localization folder is empty
|
||||
@ -59,12 +59,12 @@ namespace IW4MAdmin.Application.Localization
|
||||
{
|
||||
if (!localizationDict.TryAdd(item.Key, item.Value))
|
||||
{
|
||||
Program.ServerManager.GetLogger().WriteError($"Could not add locale string {item.Key} to localization");
|
||||
Program.ServerManager.GetLogger(0).WriteError($"Could not add locale string {item.Key} to localization");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string localizationFile = $"Localization{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocale}-{currentLocale.ToUpper()}.json";
|
||||
string localizationFile = $"{Path.Join(Utilities.OperatingDirectory, "Localization")}{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocale}-{currentLocale.ToUpper()}.json";
|
||||
|
||||
Utilities.CurrentLocalization = new SharedLibraryCore.Localization.Layout(localizationDict)
|
||||
{
|
||||
|
@ -1,29 +1,23 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
using IW4MAdmin.Application.Migration;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Localization;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Localization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
static public double Version { get; private set; }
|
||||
static public ApplicationManager ServerManager = ApplicationManager.GetInstance();
|
||||
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
|
||||
static public ApplicationManager ServerManager;
|
||||
private static ManualResetEventSlim OnShutdownComplete = new ManualResetEventSlim();
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", OperatingDirectory);
|
||||
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
@ -39,12 +33,25 @@ namespace IW4MAdmin.Application
|
||||
|
||||
try
|
||||
{
|
||||
CheckDirectories();
|
||||
|
||||
ServerManager = ApplicationManager.GetInstance();
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
|
||||
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
|
||||
|
||||
if (ServerManager.GetApplicationSettings().Configuration() != null)
|
||||
{
|
||||
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration().CustomLocale);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Localization.Configure.Initialize();
|
||||
}
|
||||
|
||||
loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
|
||||
|
||||
CheckDirectories();
|
||||
// do any needed migrations
|
||||
// todo: move out
|
||||
ConfigurationMigration.MoveConfigFolder10518(null);
|
||||
|
||||
ServerManager.Logger.WriteInfo($"Version is {Version}");
|
||||
|
||||
@ -106,15 +113,17 @@ namespace IW4MAdmin.Application
|
||||
|
||||
var consoleTask = Task.Run(async () =>
|
||||
{
|
||||
String userInput;
|
||||
Player Origin = Utilities.IW4MAdminClient;
|
||||
string userInput;
|
||||
var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
|
||||
|
||||
do
|
||||
{
|
||||
userInput = Console.ReadLine();
|
||||
|
||||
if (userInput?.ToLower() == "quit")
|
||||
{
|
||||
ServerManager.Stop();
|
||||
}
|
||||
|
||||
if (ServerManager.Servers.Count == 0)
|
||||
{
|
||||
@ -124,7 +133,6 @@ namespace IW4MAdmin.Application
|
||||
|
||||
if (userInput?.Length > 0)
|
||||
{
|
||||
Origin.CurrentServer = ServerManager.Servers[0];
|
||||
GameEvent E = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Command,
|
||||
@ -144,13 +152,16 @@ namespace IW4MAdmin.Application
|
||||
|
||||
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)
|
||||
{
|
||||
e = e.InnerException;
|
||||
}
|
||||
Console.WriteLine($"Exception: {e.Message}");
|
||||
Console.WriteLine(loc["MANAGER_EXIT"]);
|
||||
Console.WriteLine(e.Message);
|
||||
Console.WriteLine(exitMessage);
|
||||
Console.ReadKey();
|
||||
return;
|
||||
}
|
||||
@ -169,15 +180,25 @@ namespace IW4MAdmin.Application
|
||||
private static void OnCancelKey(object sender, ConsoleCancelEventArgs e)
|
||||
{
|
||||
ServerManager.Stop();
|
||||
OnShutdownComplete.Wait(5000);
|
||||
OnShutdownComplete.Wait();
|
||||
}
|
||||
|
||||
static void CheckDirectories()
|
||||
{
|
||||
string curDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
|
||||
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Plugins")))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Plugins"));
|
||||
}
|
||||
|
||||
if (!Directory.Exists($"{curDirectory}Plugins"))
|
||||
Directory.CreateDirectory($"{curDirectory}Plugins");
|
||||
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Database")))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Database"));
|
||||
}
|
||||
|
||||
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Log")))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Log"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
69
Application/Migration/ConfigurationMigration.cs
Normal file
69
Application/Migration/ConfigurationMigration.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace IW4MAdmin.Application.Migration
|
||||
{
|
||||
/// <summary>
|
||||
/// helps facilitate the migration of configs from one version and or location
|
||||
/// to another
|
||||
/// </summary>
|
||||
class ConfigurationMigration
|
||||
{
|
||||
/// <summary>
|
||||
/// moves existing configs from the root folder into a configs folder
|
||||
/// </summary>
|
||||
public static void MoveConfigFolder10518(ILogger log)
|
||||
{
|
||||
string currentDirectory = Utilities.OperatingDirectory;
|
||||
|
||||
// we don't want to do this for migrations or tests where the
|
||||
// property isn't initialized or it's wrong
|
||||
if (currentDirectory != null)
|
||||
{
|
||||
string configDirectory = Path.Join(currentDirectory, "Configuration");
|
||||
|
||||
if (!Directory.Exists(configDirectory))
|
||||
{
|
||||
log?.WriteDebug($"Creating directory for configs {configDirectory}");
|
||||
Directory.CreateDirectory(configDirectory);
|
||||
}
|
||||
|
||||
var configurationFiles = Directory.EnumerateFiles(currentDirectory, "*.json")
|
||||
.Select(f => f.Split(Path.DirectorySeparatorChar).Last())
|
||||
.Where(f => f.Count(c => c == '.') == 1);
|
||||
|
||||
foreach (var configFile in configurationFiles)
|
||||
{
|
||||
log?.WriteDebug($"Moving config file {configFile}");
|
||||
string destinationPath = Path.Join("Configuration", configFile);
|
||||
if (!File.Exists(destinationPath))
|
||||
{
|
||||
File.Move(configFile, destinationPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(Path.Join("Database", "Database.db")) &&
|
||||
File.Exists("Database.db"))
|
||||
{
|
||||
log?.WriteDebug("Moving database file");
|
||||
File.Move("Database.db", Path.Join("Database", "Database.db"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ModifyLogPath020919(SharedLibraryCore.Configuration.ServerConfiguration config)
|
||||
{
|
||||
if (config.ManualLogPath.IsRemoteLog())
|
||||
{
|
||||
config.GameLogServerUrl = new Uri(config.ManualLogPath);
|
||||
config.ManualLogPath = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
@ -18,18 +19,44 @@ namespace IW4MAdmin.Application
|
||||
}
|
||||
|
||||
readonly string FileName;
|
||||
readonly object ThreadLock;
|
||||
readonly SemaphoreSlim OnLogWriting;
|
||||
static readonly short MAX_LOG_FILES = 10;
|
||||
|
||||
public Logger(string fn)
|
||||
{
|
||||
FileName = fn;
|
||||
ThreadLock = new object();
|
||||
if (File.Exists(fn))
|
||||
File.Delete(fn);
|
||||
FileName = Path.Join(Utilities.OperatingDirectory, "Log", $"{fn}.log");
|
||||
OnLogWriting = new SemaphoreSlim(1, 1);
|
||||
RotateLogs();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// rotates logs when log is initialized
|
||||
/// </summary>
|
||||
private void RotateLogs()
|
||||
{
|
||||
string maxLog = FileName + MAX_LOG_FILES;
|
||||
|
||||
if (File.Exists(maxLog))
|
||||
{
|
||||
File.Delete(maxLog);
|
||||
}
|
||||
|
||||
for (int i = MAX_LOG_FILES - 1; i >= 0; i--)
|
||||
{
|
||||
string logToMove = i == 0 ? FileName : FileName + i;
|
||||
string movedLogName = FileName + (i + 1);
|
||||
|
||||
if (File.Exists(logToMove))
|
||||
{
|
||||
File.Move(logToMove, movedLogName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Write(string msg, LogType type)
|
||||
{
|
||||
OnLogWriting.Wait();
|
||||
|
||||
string stringType = type.ToString();
|
||||
|
||||
try
|
||||
@ -40,13 +67,12 @@ namespace IW4MAdmin.Application
|
||||
catch (Exception) { }
|
||||
|
||||
string LogLine = $"[{DateTime.Now.ToString("MM.dd.yyy HH:mm:ss.fff")}] - {stringType}: {msg}";
|
||||
lock (ThreadLock)
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
|
||||
|
||||
Console.WriteLine(LogLine);
|
||||
File.AppendAllText(FileName, LogLine + Environment.NewLine);
|
||||
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
|
||||
Console.WriteLine(LogLine);
|
||||
File.AppendAllText(FileName, LogLine + Environment.NewLine);
|
||||
#else
|
||||
if (type == LogType.Error || type == LogType.Verbose)
|
||||
Console.WriteLine(LogLine);
|
||||
@ -54,6 +80,14 @@ namespace IW4MAdmin.Application
|
||||
File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}");
|
||||
#endif
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Well.. It looks like your machine can't event write to the log file. That's something else...");
|
||||
Console.WriteLine(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
OnLogWriting.Release(1);
|
||||
}
|
||||
|
||||
public void WriteVerbose(string msg)
|
100
Application/Misc/TokenAuthentication.cs
Normal file
100
Application/Misc/TokenAuthentication.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
180
Application/RconParsers/BaseRConParser.cs
Normal file
180
Application/RconParsers/BaseRConParser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
10
Application/RconParsers/DynamicRConParser.cs
Normal file
10
Application/RconParsers/DynamicRConParser.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
18
Application/RconParsers/DynamicRConParserConfiguration.cs
Normal file
18
Application/RconParsers/DynamicRConParserConfiguration.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,125 +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;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,11 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
<OutputPath>bin\Prerelease\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DiscordWebhook.py" />
|
||||
</ItemGroup>
|
||||
@ -42,7 +47,6 @@
|
||||
</Interpreter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="config.dev.json" />
|
||||
<Content Include="config.json">
|
||||
<Publish>True</Publish>
|
||||
</Content>
|
||||
@ -50,7 +54,7 @@
|
||||
<Publish>True</Publish>
|
||||
</Content>
|
||||
</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
|
||||
Visual Studio and specify your pre- and post-build commands in
|
||||
the BeforeBuild and AfterBuild targets below. -->
|
||||
@ -59,7 +63,7 @@
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
<ProjectExtensions>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
|
@ -11,13 +11,15 @@
|
||||
<SearchPath>
|
||||
</SearchPath>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<LaunchProvider>Web launcher</LaunchProvider>
|
||||
<LaunchProvider>Standard Python launcher</LaunchProvider>
|
||||
<WebBrowserUrl>http://localhost</WebBrowserUrl>
|
||||
<OutputPath>.</OutputPath>
|
||||
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
|
||||
<Name>GameLogServer</Name>
|
||||
<RootNamespace>GameLogServer</RootNamespace>
|
||||
<InterpreterId>MSBuild|env|$(MSBuildProjectFullPath)</InterpreterId>
|
||||
<EnableNativeCodeDebugging>False</EnableNativeCodeDebugging>
|
||||
<Environment>DEBUG=True</Environment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@ -27,10 +29,16 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
<OutputPath>bin\Prerelease\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GameLogServer\log_reader.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="GameLogServer\restart_resource.py" />
|
||||
<Compile Include="GameLogServer\server.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
@ -42,8 +50,8 @@
|
||||
<Folder Include="GameLogServer\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="FolderProfile.pubxml" />
|
||||
<Content Include="requirements.txt" />
|
||||
<None Include="Stable.pubxml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Interpreter Include="env\">
|
||||
|
@ -5,26 +5,27 @@ import time
|
||||
class LogReader(object):
|
||||
def __init__(self):
|
||||
self.log_file_sizes = {}
|
||||
# (if the file changes more than this, ignore ) - 1 MB
|
||||
self.max_file_size_change = 1000000
|
||||
# (if the file changes more than this, ignore ) - 0.125 MB
|
||||
self.max_file_size_change = 125000
|
||||
# (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):
|
||||
# prevent traversing directories
|
||||
if re.search('r^.+\.\.\\.+$', path):
|
||||
return False
|
||||
# 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
|
||||
# set the initialze size to the current file size
|
||||
file_size = 0
|
||||
|
||||
if path not in self.log_file_sizes:
|
||||
self.log_file_sizes[path] = {
|
||||
'length' : self.file_length(path),
|
||||
'read': time.time()
|
||||
}
|
||||
return ''
|
||||
return True
|
||||
|
||||
# grab the previous values
|
||||
last_length = self.log_file_sizes[path]['length']
|
||||
@ -50,9 +51,9 @@ class LogReader(object):
|
||||
|
||||
# if it's been too long since we read and the amount changed is too great, discard it
|
||||
# todo: do we really want old events? maybe make this an "or"
|
||||
if file_size_difference > self.max_file_size_change and time_difference > self.max_file_time_change:
|
||||
return ''
|
||||
|
||||
if file_size_difference > self.max_file_size_change or time_difference > self.max_file_time_change:
|
||||
return True
|
||||
|
||||
new_log_info = self.get_file_lines(path, file_size_difference)
|
||||
return new_log_info
|
||||
|
||||
|
@ -9,9 +9,11 @@ class LogResource(Resource):
|
||||
|
||||
if log_info is False:
|
||||
print('could not read log file ' + path)
|
||||
|
||||
|
||||
empty_read = (log_info == False) or (log_info == True)
|
||||
|
||||
return {
|
||||
'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
|
||||
}
|
||||
|
29
GameLogServer/GameLogServer/restart_resource.py
Normal file
29
GameLogServer/GameLogServer/restart_resource.py
Normal 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
|
@ -1,9 +1,11 @@
|
||||
from flask import Flask
|
||||
from flask_restful import Api
|
||||
from .log_resource import LogResource
|
||||
from .restart_resource import RestartResource
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def init():
|
||||
api = Api(app)
|
||||
api.add_resource(LogResource, '/log/<string:path>')
|
||||
api.add_resource(RestartResource, '/restart')
|
||||
|
@ -1,12 +1,26 @@
|
||||
Flask==1.0.2
|
||||
aniso8601==3.0.2
|
||||
APScheduler==3.5.3
|
||||
certifi==2018.10.15
|
||||
chardet==3.0.4
|
||||
click==6.7
|
||||
Flask==1.0.2
|
||||
Flask-JWT==0.3.2
|
||||
Flask-JWT-Extended==3.8.1
|
||||
Flask-RESTful==0.3.6
|
||||
idna==2.7
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
MarkupSafe==1.0
|
||||
marshmallow==3.0.0b8
|
||||
pip==9.0.3
|
||||
pytz==2018.5
|
||||
setuptools==39.0.1
|
||||
psutil==5.4.8
|
||||
pygal==2.4.0
|
||||
PyJWT==1.4.2
|
||||
pytz==2018.7
|
||||
requests==2.20.0
|
||||
setuptools==40.5.0
|
||||
six==1.11.0
|
||||
timeago==1.0.8
|
||||
tzlocal==1.5.1
|
||||
urllib3==1.24
|
||||
Werkzeug==0.14.1
|
||||
|
@ -12,4 +12,4 @@ if __name__ == '__main__':
|
||||
except ValueError:
|
||||
PORT = 5555
|
||||
init()
|
||||
app.run(HOST, PORT, debug=True)
|
||||
app.run('0.0.0.0', PORT, debug=False)
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.16
|
||||
@ -11,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
_customcallbacks.gsc = _customcallbacks.gsc
|
||||
README.md = README.md
|
||||
RunPublishPre.cmd = RunPublishPre.cmd
|
||||
RunPublishRelease.cmd = RunPublishRelease.cmd
|
||||
version.txt = version.txt
|
||||
EndProjectSection
|
||||
EndProject
|
||||
@ -38,12 +38,22 @@ Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "DiscordWebhook", "DiscordWe
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlugins", "{3F9ACC27-26DB-49FA-BCD2-50C54A49C9FA}"
|
||||
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\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "GameLogServer", "GameLogServer\GameLogServer.pyproj", "{42EFDA12-10D3-4C40-A210-9483520116BC}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -282,8 +292,8 @@ Global
|
||||
{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.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.Build.0 = 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 = Prerelease|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|x64.ActiveCfg = Debug|Any CPU
|
||||
@ -302,7 +312,7 @@ Global
|
||||
{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|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|x64.ActiveCfg = Release|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|x86.ActiveCfg = Release|Any CPU
|
||||
@ -311,15 +321,13 @@ Global
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|x64.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.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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Any CPU.ActiveCfg = Prerelease|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|x64.ActiveCfg = Release|Any CPU
|
||||
@ -334,6 +342,54 @@ Global
|
||||
{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.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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -346,6 +402,9 @@ Global
|
||||
{B72DEBFB-9D48-4076-8FF5-1FD72A830845} = {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}
|
||||
{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
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
||||
|
@ -11,13 +11,13 @@
|
||||
<SearchPath>
|
||||
</SearchPath>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<LaunchProvider>Web launcher</LaunchProvider>
|
||||
<LaunchProvider>Standard Python launcher</LaunchProvider>
|
||||
<WebBrowserUrl>http://localhost</WebBrowserUrl>
|
||||
<OutputPath>.</OutputPath>
|
||||
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
|
||||
<Name>Master</Name>
|
||||
<RootNamespace>Master</RootNamespace>
|
||||
<InterpreterId>MSBuild|dev_env|$(MSBuildProjectFullPath)</InterpreterId>
|
||||
<InterpreterId>MSBuild|env_master|$(MSBuildProjectFullPath)</InterpreterId>
|
||||
<IsWindowsApplication>False</IsWindowsApplication>
|
||||
<PythonRunWebServerCommand>
|
||||
</PythonRunWebServerCommand>
|
||||
@ -25,6 +25,7 @@
|
||||
</PythonDebugWebServerCommand>
|
||||
<PythonRunWebServerCommandType>script</PythonRunWebServerCommandType>
|
||||
<PythonDebugWebServerCommandType>script</PythonDebugWebServerCommandType>
|
||||
<EnableNativeCodeDebugging>False</EnableNativeCodeDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@ -111,18 +112,18 @@
|
||||
<Folder Include="master\templates\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="FolderProfile.pubxml" />
|
||||
<Content Include="master\config\master.json" />
|
||||
<Content Include="master\templates\serverlist.html" />
|
||||
<None Include="Release.pubxml" />
|
||||
<Content Include="requirements.txt" />
|
||||
<Content Include="master\templates\index.html" />
|
||||
<Content Include="master\templates\layout.html" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Interpreter Include="dev_env\">
|
||||
<Id>dev_env</Id>
|
||||
<Interpreter Include="env_master\">
|
||||
<Id>env_master</Id>
|
||||
<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>
|
||||
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
|
||||
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
|
||||
|
@ -1,8 +1,9 @@
|
||||
|
||||
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.port = port
|
||||
self.version = version
|
||||
self.game = game
|
||||
self.hostname = hostname
|
||||
self.clientnum = clientnum
|
||||
|
@ -5,6 +5,7 @@ from marshmallow import ValidationError
|
||||
from master.schema.instanceschema import InstanceSchema
|
||||
from master import ctx
|
||||
import json
|
||||
from netaddr import IPAddress
|
||||
|
||||
class Instance(Resource):
|
||||
def get(self, id=None):
|
||||
@ -23,7 +24,10 @@ class Instance(Resource):
|
||||
def put(self, id):
|
||||
try:
|
||||
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)
|
||||
except ValidationError as err:
|
||||
return {'message' : err.messages }, 400
|
||||
@ -34,7 +38,10 @@ class Instance(Resource):
|
||||
def post(self):
|
||||
try:
|
||||
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)
|
||||
except ValidationError as err:
|
||||
return {'message' : err.messages }, 400
|
||||
|
@ -4,7 +4,7 @@ from master.models.servermodel import ServerModel
|
||||
class ServerSchema(Schema):
|
||||
id = fields.Int(
|
||||
required=True,
|
||||
validate=validate.Range(1, 2147483647, 'invalid id')
|
||||
validate=validate.Range(0, 25525525525565535, 'invalid id')
|
||||
)
|
||||
ip = fields.Str(
|
||||
required=True
|
||||
@ -13,6 +13,10 @@ class ServerSchema(Schema):
|
||||
required=True,
|
||||
validate=validate.Range(1, 65535, 'invalid port')
|
||||
)
|
||||
version = fields.String(
|
||||
required=False,
|
||||
validate=validate.Length(0, 128, 'invalid server version')
|
||||
)
|
||||
game = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(1, 5, 'invalid game name')
|
||||
@ -31,7 +35,7 @@ class ServerSchema(Schema):
|
||||
)
|
||||
map = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(1, 32, 'invalid map name')
|
||||
validate=validate.Length(0, 64, 'invalid map name')
|
||||
)
|
||||
gametype = fields.String(
|
||||
required=True,
|
||||
|
@ -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
|
||||
Flask==0.12.2
|
||||
Flask==1.0.2
|
||||
Flask-JWT==0.3.2
|
||||
Flask-JWT-Extended==3.8.1
|
||||
Flask-RESTful==0.3.6
|
||||
idna==2.7
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
MarkupSafe==1.0
|
||||
marshmallow==3.0.0b8
|
||||
pip==9.0.1
|
||||
pip==9.0.3
|
||||
psutil==5.4.8
|
||||
pygal==2.4.0
|
||||
PyJWT==1.4.2
|
||||
pytz==2018.4
|
||||
setuptools==39.0.1
|
||||
pytz==2018.7
|
||||
requests==2.20.0
|
||||
setuptools==40.5.0
|
||||
six==1.11.0
|
||||
timeago==1.0.8
|
||||
tzlocal==1.5.1
|
||||
urllib3==1.24
|
||||
Werkzeug==0.14.1
|
||||
|
21
Plugins/AutomessageFeed/AutomessageFeed.csproj
Normal file
21
Plugins/AutomessageFeed/AutomessageFeed.csproj
Normal 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 "$(TargetPath)" "$(SolutionDir)BUILD\Plugins""/>
|
||||
<Exec Command="copy "$(TargetDir)Microsoft.SyndicationFeed.ReaderWriter.dll" "$(SolutionDir)BUILD\Plugins""/>
|
||||
</Target>
|
||||
|
||||
</Project>
|
24
Plugins/AutomessageFeed/Configuration.cs
Normal file
24
Plugins/AutomessageFeed/Configuration.cs
Normal 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";
|
||||
}
|
||||
}
|
85
Plugins/AutomessageFeed/Plugin.cs
Normal file
85
Plugins/AutomessageFeed/Plugin.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,197 +1,200 @@
|
||||
//using SharedLibraryCore;
|
||||
//using SharedLibraryCore.Objects;
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Linq;
|
||||
//using System.Text;
|
||||
//using System.Threading.Tasks;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
//namespace IW4ScriptCommands.Commands
|
||||
//{
|
||||
// class Balance : Command
|
||||
// {
|
||||
// private class TeamAssignment
|
||||
// {
|
||||
// public IW4MAdmin.Plugins.Stats.IW4Info.Team CurrentTeam { get; set; }
|
||||
// public int Num { get; set; }
|
||||
// public IW4MAdmin.Plugins.Stats.Models.EFClientStatistics Stats { get; set; }
|
||||
// }
|
||||
// public Balance() : base("balance", "balance teams", "bal", Player.Permission.Trusted, false, null)
|
||||
// {
|
||||
// }
|
||||
namespace IW4ScriptCommands.Commands
|
||||
{
|
||||
class Balance
|
||||
{
|
||||
private class TeamAssignment
|
||||
{
|
||||
public IW4MAdmin.Plugins.Stats.IW4Info.Team CurrentTeam { get; set; }
|
||||
public int Num { get; set; }
|
||||
public IW4MAdmin.Plugins.Stats.Models.EFClientStatistics Stats { get; set; }
|
||||
}
|
||||
|
||||
// public override async Task ExecuteAsync(GameEvent E)
|
||||
// {
|
||||
// string teamsString = (await E.Owner.GetDvarAsync<string>("sv_iw4madmin_teams")).Value;
|
||||
public static string GetTeamAssignments(EFClient client, bool isDisconnect, Server server, string teamsString = "")
|
||||
{
|
||||
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)
|
||||
// .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 = E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong())?.ClientNumber ?? -1,
|
||||
// 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
|
||||
if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(server.MaxClients / 2.0)
|
||||
|| scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(server.MaxClients / 2.0))
|
||||
{
|
||||
// E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// // at least one team is full so we can't balance
|
||||
// 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>();
|
||||
|
||||
// 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()
|
||||
// {
|
||||
// Num = c.ClientNumber,
|
||||
// Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()),
|
||||
// CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()).Team
|
||||
// })
|
||||
// .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)
|
||||
// .OrderByDescending(c => c.Stats.Performance)
|
||||
// .ToList();
|
||||
var activeClients = _c.Select(c => new TeamAssignment()
|
||||
{
|
||||
Num = c.ClientNumber,
|
||||
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.EndPoint),
|
||||
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 => c.CurrentTeam != scriptClientTeams.FirstOrDefault(p => p.Num == c.Num)?.CurrentTeam)
|
||||
.OrderByDescending(c => c.Stats.Performance)
|
||||
.ToList();
|
||||
|
||||
// var alliesTeam = scriptClientTeams
|
||||
// .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies)
|
||||
// .Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
|
||||
// .ToList();
|
||||
var alliesTeam = scriptClientTeams
|
||||
.Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies)
|
||||
.Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
|
||||
.ToList();
|
||||
|
||||
// var axisTeam = scriptClientTeams
|
||||
// .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis)
|
||||
// .Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
|
||||
// .ToList();
|
||||
var axisTeam = scriptClientTeams
|
||||
.Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis)
|
||||
.Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
|
||||
.ToList();
|
||||
|
||||
// while (activeClients.Count() > 0)
|
||||
// {
|
||||
// int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
|
||||
// double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
|
||||
// axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
|
||||
while (activeClients.Count() > 0)
|
||||
{
|
||||
int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
|
||||
double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
|
||||
axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
|
||||
|
||||
// if (teamSizeDifference == 0)
|
||||
// {
|
||||
// if (performanceDisparity == 0)
|
||||
// {
|
||||
// alliesTeam.Add(activeClients.First());
|
||||
// activeClients.RemoveAt(0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (performanceDisparity > 0)
|
||||
// {
|
||||
// axisTeam.Add(activeClients.First());
|
||||
// activeClients.RemoveAt(0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// alliesTeam.Add(activeClients.First());
|
||||
// activeClients.RemoveAt(0);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else if (teamSizeDifference > 0)
|
||||
// {
|
||||
// if (performanceDisparity > 0)
|
||||
// {
|
||||
// axisTeam.Add(activeClients.First());
|
||||
// activeClients.RemoveAt(0);
|
||||
// }
|
||||
if (teamSizeDifference == 0)
|
||||
{
|
||||
if (performanceDisparity == 0)
|
||||
{
|
||||
alliesTeam.Add(activeClients.First());
|
||||
activeClients.RemoveAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (performanceDisparity > 0)
|
||||
{
|
||||
axisTeam.Add(activeClients.First());
|
||||
activeClients.RemoveAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
alliesTeam.Add(activeClients.First());
|
||||
activeClients.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (teamSizeDifference > 0)
|
||||
{
|
||||
if (performanceDisparity > 0)
|
||||
{
|
||||
axisTeam.Add(activeClients.First());
|
||||
activeClients.RemoveAt(0);
|
||||
}
|
||||
|
||||
// else
|
||||
// {
|
||||
// axisTeam.Add(activeClients.Last());
|
||||
// activeClients.RemoveAt(activeClients.Count - 1);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (performanceDisparity > 0)
|
||||
// {
|
||||
// alliesTeam.Add(activeClients.First());
|
||||
// activeClients.RemoveAt(0);
|
||||
// }
|
||||
else
|
||||
{
|
||||
axisTeam.Add(activeClients.Last());
|
||||
activeClients.RemoveAt(activeClients.Count - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (performanceDisparity > 0)
|
||||
{
|
||||
alliesTeam.Add(activeClients.First());
|
||||
activeClients.RemoveAt(0);
|
||||
}
|
||||
|
||||
// else
|
||||
// {
|
||||
// alliesTeam.Add(activeClients.Last());
|
||||
// activeClients.RemoveAt(activeClients.Count - 1);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
else
|
||||
{
|
||||
alliesTeam.Add(activeClients.Last());
|
||||
activeClients.RemoveAt(activeClients.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// alliesTeam = alliesTeam.OrderByDescending(t => t.Stats.Performance)
|
||||
// .ToList();
|
||||
alliesTeam = alliesTeam.OrderByDescending(t => t.Stats.Performance)
|
||||
.ToList();
|
||||
|
||||
// axisTeam = axisTeam.OrderByDescending(t => t.Stats.Performance)
|
||||
// .ToList();
|
||||
axisTeam = axisTeam.OrderByDescending(t => t.Stats.Performance)
|
||||
.ToList();
|
||||
|
||||
// while (Math.Abs(alliesTeam.Count - axisTeam.Count) > 1)
|
||||
// {
|
||||
// int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
|
||||
// double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
|
||||
// axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
|
||||
while (Math.Abs(alliesTeam.Count - axisTeam.Count) > 1)
|
||||
{
|
||||
int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
|
||||
double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
|
||||
axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
|
||||
|
||||
// if (teamSizeDifference > 0)
|
||||
// {
|
||||
// if (performanceDisparity > 0)
|
||||
// {
|
||||
// axisTeam.Add(alliesTeam.First());
|
||||
// alliesTeam.RemoveAt(0);
|
||||
// }
|
||||
if (teamSizeDifference > 0)
|
||||
{
|
||||
if (performanceDisparity > 0)
|
||||
{
|
||||
axisTeam.Add(alliesTeam.First());
|
||||
alliesTeam.RemoveAt(0);
|
||||
}
|
||||
|
||||
// else
|
||||
// {
|
||||
// axisTeam.Add(alliesTeam.Last());
|
||||
// alliesTeam.RemoveAt(axisTeam.Count - 1);
|
||||
// }
|
||||
// }
|
||||
else
|
||||
{
|
||||
axisTeam.Add(alliesTeam.Last());
|
||||
alliesTeam.RemoveAt(axisTeam.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// else
|
||||
// {
|
||||
// if (performanceDisparity > 0)
|
||||
// {
|
||||
// alliesTeam.Add(axisTeam.Last());
|
||||
// axisTeam.RemoveAt(axisTeam.Count - 1);
|
||||
// }
|
||||
else
|
||||
{
|
||||
if (performanceDisparity > 0)
|
||||
{
|
||||
alliesTeam.Add(axisTeam.Last());
|
||||
axisTeam.RemoveAt(axisTeam.Count - 1);
|
||||
}
|
||||
|
||||
// else
|
||||
// {
|
||||
// alliesTeam.Add(axisTeam.First());
|
||||
// axisTeam.RemoveAt(0);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
else
|
||||
{
|
||||
alliesTeam.Add(axisTeam.First());
|
||||
axisTeam.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// foreach (var assignment in alliesTeam)
|
||||
// {
|
||||
// teamAssignments.Add($"{assignment.Num},2");
|
||||
// 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;
|
||||
// }
|
||||
foreach (var assignment in alliesTeam)
|
||||
{
|
||||
teamAssignments.Add($"{assignment.Num},2");
|
||||
assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies;
|
||||
}
|
||||
|
||||
// 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(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
|
||||
// return;
|
||||
// }
|
||||
foreach (var assignment in axisTeam)
|
||||
{
|
||||
teamAssignments.Add($"{assignment.Num},3");
|
||||
assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis;
|
||||
}
|
||||
|
||||
// if (E.Origin?.Level > Player.Permission.Administrator)
|
||||
// {
|
||||
// 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)}");
|
||||
// }
|
||||
//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)
|
||||
//{
|
||||
// //E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
|
||||
// return string.Empty;
|
||||
//}
|
||||
|
||||
// string args = string.Join(",", teamAssignments);
|
||||
// await E.Owner.ExecuteCommandAsync($"sv_iw4madmin_command \"balance:{args}\"");
|
||||
// await E.Origin.Tell("Balance command sent");
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//if (E.Origin?.Level > Player.Permission.Administrator)
|
||||
//{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
54
Plugins/IW4ScriptCommands/GscApiController.cs
Normal file
54
Plugins/IW4ScriptCommands/GscApiController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,9 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
@ -17,7 +19,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App"/>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -17,15 +17,10 @@ namespace IW4ScriptCommands
|
||||
|
||||
public Task OnEventAsync(GameEvent E, Server S)
|
||||
{
|
||||
//if (E.Type == GameEvent.EventType.JoinTeam || E.Type == GameEvent.EventType.Disconnect)
|
||||
//{
|
||||
// E.Origin = new SharedLibraryCore.Objects.Player()
|
||||
// {
|
||||
// ClientId = 1,
|
||||
// CurrentServer = E.Owner
|
||||
// };
|
||||
// return new Commands.Balance().ExecuteAsync(E);
|
||||
//}
|
||||
if (E.Type == GameEvent.EventType.Start)
|
||||
{
|
||||
return S.SetDvarAsync("sv_iw4madmin_serverid", S.EndPoint);
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Warn)
|
||||
{
|
||||
|
@ -1,14 +1,12 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Login.Commands
|
||||
{
|
||||
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()
|
||||
{
|
||||
@ -21,18 +19,22 @@ namespace IW4MAdmin.Plugins.Login.Commands
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
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;
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]);
|
||||
string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, client.PasswordSalt));
|
||||
|
||||
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"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.Login</PackageId>
|
||||
@ -21,7 +22,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App"/>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -3,9 +3,9 @@ using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Login
|
||||
{
|
||||
@ -22,12 +22,13 @@ namespace IW4MAdmin.Plugins.Login
|
||||
|
||||
public Task OnEventAsync(GameEvent E, Server S)
|
||||
{
|
||||
if (E.Remote || Config.RequirePrivilegedClientLogin == false)
|
||||
if (E.IsRemote || Config.RequirePrivilegedClientLogin == false)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (E.Type == GameEvent.EventType.Connect)
|
||||
{
|
||||
AuthorizedClients.TryAdd(E.Origin.ClientId, false);
|
||||
E.Origin.SetAdditionalProperty("IsLoggedIn", false);
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Disconnect)
|
||||
@ -37,11 +38,11 @@ namespace IW4MAdmin.Plugins.Login
|
||||
|
||||
if (E.Type == GameEvent.EventType.Command)
|
||||
{
|
||||
if (E.Origin.Level < Player.Permission.Moderator ||
|
||||
E.Origin.Level == Player.Permission.Console)
|
||||
if (E.Origin.Level < EFClient.Permission.Moderator ||
|
||||
E.Origin.Level == EFClient.Permission.Console)
|
||||
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 &&
|
||||
client?.Password == null)
|
||||
@ -51,7 +52,14 @@ namespace IW4MAdmin.Plugins.Login
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (!AuthorizedClients[E.Origin.ClientId])
|
||||
{
|
||||
throw new AuthorizationException(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_AUTH"]);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
E.Origin.SetAdditionalProperty("IsLoggedIn", true);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -5,6 +5,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
@ -25,7 +26,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
public Task OnEventAsync(GameEvent E, Server S)
|
||||
{
|
||||
if (!Settings.Configuration().EnableProfanityDeterment)
|
||||
return Task.CompletedTask; ;
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (E.Type == GameEvent.EventType.Connect)
|
||||
{
|
||||
@ -48,9 +49,10 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
|
||||
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
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -84,20 +86,14 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
var clientProfanity = ProfanityCounts[E.Origin.ClientId];
|
||||
if (clientProfanity.Infringements >= Settings.Configuration().KickAfterInfringementCount)
|
||||
{
|
||||
clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
});
|
||||
clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, Utilities.IW4MAdminClient(E.Owner));
|
||||
}
|
||||
|
||||
else if (clientProfanity.Infringements < Settings.Configuration().KickAfterInfringementCount)
|
||||
{
|
||||
clientProfanity.Infringements++;
|
||||
|
||||
clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
});
|
||||
clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, Utilities.IW4MAdminClient(E.Owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId>
|
||||
@ -19,7 +20,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App"/>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -1,16 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
{
|
||||
class Tracking
|
||||
{
|
||||
public Player Client { get; private set; }
|
||||
public EFClient Client { get; private set; }
|
||||
public int Infringements { get; set; }
|
||||
|
||||
public Tracking(Player client)
|
||||
public Tracking(EFClient client)
|
||||
{
|
||||
Client = client;
|
||||
Infringements = 0;
|
||||
|
37
Plugins/ScriptPlugins/ParserCoD4x.js
Normal file
37
Plugins/ScriptPlugins/ParserCoD4x.js
Normal 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) {
|
||||
}
|
||||
};
|
35
Plugins/ScriptPlugins/ParserIW4x.js
Normal file
35
Plugins/ScriptPlugins/ParserIW4x.js
Normal 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) {
|
||||
}
|
||||
};
|
50
Plugins/ScriptPlugins/ParserPT6.js
Normal file
50
Plugins/ScriptPlugins/ParserPT6.js
Normal 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) {
|
||||
}
|
||||
};
|
43
Plugins/ScriptPlugins/ParserTeknoMW3.js
Normal file
43
Plugins/ScriptPlugins/ParserTeknoMW3.js
Normal 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) {
|
||||
}
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.0,
|
||||
version: 1.1,
|
||||
name: 'Shared GUID Kicker Plugin',
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
@ -10,8 +10,7 @@ var plugin = {
|
||||
}
|
||||
|
||||
// connect or join event
|
||||
if (gameEvent.Type === 3 ||
|
||||
gameEvent.Type === 4) {
|
||||
if (gameEvent.Type === 3) {
|
||||
// this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID
|
||||
if (gameEvent.Origin.NetworkId === -805366929435212061) {
|
||||
gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient);
|
||||
|
@ -28,7 +28,6 @@ var plugin = {
|
||||
var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result;
|
||||
var co = re.Content;
|
||||
var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result);
|
||||
// todo: does this work as expected now?
|
||||
co.Dispose();
|
||||
re.Dispose();
|
||||
cl.Dispose();
|
||||
@ -39,10 +38,7 @@ var plugin = {
|
||||
|
||||
if (usingVPN) {
|
||||
this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')');
|
||||
var library = importNamespace('SharedLibraryCore');
|
||||
var kickOrigin = new library.Objects.Player();
|
||||
kickOrigin.ClientId = 1;
|
||||
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], kickOrigin);
|
||||
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient);
|
||||
}
|
||||
},
|
||||
|
||||
@ -55,7 +51,7 @@ var plugin = {
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
this.manager = manager;
|
||||
this.logger = manager.GetLogger();
|
||||
this.logger = manager.GetLogger(0);
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
|
@ -1,7 +1,7 @@
|
||||
using SharedLibraryCore.Helpers;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -20,14 +20,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
};
|
||||
|
||||
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
||||
public const int QUEUE_COUNT = 10;
|
||||
|
||||
public List<EFClientKill> QueuedHits { get; set; }
|
||||
int Kills;
|
||||
int HitCount;
|
||||
Dictionary<IW4Info.HitLocation, int> HitLocationCount;
|
||||
double AngleDifferenceAverage;
|
||||
EFClientStatistics ClientStats;
|
||||
DateTime LastHit;
|
||||
long LastOffset;
|
||||
IW4Info.WeaponName LastWeapon;
|
||||
ILogger Log;
|
||||
Strain Strain;
|
||||
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
||||
@ -37,49 +39,55 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
Log = log;
|
||||
HitLocationCount = new Dictionary<IW4Info.HitLocation, int>();
|
||||
foreach (var loc in Enum.GetValues(typeof(IW4Info.HitLocation)))
|
||||
{
|
||||
HitLocationCount.Add((IW4Info.HitLocation)loc, 0);
|
||||
}
|
||||
|
||||
ClientStats = clientStats;
|
||||
Strain = new Strain();
|
||||
Tracker = new ChangeTracking<EFACSnapshot>();
|
||||
QueuedHits = new List<EFClientKill>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyze kill and see if performed by a cheater
|
||||
/// </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>
|
||||
public DetectionPenaltyResult ProcessKill(EFClientKill kill, bool isDamage)
|
||||
public DetectionPenaltyResult ProcessHit(EFClientKill hit, bool isDamage)
|
||||
{
|
||||
if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
|
||||
kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
|
||||
kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
|
||||
kill.HitLoc == IW4Info.HitLocation.none || kill.TimeOffset - LastOffset < 0)
|
||||
if ((hit.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
|
||||
hit.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
|
||||
hit.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
|
||||
hit.HitLoc == IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 ||
|
||||
// hack: prevents false positives
|
||||
(LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50))
|
||||
{
|
||||
return new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Any,
|
||||
};
|
||||
}
|
||||
|
||||
DetectionPenaltyResult result = null;
|
||||
LastWeapon = hit.Weapon;
|
||||
|
||||
if (LastHit == DateTime.MinValue)
|
||||
LastHit = DateTime.UtcNow;
|
||||
HitLocationCount[hit.HitLoc]++;
|
||||
HitCount++;
|
||||
|
||||
HitLocationCount[kill.HitLoc]++;
|
||||
if (!isDamage)
|
||||
{
|
||||
Kills++;
|
||||
}
|
||||
|
||||
HitCount++;
|
||||
|
||||
#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
|
||||
var hitLoc = ClientStats.HitLocations
|
||||
.First(hl => hl.Location == kill.HitLoc);
|
||||
.First(hl => hl.Location == hit.HitLoc);
|
||||
|
||||
float previousAverage = hitLoc.HitOffsetAverage;
|
||||
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) &&
|
||||
hitLoc.HitCount > 100)
|
||||
{
|
||||
Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
|
||||
Log.WriteDebug($"Lifetime Average = {newAverage}");
|
||||
Log.WriteDebug($"Bone = {hitLoc.Location}");
|
||||
Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
|
||||
Log.WriteDebug($"ID = {kill.AttackerId}");
|
||||
//Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
|
||||
//Log.WriteDebug($"Lifetime Average = {newAverage}");
|
||||
//Log.WriteDebug($"Bone = {hitLoc.Location}");
|
||||
//Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
|
||||
//Log.WriteDebug($"ID = {hit.AttackerId}");
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -110,10 +118,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
if (sessAverage > Thresholds.MaxOffset(HitCount) &&
|
||||
HitCount > 30)
|
||||
{
|
||||
Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
|
||||
Log.WriteDebug($"Session Average = {sessAverage}");
|
||||
Log.WriteDebug($"HitCount = {HitCount}");
|
||||
Log.WriteDebug($"ID = {kill.AttackerId}");
|
||||
//Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
|
||||
//Log.WriteDebug($"Session Average = {sessAverage}");
|
||||
//Log.WriteDebug($"HitCount = {HitCount}");
|
||||
//Log.WriteDebug($"ID = {hit.AttackerId}");
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -130,8 +138,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
#endif
|
||||
}
|
||||
|
||||
double currentStrain = Strain.GetStrain(isDamage, kill.Damage, kill.Distance / 0.0254, kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset));
|
||||
LastOffset = kill.TimeOffset;
|
||||
double currentStrain = Strain.GetStrain(isDamage, hit.Damage, hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, hit.TimeOffset - LastOffset));
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Current Strain: {currentStrain}");
|
||||
#endif
|
||||
LastOffset = hit.TimeOffset;
|
||||
|
||||
if (currentStrain > ClientStats.MaxStrain)
|
||||
{
|
||||
@ -139,8 +150,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
}
|
||||
|
||||
// flag
|
||||
if (currentStrain > Thresholds.MaxStrainFlag &&
|
||||
HitCount >= 10)
|
||||
if (currentStrain > Thresholds.MaxStrainFlag)
|
||||
{
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -153,7 +163,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
// ban
|
||||
if (currentStrain > Thresholds.MaxStrainBan &&
|
||||
HitCount >= 15)
|
||||
HitCount >= 5)
|
||||
{
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -163,11 +173,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
Type = DetectionType.Strain
|
||||
};
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Log.WriteDebug($"Current Strain: {currentStrain}");
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region SESSION_RATIOS
|
||||
@ -197,13 +202,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
if (currentHeadshotRatio > maxHeadshotLerpValueForBan)
|
||||
{
|
||||
Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
{
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
}
|
||||
|
||||
Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
@ -218,13 +226,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
else
|
||||
{
|
||||
Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
{
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
}
|
||||
|
||||
Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
@ -247,13 +258,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
|
||||
{
|
||||
Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
{
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
}
|
||||
|
||||
Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
@ -268,13 +282,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
else
|
||||
{
|
||||
Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
{
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
}
|
||||
|
||||
Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
@ -308,15 +325,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills + 30)
|
||||
{
|
||||
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"**Chest Hits: {chestHits}");
|
||||
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
Log.WriteDebug(sb.ToString());
|
||||
//Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
|
||||
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
//Log.WriteDebug($"**Chest Hits: {chestHits}");
|
||||
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
//Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
|
||||
//var sb = new StringBuilder();
|
||||
//foreach (var kvp in HitLocationCount)
|
||||
// sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
//Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -329,16 +346,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"**Chest Hits: {chestHits}");
|
||||
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
Log.WriteDebug(sb.ToString());
|
||||
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
|
||||
//Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
|
||||
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
//Log.WriteDebug($"**Chest Hits: {chestHits}");
|
||||
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
//Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
|
||||
//var sb = new StringBuilder();
|
||||
//foreach (var kvp in HitLocationCount)
|
||||
// sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
//Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -356,31 +372,31 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
Tracker.OnChange(new EFACSnapshot()
|
||||
{
|
||||
When = kill.When,
|
||||
When = hit.When,
|
||||
ClientId = ClientStats.ClientId,
|
||||
SessionAngleOffset = AngleDifferenceAverage,
|
||||
CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds,
|
||||
CurrentStrain = currentStrain,
|
||||
CurrentViewAngle = kill.ViewAngles,
|
||||
CurrentViewAngle = hit.ViewAngles,
|
||||
Hits = HitCount,
|
||||
Kills = Kills,
|
||||
Deaths = ClientStats.SessionDeaths,
|
||||
HitDestinationId = kill.DeathOrigin.Vector3Id,
|
||||
HitDestination = kill.DeathOrigin,
|
||||
HitOriginId = kill.KillOrigin.Vector3Id,
|
||||
HitOrigin = kill.KillOrigin,
|
||||
HitDestinationId = hit.DeathOrigin.Vector3Id,
|
||||
HitDestination = hit.DeathOrigin,
|
||||
HitOriginId = hit.KillOrigin.Vector3Id,
|
||||
HitOrigin = hit.KillOrigin,
|
||||
EloRating = ClientStats.EloRating,
|
||||
HitLocation = kill.HitLoc,
|
||||
HitLocation = hit.HitLoc,
|
||||
LastStrainAngle = Strain.LastAngle,
|
||||
PredictedViewAngles = kill.AnglesList,
|
||||
PredictedViewAngles = hit.AnglesList,
|
||||
// this is in "meters"
|
||||
Distance = kill.Distance,
|
||||
Distance = hit.Distance,
|
||||
SessionScore = ClientStats.SessionScore,
|
||||
HitType = kill.DeathType,
|
||||
HitType = hit.DeathType,
|
||||
SessionSPM = ClientStats.SessionSPM,
|
||||
StrainAngleBetween = Strain.LastDistance,
|
||||
TimeSinceLastEvent = (int)Strain.LastDeltaTime,
|
||||
WeaponId = kill.Weapon
|
||||
WeaponId = hit.Weapon
|
||||
});
|
||||
|
||||
return result ?? new DetectionPenaltyResult()
|
||||
@ -409,15 +425,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan)
|
||||
{
|
||||
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
|
||||
Log.WriteDebug($"ClientId: {stats.ClientId}");
|
||||
Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
|
||||
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var location in stats.HitLocations)
|
||||
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
|
||||
Log.WriteDebug(sb.ToString());
|
||||
//Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
|
||||
//Log.WriteDebug($"ClientId: {stats.ClientId}");
|
||||
//Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
|
||||
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
//Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
|
||||
//var sb = new StringBuilder();
|
||||
//foreach (var location in stats.HitLocations)
|
||||
// sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
|
||||
//Log.WriteDebug(sb.ToString());
|
||||
|
||||
return new DetectionPenaltyResult()
|
||||
{
|
||||
@ -430,15 +446,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**");
|
||||
Log.WriteDebug($"ClientId: {stats.ClientId}");
|
||||
Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
|
||||
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var location in stats.HitLocations)
|
||||
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
|
||||
Log.WriteDebug(sb.ToString());
|
||||
//Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**");
|
||||
//Log.WriteDebug($"ClientId: {stats.ClientId}");
|
||||
//Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
|
||||
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
//Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
|
||||
//var sb = new StringBuilder();
|
||||
//foreach (var location in stats.HitLocations)
|
||||
// sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
|
||||
//Log.WriteDebug(sb.ToString());
|
||||
|
||||
return new DetectionPenaltyResult()
|
||||
{
|
||||
|
@ -14,8 +14,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
public Vector3 LastAngle { 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)
|
||||
{
|
||||
if (LastAngle == null)
|
||||
@ -25,14 +23,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
double decayFactor = GetDecay(deltaTime);
|
||||
CurrentStrain *= decayFactor;
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine($"Decay Factor = {decayFactor} ");
|
||||
#endif
|
||||
|
||||
double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle);
|
||||
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
|
||||
if ((distance[0] == 0 && distance[1] == 0) ||
|
||||
deltaTime == 0 ||
|
||||
@ -41,14 +40,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
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;
|
||||
|
||||
CurrentStrain += newStrain;
|
||||
|
||||
if (CurrentStrain > Thresholds.MaxStrainBan)
|
||||
TimesReachedMaxStrain++;
|
||||
|
||||
LastAngle = newAngle;
|
||||
return CurrentStrain;
|
||||
}
|
||||
|
@ -27,9 +27,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
public const int HighSampleMinKills = 100;
|
||||
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 const double MaxStrainFlag = 0.36;
|
||||
|
||||
|
@ -8,6 +8,8 @@ using SharedLibraryCore.Objects;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using SharedLibraryCore.Database;
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
@ -15,7 +17,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
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>()
|
||||
{
|
||||
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
|
||||
@ -34,7 +37,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
join alias in db.Aliases
|
||||
on client.CurrentAliasId equals alias.AliasId
|
||||
where stats.ServerId == serverId
|
||||
where client.Level != Player.Permission.Banned
|
||||
where client.Level != EFClient.Permission.Banned
|
||||
where client.LastConnection >= thirtyDaysAgo
|
||||
orderby stats.Kills descending
|
||||
select new
|
||||
@ -55,7 +58,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
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)
|
||||
{
|
||||
|
@ -1,39 +1,46 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (E.Origin.ClientNumber >= 0)
|
||||
{
|
||||
var svc = new SharedLibraryCore.Services.GenericRepository<EFClientStatistics>();
|
||||
int serverId = E.Owner.GetHashCode();
|
||||
var stats = svc.Find(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId).First();
|
||||
|
||||
stats.Deaths = 0;
|
||||
stats.Kills = 0;
|
||||
stats.SPM = 0.0;
|
||||
stats.Skill = 0.0;
|
||||
stats.TimePlayed = 0;
|
||||
// todo: make this more dynamic
|
||||
stats.EloRating = 200.0;
|
||||
long serverId = await Helpers.StatManager.GetIdForServer(E.Owner);
|
||||
|
||||
// reset the cached version
|
||||
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
|
||||
EFClientStatistics clientStats;
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
clientStats = await ctx.Set<EFClientStatistics>()
|
||||
.Where(s => s.ClientId == E.Origin.ClientId)
|
||||
.Where(s => s.ServerId == serverId)
|
||||
.FirstAsync();
|
||||
|
||||
// fixme: this doesn't work properly when another context exists
|
||||
await svc.SaveChangesAsync();
|
||||
clientStats.Deaths = 0;
|
||||
clientStats.Kills = 0;
|
||||
clientStats.SPM = 0.0;
|
||||
clientStats.Skill = 0.0;
|
||||
clientStats.TimePlayed = 0;
|
||||
// todo: make this more dynamic
|
||||
clientStats.EloRating = 200.0;
|
||||
|
||||
// reset the cached version
|
||||
Plugin.Manager.ResetStats(E.Origin.ClientId, serverId);
|
||||
|
||||
// fixme: this doesn't work properly when another context exists
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
|
||||
}
|
||||
|
||||
|
@ -9,15 +9,16 @@ using SharedLibraryCore.Services;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using SharedLibraryCore.Database;
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
class TopStats : Command
|
||||
{
|
||||
|
||||
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>()
|
||||
{
|
||||
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
|
||||
@ -34,7 +35,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
on client.CurrentAliasId equals alias.AliasId
|
||||
where stats.ServerId == serverId
|
||||
where stats.TimePlayed >= Plugin.Config.Configuration().TopPlayersMinPlayTime
|
||||
where client.Level != Player.Permission.Banned
|
||||
where client.Level != EFClient.Permission.Banned
|
||||
where client.LastConnection >= fifteenDaysAgo
|
||||
orderby stats.Performance descending
|
||||
select new
|
||||
@ -67,7 +68,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
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)
|
||||
{
|
||||
|
@ -7,12 +7,16 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
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()
|
||||
{
|
||||
@ -39,19 +43,27 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
}
|
||||
}
|
||||
|
||||
var clientStats = new GenericRepository<EFClientStatistics>();
|
||||
int serverId = E.Owner.GetHashCode();
|
||||
long serverId = await StatManager.GetIdForServer(E.Owner);
|
||||
|
||||
if (E.Target != null)
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
pStats = (await clientStats.FindAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId)).First();
|
||||
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()}";
|
||||
}
|
||||
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}";
|
||||
|
||||
else
|
||||
{
|
||||
pStats = (await clientStats.FindAsync(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId)).First();
|
||||
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()}";
|
||||
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()} | {performanceRankingString}";
|
||||
}
|
||||
|
||||
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)));
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Message.IsBroadcastCommand())
|
||||
|
@ -1,53 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using IW4MAdmin.Plugins.Stats.Cheat;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using IW4MAdmin.Plugins.Stats.Web.Dtos;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.Commands;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
using IW4MAdmin.Plugins.Stats.Web.Dtos;
|
||||
using SharedLibraryCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Services;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
{
|
||||
public class StatManager
|
||||
{
|
||||
private ConcurrentDictionary<int, ServerStats> Servers;
|
||||
private ConcurrentDictionary<int, ThreadSafeStatsService> ContextThreads;
|
||||
private ConcurrentDictionary<long, ServerStats> Servers;
|
||||
private ILogger Log;
|
||||
private IManager Manager;
|
||||
|
||||
private readonly IManager Manager;
|
||||
|
||||
private readonly SemaphoreSlim OnProcessingPenalty;
|
||||
private readonly SemaphoreSlim OnProcessingSensitive;
|
||||
|
||||
public StatManager(IManager mgr)
|
||||
{
|
||||
Servers = new ConcurrentDictionary<int, ServerStats>();
|
||||
ContextThreads = new ConcurrentDictionary<int, ThreadSafeStatsService>();
|
||||
Log = mgr.GetLogger();
|
||||
Servers = new ConcurrentDictionary<long, ServerStats>();
|
||||
Log = mgr.GetLogger(0);
|
||||
Manager = mgr;
|
||||
OnProcessingPenalty = 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);
|
||||
return (r) => r.ServerId == serverId &&
|
||||
r.When > fifteenDaysAgo &&
|
||||
r.RatingHistory.Client.Level != Player.Permission.Banned &&
|
||||
r.RatingHistory.Client.Level != EFClient.Permission.Banned &&
|
||||
r.Newest &&
|
||||
r.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime;
|
||||
}
|
||||
@ -57,7 +57,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
/// </summary>
|
||||
/// <param name="clientId">client id of the player</param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> GetClientOverallRanking(int clientId)
|
||||
public static async Task<int> GetClientOverallRanking(int clientId)
|
||||
{
|
||||
using (var context = new DatabaseContext(true))
|
||||
{
|
||||
@ -147,7 +147,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
});
|
||||
|
||||
#if DEBUG == true
|
||||
var statsInfoSql = iqStatsInfo.ToSql();
|
||||
var statsInfoSql = iqStatsInfo.ToSql();
|
||||
#endif
|
||||
var topPlayers = await iqStatsInfo.ToListAsync();
|
||||
|
||||
@ -188,33 +188,51 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
/// <param name="sv"></param>
|
||||
public void AddServer(Server sv)
|
||||
{
|
||||
// insert the server if it does not exist
|
||||
try
|
||||
{
|
||||
int serverId = sv.GetHashCode();
|
||||
var statsSvc = new ThreadSafeStatsService();
|
||||
ContextThreads.TryAdd(serverId, statsSvc);
|
||||
long serverId = GetIdForServer(sv).Result;
|
||||
EFServer server;
|
||||
|
||||
// get the server from the database if it exists, otherwise create and insert a new one
|
||||
var server = statsSvc.ServerSvc.Find(c => c.ServerId == serverId).FirstOrDefault();
|
||||
if (server == null)
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
server = new EFServer()
|
||||
{
|
||||
Port = sv.GetPort(),
|
||||
Active = true,
|
||||
ServerId = serverId
|
||||
};
|
||||
var serverSet = ctx.Set<EFServer>();
|
||||
// get the server from the database if it exists, otherwise create and insert a new one
|
||||
server = serverSet.FirstOrDefault(s => s.ServerId == serverId);
|
||||
|
||||
statsSvc.ServerSvc.Insert(server);
|
||||
// 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)
|
||||
{
|
||||
server = new EFServer()
|
||||
{
|
||||
Port = sv.GetPort(),
|
||||
EndPoint = sv.ToString(),
|
||||
ServerId = serverId
|
||||
};
|
||||
|
||||
server = serverSet.Add(server).Entity;
|
||||
// this doesn't need to be async as it's during initialization
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// this doesn't need to be async as it's during initialization
|
||||
statsSvc.ServerSvc.SaveChanges();
|
||||
// check to see if the stats have ever been initialized
|
||||
InitializeServerStats(sv);
|
||||
statsSvc.ServerStatsSvc.SaveChanges();
|
||||
var serverStats = InitializeServerStats(server.ServerId);
|
||||
|
||||
var serverStats = statsSvc.ServerStatsSvc.Find(c => c.ServerId == serverId).FirstOrDefault();
|
||||
Servers.TryAdd(serverId, new ServerStats(server, serverStats)
|
||||
{
|
||||
IsTeamBased = sv.Gametype != "dm"
|
||||
@ -224,6 +242,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}");
|
||||
Log.WriteDebug(e.GetExceptionInfo());
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,95 +251,135 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
/// </summary>
|
||||
/// <param name="pl">Player to add/retrieve stats for</param>
|
||||
/// <returns>EFClientStatistic of specified player</returns>
|
||||
public async Task<EFClientStatistics> AddPlayer(Player pl)
|
||||
public async Task<EFClientStatistics> AddPlayer(EFClient pl)
|
||||
{
|
||||
int serverId = pl.CurrentServer.GetHashCode();
|
||||
await OnProcessingSensitive.WaitAsync();
|
||||
|
||||
if (!Servers.ContainsKey(serverId))
|
||||
try
|
||||
{
|
||||
Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found");
|
||||
return null;
|
||||
}
|
||||
long serverId = await GetIdForServer(pl.CurrentServer);
|
||||
|
||||
var playerStats = Servers[serverId].PlayerStats;
|
||||
var detectionStats = Servers[serverId].PlayerDetections;
|
||||
var statsSvc = ContextThreads[serverId];
|
||||
|
||||
if (playerStats.ContainsKey(pl.ClientId))
|
||||
{
|
||||
Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the client's stats from the database if it exists, otherwise create and attach a new one
|
||||
// if this fails we want to throw an exception
|
||||
var clientStatsSvc = statsSvc.ClientStatSvc;
|
||||
var clientStats = clientStatsSvc.Find(c => c.ClientId == pl.ClientId && c.ServerId == serverId).FirstOrDefault();
|
||||
|
||||
if (clientStats == null)
|
||||
{
|
||||
clientStats = new EFClientStatistics()
|
||||
if (!Servers.ContainsKey(serverId))
|
||||
{
|
||||
Active = true,
|
||||
ClientId = pl.ClientId,
|
||||
Deaths = 0,
|
||||
Kills = 0,
|
||||
ServerId = serverId,
|
||||
Skill = 0.0,
|
||||
SPM = 0.0,
|
||||
EloRating = 200.0,
|
||||
HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
|
||||
Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found");
|
||||
return null;
|
||||
}
|
||||
|
||||
var playerStats = Servers[serverId].PlayerStats;
|
||||
var detectionStats = Servers[serverId].PlayerDetections;
|
||||
|
||||
if (playerStats.ContainsKey(pl.ClientId))
|
||||
{
|
||||
Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}");
|
||||
return playerStats[pl.ClientId];
|
||||
}
|
||||
|
||||
// get the client's stats from the database if it exists, otherwise create and attach a new one
|
||||
// if this fails we want to throw an exception
|
||||
|
||||
EFClientStatistics clientStats;
|
||||
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
var clientStatsSet = ctx.Set<EFClientStatistics>();
|
||||
clientStats = clientStatsSet
|
||||
.Include(cl => cl.HitLocations)
|
||||
.FirstOrDefault(c => c.ClientId == pl.ClientId && c.ServerId == serverId);
|
||||
|
||||
if (clientStats == null)
|
||||
{
|
||||
Active = true,
|
||||
HitCount = 0,
|
||||
Location = hl
|
||||
}).ToList()
|
||||
};
|
||||
clientStats = new EFClientStatistics()
|
||||
{
|
||||
Active = true,
|
||||
ClientId = pl.ClientId,
|
||||
Deaths = 0,
|
||||
Kills = 0,
|
||||
ServerId = serverId,
|
||||
Skill = 0.0,
|
||||
SPM = 0.0,
|
||||
EloRating = 200.0,
|
||||
HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
|
||||
{
|
||||
Active = true,
|
||||
HitCount = 0,
|
||||
Location = hl
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
// insert if they've not been added
|
||||
clientStats = clientStatsSvc.Insert(clientStats);
|
||||
await clientStatsSvc.SaveChangesAsync();
|
||||
// insert if they've not been added
|
||||
clientStats = clientStatsSet.Add(clientStats).Entity;
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
if (!playerStats.TryAdd(clientStats.ClientId, clientStats))
|
||||
{
|
||||
Log.WriteWarning("Adding new client to stats failed");
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (!playerStats.TryAdd(clientStats.ClientId, clientStats))
|
||||
{
|
||||
Log.WriteWarning("Adding pre-existing client to stats failed");
|
||||
}
|
||||
}
|
||||
|
||||
// migration for previous existing stats
|
||||
if (clientStats.HitLocations.Count == 0)
|
||||
{
|
||||
clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>()
|
||||
.Select(hl => new EFHitLocationCount()
|
||||
{
|
||||
Active = true,
|
||||
HitCount = 0,
|
||||
Location = hl
|
||||
})
|
||||
.ToList();
|
||||
|
||||
ctx.Update(clientStats);
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// for stats before rating
|
||||
if (clientStats.EloRating == 0.0)
|
||||
{
|
||||
clientStats.EloRating = clientStats.Skill;
|
||||
}
|
||||
|
||||
if (clientStats.RollingWeightedKDR == 0)
|
||||
{
|
||||
clientStats.RollingWeightedKDR = clientStats.KDR;
|
||||
}
|
||||
|
||||
// set these on connecting
|
||||
clientStats.LastActive = DateTime.UtcNow;
|
||||
clientStats.LastStatCalculation = DateTime.UtcNow;
|
||||
clientStats.SessionScore = pl.Score;
|
||||
clientStats.LastScore = pl.Score;
|
||||
|
||||
if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats)))
|
||||
{
|
||||
Log.WriteWarning("Could not add client to detection");
|
||||
}
|
||||
|
||||
pl.CurrentServer.Logger.WriteInfo($"Adding {pl} to stats");
|
||||
}
|
||||
|
||||
return clientStats;
|
||||
}
|
||||
|
||||
// migration for previous existing stats
|
||||
if (clientStats.HitLocations.Count == 0)
|
||||
catch (Exception ex)
|
||||
{
|
||||
clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
|
||||
{
|
||||
Active = true,
|
||||
HitCount = 0,
|
||||
Location = hl
|
||||
})
|
||||
.ToList();
|
||||
await statsSvc.ClientStatSvc.SaveChangesAsync();
|
||||
Log.WriteWarning("Could not add client to stats");
|
||||
Log.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
// for stats before rating
|
||||
if (clientStats.EloRating == 0.0)
|
||||
finally
|
||||
{
|
||||
clientStats.EloRating = clientStats.Skill;
|
||||
OnProcessingSensitive.Release(1);
|
||||
}
|
||||
|
||||
if (clientStats.RollingWeightedKDR == 0)
|
||||
{
|
||||
clientStats.RollingWeightedKDR = clientStats.KDR;
|
||||
}
|
||||
|
||||
// set these on connecting
|
||||
clientStats.LastActive = DateTime.UtcNow;
|
||||
clientStats.LastStatCalculation = DateTime.UtcNow;
|
||||
clientStats.SessionScore = pl.Score;
|
||||
clientStats.LastScore = pl.Score;
|
||||
|
||||
Log.WriteInfo($"Adding {pl} to stats");
|
||||
|
||||
if (!playerStats.TryAdd(pl.ClientId, clientStats))
|
||||
Log.WriteDebug($"Could not add client to stats {pl}");
|
||||
|
||||
if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats)))
|
||||
Log.WriteDebug("Could not add client to detection");
|
||||
|
||||
return clientStats;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -328,22 +387,21 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
/// </summary>
|
||||
/// <param name="pl">Disconnecting client</param>
|
||||
/// <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 detectionStats = Servers[serverId].PlayerDetections;
|
||||
var serverStats = Servers[serverId].ServerStatistics;
|
||||
var statsSvc = ContextThreads[serverId];
|
||||
|
||||
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
|
||||
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue1);
|
||||
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2);
|
||||
detectionStats.TryRemove(pl.ClientId, out Detection removedValue2);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -352,44 +410,47 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
// remove the client from the stats dictionary as they're leaving
|
||||
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
|
||||
var clientStatsSvc = statsSvc.ClientStatSvc;
|
||||
clientStats = UpdateStats(clientStats);
|
||||
clientStatsSvc.Update(clientStats);
|
||||
await clientStatsSvc.SaveChangesAsync();
|
||||
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
ctx.Update(clientStats);
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// 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]+);(.+);(.+)$";
|
||||
var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase);
|
||||
// todo: maybe do something with this
|
||||
//string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$";
|
||||
//var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
// this gives us what team the player is on
|
||||
var attackerStats = Servers[serverId].PlayerStats[attackerClientId];
|
||||
var victimStats = Servers[serverId].PlayerStats[victimClientId];
|
||||
IW4Info.Team victimTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[4].ToString());
|
||||
IW4Info.Team attackerTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[7].ToString());
|
||||
attackerStats.Team = attackerTeam;
|
||||
victimStats.Team = victimTeam;
|
||||
}
|
||||
//if (match.Success)
|
||||
//{
|
||||
// // this gives us what team the player is on
|
||||
// var attackerStats = Servers[serverId].PlayerStats[attackerClientId];
|
||||
// var victimStats = Servers[serverId].PlayerStats[victimClientId];
|
||||
// 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(), true);
|
||||
// attackerStats.Team = attackerTeam;
|
||||
// victimStats.Team = victimTeam;
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process stats for kill event
|
||||
/// </summary>
|
||||
/// <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 fraction, string visibilityPercentage, string snapAngles)
|
||||
{
|
||||
var statsSvc = ContextThreads[serverId];
|
||||
Vector3 vDeathOrigin = null;
|
||||
Vector3 vKillOrigin = null;
|
||||
Vector3 vViewAngles = null;
|
||||
@ -466,10 +527,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return;
|
||||
}
|
||||
|
||||
// incase the add palyer event get delayed
|
||||
if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId))
|
||||
{
|
||||
await AddPlayer(attacker);
|
||||
}
|
||||
|
||||
var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId];
|
||||
var clientStats = Servers[serverId].PlayerStats[attacker.ClientId];
|
||||
var clientStatsSvc = statsSvc.ClientStatSvc;
|
||||
clientStatsSvc.Update(clientStats);
|
||||
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
ctx.Set<EFClientStatistics>().Update(clientStats);
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// increment their hit count
|
||||
if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
|
||||
@ -492,17 +563,33 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
if (Plugin.Config.Configuration().EnableAntiCheat)
|
||||
{
|
||||
await ApplyPenalty(clientDetection.ProcessKill(hit, isDamage), clientDetection, attacker, ctx);
|
||||
await ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), 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);
|
||||
}
|
||||
|
||||
await clientStatsSvc.SaveChangesAsync();
|
||||
ctx.Set<EFHitLocationCount>().UpdateRange(clientStats.HitLocations);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.WriteError("AC ERROR");
|
||||
Log.WriteError("Could not save hit or AC info");
|
||||
Log.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
@ -510,16 +597,16 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
async Task ApplyPenalty(Cheat.DetectionPenaltyResult penalty, Cheat.Detection clientDetection, Player attacker, DatabaseContext ctx)
|
||||
void ApplyPenalty(DetectionPenaltyResult penalty, Detection clientDetection, EFClient attacker, DatabaseContext ctx)
|
||||
{
|
||||
switch (penalty.ClientPenalty)
|
||||
{
|
||||
case Penalty.PenaltyType.Ban:
|
||||
if (attacker.Level == Player.Permission.Banned)
|
||||
if (attacker.Level == EFClient.Permission.Banned)
|
||||
{
|
||||
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,
|
||||
AdministeredPenalties = new List<EFPenalty>()
|
||||
@ -530,42 +617,38 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
|
||||
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
Level = EFClient.Permission.Console,
|
||||
CurrentServer = attacker.CurrentServer,
|
||||
|
||||
}, false);
|
||||
if (clientDetection.Tracker.HasChanges)
|
||||
{
|
||||
SaveTrackedSnapshots(clientDetection, ctx);
|
||||
}
|
||||
break;
|
||||
case Penalty.PenaltyType.Flag:
|
||||
if (attacker.Level != Player.Permission.User)
|
||||
if (attacker.Level != EFClient.Permission.User)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var e = new GameEvent()
|
||||
{
|
||||
Data = penalty.Type == Cheat.Detection.DetectionType.Bone ?
|
||||
|
||||
string flagReason = penalty.Type == Cheat.Detection.DetectionType.Bone ?
|
||||
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
|
||||
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
|
||||
Origin = new Player()
|
||||
{
|
||||
ClientId = 1,
|
||||
Level = Player.Permission.Console,
|
||||
ClientNumber = -1,
|
||||
CurrentServer = attacker.CurrentServer
|
||||
},
|
||||
Target = attacker,
|
||||
Owner = attacker.CurrentServer,
|
||||
Type = GameEvent.EventType.Flag
|
||||
};
|
||||
// because we created an event it must be processed by the manager
|
||||
// even if it didn't really do anything
|
||||
Manager.GetEventHandler().AddEvent(e);
|
||||
await new CFlag().ExecuteAsync(e);
|
||||
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}";
|
||||
|
||||
attacker.Flag(flagReason, new EFClient()
|
||||
{
|
||||
ClientId = 1,
|
||||
Level = EFClient.Permission.Console,
|
||||
CurrentServer = attacker.CurrentServer,
|
||||
});
|
||||
|
||||
if (clientDetection.Tracker.HasChanges)
|
||||
{
|
||||
SaveTrackedSnapshots(clientDetection, ctx);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -625,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;
|
||||
if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId))
|
||||
@ -652,18 +735,23 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Log.WriteDebug("Calculating standard kill");
|
||||
Log.WriteDebug("Calculating standard kill");
|
||||
#endif
|
||||
|
||||
// update the total stats
|
||||
Servers[serverId].ServerStatistics.TotalKills += 1;
|
||||
await Sync(attacker.CurrentServer);
|
||||
|
||||
// this happens when the round has changed
|
||||
if (attackerStats.SessionScore == 0)
|
||||
{
|
||||
attackerStats.LastScore = 0;
|
||||
}
|
||||
|
||||
if (victimStats.SessionScore == 0)
|
||||
{
|
||||
victimStats.LastScore = 0;
|
||||
}
|
||||
|
||||
attackerStats.SessionScore = attacker.Score;
|
||||
victimStats.SessionScore = victim.Score;
|
||||
@ -704,7 +792,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
#if !DEBUG
|
||||
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 2.5)
|
||||
#else
|
||||
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 0.1)
|
||||
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 0.1)
|
||||
#endif
|
||||
{
|
||||
attackerStats.LastStatHistoryUpdate = DateTime.UtcNow;
|
||||
@ -712,10 +800,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
|
||||
// todo: do we want to save this immediately?
|
||||
var clientStatsSvc = ContextThreads[serverId].ClientStatSvc;
|
||||
clientStatsSvc.Update(attackerStats);
|
||||
clientStatsSvc.Update(victimStats);
|
||||
await clientStatsSvc.SaveChangesAsync();
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
var clientStatsSet = ctx.Set<EFClientStatistics>();
|
||||
|
||||
clientStatsSet.Attach(attackerStats).State = EntityState.Modified;
|
||||
clientStatsSet.Attach(victimStats).State = EntityState.Modified;
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -724,7 +816,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
/// <param name="client">client to update</param>
|
||||
/// <param name="clientStats">stats of client that is being updated</param>
|
||||
/// <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;
|
||||
|
||||
@ -923,6 +1015,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
// calulate elo
|
||||
if (Servers[attackerStats.ServerId].PlayerStats.Count > 1)
|
||||
{
|
||||
#region DEPRECATED
|
||||
/* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats
|
||||
.Where(cs => cs.Value.ClientId != attackerStats.ClientId)
|
||||
.Where(cs =>
|
||||
@ -946,6 +1039,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ?
|
||||
validVictimLobbyRatings.Average(cs => cs.Value.EloRating) :
|
||||
victimStats.EloRating;*/
|
||||
#endregion
|
||||
|
||||
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));
|
||||
@ -1048,42 +1142,45 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return clientStats;
|
||||
}
|
||||
|
||||
public void InitializeServerStats(Server sv)
|
||||
public EFServerStatistics InitializeServerStats(long serverId)
|
||||
{
|
||||
int serverId = sv.GetHashCode();
|
||||
var statsSvc = ContextThreads[serverId];
|
||||
EFServerStatistics serverStats;
|
||||
|
||||
var serverStats = statsSvc.ServerStatsSvc.Find(s => s.ServerId == serverId).FirstOrDefault();
|
||||
if (serverStats == null)
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
Log.WriteDebug($"Initializing server stats for {sv}");
|
||||
// server stats have never been generated before
|
||||
serverStats = new EFServerStatistics()
|
||||
var serverStatsSet = ctx.Set<EFServerStatistics>();
|
||||
serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId);
|
||||
|
||||
if (serverStats == null)
|
||||
{
|
||||
Active = true,
|
||||
ServerId = serverId,
|
||||
TotalKills = 0,
|
||||
TotalPlayTime = 0,
|
||||
};
|
||||
Log.WriteDebug($"Initializing server stats for {serverId}");
|
||||
// server stats have never been generated before
|
||||
serverStats = new EFServerStatistics()
|
||||
{
|
||||
ServerId = serverId,
|
||||
TotalKills = 0,
|
||||
TotalPlayTime = 0,
|
||||
};
|
||||
|
||||
var ieClientStats = statsSvc.ClientStatSvc.Find(cs => cs.ServerId == serverId);
|
||||
serverStats = serverStatsSet.Add(serverStats).Entity;
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// set these incase we've imported settings
|
||||
serverStats.TotalKills = ieClientStats.Sum(cs => cs.Kills);
|
||||
serverStats.TotalPlayTime = Manager.GetClientService().GetTotalPlayTime().Result;
|
||||
return serverStats;
|
||||
}
|
||||
|
||||
statsSvc.ServerStatsSvc.Insert(serverStats);
|
||||
public void ResetKillstreaks(long serverId)
|
||||
{
|
||||
var serverStats = Servers[serverId];
|
||||
|
||||
foreach (var stat in serverStats.PlayerStats.Values)
|
||||
{
|
||||
stat.StartNewSession();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetKillstreaks(int serverId)
|
||||
{
|
||||
var serverStats = Servers[serverId];
|
||||
foreach (var stat in serverStats.PlayerStats.Values)
|
||||
stat.StartNewSession();
|
||||
}
|
||||
|
||||
public void ResetStats(int clientId, int serverId)
|
||||
public void ResetStats(int clientId, long serverId)
|
||||
{
|
||||
var stats = Servers[serverId].PlayerStats[clientId];
|
||||
stats.Kills = 0;
|
||||
@ -1094,43 +1191,88 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
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
|
||||
if (clientId < 1)
|
||||
return;
|
||||
|
||||
var messageSvc = ContextThreads[serverId].MessageSvc;
|
||||
messageSvc.Insert(new EFClientMessage()
|
||||
{
|
||||
Active = true,
|
||||
ClientId = clientId,
|
||||
Message = message,
|
||||
ServerId = serverId,
|
||||
TimeSent = DateTime.UtcNow
|
||||
});
|
||||
await messageSvc.SaveChangesAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
ctx.Set<EFClientMessage>().Add(new EFClientMessage()
|
||||
{
|
||||
ClientId = clientId,
|
||||
Message = message,
|
||||
ServerId = serverId,
|
||||
TimeSent = DateTime.UtcNow
|
||||
});
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Sync(Server sv)
|
||||
{
|
||||
int serverId = sv.GetHashCode();
|
||||
var statsSvc = ContextThreads[serverId];
|
||||
long serverId = await GetIdForServer(sv);
|
||||
|
||||
// Log.WriteDebug("Syncing stats contexts");
|
||||
await statsSvc.ServerStatsSvc.SaveChangesAsync();
|
||||
//await statsSvc.ClientStatSvc.SaveChangesAsync();
|
||||
await statsSvc.KillStatsSvc.SaveChangesAsync();
|
||||
await statsSvc.ServerSvc.SaveChangesAsync();
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
var serverSet = ctx.Set<EFServer>();
|
||||
serverSet.Update(Servers[serverId].Server);
|
||||
|
||||
statsSvc = null;
|
||||
// this should prevent the gunk from having a long lasting context.
|
||||
ContextThreads[serverId] = new ThreadSafeStatsService();
|
||||
var serverStatsSet = ctx.Set<EFServerStatistics>();
|
||||
serverStatsSet.Update(Servers[serverId].ServerStatistics);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTeamBased(int serverId, bool isTeamBased)
|
||||
public void SetTeamBased(long serverId, bool 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
using SharedLibraryCore.Services;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
{
|
||||
public class ThreadSafeStatsService
|
||||
{
|
||||
public GenericRepository<EFClientStatistics> ClientStatSvc
|
||||
{
|
||||
get
|
||||
{
|
||||
return new GenericRepository<EFClientStatistics>(true);
|
||||
}
|
||||
}
|
||||
public GenericRepository<EFServer> ServerSvc { get; private set; }
|
||||
public GenericRepository<EFClientKill> KillStatsSvc { get; private set; }
|
||||
public GenericRepository<EFServerStatistics> ServerStatsSvc { get; private set; }
|
||||
public GenericRepository<EFClientMessage> MessageSvc
|
||||
{
|
||||
get
|
||||
{
|
||||
return new GenericRepository<EFClientMessage>();
|
||||
}
|
||||
}
|
||||
|
||||
public ThreadSafeStatsService()
|
||||
{
|
||||
ServerSvc = new GenericRepository<EFServer>();
|
||||
KillStatsSvc = new GenericRepository<EFClientKill>();
|
||||
ServerStatsSvc = new GenericRepository<EFServerStatistics>();
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
public int AttackerId { get; set; }
|
||||
[ForeignKey("AttackerId")]
|
||||
public virtual EFClient Attacker { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")]
|
||||
public virtual EFServer Server { get; set; }
|
||||
public IW4Info.HitLocation HitLoc { get; set; }
|
||||
|
@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
{
|
||||
[Key]
|
||||
public long MessageId { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")]
|
||||
public virtual EFServer Server { get; set; }
|
||||
public int ClientId { get; set; }
|
||||
|
@ -15,7 +15,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
public int ClientId { get; set; }
|
||||
[ForeignKey("ClientId")]
|
||||
public virtual EFClient Client { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")]
|
||||
public virtual EFServer Server { get; set; }
|
||||
[Required]
|
||||
|
@ -20,9 +20,8 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
public int ClientId { get; set; }
|
||||
[ForeignKey("ClientId"), Column(Order = 0 )]
|
||||
public EFClient Client { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId"), Column(Order = 1)]
|
||||
public EFServer Server { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
[ForeignKey("RatingHistoryId")]
|
||||
public virtual EFClientRatingHistory RatingHistory { get; set; }
|
||||
// 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
|
||||
public virtual EFServer Server { get; set; }
|
||||
[Required]
|
||||
|
@ -9,8 +9,9 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.None)]
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[Required]
|
||||
public int Port { get; set; }
|
||||
public string EndPoint { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
{
|
||||
[Key]
|
||||
public int StatisticId { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")]
|
||||
public virtual EFServer Server { get; set; }
|
||||
public long TotalKills { get; set; }
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
|
||||
namespace Stats.Models
|
||||
{
|
||||
|
@ -1,18 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
|
||||
using IW4MAdmin.Plugins.Stats.Config;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Services;
|
||||
using IW4MAdmin.Plugins.Stats.Config;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats
|
||||
{
|
||||
@ -42,16 +43,19 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
break;
|
||||
case GameEvent.EventType.Disconnect:
|
||||
await Manager.RemovePlayer(E.Origin);
|
||||
await Manager.Sync(S);
|
||||
break;
|
||||
case GameEvent.EventType.Say:
|
||||
if (!string.IsNullOrEmpty(E.Data) &&
|
||||
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;
|
||||
case GameEvent.EventType.MapChange:
|
||||
Manager.SetTeamBased(E.Owner.GetHashCode(), E.Owner.Gametype != "dm");
|
||||
Manager.ResetKillstreaks(S.GetHashCode());
|
||||
await Manager.Sync(S);
|
||||
Manager.SetTeamBased(await StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm");
|
||||
Manager.ResetKillstreaks(await StatManager.GetIdForServer(E.Owner));
|
||||
break;
|
||||
case GameEvent.EventType.MapEnd:
|
||||
break;
|
||||
@ -75,7 +79,7 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
|
||||
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]);
|
||||
}
|
||||
break;
|
||||
@ -88,14 +92,14 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
case GameEvent.EventType.Damage:
|
||||
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;
|
||||
case GameEvent.EventType.ScriptDamage:
|
||||
killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
|
||||
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]);
|
||||
}
|
||||
break;
|
||||
@ -122,8 +126,11 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
// meta data info
|
||||
async Task<List<ProfileMeta>> getStats(int clientId)
|
||||
{
|
||||
var statsSvc = new GenericRepository<EFClientStatistics>();
|
||||
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId);
|
||||
IList<EFClientStatistics> clientStats;
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == clientId).ToListAsync();
|
||||
}
|
||||
|
||||
int kills = clientStats.Sum(c => c.Kills);
|
||||
int deaths = clientStats.Sum(c => c.Deaths);
|
||||
@ -138,7 +145,7 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
new ProfileMeta()
|
||||
{
|
||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
|
||||
Value = "#" + await Manager.GetClientOverallRanking(clientId),
|
||||
Value = "#" + await StatManager.GetClientOverallRanking(clientId),
|
||||
},
|
||||
new ProfileMeta()
|
||||
{
|
||||
@ -170,8 +177,14 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
|
||||
async Task<List<ProfileMeta>> getAnticheatInfo(int clientId)
|
||||
{
|
||||
var statsSvc = new GenericRepository<EFClientStatistics>();
|
||||
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId);
|
||||
IList<EFClientStatistics> clientStats;
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
clientStats = await ctx.Set<EFClientStatistics>()
|
||||
.Include(c => c.HitLocations)
|
||||
.Where(c => c.ClientId == clientId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
double headRatio = 0;
|
||||
double chestRatio = 0;
|
||||
@ -231,7 +244,8 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
new ProfileMeta()
|
||||
{
|
||||
Key = "Hit Offset Average",
|
||||
Value = $"{Math.Round(((float)hitOffsetAverage), 4)}°",
|
||||
// todo: make sure this is wrapped somewhere else
|
||||
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
|
||||
Sensitive = true
|
||||
},
|
||||
new ProfileMeta()
|
||||
@ -245,19 +259,24 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
|
||||
async Task<List<ProfileMeta>> getMessages(int clientId)
|
||||
{
|
||||
var messageSvc = new GenericRepository<EFClientMessage>();
|
||||
var messages = await messageSvc.FindAsync(m => m.ClientId == clientId);
|
||||
var messageMeta = messages.Select(m => new ProfileMeta()
|
||||
List<ProfileMeta> messageMeta;
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
Key = "EventMessage",
|
||||
Value = m.Message,
|
||||
When = m.TimeSent,
|
||||
Extra = m.ServerId.ToString()
|
||||
}).ToList();
|
||||
var messages = ctx.Set<EFClientMessage>().Where(m => m.ClientId == clientId);
|
||||
|
||||
messageMeta = await messages.Select(m => new ProfileMeta()
|
||||
{
|
||||
Key = "EventMessage",
|
||||
Value = m.Message,
|
||||
When = m.TimeSent,
|
||||
Extra = m.ServerId.ToString()
|
||||
}).ToListAsync();
|
||||
}
|
||||
|
||||
messageMeta.Add(new ProfileMeta()
|
||||
{
|
||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
|
||||
Value = messages.Count
|
||||
Value = messageMeta.Count
|
||||
});
|
||||
|
||||
return messageMeta;
|
||||
@ -272,28 +291,32 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
|
||||
MetaService.AddMeta(getMessages);
|
||||
|
||||
string totalKills(Server server)
|
||||
async Task<object> totalKills(Server server)
|
||||
{
|
||||
var serverStats = new GenericRepository<EFServerStatistics>();
|
||||
return serverStats.Find(s => s.Active)
|
||||
.Sum(c => c.TotalKills).ToString("#,##0");
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
long kills = await ctx.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalKills);
|
||||
return kills.ToString("#,##0");
|
||||
}
|
||||
}
|
||||
|
||||
string totalPlayTime(Server server)
|
||||
async Task<object> totalPlayTime(Server server)
|
||||
{
|
||||
var serverStats = new GenericRepository<EFServerStatistics>();
|
||||
return Math.Ceiling((serverStats.GetQuery(s => s.Active)
|
||||
.Sum(c => c.TotalPlayTime) / 3600.0)).ToString("#,##0");
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
long playTime = await ctx.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalPlayTime);
|
||||
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));
|
||||
@ -306,12 +329,17 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
Manager = new StatManager(manager);
|
||||
}
|
||||
|
||||
public Task OnTickAsync(Server S) => Task.CompletedTask;
|
||||
public Task OnTickAsync(Server S)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task OnUnloadAsync()
|
||||
{
|
||||
foreach (var sv in ServerManager.GetServers())
|
||||
{
|
||||
await Manager.Sync(sv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.Stats</PackageId>
|
||||
@ -14,27 +15,16 @@
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Web\Views\Stats\_MessageContext.cshtml">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||
<ProjectReference Include="..\..\WebfrontCore\WebfrontCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" />
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="copy "$(TargetPath)" "$(SolutionDir)BUILD\Plugins"" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="xcopy /E /K /Y /C /I "$(ProjectDir)Web\Views" "$(SolutionDir)WebfrontCore\Views\Plugins"
xcopy /E /K /Y /C /I "$(ProjectDir)Web\wwwroot\images" "$(SolutionDir)WebfrontCore\wwwroot\images"" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user