Compare commits

..

37 Commits

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

5
.gitignore vendored
View File

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

View File

@ -8,9 +8,13 @@ namespace IW4MAdmin.Application.API.Master
public class ApiServer
{
[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")]

View File

@ -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()
};

View File

@ -6,12 +6,12 @@
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.2</Version>
<Version>2.2.4.4</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>
@ -31,8 +31,8 @@
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.0.0</AssemblyVersion>
<FileVersion>2.2.0.0</FileVersion>
<AssemblyVersion>2.2.4.4</AssemblyVersion>
<FileVersion>2.2.4.4</FileVersion>
</PropertyGroup>
<ItemGroup>

View File

@ -1,24 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using System.Reflection;
using IW4MAdmin.Application.API.Master;
using IW4MAdmin.Application.EventParsers;
using IW4MAdmin.Application.RconParsers;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Services;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Events;
using IW4MAdmin.Application.API.Master;
using IW4MAdmin.Application.Migration;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application
{
@ -26,7 +25,7 @@ namespace IW4MAdmin.Application
{
private List<Server> _servers;
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
public Dictionary<int, Player> PrivilegedClients { get; set; }
public Dictionary<int, EFClient> PrivilegedClients { get; set; }
public ILogger Logger => GetLogger(0);
public bool Running { get; private set; }
public bool IsInitialized { get; private set; }
@ -37,6 +36,9 @@ namespace IW4MAdmin.Application
public DateTime StartTime { get; private set; }
public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString();
public IList<IRConParser> AdditionalRConParsers { get; }
public IList<IEventParser> AdditionalEventParsers { get; }
static ApplicationManager Instance;
readonly List<AsyncStatus> TaskStatuses;
List<Command> Commands;
@ -49,7 +51,7 @@ namespace IW4MAdmin.Application
ManualResetEventSlim OnQuit;
readonly IPageList PageList;
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
readonly Dictionary<int, ILogger> Loggers = new Dictionary<int, ILogger>();
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
private ApplicationManager()
{
@ -60,11 +62,12 @@ namespace IW4MAdmin.Application
ClientSvc = new ClientService();
AliasSvc = new AliasService();
PenaltySvc = new PenaltyService();
PrivilegedClients = new Dictionary<int, Player>();
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
StartTime = DateTime.UtcNow;
OnQuit = new ManualResetEventSlim();
PageList = new PageList();
AdditionalEventParsers = new List<IEventParser>();
AdditionalRConParsers = new List<IRConParser>();
OnServerEvent += OnGameEvent;
OnServerEvent += EventApi.OnGameEvent;
}
@ -85,80 +88,11 @@ namespace IW4MAdmin.Application
try
{
// if the origin client is not in an authorized state (detected by RCon) don't execute the event
if (GameEvent.ShouldOriginEventBeDelayed(newEvent))
{
Logger.WriteDebug($"Delaying origin execution of event type {newEvent.Type} for {newEvent.Origin} because they are not authed");
if (newEvent.Type == GameEvent.EventType.Command)
{
newEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_DELAYED_EVENT_WAIT"]);
}
await newEvent.Owner.ExecuteEvent(newEvent);
// offload it to the player to keep
newEvent.Origin.DelayedEvents.Enqueue(newEvent);
}
// if the target client is not in an authorized state (detected by RCon) don't execute the event
else if (GameEvent.ShouldTargetEventBeDelayed(newEvent))
{
Logger.WriteDebug($"Delaying target execution of event type {newEvent.Type} for {newEvent.Target} because they are not authed");
// offload it to the player to keep
newEvent.Target.DelayedEvents.Enqueue(newEvent);
}
else
{
await newEvent.Owner.ExecuteEvent(newEvent);
// save the event info to the database
var changeHistorySvc = new ChangeHistoryService();
await changeHistorySvc.Add(args.Event);
// todo: this is a hacky mess
if (newEvent.Origin?.DelayedEvents.Count > 0 &&
(//newEvent.Origin?.State == Player.ClientState.Connected ||
newEvent.Type == GameEvent.EventType.Connect))
{
var events = newEvent.Origin.DelayedEvents;
// add the delayed event to the queue
while (events.Count > 0)
{
var oldEvent = events.Dequeue();
var e = new GameEvent()
{
Type = oldEvent.Type,
Origin = newEvent.Origin,
Data = oldEvent.Data,
Extra = oldEvent.Extra,
Owner = oldEvent.Owner,
Message = oldEvent.Message,
Target = oldEvent.Target,
Remote = oldEvent.Remote
};
e.Origin = newEvent.Origin;
// check if the target was assigned
if (e.Target != null)
{
// update the target incase they left or have newer info
e.Target = newEvent.Owner.GetPlayersAsList()
.FirstOrDefault(p => p.NetworkId == e.Target.NetworkId);
// we have to throw out the event because they left
if (e.Target == null)
{
Logger.WriteWarning($"Delayed event for {e.Origin} was ignored because the target has left");
// hack: don't do anything with the event because the target is invalid
e.Type = GameEvent.EventType.Unknown;
}
}
Logger.WriteDebug($"Adding delayed event of type {e.Type} for {e.Origin} back for processing");
this.GetEventHandler().AddEvent(e);
}
}
}
// save the event info to the database
var changeHistorySvc = new ChangeHistoryService();
await changeHistorySvc.Add(args.Event);
#if DEBUG
Logger.WriteDebug($"Processed event with id {newEvent.Id}");
@ -192,7 +126,7 @@ namespace IW4MAdmin.Application
Logger.WriteDebug(ex.GetExceptionInfo());
}
skip:
skip:
// tell anyone waiting for the output that we're done
newEvent.OnProcessed.Set();
@ -216,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)
{
@ -236,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
{
@ -265,11 +199,7 @@ namespace IW4MAdmin.Application
ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
#endif
#if DEBUG
await Task.Delay(10000);
#else
await Task.Delay(ConfigHandler.Configuration().RConPollRate);
#endif
}
// trigger the event processing loop to end
@ -280,40 +210,21 @@ namespace IW4MAdmin.Application
{
Running = true;
#region DATABASE
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString, GetApplicationSettings().Configuration()?.DatabaseProvider))
{
await new ContextSeed(db).Seed();
}
// todo: optimize this (or replace it)
var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
.Select(c => new
{
c.Password,
c.PasswordSalt,
c.ClientId,
c.Level,
c.Name
});
#region PLUGINS
SharedLibraryCore.Plugins.PluginImporter.Load(this);
foreach (var a in ipList)
foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
{
try
{
PrivilegedClients.Add(a.ClientId, new Player()
{
Name = a.Name,
ClientId = a.ClientId,
Level = a.Level,
PasswordSalt = a.PasswordSalt,
Password = a.Password
});
await Plugin.OnLoadAsync(this);
}
catch (ArgumentException)
catch (Exception ex)
{
continue;
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
Logger.WriteDebug(ex.GetExceptionInfo());
}
}
#endregion
@ -340,7 +251,18 @@ namespace IW4MAdmin.Application
do
{
newConfig.Servers.Add((ServerConfiguration)new ServerConfiguration().Generate());
var serverConfig = new ServerConfiguration();
foreach (var parser in AdditionalRConParsers)
{
serverConfig.AddRConParser(parser);
}
foreach (var parser in AdditionalEventParsers)
{
serverConfig.AddEventParser(parser);
}
newConfig.Servers.Add((ServerConfiguration)serverConfig.Generate());
} while (Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["SETUP_SERVER_SAVE"]));
config = newConfig;
@ -364,33 +286,30 @@ namespace IW4MAdmin.Application
}
else if (config.Servers.Count == 0)
{
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
}
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252");
#endregion
#region PLUGINS
SharedLibraryCore.Plugins.PluginImporter.Load(this);
foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
#region DATABASE
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString,
GetApplicationSettings().Configuration()?.DatabaseProvider))
{
try
{
await Plugin.OnLoadAsync(this);
}
catch (Exception ex)
{
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
Logger.WriteDebug(ex.GetExceptionInfo());
}
await new ContextSeed(db).Seed();
}
PrivilegedClients = (await ClientSvc.GetPrivilegedClients()).ToDictionary(_client => _client.ClientId);
#endregion
#region COMMANDS
if (ClientSvc.GetOwners().Result.Count == 0)
{
Commands.Add(new COwner());
}
Commands.Add(new CQuit());
Commands.Add(new CKick());
@ -431,7 +350,9 @@ namespace IW4MAdmin.Application
Commands.Add(new CNextMap());
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
{
Commands.Add(C);
}
#endregion
#region INIT
@ -466,7 +387,9 @@ namespace IW4MAdmin.Application
{
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"]} [{Conf.IPAddress}:{Conf.Port}]");
if (e.GetType() == typeof(DvarException))
{
Logger.WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"]} {(e as DvarException).Data["dvar_name"]} ({Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR_HELP"]})");
}
else if (e.GetType() == typeof(NetworkException))
{
Logger.WriteDebug(e.Message);
@ -565,7 +488,7 @@ namespace IW4MAdmin.Application
Running = false;
}
public ILogger GetLogger(int serverId)
public ILogger GetLogger(long serverId)
{
if (Loggers.ContainsKey(serverId))
{
@ -595,23 +518,69 @@ namespace IW4MAdmin.Application
return MessageTokens;
}
public IList<Player> GetActiveClients() => _servers.SelectMany(s => s.Players).Where(p => p != null).ToList();
public IList<EFClient> GetActiveClients()
{
return _servers.SelectMany(s => s.Clients).Where(p => p != null).ToList();
}
public ClientService GetClientService() => ClientSvc;
public AliasService GetAliasService() => AliasSvc;
public PenaltyService GetPenaltyService() => PenaltySvc;
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings() => ConfigHandler;
public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients;
public bool ShutdownRequested() => !Running;
public IEventHandler GetEventHandler() => Handler;
public ClientService GetClientService()
{
return ClientSvc;
}
public AliasService GetAliasService()
{
return AliasSvc;
}
public PenaltyService GetPenaltyService()
{
return PenaltySvc;
}
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings()
{
return ConfigHandler;
}
public IDictionary<int, EFClient> GetPrivilegedClients()
{
return PrivilegedClients;
}
public bool ShutdownRequested()
{
return !Running;
}
public IEventHandler GetEventHandler()
{
return Handler;
}
public void SetHasEvent()
{
OnQuit.Set();
}
public IList<Assembly> GetPluginAssemblies() => SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies;
public IList<Assembly> GetPluginAssemblies()
{
return SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies;
}
public IPageList GetPageList() => PageList;
public IPageList GetPageList()
{
return PageList;
}
public IRConParser GenerateDynamicRConParser()
{
return new DynamicRConParser();
}
public IEventParser GenerateDynamicEventParser()
{
return new DynamicEventParser();
}
}
}

