reworked event management (again)

almost finished
This commit is contained in:
RaidMax 2018-08-27 17:07:54 -05:00
parent 0538d9f479
commit 56cb8c50e7
18 changed files with 382 additions and 431 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -166,7 +166,7 @@ namespace IW4MAdmin.Application
}
OnShutdownComplete.Reset();
ServerManager.Start().Wait();
ServerManager.Start();
ServerManager.Logger.WriteVerbose(loc["MANAGER_SHUTDOWN_SUCCESS"]);
OnShutdownComplete.Set();
}

View File

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

View File

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

View File

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

View File

@ -185,6 +185,7 @@ namespace IW4MAdmin.Application.RconParsers
IPAddress = ipAddress,
Ping = Ping,
Score = score,
State = Player.ClientState.Connecting,
IsBot = networkId == 0
};

View File

@ -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!",
@ -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<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,8 +593,49 @@ 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)
{
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ namespace SharedLibraryCore.Interfaces
public interface IManager
{
Task Init();
Task Start();
void Start();
void Stop();
ILogger GetLogger();
IList<Server> GetServers();

View File

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