Compare commits
49 Commits
2.2
...
2.3-prerel
Author | SHA1 | Date | |
---|---|---|---|
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 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -230,3 +230,6 @@ bootstrap-custom.min.css
|
||||
/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,16 +2,16 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
||||
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
||||
<Version>2.2</Version>
|
||||
<Version>2.2.5.1</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>
|
||||
<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>
|
||||
@ -25,14 +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.0.0</AssemblyVersion>
|
||||
<FileVersion>2.2.0.0</FileVersion>
|
||||
<AssemblyVersion>2.2.5.1</AssemblyVersion>
|
||||
<FileVersion>2.2.5.1</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -79,7 +79,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
|
@ -1,24 +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 IW4MAdmin.Application.Migration;
|
||||
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
|
||||
{
|
||||
@ -26,7 +26,7 @@ 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 Dictionary<int, EFClient> PrivilegedClients { get; set; }
|
||||
public ILogger Logger => GetLogger(0);
|
||||
public bool Running { get; private set; }
|
||||
public bool IsInitialized { get; private set; }
|
||||
@ -37,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;
|
||||
@ -49,7 +56,8 @@ namespace IW4MAdmin.Application
|
||||
ManualResetEventSlim OnQuit;
|
||||
readonly IPageList PageList;
|
||||
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
|
||||
readonly Dictionary<int, ILogger> Loggers = new Dictionary<int, ILogger>();
|
||||
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
|
||||
readonly ITokenAuthentication _authenticator;
|
||||
|
||||
private ApplicationManager()
|
||||
{
|
||||
@ -60,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)
|
||||
@ -85,81 +95,12 @@ 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"]);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Logger.WriteDebug($"Processed event with id {newEvent.Id}");
|
||||
#endif
|
||||
@ -216,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)
|
||||
{
|
||||
@ -236,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
|
||||
{
|
||||
@ -265,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
|
||||
@ -280,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
|
||||
@ -340,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;
|
||||
@ -361,36 +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);
|
||||
await new ContextSeed(db).Seed();
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
|
||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
}
|
||||
PrivilegedClients = (await ClientSvc.GetPrivilegedClients()).ToDictionary(_client => _client.ClientId);
|
||||
#endregion
|
||||
|
||||
#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 GenerateTokenCommand());
|
||||
|
||||
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,7 +517,7 @@ namespace IW4MAdmin.Application
|
||||
Running = false;
|
||||
}
|
||||
|
||||
public ILogger GetLogger(int serverId)
|
||||
public ILogger GetLogger(long serverId)
|
||||
{
|
||||
if (Loggers.ContainsKey(serverId))
|
||||
{
|
||||
@ -595,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;
|
||||
}
|
||||
|
||||
public IPageList GetPageList() => PageList;
|
||||
public IPageList GetPageList()
|
||||
{
|
||||
return PageList;
|
||||
}
|
||||
|
||||
public IRConParser GenerateDynamicRConParser()
|
||||
{
|
||||
return new DynamicRConParser();
|
||||
}
|
||||
|
||||
public IEventParser GenerateDynamicEventParser()
|
||||
{
|
||||
return new DynamicEventParser();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application.Core
|
||||
{
|
||||
class ClientAuthentication : IClientAuthentication
|
||||
{
|
||||
private Queue<Player> ClientAuthenticationQueue;
|
||||
private Dictionary<long, Player> AuthenticatedClients;
|
||||
|
||||
public ClientAuthentication()
|
||||
{
|
||||
ClientAuthenticationQueue = new Queue<Player>();
|
||||
AuthenticatedClients = new Dictionary<long, Player>();
|
||||
}
|
||||
|
||||
public void AuthenticateClients(IList<Player> clients)
|
||||
{
|
||||
// we need to un-auth all the clients that have disconnected
|
||||
var clientNetworkIds = clients.Select(c => c.NetworkId);
|
||||
var clientsToRemove = AuthenticatedClients.Keys.Where(c => !clientNetworkIds.Contains(c));
|
||||
// remove them
|
||||
foreach (long Id in clientsToRemove.ToList())
|
||||
{
|
||||
AuthenticatedClients.Remove(Id);
|
||||
}
|
||||
|
||||
// loop through the polled clients to see if they've been authenticated yet
|
||||
foreach (var client in clients)
|
||||
{
|
||||
// they've not been authenticated
|
||||
if (!AuthenticatedClients.TryGetValue(client.NetworkId, out Player value))
|
||||
{
|
||||
// authenticate them
|
||||
client.State = Player.ClientState.Authenticated;
|
||||
AuthenticatedClients.Add(client.NetworkId, client);
|
||||
}
|
||||
else
|
||||
{
|
||||
// this update their ping
|
||||
// todo: this seems kinda hacky
|
||||
value.Ping = client.Ping;
|
||||
value.Score = client.Score;
|
||||
}
|
||||
}
|
||||
|
||||
// empty out the queue of clients detected through log
|
||||
while (ClientAuthenticationQueue.Count > 0)
|
||||
{
|
||||
// grab each client that's connected via log
|
||||
var clientToAuthenticate = ClientAuthenticationQueue.Dequeue();
|
||||
// if they're not already authed, auth them
|
||||
if (!AuthenticatedClients.TryGetValue(clientToAuthenticate.NetworkId, out Player value))
|
||||
{
|
||||
// authenticate them
|
||||
clientToAuthenticate.State = Player.ClientState.Authenticated;
|
||||
AuthenticatedClients.Add(clientToAuthenticate.NetworkId, clientToAuthenticate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IList<Player> GetAuthenticatedClients()
|
||||
{
|
||||
if (AuthenticatedClients.Values.Count > 18)
|
||||
{
|
||||
Program.ServerManager.GetLogger(0).WriteError($"auth client count is {AuthenticatedClients.Values.Count}, this is bad");
|
||||
return AuthenticatedClients.Values.Take(18).ToList();
|
||||
}
|
||||
|
||||
return AuthenticatedClients.Values.ToList();
|
||||
}
|
||||
|
||||
public void RequestClientAuthentication(Player client)
|
||||
{
|
||||
ClientAuthenticationQueue.Enqueue(client);
|
||||
}
|
||||
}
|
||||
}
|
@ -247,7 +247,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "T6M",
|
||||
"Game": "T6",
|
||||
"Maps": [
|
||||
{
|
||||
"Alias": "Aftermath",
|
||||
@ -402,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 = eventType,
|
||||
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 = eventType,
|
||||
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
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@ 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(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");
|
||||
|
@ -1,16 +1,11 @@
|
||||
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 IW4MAdmin.Application.Migration;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
@ -23,7 +18,6 @@ namespace IW4MAdmin.Application
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
|
||||
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
@ -39,6 +33,18 @@ namespace IW4MAdmin.Application
|
||||
|
||||
try
|
||||
{
|
||||
ServerManager = ApplicationManager.GetInstance();
|
||||
|
||||
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);
|
||||
|
||||
@ -47,10 +53,6 @@ namespace IW4MAdmin.Application
|
||||
// todo: move out
|
||||
ConfigurationMigration.MoveConfigFolder10518(null);
|
||||
|
||||
ServerManager = ApplicationManager.GetInstance();
|
||||
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
|
||||
|
||||
|
||||
ServerManager.Logger.WriteInfo($"Version is {Version}");
|
||||
|
||||
var api = API.Master.Endpoint.Get();
|
||||
@ -111,15 +113,17 @@ namespace IW4MAdmin.Application
|
||||
|
||||
var consoleTask = Task.Run(async () =>
|
||||
{
|
||||
String userInput;
|
||||
Player Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
|
||||
string userInput;
|
||||
var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
|
||||
|
||||
do
|
||||
{
|
||||
userInput = Console.ReadLine();
|
||||
|
||||
if (userInput?.ToLower() == "quit")
|
||||
{
|
||||
ServerManager.Stop();
|
||||
}
|
||||
|
||||
if (ServerManager.Servers.Count == 0)
|
||||
{
|
||||
@ -148,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(e.Message);
|
||||
Console.WriteLine(loc["MANAGER_EXIT"]);
|
||||
Console.WriteLine(exitMessage);
|
||||
Console.ReadKey();
|
||||
return;
|
||||
}
|
||||
|
@ -56,5 +56,14 @@ namespace IW4MAdmin.Application.Migration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ModifyLogPath020919(SharedLibraryCore.Configuration.ServerConfiguration config)
|
||||
{
|
||||
if (config.ManualLogPath.IsRemoteLog())
|
||||
{
|
||||
config.GameLogServerUrl = new Uri(config.ManualLogPath);
|
||||
config.ManualLogPath = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
94
Application/Misc/TokenAuthentication.cs
Normal file
94
Application/Misc/TokenAuthentication.cs
Normal file
@ -0,0 +1,94 @@
|
||||
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, 30);
|
||||
private const short TOKEN_LENGTH = 4;
|
||||
|
||||
private class TokenState
|
||||
{
|
||||
public long NetworkId { get; set; }
|
||||
public DateTime RequestTime { get; set; } = DateTime.Now;
|
||||
public string Token { get; set; }
|
||||
}
|
||||
|
||||
public TokenAuthentication()
|
||||
{
|
||||
_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 string GenerateNextToken(long networkId)
|
||||
{
|
||||
TokenState state = null;
|
||||
if (_tokens.ContainsKey(networkId))
|
||||
{
|
||||
state = _tokens[networkId];
|
||||
|
||||
if ((DateTime.Now - state.RequestTime) < _timeoutPeriod)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_tokens.TryRemove(networkId, out TokenState _);
|
||||
}
|
||||
}
|
||||
|
||||
state = new TokenState()
|
||||
{
|
||||
NetworkId = networkId,
|
||||
Token = _generateToken()
|
||||
};
|
||||
|
||||
_tokens.TryAdd(networkId, state);
|
||||
return state.Token;
|
||||
}
|
||||
|
||||
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,123 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.RCon;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
{
|
||||
public class T6MRConParser : IRConParser
|
||||
{
|
||||
private static readonly CommandPrefix Prefixes = new CommandPrefix()
|
||||
{
|
||||
Tell = "tell {0} {1}",
|
||||
Say = "say {0}",
|
||||
Kick = "clientKick {0}",
|
||||
Ban = "clientKick {0}",
|
||||
TempBan = "clientKick {0}"
|
||||
};
|
||||
|
||||
public CommandPrefix GetCommandPrefixes() => Prefixes;
|
||||
|
||||
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
|
||||
{
|
||||
await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, false);
|
||||
return new string[] { "Command Executed" };
|
||||
}
|
||||
|
||||
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
|
||||
{
|
||||
string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"get {dvarName}");
|
||||
|
||||
if (LineSplit.Length < 2)
|
||||
{
|
||||
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
|
||||
e.Data["dvar_name"] = dvarName;
|
||||
throw e;
|
||||
}
|
||||
|
||||
string[] ValueSplit = LineSplit[1].Split(new char[] { '"' });
|
||||
|
||||
if (ValueSplit.Length == 0)
|
||||
{
|
||||
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
|
||||
e.Data["dvar_name"] = dvarName;
|
||||
throw e;
|
||||
}
|
||||
|
||||
string DvarName = dvarName;
|
||||
string DvarCurrentValue = Regex.Replace(ValueSplit[1], @"\^[0-9]", "");
|
||||
|
||||
return new Dvar<T>(DvarName)
|
||||
{
|
||||
Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T))
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<Player>> GetStatusAsync(Connection connection)
|
||||
{
|
||||
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
|
||||
return ClientsFromStatus(response);
|
||||
}
|
||||
|
||||
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
|
||||
{
|
||||
// T6M doesn't respond with anything when a value is set, so we can only hope for the best :c
|
||||
await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, $"set {dvarName} {dvarValue}", false);
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<Player> ClientsFromStatus(string[] status)
|
||||
{
|
||||
List<Player> StatusPlayers = new List<Player>();
|
||||
|
||||
foreach (string statusLine in status)
|
||||
{
|
||||
String responseLine = statusLine;
|
||||
|
||||
if (Regex.Matches(responseLine, @"^ *\d+", RegexOptions.IgnoreCase).Count > 0) // its a client line!
|
||||
{
|
||||
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
int clientId = -1;
|
||||
int Ping = -1;
|
||||
|
||||
Int32.TryParse(playerInfo[3], out Ping);
|
||||
var regex = Regex.Match(responseLine, @"\^7.*\ +0 ");
|
||||
string name = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(regex.Value.Substring(0, regex.Value.Length - 2).StripColors().Trim())));
|
||||
long networkId = playerInfo[4].ConvertLong();
|
||||
int.TryParse(playerInfo[0], out clientId);
|
||||
regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
|
||||
#if DEBUG
|
||||
Ping = 1;
|
||||
#endif
|
||||
int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
|
||||
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
|
||||
var p = new Player()
|
||||
{
|
||||
Name = name,
|
||||
NetworkId = networkId,
|
||||
ClientNumber = clientId,
|
||||
IPAddress = ipAddress,
|
||||
Ping = Ping,
|
||||
Score = 0,
|
||||
State = Player.ClientState.Connecting,
|
||||
IsBot = networkId == 0
|
||||
};
|
||||
|
||||
if (p.IsBot)
|
||||
p.NetworkId = -p.ClientNumber;
|
||||
|
||||
StatusPlayers.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
return StatusPlayers;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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,8 +51,8 @@ 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
|
||||
|
@ -10,8 +10,10 @@ 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
|
||||
@ -39,6 +38,10 @@ 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
|
||||
|
@ -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']:
|
||||
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']:
|
||||
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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
foreach (var assignment in axisTeam)
|
||||
{
|
||||
teamAssignments.Add($"{assignment.Num},3");
|
||||
assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis;
|
||||
}
|
||||
|
||||
//if (alliesTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0 &&
|
||||
// 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;
|
||||
// //E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
|
||||
// return string.Empty;
|
||||
//}
|
||||
|
||||
//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)}");
|
||||
// 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)}");
|
||||
//}
|
||||
|
||||
// string args = string.Join(",", teamAssignments);
|
||||
// await E.Owner.ExecuteCommandAsync($"sv_iw4madmin_command \"balance:{args}\"");
|
||||
// await E.Origin.Tell("Balance command sent");
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//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,10 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
@ -18,7 +19,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
<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,4 +1,5 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -8,7 +9,7 @@ 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()
|
||||
{
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.Login</PackageId>
|
||||
@ -22,7 +22,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
<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,9 +52,16 @@ 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;
|
||||
|
||||
@ -48,7 +49,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
|
||||
if (containsObjectionalWord)
|
||||
{
|
||||
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
|
||||
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new EFClient()
|
||||
{
|
||||
ClientId = 1,
|
||||
CurrentServer = E.Owner
|
||||
@ -85,22 +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,
|
||||
CurrentServer = E.Owner
|
||||
});
|
||||
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,
|
||||
CurrentServer = E.Owner
|
||||
});
|
||||
clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, Utilities.IW4MAdminClient(E.Owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId>
|
||||
@ -20,7 +20,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
<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,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,7 +20,9 @@ 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;
|
||||
@ -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 != kill.Weapon && (kill.TimeOffset - LastOffset) == 50))
|
||||
(LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50))
|
||||
{
|
||||
return new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Any,
|
||||
};
|
||||
}
|
||||
|
||||
DetectionPenaltyResult result = null;
|
||||
LastWeapon = kill.Weapon;
|
||||
LastWeapon = hit.Weapon;
|
||||
|
||||
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,32 +1,30 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Database;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System.Linq;
|
||||
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)
|
||||
{
|
||||
|
||||
int serverId = E.Owner.GetHashCode();
|
||||
long serverId = await Helpers.StatManager.GetIdForServer(E.Owner);
|
||||
|
||||
EFClientStatistics clientStats;
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
clientStats = await ctx.Set<EFClientStatistics>()
|
||||
.Where(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId)
|
||||
.Where(s => s.ClientId == E.Origin.ClientId)
|
||||
.Where(s => s.ServerId == serverId)
|
||||
.FirstAsync();
|
||||
|
||||
clientStats.Deaths = 0;
|
||||
@ -38,7 +36,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
clientStats.EloRating = 200.0;
|
||||
|
||||
// reset the cached version
|
||||
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
|
||||
Plugin.Manager.ResetStats(E.Origin.ClientId, serverId);
|
||||
|
||||
// fixme: this doesn't work properly when another context exists
|
||||
await ctx.SaveChangesAsync();
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -9,12 +9,14 @@ 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()
|
||||
{
|
||||
@ -41,20 +43,26 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
}
|
||||
}
|
||||
|
||||
int serverId = E.Owner.GetHashCode();
|
||||
long serverId = await StatManager.GetIdForServer(E.Owner);
|
||||
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
if (E.Target != null)
|
||||
{
|
||||
int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId);
|
||||
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
||||
|
||||
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId));
|
||||
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}";
|
||||
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
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()}";
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<long, ServerStats> Servers;
|
||||
private ILogger Log;
|
||||
private readonly IManager Manager;
|
||||
|
||||
|
||||
private readonly SemaphoreSlim OnProcessingPenalty;
|
||||
private readonly SemaphoreSlim OnProcessingSensitive;
|
||||
|
||||
public StatManager(IManager mgr)
|
||||
{
|
||||
Servers = new ConcurrentDictionary<int, ServerStats>();
|
||||
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))
|
||||
{
|
||||
@ -191,21 +191,36 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
// insert the server if it does not exist
|
||||
try
|
||||
{
|
||||
int serverId = sv.GetHashCode();
|
||||
long serverId = GetIdForServer(sv).Result;
|
||||
EFServer server;
|
||||
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
var serverSet = ctx.Set<EFServer>();
|
||||
// get the server from the database if it exists, otherwise create and insert a new one
|
||||
server = serverSet.FirstOrDefault(c => c.ServerId == serverId);
|
||||
server = serverSet.FirstOrDefault(s => s.ServerId == serverId);
|
||||
|
||||
// the server might be using legacy server id
|
||||
if (server == null)
|
||||
{
|
||||
server = serverSet.FirstOrDefault(s => s.EndPoint == sv.ToString());
|
||||
|
||||
if (server != null)
|
||||
{
|
||||
// this provides a way to identify legacy server entries
|
||||
server.EndPoint = sv.ToString();
|
||||
ctx.Update(server);
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// server has never been added before
|
||||
if (server == null)
|
||||
{
|
||||
server = new EFServer()
|
||||
{
|
||||
Port = sv.GetPort(),
|
||||
Active = true,
|
||||
EndPoint = sv.ToString(),
|
||||
ServerId = serverId
|
||||
};
|
||||
|
||||
@ -216,7 +231,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
|
||||
// check to see if the stats have ever been initialized
|
||||
var serverStats = InitializeServerStats(sv);
|
||||
var serverStats = InitializeServerStats(server.ServerId);
|
||||
|
||||
Servers.TryAdd(serverId, new ServerStats(server, serverStats)
|
||||
{
|
||||
@ -227,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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,14 +251,13 @@ 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)
|
||||
{
|
||||
await OnProcessingSensitive.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
int serverId = pl.CurrentServer.GetHashCode();
|
||||
|
||||
long serverId = await GetIdForServer(pl.CurrentServer);
|
||||
|
||||
if (!Servers.ContainsKey(serverId))
|
||||
{
|
||||
@ -299,7 +314,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
{
|
||||
Log.WriteWarning("Adding new client to stats failed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
else
|
||||
@ -348,7 +362,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
Log.WriteWarning("Could not add client to detection");
|
||||
}
|
||||
|
||||
Log.WriteInfo($"Adding {pl} to stats");
|
||||
pl.CurrentServer.Logger.WriteInfo($"Adding {pl} to stats");
|
||||
}
|
||||
|
||||
return clientStats;
|
||||
@ -373,21 +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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -396,7 +410,7 @@ 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
|
||||
clientStats = UpdateStats(clientStats);
|
||||
@ -408,10 +422,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
|
||||
// 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);
|
||||
@ -432,7 +446,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
/// 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)
|
||||
{
|
||||
@ -548,7 +562,22 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
if (Plugin.Config.Configuration().EnableAntiCheat)
|
||||
{
|
||||
ApplyPenalty(clientDetection.ProcessKill(hit, isDamage), clientDetection, attacker, ctx);
|
||||
if (clientDetection.QueuedHits.Count > Detection.QUEUE_COUNT)
|
||||
{
|
||||
while (clientDetection.QueuedHits.Count > 0)
|
||||
{
|
||||
clientDetection.QueuedHits = clientDetection.QueuedHits.OrderBy(_hits => _hits.TimeOffset).ToList();
|
||||
var oldestHit = clientDetection.QueuedHits.First();
|
||||
clientDetection.QueuedHits.RemoveAt(0);
|
||||
ApplyPenalty(clientDetection.ProcessHit(oldestHit, isDamage), clientDetection, attacker, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
clientDetection.QueuedHits.Add(hit);
|
||||
}
|
||||
|
||||
ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx);
|
||||
}
|
||||
|
||||
@ -567,16 +596,16 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyPenalty(Cheat.DetectionPenaltyResult penalty, Cheat.Detection clientDetection, Player attacker, DatabaseContext ctx)
|
||||
void ApplyPenalty(DetectionPenaltyResult penalty, Detection clientDetection, EFClient attacker, DatabaseContext ctx)
|
||||
{
|
||||
switch (penalty.ClientPenalty)
|
||||
{
|
||||
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>()
|
||||
@ -588,16 +617,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
|
||||
}
|
||||
},
|
||||
Level = Player.Permission.Console,
|
||||
CurrentServer = attacker.CurrentServer
|
||||
});
|
||||
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;
|
||||
}
|
||||
@ -606,10 +636,10 @@ 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}";
|
||||
|
||||
attacker.Flag(flagReason, new Player()
|
||||
attacker.Flag(flagReason, new EFClient()
|
||||
{
|
||||
ClientId = 1,
|
||||
Level = Player.Permission.Console,
|
||||
Level = EFClient.Permission.Console,
|
||||
CurrentServer = attacker.CurrentServer,
|
||||
});
|
||||
|
||||
@ -677,9 +707,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))
|
||||
@ -709,13 +739,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
// 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;
|
||||
@ -768,8 +803,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
{
|
||||
var clientStatsSet = ctx.Set<EFClientStatistics>();
|
||||
|
||||
clientStatsSet.Update(attackerStats);
|
||||
clientStatsSet.Update(victimStats);
|
||||
clientStatsSet.Attach(attackerStats).State = EntityState.Modified;
|
||||
clientStatsSet.Attach(victimStats).State = EntityState.Modified;
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@ -780,7 +815,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;
|
||||
|
||||
@ -979,6 +1014,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 =>
|
||||
@ -1002,6 +1038,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));
|
||||
@ -1104,9 +1141,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return clientStats;
|
||||
}
|
||||
|
||||
public EFServerStatistics InitializeServerStats(Server sv)
|
||||
public EFServerStatistics InitializeServerStats(long serverId)
|
||||
{
|
||||
int serverId = sv.GetHashCode();
|
||||
EFServerStatistics serverStats;
|
||||
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
@ -1116,7 +1152,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
if (serverStats == null)
|
||||
{
|
||||
Log.WriteDebug($"Initializing server stats for {sv}");
|
||||
Log.WriteDebug($"Initializing server stats for {serverId}");
|
||||
// server stats have never been generated before
|
||||
serverStats = new EFServerStatistics()
|
||||
{
|
||||
@ -1133,7 +1169,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return serverStats;
|
||||
}
|
||||
|
||||
public void ResetKillstreaks(int serverId)
|
||||
public void ResetKillstreaks(long serverId)
|
||||
{
|
||||
var serverStats = Servers[serverId];
|
||||
|
||||
@ -1143,7 +1179,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetStats(int clientId, int serverId)
|
||||
public void ResetStats(int clientId, long serverId)
|
||||
{
|
||||
var stats = Servers[serverId].PlayerStats[clientId];
|
||||
stats.Kills = 0;
|
||||
@ -1154,11 +1190,13 @@ 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;
|
||||
}
|
||||
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
@ -1176,19 +1214,64 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
public async Task Sync(Server sv)
|
||||
{
|
||||
int serverId = sv.GetHashCode();
|
||||
long serverId = await GetIdForServer(sv);
|
||||
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
var serverSet = ctx.Set<EFServer>();
|
||||
serverSet.Update(Servers[serverId].Server);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,7 +20,7 @@ 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,20 +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 SharedLibraryCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats
|
||||
{
|
||||
@ -44,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;
|
||||
@ -77,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;
|
||||
@ -90,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;
|
||||
@ -143,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()
|
||||
{
|
||||
@ -327,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,8 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.Stats</PackageId>
|
||||
@ -27,7 +27,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -1,4 +1,4 @@
|
||||
@model List<IW4MAdmin.Plugins.Stats.Web.Dtos.TopStatsInfo>
|
||||
@model List<dynamic>
|
||||
<h4 class="pb-2 text-center ">@ViewBag.Title</h4>
|
||||
|
||||
<div id="stats_top_players" class="striped border-top border-bottom">
|
||||
|
@ -1,4 +1,4 @@
|
||||
@model List<IW4MAdmin.Plugins.Stats.Web.Dtos.TopStatsInfo>
|
||||
@model List<dynamic>
|
||||
@{
|
||||
Layout = null;
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex.Set;
|
||||
@ -46,7 +46,7 @@
|
||||
</div>
|
||||
}
|
||||
<span class="text-muted pl-1 pr-1" style="font-size: 1.25rem;">—</span>
|
||||
@Html.ActionLink(stat.Name, "ProfileAsync", "Client", new { id = stat.ClientId })
|
||||
@Html.ActionLink((string)stat.Name, "ProfileAsync", "Client", new { id = stat.ClientId })
|
||||
</div>
|
||||
|
||||
<span class="text-primary">@stat.Performance</span><span class="text-muted"> @loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"]</span><br />
|
||||
|
@ -1,4 +1,4 @@
|
||||
@model IEnumerable<IW4MAdmin.Plugins.Stats.Models.EFACSnapshot>
|
||||
@model IEnumerable<dynamic>
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
@foreach (var snapshot in Model)
|
||||
{
|
||||
<!-- this is not ideal, but I didn't want to manually write out all the properties-->
|
||||
var snapProperties = typeof(IW4MAdmin.Plugins.Stats.Models.EFACSnapshot).GetProperties();
|
||||
var snapProperties = Model.First().GetType().GetProperties();
|
||||
foreach (var prop in snapProperties)
|
||||
{
|
||||
<!-- this is another ugly hack-->
|
||||
|
@ -1,6 +1,7 @@
|
||||
using IW4MAdmin.Application;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -25,7 +26,7 @@ namespace Tests
|
||||
[Fact]
|
||||
public void SetAdditionalPropertyShouldSucceed()
|
||||
{
|
||||
var client = new Player();
|
||||
var client = new EFClient();
|
||||
int newProp = 5;
|
||||
client.SetAdditionalProperty("NewProp", newProp);
|
||||
}
|
||||
@ -33,7 +34,7 @@ namespace Tests
|
||||
[Fact]
|
||||
public void GetAdditionalPropertyShouldSucceed()
|
||||
{
|
||||
var client = new Player();
|
||||
var client = new EFClient();
|
||||
int newProp = 5;
|
||||
client.SetAdditionalProperty("NewProp", newProp);
|
||||
|
||||
@ -48,11 +49,11 @@ namespace Tests
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
|
||||
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
|
||||
|
||||
Assert.False(client == null, "no client found to warn");
|
||||
|
||||
var warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
var warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
warnEvent.OnProcessed.Wait();
|
||||
|
||||
Assert.True((client.Warnings == 1 ||
|
||||
@ -60,19 +61,19 @@ namespace Tests
|
||||
Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1,
|
||||
"warning did not get applied");
|
||||
|
||||
warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
warnEvent.OnProcessed.Wait();
|
||||
|
||||
Assert.True(warnEvent.FailReason == GameEvent.EventFailReason.Permission &&
|
||||
client.Warnings == 1, "warning was applied without proper permissions");
|
||||
|
||||
// warn clear
|
||||
var warnClearEvent = client.WarnClear(new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
var warnClearEvent = client.WarnClear(new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
|
||||
Assert.True(warnClearEvent.FailReason == GameEvent.EventFailReason.Permission &&
|
||||
client.Warnings == 1, "warning was removed without proper permissions");
|
||||
|
||||
warnClearEvent = client.WarnClear(new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
warnClearEvent = client.WarnClear(new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
|
||||
Assert.True(!warnClearEvent.Failed && client.Warnings == 0, "warning was not cleared");
|
||||
}
|
||||
@ -85,11 +86,11 @@ namespace Tests
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
|
||||
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
|
||||
Assert.False(client == null, "no client found to report");
|
||||
|
||||
// fail
|
||||
var player = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer };
|
||||
var player = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer };
|
||||
player.SetAdditionalProperty("_reportCount", 3);
|
||||
var reportEvent = client.Report("test report", player);
|
||||
reportEvent.OnProcessed.Wait(TestTimeout);
|
||||
@ -98,14 +99,14 @@ namespace Tests
|
||||
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 0, $"too many reports were applied [{reportEvent.FailReason.ToString()}]");
|
||||
|
||||
// succeed
|
||||
reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
reportEvent = client.Report("test report", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
reportEvent.OnProcessed.Wait(TestTimeout);
|
||||
|
||||
Assert.True(!reportEvent.Failed &&
|
||||
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was not applied [{reportEvent.FailReason.ToString()}]");
|
||||
|
||||
// fail
|
||||
reportEvent = client.Report("test report", new Player() { ClientId = 1, NetworkId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
reportEvent = client.Report("test report", new EFClient() { ClientId = 1, NetworkId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
|
||||
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Permission &&
|
||||
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1,
|
||||
@ -118,7 +119,7 @@ namespace Tests
|
||||
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was applied to self");
|
||||
|
||||
// fail
|
||||
reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
reportEvent = client.Report("test report", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
|
||||
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Exception &&
|
||||
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"duplicate report was applied");
|
||||
@ -132,42 +133,42 @@ namespace Tests
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
|
||||
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
|
||||
Assert.False(client == null, "no client found to flag");
|
||||
|
||||
var flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
var flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
flagEvent.OnProcessed.Wait();
|
||||
|
||||
// succeed
|
||||
Assert.True(!flagEvent.Failed &&
|
||||
client.Level == Player.Permission.Flagged, $"player is not flagged [{flagEvent.FailReason.ToString()}]");
|
||||
client.Level == EFClient.Permission.Flagged, $"player is not flagged [{flagEvent.FailReason.ToString()}]");
|
||||
Assert.False(client.ReceivedPenalties.FirstOrDefault(p => p.Offense == "test flag") == null, "flag was not applied");
|
||||
|
||||
flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
flagEvent.OnProcessed.Wait();
|
||||
|
||||
// fail
|
||||
Assert.True(client.ReceivedPenalties.Count == 1, "flag was applied without permisions");
|
||||
|
||||
flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
flagEvent.OnProcessed.Wait();
|
||||
|
||||
// fail
|
||||
Assert.True(client.ReceivedPenalties.Count == 1, "duplicate flag was applied");
|
||||
|
||||
var unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
var unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
unflagEvent.OnProcessed.Wait();
|
||||
|
||||
// fail
|
||||
Assert.False(client.Level == Player.Permission.User, "user was unflagged without permissions");
|
||||
Assert.False(client.Level == EFClient.Permission.User, "user was unflagged without permissions");
|
||||
|
||||
unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
unflagEvent.OnProcessed.Wait();
|
||||
|
||||
// succeed
|
||||
Assert.True(client.Level == Player.Permission.User, "user was not unflagged");
|
||||
Assert.True(client.Level == EFClient.Permission.User, "user was not unflagged");
|
||||
|
||||
unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
unflagEvent.OnProcessed.Wait();
|
||||
|
||||
// succeed
|
||||
@ -182,18 +183,18 @@ namespace Tests
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
|
||||
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
|
||||
Assert.False(client == null, "no client found to kick");
|
||||
|
||||
var kickEvent = client.Kick("test kick", new Player() { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
var kickEvent = client.Kick("test kick", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
kickEvent.OnProcessed.Wait();
|
||||
|
||||
Assert.True(kickEvent.FailReason == GameEvent.EventFailReason.Permission, "client was kicked without permission");
|
||||
|
||||
kickEvent = client.Kick("test kick", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
kickEvent = client.Kick("test kick", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
kickEvent.OnProcessed.Wait();
|
||||
|
||||
Assert.True(Manager.Servers.First().GetPlayersAsList().FirstOrDefault(c => c.NetworkId == client.NetworkId) == null, "client was not kicked");
|
||||
Assert.True(Manager.Servers.First().GetClientsAsList().FirstOrDefault(c => c.NetworkId == client.NetworkId) == null, "client was not kicked");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -204,13 +205,13 @@ namespace Tests
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
|
||||
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
|
||||
Assert.False(client == null, "no client found to tempban");
|
||||
|
||||
var tbCommand = new CTempBan();
|
||||
tbCommand.ExecuteAsync(new GameEvent()
|
||||
{
|
||||
Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer },
|
||||
Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
|
||||
Target = client,
|
||||
Data = "5days test tempban",
|
||||
Type = GameEvent.EventType.Command,
|
||||
@ -229,13 +230,13 @@ namespace Tests
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
|
||||
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
|
||||
Assert.False(client == null, "no client found to ban");
|
||||
|
||||
var banCommand = new CBan();
|
||||
banCommand.ExecuteAsync(new GameEvent()
|
||||
{
|
||||
Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer },
|
||||
Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
|
||||
Target = client,
|
||||
Data = "test ban",
|
||||
Type = GameEvent.EventType.Command,
|
||||
@ -248,8 +249,8 @@ namespace Tests
|
||||
var unbanCommand = new CUnban();
|
||||
unbanCommand.ExecuteAsync(new GameEvent()
|
||||
{
|
||||
Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer },
|
||||
Target = Manager.GetClientService().Find(c => c.NetworkId == client.NetworkId).Result.First().AsPlayer(),
|
||||
Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
|
||||
Target = Manager.GetClientService().Find(c => c.NetworkId == client.NetworkId).Result.First(),
|
||||
Data = "test unban",
|
||||
Type = GameEvent.EventType.Command,
|
||||
Owner = client.CurrentServer
|
||||
|
@ -1,5 +1,6 @@
|
||||
using IW4MAdmin.Application;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
@ -53,7 +54,7 @@ namespace Tests
|
||||
var e = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Join,
|
||||
Origin = new Player()
|
||||
Origin = new EFClient()
|
||||
{
|
||||
Name = $"Player{i}",
|
||||
NetworkId = i,
|
||||
@ -78,7 +79,7 @@ namespace Tests
|
||||
var e = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Disconnect,
|
||||
Origin = new Player()
|
||||
Origin = new EFClient()
|
||||
{
|
||||
Name = $"Player{i}",
|
||||
NetworkId = i,
|
||||
@ -113,7 +114,7 @@ namespace Tests
|
||||
var e = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Connect,
|
||||
Origin = new Player()
|
||||
Origin = new EFClient()
|
||||
{
|
||||
Name = $"Player{i}",
|
||||
NetworkId = i,
|
||||
@ -134,7 +135,7 @@ namespace Tests
|
||||
waiters.Dequeue().OnProcessed.Wait();
|
||||
}
|
||||
|
||||
int actualClientNum = server.GetPlayersAsList().Count(p => p.State == Player.ClientState.Connected);
|
||||
int actualClientNum = server.GetClientsAsList().Count(p => p.State == EFClient.ClientState.Connected);
|
||||
Assert.True(actualClientNum == clientNum, $"client connected states don't match [{actualClientNum}:{clientNum}");
|
||||
|
||||
for (int i = clientIndexStart; i < clientNum + clientIndexStart; i++)
|
||||
@ -142,7 +143,7 @@ namespace Tests
|
||||
var e = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Disconnect,
|
||||
Origin = new Player()
|
||||
Origin = new EFClient()
|
||||
{
|
||||
Name = $"Player{i}",
|
||||
NetworkId = i,
|
||||
|
@ -1,5 +1,6 @@
|
||||
using IW4MAdmin.Application;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
using Xunit;
|
||||
|
||||
@ -21,7 +22,7 @@ namespace Tests
|
||||
var e = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Connect,
|
||||
Origin = new Player()
|
||||
Origin = new EFClient()
|
||||
{
|
||||
Name = $"Player1",
|
||||
NetworkId = 1,
|
||||
@ -33,7 +34,7 @@ namespace Tests
|
||||
Manager.GetEventHandler().AddEvent(e);
|
||||
e.OnProcessed.Wait();
|
||||
|
||||
var client = Manager.GetServers()[0].Players[0];
|
||||
var client = Manager.GetServers()[0].Clients[0];
|
||||
|
||||
e = new GameEvent()
|
||||
{
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
@ -23,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -13,12 +13,13 @@ using Microsoft.EntityFrameworkCore;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using static SharedLibraryCore.Database.Models.EFClient;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Welcome
|
||||
{
|
||||
public class Plugin : IPlugin
|
||||
{
|
||||
String TimesConnected(Player P)
|
||||
String TimesConnected(EFClient P)
|
||||
{
|
||||
int connection = P.Connections;
|
||||
String Prefix = String.Empty;
|
||||
@ -83,15 +84,15 @@ namespace IW4MAdmin.Plugins.Welcome
|
||||
|
||||
public async Task OnEventAsync(GameEvent E, Server S)
|
||||
{
|
||||
if (E.Type == GameEvent.EventType.Connect)
|
||||
if (E.Type == GameEvent.EventType.Join)
|
||||
{
|
||||
Player newPlayer = E.Origin;
|
||||
if (newPlayer.Level >= Player.Permission.Trusted && !E.Origin.Masked)
|
||||
EFClient newPlayer = E.Origin;
|
||||
if (newPlayer.Level >= Permission.Trusted && !E.Origin.Masked)
|
||||
E.Owner.Broadcast(await ProcessAnnouncement(Config.Configuration().PrivilegedAnnouncementMessage, newPlayer));
|
||||
|
||||
newPlayer.Tell(await ProcessAnnouncement(Config.Configuration().UserWelcomeMessage, newPlayer));
|
||||
|
||||
if (newPlayer.Level == Player.Permission.Flagged)
|
||||
if (newPlayer.Level == Permission.Flagged)
|
||||
{
|
||||
string penaltyReason;
|
||||
|
||||
@ -111,7 +112,7 @@ namespace IW4MAdmin.Plugins.Welcome
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> ProcessAnnouncement(string msg, Player joining)
|
||||
private async Task<string> ProcessAnnouncement(string msg, EFClient joining)
|
||||
{
|
||||
msg = msg.Replace("{{ClientName}}", joining.Name);
|
||||
msg = msg.Replace("{{ClientLevel}}", Utilities.ConvertLevelToColor(joining.Level, joining.ClientPermission.Name));
|
||||
@ -138,13 +139,14 @@ namespace IW4MAdmin.Plugins.Welcome
|
||||
{
|
||||
string response = await wc.DownloadStringTaskAsync(new Uri($"http://extreme-ip-lookup.com/json/{ip}"));
|
||||
var responseObj = JObject.Parse(response);
|
||||
response = responseObj["country"].ToString();
|
||||
|
||||
return responseObj["country"].ToString();
|
||||
return string.IsNullOrEmpty(response) ? Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_UNKNOWN_COUNTRY"] : response;
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
return "a third world country";
|
||||
return Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_UNKNOWN_IP"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.Welcome</PackageId>
|
||||
@ -20,7 +20,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
38
README.md
38
README.md
@ -5,13 +5,15 @@ _______
|
||||
### About
|
||||
**IW4MAdmin** is an administration tool for [IW4x](https://iw4xcachep26muba.onion.link/), [Pluto T6](https://forum.plutonium.pw/category/33/plutonium-t6), ~~[Pluto IW5](https://forum.plutonium.pw/category/5/plutonium-iw5)~~, and most Call of Duty<74> dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze.
|
||||
### Download
|
||||
Latest binary builds are always available at https://raidmax.org/IW4MAdmin
|
||||
Latest binary builds are always available at:
|
||||
- [RaidMax](https://raidmax.org/IW4MAdmin)
|
||||
- [GitHub](https://github.com/RaidMax/IW4M-Admin/releases)
|
||||
|
||||
---
|
||||
### Setup
|
||||
**IW4MAdmin** requires minimal effort to get up and running.
|
||||
#### Prerequisites
|
||||
* [.NET Core 2.1 Runtime](https://www.microsoft.com/net/download) *or newer*
|
||||
* [.NET Core 2.1.5 Runtime](https://www.microsoft.com/net/download) *or newer*
|
||||
#### Installation
|
||||
1. Install .NET Core Runtime
|
||||
2. Extract `IW4MAdmin-<version>.zip`
|
||||
@ -106,6 +108,14 @@ If you wish to further customize your experience of **IW4MAdmin**, the following
|
||||
* Specifies the [connection string](https://www.connectionstrings.com/mysql/) to a MySQL server that is used instead of SQLite
|
||||
* Default — `null`
|
||||
|
||||
`DatabaseProvider`
|
||||
* Specifies the database provider **IW4MAdmin** should use
|
||||
* Possible values — `sqlite`, `mysql`, `postgresql`
|
||||
* Default — `sqlite`
|
||||
|
||||
`Ignore Bots`
|
||||
* Disables bots from being registered by **IW4MAdmin**
|
||||
|
||||
`RConPollRate`
|
||||
* Specifies (in milliseconds) how often to poll each server for updates
|
||||
* Default — `5000`
|
||||
@ -164,8 +174,8 @@ ___
|
||||
|
||||
### Commands
|
||||
|Name |Alias|Description |Requires Target|Syntax |Required Level|
|
||||
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|
|
||||
|prune|pa|demote any privileged clients that have not connected recently (defaults to 30 days)|False|!pa \<optional inactive days\>|Owner|
|
||||
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ---------------|
|
||||
|prune|pa|demote any trusted clients that have not connected recently (defaults to 30 days)|False|!pa \<optional inactive days\>|Owner|
|
||||
|quit|q|quit IW4MAdmin|False|!q |Owner|
|
||||
|rcon|rcon|send rcon command to server|False|!rcon \<commands\>|Owner|
|
||||
|ban|b|permanently ban a client from the server|True|!b \<player\> \<reason\>|SeniorAdmin|
|
||||
@ -190,24 +200,21 @@ ___
|
||||
|unflag|uf|Remove flag for client|True|!uf \<player\>|Moderator|
|
||||
|uptime|up|get current application running time|False|!up |Moderator|
|
||||
|usage|us|get application memory usage|False|!us |Moderator|
|
||||
|balance|bal|balance teams|False|!bal |Trusted|
|
||||
|login|li|login using password|False|!li \<password\>|Trusted|
|
||||
|warn|w|warn client for infringing rules|True|!w \<player\> \<reason\>|Trusted|
|
||||
|warnclear|wc|remove all warnings for a client|True|!wc \<player\>|Trusted|
|
||||
|admins|a|list currently connected privileged clients|False|!a |User|
|
||||
|getexternalip|ip|view your external IP address|False|!ip |User|
|
||||
|help|h|list all available commands|False|!h \<optional commands\>|User|
|
||||
|mostplayed|mp|view the top 5 dedicated players on the server|False|!mp |User|
|
||||
|nextmap|nm|view next map in rotation|False|!nm |User|
|
||||
|owner|iamgod|claim ownership of the server|False|!iamgod |User|
|
||||
|ping|pi|get client's latency|False|!pi \<optional player\>|User|
|
||||
|privatemessage|pm|send message to other client|True|!pm \<player\> \<message\>|User|
|
||||
|report|rep|report a client for suspicious behavior|True|!rep \<player\> \<reason\>|User|
|
||||
|resetstats|rs|reset your stats to factory-new|False|!rs |User|
|
||||
|rules|r|list server rules|False|!r |User|
|
||||
|stats|xlrstats|view your stats|False|!xlrstats \<optional player\>|User|
|
||||
|topstats|ts|view the top 5 players in this server|False|!ts |User|
|
||||
|setgravatar|sg|set gravatar for webfront profile|False|!sg \<gravatar email\>|User|
|
||||
|whoami|who|give information about yourself|False|!who |User|
|
||||
|
||||
|
||||
_These commands include all shipped plugin commands._
|
||||
|
||||
---
|
||||
@ -302,6 +309,11 @@ ___
|
||||
#### VPN Detection [Script Plugin]
|
||||
- This plugin detects if a client is using a VPN and kicks them if they are
|
||||
- To disable this plugin, delete `Plugins\VPNDetection.js`
|
||||
- Adding ClientIds to `vpnExceptionIds` will prevent a client from being kicked.
|
||||
|
||||
#### Shared GUID Kicker [Script Plugin]
|
||||
- This plugin kicks users using a specific GUID
|
||||
- GUID `F4D2C30B712AC6E3` on IW4x was packed into a torrent version of the game.
|
||||
___
|
||||
### Webfront
|
||||
`Home`
|
||||
@ -432,9 +444,13 @@ python DiscordWebhook.py
|
||||
#### Anti-cheat
|
||||
This is an [IW4x](https://iw4xcachep26muba.onion.link/) only feature (wider game support planned), that uses analytics to detect aimbots and aim-assist tools.
|
||||
To utilize anti-cheat, enable it during setup **and** copy `_customcallbacks.gsc` from `userraw` into your `IW4x Server\userraw\scripts` folder.
|
||||
|
||||
The anti-cheat feature is a work in progress and as such will be constantly tweaked and may not be 100% accurate, however the goal is to deter as many cheaters as possible from IW4x.
|
||||
#### Database Storage
|
||||
By default, all **IW4MAdmin** information is stored in `Database.db`.
|
||||
|
||||
Should you need to reset your database, this file can simply be deleted.
|
||||
Additionally, this file should be preserved during updates to retain client information.
|
||||
Setting the `ConnectionString` property in `IW4MAdminSettings.json` will cause **IW4MAdmin** to attempt to use a MySQL connection for database storage.
|
||||
|
||||
Setting the `ConnectionString` and `DatabaseProvider` properties in `IW4MAdminSettings.json`
|
||||
will allow **IW4MAdmin** to use alternate methods for database storage.
|
@ -1,8 +1,9 @@
|
||||
dotnet publish WebfrontCore/WebfrontCore.csproj -c Prerelease -o C:\Projects\IW4M-Admin\Publish\WindowsPrerelease
|
||||
dotnet publish Application/Application.csproj -c Prerelease -o C:\Projects\IW4M-Admin\Publish\WindowsPrerelease
|
||||
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o C:\Projects\IW4M-Admin\Publish\WindowsPrerelease\GameLogServer
|
||||
dotnet publish WebfrontCore/WebfrontCore.csproj -c Prerelease -o X:\IW4MAdmin\Publish\WindowsPrerelease /p:PublishProfile=Prerelease
|
||||
dotnet publish Application/Application.csproj -c Prerelease -o X:\IW4MAdmin\Publish\WindowsPrerelease /p:PublishProfile=Prerelease
|
||||
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o X:\IW4MAdmin\Publish\WindowsPrerelease\GameLogServer
|
||||
::dotnet publish GameLogServer/DiscordWebhook.pyproj -c Release -o X:\IW4MAdmin\Publish\WindowsPrerelease\DiscordWebhook
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
|
||||
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=FolderProfile /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\GameLogServer\
|
||||
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=FolderProfile /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\DiscordWebhook\
|
||||
cd "C:\Projects\IW4M-Admin\DEPLOY\"
|
||||
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=PreRelease /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\GameLogServer\
|
||||
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=PreRelease /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\DiscordWebhook\
|
||||
cd "X:\IW4MAdmin\DEPLOY\"
|
||||
PowerShell ".\upload_prerelease.ps1"
|
@ -1,8 +1,8 @@
|
||||
dotnet publish WebfrontCore/WebfrontCore.csproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows
|
||||
dotnet publish Application/Application.csproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows
|
||||
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows\GameLogServer
|
||||
dotnet publish WebfrontCore/WebfrontCore.csproj -c Release -o X:\IW4MAdmin\Publish\Windows
|
||||
dotnet publish Application/Application.csproj -c Release -o X:\IW4MAdmin\Publish\Windows
|
||||
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o X:\IW4MAdmin\Publish\Windows\GameLogServer
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
|
||||
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\GameLogServer\
|
||||
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\DiscordWebhook\
|
||||
cd "C:\Projects\IW4M-Admin\DEPLOY\"
|
||||
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\GameLogServer\
|
||||
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\DiscordWebhook\
|
||||
cd "X:\IW4MAdmin\DEPLOY\"
|
||||
PowerShell ".\upload_release.ps1"
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
namespace SharedLibraryCore
|
||||
@ -14,7 +14,7 @@ namespace SharedLibraryCore
|
||||
|
||||
public abstract class Command
|
||||
{
|
||||
public Command(String commandName, String commandDescription, String commandAlias, Player.Permission requiredPermission, bool requiresTarget, CommandArgument[] param = null)
|
||||
public Command(String commandName, String commandDescription, String commandAlias, EFClient.Permission requiredPermission, bool requiresTarget, CommandArgument[] param = null)
|
||||
{
|
||||
Name = commandName;
|
||||
Description = commandDescription;
|
||||
@ -33,7 +33,7 @@ namespace SharedLibraryCore
|
||||
public String Alias { get; private set; }
|
||||
public int RequiredArgumentCount => Arguments.Count(c => c.Required);
|
||||
public bool RequiresTarget { get; private set; }
|
||||
public Player.Permission Permission { get; private set; }
|
||||
public EFClient.Permission Permission { get; private set; }
|
||||
public CommandArgument[] Arguments { get; private set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -60,7 +61,7 @@ namespace SharedLibraryCore.Commands
|
||||
var found = await Manager.GetClientService().Get(dbID);
|
||||
if (found != null)
|
||||
{
|
||||
E.Target = found.AsPlayer();
|
||||
E.Target = found;
|
||||
E.Target.CurrentServer = E.Owner;
|
||||
E.Data = String.Join(" ", Args.Skip(1));
|
||||
}
|
||||
@ -68,14 +69,14 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
else if (Args[0].Length < 3 && cNum > -1 && cNum < E.Owner.MaxClients) // user specifying target by client num
|
||||
{
|
||||
if (E.Owner.Players[cNum] != null)
|
||||
if (E.Owner.Clients[cNum] != null)
|
||||
{
|
||||
E.Target = E.Owner.Players[cNum];
|
||||
E.Target = E.Owner.Clients[cNum];
|
||||
E.Data = String.Join(" ", Args.Skip(1));
|
||||
}
|
||||
}
|
||||
|
||||
List<Player> matchingPlayers;
|
||||
List<EFClient> matchingPlayers;
|
||||
|
||||
if (E.Target == null && C.RequiresTarget) // Find active player including quotes (multiple words)
|
||||
{
|
||||
|
22
SharedLibraryCore/Commands/GenerateTokenCommand.cs
Normal file
22
SharedLibraryCore/Commands/GenerateTokenCommand.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedLibraryCore.Commands
|
||||
{
|
||||
public class GenerateTokenCommand : Command
|
||||
{
|
||||
public GenerateTokenCommand() :
|
||||
base("generatetoken", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_DESC"], "gt", EFClient.Permission.Trusted, false)
|
||||
{ }
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
string token = E.Owner.Manager.TokenAuthenticator.GenerateNextToken(E.Origin.NetworkId);
|
||||
var _event = token == null ?
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_FAIL"]) :
|
||||
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_SUCCESS"]} {token}");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -12,14 +12,13 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using static SharedLibraryCore.RCon.StaticHelpers;
|
||||
|
||||
namespace SharedLibraryCore.Commands
|
||||
{
|
||||
public class CQuit : Command
|
||||
{
|
||||
public CQuit() :
|
||||
base("quit", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_QUIT_DESC"], "q", Player.Permission.Owner, false)
|
||||
base("quit", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_QUIT_DESC"], "q", EFClient.Permission.Owner, false)
|
||||
{ }
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
@ -31,18 +30,32 @@ namespace SharedLibraryCore.Commands
|
||||
public class COwner : Command
|
||||
{
|
||||
public COwner() :
|
||||
base("owner", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_DESC"], "iamgod", Player.Permission.User, false)
|
||||
base("owner", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_DESC"], "iamgod", EFClient.Permission.User, false)
|
||||
{ }
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
if ((await (E.Owner.Manager.GetClientService() as ClientService).GetOwners()).Count == 0)
|
||||
{
|
||||
E.Origin.Level = Player.Permission.Owner;
|
||||
var oldPermission = E.Origin.Level;
|
||||
E.Origin.Level = EFClient.Permission.Owner;
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_SUCCESS"]);
|
||||
// so setpassword/login works
|
||||
E.Owner.Manager.GetPrivilegedClients().Add(E.Origin.ClientId, E.Origin);
|
||||
await E.Owner.Manager.GetClientService().Update(E.Origin);
|
||||
|
||||
var e = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.ChangePermission,
|
||||
Origin = E.Origin,
|
||||
Target = E.Origin,
|
||||
Owner = E.Owner,
|
||||
Extra = new Change()
|
||||
{
|
||||
PreviousValue = oldPermission.ToString(),
|
||||
NewValue = E.Origin.Level.ToString()
|
||||
}
|
||||
};
|
||||
|
||||
E.Owner.Manager.GetEventHandler().AddEvent(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -54,7 +67,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CWarn : Command
|
||||
{
|
||||
public CWarn() :
|
||||
base("warn", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_DESC"], "w", Player.Permission.Trusted, true, new CommandArgument[]
|
||||
base("warn", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_DESC"], "w", EFClient.Permission.Trusted, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -83,7 +96,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CWarnClear : Command
|
||||
{
|
||||
public CWarnClear() :
|
||||
base("warnclear", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_DESC"], "wc", Player.Permission.Trusted, true, new CommandArgument[]
|
||||
base("warnclear", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_DESC"], "wc", EFClient.Permission.Trusted, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -107,7 +120,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CKick : Command
|
||||
{
|
||||
public CKick() :
|
||||
base("kick", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_DESC"], "k", Player.Permission.Moderator, true, new CommandArgument[]
|
||||
base("kick", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_DESC"], "k", EFClient.Permission.Moderator, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -133,7 +146,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CSay : Command
|
||||
{
|
||||
public CSay() :
|
||||
base("say", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SAY_DESC"], "s", Player.Permission.Moderator, false, new CommandArgument[]
|
||||
base("say", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SAY_DESC"], "s", EFClient.Permission.Moderator, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -153,7 +166,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CTempBan : Command
|
||||
{
|
||||
public CTempBan() :
|
||||
base("tempban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_DESC"], "tb", Player.Permission.Administrator, true, new CommandArgument[]
|
||||
base("tempban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_DESC"], "tb", EFClient.Permission.Administrator, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -183,17 +196,25 @@ namespace SharedLibraryCore.Commands
|
||||
string tempbanReason = match.Groups[2].ToString();
|
||||
var length = match.Groups[1].ToString().ParseTimespan();
|
||||
|
||||
if (length > E.Owner.Manager.GetApplicationSettings().Configuration().MaximumTempBanTime)
|
||||
{
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL_TOOLONG"]);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var _ = !(await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync()).Failed ?
|
||||
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}") :
|
||||
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CBan : Command
|
||||
{
|
||||
public CBan() :
|
||||
base("ban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_DESC"], "b", Player.Permission.SeniorAdmin, true, new CommandArgument[]
|
||||
base("ban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_DESC"], "b", EFClient.Permission.SeniorAdmin, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -210,7 +231,7 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
var _ = !(await E.Target.Ban(E.Data, E.Origin).WaitAsync()).Failed ?
|
||||
var _ = !(await E.Target.Ban(E.Data, E.Origin, false).WaitAsync()).Failed ?
|
||||
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"]}") :
|
||||
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_FAIL"]} {E.Target.Name}");
|
||||
}
|
||||
@ -219,7 +240,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CUnban : Command
|
||||
{
|
||||
public CUnban() :
|
||||
base("unban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_DESC"], "ub", Player.Permission.SeniorAdmin, true, new CommandArgument[]
|
||||
base("unban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_DESC"], "ub", EFClient.Permission.SeniorAdmin, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -252,12 +273,12 @@ namespace SharedLibraryCore.Commands
|
||||
public class CWhoAmI : Command
|
||||
{
|
||||
public CWhoAmI() :
|
||||
base("whoami", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WHO_DESC"], "who", Player.Permission.User, false)
|
||||
base("whoami", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WHO_DESC"], "who", EFClient.Permission.User, false)
|
||||
{ }
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
String You = String.Format("{0} [^3#{1}^7] {2} [^3@{3}^7] [{4}^7] IP: {5}", E.Origin.Name, E.Origin.ClientNumber, E.Origin.NetworkId, E.Origin.ClientId, Utilities.ConvertLevelToColor(E.Origin.Level, E.Origin.ClientPermission.Name), E.Origin.IPAddressString);
|
||||
String You = String.Format("{0} [^3#{1}^7] {2} ^7[^3@{3}^7] ^7[{4}^7] IP: {5}", E.Origin.Name, E.Origin.ClientNumber, E.Origin.NetworkId, E.Origin.ClientId, Utilities.ConvertLevelToColor(E.Origin.Level, E.Origin.ClientPermission.Name), E.Origin.IPAddressString);
|
||||
E.Origin.Tell(You);
|
||||
|
||||
return Task.CompletedTask;
|
||||
@ -267,27 +288,33 @@ namespace SharedLibraryCore.Commands
|
||||
public class CList : Command
|
||||
{
|
||||
public CList() :
|
||||
base("list", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_LIST_DESC"], "l", Player.Permission.Moderator, false)
|
||||
base("list", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_LIST_DESC"], "l", EFClient.Permission.Moderator, false)
|
||||
{ }
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
StringBuilder playerList = new StringBuilder();
|
||||
int count = 0;
|
||||
for (int i = 0; i < E.Owner.Players.Count; i++)
|
||||
for (int i = 0; i < E.Owner.Clients.Count; i++)
|
||||
{
|
||||
var P = E.Owner.Players[i];
|
||||
var P = E.Owner.Clients[i];
|
||||
|
||||
if (P == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// todo: fix spacing
|
||||
// todo: make this better :)
|
||||
if (P.Masked)
|
||||
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(Player.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(Player.Permission.SeniorAdmin.ToString().Length - Player.Permission.User.ToString().Length));
|
||||
{
|
||||
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor( EFClient.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces( EFClient.Permission.SeniorAdmin.ToString().Length - EFClient.Permission.User.ToString().Length));
|
||||
}
|
||||
else
|
||||
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(P.Level, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(Player.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length));
|
||||
{
|
||||
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(P.Level, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces( EFClient.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length));
|
||||
}
|
||||
|
||||
if (count == 2 || E.Owner.GetPlayersAsList().Count == 1)
|
||||
if (count == 2 || E.Owner.GetClientsAsList().Count == 1)
|
||||
{
|
||||
E.Origin.Tell(playerList.ToString());
|
||||
count = 0;
|
||||
@ -312,7 +339,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CHelp : Command
|
||||
{
|
||||
public CHelp() :
|
||||
base("help", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_HELP_DESC"], "h", Player.Permission.User, false, new CommandArgument[]
|
||||
base("help", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_HELP_DESC"], "h", EFClient.Permission.User, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -377,7 +404,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CFastRestart : Command
|
||||
{
|
||||
public CFastRestart() :
|
||||
base("fastrestart", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FASTRESTART_DESC"], "fr", Player.Permission.Moderator, false)
|
||||
base("fastrestart", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FASTRESTART_DESC"], "fr", EFClient.Permission.Moderator, false)
|
||||
{ }
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
@ -393,7 +420,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CMapRotate : Command
|
||||
{
|
||||
public CMapRotate() :
|
||||
base("maprotate", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE_DESC"], "mr", Player.Permission.Administrator, false)
|
||||
base("maprotate", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE_DESC"], "mr", EFClient.Permission.Administrator, false)
|
||||
{ }
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
@ -410,7 +437,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CSetLevel : Command
|
||||
{
|
||||
public CSetLevel() :
|
||||
base("setlevel", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DESC"], "sl", Player.Permission.Moderator, true, new CommandArgument[]
|
||||
base("setlevel", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DESC"], "sl", EFClient.Permission.Moderator, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -433,17 +460,17 @@ namespace SharedLibraryCore.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
Player.Permission oldPerm = E.Target.Level;
|
||||
Player.Permission newPerm = Utilities.MatchPermission(E.Data);
|
||||
EFClient.Permission oldPerm = E.Target.Level;
|
||||
EFClient.Permission newPerm = Utilities.MatchPermission(E.Data);
|
||||
|
||||
if (newPerm == Player.Permission.Owner &&
|
||||
if (newPerm == EFClient.Permission.Owner &&
|
||||
!E.Owner.Manager.GetApplicationSettings().Configuration().EnableMultipleOwners)
|
||||
{
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_OWNER"]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (E.Origin.Level < Player.Permission.Owner &&
|
||||
if (E.Origin.Level < EFClient.Permission.Owner &&
|
||||
!E.Owner.Manager.GetApplicationSettings().Configuration().EnableSteppedHierarchy)
|
||||
{
|
||||
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_STEPPEDDISABLED"]} ^5{E.Target.Name}");
|
||||
@ -452,14 +479,14 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
if (newPerm >= E.Origin.Level)
|
||||
{
|
||||
if (E.Origin.Level < Player.Permission.Owner)
|
||||
if (E.Origin.Level < EFClient.Permission.Owner)
|
||||
{
|
||||
E.Origin.Tell(string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_LEVELTOOHIGH"], E.Target.Name, (E.Origin.Level - 1).ToString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
else if (newPerm > Player.Permission.Banned)
|
||||
else if (newPerm > EFClient.Permission.Banned)
|
||||
{
|
||||
var ActiveClient = E.Owner.Manager.GetActiveClients()
|
||||
.FirstOrDefault(p => p.NetworkId == E.Target.NetworkId);
|
||||
@ -468,8 +495,12 @@ namespace SharedLibraryCore.Commands
|
||||
{
|
||||
ActiveClient.Level = newPerm;
|
||||
await E.Owner.Manager.GetClientService().Update(ActiveClient);
|
||||
|
||||
if (newPerm > oldPerm)
|
||||
{
|
||||
ActiveClient.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS_TARGET"]} {newPerm}");
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
@ -477,17 +508,6 @@ namespace SharedLibraryCore.Commands
|
||||
await E.Owner.Manager.GetClientService().Update(E.Target);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
E.Owner.Manager.GetPrivilegedClients().Add(E.Target.ClientId, E.Target);
|
||||
}
|
||||
|
||||
catch (Exception)
|
||||
{
|
||||
// this updates their privilege level to the webfront claims
|
||||
E.Owner.Manager.GetPrivilegedClients()[E.Target.ClientId] = E.Target;
|
||||
}
|
||||
|
||||
var e = new GameEvent()
|
||||
{
|
||||
Origin = E.Origin,
|
||||
@ -503,6 +523,8 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
E.Owner.Manager.GetEventHandler().AddEvent(e);
|
||||
|
||||
var _ = newPerm < oldPerm ?
|
||||
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DEMOTE_SUCCESS"]} {E.Target.Name}") :
|
||||
E.Origin.Tell($"{E.Target.Name} {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS"]}");
|
||||
}
|
||||
|
||||
@ -516,7 +538,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CUsage : Command
|
||||
{
|
||||
public CUsage() :
|
||||
base("usage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_USAGE_DESC"], "us", Player.Permission.Moderator, false)
|
||||
base("usage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_USAGE_DESC"], "us", EFClient.Permission.Moderator, false)
|
||||
{ }
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
@ -529,7 +551,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CUptime : Command
|
||||
{
|
||||
public CUptime() :
|
||||
base("uptime", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UPTIME_DESC"], "up", Player.Permission.Moderator, false)
|
||||
base("uptime", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UPTIME_DESC"], "up", EFClient.Permission.Moderator, false)
|
||||
{ }
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
@ -544,13 +566,13 @@ namespace SharedLibraryCore.Commands
|
||||
public class CListAdmins : Command
|
||||
{
|
||||
public CListAdmins() :
|
||||
base("admins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ADMINS_DESC"], "a", Player.Permission.User, false)
|
||||
base("admins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ADMINS_DESC"], "a", EFClient.Permission.User, false)
|
||||
{ }
|
||||
|
||||
public static string OnlineAdmins(Server S)
|
||||
{
|
||||
var onlineAdmins = S.GetPlayersAsList()
|
||||
.Where(p => p.Level > Player.Permission.Flagged)
|
||||
var onlineAdmins = S.GetClientsAsList()
|
||||
.Where(p => p.Level > EFClient.Permission.Flagged)
|
||||
.Where(p => !p.Masked)
|
||||
.Select(p => $"[^3{Utilities.ConvertLevelToColor(p.Level, p.ClientPermission.Name)}^7] {p.Name}");
|
||||
|
||||
@ -573,7 +595,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CLoadMap : Command
|
||||
{
|
||||
public CLoadMap() :
|
||||
base("map", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_DESC"], "m", Player.Permission.Administrator, false, new CommandArgument[]
|
||||
base("map", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_DESC"], "m", EFClient.Permission.Administrator, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -606,7 +628,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CFindPlayer : Command
|
||||
{
|
||||
public CFindPlayer() :
|
||||
base("find", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FIND_DESC"], "f", Player.Permission.Administrator, false, new CommandArgument[]
|
||||
base("find", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FIND_DESC"], "f", EFClient.Permission.Administrator, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -625,7 +647,7 @@ namespace SharedLibraryCore.Commands
|
||||
}
|
||||
|
||||
IList<EFClient> db_players = (await (E.Owner.Manager.GetClientService() as ClientService)
|
||||
.GetClientByName(E.Data))
|
||||
.FindClientsByIdentifier(E.Data))
|
||||
.OrderByDescending(p => p.LastConnection)
|
||||
.ToList();
|
||||
|
||||
@ -650,7 +672,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CListRules : Command
|
||||
{
|
||||
public CListRules() :
|
||||
base("rules", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RULES_DESC"], "r", Player.Permission.User, false)
|
||||
base("rules", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RULES_DESC"], "r", EFClient.Permission.User, false)
|
||||
{ }
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
@ -668,7 +690,9 @@ namespace SharedLibraryCore.Commands
|
||||
var rules = new List<string>();
|
||||
rules.AddRange(E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules);
|
||||
if (E.Owner.ServerConfig.Rules != null)
|
||||
{
|
||||
rules.AddRange(E.Owner.ServerConfig.Rules);
|
||||
}
|
||||
|
||||
foreach (string r in rules)
|
||||
{
|
||||
@ -683,7 +707,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CPrivateMessage : Command
|
||||
{
|
||||
public CPrivateMessage() :
|
||||
base("privatemessage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PM_DESC"], "pm", Player.Permission.User, true, new CommandArgument[]
|
||||
base("privatemessage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PM_DESC"], "pm", EFClient.Permission.User, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -710,7 +734,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CFlag : Command
|
||||
{
|
||||
public CFlag() :
|
||||
base("flag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_DESC"], "fp", Player.Permission.Moderator, true, new CommandArgument[]
|
||||
base("flag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_DESC"], "fp", EFClient.Permission.Moderator, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -752,7 +776,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CUnflag : Command
|
||||
{
|
||||
public CUnflag() :
|
||||
base("unflag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_DESC"], "uf", Player.Permission.Moderator, true, new CommandArgument[]
|
||||
base("unflag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_DESC"], "uf", EFClient.Permission.Moderator, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -790,7 +814,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CReport : Command
|
||||
{
|
||||
public CReport() :
|
||||
base("report", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_DESC"], "rep", Player.Permission.User, true, new CommandArgument[]
|
||||
base("report", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_DESC"], "rep", EFClient.Permission.User, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -861,7 +885,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CListReports : Command
|
||||
{
|
||||
public CListReports() :
|
||||
base("reports", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORTS_DESC"], "reps", Player.Permission.Moderator, false, new CommandArgument[]
|
||||
base("reports", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORTS_DESC"], "reps", EFClient.Permission.Moderator, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -898,7 +922,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CMask : Command
|
||||
{
|
||||
public CMask() :
|
||||
base("mask", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MASK_DESC"], "hide", Player.Permission.Moderator, false)
|
||||
base("mask", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MASK_DESC"], "hide", EFClient.Permission.Moderator, false)
|
||||
{ }
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
@ -921,7 +945,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CListBanInfo : Command
|
||||
{
|
||||
public CListBanInfo() :
|
||||
base("baninfo", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_DESC"], "bi", Player.Permission.Moderator, true, new CommandArgument[]
|
||||
base("baninfo", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_DESC"], "bi", EFClient.Permission.Moderator, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -935,7 +959,8 @@ namespace SharedLibraryCore.Commands
|
||||
{
|
||||
var B = await E.Owner.Manager.GetPenaltyService().GetClientPenaltiesAsync(E.Target.ClientId);
|
||||
|
||||
var penalty = B.FirstOrDefault(b => b.Type > Penalty.PenaltyType.Kick && b.Expires > DateTime.UtcNow);
|
||||
var penalty = B.FirstOrDefault(b => b.Type > Penalty.PenaltyType.Kick &&
|
||||
(b.Expires == null || b.Expires > DateTime.UtcNow));
|
||||
|
||||
if (penalty == null)
|
||||
{
|
||||
@ -943,7 +968,7 @@ namespace SharedLibraryCore.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
string timeRemaining = penalty.Type == Penalty.PenaltyType.TempBan ? $"({(penalty.Expires - DateTime.UtcNow).TimeSpanText()} remaining)" : "";
|
||||
string timeRemaining = penalty.Type == Penalty.PenaltyType.TempBan ? $"({(penalty.Expires.Value - DateTime.UtcNow).TimeSpanText()} remaining)" : "";
|
||||
string success = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_SUCCESS"];
|
||||
|
||||
E.Origin.Tell($"^1{E.Target.Name} ^7{string.Format(success, penalty.Punisher.Name)} {penalty.Punisher.Name} {timeRemaining}");
|
||||
@ -953,7 +978,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CListAlias : Command
|
||||
{
|
||||
public CListAlias() :
|
||||
base("alias", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ALIAS_DESC"], "known", Player.Permission.Moderator, true, new CommandArgument[]
|
||||
base("alias", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ALIAS_DESC"], "known", EFClient.Permission.Moderator, true, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -987,7 +1012,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CExecuteRCON : Command
|
||||
{
|
||||
public CExecuteRCON() :
|
||||
base("rcon", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_DESC"], "rcon", Player.Permission.Owner, false, new CommandArgument[]
|
||||
base("rcon", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_DESC"], "rcon", EFClient.Permission.Owner, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -1001,16 +1026,21 @@ namespace SharedLibraryCore.Commands
|
||||
{
|
||||
var Response = await E.Owner.ExecuteCommandAsync(E.Data.Trim());
|
||||
foreach (string S in Response)
|
||||
{
|
||||
E.Origin.Tell(S.StripColors());
|
||||
}
|
||||
|
||||
if (Response.Length == 0)
|
||||
{
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_SUCCESS"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CPlugins : Command
|
||||
{
|
||||
public CPlugins() :
|
||||
base("plugins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PLUGINS_DESC"], "p", Player.Permission.Administrator, false)
|
||||
base("plugins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PLUGINS_DESC"], "p", EFClient.Permission.Administrator, false)
|
||||
{ }
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
@ -1027,7 +1057,7 @@ namespace SharedLibraryCore.Commands
|
||||
public class CIP : Command
|
||||
{
|
||||
public CIP() :
|
||||
base("getexternalip", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_IP_DESC"], "ip", Player.Permission.User, false)
|
||||
base("getexternalip", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_IP_DESC"], "ip", EFClient.Permission.User, false)
|
||||
{ }
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
@ -1039,7 +1069,7 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
public class CPruneAdmins : Command
|
||||
{
|
||||
public CPruneAdmins() : base("prune", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_DESC"], "pa", Player.Permission.Owner, false, new CommandArgument[]
|
||||
public CPruneAdmins() : base("prune", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_DESC"], "pa", EFClient.Permission.Owner, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -1059,9 +1089,11 @@ namespace SharedLibraryCore.Commands
|
||||
{
|
||||
inactiveDays = Int32.Parse(E.Data);
|
||||
if (inactiveDays < 1)
|
||||
{
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (FormatException)
|
||||
{
|
||||
@ -1076,10 +1108,10 @@ namespace SharedLibraryCore.Commands
|
||||
{
|
||||
var lastActive = DateTime.UtcNow.AddDays(-inactiveDays);
|
||||
inactiveUsers = await context.Clients
|
||||
.Where(c => c.Level > Player.Permission.Flagged && c.Level <= Player.Permission.Moderator)
|
||||
.Where(c => c.Level > EFClient.Permission.Flagged && c.Level <= EFClient.Permission.Moderator)
|
||||
.Where(c => c.LastConnection < lastActive)
|
||||
.ToListAsync();
|
||||
inactiveUsers.ForEach(c => c.Level = Player.Permission.User);
|
||||
inactiveUsers.ForEach(c => c.Level = EFClient.Permission.User);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
E.Origin.Tell($"^5{inactiveUsers.Count} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_SUCCESS"]}");
|
||||
@ -1088,7 +1120,7 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
public class CSetPassword : Command
|
||||
{
|
||||
public CSetPassword() : base("setpassword", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETPASSWORD_DESC"], "sp", Player.Permission.Moderator, false, new CommandArgument[]
|
||||
public CSetPassword() : base("setpassword", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETPASSWORD_DESC"], "sp", EFClient.Permission.Moderator, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -1122,11 +1154,22 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
public class CKillServer : Command
|
||||
{
|
||||
public CKillServer() : base("killserver", "kill the game server", "kill", Player.Permission.Administrator, false)
|
||||
public CKillServer() : base("killserver", "kill the game server", "kill", EFClient.Permission.Administrator, false)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
if (E.Owner.ServerConfig.ManualLogPath != null)
|
||||
{
|
||||
using (var wc = new WebClient())
|
||||
{
|
||||
E.Owner.RestartRequested = true;
|
||||
var response = await wc.DownloadStringTaskAsync(new Uri($"{E.Owner.ServerConfig.ManualLogPath}/restart"));
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x");
|
||||
|
||||
@ -1188,11 +1231,12 @@ namespace SharedLibraryCore.Commands
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class CPing : Command
|
||||
{
|
||||
public CPing() : base("ping", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_DESC"], "pi", Player.Permission.User, false, new CommandArgument[]
|
||||
public CPing() : base("ping", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_DESC"], "pi", EFClient.Permission.User, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -1207,17 +1251,25 @@ namespace SharedLibraryCore.Commands
|
||||
if (E.Message.IsBroadcastCommand())
|
||||
{
|
||||
if (E.Target == null)
|
||||
{
|
||||
E.Owner.Broadcast($"{E.Origin.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Origin.Ping}^7ms");
|
||||
}
|
||||
else
|
||||
{
|
||||
E.Owner.Broadcast($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (E.Target == null)
|
||||
{
|
||||
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_SELF"]} ^5{E.Origin.Ping}^7ms");
|
||||
}
|
||||
else
|
||||
{
|
||||
E.Origin.Tell($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms");
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -1225,7 +1277,7 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
public class CSetGravatar : Command
|
||||
{
|
||||
public CSetGravatar() : base("setgravatar", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_DESC"], "sg", Player.Permission.User, false, new CommandArgument[]
|
||||
public CSetGravatar() : base("setgravatar", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_DESC"], "sg", EFClient.Permission.User, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -1289,7 +1341,7 @@ namespace SharedLibraryCore.Commands
|
||||
/// </summary>
|
||||
public class CNextMap : Command
|
||||
{
|
||||
public CNextMap() : base("nextmap", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_DESC"], "nm", Player.Permission.User, false) { }
|
||||
public CNextMap() : base("nextmap", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_DESC"], "nm", EFClient.Permission.User, false) { }
|
||||
public static async Task<string> GetNextMap(Server s)
|
||||
{
|
||||
string mapRotation = (await s.GetDvarAsync<string>("sv_mapRotation")).Value.ToLower();
|
||||
|
@ -16,11 +16,17 @@ namespace SharedLibraryCore.Configuration
|
||||
public string SocialLinkAddress { get; set; }
|
||||
public string SocialLinkTitle { get; set; }
|
||||
public string WebfrontBindUrl { get; set; }
|
||||
public string ManualWebfrontUrl { get; set; }
|
||||
public string WebfrontUrl => string.IsNullOrEmpty(ManualWebfrontUrl) ? WebfrontBindUrl.Replace("0.0.0.0", "127.0.0.1") : ManualWebfrontUrl;
|
||||
public string CustomParserEncoding { get; set; }
|
||||
public string CustomLocale { get; set; }
|
||||
public string DatabaseProvider { get; set; } = "sqlite";
|
||||
public string ConnectionString { get; set; }
|
||||
public int RConPollRate { get; set; } = 5000;
|
||||
public bool IgnoreBots { get; set; }
|
||||
public TimeSpan MaximumTempBanTime { get; set; } = new TimeSpan(24 * 30, 0, 0);
|
||||
public bool EnableWebfrontConnectionWhitelist { get; set; }
|
||||
public List<string> WebfrontConnectionWhitelist { get; set; }
|
||||
public string Id { get; set; }
|
||||
public List<ServerConfiguration> Servers { get; set; }
|
||||
public int AutoMessagePeriod { get; set; }
|
||||
|
@ -1,6 +1,7 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharedLibraryCore.Configuration
|
||||
{
|
||||
@ -11,10 +12,49 @@ namespace SharedLibraryCore.Configuration
|
||||
public string Password { get; set; }
|
||||
public IList<string> Rules { get; set; }
|
||||
public IList<string> AutoMessages { get; set; }
|
||||
public bool UseT6MParser { get; set; }
|
||||
public bool UseIW5MParser { get; set; }
|
||||
public string ManualLogPath { get; set; }
|
||||
public string RConParserVersion { get; set; }
|
||||
public string EventParserVersion { get; set; }
|
||||
public int ReservedSlotNumber { get; set; }
|
||||
public Uri GameLogServerUrl { get; set; }
|
||||
|
||||
private readonly IList<IRConParser> rconParsers;
|
||||
private readonly IList<IEventParser> eventParsers;
|
||||
|
||||
public ServerConfiguration()
|
||||
{
|
||||
rconParsers = new List<IRConParser>();
|
||||
eventParsers = new List<IEventParser>();
|
||||
}
|
||||
|
||||
public void AddRConParser(IRConParser parser) => rconParsers.Add(parser);
|
||||
public void AddEventParser(IEventParser parser) => eventParsers.Add(parser);
|
||||
|
||||
public void ModifyParsers()
|
||||
{
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
var parserVersions = rconParsers.Select(_parser => _parser.Version).ToArray();
|
||||
var selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_RCON_PARSER_VERSION"]} ({IPAddress}:{Port})", $"{loc["SETUP_PROMPT_DEFAULT"]} (Call of Duty)", null, parserVersions);
|
||||
|
||||
if (selection.Item1 > 0)
|
||||
{
|
||||
RConParserVersion = selection.Item2;
|
||||
|
||||
if (!rconParsers[selection.Item1 - 1].CanGenerateLogPath)
|
||||
{
|
||||
Console.WriteLine(loc["SETUP_SERVER_NO_LOG"]);
|
||||
ManualLogPath = Utilities.PromptString(loc["SETUP_SERVER_LOG_PATH"]);
|
||||
}
|
||||
}
|
||||
|
||||
parserVersions = eventParsers.Select(_parser => _parser.Version).ToArray();
|
||||
selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_EVENT_PARSER_VERSION"]} ({IPAddress}:{Port})", $"{loc["SETUP_PROMPT_DEFAULT"]} (Call of Duty)", null, parserVersions);
|
||||
|
||||
if (selection.Item1 > 0)
|
||||
{
|
||||
EventParserVersion = selection.Item2;
|
||||
}
|
||||
}
|
||||
|
||||
public IBaseConfiguration Generate()
|
||||
{
|
||||
@ -36,18 +76,11 @@ namespace SharedLibraryCore.Configuration
|
||||
}
|
||||
|
||||
Password = Utilities.PromptString(loc["SETUP_SERVER_RCON"]);
|
||||
|
||||
AutoMessages = new List<string>();
|
||||
Rules = new List<string>();
|
||||
ReservedSlotNumber = loc["SETUP_SERVER_RESERVEDSLOT"].PromptInt(null, 0, 32);
|
||||
|
||||
|
||||
UseT6MParser = Utilities.PromptBool(loc["SETUP_SERVER_USET6M"]);
|
||||
if (!UseT6MParser)
|
||||
UseIW5MParser = Utilities.PromptBool(loc["SETUP_SERVER_USEIW5M"]);
|
||||
if (UseIW5MParser)
|
||||
ManualLogPath = Utilities.PromptString(loc["SETUP_SERVER_MANUALLOG"]);
|
||||
|
||||
ReservedSlotNumber = loc["SETUP_SERVER_RESERVEDSLOT"].PromptInt(0, 32);
|
||||
ModifyParsers();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using SharedLibraryCore.Database.Models;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static SharedLibraryCore.Database.Models.EFClient;
|
||||
|
||||
namespace SharedLibraryCore.Database
|
||||
{
|
||||
@ -47,7 +48,7 @@ namespace SharedLibraryCore.Database
|
||||
Connections = 0,
|
||||
FirstConnection = DateTime.UtcNow,
|
||||
LastConnection = DateTime.UtcNow,
|
||||
Level = Objects.Player.Permission.Console,
|
||||
Level = Permission.Console,
|
||||
Masked = true,
|
||||
NetworkId = 0,
|
||||
AliasLinkId = 1,
|
||||
|
@ -1,17 +1,13 @@
|
||||
using System;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Npgsql;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace SharedLibraryCore.Database
|
||||
{
|
||||
@ -26,6 +22,7 @@ namespace SharedLibraryCore.Database
|
||||
|
||||
static string _ConnectionString;
|
||||
static string _provider;
|
||||
private static readonly string _migrationPluginDirectory = @"X:\IW4MAdmin\BUILD\Plugins\";
|
||||
|
||||
public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt) { }
|
||||
|
||||
@ -39,6 +36,13 @@ namespace SharedLibraryCore.Database
|
||||
this.ChangeTracker.LazyLoadingEnabled = false;
|
||||
this.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
this.ChangeTracker.AutoDetectChangesEnabled = true;
|
||||
this.ChangeTracker.LazyLoadingEnabled = true;
|
||||
this.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;
|
||||
}
|
||||
}
|
||||
|
||||
public DatabaseContext(string connStr, string provider)
|
||||
@ -57,7 +61,7 @@ namespace SharedLibraryCore.Database
|
||||
$"{Path.DirectorySeparatorChar}{currentPath}" :
|
||||
currentPath;
|
||||
|
||||
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = $"{currentPath}{Path.DirectorySeparatorChar}Database{Path.DirectorySeparatorChar}Database.db" };
|
||||
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = Path.Join(currentPath, "Database", "Database.db") };
|
||||
var connectionString = connectionStringBuilder.ToString();
|
||||
var connection = new SqliteConnection(connectionString);
|
||||
|
||||
@ -98,6 +102,9 @@ namespace SharedLibraryCore.Database
|
||||
.WithMany(p => p.AdministeredPenalties)
|
||||
.HasForeignKey(c => c.PunisherId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
entity.Property(p => p.Expires)
|
||||
.IsRequired(false);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<EFAliasLink>(entity =>
|
||||
@ -110,6 +117,7 @@ namespace SharedLibraryCore.Database
|
||||
|
||||
modelBuilder.Entity<EFAlias>(ent =>
|
||||
{
|
||||
ent.Property(a => a.IPAddress).IsRequired(false);
|
||||
ent.HasIndex(a => a.IPAddress);
|
||||
ent.Property(a => a.Name).HasMaxLength(24);
|
||||
ent.HasIndex(a => a.Name);
|
||||
@ -123,26 +131,15 @@ namespace SharedLibraryCore.Database
|
||||
|
||||
// adapted from
|
||||
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
|
||||
IEnumerable<string> directoryFiles;
|
||||
|
||||
string pluginDir = $@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}Debug{Path.DirectorySeparatorChar}netcoreapp2.1{Path.DirectorySeparatorChar}Plugins";
|
||||
|
||||
if (!Directory.Exists(pluginDir))
|
||||
{
|
||||
pluginDir = Path.Join(Environment.CurrentDirectory, "Plugins");
|
||||
|
||||
if (!Directory.Exists(pluginDir))
|
||||
{
|
||||
pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
|
||||
}
|
||||
}
|
||||
|
||||
directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
|
||||
#if DEBUG == TRUE
|
||||
foreach (string dllPath in Directory.GetFiles(@"C:\Projects\IW4M-Admin\Application\bin\Debug\netcoreapp2.1\Plugins").Where(f => f.EndsWith(".dll")))
|
||||
#if DEBUG
|
||||
string pluginDir = _migrationPluginDirectory;
|
||||
#else
|
||||
foreach (string dllPath in directoryFiles)
|
||||
string pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
|
||||
#endif
|
||||
IEnumerable<string> directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
|
||||
|
||||
|
||||
foreach (string dllPath in directoryFiles)
|
||||
{
|
||||
Assembly library;
|
||||
try
|
||||
@ -160,7 +157,9 @@ namespace SharedLibraryCore.Database
|
||||
.Select(c => (IModelConfiguration)Activator.CreateInstance(c));
|
||||
|
||||
foreach (var configurable in configurations)
|
||||
{
|
||||
configurable.Configure(modelBuilder);
|
||||
}
|
||||
|
||||
foreach (var type in library.ExportedTypes)
|
||||
{
|
||||
|
@ -16,7 +16,7 @@ namespace SharedLibraryCore.Database.Models
|
||||
[MaxLength(24)]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public int IPAddress { get; set; }
|
||||
public int? IPAddress { get; set; }
|
||||
[Required]
|
||||
public DateTime DateAdded { get; set; }
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace SharedLibraryCore.Database.Models
|
||||
{
|
||||
public class EFClient : SharedEntity
|
||||
public partial class EFClient : SharedEntity
|
||||
{
|
||||
[Key]
|
||||
public int ClientId { get; set; }
|
||||
@ -25,7 +25,7 @@ namespace SharedLibraryCore.Database.Models
|
||||
[ForeignKey("AliasLinkId")]
|
||||
public virtual EFAliasLink AliasLink { get; set; }
|
||||
[Required]
|
||||
public Objects.Player.Permission Level { get; set; }
|
||||
public Permission Level { get; set; }
|
||||
|
||||
[Required]
|
||||
public int CurrentAliasId { get; set; }
|
||||
@ -41,27 +41,21 @@ namespace SharedLibraryCore.Database.Models
|
||||
public virtual string Name
|
||||
{
|
||||
get { return CurrentAlias.Name; }
|
||||
set { }
|
||||
set { CurrentAlias.Name = value; }
|
||||
}
|
||||
[NotMapped]
|
||||
public virtual int IPAddress
|
||||
public virtual int? IPAddress
|
||||
{
|
||||
get { return CurrentAlias.IPAddress; }
|
||||
set { }
|
||||
set { CurrentAlias.IPAddress = value; }
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public string IPAddressString => new System.Net.IPAddress(BitConverter.GetBytes(IPAddress)).ToString();
|
||||
public string IPAddressString => IPAddress.ConvertIPtoString();
|
||||
[NotMapped]
|
||||
public virtual IDictionary<int, long> LinkedAccounts { get; set; }
|
||||
|
||||
public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; }
|
||||
public virtual ICollection<EFPenalty> AdministeredPenalties { get; set; }
|
||||
|
||||
public EFClient()
|
||||
{
|
||||
ReceivedPenalties = new List<EFPenalty>();
|
||||
AdministeredPenalties = new List<EFPenalty>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user