View File

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

View File

@ -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
};
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,6 @@
using SharedLibraryCore;
using SharedLibraryCore.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;

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@ namespace IW4MAdmin.Application.Localization
{
public class Configure
{
public static void Initialize(string customLocale)
public static void Initialize(string customLocale = null)
{
string currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale;
string[] localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");

View File

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

View 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;
}
}
}

View File

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

View File

@ -0,0 +1,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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {
}
};

View 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) {
}
};

View File

@ -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>

View File

@ -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\">

View File

@ -5,26 +5,27 @@ import time
class LogReader(object):
def __init__(self):
self.log_file_sizes = {}
# (if the file changes more than this, ignore ) - 1 MB
self.max_file_size_change = 1000000
# (if the file changes more than this, ignore ) - 0.125 MB
self.max_file_size_change = 125000
# (if the time between checks is greater, ignore ) - 5 minutes
self.max_file_time_change = 1000
self.max_file_time_change = 60
def read_file(self, path):
# prevent traversing directories
if re.search('r^.+\.\.\\.+$', path):
return False
# must be a valid log path and log file
if not re.search(r'^.+[\\|\/](userraw|mods)[\\|\/].+.log$', path):
if not re.search(r'^.+[\\|\/](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

View File

@ -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
}

View File

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

View File

@ -1,9 +1,11 @@
from flask import Flask
from flask_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')

View File

@ -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

View File

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

View File

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.16
@ -39,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

View File

@ -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>
@ -111,18 +111,18 @@
<Folder Include="master\templates\" />
</ItemGroup>
<ItemGroup>
<None Include="FolderProfile.pubxml" />
<Content Include="master\config\master.json" />
<Content Include="master\templates\serverlist.html" />
<None Include="Release.pubxml" />
<Content Include="requirements.txt" />
<Content Include="master\templates\index.html" />
<Content Include="master\templates\layout.html" />
</ItemGroup>
<ItemGroup>
<Interpreter Include="dev_env\">
<Id>dev_env</Id>
<Interpreter Include="env_master\">
<Id>env_master</Id>
<Version>3.6</Version>
<Description>dev_env (Python 3.6 (64-bit))</Description>
<Description>env_master (Python 3.6 (64-bit))</Description>
<InterpreterPath>Scripts\python.exe</InterpreterPath>
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>

View File

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

View File

@ -23,7 +23,10 @@ class Instance(Resource):
def put(self, id):
try:
for server in request.json['servers']:
server['ip'] = request.remote_addr
if 'ip' not in server or 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
@ -34,7 +37,10 @@ class Instance(Resource):
def post(self):
try:
for server in request.json['servers']:
server['ip'] = request.remote_addr
if 'ip' not in server or server['ip'] == 'localhost':
server['ip'] = request.remote_addr
if 'version' not in server:
server['version'] = 'Unknown'
instance = InstanceSchema().load(request.json)
except ValidationError as err:
return {'message' : err.messages }, 400

View File

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

View File

@ -1,16 +1,26 @@
aniso8601==3.0.0
aniso8601==3.0.2
APScheduler==3.5.3
certifi==2018.10.15
chardet==3.0.4
click==6.7
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

View File

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

View File

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

View File

@ -6,6 +6,7 @@
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -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)
{

View File

@ -1,4 +1,5 @@
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
@ -8,7 +9,7 @@ namespace IW4MAdmin.Plugins.Login.Commands
{
public class CLogin : Command
{
public CLogin() : base("login", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_DESC"], "li", Player.Permission.Trusted, false, new CommandArgument[]
public CLogin() : base("login", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_DESC"], "li", EFClient.Permission.Trusted, false, new CommandArgument[]
{
new CommandArgument()
{

View File

@ -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;

View File

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

View File

@ -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;

View 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) {
}
};

View 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) {
}
};

View 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) {
}
};

View File

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

View File

@ -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;
}

View File

@ -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;

View File

@ -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)
{

View File

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

View File

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

View File

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

View File

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

View File

@ -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; }

View File

@ -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; }

View File

@ -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]

View File

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

View File

@ -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]

View File

@ -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; }
}
}

View File

@ -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; }

View File

@ -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
{

View File

@ -1,20 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Reflection;
using IW4MAdmin.Plugins.Stats.Config;
using IW4MAdmin.Plugins.Stats.Helpers;
using IW4MAdmin.Plugins.Stats.Models;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Config;
using IW4MAdmin.Plugins.Stats.Helpers;
using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats
{
@ -44,16 +43,19 @@ namespace IW4MAdmin.Plugins.Stats
break;
case GameEvent.EventType.Disconnect:
await Manager.RemovePlayer(E.Origin);
await Manager.Sync(S);
break;
case GameEvent.EventType.Say:
if (!string.IsNullOrEmpty(E.Data) &&
E.Origin.ClientId > 1)
await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data);
{
await Manager.AddMessageAsync(E.Origin.ClientId, await StatManager.GetIdForServer(E.Owner), E.Data);
}
break;
case GameEvent.EventType.MapChange:
Manager.SetTeamBased(E.Owner.GetHashCode(), E.Owner.Gametype != "dm");
Manager.ResetKillstreaks(S.GetHashCode());
await Manager.Sync(S);
Manager.SetTeamBased(await StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm");
Manager.ResetKillstreaks(await StatManager.GetIdForServer(E.Owner));
break;
case GameEvent.EventType.MapEnd:
break;
@ -77,7 +79,7 @@ namespace IW4MAdmin.Plugins.Stats
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 14)
{
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8],
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
}
break;
@ -90,14 +92,14 @@ namespace IW4MAdmin.Plugins.Stats
case GameEvent.EventType.Damage:
if (!E.Owner.CustomCallback)
{
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, E.Owner.GetHashCode());
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, await StatManager.GetIdForServer(E.Owner));
}
break;
case GameEvent.EventType.ScriptDamage:
killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 14)
{
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8],
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
}
break;
@ -143,7 +145,7 @@ namespace IW4MAdmin.Plugins.Stats
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
Value = "#" + await Manager.GetClientOverallRanking(clientId),
Value = "#" + await StatManager.GetClientOverallRanking(clientId),
},
new ProfileMeta()
{
@ -327,12 +329,17 @@ namespace IW4MAdmin.Plugins.Stats
Manager = new StatManager(manager);
}
public Task OnTickAsync(Server S) => Task.CompletedTask;
public Task OnTickAsync(Server S)
{
return Task.CompletedTask;
}
public async Task OnUnloadAsync()
{
foreach (var sv in ServerManager.GetServers())
{
await Manager.Sync(sv);
}
}
}
}

View File

