reworked event management (again)
almost finished
This commit is contained in:
parent
0538d9f479
commit
56cb8c50e7
@ -5,7 +5,7 @@
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
||||
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
||||
<Version>2.1.4</Version>
|
||||
<Version>2.1.5</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Product>IW4MAdmin</Product>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -8,61 +8,20 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
class GameEventHandler : IEventHandler
|
||||
{
|
||||
private ConcurrentQueue<GameEvent> EventQueue;
|
||||
private Queue<GameEvent> DelayedEventQueue;
|
||||
private IManager Manager;
|
||||
private readonly IManager Manager;
|
||||
|
||||
public GameEventHandler(IManager mgr)
|
||||
{
|
||||
EventQueue = new ConcurrentQueue<GameEvent>();
|
||||
DelayedEventQueue = new Queue<GameEvent>();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ namespace IW4MAdmin.Application
|
||||
}
|
||||
|
||||
OnShutdownComplete.Reset();
|
||||
ServerManager.Start().Wait();
|
||||
ServerManager.Start();
|
||||
ServerManager.Logger.WriteVerbose(loc["MANAGER_SHUTDOWN_SUCCESS"]);
|
||||
OnShutdownComplete.Set();
|
||||
}
|
||||
|
@ -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<GameEvent> ServerEventOccurred { get; private set; }
|
||||
//public EventHandler<GameEvent> 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<int, Player>();
|
||||
Api = new EventApi();
|
||||
ServerEventOccurred += Api.OnServerEvent;
|
||||
//ServerEventOccurred += Api.OnServerEvent;
|
||||
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("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<Server> 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<Task>();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -185,6 +185,7 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
IPAddress = ipAddress,
|
||||
Ping = Ping,
|
||||
Score = score,
|
||||
State = Player.ClientState.Connecting,
|
||||
IsBot = networkId == 0
|
||||
};
|
||||
|
||||
|
@ -68,32 +68,21 @@ namespace IW4MAdmin
|
||||
|
||||
override public async Task<bool> 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
|
||||
/// <returns></returns>
|
||||
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!",
|
||||
@ -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<int> PollPlayersAsync()
|
||||
/// <summary>
|
||||
/// lists the connecting and disconnecting clients via RCon response
|
||||
/// array index 0 = connecting clients
|
||||
/// array index 1 = disconnecting clients
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
async Task<IList<Player>[]> PollPlayersAsync()
|
||||
{
|
||||
#if DEBUG
|
||||
var now = DateTime.Now;
|
||||
|
||||
List<Player> 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<Player>[] { connectingClients.ToList(), disconnectingClients.ToList() };
|
||||
}
|
||||
|
||||
DateTime start = DateTime.Now;
|
||||
@ -621,7 +593,48 @@ namespace IW4MAdmin
|
||||
|
||||
try
|
||||
{
|
||||
int polledPlayerCount = await PollPlayersAsync();
|
||||
var polledClients = await PollPlayersAsync();
|
||||
var waiterList = new List<ManualResetEventSlim>();
|
||||
|
||||
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)
|
||||
{
|
||||
@ -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)
|
||||
|
@ -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) ?
|
||||
|
@ -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;
|
||||
|
||||
await Task.Run(() =>
|
||||
#region UNIT_TEST_LOG_CONNECT
|
||||
for (int i = 1; i <= 8; i++)
|
||||
{
|
||||
if (S.PlayerHistory.Count > 0)
|
||||
return;
|
||||
|
||||
while (S.PlayerHistory.Count < 144)
|
||||
var e = new GameEvent()
|
||||
{
|
||||
S.PlayerHistory.Enqueue(new PlayerHistory(time, rand.Next(7, 18)));
|
||||
time = time.AddMinutes(PlayerHistory.UpdateInterval);
|
||||
Type = GameEvent.EventType.Join,
|
||||
Origin = new Player()
|
||||
{
|
||||
Name = $"Player{i}",
|
||||
NetworkId = i,
|
||||
ClientNumber = i - 1
|
||||
},
|
||||
Owner = S
|
||||
};
|
||||
|
||||
S.Manager.GetEventHandler().AddEvent(e);
|
||||
e.OnProcessed.Wait();
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -13,17 +13,6 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// Add a game event event to the queue to be processed
|
||||
/// </summary>
|
||||
/// <param name="gameEvent">Game event</param>
|
||||
/// <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>
|
||||
/// <returns>Game event that needs to be processed</returns>
|
||||
GameEvent GetNextEvent();
|
||||
/// <summary>
|
||||
/// If an event has output. Like executing a command wait until it's available
|
||||
/// </summary>
|
||||
/// <returns>List of output strings</returns>
|
||||
string[] GetEventOutput();
|
||||
void AddEvent(GameEvent gameEvent);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace SharedLibraryCore.Interfaces
|
||||
public interface IManager
|
||||
{
|
||||
Task Init();
|
||||
Task Start();
|
||||
void Start();
|
||||
void Stop();
|
||||
ILogger GetLogger();
|
||||
IList<Server> GetServers();
|
||||
|
@ -11,8 +11,24 @@ namespace SharedLibraryCore.Objects
|
||||
{
|
||||
public enum ClientState
|
||||
{
|
||||
/// <summary>
|
||||
/// represents when the client has been detected as joining
|
||||
/// by the log file, but has not be authenticated by RCon
|
||||
/// </summary>
|
||||
Connecting,
|
||||
/// <summary>
|
||||
/// represents when the client has been parsed by RCon,
|
||||
/// but has not been validated against the database
|
||||
/// </summary>
|
||||
Authenticated,
|
||||
/// <summary>
|
||||
/// represents when the client has been authenticated by RCon
|
||||
/// and validated by the database
|
||||
/// </summary>
|
||||
Connected,
|
||||
/// <summary>
|
||||
/// represents when the client is leaving (either through RCon or log file)
|
||||
/// </summary>
|
||||
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<GameEvent> DelayedEvents { get; set; }
|
||||
|
Loading…
Reference in New Issue
Block a user