moved validate command into shared library.
reworked connection system to read from log file for join/quits and authenticate later with polling
This commit is contained in:
parent
454238a192
commit
af6361144e
74
Application/Core/ClientAuthentication.cs
Normal file
74
Application/Core/ClientAuthentication.cs
Normal file
@ -0,0 +1,74 @@
|
||||
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.IsAuthenticated = true;
|
||||
AuthenticatedClients.Add(client.NetworkId, client);
|
||||
}
|
||||
else
|
||||
{
|
||||
// this update their ping
|
||||
value.Ping = client.Ping;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.IsAuthenticated = true;
|
||||
AuthenticatedClients.Add(clientToAuthenticate.NetworkId, clientToAuthenticate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IList<Player> GetAuthenticatedClients()
|
||||
{
|
||||
return AuthenticatedClients.Values.ToList();
|
||||
}
|
||||
|
||||
public void RequestClientAuthentication(Player client)
|
||||
{
|
||||
ClientAuthenticationQueue.Enqueue(client);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,11 +12,12 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
public virtual GameEvent GetEvent(Server server, string logLine)
|
||||
{
|
||||
logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim();
|
||||
string[] lineSplit = logLine.Split(';');
|
||||
string cleanedEventLine = Regex.Replace(lineSplit[0], @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim();
|
||||
string eventType = lineSplit[0];
|
||||
|
||||
// kill
|
||||
if (cleanedEventLine[0] == 'K')
|
||||
if (eventType == "K")
|
||||
{
|
||||
if (!server.CustomCallback)
|
||||
{
|
||||
@ -31,18 +32,18 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanedEventLine.Contains("JoinTeam"))
|
||||
if (eventType == "JoinTeam")
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.JoinTeam,
|
||||
Data = cleanedEventLine,
|
||||
Data = eventType,
|
||||
Origin = server.GetPlayersAsList().FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong()),
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
if (cleanedEventLine == "say" || cleanedEventLine == "sayteam")
|
||||
if (eventType == "say" || eventType == "sayteam")
|
||||
{
|
||||
string message = lineSplit[4].Replace("\x15", "");
|
||||
|
||||
@ -68,7 +69,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
};
|
||||
}
|
||||
|
||||
if (cleanedEventLine.Contains("ScriptKill"))
|
||||
if (eventType == "ScriptKill")
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -80,7 +81,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
};
|
||||
}
|
||||
|
||||
if (cleanedEventLine.Contains("ScriptDamage"))
|
||||
if (eventType == "ScriptDamage")
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -93,14 +94,14 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
|
||||
// damage
|
||||
if (cleanedEventLine[0] == 'D')
|
||||
if (eventType == "D")
|
||||
{
|
||||
if (Regex.Match(cleanedEventLine, @"^(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)
|
||||
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)
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Damage,
|
||||
Data = cleanedEventLine,
|
||||
Data = eventType,
|
||||
Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong()),
|
||||
Target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()),
|
||||
Owner = server
|
||||
@ -109,19 +110,19 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
|
||||
// join
|
||||
if (cleanedEventLine[0] == 'J')
|
||||
if (eventType == "J")
|
||||
{
|
||||
var regexMatch = Regex.Match(cleanedEventLine, @"^(J;)(.{4,32});([0-9]+);(.*)$");
|
||||
var regexMatch = Regex.Match(logLine, @"^(J;)(.{4,32});([0-9]+);(.*)$");
|
||||
if (regexMatch.Success)
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Join,
|
||||
Data = cleanedEventLine,
|
||||
Data = logLine,
|
||||
Owner = server,
|
||||
Origin = new Player()
|
||||
{
|
||||
Name = regexMatch.Groups[4].ToString(),
|
||||
Name = regexMatch.Groups[4].ToString().StripColors(),
|
||||
NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
|
||||
ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString())
|
||||
}
|
||||
@ -129,7 +130,28 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanedEventLine.Contains("ExitLevel"))
|
||||
if (eventType == "Q")
|
||||
{
|
||||
var regexMatch = Regex.Match(logLine, @"^(Q;)(.{4,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()
|
||||
{
|
||||
@ -147,9 +169,9 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
};
|
||||
}
|
||||
|
||||
if (cleanedEventLine.Contains("InitGame"))
|
||||
if (eventType.Contains("InitGame"))
|
||||
{
|
||||
string dump = cleanedEventLine.Replace("InitGame: ", "");
|
||||
string dump = eventType.Replace("InitGame: ", "");
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
|
@ -12,40 +12,28 @@ namespace IW4MAdmin.Application
|
||||
class GameEventHandler : IEventHandler
|
||||
{
|
||||
private ConcurrentQueue<GameEvent> EventQueue;
|
||||
private Queue<GameEvent> StatusSensitiveQueue;
|
||||
private Queue<GameEvent> DelayedEventQueue;
|
||||
private IManager Manager;
|
||||
private const int DelayAmount = 5000;
|
||||
private DateTime LastDelayedEvent;
|
||||
|
||||
public GameEventHandler(IManager mgr)
|
||||
{
|
||||
EventQueue = new ConcurrentQueue<GameEvent>();
|
||||
StatusSensitiveQueue = new Queue<GameEvent>();
|
||||
DelayedEventQueue = new Queue<GameEvent>();
|
||||
|
||||
Manager = mgr;
|
||||
}
|
||||
|
||||
public void AddEvent(GameEvent gameEvent)
|
||||
public void AddEvent(GameEvent gameEvent, bool delayedExecution = false)
|
||||
{
|
||||
#if DEBUG
|
||||
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner}");
|
||||
#endif
|
||||
// we need this to keep accurate track of the score
|
||||
if (gameEvent.Type == GameEvent.EventType.Kill ||
|
||||
gameEvent.Type == GameEvent.EventType.Damage ||
|
||||
gameEvent.Type == GameEvent.EventType.ScriptDamage ||
|
||||
gameEvent.Type == GameEvent.EventType.ScriptKill ||
|
||||
gameEvent.Type == GameEvent.EventType.MapChange ||
|
||||
gameEvent.Type == GameEvent.EventType.JoinTeam)
|
||||
if (delayedExecution)
|
||||
{
|
||||
#if DEBUG
|
||||
Manager.GetLogger().WriteDebug($"Added sensitive event to queue");
|
||||
#endif
|
||||
lock (StatusSensitiveQueue)
|
||||
{
|
||||
StatusSensitiveQueue.Enqueue(gameEvent);
|
||||
}
|
||||
return;
|
||||
DelayedEventQueue.Enqueue(gameEvent);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
EventQueue.Enqueue(gameEvent);
|
||||
@ -61,27 +49,6 @@ namespace IW4MAdmin.Application
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public GameEvent GetNextSensitiveEvent()
|
||||
{
|
||||
if (StatusSensitiveQueue.Count > 0)
|
||||
{
|
||||
lock (StatusSensitiveQueue)
|
||||
{
|
||||
if (!StatusSensitiveQueue.TryDequeue(out GameEvent newEvent))
|
||||
{
|
||||
Manager.GetLogger().WriteWarning("Could not dequeue time sensitive event for processing");
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return newEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public GameEvent GetNextEvent()
|
||||
{
|
||||
if (EventQueue.Count > 0)
|
||||
@ -100,6 +67,24 @@ namespace IW4MAdmin.Application
|
||||
}
|
||||
}
|
||||
|
||||
if (DelayedEventQueue.Count > 0 &&
|
||||
(DateTime.Now - LastDelayedEvent).TotalMilliseconds > DelayAmount)
|
||||
{
|
||||
LastDelayedEvent = DateTime.Now;
|
||||
#if DEBUG
|
||||
Manager.GetLogger().WriteDebug("Getting next delayed event to be processed");
|
||||
#endif
|
||||
if (!DelayedEventQueue.TryDequeue(out GameEvent newEvent))
|
||||
{
|
||||
Manager.GetLogger().WriteWarning("Could not dequeue delayed event for processing");
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return newEvent;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ namespace IW4MAdmin.Application
|
||||
Console.WriteLine("=====================================================");
|
||||
Console.WriteLine(" IW4M ADMIN");
|
||||
Console.WriteLine(" by RaidMax ");
|
||||
Console.WriteLine($" Version {Version.ToString("0.0")}");
|
||||
Console.WriteLine($" Version {Version.ToString("0.00")}");
|
||||
Console.WriteLine("=====================================================");
|
||||
|
||||
Index loc = null;
|
||||
|
@ -81,18 +81,14 @@ namespace IW4MAdmin.Application
|
||||
|
||||
public async Task UpdateStatus(object state)
|
||||
{
|
||||
var taskList = new Dictionary<int, Task>();
|
||||
var taskList = new List<Task>();
|
||||
|
||||
while (Running)
|
||||
{
|
||||
var tasksToRemove = taskList.Where(t => t.Value.Status == TaskStatus.RanToCompletion)
|
||||
.Select(t => t.Key).ToList();
|
||||
|
||||
tasksToRemove.ForEach(t => taskList.Remove(t));
|
||||
|
||||
taskList.Clear();
|
||||
foreach (var server in Servers)
|
||||
{
|
||||
var newTask = Task.Run(async () =>
|
||||
taskList.Add(Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -105,12 +101,7 @@ namespace IW4MAdmin.Application
|
||||
Logger.WriteDebug($"Exception: {e.Message}");
|
||||
Logger.WriteDebug($"StackTrace: {e.StackTrace}");
|
||||
}
|
||||
});
|
||||
|
||||
if (!taskList.ContainsKey(server.GetHashCode()))
|
||||
{
|
||||
taskList.Add(server.GetHashCode(), newTask);
|
||||
}
|
||||
}));
|
||||
}
|
||||
#if DEBUG
|
||||
Logger.WriteDebug($"{taskList.Count} servers queued for stats updates");
|
||||
@ -119,35 +110,7 @@ namespace IW4MAdmin.Application
|
||||
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
|
||||
#endif
|
||||
|
||||
await Task.WhenAny(taskList.Values.ToArray());
|
||||
|
||||
GameEvent sensitiveEvent;
|
||||
while ((sensitiveEvent = Handler.GetNextSensitiveEvent()) != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await sensitiveEvent.Owner.ExecuteEvent(sensitiveEvent);
|
||||
#if DEBUG
|
||||
Logger.WriteDebug($"Processed Sensitive Event {sensitiveEvent.Type}");
|
||||
#endif
|
||||
}
|
||||
|
||||
catch (NetworkException e)
|
||||
{
|
||||
Logger.WriteError(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]);
|
||||
Logger.WriteDebug(e.Message);
|
||||
}
|
||||
|
||||
catch (Exception E)
|
||||
{
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"]} {sensitiveEvent.Owner}");
|
||||
Logger.WriteDebug("Error Message: " + E.Message);
|
||||
Logger.WriteDebug("Error Trace: " + E.StackTrace);
|
||||
}
|
||||
|
||||
sensitiveEvent.OnProcessed.Set();
|
||||
}
|
||||
|
||||
await Task.WhenAny(taskList);
|
||||
await Task.Delay(ConfigHandler.Configuration().RConPollRate);
|
||||
}
|
||||
}
|
||||
@ -460,15 +423,34 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
// wait for new event to be added
|
||||
OnEvent.Wait();
|
||||
|
||||
var taskList = new List<Task>();
|
||||
// todo: sequencially or parallelize?
|
||||
while ((queuedEvent = Handler.GetNextEvent()) != null)
|
||||
{
|
||||
if (queuedEvent.Origin != null &&
|
||||
!queuedEvent.Origin.IsAuthenticated &&
|
||||
// we want to allow join events
|
||||
queuedEvent.Type != GameEvent.EventType.Join &&
|
||||
queuedEvent.Type != GameEvent.EventType.Quit &&
|
||||
// we don't care about unknown events
|
||||
queuedEvent.Origin.NetworkId != 0)
|
||||
{
|
||||
Logger.WriteDebug($"Delaying execution of event type {queuedEvent.Type} for {queuedEvent.Origin} because they are not authed");
|
||||
// update the event origin for possible authed client
|
||||
queuedEvent.Origin = queuedEvent.Owner.Players.FirstOrDefault(p => p != null && p.NetworkId == queuedEvent.Origin.NetworkId);
|
||||
queuedEvent.Target = queuedEvent.Target == null ? null : queuedEvent.Owner.Players.FirstOrDefault(p => p != null && p.NetworkId == queuedEvent.Target.NetworkId);
|
||||
// add it back to the queue for reprocessing
|
||||
Handler.AddEvent(queuedEvent, true);
|
||||
continue;
|
||||
}
|
||||
await processEvent(queuedEvent);
|
||||
}
|
||||
|
||||
// this should allow parallel processing of events
|
||||
// await Task.WhenAll(eventList);
|
||||
if (taskList.Count > 0)
|
||||
{
|
||||
// this should allow parallel processing of events
|
||||
await Task.WhenAny(taskList);
|
||||
}
|
||||
|
||||
// signal that all events have been processed
|
||||
OnEvent.Reset();
|
||||
|
@ -20,6 +20,7 @@ using Application.RconParsers;
|
||||
using IW4MAdmin.Application.EventParsers;
|
||||
using IW4MAdmin.Application.IO;
|
||||
using SharedLibraryCore.Localization;
|
||||
using IW4MAdmin.Application.Core;
|
||||
|
||||
namespace IW4MAdmin
|
||||
{
|
||||
@ -27,8 +28,12 @@ namespace IW4MAdmin
|
||||
{
|
||||
private static Index loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
private GameLogEvent LogEvent;
|
||||
private ClientAuthentication AuthQueue;
|
||||
|
||||
public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg) { }
|
||||
public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg)
|
||||
{
|
||||
AuthQueue = new ClientAuthentication();
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
@ -49,6 +54,17 @@ namespace IW4MAdmin
|
||||
return id;
|
||||
}
|
||||
|
||||
public async Task OnPlayerJoined(Player logClient)
|
||||
{
|
||||
Logger.WriteDebug($"Log detected {logClient} joining");
|
||||
if (Players[logClient.ClientNumber] == null || Players[logClient.ClientNumber].NetworkId != logClient.NetworkId)
|
||||
{
|
||||
Players[logClient.ClientNumber] = logClient;
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
override public async Task<bool> AddPlayer(Player polledPlayer)
|
||||
{
|
||||
if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) ||
|
||||
@ -60,7 +76,9 @@ namespace IW4MAdmin
|
||||
}
|
||||
|
||||
if (Players[polledPlayer.ClientNumber] != null &&
|
||||
Players[polledPlayer.ClientNumber].NetworkId == polledPlayer.NetworkId)
|
||||
Players[polledPlayer.ClientNumber].NetworkId == polledPlayer.NetworkId &&
|
||||
// only update if they're unauthenticated
|
||||
Players[polledPlayer.ClientNumber].IsAuthenticated)
|
||||
{
|
||||
// update their ping & score
|
||||
Players[polledPlayer.ClientNumber].Ping = polledPlayer.Ping;
|
||||
@ -76,7 +94,7 @@ namespace IW4MAdmin
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Players.FirstOrDefault(p => p != null && p.Name == polledPlayer.Name) != null)
|
||||
if (Players.FirstOrDefault(p => p != null && p.Name == polledPlayer.Name && p.NetworkId != polledPlayer.NetworkId) != null)
|
||||
{
|
||||
Logger.WriteDebug($"Kicking {polledPlayer} because their name is already in use");
|
||||
string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, loc["SERVER_KICK_NAME_INUSE"]);
|
||||
@ -154,7 +172,9 @@ namespace IW4MAdmin
|
||||
player.ClientNumber = polledPlayer.ClientNumber;
|
||||
player.IsBot = polledPlayer.IsBot;
|
||||
player.Score = polledPlayer.Score;
|
||||
player.IsAuthenticated = true;
|
||||
player.CurrentServer = this;
|
||||
player.State = Player.ClientState.Connected;
|
||||
Players[player.ClientNumber] = player;
|
||||
|
||||
var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(player.AliasLinkId, player.IPAddress);
|
||||
@ -230,149 +250,19 @@ namespace IW4MAdmin
|
||||
Player Leaving = Players[cNum];
|
||||
Logger.WriteInfo($"Client {Leaving} disconnecting...");
|
||||
|
||||
Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds;
|
||||
Leaving.LastConnection = DateTime.UtcNow;
|
||||
await Manager.GetClientService().Update(Leaving);
|
||||
Players[cNum] = null;
|
||||
|
||||
var e = new GameEvent(GameEvent.EventType.Disconnect, "", Leaving, null, this);
|
||||
Manager.GetEventHandler().AddEvent(e);
|
||||
|
||||
// wait until the disconnect event is complete
|
||||
e.OnProcessed.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
//Process requested command correlating to an event
|
||||
// todo: this needs to be removed out of here
|
||||
override public async Task<Command> ValidateCommand(GameEvent E)
|
||||
{
|
||||
string CommandString = E.Data.Substring(1, E.Data.Length - 1).Split(' ')[0];
|
||||
E.Message = E.Data;
|
||||
|
||||
Command C = null;
|
||||
foreach (Command cmd in Manager.GetCommands())
|
||||
{
|
||||
if (cmd.Name == CommandString.ToLower() || cmd.Alias == CommandString.ToLower())
|
||||
C = cmd;
|
||||
}
|
||||
|
||||
if (C == null)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_UNKNOWN"]);
|
||||
throw new CommandException($"{E.Origin} entered unknown command \"{CommandString}\"");
|
||||
}
|
||||
|
||||
E.Data = E.Data.RemoveWords(1);
|
||||
String[] Args = E.Data.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (E.Origin.Level < C.Permission)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_NOACCESS"]);
|
||||
throw new CommandException($"{E.Origin} does not have access to \"{C.Name}\"");
|
||||
}
|
||||
|
||||
if (Args.Length < (C.RequiredArgumentCount))
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_MISSINGARGS"]);
|
||||
await E.Origin.Tell(C.Syntax);
|
||||
throw new CommandException($"{E.Origin} did not supply enough arguments for \"{C.Name}\"");
|
||||
}
|
||||
|
||||
if (C.RequiresTarget || Args.Length > 0)
|
||||
{
|
||||
if (!Int32.TryParse(Args[0], out int cNum))
|
||||
cNum = -1;
|
||||
|
||||
if (Args[0][0] == '@') // user specifying target by database ID
|
||||
if (!Leaving.IsAuthenticated)
|
||||
{
|
||||
int dbID = -1;
|
||||
int.TryParse(Args[0].Substring(1, Args[0].Length - 1), out dbID);
|
||||
|
||||
var found = await Manager.GetClientService().Get(dbID);
|
||||
if (found != null)
|
||||
{
|
||||
E.Target = found.AsPlayer();
|
||||
E.Target.CurrentServer = this as IW4MServer;
|
||||
E.Owner = this as IW4MServer;
|
||||
E.Data = String.Join(" ", Args.Skip(1));
|
||||
}
|
||||
Players[cNum] = null;
|
||||
}
|
||||
|
||||
else if (Args[0].Length < 3 && cNum > -1 && cNum < MaxClients) // user specifying target by client num
|
||||
else
|
||||
{
|
||||
if (Players[cNum] != null)
|
||||
{
|
||||
E.Target = Players[cNum];
|
||||
E.Data = String.Join(" ", Args.Skip(1));
|
||||
}
|
||||
}
|
||||
|
||||
List<Player> matchingPlayers;
|
||||
|
||||
if (E.Target == null && C.RequiresTarget) // Find active player including quotes (multiple words)
|
||||
{
|
||||
matchingPlayers = GetClientByName(E.Data.Trim());
|
||||
if (matchingPlayers.Count > 1)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_TARGET_MULTI"]);
|
||||
throw new CommandException($"{E.Origin} had multiple players found for {C.Name}");
|
||||
}
|
||||
else if (matchingPlayers.Count == 1)
|
||||
{
|
||||
E.Target = matchingPlayers.First();
|
||||
|
||||
string escapedName = Regex.Escape(E.Target.Name);
|
||||
var reg = new Regex($"(\"{escapedName}\")|({escapedName})", RegexOptions.IgnoreCase);
|
||||
E.Data = reg.Replace(E.Data, "", 1).Trim();
|
||||
|
||||
if (E.Data.Length == 0 && C.RequiredArgumentCount > 1)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_MISSINGARGS"]);
|
||||
await E.Origin.Tell(C.Syntax);
|
||||
throw new CommandException($"{E.Origin} did not supply enough arguments for \"{C.Name}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Target == null && C.RequiresTarget) // Find active player as single word
|
||||
{
|
||||
matchingPlayers = GetClientByName(Args[0]);
|
||||
if (matchingPlayers.Count > 1)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_TARGET_MULTI"]);
|
||||
foreach (var p in matchingPlayers)
|
||||
await E.Origin.Tell($"[^3{p.ClientNumber}^7] {p.Name}");
|
||||
throw new CommandException($"{E.Origin} had multiple players found for {C.Name}");
|
||||
}
|
||||
else if (matchingPlayers.Count == 1)
|
||||
{
|
||||
E.Target = matchingPlayers.First();
|
||||
|
||||
string escapedName = Regex.Escape(E.Target.Name);
|
||||
string escapedArg = Regex.Escape(Args[0]);
|
||||
var reg = new Regex($"({escapedName})|({escapedArg})", RegexOptions.IgnoreCase);
|
||||
E.Data = reg.Replace(E.Data, "", 1).Trim();
|
||||
|
||||
if ((E.Data.Trim() == E.Target.Name.ToLower().Trim() ||
|
||||
E.Data == String.Empty) &&
|
||||
C.RequiresTarget)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_MISSINGARGS"]);
|
||||
await E.Origin.Tell(C.Syntax);
|
||||
throw new CommandException($"{E.Origin} did not supply enough arguments for \"{C.Name}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Target == null && C.RequiresTarget)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_TARGET_NOTFOUND"]);
|
||||
throw new CommandException($"{E.Origin} specified invalid player for \"{C.Name}\"");
|
||||
Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds;
|
||||
Leaving.LastConnection = DateTime.UtcNow;
|
||||
await Manager.GetClientService().Update(Leaving);
|
||||
Players.RemoveAt(cNum);
|
||||
}
|
||||
}
|
||||
E.Data = E.Data.Trim();
|
||||
return C;
|
||||
}
|
||||
|
||||
public override async Task ExecuteEvent(GameEvent E)
|
||||
@ -381,13 +271,12 @@ namespace IW4MAdmin
|
||||
await ProcessEvent(E);
|
||||
Manager.GetEventApi().OnServerEvent(this, E);
|
||||
|
||||
|
||||
Command C = null;
|
||||
if (E.Type == GameEvent.EventType.Command)
|
||||
{
|
||||
try
|
||||
{
|
||||
C = await ValidateCommand(E);
|
||||
C = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E);
|
||||
}
|
||||
|
||||
catch (CommandException e)
|
||||
@ -471,22 +360,21 @@ namespace IW4MAdmin
|
||||
|
||||
else if (E.Type == GameEvent.EventType.Join)
|
||||
{
|
||||
// special case for IW5 when connect is from the log
|
||||
if (E.Extra != null && GameName == Game.IW5)
|
||||
{
|
||||
var logClient = (Player)E.Extra;
|
||||
var client = (await this.GetStatusAsync())
|
||||
.Single(c => c.ClientNumber == logClient.ClientNumber &&
|
||||
c.Name == logClient.Name);
|
||||
client.NetworkId = logClient.NetworkId;
|
||||
await OnPlayerJoined(E.Origin);
|
||||
}
|
||||
|
||||
await AddPlayer(client);
|
||||
}
|
||||
|
||||
/*else
|
||||
else if (E.Type == GameEvent.EventType.Quit)
|
||||
{
|
||||
var e = new GameEvent()
|
||||
{
|
||||
await AddPlayer(E.Origin);
|
||||
}*/
|
||||
Type = GameEvent.EventType.Disconnect,
|
||||
Origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId),
|
||||
Owner = this
|
||||
};
|
||||
|
||||
e.Origin.State = Player.ClientState.Disconnecting;
|
||||
|
||||
Manager.GetEventHandler().AddEvent(e);
|
||||
}
|
||||
|
||||
else if (E.Type == GameEvent.EventType.Disconnect)
|
||||
@ -501,6 +389,8 @@ namespace IW4MAdmin
|
||||
Time = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
await RemovePlayer(E.Origin.ClientNumber);
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Say)
|
||||
@ -574,7 +464,10 @@ namespace IW4MAdmin
|
||||
if (E.Type == GameEvent.EventType.Broadcast)
|
||||
{
|
||||
// this is a little ugly but I don't want to change the abstract class
|
||||
await E.Owner.ExecuteCommandAsync(E.Message);
|
||||
if (E.Message != null)
|
||||
{
|
||||
await E.Owner.ExecuteCommandAsync(E.Message);
|
||||
}
|
||||
}
|
||||
|
||||
while (ChatHistory.Count > Math.Ceiling((double)ClientNum / 2))
|
||||
@ -611,24 +504,33 @@ namespace IW4MAdmin
|
||||
var clients = GetPlayersAsList();
|
||||
foreach (var client in clients)
|
||||
{
|
||||
if (GameName == Game.IW5)
|
||||
// remove players that have disconnected
|
||||
if (!CurrentPlayers.Select(c => c.NetworkId).Contains(client.NetworkId))
|
||||
{
|
||||
if (!CurrentPlayers.Select(c => c.ClientNumber).Contains(client.ClientNumber))
|
||||
await RemovePlayer(client.ClientNumber);
|
||||
}
|
||||
// the log should already have started a disconnect event
|
||||
if (client.State == Player.ClientState.Disconnecting)
|
||||
continue;
|
||||
|
||||
else
|
||||
{
|
||||
if (!CurrentPlayers.Select(c => c.NetworkId).Contains(client.NetworkId))
|
||||
await RemovePlayer(client.ClientNumber);
|
||||
var e = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Disconnect,
|
||||
Origin = client,
|
||||
Owner = this
|
||||
};
|
||||
|
||||
Manager.GetEventHandler().AddEvent(e);
|
||||
// todo: needed?
|
||||
// wait until the disconnect event is complete
|
||||
e.OnProcessed.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < CurrentPlayers.Count; i++)
|
||||
AuthQueue.AuthenticateClients(CurrentPlayers);
|
||||
|
||||
// all polled players should be authenticated
|
||||
foreach (var client in AuthQueue.GetAuthenticatedClients())
|
||||
{
|
||||
// todo: wait til GUID is included in status to fix this
|
||||
if (GameName != Game.IW5)
|
||||
await AddPlayer(CurrentPlayers[i]);
|
||||
await AddPlayer(client);
|
||||
}
|
||||
|
||||
return CurrentPlayers.Count;
|
||||
@ -645,8 +547,9 @@ namespace IW4MAdmin
|
||||
{
|
||||
if (Manager.ShutdownRequested())
|
||||
{
|
||||
for (int i = 0; i < Players.Count; i++)
|
||||
await RemovePlayer(i);
|
||||
// todo: fix up disconnect
|
||||
//for (int i = 0; i < Players.Count; i++)
|
||||
// await RemovePlayer(i);
|
||||
|
||||
foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
|
||||
await plugin.OnUnloadAsync();
|
||||
@ -816,9 +719,6 @@ namespace IW4MAdmin
|
||||
this.MaxClients = maxplayers;
|
||||
this.FSGame = game;
|
||||
this.Gametype = gametype;
|
||||
|
||||
//wait this.SetDvarAsync("sv_kickbantime", 60);
|
||||
|
||||
if (logsync.Value == 0 || logfile.Value == string.Empty)
|
||||
{
|
||||
// this DVAR isn't set until the a map is loaded
|
||||
@ -833,7 +733,7 @@ namespace IW4MAdmin
|
||||
CustomCallback = await ScriptLoaded();
|
||||
string mainPath = EventParser.GetGameDir();
|
||||
#if DEBUG
|
||||
basepath.Value = @"\\192.168.88.253\logs\games_mp.log";
|
||||
basepath.Value = @"D:\";
|
||||
#endif
|
||||
string logPath;
|
||||
if (GameName == Game.IW5)
|
||||
|
145
SharedLibraryCore/Commands/CommandProcessing.cs
Normal file
145
SharedLibraryCore/Commands/CommandProcessing.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedLibraryCore.Commands
|
||||
{
|
||||
public class CommandProcessing
|
||||
{
|
||||
public static async Task<Command> ValidateCommand(GameEvent E)
|
||||
{
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
var Manager = E.Owner.Manager;
|
||||
|
||||
string CommandString = E.Data.Substring(1, E.Data.Length - 1).Split(' ')[0];
|
||||
E.Message = E.Data;
|
||||
|
||||
Command C = null;
|
||||
foreach (Command cmd in Manager.GetCommands())
|
||||
{
|
||||
if (cmd.Name == CommandString.ToLower() || cmd.Alias == CommandString.ToLower())
|
||||
C = cmd;
|
||||
}
|
||||
|
||||
if (C == null)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_UNKNOWN"]);
|
||||
throw new CommandException($"{E.Origin} entered unknown command \"{CommandString}\"");
|
||||
}
|
||||
|
||||
E.Data = E.Data.RemoveWords(1);
|
||||
String[] Args = E.Data.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (E.Origin.Level < C.Permission)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_NOACCESS"]);
|
||||
throw new CommandException($"{E.Origin} does not have access to \"{C.Name}\"");
|
||||
}
|
||||
|
||||
if (Args.Length < (C.RequiredArgumentCount))
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_MISSINGARGS"]);
|
||||
await E.Origin.Tell(C.Syntax);
|
||||
throw new CommandException($"{E.Origin} did not supply enough arguments for \"{C.Name}\"");
|
||||
}
|
||||
|
||||
if (C.RequiresTarget || Args.Length > 0)
|
||||
{
|
||||
if (!Int32.TryParse(Args[0], out int cNum))
|
||||
cNum = -1;
|
||||
|
||||
if (Args[0][0] == '@') // user specifying target by database ID
|
||||
{
|
||||
int dbID = -1;
|
||||
int.TryParse(Args[0].Substring(1, Args[0].Length - 1), out dbID);
|
||||
|
||||
var found = await Manager.GetClientService().Get(dbID);
|
||||
if (found != null)
|
||||
{
|
||||
E.Target = found.AsPlayer();
|
||||
E.Target.CurrentServer = E.Owner;
|
||||
E.Data = String.Join(" ", Args.Skip(1));
|
||||
}
|
||||
}
|
||||
|
||||
else if (Args[0].Length < 3 && cNum > -1 && cNum < E.Owner.MaxClients) // user specifying target by client num
|
||||
{
|
||||
if (E.Owner.Players[cNum] != null)
|
||||
{
|
||||
E.Target = E.Owner.Players[cNum];
|
||||
E.Data = String.Join(" ", Args.Skip(1));
|
||||
}
|
||||
}
|
||||
|
||||
List<Player> matchingPlayers;
|
||||
|
||||
if (E.Target == null && C.RequiresTarget) // Find active player including quotes (multiple words)
|
||||
{
|
||||
matchingPlayers = E.Owner.GetClientByName(E.Data.Trim());
|
||||
if (matchingPlayers.Count > 1)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_TARGET_MULTI"]);
|
||||
throw new CommandException($"{E.Origin} had multiple players found for {C.Name}");
|
||||
}
|
||||
else if (matchingPlayers.Count == 1)
|
||||
{
|
||||
E.Target = matchingPlayers.First();
|
||||
|
||||
string escapedName = Regex.Escape(E.Target.Name);
|
||||
var reg = new Regex($"(\"{escapedName}\")|({escapedName})", RegexOptions.IgnoreCase);
|
||||
E.Data = reg.Replace(E.Data, "", 1).Trim();
|
||||
|
||||
if (E.Data.Length == 0 && C.RequiredArgumentCount > 1)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_MISSINGARGS"]);
|
||||
await E.Origin.Tell(C.Syntax);
|
||||
throw new CommandException($"{E.Origin} did not supply enough arguments for \"{C.Name}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Target == null && C.RequiresTarget) // Find active player as single word
|
||||
{
|
||||
matchingPlayers = E.Owner.GetClientByName(Args[0]);
|
||||
if (matchingPlayers.Count > 1)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_TARGET_MULTI"]);
|
||||
foreach (var p in matchingPlayers)
|
||||
await E.Origin.Tell($"[^3{p.ClientNumber}^7] {p.Name}");
|
||||
throw new CommandException($"{E.Origin} had multiple players found for {C.Name}");
|
||||
}
|
||||
else if (matchingPlayers.Count == 1)
|
||||
{
|
||||
E.Target = matchingPlayers.First();
|
||||
|
||||
string escapedName = Regex.Escape(E.Target.Name);
|
||||
string escapedArg = Regex.Escape(Args[0]);
|
||||
var reg = new Regex($"({escapedName})|({escapedArg})", RegexOptions.IgnoreCase);
|
||||
E.Data = reg.Replace(E.Data, "", 1).Trim();
|
||||
|
||||
if ((E.Data.Trim() == E.Target.Name.ToLower().Trim() ||
|
||||
E.Data == String.Empty) &&
|
||||
C.RequiresTarget)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_MISSINGARGS"]);
|
||||
await E.Origin.Tell(C.Syntax);
|
||||
throw new CommandException($"{E.Origin} did not supply enough arguments for \"{C.Name}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Target == null && C.RequiresTarget)
|
||||
{
|
||||
await E.Origin.Tell(loc["COMMAND_TARGET_NOTFOUND"]);
|
||||
throw new CommandException($"{E.Origin} specified invalid player for \"{C.Name}\"");
|
||||
}
|
||||
}
|
||||
E.Data = E.Data.Trim();
|
||||
return C;
|
||||
}
|
||||
}
|
||||
}
|
27
SharedLibraryCore/Interfaces/IClientAuthentication.cs
Normal file
27
SharedLibraryCore/Interfaces/IClientAuthentication.cs
Normal file
@ -0,0 +1,27 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -13,7 +13,8 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// Add a game event event to the queue to be processed
|
||||
/// </summary>
|
||||
/// <param name="gameEvent">Game event</param>
|
||||
void AddEvent(GameEvent gameEvent);
|
||||
/// <param name="delayedExecution">don't signal that an event has been aded</param>
|
||||
void AddEvent(GameEvent gameEvent, bool delayedExecution = false);
|
||||
/// <summary>
|
||||
/// Get the next event to be processed
|
||||
/// </summary>
|
||||
|
10
SharedLibraryCore/Objects/ClientStats.cs
Normal file
10
SharedLibraryCore/Objects/ClientStats.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SharedLibraryCore.Objects
|
||||
{
|
||||
class ClientStats
|
||||
{
|
||||
}
|
||||
}
|
@ -8,6 +8,13 @@ namespace SharedLibraryCore.Objects
|
||||
{
|
||||
public class Player : Database.Models.EFClient
|
||||
{
|
||||
public enum ClientState
|
||||
{
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnecting,
|
||||
}
|
||||
|
||||
public enum Permission
|
||||
{
|
||||
Banned = -1,
|
||||
@ -109,6 +116,10 @@ namespace SharedLibraryCore.Objects
|
||||
get { return _name; }
|
||||
set { _name = value; }
|
||||
}
|
||||
[NotMapped]
|
||||
public bool IsAuthenticated { get; set; }
|
||||
[NotMapped]
|
||||
public ClientState State { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
|
@ -13,6 +13,6 @@ namespace SharedLibraryCore.RCon
|
||||
}
|
||||
|
||||
public static char SeperatorChar = (char)int.Parse("0a", System.Globalization.NumberStyles.AllowHexSpecifier);
|
||||
public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 10);
|
||||
public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 5);
|
||||
}
|
||||
}
|
||||
|
@ -100,14 +100,6 @@ namespace SharedLibraryCore
|
||||
return Players.Where(p => p != null && p.Name.ToLower().Contains(pName.ToLower())).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process requested command correlating to an event
|
||||
/// </summary>
|
||||
/// <param name="E">Event parameter</param>
|
||||
/// <param name="C">Command requested from the event</param>
|
||||
/// <returns></returns>
|
||||
abstract public Task<Command> ValidateCommand(GameEvent E);
|
||||
|
||||
virtual public Task<bool> ProcessUpdatesAsync(CancellationToken cts) => (Task<bool>)Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
|
Loading…
x
Reference in New Issue
Block a user