@ -1,6 +1,7 @@
using IW4MAdmin.Application;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
@ -25,7 +26,7 @@ namespace Tests
[Fact]
public void SetAdditionalPropertyShouldSucceed()
{
var client = new Player();
var client = new EFClient();
int newProp = 5;
client.SetAdditionalProperty("NewProp", newProp);
}
@ -33,7 +34,7 @@ namespace Tests
[Fact]
public void GetAdditionalPropertyShouldSucceed()
{
var client = new Player();
var client = new EFClient();
int newProp = 5;
client.SetAdditionalProperty("NewProp", newProp);
@ -48,11 +49,11 @@ namespace Tests
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to warn");
var warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
var warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait();
Assert.True((client.Warnings == 1 ||
@ -60,19 +61,19 @@ namespace Tests
Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1,
"warning did not get applied");
warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait();
Assert.True(warnEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was applied without proper permissions");
// warn clear
var warnClearEvent = client.WarnClear(new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
var warnClearEvent = client.WarnClear(new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
Assert.True(warnClearEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was removed without proper permissions");
warnClearEvent = client.WarnClear(new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
warnClearEvent = client.WarnClear(new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
Assert.True(!warnClearEvent.Failed && client.Warnings == 0, "warning was not cleared");
}
@ -85,11 +86,11 @@ namespace Tests
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to report");
// fail
var player = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer };
var player = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer };
player.SetAdditionalProperty("_reportCount", 3);
var reportEvent = client.Report("test report", player);
reportEvent.OnProcessed.Wait(TestTimeout);
@ -98,14 +99,14 @@ namespace Tests
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 0, $"too many reports were applied [{reportEvent.FailReason.ToString()}]");
// succeed
reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
reportEvent = client.Report("test report", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
reportEvent.OnProcessed.Wait(TestTimeout);
Assert.True(!reportEvent.Failed &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was not applied [{reportEvent.FailReason.ToString()}]");
// fail
reportEvent = client.Report("test report", new Player() { ClientId = 1, NetworkId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
reportEvent = client.Report("test report", new EFClient() { ClientId = 1, NetworkId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1,
@ -118,7 +119,7 @@ namespace Tests
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was applied to self");
// fail
reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
reportEvent = client.Report("test report", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Exception &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"duplicate report was applied");
@ -132,42 +133,42 @@ namespace Tests
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to flag");
var flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
var flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait();
// succeed
Assert.True(!flagEvent.Failed &&
client.Level == Player.Permission.Flagged, $"player is not flagged [{flagEvent.FailReason.ToString()}]");
client.Level == EFClient.Permission.Flagged, $"player is not flagged [{flagEvent.FailReason.ToString()}]");
Assert.False(client.ReceivedPenalties.FirstOrDefault(p => p.Offense == "test flag") == null, "flag was not applied");
flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait();
// fail
Assert.True(client.ReceivedPenalties.Count == 1, "flag was applied without permisions");
flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait();
// fail
Assert.True(client.ReceivedPenalties.Count == 1, "duplicate flag was applied");
var unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
var unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait();
// fail
Assert.False(client.Level == Player.Permission.User, "user was unflagged without permissions");
Assert.False(client.Level == EFClient.Permission.User, "user was unflagged without permissions");
unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait();
// succeed
Assert.True(client.Level == Player.Permission.User, "user was not unflagged");
Assert.True(client.Level == EFClient.Permission.User, "user was not unflagged");
unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait();
// succeed
@ -182,18 +183,18 @@ namespace Tests
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to kick");
var kickEvent = client.Kick("test kick", new Player() { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
var kickEvent = client.Kick("test kick", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
kickEvent.OnProcessed.Wait();
Assert.True(kickEvent.FailReason == GameEvent.EventFailReason.Permission, "client was kicked without permission");
kickEvent = client.Kick("test kick", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
kickEvent = client.Kick("test kick", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
kickEvent.OnProcessed.Wait();
Assert.True(Manager.Servers.First().GetPlayersAsList().FirstOrDefault(c => c.NetworkId == client.NetworkId) == null, "client was not kicked");
Assert.True(Manager.Servers.First().GetClientsAsList().FirstOrDefault(c => c.NetworkId == client.NetworkId) == null, "client was not kicked");
}
[Fact]
@ -204,13 +205,13 @@ namespace Tests
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to tempban");
var tbCommand = new CTempBan();
tbCommand.ExecuteAsync(new GameEvent()
{
Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer },
Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
Target = client,
Data = "5days test tempban",
Type = GameEvent.EventType.Command,
@ -229,13 +230,13 @@ namespace Tests
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to ban");
var banCommand = new CBan();
banCommand.ExecuteAsync(new GameEvent()
{
Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer },
Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
Target = client,
Data = "test ban",
Type = GameEvent.EventType.Command,
@ -248,8 +249,8 @@ namespace Tests
var unbanCommand = new CUnban();
unbanCommand.ExecuteAsync(new GameEvent()
{
Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer },
Target = Manager.GetClientService().Find(c => c.NetworkId == client.NetworkId).Result.First().AsPlayer(),
Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
Target = Manager.GetClientService().Find(c => c.NetworkId == client.NetworkId).Result.First(),
Data = "test unban",
Type = GameEvent.EventType.Command,
Owner = client.CurrentServer

View File

@ -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,

View File

@ -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()
{

View File

@ -13,12 +13,13 @@ using Microsoft.EntityFrameworkCore;
using System.Net;
using Newtonsoft.Json.Linq;
using System.Text.RegularExpressions;
using static SharedLibraryCore.Database.Models.EFClient;
namespace IW4MAdmin.Plugins.Welcome
{
public class Plugin : IPlugin
{
String TimesConnected(Player P)
String TimesConnected(EFClient P)
{
int connection = P.Connections;
String Prefix = String.Empty;
@ -83,15 +84,15 @@ namespace IW4MAdmin.Plugins.Welcome
public async Task OnEventAsync(GameEvent E, Server S)
{
if (E.Type == GameEvent.EventType.Connect)
if (E.Type == GameEvent.EventType.Join)
{
Player newPlayer = E.Origin;
if (newPlayer.Level >= Player.Permission.Trusted && !E.Origin.Masked)
EFClient newPlayer = E.Origin;
if (newPlayer.Level >= Permission.Trusted && !E.Origin.Masked)
E.Owner.Broadcast(await ProcessAnnouncement(Config.Configuration().PrivilegedAnnouncementMessage, newPlayer));
newPlayer.Tell(await ProcessAnnouncement(Config.Configuration().UserWelcomeMessage, newPlayer));
if (newPlayer.Level == Player.Permission.Flagged)
if (newPlayer.Level == Permission.Flagged)
{
string penaltyReason;
@ -111,7 +112,7 @@ namespace IW4MAdmin.Plugins.Welcome
}
}
private async Task<string> ProcessAnnouncement(string msg, Player joining)
private async Task<string> ProcessAnnouncement(string msg, EFClient joining)
{
msg = msg.Replace("{{ClientName}}", joining.Name);
msg = msg.Replace("{{ClientLevel}}", Utilities.ConvertLevelToColor(joining.Level, joining.ClientPermission.Name));

View File

@ -5,13 +5,15 @@ _______
### About
**IW4MAdmin** is an administration tool for [IW4x](https://iw4xcachep26muba.onion.link/), [Pluto T6](https://forum.plutonium.pw/category/33/plutonium-t6), ~~[Pluto IW5](https://forum.plutonium.pw/category/5/plutonium-iw5)~~, and most Call of Duty<74> dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze.
### Download
Latest binary builds are always available at https://raidmax.org/IW4MAdmin
Latest binary builds are always available at:
- [RaidMax](https://raidmax.org/IW4MAdmin)
- [GitHub](https://github.com/RaidMax/IW4M-Admin/releases)
---
### Setup
**IW4MAdmin** requires minimal effort to get up and running.
#### Prerequisites
* [.NET Core 2.1 Runtime](https://www.microsoft.com/net/download) *or newer*
* [.NET Core 2.1.5 Runtime](https://www.microsoft.com/net/download) *or newer*
#### Installation
1. Install .NET Core Runtime
2. Extract `IW4MAdmin-<version>.zip`
@ -105,6 +107,14 @@ If you wish to further customize your experience of **IW4MAdmin**, the following
`ConnectionString`
* Specifies the [connection string](https://www.connectionstrings.com/mysql/) to a MySQL server that is used instead of SQLite
* Default &mdash; `null`
`DatabaseProvider`
* Specifies the database provider **IW4MAdmin** should use
* Possible values &mdash; `sqlite`, `mysql`, `postgresql`
* Default &mdash; `sqlite`
`Ignore Bots`
* Disables bots from being registered by **IW4MAdmin**
`RConPollRate`
* Specifies (in milliseconds) how often to poll each server for updates
@ -163,9 +173,9 @@ If you wish to further customize your experience of **IW4MAdmin**, the following
___
### Commands
|Name |Alias|Description |Requires Target|Syntax |Required Level|
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|
|prune|pa|demote any privileged clients that have not connected recently (defaults to 30 days)|False|!pa \<optional inactive days\>|Owner|
|Name |Alias|Description |Requires Target|Syntax |Required Level|
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ---------------|
|prune|pa|demote any trusted clients that have not connected recently (defaults to 30 days)|False|!pa \<optional inactive days\>|Owner|
|quit|q|quit IW4MAdmin|False|!q |Owner|
|rcon|rcon|send rcon command to server|False|!rcon \<commands\>|Owner|
|ban|b|permanently ban a client from the server|True|!b \<player\> \<reason\>|SeniorAdmin|
@ -190,24 +200,21 @@ ___
|unflag|uf|Remove flag for client|True|!uf \<player\>|Moderator|
|uptime|up|get current application running time|False|!up |Moderator|
|usage|us|get application memory usage|False|!us |Moderator|
|balance|bal|balance teams|False|!bal |Trusted|
|login|li|login using password|False|!li \<password\>|Trusted|
|warn|w|warn client for infringing rules|True|!w \<player\> \<reason\>|Trusted|
|warnclear|wc|remove all warnings for a client|True|!wc \<player\>|Trusted|
|admins|a|list currently connected privileged clients|False|!a |User|
|getexternalip|ip|view your external IP address|False|!ip |User|
|help|h|list all available commands|False|!h \<optional commands\>|User|
|mostplayed|mp|view the top 5 dedicated players on the server|False|!mp |User|
|nextmap|nm|view next map in rotation|False|!nm |User|
|owner|iamgod|claim ownership of the server|False|!iamgod |User|
|ping|pi|get client's latency|False|!pi \<optional player\>|User|
|privatemessage|pm|send message to other client|True|!pm \<player\> \<message\>|User|
|report|rep|report a client for suspicious behavior|True|!rep \<player\> \<reason\>|User|
|resetstats|rs|reset your stats to factory-new|False|!rs |User|
|rules|r|list server rules|False|!r |User|
|stats|xlrstats|view your stats|False|!xlrstats \<optional player\>|User|
|topstats|ts|view the top 5 players in this server|False|!ts |User|
|setgravatar|sg|set gravatar for webfront profile|False|!sg \<gravatar email\>|User|
|whoami|who|give information about yourself|False|!who |User|
_These commands include all shipped plugin commands._
---
@ -302,6 +309,11 @@ ___
#### VPN Detection [Script Plugin]
- This plugin detects if a client is using a VPN and kicks them if they are
- To disable this plugin, delete `Plugins\VPNDetection.js`
- Adding ClientIds to `vpnExceptionIds` will prevent a client from being kicked.
#### Shared GUID Kicker [Script Plugin]
- This plugin kicks users using a specific GUID
- GUID `F4D2C30B712AC6E3` on IW4x was packed into a torrent version of the game.
___
### Webfront
`Home`
@ -432,9 +444,13 @@ python DiscordWebhook.py
#### Anti-cheat
This is an [IW4x](https://iw4xcachep26muba.onion.link/) only feature (wider game support planned), that uses analytics to detect aimbots and aim-assist tools.
To utilize anti-cheat, enable it during setup **and** copy `_customcallbacks.gsc` from `userraw` into your `IW4x Server\userraw\scripts` folder.
The anti-cheat feature is a work in progress and as such will be constantly tweaked and may not be 100% accurate, however the goal is to deter as many cheaters as possible from IW4x.
#### Database Storage
By default, all **IW4MAdmin** information is stored in `Database.db`.
By default, all **IW4MAdmin** information is stored in `Database.db`.
Should you need to reset your database, this file can simply be deleted.
Additionally, this file should be preserved during updates to retain client information.
Setting the `ConnectionString` property in `IW4MAdminSettings.json` will cause **IW4MAdmin** to attempt to use a MySQL connection for database storage.
Setting the `ConnectionString` and `DatabaseProvider` properties in `IW4MAdminSettings.json`
will allow **IW4MAdmin** to use alternate methods for database storage.

View File

@ -1,8 +1,9 @@
dotnet publish WebfrontCore/WebfrontCore.csproj -c Prerelease -o C:\Projects\IW4M-Admin\Publish\WindowsPrerelease
dotnet publish Application/Application.csproj -c Prerelease -o C:\Projects\IW4M-Admin\Publish\WindowsPrerelease
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o C:\Projects\IW4M-Admin\Publish\WindowsPrerelease\GameLogServer
dotnet publish WebfrontCore/WebfrontCore.csproj -c Prerelease -o X:\IW4MAdmin\Publish\WindowsPrerelease /p:PublishProfile=Prerelease
dotnet publish Application/Application.csproj -c Prerelease -o X:\IW4MAdmin\Publish\WindowsPrerelease /p:PublishProfile=Prerelease
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o X:\IW4MAdmin\Publish\WindowsPrerelease\GameLogServer
::dotnet publish GameLogServer/DiscordWebhook.pyproj -c Release -o X:\IW4MAdmin\Publish\WindowsPrerelease\DiscordWebhook
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=FolderProfile /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\GameLogServer\
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=FolderProfile /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\DiscordWebhook\
cd "C:\Projects\IW4M-Admin\DEPLOY\"
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=PreRelease /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\GameLogServer\
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=PreRelease /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\DiscordWebhook\
cd "X:\IW4MAdmin\DEPLOY\"
PowerShell ".\upload_prerelease.ps1"

View File

@ -1,8 +1,8 @@
dotnet publish WebfrontCore/WebfrontCore.csproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows
dotnet publish Application/Application.csproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows\GameLogServer
dotnet publish WebfrontCore/WebfrontCore.csproj -c Release -o X:\IW4MAdmin\Publish\Windows
dotnet publish Application/Application.csproj -c Release -o X:\IW4MAdmin\Publish\Windows
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o X:\IW4MAdmin\Publish\Windows\GameLogServer
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\GameLogServer\
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\DiscordWebhook\
cd "C:\Projects\IW4M-Admin\DEPLOY\"
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\GameLogServer\
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\DiscordWebhook\
cd "X:\IW4MAdmin\DEPLOY\"
PowerShell ".\upload_release.ps1"

View File

@ -1,7 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
namespace SharedLibraryCore
@ -14,7 +14,7 @@ namespace SharedLibraryCore
public abstract class Command
{
public Command(String commandName, String commandDescription, String commandAlias, Player.Permission requiredPermission, bool requiresTarget, CommandArgument[] param = null)
public Command(String commandName, String commandDescription, String commandAlias, EFClient.Permission requiredPermission, bool requiresTarget, CommandArgument[] param = null)
{
Name = commandName;
Description = commandDescription;
@ -33,7 +33,7 @@ namespace SharedLibraryCore
public String Alias { get; private set; }
public int RequiredArgumentCount => Arguments.Count(c => c.Required);
public bool RequiresTarget { get; private set; }
public Player.Permission Permission { get; private set; }
public EFClient.Permission Permission { get; private set; }
public CommandArgument[] Arguments { get; private set; }
}
}

View File

@ -1,4 +1,5 @@
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
@ -60,7 +61,7 @@ namespace SharedLibraryCore.Commands
var found = await Manager.GetClientService().Get(dbID);
if (found != null)
{
E.Target = found.AsPlayer();
E.Target = found;
E.Target.CurrentServer = E.Owner;
E.Data = String.Join(" ", Args.Skip(1));
}
@ -68,14 +69,14 @@ namespace SharedLibraryCore.Commands
else if (Args[0].Length < 3 && cNum > -1 && cNum < E.Owner.MaxClients) // user specifying target by client num
{
if (E.Owner.Players[cNum] != null)
if (E.Owner.Clients[cNum] != null)
{
E.Target = E.Owner.Players[cNum];
E.Target = E.Owner.Clients[cNum];
E.Data = String.Join(" ", Args.Skip(1));
}
}
List<Player> matchingPlayers;
List<EFClient> matchingPlayers;
if (E.Target == null && C.RequiresTarget) // Find active player including quotes (multiple words)
{

View File

@ -12,14 +12,13 @@ using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static SharedLibraryCore.RCon.StaticHelpers;
namespace SharedLibraryCore.Commands
{
public class CQuit : Command
{
public CQuit() :
base("quit", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_QUIT_DESC"], "q", Player.Permission.Owner, false)
base("quit", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_QUIT_DESC"], "q", EFClient.Permission.Owner, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -31,18 +30,32 @@ namespace SharedLibraryCore.Commands
public class COwner : Command
{
public COwner() :
base("owner", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_DESC"], "iamgod", Player.Permission.User, false)
base("owner", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_DESC"], "iamgod", EFClient.Permission.User, false)
{ }
public override async Task ExecuteAsync(GameEvent E)
{
if ((await (E.Owner.Manager.GetClientService() as ClientService).GetOwners()).Count == 0)
{
E.Origin.Level = Player.Permission.Owner;
var oldPermission = E.Origin.Level;
E.Origin.Level = EFClient.Permission.Owner;
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_SUCCESS"]);
// so setpassword/login works
E.Owner.Manager.GetPrivilegedClients().Add(E.Origin.ClientId, E.Origin);
await E.Owner.Manager.GetClientService().Update(E.Origin);
var e = new GameEvent()
{
Type = GameEvent.EventType.ChangePermission,
Origin = E.Origin,
Target = E.Origin,
Owner = E.Owner,
Extra = new Change()
{
PreviousValue = oldPermission.ToString(),
NewValue = E.Origin.Level.ToString()
}
};
E.Owner.Manager.GetEventHandler().AddEvent(e);
}
else
{
@ -54,7 +67,7 @@ namespace SharedLibraryCore.Commands
public class CWarn : Command
{
public CWarn() :
base("warn", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_DESC"], "w", Player.Permission.Trusted, true, new CommandArgument[]
base("warn", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_DESC"], "w", EFClient.Permission.Trusted, true, new CommandArgument[]
{
new CommandArgument()
{
@ -83,7 +96,7 @@ namespace SharedLibraryCore.Commands
public class CWarnClear : Command
{
public CWarnClear() :
base("warnclear", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_DESC"], "wc", Player.Permission.Trusted, true, new CommandArgument[]
base("warnclear", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_DESC"], "wc", EFClient.Permission.Trusted, true, new CommandArgument[]
{
new CommandArgument()
{
@ -107,7 +120,7 @@ namespace SharedLibraryCore.Commands
public class CKick : Command
{
public CKick() :
base("kick", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_DESC"], "k", Player.Permission.Moderator, true, new CommandArgument[]
base("kick", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_DESC"], "k", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -133,7 +146,7 @@ namespace SharedLibraryCore.Commands
public class CSay : Command
{
public CSay() :
base("say", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SAY_DESC"], "s", Player.Permission.Moderator, false, new CommandArgument[]
base("say", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SAY_DESC"], "s", EFClient.Permission.Moderator, false, new CommandArgument[]
{
new CommandArgument()
{
@ -153,7 +166,7 @@ namespace SharedLibraryCore.Commands
public class CTempBan : Command
{
public CTempBan() :
base("tempban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_DESC"], "tb", Player.Permission.Administrator, true, new CommandArgument[]
base("tempban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_DESC"], "tb", EFClient.Permission.Administrator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -183,9 +196,17 @@ namespace SharedLibraryCore.Commands
string tempbanReason = match.Groups[2].ToString();
var length = match.Groups[1].ToString().ParseTimespan();
var _ = !(await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync()).Failed ?
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}") :
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}");
if (length > E.Owner.Manager.GetApplicationSettings().Configuration().MaximumTempBanTime)
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL_TOOLONG"]);
}
else
{
var _ = !(await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync()).Failed ?
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}") :
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}");
}
}
}
}
@ -193,7 +214,7 @@ namespace SharedLibraryCore.Commands
public class CBan : Command
{
public CBan() :
base("ban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_DESC"], "b", Player.Permission.SeniorAdmin, true, new CommandArgument[]
base("ban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_DESC"], "b", EFClient.Permission.SeniorAdmin, true, new CommandArgument[]
{
new CommandArgument()
{
@ -210,7 +231,7 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E)
{
var _ = !(await E.Target.Ban(E.Data, E.Origin).WaitAsync()).Failed ?
var _ = !(await E.Target.Ban(E.Data, E.Origin, false).WaitAsync()).Failed ?
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"]}") :
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_FAIL"]} {E.Target.Name}");
}
@ -219,7 +240,7 @@ namespace SharedLibraryCore.Commands
public class CUnban : Command
{
public CUnban() :
base("unban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_DESC"], "ub", Player.Permission.SeniorAdmin, true, new CommandArgument[]
base("unban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_DESC"], "ub", EFClient.Permission.SeniorAdmin, true, new CommandArgument[]
{
new CommandArgument()
{
@ -252,12 +273,12 @@ namespace SharedLibraryCore.Commands
public class CWhoAmI : Command
{
public CWhoAmI() :
base("whoami", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WHO_DESC"], "who", Player.Permission.User, false)
base("whoami", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WHO_DESC"], "who", EFClient.Permission.User, false)
{ }
public override Task ExecuteAsync(GameEvent E)
{
String You = String.Format("{0} [^3#{1}^7] {2} [^3@{3}^7] [{4}^7] IP: {5}", E.Origin.Name, E.Origin.ClientNumber, E.Origin.NetworkId, E.Origin.ClientId, Utilities.ConvertLevelToColor(E.Origin.Level, E.Origin.ClientPermission.Name), E.Origin.IPAddressString);
String You = String.Format("{0} [^3#{1}^7] {2} ^7[^3@{3}^7] ^7[{4}^7] IP: {5}", E.Origin.Name, E.Origin.ClientNumber, E.Origin.NetworkId, E.Origin.ClientId, Utilities.ConvertLevelToColor(E.Origin.Level, E.Origin.ClientPermission.Name), E.Origin.IPAddressString);
E.Origin.Tell(You);
return Task.CompletedTask;
@ -267,27 +288,33 @@ namespace SharedLibraryCore.Commands
public class CList : Command
{
public CList() :
base("list", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_LIST_DESC"], "l", Player.Permission.Moderator, false)
base("list", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_LIST_DESC"], "l", EFClient.Permission.Moderator, false)
{ }
public override Task ExecuteAsync(GameEvent E)
{
StringBuilder playerList = new StringBuilder();
int count = 0;
for (int i = 0; i < E.Owner.Players.Count; i++)
for (int i = 0; i < E.Owner.Clients.Count; i++)
{
var P = E.Owner.Players[i];
var P = E.Owner.Clients[i];
if (P == null)
{
continue;
}
// todo: fix spacing
// todo: make this better :)
if (P.Masked)
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(Player.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(Player.Permission.SeniorAdmin.ToString().Length - Player.Permission.User.ToString().Length));
{
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor( EFClient.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces( EFClient.Permission.SeniorAdmin.ToString().Length - EFClient.Permission.User.ToString().Length));
}
else
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(P.Level, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(Player.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length));
{
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(P.Level, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces( EFClient.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length));
}
if (count == 2 || E.Owner.GetPlayersAsList().Count == 1)
if (count == 2 || E.Owner.GetClientsAsList().Count == 1)
{
E.Origin.Tell(playerList.ToString());
count = 0;
@ -312,7 +339,7 @@ namespace SharedLibraryCore.Commands
public class CHelp : Command
{
public CHelp() :
base("help", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_HELP_DESC"], "h", Player.Permission.User, false, new CommandArgument[]
base("help", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_HELP_DESC"], "h", EFClient.Permission.User, false, new CommandArgument[]
{
new CommandArgument()
{
@ -377,7 +404,7 @@ namespace SharedLibraryCore.Commands
public class CFastRestart : Command
{
public CFastRestart() :
base("fastrestart", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FASTRESTART_DESC"], "fr", Player.Permission.Moderator, false)
base("fastrestart", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FASTRESTART_DESC"], "fr", EFClient.Permission.Moderator, false)
{ }
public override async Task ExecuteAsync(GameEvent E)
@ -393,7 +420,7 @@ namespace SharedLibraryCore.Commands
public class CMapRotate : Command
{
public CMapRotate() :
base("maprotate", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE_DESC"], "mr", Player.Permission.Administrator, false)
base("maprotate", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE_DESC"], "mr", EFClient.Permission.Administrator, false)
{ }
public override async Task ExecuteAsync(GameEvent E)
@ -410,7 +437,7 @@ namespace SharedLibraryCore.Commands
public class CSetLevel : Command
{
public CSetLevel() :
base("setlevel", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DESC"], "sl", Player.Permission.Moderator, true, new CommandArgument[]
base("setlevel", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DESC"], "sl", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -433,17 +460,17 @@ namespace SharedLibraryCore.Commands
return;
}
Player.Permission oldPerm = E.Target.Level;
Player.Permission newPerm = Utilities.MatchPermission(E.Data);
EFClient.Permission oldPerm = E.Target.Level;
EFClient.Permission newPerm = Utilities.MatchPermission(E.Data);
if (newPerm == Player.Permission.Owner &&
if (newPerm == EFClient.Permission.Owner &&
!E.Owner.Manager.GetApplicationSettings().Configuration().EnableMultipleOwners)
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_OWNER"]);
return;
}
if (E.Origin.Level < Player.Permission.Owner &&
if (E.Origin.Level < EFClient.Permission.Owner &&
!E.Owner.Manager.GetApplicationSettings().Configuration().EnableSteppedHierarchy)
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_STEPPEDDISABLED"]} ^5{E.Target.Name}");
@ -452,14 +479,14 @@ namespace SharedLibraryCore.Commands
if (newPerm >= E.Origin.Level)
{
if (E.Origin.Level < Player.Permission.Owner)
if (E.Origin.Level < EFClient.Permission.Owner)
{
E.Origin.Tell(string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_LEVELTOOHIGH"], E.Target.Name, (E.Origin.Level - 1).ToString()));
return;
}
}
else if (newPerm > Player.Permission.Banned)
else if (newPerm > EFClient.Permission.Banned)
{
var ActiveClient = E.Owner.Manager.GetActiveClients()
.FirstOrDefault(p => p.NetworkId == E.Target.NetworkId);
@ -468,7 +495,11 @@ namespace SharedLibraryCore.Commands
{
ActiveClient.Level = newPerm;
await E.Owner.Manager.GetClientService().Update(ActiveClient);
ActiveClient.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS_TARGET"]} {newPerm}");
if (newPerm > oldPerm)
{
ActiveClient.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS_TARGET"]} {newPerm}");
}
}
else
@ -477,17 +508,6 @@ namespace SharedLibraryCore.Commands
await E.Owner.Manager.GetClientService().Update(E.Target);
}
try
{
E.Owner.Manager.GetPrivilegedClients().Add(E.Target.ClientId, E.Target);
}
catch (Exception)
{
// this updates their privilege level to the webfront claims
E.Owner.Manager.GetPrivilegedClients()[E.Target.ClientId] = E.Target;
}
var e = new GameEvent()
{
Origin = E.Origin,
@ -503,7 +523,9 @@ namespace SharedLibraryCore.Commands
E.Owner.Manager.GetEventHandler().AddEvent(e);
E.Origin.Tell($"{E.Target.Name} {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS"]}");
var _ = newPerm < oldPerm ?
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DEMOTE_SUCCESS"]} {E.Target.Name}") :
E.Origin.Tell($"{E.Target.Name} {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS"]}");
}
else
@ -516,7 +538,7 @@ namespace SharedLibraryCore.Commands
public class CUsage : Command
{
public CUsage() :
base("usage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_USAGE_DESC"], "us", Player.Permission.Moderator, false)
base("usage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_USAGE_DESC"], "us", EFClient.Permission.Moderator, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -529,7 +551,7 @@ namespace SharedLibraryCore.Commands
public class CUptime : Command
{
public CUptime() :
base("uptime", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UPTIME_DESC"], "up", Player.Permission.Moderator, false)
base("uptime", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UPTIME_DESC"], "up", EFClient.Permission.Moderator, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -544,13 +566,13 @@ namespace SharedLibraryCore.Commands
public class CListAdmins : Command
{
public CListAdmins() :
base("admins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ADMINS_DESC"], "a", Player.Permission.User, false)
base("admins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ADMINS_DESC"], "a", EFClient.Permission.User, false)
{ }
public static string OnlineAdmins(Server S)
{
var onlineAdmins = S.GetPlayersAsList()
.Where(p => p.Level > Player.Permission.Flagged)
var onlineAdmins = S.GetClientsAsList()
.Where(p => p.Level > EFClient.Permission.Flagged)
.Where(p => !p.Masked)
.Select(p => $"[^3{Utilities.ConvertLevelToColor(p.Level, p.ClientPermission.Name)}^7] {p.Name}");
@ -573,7 +595,7 @@ namespace SharedLibraryCore.Commands
public class CLoadMap : Command
{
public CLoadMap() :
base("map", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_DESC"], "m", Player.Permission.Administrator, false, new CommandArgument[]
base("map", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_DESC"], "m", EFClient.Permission.Administrator, false, new CommandArgument[]
{
new CommandArgument()
{
@ -606,7 +628,7 @@ namespace SharedLibraryCore.Commands
public class CFindPlayer : Command
{
public CFindPlayer() :
base("find", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FIND_DESC"], "f", Player.Permission.Administrator, false, new CommandArgument[]
base("find", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FIND_DESC"], "f", EFClient.Permission.Administrator, false, new CommandArgument[]
{
new CommandArgument()
{
@ -625,7 +647,7 @@ namespace SharedLibraryCore.Commands
}
IList<EFClient> db_players = (await (E.Owner.Manager.GetClientService() as ClientService)
.GetClientByName(E.Data))
.FindClientsByIdentifier(E.Data))
.OrderByDescending(p => p.LastConnection)
.ToList();
@ -650,7 +672,7 @@ namespace SharedLibraryCore.Commands
public class CListRules : Command
{
public CListRules() :
base("rules", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RULES_DESC"], "r", Player.Permission.User, false)
base("rules", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RULES_DESC"], "r", EFClient.Permission.User, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -668,7 +690,9 @@ namespace SharedLibraryCore.Commands
var rules = new List<string>();
rules.AddRange(E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules);
if (E.Owner.ServerConfig.Rules != null)
{
rules.AddRange(E.Owner.ServerConfig.Rules);
}
foreach (string r in rules)
{
@ -683,7 +707,7 @@ namespace SharedLibraryCore.Commands
public class CPrivateMessage : Command
{
public CPrivateMessage() :
base("privatemessage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PM_DESC"], "pm", Player.Permission.User, true, new CommandArgument[]
base("privatemessage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PM_DESC"], "pm", EFClient.Permission.User, true, new CommandArgument[]
{
new CommandArgument()
{
@ -710,7 +734,7 @@ namespace SharedLibraryCore.Commands
public class CFlag : Command
{
public CFlag() :
base("flag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_DESC"], "fp", Player.Permission.Moderator, true, new CommandArgument[]
base("flag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_DESC"], "fp", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -752,7 +776,7 @@ namespace SharedLibraryCore.Commands
public class CUnflag : Command
{
public CUnflag() :
base("unflag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_DESC"], "uf", Player.Permission.Moderator, true, new CommandArgument[]
base("unflag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_DESC"], "uf", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -790,7 +814,7 @@ namespace SharedLibraryCore.Commands
public class CReport : Command
{
public CReport() :
base("report", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_DESC"], "rep", Player.Permission.User, true, new CommandArgument[]
base("report", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_DESC"], "rep", EFClient.Permission.User, true, new CommandArgument[]
{
new CommandArgument()
{
@ -861,7 +885,7 @@ namespace SharedLibraryCore.Commands
public class CListReports : Command
{
public CListReports() :
base("reports", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORTS_DESC"], "reps", Player.Permission.Moderator, false, new CommandArgument[]
base("reports", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORTS_DESC"], "reps", EFClient.Permission.Moderator, false, new CommandArgument[]
{
new CommandArgument()
{
@ -898,7 +922,7 @@ namespace SharedLibraryCore.Commands
public class CMask : Command
{
public CMask() :
base("mask", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MASK_DESC"], "hide", Player.Permission.Moderator, false)
base("mask", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MASK_DESC"], "hide", EFClient.Permission.Moderator, false)
{ }
public override async Task ExecuteAsync(GameEvent E)
@ -921,7 +945,7 @@ namespace SharedLibraryCore.Commands
public class CListBanInfo : Command
{
public CListBanInfo() :
base("baninfo", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_DESC"], "bi", Player.Permission.Moderator, true, new CommandArgument[]
base("baninfo", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_DESC"], "bi", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -935,7 +959,8 @@ namespace SharedLibraryCore.Commands
{
var B = await E.Owner.Manager.GetPenaltyService().GetClientPenaltiesAsync(E.Target.ClientId);
var penalty = B.FirstOrDefault(b => b.Type > Penalty.PenaltyType.Kick && b.Expires > DateTime.UtcNow);
var penalty = B.FirstOrDefault(b => b.Type > Penalty.PenaltyType.Kick &&
(b.Expires == null || b.Expires > DateTime.UtcNow));
if (penalty == null)
{
@ -943,7 +968,7 @@ namespace SharedLibraryCore.Commands
return;
}
string timeRemaining = penalty.Type == Penalty.PenaltyType.TempBan ? $"({(penalty.Expires - DateTime.UtcNow).TimeSpanText()} remaining)" : "";
string timeRemaining = penalty.Type == Penalty.PenaltyType.TempBan ? $"({(penalty.Expires.Value - DateTime.UtcNow).TimeSpanText()} remaining)" : "";
string success = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_SUCCESS"];
E.Origin.Tell($"^1{E.Target.Name} ^7{string.Format(success, penalty.Punisher.Name)} {penalty.Punisher.Name} {timeRemaining}");
@ -953,7 +978,7 @@ namespace SharedLibraryCore.Commands
public class CListAlias : Command
{
public CListAlias() :
base("alias", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ALIAS_DESC"], "known", Player.Permission.Moderator, true, new CommandArgument[]
base("alias", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ALIAS_DESC"], "known", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -987,7 +1012,7 @@ namespace SharedLibraryCore.Commands
public class CExecuteRCON : Command
{
public CExecuteRCON() :
base("rcon", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_DESC"], "rcon", Player.Permission.Owner, false, new CommandArgument[]
base("rcon", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_DESC"], "rcon", EFClient.Permission.Owner, false, new CommandArgument[]
{
new CommandArgument()
{
@ -1001,16 +1026,21 @@ namespace SharedLibraryCore.Commands
{
var Response = await E.Owner.ExecuteCommandAsync(E.Data.Trim());
foreach (string S in Response)
{
E.Origin.Tell(S.StripColors());
}
if (Response.Length == 0)
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_SUCCESS"]);
}
}
}
public class CPlugins : Command
{
public CPlugins() :
base("plugins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PLUGINS_DESC"], "p", Player.Permission.Administrator, false)
base("plugins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PLUGINS_DESC"], "p", EFClient.Permission.Administrator, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -1027,7 +1057,7 @@ namespace SharedLibraryCore.Commands
public class CIP : Command
{
public CIP() :
base("getexternalip", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_IP_DESC"], "ip", Player.Permission.User, false)
base("getexternalip", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_IP_DESC"], "ip", EFClient.Permission.User, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -1039,7 +1069,7 @@ namespace SharedLibraryCore.Commands
public class CPruneAdmins : Command
{
public CPruneAdmins() : base("prune", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_DESC"], "pa", Player.Permission.Owner, false, new CommandArgument[]
public CPruneAdmins() : base("prune", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_DESC"], "pa", EFClient.Permission.Owner, false, new CommandArgument[]
{
new CommandArgument()
{
@ -1059,7 +1089,9 @@ namespace SharedLibraryCore.Commands
{
inactiveDays = Int32.Parse(E.Data);
if (inactiveDays < 1)
{
throw new FormatException();
}
}
}
@ -1076,10 +1108,10 @@ namespace SharedLibraryCore.Commands
{
var lastActive = DateTime.UtcNow.AddDays(-inactiveDays);
inactiveUsers = await context.Clients
.Where(c => c.Level > Player.Permission.Flagged && c.Level <= Player.Permission.Moderator)
.Where(c => c.Level > EFClient.Permission.Flagged && c.Level <= EFClient.Permission.Moderator)
.Where(c => c.LastConnection < lastActive)
.ToListAsync();
inactiveUsers.ForEach(c => c.Level = Player.Permission.User);
inactiveUsers.ForEach(c => c.Level = EFClient.Permission.User);
await context.SaveChangesAsync();
}
E.Origin.Tell($"^5{inactiveUsers.Count} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_SUCCESS"]}");
@ -1088,7 +1120,7 @@ namespace SharedLibraryCore.Commands
public class CSetPassword : Command
{
public CSetPassword() : base("setpassword", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETPASSWORD_DESC"], "sp", Player.Permission.Moderator, false, new CommandArgument[]
public CSetPassword() : base("setpassword", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETPASSWORD_DESC"], "sp", EFClient.Permission.Moderator, false, new CommandArgument[]
{
new CommandArgument()
{
@ -1122,77 +1154,89 @@ namespace SharedLibraryCore.Commands
public class CKillServer : Command
{
public CKillServer() : base("killserver", "kill the game server", "kill", Player.Permission.Administrator, false)
public CKillServer() : base("killserver", "kill the game server", "kill", EFClient.Permission.Administrator, false)
{
}
public override async Task ExecuteAsync(GameEvent E)
{
var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x");
System.Diagnostics.Process currentProcess = null;
foreach (var p in gameserverProcesses)
if (E.Owner.ServerConfig.ManualLogPath != null)
{
string cmdLine = Utilities.GetCommandLine(p.Id);
var regex = Regex.Match(cmdLine, @".*((?:\+set|\+) net_port) +([0-9]+).*");
if (regex.Success && Int32.Parse(regex.Groups[2].Value) == E.Owner.GetPort())
using (var wc = new WebClient())
{
currentProcess = p;
E.Owner.RestartRequested = true;
var response = await wc.DownloadStringTaskAsync(new Uri($"{E.Owner.ServerConfig.ManualLogPath}/restart"));
}
}
if (currentProcess == null)
{
E.Origin.Tell("Could not find running/stalled instance of IW4x");
}
else
{
// attempt to kill it natively
try
var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x");
System.Diagnostics.Process currentProcess = null;
foreach (var p in gameserverProcesses)
{
if (!E.Owner.Throttled)
string cmdLine = Utilities.GetCommandLine(p.Id);
var regex = Regex.Match(cmdLine, @".*((?:\+set|\+) net_port) +([0-9]+).*");
if (regex.Success && Int32.Parse(regex.Groups[2].Value) == E.Owner.GetPort())
{
currentProcess = p;
}
}
if (currentProcess == null)
{
E.Origin.Tell("Could not find running/stalled instance of IW4x");
}
else
{
// attempt to kill it natively
try
{
if (!E.Owner.Throttled)
{
#if !DEBUG
await E.Owner.ExecuteCommandAsync("quit");
#endif
}
}
catch (Exceptions.NetworkException)
{
E.Origin.Tell("Unable to cleanly shutdown server, forcing");
}
if (!currentProcess.HasExited)
{
try
{
currentProcess.Kill();
E.Origin.Tell("Successfully killed server process");
}
catch (Exception e)
{
E.Origin.Tell("Could not kill server process");
E.Owner.Logger.WriteDebug("Unable to kill process");
E.Owner.Logger.WriteDebug($"Exception: {e.Message}");
return;
}
}
}
catch (Exceptions.NetworkException)
{
E.Origin.Tell("Unable to cleanly shutdown server, forcing");
}
if (!currentProcess.HasExited)
{
try
{
currentProcess.Kill();
E.Origin.Tell("Successfully killed server process");
}
catch (Exception e)
{
E.Origin.Tell("Could not kill server process");
E.Owner.Logger.WriteDebug("Unable to kill process");
E.Owner.Logger.WriteDebug($"Exception: {e.Message}");
return;
}
}
return;
}
return;
}
}
public class CPing : Command
{
public CPing() : base("ping", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_DESC"], "pi", Player.Permission.User, false, new CommandArgument[]
public CPing() : base("ping", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_DESC"], "pi", EFClient.Permission.User, false, new CommandArgument[]
{
new CommandArgument()
{
@ -1207,16 +1251,24 @@ namespace SharedLibraryCore.Commands
if (E.Message.IsBroadcastCommand())
{
if (E.Target == null)
{
E.Owner.Broadcast($"{E.Origin.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Origin.Ping}^7ms");
}
else
{
E.Owner.Broadcast($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms");
}
}
else
{
if (E.Target == null)
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_SELF"]} ^5{E.Origin.Ping}^7ms");
}
else
{
E.Origin.Tell($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms");
}
}
return Task.CompletedTask;
@ -1225,7 +1277,7 @@ namespace SharedLibraryCore.Commands
public class CSetGravatar : Command
{
public CSetGravatar() : base("setgravatar", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_DESC"], "sg", Player.Permission.User, false, new CommandArgument[]
public CSetGravatar() : base("setgravatar", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_DESC"], "sg", EFClient.Permission.User, false, new CommandArgument[]
{
new CommandArgument()
{
@ -1289,7 +1341,7 @@ namespace SharedLibraryCore.Commands
/// </summary>
public class CNextMap : Command
{
public CNextMap() : base("nextmap", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_DESC"], "nm", Player.Permission.User, false) { }
public CNextMap() : base("nextmap", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_DESC"], "nm", EFClient.Permission.User, false) { }
public static async Task<string> GetNextMap(Server s)
{
string mapRotation = (await s.GetDvarAsync<string>("sv_mapRotation")).Value.ToLower();

View File

@ -16,11 +16,15 @@ namespace SharedLibraryCore.Configuration
public string SocialLinkAddress { get; set; }
public string SocialLinkTitle { get; set; }
public string WebfrontBindUrl { get; set; }
public string ManualWebfrontUrl { get; set; }
public string WebfrontUrl => string.IsNullOrEmpty(ManualWebfrontUrl) ? WebfrontBindUrl.Replace("0.0.0.0", "127.0.0.1") : ManualWebfrontUrl;
public string CustomParserEncoding { get; set; }
public string CustomLocale { get; set; }
public string DatabaseProvider { get; set; } = "sqlite";
public string ConnectionString { get; set; }
public int RConPollRate { get; set; } = 5000;
public bool IgnoreBots { get; set; }
public TimeSpan MaximumTempBanTime { get; set; } = new TimeSpan(24 * 30, 0, 0);
public string Id { get; set; }
public List<ServerConfiguration> Servers { get; set; }
public int AutoMessagePeriod { get; set; }

View File

@ -1,6 +1,7 @@
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
namespace SharedLibraryCore.Configuration
{
@ -11,11 +12,22 @@ namespace SharedLibraryCore.Configuration
public string Password { get; set; }
public IList<string> Rules { get; set; }
public IList<string> AutoMessages { get; set; }
public bool UseT6MParser { get; set; }
public bool UseIW5MParser { get; set; }
public string ManualLogPath { get; set; }
public string CustomParserVersion { get; set; }
public int ReservedSlotNumber { get; set; }
private readonly IList<IRConParser> rconParsers;
private readonly IList<IEventParser> eventParsers;
public ServerConfiguration()
{
rconParsers = new List<IRConParser>();
eventParsers = new List<IEventParser>();
}
public void AddRConParser(IRConParser parser) => rconParsers.Add(parser);
public void AddEventParser(IEventParser parser) => eventParsers.Add(parser);
public IBaseConfiguration Generate()
{
var loc = Utilities.CurrentLocalization.LocalizationIndex;
@ -36,18 +48,25 @@ namespace SharedLibraryCore.Configuration
}
Password = Utilities.PromptString(loc["SETUP_SERVER_RCON"]);
AutoMessages = new List<string>();
Rules = new List<string>();
ReservedSlotNumber = loc["SETUP_SERVER_RESERVEDSLOT"].PromptInt(null, 0, 32);
var parserVersions = rconParsers.Select(_parser => _parser.Version).ToArray();
var selection = Utilities.PromptSelection(loc["SETUP_SERVER_RCON_PARSER_VERSION"], $"{loc["SETUP_PROMPT_DEFAULT"]} (IW4x)", null, parserVersions);
UseT6MParser = Utilities.PromptBool(loc["SETUP_SERVER_USET6M"]);
if (!UseT6MParser)
UseIW5MParser = Utilities.PromptBool(loc["SETUP_SERVER_USEIW5M"]);
if (UseIW5MParser)
ManualLogPath = Utilities.PromptString(loc["SETUP_SERVER_MANUALLOG"]);
if (selection.Item1 > 0)
{
CustomParserVersion = selection.Item2;
}
ReservedSlotNumber = loc["SETUP_SERVER_RESERVEDSLOT"].PromptInt(0, 32);
parserVersions = eventParsers.Select(_parser => _parser.Version).ToArray();
selection = Utilities.PromptSelection(loc["SETUP_SERVER_EVENT_PARSER_VERSION"], $"{loc["SETUP_PROMPT_DEFAULT"]} (IW4x)", null, parserVersions);
if (selection.Item1 > 0)
{
CustomParserVersion = selection.Item2;
}
return this;
}

View File

@ -3,6 +3,7 @@ using SharedLibraryCore.Database.Models;
using System;
using System.Linq;
using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
namespace SharedLibraryCore.Database
{
@ -47,7 +48,7 @@ namespace SharedLibraryCore.Database
Connections = 0,
FirstConnection = DateTime.UtcNow,
LastConnection = DateTime.UtcNow,
Level = Objects.Player.Permission.Console,
Level = Permission.Console,
Masked = true,
NetworkId = 0,
AliasLinkId = 1,

View File

@ -1,17 +1,13 @@
using System;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models;
using System.Reflection;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Data.Sqlite;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore.Metadata;
using Npgsql;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace SharedLibraryCore.Database
{
@ -26,6 +22,7 @@ namespace SharedLibraryCore.Database
static string _ConnectionString;
static string _provider;
private static readonly string _migrationPluginDirectory = @"X:\IW4MAdmin\BUILD\Plugins\";
public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt) { }
@ -39,6 +36,13 @@ namespace SharedLibraryCore.Database
this.ChangeTracker.LazyLoadingEnabled = false;
this.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
else
{
this.ChangeTracker.AutoDetectChangesEnabled = true;
this.ChangeTracker.LazyLoadingEnabled = true;
this.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;
}
}
public DatabaseContext(string connStr, string provider)
@ -57,7 +61,7 @@ namespace SharedLibraryCore.Database
$"{Path.DirectorySeparatorChar}{currentPath}" :
currentPath;
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = $"{currentPath}{Path.DirectorySeparatorChar}Database{Path.DirectorySeparatorChar}Database.db" };
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = Path.Join(currentPath, "Database", "Database.db") };
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);
@ -98,6 +102,9 @@ namespace SharedLibraryCore.Database
.WithMany(p => p.AdministeredPenalties)
.HasForeignKey(c => c.PunisherId)
.OnDelete(DeleteBehavior.Restrict);
entity.Property(p => p.Expires)
.IsRequired(false);
});
modelBuilder.Entity<EFAliasLink>(entity =>
@ -110,6 +117,7 @@ namespace SharedLibraryCore.Database
modelBuilder.Entity<EFAlias>(ent =>
{
ent.Property(a => a.IPAddress).IsRequired(false);
ent.HasIndex(a => a.IPAddress);
ent.Property(a => a.Name).HasMaxLength(24);
ent.HasIndex(a => a.Name);
@ -123,26 +131,15 @@ namespace SharedLibraryCore.Database
// adapted from
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
IEnumerable<string> directoryFiles;
string pluginDir = $@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}Debug{Path.DirectorySeparatorChar}netcoreapp2.1{Path.DirectorySeparatorChar}Plugins";
if (!Directory.Exists(pluginDir))
{
pluginDir = Path.Join(Environment.CurrentDirectory, "Plugins");
if (!Directory.Exists(pluginDir))
{
pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
}
}
directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
#if DEBUG == TRUE
foreach (string dllPath in Directory.GetFiles(@"C:\Projects\IW4M-Admin\Application\bin\Debug\netcoreapp2.1\Plugins").Where(f => f.EndsWith(".dll")))
#if DEBUG
string pluginDir = _migrationPluginDirectory;
#else
foreach (string dllPath in directoryFiles)
string pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
#endif
IEnumerable<string> directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
foreach (string dllPath in directoryFiles)
{
Assembly library;
try
@ -160,7 +157,9 @@ namespace SharedLibraryCore.Database
.Select(c => (IModelConfiguration)Activator.CreateInstance(c));
foreach (var configurable in configurations)
{
configurable.Configure(modelBuilder);
}
foreach (var type in library.ExportedTypes)
{

View File

@ -16,7 +16,7 @@ namespace SharedLibraryCore.Database.Models
[MaxLength(24)]
public string Name { get; set; }
[Required]
public int IPAddress { get; set; }
public int? IPAddress { get; set; }
[Required]
public DateTime DateAdded { get; set; }
}

View File

@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace SharedLibraryCore.Database.Models
{
public class EFClient : SharedEntity
public partial class EFClient : SharedEntity
{
[Key]
public int ClientId { get; set; }
@ -25,7 +25,7 @@ namespace SharedLibraryCore.Database.Models
[ForeignKey("AliasLinkId")]
public virtual EFAliasLink AliasLink { get; set; }
[Required]
public Objects.Player.Permission Level { get; set; }
public Permission Level { get; set; }
[Required]
public int CurrentAliasId { get; set; }
@ -41,27 +41,21 @@ namespace SharedLibraryCore.Database.Models
public virtual string Name
{
get { return CurrentAlias.Name; }
set { }
set { CurrentAlias.Name = value; }
}
[NotMapped]
public virtual int IPAddress
public virtual int? IPAddress
{
get { return CurrentAlias.IPAddress; }
set { }
set { CurrentAlias.IPAddress = value; }
}
[NotMapped]
public string IPAddressString => new System.Net.IPAddress(BitConverter.GetBytes(IPAddress)).ToString();
public string IPAddressString => IPAddress.ConvertIPtoString();
[NotMapped]
public virtual IDictionary<int, long> LinkedAccounts { get; set; }
public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; }
public virtual ICollection<EFPenalty> AdministeredPenalties { get; set; }
public EFClient()
{
ReceivedPenalties = new List<EFPenalty>();
AdministeredPenalties = new List<EFPenalty>();
}
}
}

View File

@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Database.Models
{
@ -27,10 +23,12 @@ namespace SharedLibraryCore.Database.Models
[Required]
public DateTime When { get; set; }
[Required]
public DateTime Expires { get; set; }
public DateTime? Expires { get; set; }
[Required]
public string Offense { get; set; }
public string AutomatedOffense { get; set; }
[Required]
public bool IsEvadedOffense { get; set; }
public Objects.Penalty.PenaltyType Type { get; set; }
}
}

View File

@ -1,17 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static SharedLibraryCore.Objects.Player;
namespace SharedLibraryCore.Dtos
namespace SharedLibraryCore.Dtos
{
public class ClientInfo
{
public string Name { get; set; }
public int ClientId { get; set; }
public int LinkId { get; set; }
public Permission Level { get; set; }
public Database.Models.EFClient.Permission Level { get; set; }
}
}

View File

@ -9,7 +9,7 @@ namespace SharedLibraryCore.Dtos
/// </summary>
public class EntityInfo
{
public int Id { get; set; }
public long Id { get; set; }
public string Name { get; set; }
}
}

View File

@ -17,7 +17,7 @@ namespace SharedLibraryCore.Dtos
public List<ChatInfo> ChatHistory { get; set; }
public List<PlayerInfo> Players { get; set; }
public Helpers.PlayerHistory[] PlayerHistory { get; set; }
public int ID { get; set; }
public long ID { get; set; }
public bool Online { get; set; }
}
}

View File

@ -2,12 +2,10 @@
{
public class Dvar<T>
{
public string Name { get; private set; }
public T Value;
public Dvar(string name)
{
Name = name;
}
public string Name { get; set; }
public T Value { get; set; }
public T DefaultValue { get; set; }
public T LatchedValue { get; set; }
public string Domain { get; set; }
}
}

View File

@ -41,7 +41,7 @@ namespace SharedLibraryCore.Events
OwnerEntity = new EntityInfo()
{
Name = E.Owner.Hostname,
Id = E.Owner.GetHashCode()
Id = E.Owner.EndPoint
},
OriginEntity = E.Origin == null ? null : new EntityInfo()
{

View File

@ -1,7 +1,7 @@
using System;
using SharedLibraryCore.Database.Models;
using System;
using System.Threading;
using System.Threading.Tasks;
using SharedLibraryCore.Objects;
namespace SharedLibraryCore
{
@ -50,11 +50,11 @@ namespace SharedLibraryCore
/// </summary>
Stop,
/// <summary>
/// a client was detecting as connecting via RCon
/// a client was detecting as connecting via log
/// </summary>
Connect,
/// <summary>
/// a client was detecting joining via log
/// a client was detecting joining by RCon
/// </summary>
Join,
/// <summary>
@ -73,6 +73,18 @@ namespace SharedLibraryCore
/// the current map changed
/// </summary>
MapChange,
/// <summary>
/// a client was detected as starting to connect
/// </summary>
PreConnect,
/// <summary>
/// a client was detecting as starting to disconnect
/// </summary>
PreDisconnect,
/// <summary>
/// a client's information was updated
/// </summary>
Update,
// events "generated" by clients
/// <summary>
@ -158,7 +170,10 @@ namespace SharedLibraryCore
}
static long NextEventId;
static long GetNextEventId() => Interlocked.Increment(ref NextEventId);
static long GetNextEventId()
{
return Interlocked.Increment(ref NextEventId);
}
public GameEvent()
{
@ -170,10 +185,10 @@ namespace SharedLibraryCore
public EventType Type;
public string Data; // Data is usually the message sent by player
public string Message;
public Player Origin;
public Player Target;
public EFClient Origin;
public EFClient Target;
public Server Owner;
public Boolean Remote = false;
public bool IsRemote { get; set; } = false;
public object Extra { get; set; }
public ManualResetEventSlim OnProcessed { get; set; }
public DateTime Time { get; set; }
@ -185,43 +200,13 @@ namespace SharedLibraryCore
/// asynchronously wait for GameEvent to be processed
/// </summary>
/// <returns>waitable task </returns>
public Task<GameEvent> WaitAsync(int timeOut = int.MaxValue) => Task.Run(() =>
public Task<GameEvent> WaitAsync(int timeOut = int.MaxValue)
{
OnProcessed.Wait(timeOut);
return this;
});
/// <summary>
/// determine whether an event should be delayed or not
/// applies only to the origin entity
/// </summary>
/// <param name="queuedEvent">event to determine status for</param>
/// <returns>true if event should be delayed, false otherwise</returns>
public static bool ShouldOriginEventBeDelayed(GameEvent queuedEvent)
{
return queuedEvent.Origin != null &&
(queuedEvent.Origin.State != Player.ClientState.Connected &&
// we want to allow join and quit events
queuedEvent.Type != EventType.Connect &&
queuedEvent.Type != EventType.Join &&
queuedEvent.Type != EventType.Quit &&
queuedEvent.Type != EventType.Disconnect &&
// we don't care about unknown events
queuedEvent.Origin.NetworkId != 0);
}
/// <summary>
/// determine whether an event should be delayed or not
/// applies only to the target entity
/// </summary>
/// <param name="queuedEvent">event to determine status for</param>
/// <returns>true if event should be delayed, false otherwise</returns>
public static bool ShouldTargetEventBeDelayed(GameEvent queuedEvent)
{
return (queuedEvent.Target != null && queuedEvent.Target.ClientNumber != -1) &&
(queuedEvent.Target.State != Player.ClientState.Connected &&
queuedEvent.Target.NetworkId != 0 &&
queuedEvent.Origin?.ClientId != 1);
return Task.Run(() =>
{
OnProcessed.Wait(timeOut);
return this;
});
}
}
}

View File

@ -1,5 +1,6 @@
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;

View File

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Interfaces
{
public sealed class ParserRegex
{
/// <summary>
/// represents the logical mapping of information provided by
/// game logs, get status, and get dvar information
/// </summary>
public enum GroupType
{
EventType,
OriginNetworkId,
TargetNetworkId,
OriginClientNumber,
TargetClientNumber,
OriginName,
TargetName,
OriginTeam,
TargetTeam,
Weapon,
Damage,
MeansOfDeath,
HitLocation,
Message,
RConClientNumber = 100,
RConScore = 101,
RConPing = 102,
RConNetworkId = 103,
RConName = 104,
RConIpAddress = 105,
RConDvarName = 106,
RConDvarValue = 107,
RConDvarDefaultValue = 108,
RConDvarLatchedValue = 109,
RConDvarDomain = 110,
AdditionalGroup = 200
}
/// <summary>
/// stores the regular expression groups that will be mapped to group types
/// </summary>
public string Pattern { get; set; }
/// <summary>
/// stores the mapping from group type to group index in the regular expression
/// </summary>
public Dictionary<GroupType, int> GroupMapping { get; private set; }
/// <summary>
/// helper method to enable script parsers to app regex mapping
/// the first parameter specifies the group type contained in the regex pattern
/// the second parameter specifies the group index to retrieve in the matched regex pattern
/// </summary>
/// <param name="mapKey">group type</param>
/// <param name="mapValue">group index</param>
public void AddMapping(object mapKey, object mapValue)
{
if (int.TryParse(mapKey.ToString(), out int key) && int.TryParse(mapValue.ToString(), out int value))
{
if (GroupMapping.ContainsKey((GroupType)key))
{
GroupMapping[(GroupType)key] = value;
}
else
{
GroupMapping.Add((GroupType)key, value);
}
}
if (mapKey.GetType() == typeof(GroupType) && mapValue.GetType().ToString() == "System.Int32")
{
GroupType k = (GroupType)Enum.Parse(typeof(GroupType), mapKey.ToString());
int v = int.Parse(mapValue.ToString());
if (GroupMapping.ContainsKey(k))
{
GroupMapping[k] = v;
}
else
{
GroupMapping.Add(k, v);
}
}
}
public ParserRegex()
{
GroupMapping = new Dictionary<GroupType, int>();
}
}
}

View File

@ -21,6 +21,7 @@ namespace SharedLibraryCore.Helpers
{
}
public Vector3(float x, float y, float z)
{
X = x;

View File

@ -1,27 +0,0 @@
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Interfaces
{
public interface IClientAuthentication
{
/// <summary>
/// request authentication when a client join event
/// occurs in the log, as no IP is given
/// </summary>
/// <param name="client">client that has joined from the log</param>
void RequestClientAuthentication(Player client);
/// <summary>
/// get all clients that have been authenticated by the status poll
/// </summary>
/// <returns>list of all authenticated clients</returns>
IList<Player> GetAuthenticatedClients();
/// <summary>
/// authenticate a list of clients from status poll
/// </summary>
/// <param name="clients">list of clients to authenticate</param>
void AuthenticateClients(IList<Player> clients);
}
}

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Interfaces
namespace SharedLibraryCore.Interfaces
{
public interface IEventParser
{
@ -18,6 +14,7 @@ namespace SharedLibraryCore.Interfaces
/// Get game specific folder prefix for log files
/// </summary>
/// <returns>Game directory prefix</returns>
string GetGameDir();
IEventParserConfiguration Configuration { get; set; }
string Version { get; set; }
}
}

View File

@ -0,0 +1,34 @@
namespace SharedLibraryCore.Interfaces
{
public interface IEventParserConfiguration
{
/// <summary>
/// stores the fs_game directory (this folder may vary between different clients)
/// </summary>
string GameDirectory { get; set; }
/// <summary>
/// stores the regex information for a say event printed in the game log
/// </summary>
ParserRegex Say { get; set; }
/// <summary>
/// stores the regex information for a join event printed in the game log
/// </summary>
ParserRegex Join { get; set; }
/// <summary>
/// stores the regex information for a quit event printed in the game log
/// </summary>
ParserRegex Quit { get; set; }
/// <summary>
/// stores the regex information for a kill event printed in the game log
/// </summary>
ParserRegex Kill { get; set; }
/// <summary>
/// stores the regex information for a damage event printed in the game log
/// </summary>
ParserRegex Damage { get; set; }
/// <summary>
/// stores the regex information for an action event printed in the game log
/// </summary>
ParserRegex Action { get; set; }
}
}

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