Compare commits
52 Commits
2.2-prerel
...
2.3-prerel
Author | SHA1 | Date | |
---|---|---|---|
ce02f5dd68 | |||
e6bfa408f8 | |||
0a1dc46760 | |||
97ba6aae2e | |||
a456fab0e5 | |||
3e5282df87 | |||
59e0072744 | |||
f1dd4f7c7f | |||
760d3026ce | |||
07df6dbf79 | |||
ca535019c6 | |||
e6154822f6 | |||
7a6dccc26a | |||
08c883e0ff | |||
aaf9eb09b6 | |||
7b75c35c9b | |||
07ec5cf52f | |||
cf5ee8765d | |||
9494a17997 | |||
5f4171ccf4 | |||
a10746d5ff | |||
8dca05a442 | |||
8aa0d204f4 | |||
12cf2e8247 | |||
b77bdbe793 | |||
4522992c0e | |||
9d6cbee69c | |||
abf0609e2e | |||
5ac8a55c72 | |||
9bdd7d1b8a | |||
ed83c4c011 | |||
d9d548ea18 | |||
1779bf821d | |||
d50e6c8030 | |||
a58726d872 | |||
dded60a6ef | |||
305817d00c | |||
65cf3566db | |||
e91d076b41 | |||
b5e9519f0c | |||
7caee55a7e | |||
b289917319 | |||
c8366a22e5 | |||
de902a58ac | |||
c7547f1ad5 | |||
9d946d1bad | |||
f4ac815d07 | |||
7fa0b52543 | |||
d45729d7e1 | |||
5d93e7ac57 | |||
0f9d2e92e1 | |||
4a46abc46d |
4
.gitignore
vendored
4
.gitignore
vendored
@ -229,3 +229,7 @@ bootstrap-custom.min.css
|
||||
/DiscordWebhook/env
|
||||
/DiscordWebhook/config.dev.json
|
||||
/GameLogServer/env
|
||||
launchSettings.json
|
||||
/VpnDetectionPrivate.js
|
||||
/Plugins/ScriptPlugins/VpnDetectionPrivate.js
|
||||
**/Master/env_master
|
@ -8,9 +8,13 @@ namespace IW4MAdmin.Application.API.Master
|
||||
public class ApiServer
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
public long Id { get; set; }
|
||||
[JsonProperty("ip")]
|
||||
public string IPAddress { get; set; }
|
||||
[JsonProperty("port")]
|
||||
public short Port { get; set; }
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; set; }
|
||||
[JsonProperty("gametype")]
|
||||
public string Gametype { get; set; }
|
||||
[JsonProperty("map")]
|
||||
|
@ -39,12 +39,14 @@ namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
ClientNum = s.ClientNum,
|
||||
Game = s.GameName.ToString(),
|
||||
Version = s.Version,
|
||||
Gametype = s.Gametype,
|
||||
Hostname = s.Hostname,
|
||||
Map = s.CurrentMap.Name,
|
||||
MaxClientNum = s.MaxClients,
|
||||
Id = s.GetHashCode(),
|
||||
Port = (short)s.GetPort()
|
||||
Id = s.EndPoint,
|
||||
Port = (short)s.GetPort(),
|
||||
IPAddress = s.IP
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
|
@ -3,14 +3,15 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
||||
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
||||
<Version>2.1.9.1</Version>
|
||||
<Version>2.2.4.3</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Product>IW4MAdmin</Product>
|
||||
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated server</Description>
|
||||
<Copyright>2018</Copyright>
|
||||
<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>
|
||||
@ -30,6 +31,8 @@
|
||||
<PropertyGroup>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<TieredCompilation>true</TieredCompilation>
|
||||
<AssemblyVersion>2.2.4.3</AssemblyVersion>
|
||||
<FileVersion>2.2.4.3</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -76,7 +79,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" />
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
@ -91,6 +94,6 @@
|
||||
</Target>
|
||||
|
||||
<Target Name="PostPublish" AfterTargets="Publish">
|
||||
<Exec Command="call $(ProjectDir)BuildScripts\PostPublish.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(OutDir)" />
|
||||
<Exec Command="call $(ProjectDir)BuildScripts\PostPublish.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(ConfigurationName)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
@ -1,28 +1,23 @@
|
||||
using System;
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
using IW4MAdmin.Application.EventParsers;
|
||||
using IW4MAdmin.Application.RconParsers;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Events;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.Services;
|
||||
using IW4MAdmin.Application.API;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using WebfrontCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Text;
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
using System.Reflection;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Events;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
@ -30,8 +25,8 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
private List<Server> _servers;
|
||||
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
|
||||
public Dictionary<int, Player> PrivilegedClients { get; set; }
|
||||
public ILogger Logger { get; private set; }
|
||||
public Dictionary<int, EFClient> PrivilegedClients { get; set; }
|
||||
public ILogger Logger => GetLogger(0);
|
||||
public bool Running { get; private set; }
|
||||
public bool IsInitialized { get; private set; }
|
||||
// define what the delagate function looks like
|
||||
@ -39,6 +34,10 @@ namespace IW4MAdmin.Application
|
||||
// expose the event handler so we can execute the events
|
||||
public OnServerEventEventHandler OnServerEvent { get; set; }
|
||||
public DateTime StartTime { get; private set; }
|
||||
public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString();
|
||||
|
||||
public IList<IRConParser> AdditionalRConParsers { get; }
|
||||
public IList<IEventParser> AdditionalEventParsers { get; }
|
||||
|
||||
static ApplicationManager Instance;
|
||||
readonly List<AsyncStatus> TaskStatuses;
|
||||
@ -51,10 +50,11 @@ namespace IW4MAdmin.Application
|
||||
GameEventHandler Handler;
|
||||
ManualResetEventSlim OnQuit;
|
||||
readonly IPageList PageList;
|
||||
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
|
||||
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
|
||||
|
||||
private ApplicationManager()
|
||||
{
|
||||
Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log");
|
||||
_servers = new List<Server>();
|
||||
Commands = new List<Command>();
|
||||
TaskStatuses = new List<AsyncStatus>();
|
||||
@ -62,90 +62,38 @@ 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();
|
||||
OnServerEvent += OnServerEventAsync;
|
||||
AdditionalEventParsers = new List<IEventParser>();
|
||||
AdditionalRConParsers = new List<IRConParser>();
|
||||
OnServerEvent += OnGameEvent;
|
||||
OnServerEvent += EventApi.OnGameEvent;
|
||||
}
|
||||
|
||||
private async void OnServerEventAsync(object sender, GameEventArgs args)
|
||||
private async void OnGameEvent(object sender, GameEventArgs args)
|
||||
{
|
||||
#if DEBUG == true
|
||||
Logger.WriteDebug($"Entering event process for {args.Event.Id}");
|
||||
#endif
|
||||
|
||||
var newEvent = args.Event;
|
||||
|
||||
// the event has failed already
|
||||
if (newEvent.Failed)
|
||||
{
|
||||
goto skip;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
await newEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_DELAYED_EVENT_WAIT"]);
|
||||
}
|
||||
|
||||
// offload it to the player to keep
|
||||
newEvent.Origin.DelayedEvents.Enqueue(newEvent);
|
||||
newEvent.OnProcessed.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
// if the target client is not in an authorized state (detected by RCon) don't execute the event
|
||||
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);
|
||||
newEvent.OnProcessed.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
// todo: this is a hacky mess
|
||||
if (newEvent.Origin?.DelayedEvents.Count > 0 &&
|
||||
newEvent.Origin?.State == Player.ClientState.Connected)
|
||||
{
|
||||
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 removed because the target has left");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Logger.WriteDebug($"Adding delayed event of type {e.Type} for {e.Origin} back for processing");
|
||||
this.GetEventHandler().AddEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
await newEvent.Owner.ExecuteEvent(newEvent);
|
||||
|
||||
// save the event info to the database
|
||||
var changeHistorySvc = new ChangeHistoryService();
|
||||
await changeHistorySvc.Add(args.Event);
|
||||
|
||||
#if DEBUG
|
||||
Logger.WriteDebug($"Processed event with id {newEvent.Id}");
|
||||
#endif
|
||||
@ -154,25 +102,32 @@ namespace IW4MAdmin.Application
|
||||
// this happens if a plugin requires login
|
||||
catch (AuthorizationException ex)
|
||||
{
|
||||
await newEvent.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMAND_NOTAUTHORIZED"]} - {ex.Message}");
|
||||
newEvent.FailReason = GameEvent.EventFailReason.Permission;
|
||||
newEvent.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMAND_NOTAUTHORIZED"]} - {ex.Message}");
|
||||
}
|
||||
|
||||
catch (NetworkException ex)
|
||||
{
|
||||
newEvent.FailReason = GameEvent.EventFailReason.Exception;
|
||||
Logger.WriteError(ex.Message);
|
||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
catch (ServerException ex)
|
||||
{
|
||||
newEvent.FailReason = GameEvent.EventFailReason.Exception;
|
||||
Logger.WriteWarning(ex.Message);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
newEvent.FailReason = GameEvent.EventFailReason.Exception;
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}");
|
||||
Logger.WriteDebug("Error Message: " + ex.Message);
|
||||
Logger.WriteDebug("Error Trace: " + ex.StackTrace);
|
||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
skip:
|
||||
|
||||
// tell anyone waiting for the output that we're done
|
||||
newEvent.OnProcessed.Set();
|
||||
}
|
||||
@ -195,7 +150,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)
|
||||
{
|
||||
@ -215,16 +170,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
|
||||
{
|
||||
@ -234,8 +189,7 @@ namespace IW4MAdmin.Application
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.WriteWarning($"Failed to update status for {server}");
|
||||
Logger.WriteDebug($"Exception: {e.Message}");
|
||||
Logger.WriteDebug($"StackTrace: {e.StackTrace}");
|
||||
Logger.WriteDebug(e.GetExceptionInfo());
|
||||
}
|
||||
}));
|
||||
}
|
||||
@ -245,56 +199,17 @@ 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
|
||||
SetHasEvent();
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
Running = true;
|
||||
|
||||
#region DATABASE
|
||||
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString))
|
||||
{
|
||||
await new ContextSeed(db).Seed();
|
||||
}
|
||||
|
||||
// todo: optimize this
|
||||
var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
|
||||
.Select(c => new
|
||||
{
|
||||
c.Password,
|
||||
c.PasswordSalt,
|
||||
c.ClientId,
|
||||
c.Level,
|
||||
c.Name
|
||||
});
|
||||
|
||||
foreach (var a in ipList)
|
||||
{
|
||||
try
|
||||
{
|
||||
PrivilegedClients.Add(a.ClientId, new Player()
|
||||
{
|
||||
Name = a.Name,
|
||||
ClientId = a.ClientId,
|
||||
Level = a.Level,
|
||||
PasswordSalt = a.PasswordSalt,
|
||||
Password = a.Password
|
||||
});
|
||||
}
|
||||
|
||||
catch (ArgumentException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CONFIG
|
||||
var config = ConfigHandler.Configuration();
|
||||
|
||||
@ -335,18 +250,31 @@ namespace IW4MAdmin.Application
|
||||
|
||||
if (string.IsNullOrEmpty(config.WebfrontBindUrl))
|
||||
{
|
||||
config.WebfrontBindUrl = "http://127.0.0.1:1624";
|
||||
config.WebfrontBindUrl = "http://0.0.0.0:1624";
|
||||
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 DATABASE
|
||||
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString,
|
||||
GetApplicationSettings().Configuration()?.DatabaseProvider))
|
||||
{
|
||||
await new ContextSeed(db).Seed();
|
||||
}
|
||||
|
||||
PrivilegedClients = (await ClientSvc.GetPrivilegedClients()).ToDictionary(_client => _client.ClientId);
|
||||
#endregion
|
||||
|
||||
#region PLUGINS
|
||||
SharedLibraryCore.Plugins.PluginImporter.Load(this);
|
||||
|
||||
@ -357,18 +285,19 @@ namespace IW4MAdmin.Application
|
||||
await Plugin.OnLoadAsync(this);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
|
||||
Logger.WriteDebug($"Exception: {e.Message}");
|
||||
Logger.WriteDebug($"Stack Trace: {e.StackTrace}");
|
||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region COMMANDS
|
||||
if (ClientSvc.GetOwners().Result.Count == 0)
|
||||
{
|
||||
Commands.Add(new COwner());
|
||||
}
|
||||
|
||||
Commands.Add(new CQuit());
|
||||
Commands.Add(new CKick());
|
||||
@ -409,7 +338,9 @@ namespace IW4MAdmin.Application
|
||||
Commands.Add(new CNextMap());
|
||||
|
||||
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
|
||||
{
|
||||
Commands.Add(C);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region INIT
|
||||
@ -444,7 +375,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);
|
||||
@ -527,13 +460,8 @@ namespace IW4MAdmin.Application
|
||||
public void Start()
|
||||
{
|
||||
// this needs to be run seperately from the main thread
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
#if !DEBUG
|
||||
// start heartbeat
|
||||
Task.Run(() => SendHeartbeat(new HeartbeatState()));
|
||||
#endif
|
||||
Task.Run(() => UpdateServerStates());
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
var _ = Task.Run(() => SendHeartbeat(new HeartbeatState()));
|
||||
_ = Task.Run(() => UpdateServerStates());
|
||||
|
||||
while (Running)
|
||||
{
|
||||
@ -546,14 +474,31 @@ namespace IW4MAdmin.Application
|
||||
public void Stop()
|
||||
{
|
||||
Running = false;
|
||||
|
||||
// trigger the event processing loop to end
|
||||
SetHasEvent();
|
||||
}
|
||||
|
||||
public ILogger GetLogger()
|
||||
public ILogger GetLogger(long serverId)
|
||||
{
|
||||
return Logger;
|
||||
if (Loggers.ContainsKey(serverId))
|
||||
{
|
||||
return Loggers[serverId];
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Logger newLogger;
|
||||
|
||||
if (serverId == 0)
|
||||
{
|
||||
newLogger = new Logger("IW4MAdmin-Manager");
|
||||
}
|
||||
else
|
||||
{
|
||||
newLogger = new Logger($"IW4MAdmin-Server-{serverId}");
|
||||
}
|
||||
|
||||
Loggers.Add(serverId, newLogger);
|
||||
return newLogger;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<MessageToken> GetMessageTokens()
|
||||
@ -561,31 +506,69 @@ namespace IW4MAdmin.Application
|
||||
return MessageTokens;
|
||||
}
|
||||
|
||||
public IList<Player> GetActiveClients()
|
||||
public IList<EFClient> GetActiveClients()
|
||||
{
|
||||
var ActiveClients = new List<Player>();
|
||||
|
||||
foreach (var server in _servers)
|
||||
ActiveClients.AddRange(server.Players.Where(p => p != null));
|
||||
|
||||
return ActiveClients;
|
||||
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,6 +1,8 @@
|
||||
set SolutionDir=%1
|
||||
set ProjectDir=%2
|
||||
set TargetDir=%3
|
||||
set CurrentConfiguration=%4
|
||||
SET COPYCMD=/Y
|
||||
|
||||
echo Deleting extra language files
|
||||
|
||||
@ -54,9 +56,52 @@ del "%SolutionDir%Publish\Windows\*pdb"
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\web.config" del "%SolutionDir%Publish\WindowsPrerelease\web.config"
|
||||
del "%SolutionDir%Publish\WindowsPrerelease\*pdb"
|
||||
|
||||
echo making start scripts
|
||||
@echo dotnet IW4MAdmin.dll > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
|
||||
@echo dotnet IW4MAdmin.dll > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd"
|
||||
echo setting up library folders
|
||||
|
||||
@(echo #!/bin/bash && echo dotnet IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh"
|
||||
@(echo #!/bin/bash && echo dotnet IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh"
|
||||
if "%CurrentConfiguration%" == "Prerelease" (
|
||||
echo PR-Config
|
||||
if not exist "%SolutionDir%Publish\WindowsPrerelease\Configuration" md "%SolutionDir%Publish\WindowsPrerelease\Configuration"
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\DefaultSettings.json" "%SolutionDir%Publish\WindowsPrerelease\Configuration\"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Release" (
|
||||
echo R-Config
|
||||
if not exist "%SolutionDir%Publish\Windows\Configuration" md "%SolutionDir%Publish\Windows\Configuration"
|
||||
if exist "%SolutionDir%Publish\Windows\DefaultSettings.json" move "%SolutionDir%Publish\Windows\DefaultSettings.json" "%SolutionDir%Publish\Windows\Configuration\DefaultSettings.json"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Prerelease" (
|
||||
echo PR-LIB
|
||||
if not exist "%SolutionDir%Publish\WindowsPrerelease\Lib\" md "%SolutionDir%Publish\WindowsPrerelease\Lib\"
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\*.dll" "%SolutionDir%Publish\WindowsPrerelease\Lib\"
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\*.json" "%SolutionDir%Publish\WindowsPrerelease\Lib\"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Release" (
|
||||
echo R-LIB
|
||||
if not exist "%SolutionDir%Publish\Windows\Lib\" md "%SolutionDir%Publish\Windows\Lib\"
|
||||
move "%SolutionDir%Publish\Windows\*.dll" "%SolutionDir%Publish\Windows\Lib\"
|
||||
move "%SolutionDir%Publish\Windows\*.json" "%SolutionDir%Publish\Windows\Lib\"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Prerelease" (
|
||||
echo PR-RT
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\runtimes" "%SolutionDir%Publish\WindowsPrerelease\Lib\runtimes"
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\refs" move "%SolutionDir%Publish\WindowsPrerelease\refs" "%SolutionDir%Publish\WindowsPrerelease\Lib\refs"
|
||||
)
|
||||
|
||||
|
||||
if "%CurrentConfiguration%" == "Release" (
|
||||
echo R-RT
|
||||
move "%SolutionDir%Publish\Windows\runtimes" "%SolutionDir%Publish\Windows\Lib\runtimes"
|
||||
if exist "%SolutionDir%Publish\Windows\refs" move "%SolutionDir%Publish\Windows\refs" "%SolutionDir%Publish\Windows\Lib\refs"
|
||||
)
|
||||
|
||||
echo making start scripts
|
||||
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
|
||||
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd"
|
||||
|
||||
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh"
|
||||
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh"
|
||||
|
||||
eCHO "%CurrentConfiguration%"
|
||||
|
@ -1,82 +0,0 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application.Core
|
||||
{
|
||||
class ClientAuthentication : IClientAuthentication
|
||||
{
|
||||
private Queue<Player> ClientAuthenticationQueue;
|
||||
private Dictionary<long, Player> AuthenticatedClients;
|
||||
|
||||
public ClientAuthentication()
|
||||
{
|
||||
ClientAuthenticationQueue = new Queue<Player>();
|
||||
AuthenticatedClients = new Dictionary<long, Player>();
|
||||
}
|
||||
|
||||
public void AuthenticateClients(IList<Player> clients)
|
||||
{
|
||||
// we need to un-auth all the clients that have disconnected
|
||||
var clientNetworkIds = clients.Select(c => c.NetworkId);
|
||||
var clientsToRemove = AuthenticatedClients.Keys.Where(c => !clientNetworkIds.Contains(c));
|
||||
// remove them
|
||||
foreach (long Id in clientsToRemove.ToList())
|
||||
{
|
||||
AuthenticatedClients.Remove(Id);
|
||||
}
|
||||
|
||||
// loop through the polled clients to see if they've been authenticated yet
|
||||
foreach (var client in clients)
|
||||
{
|
||||
// they've not been authenticated
|
||||
if (!AuthenticatedClients.TryGetValue(client.NetworkId, out Player value))
|
||||
{
|
||||
// authenticate them
|
||||
client.State = Player.ClientState.Authenticated;
|
||||
AuthenticatedClients.Add(client.NetworkId, client);
|
||||
}
|
||||
else
|
||||
{
|
||||
// this update their ping
|
||||
// todo: this seems kinda hacky
|
||||
value.Ping = client.Ping;
|
||||
value.Score = client.Score;
|
||||
}
|
||||
}
|
||||
|
||||
// empty out the queue of clients detected through log
|
||||
while (ClientAuthenticationQueue.Count > 0)
|
||||
{
|
||||
// grab each client that's connected via log
|
||||
var clientToAuthenticate = ClientAuthenticationQueue.Dequeue();
|
||||
// if they're not already authed, auth them
|
||||
if (!AuthenticatedClients.TryGetValue(clientToAuthenticate.NetworkId, out Player value))
|
||||
{
|
||||
// authenticate them
|
||||
clientToAuthenticate.State = Player.ClientState.Authenticated;
|
||||
AuthenticatedClients.Add(clientToAuthenticate.NetworkId, clientToAuthenticate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IList<Player> GetAuthenticatedClients()
|
||||
{
|
||||
if (AuthenticatedClients.Values.Count > 18)
|
||||
{
|
||||
Program.ServerManager.GetLogger().WriteWarning($"auth client count is {AuthenticatedClients.Values.Count}, this is bad");
|
||||
return AuthenticatedClients.Values.Take(18).ToList();
|
||||
}
|
||||
|
||||
return AuthenticatedClients.Values.ToList();
|
||||
}
|
||||
|
||||
public void RequestClientAuthentication(Player client)
|
||||
{
|
||||
ClientAuthenticationQueue.Enqueue(client);
|
||||
}
|
||||
}
|
||||
}
|
@ -25,11 +25,6 @@
|
||||
"Name": "mp_rust"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Highrise",
|
||||
"Name": "mp_highrise"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Terminal",
|
||||
"Name": "mp_terminal"
|
||||
@ -40,16 +35,6 @@
|
||||
"Name": "mp_crash"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Skidrow",
|
||||
"Name": "mp_nightshift"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Quarry",
|
||||
"Name": "mp_quarry"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Afghan",
|
||||
"Name": "mp_afghan"
|
||||
|
300
Application/EventParsers/BaseEventParser.cs
Normal file
300
Application/EventParsers/BaseEventParser.cs
Normal file
@ -0,0 +1,300 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
class BaseEventParser : IEventParser
|
||||
{
|
||||
public BaseEventParser()
|
||||
{
|
||||
Configuration = new DynamicEventParserConfiguration()
|
||||
{
|
||||
GameDirectory = "userraw",
|
||||
};
|
||||
|
||||
Configuration.Say.Pattern = @"^(say|sayteam);(.{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);(.{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);(.{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; } = "IW4x (v0.6.0)";
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
@ -9,112 +9,15 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
class GameEventHandler : IEventHandler
|
||||
{
|
||||
static long NextEventId = 1;
|
||||
readonly IManager Manager;
|
||||
readonly SortedList<long, GameEvent> OutOfOrderEvents;
|
||||
readonly SemaphoreSlim IsProcessingEvent;
|
||||
|
||||
public GameEventHandler(IManager mgr)
|
||||
{
|
||||
Manager = mgr;
|
||||
OutOfOrderEvents = new SortedList<long, GameEvent>();
|
||||
IsProcessingEvent = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
public void AddEvent(GameEvent gameEvent)
|
||||
{
|
||||
// IsProcessingEvent.Wait();
|
||||
((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent));
|
||||
// IsProcessingEvent.Release(1);
|
||||
//if (gameEvent.Type == GameEvent.EventType.Connect)
|
||||
//{
|
||||
// IsProcessingEvent.Wait();
|
||||
// if (!gameEvent.OnProcessed.Wait(10 * 1000))
|
||||
// {
|
||||
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
|
||||
// }
|
||||
// IsProcessingEvent.Release(1);
|
||||
//}
|
||||
|
||||
return;
|
||||
#if DEBUG
|
||||
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner} with id {gameEvent.Id}");
|
||||
#endif
|
||||
GameEvent sortedEvent = null;
|
||||
lock (OutOfOrderEvents)
|
||||
{
|
||||
sortedEvent = OutOfOrderEvents.Values.FirstOrDefault();
|
||||
|
||||
while (sortedEvent?.Id == Interlocked.Read(ref NextEventId))
|
||||
{
|
||||
if (OutOfOrderEvents.Count > 0)
|
||||
{
|
||||
OutOfOrderEvents.RemoveAt(0);
|
||||
}
|
||||
|
||||
AddEvent(sortedEvent);
|
||||
sortedEvent = OutOfOrderEvents.Values.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// both the gameEvent Id and the LastEventId are thread safe because we want to synchronize when the
|
||||
// event occurs
|
||||
if (gameEvent.Id == Interlocked.Read(ref NextEventId))
|
||||
{
|
||||
//#if DEBUG == true
|
||||
// Manager.GetLogger().WriteDebug($"sent event with id {gameEvent.Id} to be processed");
|
||||
// IsProcessingEvent.Wait();
|
||||
//#else
|
||||
// if (GameEvent.IsEventTimeSensitive(gameEvent) &&
|
||||
// !IsProcessingEvent.Wait(30 * 1000))
|
||||
// {
|
||||
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
|
||||
// }
|
||||
//#endif
|
||||
((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent));
|
||||
|
||||
//if (GameEvent.IsEventTimeSensitive(gameEvent))
|
||||
//{
|
||||
// if( !gameEvent.OnProcessed.Wait(30 * 1000))
|
||||
// {
|
||||
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EVENT_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
|
||||
// }
|
||||
//}
|
||||
Interlocked.Increment(ref NextEventId);
|
||||
//#if DEBUG == true
|
||||
// gameEvent.OnProcessed.Wait();
|
||||
//#else
|
||||
// if (GameEvent.IsEventTimeSensitive(gameEvent) &&
|
||||
// !gameEvent.OnProcessed.Wait(30 * 1000))
|
||||
// {
|
||||
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EVENT_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
|
||||
// }
|
||||
//#endif
|
||||
// Interlocked.Increment(ref NextEventId);
|
||||
// if (GameEvent.IsEventTimeSensitive(gameEvent))
|
||||
// {
|
||||
// IsProcessingEvent.Release();
|
||||
// }
|
||||
}
|
||||
|
||||
// a "newer" event has been added before and "older" one has been added (due to threads and context switching)
|
||||
// so me must wait until the next expected one arrives
|
||||
else
|
||||
{
|
||||
#if DEBUG == true
|
||||
Manager.GetLogger().WriteWarning("Incoming event is out of order");
|
||||
Manager.GetLogger().WriteDebug($"Expected event Id is {Interlocked.Read(ref NextEventId)}, but got {gameEvent.Id} instead");
|
||||
#endif
|
||||
|
||||
// this prevents multiple threads from adding simultaneously
|
||||
lock (OutOfOrderEvents)
|
||||
{
|
||||
if (!OutOfOrderEvents.TryGetValue(gameEvent.Id, out GameEvent discard))
|
||||
{
|
||||
OutOfOrderEvents.Add(gameEvent.Id, gameEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -37,7 +35,7 @@ namespace IW4MAdmin.Application.IO
|
||||
Server = server;
|
||||
}
|
||||
|
||||
public void PollForChanges()
|
||||
public async Task PollForChanges()
|
||||
{
|
||||
while (!Server.Manager.ShutdownRequested())
|
||||
{
|
||||
@ -45,12 +43,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 +57,7 @@ namespace IW4MAdmin.Application.IO
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLogEvents()
|
||||
private async Task UpdateLogEvents()
|
||||
{
|
||||
long fileSize = Reader.Length;
|
||||
|
||||
@ -74,11 +72,12 @@ namespace IW4MAdmin.Application.IO
|
||||
|
||||
PreviousFileSize = fileSize;
|
||||
|
||||
var events = Reader.ReadEventsFromLog(Server, fileDiff, 0);
|
||||
var events = await Reader.ReadEventsFromLog(Server, fileDiff, 0);
|
||||
|
||||
foreach (var ev in events)
|
||||
{
|
||||
Server.Manager.GetEventHandler().AddEvent(ev);
|
||||
await ev.WaitAsync();
|
||||
}
|
||||
|
||||
PreviousFileSize = fileSize;
|
||||
|
@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
@ -22,7 +23,7 @@ namespace IW4MAdmin.Application.IO
|
||||
Parser = parser;
|
||||
}
|
||||
|
||||
public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
|
||||
public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
|
||||
{
|
||||
// allocate the bytes for the new log lines
|
||||
List<string> logLines = new List<string>();
|
||||
@ -30,6 +31,7 @@ namespace IW4MAdmin.Application.IO
|
||||
// open the file as a stream
|
||||
using (var rd = new StreamReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType))
|
||||
{
|
||||
// todo: max async
|
||||
// take the old start position and go back the number of new characters
|
||||
rd.BaseStream.Seek(-fileSizeDiff, SeekOrigin.End);
|
||||
// the difference should be in the range of a int :P
|
||||
@ -55,9 +57,9 @@ namespace IW4MAdmin.Application.IO
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line");
|
||||
Program.ServerManager.GetLogger().WriteDebug(e.Message);
|
||||
Program.ServerManager.GetLogger().WriteDebug(eventLine);
|
||||
server.Logger.WriteWarning("Could not properly parse event line");
|
||||
server.Logger.WriteDebug(e.Message);
|
||||
server.Logger.WriteDebug(eventLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using static SharedLibraryCore.Utilities;
|
||||
|
||||
namespace IW4MAdmin.Application.IO
|
||||
@ -27,20 +28,21 @@ namespace IW4MAdmin.Application.IO
|
||||
|
||||
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;
|
||||
var response = await Api.Log(b64Path);
|
||||
|
||||
if (!response.Success)
|
||||
{
|
||||
server.Logger.WriteError($"Could not get log server info of {LogFile}/{b64Path} ({server.LogPath})");
|
||||
return events;
|
||||
}
|
||||
|
||||
// parse each line
|
||||
@ -59,9 +61,9 @@ namespace IW4MAdmin.Application.IO
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line");
|
||||
Program.ServerManager.GetLogger().WriteDebug(e.Message);
|
||||
Program.ServerManager.GetLogger().WriteDebug(eventLine);
|
||||
server.Logger.WriteWarning("Could not properly parse event line");
|
||||
server.Logger.WriteDebug(e.Message);
|
||||
server.Logger.WriteDebug(eventLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,10 +12,10 @@ namespace IW4MAdmin.Application.Localization
|
||||
{
|
||||
public class Configure
|
||||
{
|
||||
public static void Initialize(string customLocale)
|
||||
public static void Initialize(string customLocale = null)
|
||||
{
|
||||
string currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale;
|
||||
string[] localizationFiles = Directory.GetFiles("Localization", $"*.{currentLocale}.json");
|
||||
string[] localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");
|
||||
|
||||
try
|
||||
{
|
||||
@ -33,13 +33,13 @@ namespace IW4MAdmin.Application.Localization
|
||||
// culture doesn't exist so we just want language
|
||||
if (localizationFiles.Length == 0)
|
||||
{
|
||||
localizationFiles = Directory.GetFiles("Localization", $"*.{currentLocale.Substring(0, 2)}*.json");
|
||||
localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale.Substring(0, 2)}*.json");
|
||||
}
|
||||
|
||||
// language doesn't exist either so defaulting to english
|
||||
if (localizationFiles.Length == 0)
|
||||
{
|
||||
localizationFiles = Directory.GetFiles("Localization", "*.en-US.json");
|
||||
localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), "*.en-US.json");
|
||||
}
|
||||
|
||||
// this should never happen unless the localization folder is empty
|
||||
@ -59,12 +59,12 @@ namespace IW4MAdmin.Application.Localization
|
||||
{
|
||||
if (!localizationDict.TryAdd(item.Key, item.Value))
|
||||
{
|
||||
Program.ServerManager.GetLogger().WriteError($"Could not add locale string {item.Key} to localization");
|
||||
Program.ServerManager.GetLogger(0).WriteError($"Could not add locale string {item.Key} to localization");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string localizationFile = $"Localization{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocale}-{currentLocale.ToUpper()}.json";
|
||||
string localizationFile = $"{Path.Join(Utilities.OperatingDirectory, "Localization")}{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocale}-{currentLocale.ToUpper()}.json";
|
||||
|
||||
Utilities.CurrentLocalization = new SharedLibraryCore.Localization.Layout(localizationDict)
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
@ -18,18 +19,44 @@ namespace IW4MAdmin.Application
|
||||
}
|
||||
|
||||
readonly string FileName;
|
||||
readonly object ThreadLock;
|
||||
readonly SemaphoreSlim OnLogWriting;
|
||||
static readonly short MAX_LOG_FILES = 10;
|
||||
|
||||
public Logger(string fn)
|
||||
{
|
||||
FileName = fn;
|
||||
ThreadLock = new object();
|
||||
if (File.Exists(fn))
|
||||
File.Delete(fn);
|
||||
FileName = Path.Join(Utilities.OperatingDirectory, "Log", $"{fn}.log");
|
||||
OnLogWriting = new SemaphoreSlim(1, 1);
|
||||
RotateLogs();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// rotates logs when log is initialized
|
||||
/// </summary>
|
||||
private void RotateLogs()
|
||||
{
|
||||
string maxLog = FileName + MAX_LOG_FILES;
|
||||
|
||||
if (File.Exists(maxLog))
|
||||
{
|
||||
File.Delete(maxLog);
|
||||
}
|
||||
|
||||
for (int i = MAX_LOG_FILES - 1; i >= 0; i--)
|
||||
{
|
||||
string logToMove = i == 0 ? FileName : FileName + i;
|
||||
string movedLogName = FileName + (i + 1);
|
||||
|
||||
if (File.Exists(logToMove))
|
||||
{
|
||||
File.Move(logToMove, movedLogName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Write(string msg, LogType type)
|
||||
{
|
||||
OnLogWriting.Wait();
|
||||
|
||||
string stringType = type.ToString();
|
||||
|
||||
try
|
||||
@ -40,13 +67,12 @@ namespace IW4MAdmin.Application
|
||||
catch (Exception) { }
|
||||
|
||||
string LogLine = $"[{DateTime.Now.ToString("MM.dd.yyy HH:mm:ss.fff")}] - {stringType}: {msg}";
|
||||
lock (ThreadLock)
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
|
||||
|
||||
Console.WriteLine(LogLine);
|
||||
File.AppendAllText(FileName, LogLine + Environment.NewLine);
|
||||
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
|
||||
Console.WriteLine(LogLine);
|
||||
File.AppendAllText(FileName, LogLine + Environment.NewLine);
|
||||
#else
|
||||
if (type == LogType.Error || type == LogType.Verbose)
|
||||
Console.WriteLine(LogLine);
|
||||
@ -54,6 +80,14 @@ namespace IW4MAdmin.Application
|
||||
File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}");
|
||||
#endif
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Well.. It looks like your machine can't event write to the log file. That's something else...");
|
||||
Console.WriteLine(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
OnLogWriting.Release(1);
|
||||
}
|
||||
|
||||
public void WriteVerbose(string msg)
|
||||
|
@ -1,29 +1,23 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
using IW4MAdmin.Application.Migration;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Localization;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Localization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
static public double Version { get; private set; }
|
||||
static public ApplicationManager ServerManager = ApplicationManager.GetInstance();
|
||||
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
|
||||
static public ApplicationManager ServerManager;
|
||||
private static ManualResetEventSlim OnShutdownComplete = new ManualResetEventSlim();
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", OperatingDirectory);
|
||||
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
@ -39,12 +33,25 @@ namespace IW4MAdmin.Application
|
||||
|
||||
try
|
||||
{
|
||||
CheckDirectories();
|
||||
|
||||
ServerManager = ApplicationManager.GetInstance();
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
|
||||
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
|
||||
|
||||
if (ServerManager.GetApplicationSettings().Configuration() != null)
|
||||
{
|
||||
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration().CustomLocale);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Localization.Configure.Initialize();
|
||||
}
|
||||
|
||||
loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
|
||||
|
||||
CheckDirectories();
|
||||
// do any needed migrations
|
||||
// todo: move out
|
||||
ConfigurationMigration.MoveConfigFolder10518(null);
|
||||
|
||||
ServerManager.Logger.WriteInfo($"Version is {Version}");
|
||||
|
||||
@ -106,15 +113,17 @@ namespace IW4MAdmin.Application
|
||||
|
||||
var consoleTask = Task.Run(async () =>
|
||||
{
|
||||
String userInput;
|
||||
Player Origin = Utilities.IW4MAdminClient;
|
||||
string userInput;
|
||||
var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
|
||||
|
||||
do
|
||||
{
|
||||
userInput = Console.ReadLine();
|
||||
|
||||
if (userInput?.ToLower() == "quit")
|
||||
{
|
||||
ServerManager.Stop();
|
||||
}
|
||||
|
||||
if (ServerManager.Servers.Count == 0)
|
||||
{
|
||||
@ -124,7 +133,6 @@ namespace IW4MAdmin.Application
|
||||
|
||||
if (userInput?.Length > 0)
|
||||
{
|
||||
Origin.CurrentServer = ServerManager.Servers[0];
|
||||
GameEvent E = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Command,
|
||||
@ -144,13 +152,16 @@ namespace IW4MAdmin.Application
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(loc["MANAGER_INIT_FAIL"]);
|
||||
string failMessage = loc == null ? "Failed to initalize IW4MAdmin" : loc["MANAGER_INIT_FAIL"];
|
||||
string exitMessage = loc == null ? "Press any key to exit..." : loc["MANAGER_EXIT"];
|
||||
|
||||
Console.WriteLine(failMessage);
|
||||
while (e.InnerException != null)
|
||||
{
|
||||
e = e.InnerException;
|
||||
}
|
||||
Console.WriteLine($"Exception: {e.Message}");
|
||||
Console.WriteLine(loc["MANAGER_EXIT"]);
|
||||
Console.WriteLine(e.Message);
|
||||
Console.WriteLine(exitMessage);
|
||||
Console.ReadKey();
|
||||
return;
|
||||
}
|
||||
@ -169,15 +180,25 @@ namespace IW4MAdmin.Application
|
||||
private static void OnCancelKey(object sender, ConsoleCancelEventArgs e)
|
||||
{
|
||||
ServerManager.Stop();
|
||||
OnShutdownComplete.Wait(5000);
|
||||
OnShutdownComplete.Wait();
|
||||
}
|
||||
|
||||
static void CheckDirectories()
|
||||
{
|
||||
string curDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
|
||||
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Plugins")))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Plugins"));
|
||||
}
|
||||
|
||||
if (!Directory.Exists($"{curDirectory}Plugins"))
|
||||
Directory.CreateDirectory($"{curDirectory}Plugins");
|
||||
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Database")))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Database"));
|
||||
}
|
||||
|
||||
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Log")))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Log"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
60
Application/Migration/ConfigurationMigration.cs
Normal file
60
Application/Migration/ConfigurationMigration.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace IW4MAdmin.Application.Migration
|
||||
{
|
||||
/// <summary>
|
||||
/// helps facilitate the migration of configs from one version and or location
|
||||
/// to another
|
||||
/// </summary>
|
||||
class ConfigurationMigration
|
||||
{
|
||||
/// <summary>
|
||||
/// moves existing configs from the root folder into a configs folder
|
||||
/// </summary>
|
||||
public static void MoveConfigFolder10518(ILogger log)
|
||||
{
|
||||
string currentDirectory = Utilities.OperatingDirectory;
|
||||
|
||||
// we don't want to do this for migrations or tests where the
|
||||
// property isn't initialized or it's wrong
|
||||
if (currentDirectory != null)
|
||||
{
|
||||
string configDirectory = Path.Join(currentDirectory, "Configuration");
|
||||
|
||||
if (!Directory.Exists(configDirectory))
|
||||
{
|
||||
log?.WriteDebug($"Creating directory for configs {configDirectory}");
|
||||
Directory.CreateDirectory(configDirectory);
|
||||
}
|
||||
|
||||
var configurationFiles = Directory.EnumerateFiles(currentDirectory, "*.json")
|
||||
.Select(f => f.Split(Path.DirectorySeparatorChar).Last())
|
||||
.Where(f => f.Count(c => c == '.') == 1);
|
||||
|
||||
foreach (var configFile in configurationFiles)
|
||||
{
|
||||
log?.WriteDebug($"Moving config file {configFile}");
|
||||
string destinationPath = Path.Join("Configuration", configFile);
|
||||
if (!File.Exists(destinationPath))
|
||||
{
|
||||
File.Move(configFile, destinationPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(Path.Join("Database", "Database.db")) &&
|
||||
File.Exists("Database.db"))
|
||||
{
|
||||
log?.WriteDebug("Moving database file");
|
||||
File.Move("Database.db", Path.Join("Database", "Database.db"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
178
Application/RconParsers/BaseRConParser.cs
Normal file
178
Application/RconParsers/BaseRConParser.cs
Normal file
@ -0,0 +1,178 @@
|
||||
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;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
{
|
||||
class BaseRConParser : IRConParser
|
||||
{
|
||||
public BaseRConParser()
|
||||
{
|
||||
Configuration = new DynamicRConParserConfiguration()
|
||||
{
|
||||
CommandPrefixes = new CommandPrefix()
|
||||
{
|
||||
Tell = "tellraw {0} {1}",
|
||||
Say = "sayraw {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",
|
||||
},
|
||||
GameName = Server.Game.IW4
|
||||
};
|
||||
|
||||
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]+) *$";
|
||||
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; } = "IW4x (v0.6.0)";
|
||||
|
||||
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
|
||||
{
|
||||
}
|
||||
}
|
19
Application/RconParsers/DynamicRConParserConfiguration.cs
Normal file
19
Application/RconParsers/DynamicRConParserConfiguration.cs
Normal file
@ -0,0 +1,19 @@
|
||||
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 Server.Game GameName { 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,202 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.RCon;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
{
|
||||
public class T6MRConParser : IRConParser
|
||||
{
|
||||
class T6MResponse
|
||||
{
|
||||
public class SInfo
|
||||
{
|
||||
public short Com_maxclients { get; set; }
|
||||
public string Game { get; set; }
|
||||
public string Gametype { get; set; }
|
||||
public string Mapname { get; set; }
|
||||
public short NumBots { get; set; }
|
||||
public short NumClients { get; set; }
|
||||
public short Round { get; set; }
|
||||
public string Sv_hostname { get; set; }
|
||||
}
|
||||
|
||||
public class PInfo
|
||||
{
|
||||
public short Assists { get; set; }
|
||||
public string Clan { get; set; }
|
||||
public short Deaths { get; set; }
|
||||
public short Downs { get; set; }
|
||||
public short Headshots { get; set; }
|
||||
public short Id { get; set; }
|
||||
public bool IsBot { get; set; }
|
||||
public short Kills { get; set; }
|
||||
public string Name { get; set; }
|
||||
public short Ping { get; set; }
|
||||
public short Revives { get; set; }
|
||||
public int Score { get; set; }
|
||||
public long Xuid { get; set; }
|
||||
public string Ip { get; set; }
|
||||
}
|
||||
|
||||
public SInfo Info { get; set; }
|
||||
public PInfo[] Players { get; set; }
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
//return ClientsFromResponse(connection);
|
||||
}
|
||||
|
||||
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 async Task<List<Player>> ClientsFromResponse(Connection conn)
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.BaseAddress = new Uri($"http://{conn.Endpoint.Address}:{conn.Endpoint.Port}/");
|
||||
|
||||
try
|
||||
{
|
||||
var parameters = new FormUrlEncodedContent(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("rcon_password", conn.RConPassword)
|
||||
});
|
||||
|
||||
var serverResponse = await client.PostAsync("/info", parameters);
|
||||
var serverResponseObject = Newtonsoft.Json.JsonConvert.DeserializeObject<T6MResponse>(await serverResponse.Content.ReadAsStringAsync());
|
||||
|
||||
return serverResponseObject.Players.Select(p => new Player()
|
||||
{
|
||||
Name = p.Name,
|
||||
NetworkId = p.Xuid,
|
||||
ClientNumber = p.Id,
|
||||
IPAddress = p.Ip.Split(':')[0].ConvertToIP(),
|
||||
Ping = p.Ping,
|
||||
Score = p.Score,
|
||||
IsBot = p.IsBot,
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
throw new NetworkException(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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+");
|
||||
int score = 0;
|
||||
// todo: fix this when T6M score is valid ;)
|
||||
//int score = Int32.Parse(playerInfo[1]);
|
||||
var p = new Player()
|
||||
{
|
||||
Name = name,
|
||||
NetworkId = networkId,
|
||||
ClientNumber = clientId,
|
||||
IPAddress = ipAddress,
|
||||
Ping = Ping,
|
||||
Score = score,
|
||||
State = Player.ClientState.Connecting,
|
||||
IsBot = networkId == 0
|
||||
};
|
||||
|
||||
if (p.IsBot)
|
||||
p.NetworkId = -p.ClientNumber;
|
||||
|
||||
StatusPlayers.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
return StatusPlayers;
|
||||
}
|
||||
}
|
||||
}
|
31
Application/SharedGUIDKick.js
Normal file
31
Application/SharedGUIDKick.js
Normal file
@ -0,0 +1,31 @@
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.1,
|
||||
name: 'Shared GUID Kicker Plugin',
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
// make sure we only check for IW4(x)
|
||||
if (server.GameName !== 2) {
|
||||
return false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
// connect or join event
|
||||
if (gameEvent.Type === 3) {
|
||||
// this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID
|
||||
if (gameEvent.Origin.NetworkId === -805366929435212061) {
|
||||
gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
62
Application/VPNDetection.js
Normal file
62
Application/VPNDetection.js
Normal file
@ -0,0 +1,62 @@
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.0,
|
||||
name: 'VPN Detection Plugin',
|
||||
|
||||
manager: null,
|
||||
logger: null,
|
||||
vpnExceptionIds: [],
|
||||
|
||||
checkForVpn: function (origin) {
|
||||
var exempt = false;
|
||||
// prevent players that are exempt from being kicked
|
||||
this.vpnExceptionIds.forEach(function (id) {
|
||||
if (id === origin.ClientId) {
|
||||
exempt = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (exempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
var usingVPN = false;
|
||||
|
||||
try {
|
||||
var cl = new System.Net.Http.HttpClient();
|
||||
var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result;
|
||||
var co = re.Content;
|
||||
var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result);
|
||||
co.Dispose();
|
||||
re.Dispose();
|
||||
cl.Dispose();
|
||||
usingVPN = parsedJSON.success && parsedJSON.proxy;
|
||||
} catch (e) {
|
||||
this.logger.WriteError(e.message);
|
||||
}
|
||||
|
||||
if (usingVPN) {
|
||||
this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')');
|
||||
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient);
|
||||
}
|
||||
},
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
// connect event
|
||||
if (gameEvent.Type === 3) {
|
||||
this.checkForVpn(gameEvent.Origin);
|
||||
}
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
this.manager = manager;
|
||||
this.logger = manager.GetLogger(0);
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
@ -27,6 +27,11 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
<OutputPath>bin\Prerelease\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DiscordWebhook.py" />
|
||||
</ItemGroup>
|
||||
@ -42,7 +47,6 @@
|
||||
</Interpreter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="config.dev.json" />
|
||||
<Content Include="config.json">
|
||||
<Publish>True</Publish>
|
||||
</Content>
|
||||
@ -50,7 +54,7 @@
|
||||
<Publish>True</Publish>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
|
||||
<!-- Uncomment the CoreCompile target to enable the Build command in
|
||||
Visual Studio and specify your pre- and post-build commands in
|
||||
the BeforeBuild and AfterBuild targets below. -->
|
||||
@ -59,7 +63,7 @@
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
<ProjectExtensions>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
|
@ -27,10 +27,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 +48,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 = 300
|
||||
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'^.+[\\|\/](userraw|mods|main)[\\|\/].+.log$', path):
|
||||
return False
|
||||
# set the initialze size to the current file size
|
||||
file_size = 0
|
||||
|
||||
if path not in self.log_file_sizes:
|
||||
self.log_file_sizes[path] = {
|
||||
'length' : self.file_length(path),
|
||||
'read': time.time()
|
||||
}
|
||||
return ''
|
||||
return True
|
||||
|
||||
# grab the previous values
|
||||
last_length = self.log_file_sizes[path]['length']
|
||||
@ -50,9 +51,9 @@ class LogReader(object):
|
||||
|
||||
# if it's been too long since we read and the amount changed is too great, discard it
|
||||
# todo: do we really want old events? maybe make this an "or"
|
||||
if file_size_difference > self.max_file_size_change and time_difference > self.max_file_time_change:
|
||||
return ''
|
||||
|
||||
if file_size_difference > self.max_file_size_change or time_difference > self.max_file_time_change:
|
||||
return True
|
||||
|
||||
new_log_info = self.get_file_lines(path, file_size_difference)
|
||||
return new_log_info
|
||||
|
||||
|
@ -9,9 +9,11 @@ class LogResource(Resource):
|
||||
|
||||
if log_info is False:
|
||||
print('could not read log file ' + path)
|
||||
|
||||
|
||||
empty_read = (log_info == False) or (log_info == True)
|
||||
|
||||
return {
|
||||
'success' : log_info is not False,
|
||||
'length': -1 if log_info is False else len(log_info),
|
||||
'length': -1 if empty_read else len(log_info),
|
||||
'data': log_info
|
||||
}
|
||||
|
29
GameLogServer/GameLogServer/restart_resource.py
Normal file
29
GameLogServer/GameLogServer/restart_resource.py
Normal file
@ -0,0 +1,29 @@
|
||||
from flask_restful import Resource
|
||||
from flask import request
|
||||
import requests
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
def get_pid_of_server_windows(port):
|
||||
process = subprocess.Popen('netstat -aon', shell=True, stdout=subprocess.PIPE)
|
||||
output = process.communicate()[0]
|
||||
matches = re.search(' *(UDP) +([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}):'+ str(port) + ' +[^\w]*([0-9]+)', output.decode('utf-8'))
|
||||
if matches is not None:
|
||||
return matches.group(3)
|
||||
else:
|
||||
return 0
|
||||
|
||||
class RestartResource(Resource):
|
||||
def get(self):
|
||||
try:
|
||||
response = requests.get('http://' + request.remote_addr + ':1624/api/restartapproved')
|
||||
if response.status_code == 200:
|
||||
pid = get_pid_of_server_windows(response.json()['port'])
|
||||
subprocess.check_output("Taskkill /PID %s /F" % pid)
|
||||
else:
|
||||
return {}, 400
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {}, 500
|
||||
return {}, 200
|
@ -1,9 +1,11 @@
|
||||
from flask import Flask
|
||||
from flask_restful import Api
|
||||
from .log_resource import LogResource
|
||||
from .restart_resource import RestartResource
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def init():
|
||||
api = Api(app)
|
||||
api.add_resource(LogResource, '/log/<string:path>')
|
||||
api.add_resource(RestartResource, '/restart')
|
||||
|
@ -1,12 +1,26 @@
|
||||
Flask==1.0.2
|
||||
aniso8601==3.0.2
|
||||
APScheduler==3.5.3
|
||||
certifi==2018.10.15
|
||||
chardet==3.0.4
|
||||
click==6.7
|
||||
Flask==1.0.2
|
||||
Flask-JWT==0.3.2
|
||||
Flask-JWT-Extended==3.8.1
|
||||
Flask-RESTful==0.3.6
|
||||
idna==2.7
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
MarkupSafe==1.0
|
||||
marshmallow==3.0.0b8
|
||||
pip==9.0.3
|
||||
pytz==2018.5
|
||||
setuptools==39.0.1
|
||||
psutil==5.4.8
|
||||
pygal==2.4.0
|
||||
PyJWT==1.4.2
|
||||
pytz==2018.7
|
||||
requests==2.20.0
|
||||
setuptools==40.5.0
|
||||
six==1.11.0
|
||||
timeago==1.0.8
|
||||
tzlocal==1.5.1
|
||||
urllib3==1.24
|
||||
Werkzeug==0.14.1
|
||||
|
@ -12,4 +12,4 @@ if __name__ == '__main__':
|
||||
except ValueError:
|
||||
PORT = 5555
|
||||
init()
|
||||
app.run(HOST, PORT, debug=True)
|
||||
app.run(HOST, PORT, debug=False)
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.16
|
||||
@ -11,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
_customcallbacks.gsc = _customcallbacks.gsc
|
||||
README.md = README.md
|
||||
RunPublishPre.cmd = RunPublishPre.cmd
|
||||
RunPublishRelease.cmd = RunPublishRelease.cmd
|
||||
version.txt = version.txt
|
||||
EndProjectSection
|
||||
EndProject
|
||||
@ -38,6 +38,9 @@ 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\ParserIW3.js = Plugins\ScriptPlugins\ParserIW3.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
|
||||
|
@ -17,7 +17,7 @@
|
||||
<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>
|
||||
@ -73,6 +73,9 @@
|
||||
<Compile Include="master\resources\null.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Master\resources\server.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\resources\version.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
@ -109,15 +112,17 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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,14 +1,16 @@
|
||||
|
||||
class ServerModel(object):
|
||||
def __init__(self, id, port, game, hostname, clientnum, maxclientnum, map, gametype):
|
||||
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
|
||||
self.maxclientnum = maxclientnum
|
||||
self.map = map
|
||||
self.gametype = gametype
|
||||
self.ip = ip
|
||||
|
||||
def __repr__(self):
|
||||
return '<ServerModel(id={id})>'.format(id=self.id)
|
||||
|
@ -4,6 +4,7 @@ from flask_jwt_extended import jwt_required
|
||||
from marshmallow import ValidationError
|
||||
from master.schema.instanceschema import InstanceSchema
|
||||
from master import ctx
|
||||
import json
|
||||
|
||||
class Instance(Resource):
|
||||
def get(self, id=None):
|
||||
@ -21,6 +22,11 @@ class Instance(Resource):
|
||||
@jwt_required
|
||||
def put(self, id):
|
||||
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
|
||||
@ -30,6 +36,11 @@ class Instance(Resource):
|
||||
@jwt_required
|
||||
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
|
||||
|
6
Master/master/resources/server.py
Normal file
6
Master/master/resources/server.py
Normal file
@ -0,0 +1,6 @@
|
||||
from flask_restful import Resource
|
||||
|
||||
class Server(Resource):
|
||||
"""description of class"""
|
||||
|
||||
|
@ -6,10 +6,12 @@ from master.resources.authenticate import Authenticate
|
||||
from master.resources.version import Version
|
||||
from master.resources.history_graph import HistoryGraph
|
||||
from master.resources.localization import Localization
|
||||
from master.resources.server import Server
|
||||
|
||||
api.add_resource(Null, '/null')
|
||||
api.add_resource(Instance, '/instance/', '/instance/<string:id>')
|
||||
api.add_resource(Version, '/version')
|
||||
api.add_resource(Authenticate, '/authenticate')
|
||||
api.add_resource(HistoryGraph, '/history/', '/history/<int:history_count>')
|
||||
api.add_resource(Localization, '/localization/', '/localization/<string:language_tag>')
|
||||
api.add_resource(Localization, '/localization/', '/localization/<string:language_tag>')
|
||||
api.add_resource(Server, '/server')
|
@ -4,19 +4,26 @@ from master.models.servermodel import ServerModel
|
||||
class ServerSchema(Schema):
|
||||
id = fields.Int(
|
||||
required=True,
|
||||
validate=validate.Range(1, 2147483647, 'invalid id')
|
||||
validate=validate.Range(1, 25525525525565535, 'invalid id')
|
||||
)
|
||||
ip = fields.Str(
|
||||
required=True
|
||||
)
|
||||
port = fields.Int(
|
||||
required=True,
|
||||
validate=validate.Range(1, 665535, 'invalid port')
|
||||
validate=validate.Range(1, 65535, 'invalid port')
|
||||
)
|
||||
version = fields.String(
|
||||
required=False,
|
||||
validate=validate.Length(0, 128, 'invalid server version')
|
||||
)
|
||||
game = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(1, 8, 'invalid game name')
|
||||
validate=validate.Length(1, 5, 'invalid game name')
|
||||
)
|
||||
hostname = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(1, 48, 'invalid hostname')
|
||||
validate=validate.Length(1, 64, 'invalid hostname')
|
||||
)
|
||||
clientnum = fields.Int(
|
||||
required=True,
|
||||
|
@ -39,6 +39,7 @@
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
|
||||
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
113
Master/master/templates/serverlist.html
Normal file
113
Master/master/templates/serverlist.html
Normal file
@ -0,0 +1,113 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<!-- todo: move this! -->
|
||||
<style>
|
||||
.server-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-content, .nav-item {
|
||||
background-color: #212529;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.modal-header, .modal-footer {
|
||||
border-color: #32383e !important;
|
||||
}
|
||||
|
||||
.modal-dark button.close, a.nav-link {
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal modal-dark" id="serverModalCenter" tabindex="-1" role="dialog" aria-labelledby="serverModalCenterTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="serverModalTitle">Modal title</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="h5" id="server_socket"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="connect_button" type="button" class="btn btn-dark">Connect</button>
|
||||
<button type="button" class="btn btn-dark" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="server_game_tabs" role="tablist">
|
||||
{% for game in games %}
|
||||
<a class="nav-item nav-link {{'active' if loop.first else ''}}" id="{{game}}_servers_tab" data-toggle="tab" href="#{{game}}_servers" role="tab" aria-controls="{{game}}_servers" aria-selected="{{'true' if loop.first else 'false' }}">{{game}}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content" id="server_game_tabs_content">
|
||||
{% for game, servers in games.items() %}
|
||||
|
||||
<div class="tab-pane {{'show active' if loop.first else ''}}" id="{{game}}_servers" role="tabpanel" aria-labelledby="{{game}}_servers_tab">
|
||||
|
||||
<table class="table table-dark table-striped table-hover table-responsive-lg">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Server Name</th>
|
||||
<th>Map Name</th>
|
||||
<th>Players</th>
|
||||
<th>Mode</th>
|
||||
<th class="text-center">Connect</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{% for server in servers %}
|
||||
|
||||
<tr class="server-row" data-toggle="modal" data-target="#serverModalCenter"
|
||||
data-ip="{{server.ip}}" data-port="{{server.port}}">
|
||||
<td data-hostname="{{server.hostname}}" class="server-hostname">{{server.hostname}}</td>
|
||||
<td data-map="{{server.map}} " class="server-map">{{server.map}}</td>
|
||||
<td data-clientnum="{{server.clientnum}}" data-maxclientnum="{{server.maxclientnum}}"
|
||||
class="server-clientnum">
|
||||
{{server.clientnum}}/{{server.maxclientnum}}
|
||||
</td>
|
||||
<td data-gametype="{{server.gametype}}" class="server-gametype">{{server.gametype}}</td>
|
||||
<td class="text-center"><span class="oi oi-play-circle"></span></td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="w-100 small text-right text-muted">
|
||||
<span>Developed by RaidMax</span><br />
|
||||
<span>PRERELEASE</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
$('.server-row').off('click');
|
||||
$('.server-row').on('click', function (e) {
|
||||
$('#serverModalTitle').text($(this).find('.server-hostname').text());
|
||||
$('#server_socket').text(`/connect ${$(this).data('ip')}:${$(this).data('port')}`);
|
||||
});
|
||||
|
||||
$('#connect_button').off('click');
|
||||
$('#connect_button').on('click', (e) => alert('soon...'));
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -4,8 +4,9 @@ Routes and views for the flask application.
|
||||
|
||||
from datetime import datetime
|
||||
from flask import render_template
|
||||
from master import app
|
||||
from master import app, ctx
|
||||
from master.resources.history_graph import HistoryGraph
|
||||
from collections import defaultdict
|
||||
|
||||
@app.route('/')
|
||||
def home():
|
||||
@ -19,3 +20,16 @@ def home():
|
||||
client_count = _history_graph[0]['client_count'],
|
||||
server_count = _history_graph[0]['server_count']
|
||||
)
|
||||
|
||||
@app.route('/servers')
|
||||
def servers():
|
||||
servers = defaultdict(list)
|
||||
if len(ctx.instance_list.values()) > 0:
|
||||
ungrouped_servers = [server for instance in ctx.instance_list.values() for server in instance.servers]
|
||||
for server in ungrouped_servers:
|
||||
servers[server.game].append(server)
|
||||
return render_template(
|
||||
'serverlist.html',
|
||||
title = 'Server List',
|
||||
games = servers
|
||||
)
|
||||
|
@ -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,4 +1,5 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -8,7 +9,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace IW4ScriptCommands.Commands
|
||||
{
|
||||
class Balance : Command
|
||||
class Balance
|
||||
{
|
||||
private class TeamAssignment
|
||||
{
|
||||
@ -16,39 +17,40 @@ namespace IW4ScriptCommands.Commands
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
public static string GetTeamAssignments(EFClient client, bool isDisconnect, Server server, string teamsString = "")
|
||||
{
|
||||
string teamsString = (await E.Owner.GetDvarAsync<string>("sv_iw4madmin_teams")).Value;
|
||||
|
||||
var scriptClientTeams = teamsString.Split(';', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(c => c.Split(','))
|
||||
.Select(c => new TeamAssignment()
|
||||
{
|
||||
CurrentTeam = (IW4MAdmin.Plugins.Stats.IW4Info.Team)Enum.Parse(typeof(IW4MAdmin.Plugins.Stats.IW4Info.Team), c[1]),
|
||||
Num = 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())
|
||||
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();
|
||||
|
||||
// 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))
|
||||
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))
|
||||
{
|
||||
await E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
|
||||
return;
|
||||
// E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
List<string> teamAssignments = new List<string>();
|
||||
|
||||
var activeClients = E.Owner.GetPlayersAsList().Select(c => new TeamAssignment()
|
||||
var _c = server.GetClientsAsList();
|
||||
if (isDisconnect && client != null)
|
||||
{
|
||||
_c = _c.Where(c => c.ClientNumber != client.ClientNumber).ToList();
|
||||
}
|
||||
|
||||
var activeClients = _c.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
|
||||
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)
|
||||
@ -170,28 +172,29 @@ namespace IW4ScriptCommands.Commands
|
||||
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;
|
||||
}
|
||||
//if (alliesTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0 &&
|
||||
// axisTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0)
|
||||
//{
|
||||
// //E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
|
||||
// return string.Empty;
|
||||
//}
|
||||
|
||||
if (E.Origin?.Level > Player.Permission.Administrator)
|
||||
{
|
||||
await E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}");
|
||||
await E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}");
|
||||
}
|
||||
//if (E.Origin?.Level > Player.Permission.Administrator)
|
||||
//{
|
||||
// E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}");
|
||||
// E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}");
|
||||
//}
|
||||
|
||||
//E.Origin.Tell("Balance command sent");
|
||||
string args = string.Join(",", teamAssignments);
|
||||
await E.Owner.ExecuteCommandAsync($"sv_iw4madmin_command \"balance:{args}\"");
|
||||
await E.Origin.Tell("Balance command sent");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,8 +3,10 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
@ -17,7 +19,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App"/>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
</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,14 +9,15 @@ 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()
|
||||
{
|
||||
Name = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ARGS_PASSWORD"],
|
||||
Required = true
|
||||
}
|
||||
}){ }
|
||||
})
|
||||
{ }
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
@ -25,12 +27,12 @@ namespace IW4MAdmin.Plugins.Login.Commands
|
||||
if (hashedPassword[0] == client.Password)
|
||||
{
|
||||
Plugin.AuthorizedClients[E.Origin.ClientId] = true;
|
||||
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]);
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]);
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.Login</PackageId>
|
||||
@ -21,7 +22,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App"/>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -3,9 +3,9 @@ using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Login
|
||||
{
|
||||
@ -22,12 +22,13 @@ namespace IW4MAdmin.Plugins.Login
|
||||
|
||||
public Task OnEventAsync(GameEvent E, Server S)
|
||||
{
|
||||
if (E.Remote || Config.RequirePrivilegedClientLogin == false)
|
||||
if (E.IsRemote || Config.RequirePrivilegedClientLogin == false)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (E.Type == GameEvent.EventType.Connect)
|
||||
{
|
||||
AuthorizedClients.TryAdd(E.Origin.ClientId, false);
|
||||
E.Origin.SetAdditionalProperty("IsLoggedIn", false);
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Disconnect)
|
||||
@ -37,11 +38,11 @@ namespace IW4MAdmin.Plugins.Login
|
||||
|
||||
if (E.Type == GameEvent.EventType.Command)
|
||||
{
|
||||
if (E.Origin.Level < Player.Permission.Moderator ||
|
||||
E.Origin.Level == Player.Permission.Console)
|
||||
if (E.Origin.Level < EFClient.Permission.Moderator ||
|
||||
E.Origin.Level == EFClient.Permission.Console)
|
||||
return Task.CompletedTask;
|
||||
|
||||
E.Owner.Manager.GetPrivilegedClients().TryGetValue(E.Origin.ClientId, out Player client);
|
||||
E.Owner.Manager.GetPrivilegedClients().TryGetValue(E.Origin.ClientId, out EFClient client);
|
||||
|
||||
if (((Command)E.Extra).Name == new SharedLibraryCore.Commands.CSetPassword().Name &&
|
||||
client?.Password == null)
|
||||
@ -51,7 +52,14 @@ namespace IW4MAdmin.Plugins.Login
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (!AuthorizedClients[E.Origin.ClientId])
|
||||
{
|
||||
throw new AuthorizationException(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_AUTH"]);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
E.Origin.SetAdditionalProperty("IsLoggedIn", true);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -16,9 +16,9 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
{
|
||||
OffensiveWords = new List<string>()
|
||||
{
|
||||
"nigger",
|
||||
"nigga",
|
||||
"fuck"
|
||||
@"\s*n+.*i+.*g+.*e+.*r+\s*",
|
||||
@"\s*n+.*i+.*g+.*a+\s*",
|
||||
@"\s*f+u+.*c+.*k+.*\s*"
|
||||
};
|
||||
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
|
@ -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;
|
||||
|
||||
@ -22,10 +23,10 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
ConcurrentDictionary<int, Tracking> ProfanityCounts;
|
||||
IManager Manager;
|
||||
|
||||
public async Task OnEventAsync(GameEvent E, Server S)
|
||||
public Task OnEventAsync(GameEvent E, Server S)
|
||||
{
|
||||
if (!Settings.Configuration().EnableProfanityDeterment)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (E.Type == GameEvent.EventType.Connect)
|
||||
{
|
||||
@ -48,9 +49,10 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
|
||||
if (containsObjectionalWord)
|
||||
{
|
||||
await E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
|
||||
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new EFClient()
|
||||
{
|
||||
ClientId = 1
|
||||
ClientId = 1,
|
||||
CurrentServer = E.Owner
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -84,23 +86,18 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
var clientProfanity = ProfanityCounts[E.Origin.ClientId];
|
||||
if (clientProfanity.Infringements >= Settings.Configuration().KickAfterInfringementCount)
|
||||
{
|
||||
await clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
});
|
||||
clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, Utilities.IW4MAdminClient(E.Owner));
|
||||
}
|
||||
|
||||
else if (clientProfanity.Infringements < Settings.Configuration().KickAfterInfringementCount)
|
||||
{
|
||||
clientProfanity.Infringements++;
|
||||
|
||||
await clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
});
|
||||
clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, Utilities.IW4MAdminClient(E.Owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task OnLoadAsync(IManager manager)
|
||||
|
@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId>
|
||||
@ -19,7 +20,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App"/>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
</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;
|
||||
|
33
Plugins/ScriptPlugins/ParserIW3.js
Normal file
33
Plugins/ScriptPlugins/ParserIW3.js
Normal file
@ -0,0 +1,33 @@
|
||||
var rconParser;
|
||||
var eventParser;
|
||||
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.1,
|
||||
name: 'IW3 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 {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0} "{1}"';
|
||||
rconParser.Version = 'CoD4 MP 1.8 build 13620 Thu Oct 04 00:43:04 2007 win-x86';
|
||||
|
||||
eventParser.Configuration.GameDirectory = 'main';
|
||||
eventParser.Version = 'CoD4 MP 1.8 build 13620 Thu Oct 04 00:43:04 2007 win-x86';
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
49
Plugins/ScriptPlugins/ParserPT6.js
Normal file
49
Plugins/ScriptPlugins/ParserPT6.js
Normal file
@ -0,0 +1,49 @@
|
||||
var rconParser;
|
||||
var eventParser;
|
||||
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.1,
|
||||
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';
|
||||
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';
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
40
Plugins/ScriptPlugins/ParserTeknoMW3.js
Normal file
40
Plugins/ScriptPlugins/ParserTeknoMW3.js
Normal file
@ -0,0 +1,40 @@
|
||||
var rconParser;
|
||||
var eventParser;
|
||||
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.1,
|
||||
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';
|
||||
|
||||
eventParser.Configuration.GameDirectory = 'scripts';
|
||||
eventParser.Version = 'IW5 MP 1.4 build 382 latest Thu Jan 19 2012 11:09:49AM win-x86';
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.0,
|
||||
version: 1.1,
|
||||
name: 'Shared GUID Kicker Plugin',
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
@ -10,8 +10,7 @@ var plugin = {
|
||||
}
|
||||
|
||||
// connect or join event
|
||||
if (gameEvent.Type === 3 ||
|
||||
gameEvent.Type === 4) {
|
||||
if (gameEvent.Type === 3) {
|
||||
// this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID
|
||||
if (gameEvent.Origin.NetworkId === -805366929435212061) {
|
||||
gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient);
|
||||
|
@ -28,7 +28,6 @@ var plugin = {
|
||||
var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result;
|
||||
var co = re.Content;
|
||||
var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result);
|
||||
// todo: does this work as expected now?
|
||||
co.Dispose();
|
||||
re.Dispose();
|
||||
cl.Dispose();
|
||||
@ -39,10 +38,7 @@ var plugin = {
|
||||
|
||||
if (usingVPN) {
|
||||
this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')');
|
||||
var library = importNamespace('SharedLibraryCore');
|
||||
var kickOrigin = new library.Objects.Player();
|
||||
kickOrigin.ClientId = 1;
|
||||
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], kickOrigin);
|
||||
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient);
|
||||
}
|
||||
},
|
||||
|
||||
@ -55,7 +51,7 @@ var plugin = {
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
this.manager = manager;
|
||||
this.logger = manager.GetLogger();
|
||||
this.logger = manager.GetLogger(0);
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
|
@ -1,7 +1,7 @@
|
||||
using SharedLibraryCore.Helpers;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -20,14 +20,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
};
|
||||
|
||||
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
||||
public const int QUEUE_COUNT = 10;
|
||||
|
||||
public List<EFClientKill> QueuedHits { get; set; }
|
||||
int Kills;
|
||||
int HitCount;
|
||||
Dictionary<IW4Info.HitLocation, int> HitLocationCount;
|
||||
double AngleDifferenceAverage;
|
||||
EFClientStatistics ClientStats;
|
||||
DateTime LastHit;
|
||||
long LastOffset;
|
||||
IW4Info.WeaponName LastWeapon;
|
||||
ILogger Log;
|
||||
Strain Strain;
|
||||
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
||||
@ -37,49 +39,55 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
Log = log;
|
||||
HitLocationCount = new Dictionary<IW4Info.HitLocation, int>();
|
||||
foreach (var loc in Enum.GetValues(typeof(IW4Info.HitLocation)))
|
||||
{
|
||||
HitLocationCount.Add((IW4Info.HitLocation)loc, 0);
|
||||
}
|
||||
|
||||
ClientStats = clientStats;
|
||||
Strain = new Strain();
|
||||
Tracker = new ChangeTracking<EFACSnapshot>();
|
||||
QueuedHits = new List<EFClientKill>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyze kill and see if performed by a cheater
|
||||
/// </summary>
|
||||
/// <param name="kill">kill performed by the player</param>
|
||||
/// <param name="hit">kill performed by the player</param>
|
||||
/// <returns>true if detection reached thresholds, false otherwise</returns>
|
||||
public DetectionPenaltyResult ProcessKill(EFClientKill kill, bool isDamage)
|
||||
public DetectionPenaltyResult ProcessHit(EFClientKill hit, bool isDamage)
|
||||
{
|
||||
if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
|
||||
kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
|
||||
kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
|
||||
kill.HitLoc == IW4Info.HitLocation.none || kill.TimeOffset - LastOffset < 0)
|
||||
if ((hit.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
|
||||
hit.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
|
||||
hit.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
|
||||
hit.HitLoc == IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 ||
|
||||
// hack: prevents false positives
|
||||
(LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50))
|
||||
{
|
||||
return new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Any,
|
||||
};
|
||||
}
|
||||
|
||||
DetectionPenaltyResult result = null;
|
||||
LastWeapon = hit.Weapon;
|
||||
|
||||
if (LastHit == DateTime.MinValue)
|
||||
LastHit = DateTime.UtcNow;
|
||||
HitLocationCount[hit.HitLoc]++;
|
||||
HitCount++;
|
||||
|
||||
HitLocationCount[kill.HitLoc]++;
|
||||
if (!isDamage)
|
||||
{
|
||||
Kills++;
|
||||
}
|
||||
|
||||
HitCount++;
|
||||
|
||||
#region VIEWANGLES
|
||||
if (kill.AnglesList.Count >= 2)
|
||||
if (hit.AnglesList.Count >= 2)
|
||||
{
|
||||
double realAgainstPredict = Vector3.ViewAngleDistance(kill.AnglesList[0], kill.AnglesList[1], kill.ViewAngles);
|
||||
double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles);
|
||||
|
||||
// LIFETIME
|
||||
var hitLoc = ClientStats.HitLocations
|
||||
.First(hl => hl.Location == kill.HitLoc);
|
||||
.First(hl => hl.Location == hit.HitLoc);
|
||||
|
||||
float previousAverage = hitLoc.HitOffsetAverage;
|
||||
double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount;
|
||||
@ -88,11 +96,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset(hitLoc.HitCount) &&
|
||||
hitLoc.HitCount > 100)
|
||||
{
|
||||
Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
|
||||
Log.WriteDebug($"Lifetime Average = {newAverage}");
|
||||
Log.WriteDebug($"Bone = {hitLoc.Location}");
|
||||
Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
|
||||
Log.WriteDebug($"ID = {kill.AttackerId}");
|
||||
//Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
|
||||
//Log.WriteDebug($"Lifetime Average = {newAverage}");
|
||||
//Log.WriteDebug($"Bone = {hitLoc.Location}");
|
||||
//Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
|
||||
//Log.WriteDebug($"ID = {hit.AttackerId}");
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -110,10 +118,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
if (sessAverage > Thresholds.MaxOffset(HitCount) &&
|
||||
HitCount > 30)
|
||||
{
|
||||
Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
|
||||
Log.WriteDebug($"Session Average = {sessAverage}");
|
||||
Log.WriteDebug($"HitCount = {HitCount}");
|
||||
Log.WriteDebug($"ID = {kill.AttackerId}");
|
||||
//Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
|
||||
//Log.WriteDebug($"Session Average = {sessAverage}");
|
||||
//Log.WriteDebug($"HitCount = {HitCount}");
|
||||
//Log.WriteDebug($"ID = {hit.AttackerId}");
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -130,8 +138,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
#endif
|
||||
}
|
||||
|
||||
double currentStrain = Strain.GetStrain(isDamage, kill.Damage, kill.Distance / 0.0254, kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset));
|
||||
LastOffset = kill.TimeOffset;
|
||||
double currentStrain = Strain.GetStrain(isDamage, hit.Damage, hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, hit.TimeOffset - LastOffset));
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Current Strain: {currentStrain}");
|
||||
#endif
|
||||
LastOffset = hit.TimeOffset;
|
||||
|
||||
if (currentStrain > ClientStats.MaxStrain)
|
||||
{
|
||||
@ -139,8 +150,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
}
|
||||
|
||||
// flag
|
||||
if (currentStrain > Thresholds.MaxStrainFlag
|
||||
&& HitCount >= 10)
|
||||
if (currentStrain > Thresholds.MaxStrainFlag)
|
||||
{
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -153,7 +163,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
// ban
|
||||
if (currentStrain > Thresholds.MaxStrainBan &&
|
||||
HitCount >= 15)
|
||||
HitCount >= 5)
|
||||
{
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -163,11 +173,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
Type = DetectionType.Strain
|
||||
};
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Log.WriteDebug($"Current Strain: {currentStrain}");
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region SESSION_RATIOS
|
||||
@ -197,13 +202,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
if (currentHeadshotRatio > maxHeadshotLerpValueForBan)
|
||||
{
|
||||
Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
{
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
}
|
||||
|
||||
Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
@ -218,13 +226,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
else
|
||||
{
|
||||
Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
{
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
}
|
||||
|
||||
Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
@ -247,13 +258,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
|
||||
{
|
||||
Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
{
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
}
|
||||
|
||||
Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
@ -268,13 +282,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
else
|
||||
{
|
||||
Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
{
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
}
|
||||
|
||||
Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
@ -308,15 +325,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills + 30)
|
||||
{
|
||||
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"**Chest Hits: {chestHits}");
|
||||
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
Log.WriteDebug(sb.ToString());
|
||||
//Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
|
||||
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
//Log.WriteDebug($"**Chest Hits: {chestHits}");
|
||||
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
//Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
|
||||
//var sb = new StringBuilder();
|
||||
//foreach (var kvp in HitLocationCount)
|
||||
// sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
//Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -329,16 +346,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"**Chest Hits: {chestHits}");
|
||||
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in HitLocationCount)
|
||||
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
Log.WriteDebug(sb.ToString());
|
||||
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
|
||||
//Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
|
||||
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
//Log.WriteDebug($"**Chest Hits: {chestHits}");
|
||||
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
//Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
|
||||
//var sb = new StringBuilder();
|
||||
//foreach (var kvp in HitLocationCount)
|
||||
// sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
|
||||
//Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
@ -356,30 +372,31 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
Tracker.OnChange(new EFACSnapshot()
|
||||
{
|
||||
Active = true,
|
||||
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,
|
||||
HitDestination = kill.DeathOrigin,
|
||||
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()
|
||||
@ -408,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()
|
||||
{
|
||||
@ -429,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)
|
||||
{
|
||||
@ -63,12 +66,16 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
if (!E.Message.IsBroadcastCommand())
|
||||
{
|
||||
foreach (var stat in topStats)
|
||||
await E.Origin.Tell(stat);
|
||||
{
|
||||
E.Origin.Tell(stat);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var stat in topStats)
|
||||
await E.Owner.Broadcast(stat);
|
||||
{
|
||||
E.Owner.Broadcast(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,52 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
public class ResetStats : Command
|
||||
{
|
||||
public ResetStats() : base("resetstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_DESC"], "rs", Player.Permission.User, false) { }
|
||||
public ResetStats() : base("resetstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_DESC"], "rs", EFClient.Permission.User, false) { }
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
if (E.Origin.ClientNumber >= 0)
|
||||
{
|
||||
var svc = new SharedLibraryCore.Services.GenericRepository<EFClientStatistics>();
|
||||
int serverId = E.Owner.GetHashCode();
|
||||
var stats = svc.Find(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId).First();
|
||||
|
||||
stats.Deaths = 0;
|
||||
stats.Kills = 0;
|
||||
stats.SPM = 0.0;
|
||||
stats.Skill = 0.0;
|
||||
stats.TimePlayed = 0;
|
||||
// todo: make this more dynamic
|
||||
stats.EloRating = 200.0;
|
||||
long serverId = await Helpers.StatManager.GetIdForServer(E.Owner);
|
||||
|
||||
// reset the cached version
|
||||
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
|
||||
EFClientStatistics clientStats;
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
clientStats = await ctx.Set<EFClientStatistics>()
|
||||
.Where(s => s.ClientId == E.Origin.ClientId)
|
||||
.Where(s => s.ServerId == serverId)
|
||||
.FirstAsync();
|
||||
|
||||
// fixme: this doesn't work properly when another context exists
|
||||
await svc.SaveChangesAsync();
|
||||
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
|
||||
clientStats.Deaths = 0;
|
||||
clientStats.Kills = 0;
|
||||
clientStats.SPM = 0.0;
|
||||
clientStats.Skill = 0.0;
|
||||
clientStats.TimePlayed = 0;
|
||||
// todo: make this more dynamic
|
||||
clientStats.EloRating = 200.0;
|
||||
|
||||
// reset the cached version
|
||||
Plugin.Manager.ResetStats(E.Origin.ClientId, serverId);
|
||||
|
||||
// fixme: this doesn't work properly when another context exists
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_FAIL"]);
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_FAIL"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,26 +9,24 @@ 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"]}--"
|
||||
};
|
||||
|
||||
using (var db = new DatabaseContext())
|
||||
using (var db = new DatabaseContext(true))
|
||||
{
|
||||
db.ChangeTracker.AutoDetectChangesEnabled = false;
|
||||
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
|
||||
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
|
||||
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
|
||||
|
||||
var iqStats = (from stats in db.Set<EFClientStatistics>()
|
||||
join client in db.Clients
|
||||
@ -36,9 +34,9 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
join alias in db.Aliases
|
||||
on client.CurrentAliasId equals alias.AliasId
|
||||
where stats.ServerId == serverId
|
||||
where stats.TimePlayed >= 3600
|
||||
where client.Level != Player.Permission.Banned
|
||||
where client.LastConnection >= thirtyDaysAgo
|
||||
where stats.TimePlayed >= Plugin.Config.Configuration().TopPlayersMinPlayTime
|
||||
where client.Level != EFClient.Permission.Banned
|
||||
where client.LastConnection >= fifteenDaysAgo
|
||||
orderby stats.Performance descending
|
||||
select new
|
||||
{
|
||||
@ -48,6 +46,10 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
})
|
||||
.Take(5);
|
||||
|
||||
#if DEBUG == true
|
||||
var statsSql = iqStats.ToSql();
|
||||
#endif
|
||||
|
||||
var statsList = (await iqStats.ToListAsync())
|
||||
.Select(stats => $"^3{stats.Name}^7 - ^5{stats.KDR} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"]} | ^5{stats.Performance} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_PERFORMANCE"]}");
|
||||
|
||||
@ -66,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)
|
||||
{
|
||||
@ -74,12 +76,17 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
if (!E.Message.IsBroadcastCommand())
|
||||
{
|
||||
foreach (var stat in topStats)
|
||||
await E.Origin.Tell(stat);
|
||||
{
|
||||
E.Origin.Tell(stat);
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var stat in topStats)
|
||||
await E.Owner.Broadcast(stat);
|
||||
{
|
||||
E.Owner.Broadcast(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,16 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
public class CViewStats : Command
|
||||
{
|
||||
public CViewStats() : base("stats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_VIEW_DESC"], "xlrstats", Player.Permission.User, false, new CommandArgument[]
|
||||
public CViewStats() : base("stats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_VIEW_DESC"], "xlrstats", EFClient.Permission.User, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -35,39 +39,48 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
|
||||
if (E.Target == null)
|
||||
{
|
||||
await E.Origin.Tell(loc["PLUGINS_STATS_COMMANDS_VIEW_FAIL"]);
|
||||
return;
|
||||
E.Origin.Tell(loc["PLUGINS_STATS_COMMANDS_VIEW_FAIL"]);
|
||||
}
|
||||
}
|
||||
|
||||
var clientStats = new GenericRepository<EFClientStatistics>();
|
||||
int serverId = E.Owner.GetHashCode();
|
||||
long serverId = await StatManager.GetIdForServer(E.Owner);
|
||||
|
||||
|
||||
if (E.Target != null)
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
pStats = clientStats.Find(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId).First();
|
||||
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}";
|
||||
}
|
||||
if (E.Target != null)
|
||||
{
|
||||
int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId);
|
||||
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
||||
|
||||
else
|
||||
{
|
||||
pStats = pStats = clientStats.Find(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId).First();
|
||||
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}";
|
||||
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId));
|
||||
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId);
|
||||
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
||||
|
||||
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync((c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId)));
|
||||
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Message.IsBroadcastCommand())
|
||||
{
|
||||
string name = E.Target == null ? E.Origin.Name : E.Target.Name;
|
||||
await E.Owner.Broadcast($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{name}^7");
|
||||
await E.Owner.Broadcast(statLine);
|
||||
E.Owner.Broadcast($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{name}^7");
|
||||
E.Owner.Broadcast(statLine);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (E.Target != null)
|
||||
await E.Origin.Tell($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{E.Target.Name}^7");
|
||||
await E.Origin.Tell(statLine);
|
||||
{
|
||||
E.Origin.Tell($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{E.Target.Name}^7");
|
||||
}
|
||||
|
||||
E.Origin.Tell(statLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
||||
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }
|
||||
public List<StreakMessageConfiguration> DeathstreakMessages { get; set; }
|
||||
public int TopPlayersMinPlayTime { get; set; }
|
||||
public bool StoreClientKills { get; set; }
|
||||
public string Name() => "Stats";
|
||||
public IBaseConfiguration Generate()
|
||||
{
|
||||
@ -49,6 +50,7 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
||||
};
|
||||
|
||||
TopPlayersMinPlayTime = 3600 * 3;
|
||||
StoreClientKills = false;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,40 +0,0 @@
|
||||
using SharedLibraryCore.Services;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
{
|
||||
public class ThreadSafeStatsService
|
||||
{
|
||||
public GenericRepository<EFClientStatistics> ClientStatSvc
|
||||
{
|
||||
get
|
||||
{
|
||||
return new GenericRepository<EFClientStatistics>();
|
||||
}
|
||||
}
|
||||
public GenericRepository<EFServer> ServerSvc { get; private set; }
|
||||
public GenericRepository<EFClientKill> KillStatsSvc { get; private set; }
|
||||
public GenericRepository<EFServerStatistics> ServerStatsSvc { get; private set; }
|
||||
public GenericRepository<EFClientMessage> MessageSvc
|
||||
{
|
||||
get
|
||||
{
|
||||
return new GenericRepository<EFClientMessage>();
|
||||
}
|
||||
}
|
||||
|
||||
public ThreadSafeStatsService()
|
||||
{
|
||||
//ClientStatSvc = new GenericRepository<EFClientStatistics>();
|
||||
ServerSvc = new GenericRepository<EFServer>();
|
||||
KillStatsSvc = new GenericRepository<EFClientKill>();
|
||||
ServerStatsSvc = new GenericRepository<EFServerStatistics>();
|
||||
//MessageSvc = new GenericRepository<EFClientMessage>();
|
||||
}
|
||||
}
|
||||
}
|
@ -65,6 +65,7 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
right_foot,
|
||||
left_foot,
|
||||
gun,
|
||||
shield
|
||||
}
|
||||
|
||||
public enum WeaponName
|
||||
@ -1369,7 +1370,9 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
dragunov_mp,
|
||||
cobra_player_minigun_mp,
|
||||
destructible_car,
|
||||
sentry_minigun_mp
|
||||
sentry_minigun_mp,
|
||||
cobra_20mm_mp,
|
||||
shield
|
||||
}
|
||||
|
||||
public enum MapName
|
||||
|
@ -30,10 +30,18 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
public double CurrentStrain { get; set; }
|
||||
public double StrainAngleBetween { get; set; }
|
||||
public double SessionAngleOffset { get; set; }
|
||||
public int LastStrainAngleId { get; set; }
|
||||
[ForeignKey("LastStrainAngleId")]
|
||||
public Vector3 LastStrainAngle { get; set; }
|
||||
public int HitOriginId { get; set; }
|
||||
[ForeignKey("HitOriginId")]
|
||||
public Vector3 HitOrigin { get; set; }
|
||||
public int HitDestinationId { get; set; }
|
||||
[ForeignKey("HitDestinationId")]
|
||||
public Vector3 HitDestination { get; set; }
|
||||
public double Distance { get; set; }
|
||||
public int CurrentViewAngleId { get; set; }
|
||||
[ForeignKey("CurrentViewAngleId")]
|
||||
public Vector3 CurrentViewAngle { get; set; }
|
||||
public IW4Info.WeaponName WeaponId { get; set; }
|
||||
public IW4Info.HitLocation HitLocation { get; set; }
|
||||
|
@ -18,7 +18,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
public int AttackerId { get; set; }
|
||||
[ForeignKey("AttackerId")]
|
||||
public virtual EFClient Attacker { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")]
|
||||
public virtual EFServer Server { get; set; }
|
||||
public IW4Info.HitLocation HitLoc { get; set; }
|
||||
|
@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
{
|
||||
[Key]
|
||||
public long MessageId { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")]
|
||||
public virtual EFServer Server { get; set; }
|
||||
public int ClientId { get; set; }
|
||||
|
@ -15,7 +15,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
public int ClientId { get; set; }
|
||||
[ForeignKey("ClientId")]
|
||||
public virtual EFClient Client { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")]
|
||||
public virtual EFServer Server { get; set; }
|
||||
[Required]
|
||||
|
@ -20,9 +20,8 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
public int ClientId { get; set; }
|
||||
[ForeignKey("ClientId"), Column(Order = 0 )]
|
||||
public EFClient Client { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId"), Column(Order = 1)]
|
||||
public EFServer Server { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
[ForeignKey("RatingHistoryId")]
|
||||
public virtual EFClientRatingHistory RatingHistory { get; set; }
|
||||
// if null, indicates that the rating is an average rating
|
||||
public int? ServerId { get; set; }
|
||||
public long? ServerId { get; set; }
|
||||
// [ForeignKey("ServerId")] can't make this nullable if this annotation is set
|
||||
public virtual EFServer Server { get; set; }
|
||||
[Required]
|
||||
|
@ -9,8 +9,9 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.None)]
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[Required]
|
||||
public int Port { get; set; }
|
||||
public string EndPoint { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
{
|
||||
[Key]
|
||||
public int StatisticId { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")]
|
||||
public virtual EFServer Server { get; set; }
|
||||
public long TotalKills { get; set; }
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
|
||||
namespace Stats.Models
|
||||
{
|
||||
@ -30,6 +29,9 @@ namespace Stats.Models
|
||||
builder.Entity<EFRating>()
|
||||
.HasIndex(p => p.When);
|
||||
|
||||
builder.Entity<EFClientMessage>()
|
||||
.HasIndex(p => p.TimeSent);
|
||||
|
||||
// force pluralization
|
||||
builder.Entity<EFClientKill>().ToTable("EFClientKills");
|
||||
builder.Entity<EFClientMessage>().ToTable("EFClientMessages");
|
||||
|
@ -1,18 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
|
||||
using IW4MAdmin.Plugins.Stats.Config;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Services;
|
||||
using IW4MAdmin.Plugins.Stats.Config;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats
|
||||
{
|
||||
@ -42,16 +43,19 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
break;
|
||||
case GameEvent.EventType.Disconnect:
|
||||
await Manager.RemovePlayer(E.Origin);
|
||||
await Manager.Sync(S);
|
||||
break;
|
||||
case GameEvent.EventType.Say:
|
||||
if (!string.IsNullOrEmpty(E.Data) &&
|
||||
E.Origin.ClientId > 1)
|
||||
await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data);
|
||||
{
|
||||
await Manager.AddMessageAsync(E.Origin.ClientId, await StatManager.GetIdForServer(E.Owner), E.Data);
|
||||
}
|
||||
|
||||
break;
|
||||
case GameEvent.EventType.MapChange:
|
||||
Manager.SetTeamBased(E.Owner.GetHashCode(), E.Owner.Gametype != "dm");
|
||||
Manager.ResetKillstreaks(S.GetHashCode());
|
||||
await Manager.Sync(S);
|
||||
Manager.SetTeamBased(await StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm");
|
||||
Manager.ResetKillstreaks(await StatManager.GetIdForServer(E.Owner));
|
||||
break;
|
||||
case GameEvent.EventType.MapEnd:
|
||||
break;
|
||||
@ -75,23 +79,27 @@ 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;
|
||||
case GameEvent.EventType.Kill:
|
||||
if (!E.Owner.CustomCallback)
|
||||
{
|
||||
await Manager.AddStandardKill(E.Origin, E.Target);
|
||||
}
|
||||
break;
|
||||
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;
|
||||
@ -118,8 +126,11 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
// meta data info
|
||||
async Task<List<ProfileMeta>> getStats(int clientId)
|
||||
{
|
||||
var statsSvc = new GenericRepository<EFClientStatistics>();
|
||||
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId);
|
||||
IList<EFClientStatistics> clientStats;
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == clientId).ToListAsync();
|
||||
}
|
||||
|
||||
int kills = clientStats.Sum(c => c.Kills);
|
||||
int deaths = clientStats.Sum(c => c.Deaths);
|
||||
@ -134,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()
|
||||
{
|
||||
@ -166,8 +177,14 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
|
||||
async Task<List<ProfileMeta>> getAnticheatInfo(int clientId)
|
||||
{
|
||||
var statsSvc = new GenericRepository<EFClientStatistics>();
|
||||
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId);
|
||||
IList<EFClientStatistics> clientStats;
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
clientStats = await ctx.Set<EFClientStatistics>()
|
||||
.Include(c => c.HitLocations)
|
||||
.Where(c => c.ClientId == clientId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
double headRatio = 0;
|
||||
double chestRatio = 0;
|
||||
@ -227,7 +244,8 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
new ProfileMeta()
|
||||
{
|
||||
Key = "Hit Offset Average",
|
||||
Value = $"{Math.Round(((float)hitOffsetAverage), 4)}°",
|
||||
// todo: make sure this is wrapped somewhere else
|
||||
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
|
||||
Sensitive = true
|
||||
},
|
||||
new ProfileMeta()
|
||||
@ -241,19 +259,24 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
|
||||
async Task<List<ProfileMeta>> getMessages(int clientId)
|
||||
{
|
||||
var messageSvc = new GenericRepository<EFClientMessage>();
|
||||
var messages = await messageSvc.FindAsync(m => m.ClientId == clientId);
|
||||
var messageMeta = messages.Select(m => new ProfileMeta()
|
||||
List<ProfileMeta> messageMeta;
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
Key = "EventMessage",
|
||||
Value = m.Message,
|
||||
When = m.TimeSent,
|
||||
Extra = m.ServerId.ToString()
|
||||
}).ToList();
|
||||
var messages = ctx.Set<EFClientMessage>().Where(m => m.ClientId == clientId);
|
||||
|
||||
messageMeta = await messages.Select(m => new ProfileMeta()
|
||||
{
|
||||
Key = "EventMessage",
|
||||
Value = m.Message,
|
||||
When = m.TimeSent,
|
||||
Extra = m.ServerId.ToString()
|
||||
}).ToListAsync();
|
||||
}
|
||||
|
||||
messageMeta.Add(new ProfileMeta()
|
||||
{
|
||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
|
||||
Value = messages.Count
|
||||
Value = messageMeta.Count
|
||||
});
|
||||
|
||||
return messageMeta;
|
||||
@ -270,16 +293,20 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
|
||||
string totalKills(Server server)
|
||||
{
|
||||
var serverStats = new GenericRepository<EFServerStatistics>();
|
||||
return serverStats.Find(s => s.Active)
|
||||
.Sum(c => c.TotalKills).ToString("#,##0");
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
long kills = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalKills);
|
||||
return kills.ToString("#,##0");
|
||||
}
|
||||
}
|
||||
|
||||
string totalPlayTime(Server server)
|
||||
{
|
||||
var serverStats = new GenericRepository<EFServerStatistics>();
|
||||
return Math.Ceiling((serverStats.GetQuery(s => s.Active)
|
||||
.Sum(c => c.TotalPlayTime) / 3600.0)).ToString("#,##0");
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
long playTime = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalPlayTime);
|
||||
return (playTime / 3600.0).ToString("#,##0");
|
||||
}
|
||||
}
|
||||
|
||||
string topStats(Server s)
|
||||
@ -302,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.Stats</PackageId>
|
||||
@ -26,7 +27,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" />
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -40,11 +40,16 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
|
||||
where message.TimeSent <= whenUpper
|
||||
select new SharedLibraryCore.Dtos.ChatInfo()
|
||||
{
|
||||
ClientId = message.ClientId,
|
||||
Message = message.Message,
|
||||
Name = message.Client.CurrentAlias.Name,
|
||||
Time = message.TimeSent
|
||||
};
|
||||
|
||||
#if DEBUG == true
|
||||
var messagesSql = iqMessages.ToSql();
|
||||
#endif
|
||||
|
||||
var messages = await iqMessages.ToListAsync();
|
||||
|
||||
return View("_MessageContext", messages);
|
||||
|
@ -7,6 +7,6 @@
|
||||
<h5>@Model.First().Time.ToString()</h5>
|
||||
@foreach (var message in Model)
|
||||
{
|
||||
<span class="text-white">@message.Name</span><span> — @message.Message</span><br />
|
||||
<span class="text-white">@Html.ActionLink(@message.Name, "ProfileAsync", "Client", new { id = message.ClientId})</span><span> — @message.Message</span><br />
|
||||
}
|
||||
</div>
|
@ -1,17 +1,32 @@
|
||||
using SharedLibraryCore.Objects;
|
||||
using IW4MAdmin.Application;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Xunit;
|
||||
|
||||
namespace Tests
|
||||
{
|
||||
[Collection("ManagerCollection")]
|
||||
public class ClientTests
|
||||
{
|
||||
readonly ApplicationManager Manager;
|
||||
const int TestTimeout = 10000;
|
||||
|
||||
public ClientTests(ManagerFixture fixture)
|
||||
{
|
||||
Manager = fixture.Manager;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetAdditionalPropertyShouldSucceed()
|
||||
{
|
||||
var client = new Player();
|
||||
var client = new EFClient();
|
||||
int newProp = 5;
|
||||
client.SetAdditionalProperty("NewProp", newProp);
|
||||
}
|
||||
@ -19,11 +34,231 @@ namespace Tests
|
||||
[Fact]
|
||||
public void GetAdditionalPropertyShouldSucceed()
|
||||
{
|
||||
var client = new Player();
|
||||
var client = new EFClient();
|
||||
int newProp = 5;
|
||||
client.SetAdditionalProperty("NewProp", newProp);
|
||||
|
||||
Assert.True(client.GetAdditionalProperty<int>("NewProp") == 5, "added property does not match retrieved property");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WarnClientShouldSucceed()
|
||||
{
|
||||
while (!Manager.IsInitialized)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
|
||||
|
||||
Assert.False(client == null, "no client found to warn");
|
||||
|
||||
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 ||
|
||||
warnEvent.Failed) &&
|
||||
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 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 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 EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
|
||||
Assert.True(!warnClearEvent.Failed && client.Warnings == 0, "warning was not cleared");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportClientShouldSucceed()
|
||||
{
|
||||
while (!Manager.IsInitialized)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
|
||||
Assert.False(client == null, "no client found to report");
|
||||
|
||||
// fail
|
||||
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);
|
||||
|
||||
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Throttle &
|
||||
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 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 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,
|
||||
$"report was applied without proper permission [{reportEvent.FailReason.ToString()},{ client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId)}]");
|
||||
|
||||
// fail
|
||||
reportEvent = client.Report("test report", client);
|
||||
|
||||
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Invalid &&
|
||||
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was applied to self");
|
||||
|
||||
// fail
|
||||
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");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FlagClientShouldSucceed()
|
||||
{
|
||||
while (!Manager.IsInitialized)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
|
||||
Assert.False(client == null, "no client found to flag");
|
||||
|
||||
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 == 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 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 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 EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
|
||||
unflagEvent.OnProcessed.Wait();
|
||||
|
||||
// fail
|
||||
Assert.False(client.Level == EFClient.Permission.User, "user was unflagged without permissions");
|
||||
|
||||
unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
unflagEvent.OnProcessed.Wait();
|
||||
|
||||
// succeed
|
||||
Assert.True(client.Level == EFClient.Permission.User, "user was not unflagged");
|
||||
|
||||
unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
unflagEvent.OnProcessed.Wait();
|
||||
|
||||
// succeed
|
||||
Assert.True(unflagEvent.FailReason == GameEvent.EventFailReason.Invalid, "user was not flagged");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
void KickClientShouldSucceed()
|
||||
{
|
||||
while (!Manager.IsInitialized)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
|
||||
Assert.False(client == null, "no client found to kick");
|
||||
|
||||
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 EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
|
||||
kickEvent.OnProcessed.Wait();
|
||||
|
||||
Assert.True(Manager.Servers.First().GetClientsAsList().FirstOrDefault(c => c.NetworkId == client.NetworkId) == null, "client was not kicked");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
void TempBanClientShouldSucceed()
|
||||
{
|
||||
while (!Manager.IsInitialized)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
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 EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
|
||||
Target = client,
|
||||
Data = "5days test tempban",
|
||||
Type = GameEvent.EventType.Command,
|
||||
Owner = client.CurrentServer
|
||||
}).Wait();
|
||||
|
||||
Assert.True(Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId).Result.Count(p => p.Type == Penalty.PenaltyType.TempBan) == 1,
|
||||
"tempban was not added");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
void BanUnbanClientShouldSucceed()
|
||||
{
|
||||
while (!Manager.IsInitialized)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
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 EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
|
||||
Target = client,
|
||||
Data = "test ban",
|
||||
Type = GameEvent.EventType.Command,
|
||||
Owner = client.CurrentServer
|
||||
}).Wait();
|
||||
|
||||
Assert.True(Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId).Result.Count(p => p.Type == Penalty.PenaltyType.Ban) == 1,
|
||||
"ban was not added");
|
||||
|
||||
var unbanCommand = new CUnban();
|
||||
unbanCommand.ExecuteAsync(new GameEvent()
|
||||
{
|
||||
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
|
||||
}).Wait();
|
||||
|
||||
Assert.True(Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId).Result.Count(p => p.Type == Penalty.PenaltyType.Ban) == 0,
|
||||
"ban was not removed");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,11 @@ namespace Tests
|
||||
|
||||
public ManagerFixture()
|
||||
{
|
||||
File.WriteAllText("test_mp.log", "test_log_file");
|
||||
|
||||
File.WriteAllText("test_mp.log", "TEST_LOG_FILE");
|
||||
IW4MAdmin.Application.Localization.Configure.Initialize("en-US");
|
||||
|
||||
Manager = Program.ServerManager;
|
||||
Manager = ApplicationManager.GetInstance();
|
||||
|
||||
var config = new ApplicationConfiguration
|
||||
{
|
||||
@ -32,7 +33,7 @@ namespace Tests
|
||||
Password = "test",
|
||||
Port = 28963,
|
||||
Rules = new List<string>(),
|
||||
ManualLogPath = "https://raidmax.org/IW4MAdmin/getlog.php"
|
||||
ManualLogPath = "http://google.com"
|
||||
}
|
||||
},
|
||||
AutoMessages = new List<string>(),
|
||||
@ -40,10 +41,11 @@ namespace Tests
|
||||
Maps = new List<MapConfiguration>(),
|
||||
RConPollRate = 10000
|
||||
};
|
||||
Manager.ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("Test.json");
|
||||
Manager.ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("test.json");
|
||||
Manager.ConfigHandler.Set(config);
|
||||
|
||||
Manager.Init().Wait();
|
||||
|
||||
Task.Run(() => Manager.Start());
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
@ -188,6 +189,22 @@ namespace Tests
|
||||
|
||||
resetEvent.Wait(5000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrintCommands()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("|Name |Alias|Description |Requires Target|Syntax |Required Level|");
|
||||
sb.AppendLine("|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|");
|
||||
|
||||
foreach (var command in Manager.GetCommands().OrderByDescending(c => c.Permission).ThenBy(c => c.Name))
|
||||
{
|
||||
sb.AppendLine($"|{command.Name}|{command.Alias}|{command.Description}|{command.RequiresTarget}|{command.Syntax.Substring(8).EscapeMarkdown()}|{command.Permission}|");
|
||||
}
|
||||
|
||||
Assert.True(false, sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
@ -22,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" />
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,142 +0,0 @@
|
||||
/* CountryLookup.cs
|
||||
*
|
||||
* Copyright (C) 2008 MaxMind, Inc. All Rights Reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
using SharedLibraryCore;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
|
||||
|
||||
namespace CountryLookupProj
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for CountryLookup.
|
||||
/// </summary>
|
||||
public class CountryLookup
|
||||
{
|
||||
private FileStream fileInput;
|
||||
private static long COUNTRY_BEGIN = 16776960;
|
||||
private static string[] countryCode =
|
||||
{ "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN","AO","AQ","AR","AS","AT","AU","AW","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO","DZ",
|
||||
"EC","EE","EG","EH","ER","ES","ET","FI","FJ","FK","FM","FO","FR","FX","GA","GB","GD","GE","GF","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ",
|
||||
"LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA",
|
||||
"RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH","TJ","TK","TM","TN","TO","TL","TR","TT","TV","TW","TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE","YT","RS","ZA","ZM","ME","ZW","A1","A2",
|
||||
"O1","AX","GG","IM","JE","BL","MF"
|
||||
};
|
||||
private static string[] countryName =
|
||||
{"a third world country","Asia/Pacific Region","Europe","Andorra","United Arab Emirates","Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia","Netherlands Antilles","Angola","Antarctica","Argentina","American Samoa","Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina","Barbados","Bangladesh","Belgium",
|
||||
"Burkina Faso","Bulgaria","Bahrain","Burundi","Benin","Bermuda","Brunei Darussalam","Bolivia","Brazil","Bahamas","Bhutan","Bouvet Island","Botswana","Belarus","Belize","Canada","Cocos (Keeling) Islands","Congo, The Democratic Republic of the","Central African Republic","Congo","Switzerland","Cote D'Ivoire",
|
||||
"Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica","Cuba","Cape Verde","Christmas Island","Cyprus","Czech Republic","Germany","Djibouti","Denmark","Dominica","Dominican Republic","Algeria","Ecuador","Estonia","Egypt","Western Sahara","Eritrea","Spain","Ethiopia","Finland","Fiji","Falkland Islands (Malvinas)",
|
||||
"Micronesia, Federated States of","Faroe Islands","France","France, Metropolitan","Gabon","United Kingdom","Grenada","Georgia","French Guiana","Ghana","Gibraltar","Greenland","Gambia","Guinea","Guadeloupe","Equatorial Guinea","Greece","South Georgia and the South Sandwich Islands","Guatemala","Guam","Guinea-Bissau","Guyana",
|
||||
"Hong Kong","Heard Island and McDonald Islands","Honduras","Croatia","Haiti","Hungary","Indonesia","Ireland","Israel","India","British Indian Ocean Territory","Iraq","Iran, Islamic Republic of","Iceland","Italy","Jamaica","Jordan","Japan","Kenya","Kyrgyzstan","Cambodia","Kiribati","Comoros","Saint Kitts and Nevis",
|
||||
"Korea, Democratic People's Republic of","Korea, Republic of","Kuwait","Cayman Islands","Kazakstan","Lao People's Democratic Republic","Lebanon","Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho","Lithuania","Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco","Monaco","Moldova, Republic of","Madagascar",
|
||||
"Marshall Islands","Macedonia","Mali","Myanmar","Mongolia","Macau","Northern Mariana Islands","Martinique","Mauritania","Montserrat","Malta","Mauritius","Maldives","Malawi","Mexico","Malaysia","Mozambique","Namibia","New Caledonia","Niger","Norfolk Island","Nigeria","Nicaragua","Netherlands",
|
||||
"Norway","Nepal","Nauru","Niue","New Zealand","Oman","Panama","Peru","French Polynesia","Papua New Guinea","Philippines","Pakistan","Poland","Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","Palestinian Territory","Portugal","Palau","Paraguay","Qatar","Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia",
|
||||
"Solomon Islands","Seychelles","Sudan","Sweden","Singapore","Saint Helena","Slovenia","Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino","Senegal","Somalia","Suriname","Sao Tome and Principe","El Salvador","Syrian Arab Republic","Swaziland","Turks and Caicos Islands","Chad","French Southern Territories","Togo",
|
||||
"Thailand","Tajikistan","Tokelau","Turkmenistan","Tunisia","Tonga","Timor-Leste","Turkey","Trinidad and Tobago","Tuvalu","Taiwan","Tanzania, United Republic of","Ukraine","Uganda","United States Minor Outlying Islands","United States","Uruguay","Uzbekistan","Holy See (Vatican City State)","Saint Vincent and the Grenadines",
|
||||
"Venezuela","Virgin Islands, British","Virgin Islands, U.S.","Vietnam","Vanuatu","Wallis and Futuna","Samoa","Yemen","Mayotte","Serbia","South Africa","Zambia","Montenegro","Zimbabwe","Anonymous Proxy","Satellite Provider",
|
||||
"Other","Aland Islands","Guernsey","Isle of Man","Jersey","Saint Barthelemy","Saint Martin"};
|
||||
|
||||
|
||||
public CountryLookup(string fileName)
|
||||
{
|
||||
fileInput = new FileStream(fileName, FileMode.Open, FileAccess.Read);
|
||||
}
|
||||
|
||||
private long AddrToNum(IPAddress addr)
|
||||
{
|
||||
long ipnum = 0;
|
||||
byte[] b = BitConverter.GetBytes((UInt32)addr.ToString().ConvertToIP());
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
long y = b[i];
|
||||
if (y < 0)
|
||||
{
|
||||
y += 256;
|
||||
}
|
||||
ipnum += y << ((3 - i) * 8);
|
||||
}
|
||||
return ipnum;
|
||||
}
|
||||
|
||||
public string LookupCountryCode(IPAddress addr)
|
||||
{
|
||||
return (countryCode[(int)SeekCountry(0, AddrToNum(addr), 31)]);
|
||||
}
|
||||
|
||||
public string LookupCountryName(string str)
|
||||
{
|
||||
IPAddress addr;
|
||||
try
|
||||
{
|
||||
addr = IPAddress.Parse(str);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return "a third world country";
|
||||
}
|
||||
return LookupCountryName(addr);
|
||||
}
|
||||
|
||||
public string LookupCountryName(IPAddress addr)
|
||||
{
|
||||
return (countryName[(int)SeekCountry(0, AddrToNum(addr), 31)]);
|
||||
}
|
||||
|
||||
private long SeekCountry(long offset, long ipnum, int depth)
|
||||
{
|
||||
byte[] buf = new byte[6];
|
||||
long[] x = new long[2];
|
||||
|
||||
fileInput.Seek(6 * offset, 0);
|
||||
fileInput.Read(buf, 0, 6);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
x[i] = 0;
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
int y = buf[i * 3 + j];
|
||||
if (y < 0)
|
||||
{
|
||||
y += 256;
|
||||
}
|
||||
x[i] += (y << (j * 8));
|
||||
}
|
||||
}
|
||||
|
||||
if ((ipnum & (1 << depth)) > 0)
|
||||
{
|
||||
if (x[1] >= COUNTRY_BEGIN)
|
||||
{
|
||||
return x[1] - COUNTRY_BEGIN;
|
||||
}
|
||||
return SeekCountry(x[1], ipnum, depth - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x[0] >= COUNTRY_BEGIN)
|
||||
{
|
||||
return x[0] - COUNTRY_BEGIN;
|
||||
}
|
||||
return SeekCountry(x[0], ipnum, depth - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user