diff --git a/Application/Application.csproj b/Application/Application.csproj
index b7359761d..3550af1e0 100644
--- a/Application/Application.csproj
+++ b/Application/Application.csproj
@@ -5,7 +5,7 @@
netcoreapp2.1
false
RaidMax.IW4MAdmin.Application
- 2.1.4
+ 2.1.5
RaidMax
Forever None
IW4MAdmin
diff --git a/Application/Core/ClientAuthentication.cs b/Application/Core/ClientAuthentication.cs
index 1f818cabc..4b73fd321 100644
--- a/Application/Core/ClientAuthentication.cs
+++ b/Application/Core/ClientAuthentication.cs
@@ -36,7 +36,7 @@ namespace IW4MAdmin.Application.Core
if (!AuthenticatedClients.TryGetValue(client.NetworkId, out Player value))
{
// authenticate them
- client.IsAuthenticated = true;
+ client.State = Player.ClientState.Authenticated;
AuthenticatedClients.Add(client.NetworkId, client);
}
else
@@ -57,7 +57,7 @@ namespace IW4MAdmin.Application.Core
if (!AuthenticatedClients.TryGetValue(clientToAuthenticate.NetworkId, out Player value))
{
// authenticate them
- clientToAuthenticate.IsAuthenticated = true;
+ clientToAuthenticate.State = Player.ClientState.Authenticated;
AuthenticatedClients.Add(clientToAuthenticate.NetworkId, clientToAuthenticate);
}
}
diff --git a/Application/EventParsers/IW4EventParser.cs b/Application/EventParsers/IW4EventParser.cs
index 2f0af3947..db3f08f82 100644
--- a/Application/EventParsers/IW4EventParser.cs
+++ b/Application/EventParsers/IW4EventParser.cs
@@ -133,7 +133,8 @@ namespace IW4MAdmin.Application.EventParsers
{
Name = regexMatch.Groups[4].ToString().StripColors(),
NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
- ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString())
+ ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
+ State = Player.ClientState.Connecting
}
};
}
diff --git a/Application/EventParsers/T6MEventParser.cs b/Application/EventParsers/T6MEventParser.cs
index 901a2ce4b..1f016b0d9 100644
--- a/Application/EventParsers/T6MEventParser.cs
+++ b/Application/EventParsers/T6MEventParser.cs
@@ -11,101 +11,6 @@ namespace IW4MAdmin.Application.EventParsers
{
class T6MEventParser : IW4EventParser
{
- /*public GameEvent GetEvent(Server server, string logLine)
- {
- string cleanedEventLine = Regex.Replace(logLine, @"^ *[0-9]+:[0-9]+ *", "").Trim();
- string[] lineSplit = cleanedEventLine.Split(';');
-
- if (lineSplit[0][0] == 'K')
- {
- return new GameEvent()
- {
- Type = GameEvent.EventType.Kill,
- Data = cleanedEventLine,
- Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)),
- Target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
- Owner = server
- };
- }
-
- if (lineSplit[0][0] == 'D')
- {
- return new GameEvent()
- {
- Type = GameEvent.EventType.Damage,
- Data = cleanedEventLine,
- Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)),
- Target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
- Owner = server
- };
- }
-
- if (lineSplit[0] == "say" || lineSplit[0] == "sayteam")
- {
- return new GameEvent()
- {
- Type = GameEvent.EventType.Say,
- Data = lineSplit[4],
- Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
- Owner = server,
- Message = lineSplit[4]
- };
- }
-
- if (lineSplit[0].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 (lineSplit[0].Contains("InitGame"))
- {
- string dump = cleanedEventLine.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
- };
- }*/
-
public override string GetGameDir() => $"t6r{Path.DirectorySeparatorChar}data";
}
}
diff --git a/Application/GameEventHandler.cs b/Application/GameEventHandler.cs
index 360204527..d23d74d95 100644
--- a/Application/GameEventHandler.cs
+++ b/Application/GameEventHandler.cs
@@ -8,61 +8,20 @@ namespace IW4MAdmin.Application
{
class GameEventHandler : IEventHandler
{
- private ConcurrentQueue EventQueue;
- private Queue DelayedEventQueue;
- private IManager Manager;
+ private readonly IManager Manager;
public GameEventHandler(IManager mgr)
{
- EventQueue = new ConcurrentQueue();
- DelayedEventQueue = new Queue();
-
Manager = mgr;
}
- public void AddEvent(GameEvent gameEvent, bool delayedExecution = false)
+ public void AddEvent(GameEvent gameEvent)
{
#if DEBUG
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner}");
#endif
- if (delayedExecution)
- {
- DelayedEventQueue.Enqueue(gameEvent);
- }
- else
- {
- EventQueue.Enqueue(gameEvent);
- Manager.SetHasEvent();
- }
-#if DEBUG
- Manager.GetLogger().WriteDebug($"There are now {EventQueue.Count} events in queue");
-#endif
- }
-
- public string[] GetEventOutput()
- {
- throw new NotImplementedException();
- }
-
- public GameEvent GetNextEvent()
- {
- if (EventQueue.Count > 0)
- {
-#if DEBUG
- Manager.GetLogger().WriteDebug("Getting next event to be processed");
-#endif
- if (!EventQueue.TryDequeue(out GameEvent newEvent))
- {
- Manager.GetLogger().WriteError("Could not dequeue event for processing");
- }
-
- else
- {
- return newEvent;
- }
- }
-
- return null;
+ // todo: later
+ ((Manager as ApplicationManager).OnServerEvent)(this, new ApplicationManager.GameEventArgs(null, false, gameEvent));
}
}
}
diff --git a/Application/Main.cs b/Application/Main.cs
index d8a4787c7..21e535c79 100644
--- a/Application/Main.cs
+++ b/Application/Main.cs
@@ -166,7 +166,7 @@ namespace IW4MAdmin.Application
}
OnShutdownComplete.Reset();
- ServerManager.Start().Wait();
+ ServerManager.Start();
ServerManager.Logger.WriteVerbose(loc["MANAGER_SHUTDOWN_SUCCESS"]);
OnShutdownComplete.Set();
}
diff --git a/Application/Manager.cs b/Application/Manager.cs
index 102c69341..8bc28edfd 100644
--- a/Application/Manager.cs
+++ b/Application/Manager.cs
@@ -32,7 +32,11 @@ namespace IW4MAdmin.Application
public ILogger Logger { get; private set; }
public bool Running { get; private set; }
public bool IsInitialized { get; private set; }
- public EventHandler ServerEventOccurred { get; private set; }
+ //public EventHandler ServerEventOccurred { get; private set; }
+ // define what the delagate function looks like
+ public delegate void OnServerEventEventHandler(object sender, GameEventArgs e);
+ // expose the event handler so we can execute the events
+ public OnServerEventEventHandler OnServerEvent { get; private set; }
public DateTime StartTime { get; private set; }
static ApplicationManager Instance;
@@ -48,6 +52,17 @@ namespace IW4MAdmin.Application
ManualResetEventSlim OnEvent;
readonly IPageList PageList;
+ public class GameEventArgs : System.ComponentModel.AsyncCompletedEventArgs
+ {
+
+ public GameEventArgs(Exception error, bool cancelled, GameEvent userState) : base(error, cancelled, userState)
+ {
+ Event = userState;
+ }
+
+ public GameEvent Event { get; }
+ }
+
private ApplicationManager()
{
Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log");
@@ -60,11 +75,96 @@ namespace IW4MAdmin.Application
PenaltySvc = new PenaltyService();
PrivilegedClients = new Dictionary();
Api = new EventApi();
- ServerEventOccurred += Api.OnServerEvent;
+ //ServerEventOccurred += Api.OnServerEvent;
ConfigHandler = new BaseConfigurationHandler("IW4MAdminSettings");
StartTime = DateTime.UtcNow;
OnEvent = new ManualResetEventSlim();
PageList = new PageList();
+ OnServerEvent += OnServerEventAsync;
+ }
+
+ private async void OnServerEventAsync(object sender, GameEventArgs args)
+ {
+ var newEvent = args.Event;
+
+ 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");
+ // offload it to the player to keep
+ newEvent.Origin.DelayedEvents.Enqueue(newEvent);
+ return;
+ }
+
+ // if the target client is not in an authorized state (detected by RCon) don't execute the event
+ if (GameEvent.ShouldTargetEventBeDelayed(newEvent))
+ {
+ Logger.WriteDebug($"Delaying target execution of event type {newEvent.Type} for {newEvent.Target} because they are not authed");
+ // offload it to the player to keep
+ newEvent.Target.DelayedEvents.Enqueue(newEvent);
+ return;
+ }
+
+ await newEvent.Owner.ExecuteEvent(newEvent);
+
+ //// todo: this is a hacky mess
+ if (newEvent.Origin?.DelayedEvents?.Count > 0 &&
+ newEvent.Origin?.State == Player.ClientState.Connected)
+ {
+ var events = newEvent.Origin.DelayedEvents;
+
+ // add the delayed event to the queue
+ while (events?.Count > 0)
+ {
+ var e = events.Dequeue();
+ e.Origin = newEvent.Origin;
+ // check if the target was assigned
+ if (e.Target != null)
+ {
+ // update the target incase they left or have newer info
+ e.Target = newEvent.Owner.GetPlayersAsList()
+ .FirstOrDefault(p => p.NetworkId == e.Target.NetworkId);
+ // we have to throw out the event because they left
+ if (e.Target == null)
+ {
+ Logger.WriteWarning($"Delayed event for {e.Origin} was removed because the target has left");
+ continue;
+ }
+ }
+ this.GetEventHandler().AddEvent(e);
+ }
+ }
+#if DEBUG
+ Logger.WriteDebug("Processed Event");
+#endif
+ }
+
+ // this happens if a plugin requires login
+ catch (AuthorizationException ex)
+ {
+ await newEvent.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMAND_NOTAUTHORIZED"]} - {ex.Message}");
+ }
+
+ catch (NetworkException ex)
+ {
+ Logger.WriteError(ex.Message);
+ }
+
+ catch (ServerException ex)
+ {
+ Logger.WriteWarning(ex.Message);
+ }
+
+ catch (Exception ex)
+ {
+ Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}");
+ Logger.WriteDebug("Error Message: " + ex.Message);
+ Logger.WriteDebug("Error Trace: " + ex.StackTrace);
+ }
+ // tell anyone waiting for the output that we're done
+ newEvent.OnProcessed.Set();
}
public IList GetServers()
@@ -91,7 +191,9 @@ namespace IW4MAdmin.Application
{
// select the server ids that have completed the update task
var serverTasksToRemove = runningUpdateTasks
- .Where(ut => ut.Value.Status != TaskStatus.Running)
+ .Where(ut => ut.Value.Status == TaskStatus.RanToCompletion ||
+ ut.Value.Status == TaskStatus.Canceled ||
+ ut.Value.Status == TaskStatus.Faulted)
.Select(ut => ut.Key)
.ToList();
@@ -109,7 +211,8 @@ namespace IW4MAdmin.Application
}
// select the servers where the tasks have completed
- foreach (var server in Servers.Where(s => serverTasksToRemove.Count == 0 ? true : serverTasksToRemove.Contains(GetHashCode())))
+ 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())))
{
runningUpdateTasks.Add(server.GetHashCode(), Task.Run(async () =>
{
@@ -133,7 +236,7 @@ namespace IW4MAdmin.Application
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
#endif
#if DEBUG
- await Task.Delay(30000);
+ await Task.Delay(10000);
#else
await Task.Delay(ConfigHandler.Configuration().RConPollRate);
#endif
@@ -397,7 +500,7 @@ namespace IW4MAdmin.Application
}
}
- public async Task Start()
+ public void Start()
{
// this needs to be run seperately from the main thread
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
@@ -408,101 +511,11 @@ namespace IW4MAdmin.Application
Task.Run(() => UpdateServerStates());
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
- var eventList = new List();
-
- async Task processEvent(GameEvent newEvent)
- {
- try
- {
- await newEvent.Owner.ExecuteEvent(newEvent);
-
- // todo: this is a hacky mess
- if (newEvent.Origin?.DelayedEvents?.Count > 0 &&
- newEvent.Origin?.State == Player.ClientState.Connected)
- {
- var events = newEvent.Origin.DelayedEvents;
-
- // add the delayed event to the queue
- while (events?.Count > 0)
- {
- var e = events.Dequeue();
- e.Origin = newEvent.Origin;
- // check if the target was assigned
- if (e.Target != null)
- {
- // update the target incase they left or have newer info
- e.Target = newEvent.Owner.GetPlayersAsList()
- .FirstOrDefault(p => p.NetworkId == e.Target.NetworkId);
- // we have to throw out the event because they left
- if (e.Target == null)
- {
- Logger.WriteWarning($"Delayed event for {e.Origin} was removed because the target has left");
- continue;
- }
- }
- this.GetEventHandler().AddEvent(e);
- }
- }
-#if DEBUG
- Logger.WriteDebug("Processed Event");
-#endif
- }
-
- // this happens if a plugin requires login
- catch (AuthorizationException e)
- {
- await newEvent.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMAND_NOTAUTHORIZED"]} - {e.Message}");
- }
-
- catch (NetworkException e)
- {
- Logger.WriteError(e.Message);
- }
-
- catch (Exception E)
- {
- Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}");
- Logger.WriteDebug("Error Message: " + E.Message);
- Logger.WriteDebug("Error Trace: " + E.StackTrace);
- }
- // tell anyone waiting for the output that we're done
- newEvent.OnProcessed.Set();
- };
-
- GameEvent queuedEvent = null;
-
while (Running)
{
- // wait for new event to be added
OnEvent.Wait();
- while ((queuedEvent = Handler.GetNextEvent()) != null)
- {
- if (GameEvent.ShouldOriginEventBeDelayed(queuedEvent))
- {
- Logger.WriteDebug($"Delaying origin execution of event type {queuedEvent.Type} for {queuedEvent.Origin} because they are not authed");
- // offload it to the player to keep
- queuedEvent.Origin.DelayedEvents.Enqueue(queuedEvent);
- continue;
- }
-
- if (GameEvent.ShouldTargetEventBeDelayed(queuedEvent))
- {
- Logger.WriteDebug($"Delaying target execution of event type {queuedEvent.Type} for {queuedEvent.Target} because they are not authed");
- // offload it to the player to keep
- queuedEvent.Target.DelayedEvents.Enqueue(queuedEvent);
- continue;
- }
- // for delayed events, they're added after the connect event so it should work out
- await processEvent(queuedEvent);
- }
-
- // signal that all events have been processed
OnEvent.Reset();
}
-#if !DEBUG
- foreach (var S in _servers)
- await S.Broadcast("^1" + Utilities.CurrentLocalization.LocalizationIndex["BROADCAST_OFFLINE"]);
-#endif
_servers.Clear();
}
diff --git a/Application/RconParsers/IW4RConParser.cs b/Application/RconParsers/IW4RConParser.cs
index af37227ef..ead23c3d1 100644
--- a/Application/RconParsers/IW4RConParser.cs
+++ b/Application/RconParsers/IW4RConParser.cs
@@ -114,7 +114,8 @@ namespace IW4MAdmin.Application.RconParsers
IPAddress = ip == 0 ? int.MinValue : ip,
Ping = ping,
Score = score,
- IsBot = ip == 0
+ IsBot = ip == 0,
+ State = Player.ClientState.Connecting
};
StatusPlayers.Add(P);
diff --git a/Application/RconParsers/IW5MRConParser.cs b/Application/RconParsers/IW5MRConParser.cs
index 4112e0ef4..baec8387f 100644
--- a/Application/RconParsers/IW5MRConParser.cs
+++ b/Application/RconParsers/IW5MRConParser.cs
@@ -153,7 +153,8 @@ namespace IW4MAdmin.WApplication.RconParsers
IPAddress = ipAddress,
Ping = Ping,
Score = score,
- IsBot = false
+ IsBot = false,
+ State = Player.ClientState.Connecting
};
StatusPlayers.Add(p);
diff --git a/Application/RconParsers/T6MRConParser.cs b/Application/RconParsers/T6MRConParser.cs
index 48016f0b3..485d1a64d 100644
--- a/Application/RconParsers/T6MRConParser.cs
+++ b/Application/RconParsers/T6MRConParser.cs
@@ -185,6 +185,7 @@ namespace IW4MAdmin.Application.RconParsers
IPAddress = ipAddress,
Ping = Ping,
Score = score,
+ State = Player.ClientState.Connecting,
IsBot = networkId == 0
};
diff --git a/Application/Server.cs b/Application/Server.cs
index 7d7542931..dc633944f 100644
--- a/Application/Server.cs
+++ b/Application/Server.cs
@@ -68,32 +68,21 @@ namespace IW4MAdmin
override public async Task AddPlayer(Player polledPlayer)
{
- if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) ||
- polledPlayer.Ping < 1 ||
+ //if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) ||
+ // polledPlayer.Ping < 1 ||
+ if (
polledPlayer.ClientNumber < 0)
{
//Logger.WriteDebug($"Skipping client not in connected state {P}");
- return true;
+ return false;
}
- if (Players[polledPlayer.ClientNumber] != null &&
- Players[polledPlayer.ClientNumber].NetworkId == polledPlayer.NetworkId &&
- Players[polledPlayer.ClientNumber].State == Player.ClientState.Connected)
- {
- // update their ping & score
- Players[polledPlayer.ClientNumber].Ping = polledPlayer.Ping;
- Players[polledPlayer.ClientNumber].Score = polledPlayer.Score;
- return true;
- }
-
- if (Players[polledPlayer.ClientNumber] == null)
+ // set this when they are waiting for authentication
+ if (Players[polledPlayer.ClientNumber] == null &&
+ polledPlayer.State == Player.ClientState.Connecting)
{
Players[polledPlayer.ClientNumber] = polledPlayer;
- }
-
- if (!polledPlayer.IsAuthenticated)
- {
- return true;
+ return false;
}
#if !DEBUG
@@ -196,7 +185,6 @@ namespace IW4MAdmin
player.ClientNumber = polledPlayer.ClientNumber;
player.IsBot = polledPlayer.IsBot;
player.Score = polledPlayer.Score;
- player.IsAuthenticated = true;
player.CurrentServer = this;
Players[player.ClientNumber] = player;
@@ -246,18 +234,9 @@ namespace IW4MAdmin
// they didn't fully connect so empty their slot
Players[player.ClientNumber] = null;
- return true;
+ return false;
}
- var e = new GameEvent()
- {
- Type = GameEvent.EventType.Connect,
- Origin = player,
- Owner = this
- };
-
- Manager.GetEventHandler().AddEvent(e);
-
player.State = Player.ClientState.Connected;
return true;
}
@@ -278,8 +257,10 @@ namespace IW4MAdmin
{
Player Leaving = Players[cNum];
Logger.WriteInfo($"Client {Leaving}, state {Leaving.State.ToString()} disconnecting...");
+ Leaving.State = Player.ClientState.Disconnecting;
- if (!Leaving.IsAuthenticated || Leaving.State != Player.ClientState.Connected)
+ // occurs when the player disconnects via log before being authenticated by RCon
+ if (Leaving.State != Player.ClientState.Connected)
{
Players[cNum] = null;
}
@@ -394,8 +375,29 @@ namespace IW4MAdmin
///
override protected async Task ProcessEvent(GameEvent E)
{
+
+ if (E.Type == GameEvent.EventType.StatusUpdate)
+ {
+ // this event gets called before they're full connected
+ if (E.Origin != null)
+ {
+ //var existingClient = Players[E.Origin.ClientNumber] ?? E.Origin;
+ //existingClient.Ping = E.Origin.Ping;
+ //existingClient.Score = E.Origin.Score;
+ }
+ }
+
if (E.Type == GameEvent.EventType.Connect)
{
+ E.Origin.State = Player.ClientState.Authenticated;
+ // add them to the server
+ if (!await AddPlayer(E.Origin))
+ {
+ throw new ServerException("Player didn't pass authorization, so we are discontinuing event");
+ }
+ // hack makes the event propgate with the correct info
+ E.Origin = Players[E.Origin.ClientNumber];
+
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin?.Name ?? "ERROR!",
@@ -404,7 +406,7 @@ namespace IW4MAdmin
});
if (E.Origin.Level > Player.Permission.Moderator)
- await E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
+ await E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
}
else if (E.Type == GameEvent.EventType.Join)
@@ -427,16 +429,11 @@ namespace IW4MAdmin
Owner = this
};
- if (e.Origin != null)
- {
- e.Origin.State = Player.ClientState.Disconnecting;
- }
-
Manager.GetEventHandler().AddEvent(e);
}
else if (origin != null &&
- origin.State == Player.ClientState.Connecting)
+ origin.State != Player.ClientState.Connected)
{
await RemovePlayer(origin.ClientNumber);
}
@@ -540,61 +537,36 @@ namespace IW4MAdmin
ChatHistory.Clear();
}
- async Task PollPlayersAsync()
+ ///
+ /// lists the connecting and disconnecting clients via RCon response
+ /// array index 0 = connecting clients
+ /// array index 1 = disconnecting clients
+ ///
+ ///
+ async Task[]> PollPlayersAsync()
{
+#if DEBUG
var now = DateTime.Now;
-
- List CurrentPlayers = null;
- try
- {
- CurrentPlayers = await this.GetStatusAsync();
- }
-
- // when the server has lost connection
- catch (NetworkException)
- {
- Throttled = true;
- return ClientNum;
- }
+#endif
+ var currentClients = GetPlayersAsList();
+ var polledClients = await this.GetStatusAsync();
#if DEBUG
Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms");
#endif
Throttled = false;
- var clients = GetPlayersAsList();
- foreach (var client in clients)
+ foreach(var client in polledClients)
{
- // remove players that have disconnected
- if (!CurrentPlayers.Select(c => c.NetworkId).Contains(client.NetworkId))
- {
- // the log should already have started a disconnect event
- if (client.State == Player.ClientState.Disconnecting)
- continue;
-
- var e = new GameEvent()
- {
- Type = GameEvent.EventType.Disconnect,
- Origin = client,
- Owner = this
- };
-
- client.State = Player.ClientState.Disconnecting;
-
- Manager.GetEventHandler().AddEvent(e);
- // todo: needed?
- // wait until the disconnect event is complete
- e.OnProcessed.Wait();
- }
+ // todo: move out somehwere
+ var existingClient = Players[client.ClientNumber] ?? client;
+ existingClient.Ping = client.Ping;
+ existingClient.Score = client.Score;
}
- AuthQueue.AuthenticateClients(CurrentPlayers);
+ var disconnectingClients = currentClients.Except(polledClients);
+ var connectingClients = polledClients.Except(currentClients);
- foreach (var c in AuthQueue.GetAuthenticatedClients())
- {
- await AddPlayer(c);
- }
-
- return CurrentPlayers.Count;
+ return new List[] { connectingClients.ToList(), disconnectingClients.ToList() };
}
DateTime start = DateTime.Now;
@@ -621,8 +593,49 @@ namespace IW4MAdmin
try
{
- int polledPlayerCount = await PollPlayersAsync();
+ var polledClients = await PollPlayersAsync();
+ var waiterList = new List();
+ foreach (var disconnectingClient in polledClients[1])
+ {
+ if (disconnectingClient.State == Player.ClientState.Disconnecting)
+ {
+ continue;
+ }
+
+ var e = new GameEvent()
+ {
+ Type = GameEvent.EventType.Disconnect,
+ Origin = disconnectingClient,
+ Owner = this
+ };
+
+ Manager.GetEventHandler().AddEvent(e);
+ // wait until the disconnect event is complete
+ // because we don't want to try to fill up a slot that's not empty yet
+ waiterList.Add(e.OnProcessed);
+ }
+ // wait for all the disconnect tasks to finish
+ await Task.WhenAll(waiterList.Select(t => Task.Run(() => t.Wait(5000))));
+
+ waiterList.Clear();
+ // this are our new connecting clients
+ foreach (var client in polledClients[0])
+ {
+ var e = new GameEvent()
+ {
+ Type = GameEvent.EventType.Connect,
+ Origin = client,
+ Owner = this
+ };
+
+ Manager.GetEventHandler().AddEvent(e);
+ waiterList.Add(e.OnProcessed);
+ }
+
+ // wait for all the connect tasks to finish
+ await Task.WhenAll(waiterList.Select(t => Task.Run(() => t.Wait())));
+
if (ConnectionErrors > 0)
{
Logger.WriteVerbose($"{loc["MANAGER_CONNECTION_REST"]} {IP}:{Port}");
@@ -793,7 +806,7 @@ namespace IW4MAdmin
CustomCallback = await ScriptLoaded();
string mainPath = EventParser.GetGameDir();
#if DEBUG
- basepath.Value = @"\\192.168.88.253\Call of Duty Black Ops II";
+ basepath.Value = @"";
#endif
string logPath;
if (GameName == Game.IW5)
diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs
index e7e667d31..f1d4391de 100644
--- a/Plugins/Stats/Helpers/StatManager.cs
+++ b/Plugins/Stats/Helpers/StatManager.cs
@@ -831,7 +831,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
double currentKDR = clientStats.SessionDeaths == 0 ? clientStats.SessionKills : clientStats.SessionKills / clientStats.SessionDeaths;
double alpha = Math.Sqrt(2) / Math.Min(600, clientStats.Kills + clientStats.Deaths);
clientStats.RollingWeightedKDR = (alpha * currentKDR) + (1.0 - alpha) * clientStats.KDR;
- double KDRWeight = Math.Round(Math.Pow(clientStats.RollingWeightedKDR, 1.637 / Math.E), 3);
+ double KDRWeight = clientStats.RollingWeightedKDR != 0 ? Math.Round(Math.Pow(clientStats.RollingWeightedKDR, 1.637 / Math.E), 3) : 0;
// calculate the weight of the new play time against last 10 hours of gameplay
int totalPlayTime = (clientStats.TimePlayed == 0) ?
diff --git a/Plugins/Tests/Plugin.cs b/Plugins/Tests/Plugin.cs
index 76b194c56..8aaab03cb 100644
--- a/Plugins/Tests/Plugin.cs
+++ b/Plugins/Tests/Plugin.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Helpers;
+using SharedLibraryCore.Objects;
namespace IW4MAdmin.Plugins
{
@@ -19,34 +20,85 @@ namespace IW4MAdmin.Plugins
public async Task OnEventAsync(GameEvent E, Server S)
{
+ return;
if (E.Type == GameEvent.EventType.Start)
{
- #region PLAYER_HISTORY
- var rand = new Random(GetHashCode());
- var time = DateTime.UtcNow;
+ #region UNIT_TEST_LOG_CONNECT
+ for (int i = 1; i <= 8; i++)
+ {
+ var e = new GameEvent()
+ {
+ Type = GameEvent.EventType.Join,
+ Origin = new Player()
+ {
+ Name = $"Player{i}",
+ NetworkId = i,
+ ClientNumber = i - 1
+ },
+ Owner = S
+ };
- await Task.Run(() =>
- {
- if (S.PlayerHistory.Count > 0)
- return;
+ S.Manager.GetEventHandler().AddEvent(e);
+ e.OnProcessed.Wait();
+ }
- while (S.PlayerHistory.Count < 144)
- {
- S.PlayerHistory.Enqueue(new PlayerHistory(time, rand.Next(7, 18)));
- time = time.AddMinutes(PlayerHistory.UpdateInterval);
- }
- });
+ S.Logger.WriteAssert(S.ClientNum == 8, "UNIT_TEST_LOG_CONNECT failed client num check");
#endregion
- #region PLUGIN_INFO
- Console.WriteLine("|Name |Alias|Description |Requires Target|Syntax |Required Level|");
- Console.WriteLine("|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|");
- foreach (var command in S.Manager.GetCommands().OrderByDescending(c => c.Permission).ThenBy(c => c.Name))
+ #region UNIT_TEST_RCON_AUTHENTICATE
+ for (int i = 1; i <= 8; i++)
{
- Console.WriteLine($"|{command.Name}|{command.Alias}|{command.Description}|{command.RequiresTarget}|{command.Syntax.Substring(8).EscapeMarkdown()}|{command.Permission}|");
+ var e = new GameEvent()
+ {
+ Type = GameEvent.EventType.Connect,
+ Origin = new Player()
+ {
+ Name = $"Player{i}",
+ NetworkId = i,
+ ClientNumber = i - 1,
+ IPAddress = i,
+ Ping = 50,
+ CurrentServer = S
+ },
+ Owner = S,
+ };
+
+ S.Manager.GetEventHandler().AddEvent(e);
+ e.OnProcessed.Wait();
}
+
+ S.Logger.WriteAssert(S.GetPlayersAsList().Count(p => p.State == Player.ClientState.Connected) == 8,
+ "UNIT_TEST_RCON_AUTHENTICATE failed client num connected state check");
#endregion
}
+ //if (E.Type == GameEvent.EventType.Start)
+ //{
+ // #region PLAYER_HISTORY
+ // var rand = new Random(GetHashCode());
+ // var time = DateTime.UtcNow;
+
+ // await Task.Run(() =>
+ // {
+ // if (S.PlayerHistory.Count > 0)
+ // return;
+
+ // while (S.PlayerHistory.Count < 144)
+ // {
+ // S.PlayerHistory.Enqueue(new PlayerHistory(time, rand.Next(7, 18)));
+ // time = time.AddMinutes(PlayerHistory.UpdateInterval);
+ // }
+ // });
+ // #endregion
+
+ // #region PLUGIN_INFO
+ // Console.WriteLine("|Name |Alias|Description |Requires Target|Syntax |Required Level|");
+ // Console.WriteLine("|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|");
+ // foreach (var command in S.Manager.GetCommands().OrderByDescending(c => c.Permission).ThenBy(c => c.Name))
+ // {
+ // Console.WriteLine($"|{command.Name}|{command.Alias}|{command.Description}|{command.RequiresTarget}|{command.Syntax.Substring(8).EscapeMarkdown()}|{command.Permission}|");
+ // }
+ // #endregion
+ //}
}
public Task OnLoadAsync(IManager manager) => Task.CompletedTask;
@@ -54,84 +106,84 @@ namespace IW4MAdmin.Plugins
public Task OnTickAsync(Server S)
{
return Task.CompletedTask;
- /*
- if ((DateTime.Now - Interval).TotalSeconds > 1)
- {
- var rand = new Random();
- int index = rand.Next(0, 17);
- var p = new Player()
- {
- Name = $"Test_{index}",
- NetworkId = (long)$"_test_{index}".GetHashCode(),
- ClientNumber = index,
- Ping = 1,
- IPAddress = $"127.0.0.{index}".ConvertToIP()
- };
+ /*
+ if ((DateTime.Now - Interval).TotalSeconds > 1)
+ {
+ var rand = new Random();
+ int index = rand.Next(0, 17);
+ var p = new Player()
+ {
+ Name = $"Test_{index}",
+ NetworkId = (long)$"_test_{index}".GetHashCode(),
+ ClientNumber = index,
+ Ping = 1,
+ IPAddress = $"127.0.0.{index}".ConvertToIP()
+ };
- if (S.Players.ElementAt(index) != null)
- await S.RemovePlayer(index);
- // await S.AddPlayer(p);
+ if (S.Players.ElementAt(index) != null)
+ await S.RemovePlayer(index);
+ // await S.AddPlayer(p);
- Interval = DateTime.Now;
- if (S.ClientNum > 0)
- {
- var victimPlayer = S.Players.Where(pl => pl != null).ToList()[rand.Next(0, S.ClientNum - 1)];
- var attackerPlayer = S.Players.Where(pl => pl != null).ToList()[rand.Next(0, S.ClientNum - 1)];
+ Interval = DateTime.Now;
+ if (S.ClientNum > 0)
+ {
+ var victimPlayer = S.Players.Where(pl => pl != null).ToList()[rand.Next(0, S.ClientNum - 1)];
+ var attackerPlayer = S.Players.Where(pl => pl != null).ToList()[rand.Next(0, S.ClientNum - 1)];
- await S.ExecuteEvent(new Event(Event.GType.Say, $"test_{attackerPlayer.ClientNumber}", victimPlayer, attackerPlayer, S));
+ await S.ExecuteEvent(new Event(Event.GType.Say, $"test_{attackerPlayer.ClientNumber}", victimPlayer, attackerPlayer, S));
- string[] eventLine = null;
+ string[] eventLine = null;
- for (int i = 0; i < 1; i++)
- {
- if (S.GameName == Server.Game.IW4)
- {
+ for (int i = 0; i < 1; i++)
+ {
+ if (S.GameName == Server.Game.IW4)
+ {
- // attackerID ; victimID ; attackerOrigin ; victimOrigin ; Damage ; Weapon ; hitLocation ; meansOfDeath
- var minimapInfo = StatsPlugin.MinimapConfig.IW4Minimaps().MapInfo.FirstOrDefault(m => m.MapName == S.CurrentMap.Name);
- if (minimapInfo == null)
- return;
- eventLine = new string[]
+ // attackerID ; victimID ; attackerOrigin ; victimOrigin ; Damage ; Weapon ; hitLocation ; meansOfDeath
+ var minimapInfo = StatsPlugin.MinimapConfig.IW4Minimaps().MapInfo.FirstOrDefault(m => m.MapName == S.CurrentMap.Name);
+ if (minimapInfo == null)
+ return;
+ eventLine = new string[]
+ {
+ "ScriptKill",
+ attackerPlayer.NetworkId.ToString(),
+ victimPlayer.NetworkId.ToString(),
+ new Vector3(rand.Next(minimapInfo.MaxRight, minimapInfo.MaxLeft), rand.Next(minimapInfo.MaxBottom, minimapInfo.MaxTop), rand.Next(0, 100)).ToString(),
+ new Vector3(rand.Next(minimapInfo.MaxRight, minimapInfo.MaxLeft), rand.Next(minimapInfo.MaxBottom, minimapInfo.MaxTop), rand.Next(0, 100)).ToString(),
+ rand.Next(50, 105).ToString(),
+ ((StatsPlugin.IW4Info.WeaponName)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.WeaponName)).Length - 1)).ToString(),
+ ((StatsPlugin.IW4Info.HitLocation)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.HitLocation)).Length - 1)).ToString(),
+ ((StatsPlugin.IW4Info.MeansOfDeath)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.MeansOfDeath)).Length - 1)).ToString()
+ };
+
+ }
+ else
+ {
+ eventLine = new string[]
{
- "ScriptKill",
- attackerPlayer.NetworkId.ToString(),
- victimPlayer.NetworkId.ToString(),
- new Vector3(rand.Next(minimapInfo.MaxRight, minimapInfo.MaxLeft), rand.Next(minimapInfo.MaxBottom, minimapInfo.MaxTop), rand.Next(0, 100)).ToString(),
- new Vector3(rand.Next(minimapInfo.MaxRight, minimapInfo.MaxLeft), rand.Next(minimapInfo.MaxBottom, minimapInfo.MaxTop), rand.Next(0, 100)).ToString(),
- rand.Next(50, 105).ToString(),
- ((StatsPlugin.IW4Info.WeaponName)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.WeaponName)).Length - 1)).ToString(),
- ((StatsPlugin.IW4Info.HitLocation)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.HitLocation)).Length - 1)).ToString(),
- ((StatsPlugin.IW4Info.MeansOfDeath)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.MeansOfDeath)).Length - 1)).ToString()
+ "K",
+ victimPlayer.NetworkId.ToString(),
+ victimPlayer.ClientNumber.ToString(),
+ rand.Next(0, 1) == 0 ? "allies" : "axis",
+ victimPlayer.Name,
+ attackerPlayer.NetworkId.ToString(),
+ attackerPlayer.ClientNumber.ToString(),
+ rand.Next(0, 1) == 0 ? "allies" : "axis",
+ attackerPlayer.Name.ToString(),
+ ((StatsPlugin.IW4Info.WeaponName)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.WeaponName)).Length - 1)).ToString(), // Weapon
+ rand.Next(50, 105).ToString(), // Damage
+ ((StatsPlugin.IW4Info.MeansOfDeath)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.MeansOfDeath)).Length - 1)).ToString(), // Means of Death
+ ((StatsPlugin.IW4Info.HitLocation)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.HitLocation)).Length - 1)).ToString(), // Hit Location
};
+ }
- }
- else
- {
- eventLine = new string[]
- {
- "K",
- victimPlayer.NetworkId.ToString(),
- victimPlayer.ClientNumber.ToString(),
- rand.Next(0, 1) == 0 ? "allies" : "axis",
- victimPlayer.Name,
- attackerPlayer.NetworkId.ToString(),
- attackerPlayer.ClientNumber.ToString(),
- rand.Next(0, 1) == 0 ? "allies" : "axis",
- attackerPlayer.Name.ToString(),
- ((StatsPlugin.IW4Info.WeaponName)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.WeaponName)).Length - 1)).ToString(), // Weapon
- rand.Next(50, 105).ToString(), // Damage
- ((StatsPlugin.IW4Info.MeansOfDeath)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.MeansOfDeath)).Length - 1)).ToString(), // Means of Death
- ((StatsPlugin.IW4Info.HitLocation)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.HitLocation)).Length - 1)).ToString(), // Hit Location
- };
- }
-
- var _event = Event.ParseEventString(eventLine, S);
- await S.ExecuteEvent(_event);
- }
- }
- }
- */
+ var _event = Event.ParseEventString(eventLine, S);
+ await S.ExecuteEvent(_event);
+ }
+ }
+ }
+ */
}
public Task OnUnloadAsync() => Task.CompletedTask;
diff --git a/SharedLibraryCore/Event.cs b/SharedLibraryCore/Event.cs
index b5db77d50..da9606492 100644
--- a/SharedLibraryCore/Event.cs
+++ b/SharedLibraryCore/Event.cs
@@ -40,6 +40,8 @@ namespace SharedLibraryCore
Damage,
Kill,
JoinTeam,
+
+ StatusUpdate
}
public GameEvent(EventType t, string d, Player O, Player T, Server S)
@@ -86,9 +88,9 @@ namespace SharedLibraryCore
public static bool ShouldOriginEventBeDelayed(GameEvent queuedEvent)
{
return queuedEvent.Origin != null &&
- !queuedEvent.Origin.IsAuthenticated &&
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 &&
// we don't care about unknown events
@@ -104,7 +106,6 @@ namespace SharedLibraryCore
public static bool ShouldTargetEventBeDelayed(GameEvent queuedEvent)
{
return queuedEvent.Target != null &&
- !queuedEvent.Target.IsAuthenticated &&
queuedEvent.Target.State != Player.ClientState.Connected &&
queuedEvent.Target.NetworkId != 0;
}
diff --git a/SharedLibraryCore/Interfaces/IEventHandler.cs b/SharedLibraryCore/Interfaces/IEventHandler.cs
index 5bf54fc73..c92c5afd7 100644
--- a/SharedLibraryCore/Interfaces/IEventHandler.cs
+++ b/SharedLibraryCore/Interfaces/IEventHandler.cs
@@ -13,17 +13,6 @@ namespace SharedLibraryCore.Interfaces
/// Add a game event event to the queue to be processed
///
/// Game event
- /// don't signal that an event has been aded
- void AddEvent(GameEvent gameEvent, bool delayedExecution = false);
- ///
- /// Get the next event to be processed
- ///
- /// Game event that needs to be processed
- GameEvent GetNextEvent();
- ///
- /// If an event has output. Like executing a command wait until it's available
- ///
- /// List of output strings
- string[] GetEventOutput();
+ void AddEvent(GameEvent gameEvent);
}
}
diff --git a/SharedLibraryCore/Interfaces/ILogger.cs b/SharedLibraryCore/Interfaces/ILogger.cs
index fa04182af..57049b526 100644
--- a/SharedLibraryCore/Interfaces/ILogger.cs
+++ b/SharedLibraryCore/Interfaces/ILogger.cs
@@ -13,5 +13,6 @@ namespace SharedLibraryCore.Interfaces
void WriteDebug(string msg);
void WriteWarning(string msg);
void WriteError(string msg);
+ void WriteAssert(bool condition, string msg);
}
}
diff --git a/SharedLibraryCore/Interfaces/IManager.cs b/SharedLibraryCore/Interfaces/IManager.cs
index a37bdb966..f14e0ff06 100644
--- a/SharedLibraryCore/Interfaces/IManager.cs
+++ b/SharedLibraryCore/Interfaces/IManager.cs
@@ -11,7 +11,7 @@ namespace SharedLibraryCore.Interfaces
public interface IManager
{
Task Init();
- Task Start();
+ void Start();
void Stop();
ILogger GetLogger();
IList GetServers();
diff --git a/SharedLibraryCore/Objects/Player.cs b/SharedLibraryCore/Objects/Player.cs
index 334e6550d..39d8f9b98 100644
--- a/SharedLibraryCore/Objects/Player.cs
+++ b/SharedLibraryCore/Objects/Player.cs
@@ -11,8 +11,24 @@ namespace SharedLibraryCore.Objects
{
public enum ClientState
{
+ ///
+ /// represents when the client has been detected as joining
+ /// by the log file, but has not be authenticated by RCon
+ ///
Connecting,
+ ///
+ /// represents when the client has been parsed by RCon,
+ /// but has not been validated against the database
+ ///
+ Authenticated,
+ ///
+ /// represents when the client has been authenticated by RCon
+ /// and validated by the database
+ ///
Connected,
+ ///
+ /// represents when the client is leaving (either through RCon or log file)
+ ///
Disconnecting,
}
@@ -117,8 +133,6 @@ namespace SharedLibraryCore.Objects
set { _name = value; }
}
[NotMapped]
- public bool IsAuthenticated { get; set; }
- [NotMapped]
public ClientState State { get; set; }
[NotMapped]
public Queue DelayedEvents { get; set; }