moved event API stuff around

finally fixed threading issue (which actually had to do with IW4x log outputs being out of sync (not an issue with my code). What a lot of headache over something that wasn't my fault.
This commit is contained in:
RaidMax 2018-08-30 20:53:00 -05:00
parent bbefd53db4
commit 46bdc2ac33
25 changed files with 254 additions and 164 deletions

View File

@ -5,7 +5,7 @@
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish> <MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId> <PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.1.5</Version> <Version>2.1.6</Version>
<Authors>RaidMax</Authors> <Authors>RaidMax</Authors>
<Company>Forever None</Company> <Company>Forever None</Company>
<Product>IW4MAdmin</Product> <Product>IW4MAdmin</Product>

View File

@ -1,27 +1,72 @@
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
class GameEventHandler : IEventHandler class GameEventHandler : IEventHandler
{ {
private readonly IManager Manager; private readonly IManager Manager;
static long NextEventId = 1;
private readonly SortedList<long, GameEvent> OutOfOrderEvents;
private readonly SemaphoreSlim ProcessingEvent;
public GameEventHandler(IManager mgr) public GameEventHandler(IManager mgr)
{ {
Manager = mgr; Manager = mgr;
OutOfOrderEvents = new SortedList<long, GameEvent>();
ProcessingEvent = new SemaphoreSlim(0);
ProcessingEvent.Release();
} }
public void AddEvent(GameEvent gameEvent) public bool AddEvent(GameEvent gameEvent)
{ {
#if DEBUG #if DEBUG
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner}"); Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner} with id {gameEvent.Id}");
#endif #endif
// todo: later while (OutOfOrderEvents.Values.FirstOrDefault()?.Id == Interlocked.Read(ref NextEventId))
((Manager as ApplicationManager).OnServerEvent)(this, new ApplicationManager.GameEventArgs(null, false, gameEvent)); {
lock (OutOfOrderEvents)
{
var fixedEvent = OutOfOrderEvents.Values[0];
OutOfOrderEvents.RemoveAt(0);
AddEvent(fixedEvent);
}
}
// both the gameEvent Id and the LastEventId are thread safe because we want to synchronize when the
// event occurs
if (gameEvent.Id == Interlocked.Read(ref NextEventId))
{
Manager.GetLogger().WriteDebug($"Starting to wait for event with id {gameEvent.Id}");
((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent));
Manager.GetLogger().WriteDebug($"Finished waiting for event with id {gameEvent.Id}");
Interlocked.Increment(ref NextEventId);
}
// a "newer" event has been added before and "older" one has been added (due to threads and context switching)
// so me must wait until the next expected one arrives
else
{
Manager.GetLogger().WriteWarning("Incoming event is out of order");
Manager.GetLogger().WriteDebug($"Expected event Id is {Interlocked.Read(ref NextEventId)}, but got {gameEvent.Id} instead");
// this prevents multiple threads from adding simultaneously
lock (OutOfOrderEvents)
{
if (!OutOfOrderEvents.TryGetValue(gameEvent.Id, out GameEvent discard))
{
OutOfOrderEvents.Add(gameEvent.Id, gameEvent);
}
}
}
return true;
} }
} }
} }

View File

@ -1,7 +1,9 @@
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace IW4MAdmin.Application.IO namespace IW4MAdmin.Application.IO
@ -31,44 +33,36 @@ namespace IW4MAdmin.Application.IO
{ {
Reader = new GameLogReader(gameLogPath, server.EventParser); Reader = new GameLogReader(gameLogPath, server.EventParser);
} }
Server = server; Server = server;
Task.Run(async () =>
{
while (!server.Manager.ShutdownRequested())
{
if ((server.Manager as ApplicationManager).IsInitialized)
{
OnEvent(new EventState()
{
Log = server.Manager.GetLogger(),
ServerId = server.ToString()
});
}
await Task.Delay(100);
}
});
} }
private void OnEvent(object state) public void PollForChanges()
{
while (!Server.Manager.ShutdownRequested())
{
if ((Server.Manager as ApplicationManager).IsInitialized)
{ {
long newLength = Reader.Length;
try try
{ {
UpdateLogEvents(newLength); UpdateLogEvents();
} }
catch (Exception e) catch (Exception e)
{ {
((EventState)state).Log.WriteWarning($"Failed to update log event for {((EventState)state).ServerId}"); Server.Logger.WriteWarning($"Failed to update log event for {Server.GetHashCode()}");
((EventState)state).Log.WriteDebug($"Exception: {e.Message}"); Server.Logger.WriteDebug($"Exception: {e.Message}");
((EventState)state).Log.WriteDebug($"StackTrace: {e.StackTrace}"); Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}");
}
}
Thread.Sleep(Reader.UpdateInterval);
} }
} }
private void UpdateLogEvents(long fileSize) private void UpdateLogEvents()
{ {
long fileSize = Reader.Length;
if (PreviousFileSize == 0) if (PreviousFileSize == 0)
PreviousFileSize = fileSize; PreviousFileSize = fileSize;
@ -79,9 +73,12 @@ namespace IW4MAdmin.Application.IO
PreviousFileSize = fileSize; PreviousFileSize = fileSize;
var events = Reader.EventsFromLog(Server, fileDiff, 0); var events = Reader.ReadEventsFromLog(Server, fileDiff, 0);
foreach (var ev in events) foreach (var ev in events)
{
Server.Manager.GetEventHandler().AddEvent(ev); Server.Manager.GetEventHandler().AddEvent(ev);
}
PreviousFileSize = fileSize; PreviousFileSize = fileSize;
} }

View File

@ -14,7 +14,7 @@ namespace IW4MAdmin.Application.IO
public long Length => new FileInfo(LogFile).Length; public long Length => new FileInfo(LogFile).Length;
public int UpdateInterval => 100; public int UpdateInterval => 300;
public GameLogReader(string logFile, IEventParser parser) public GameLogReader(string logFile, IEventParser parser)
{ {
@ -22,7 +22,7 @@ namespace IW4MAdmin.Application.IO
Parser = parser; Parser = parser;
} }
public ICollection<GameEvent> EventsFromLog(Server server, long fileSizeDiff, long startPosition) public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{ {
// allocate the bytes for the new log lines // allocate the bytes for the new log lines
List<string> logLines = new List<string>(); List<string> logLines = new List<string>();

View File

@ -29,11 +29,12 @@ namespace IW4MAdmin.Application.IO
{ {
using (var cl = new HttpClient()) using (var cl = new HttpClient())
{ {
using (var re = cl.GetAsync($"{LogFile}?length=1").Result) using (var re = cl.GetAsync($"{LogFile}&length=1").Result)
{ {
using (var content = re.Content) using (var content = re.Content)
{ {
return Convert.ToInt64(content.ReadAsStringAsync().Result ?? "0"); string response = content.ReadAsStringAsync().Result ?? "0";
return Convert.ToInt64(response);
} }
} }
} }
@ -42,12 +43,15 @@ namespace IW4MAdmin.Application.IO
public int UpdateInterval => 1000; public int UpdateInterval => 1000;
public ICollection<GameEvent> EventsFromLog(Server server, long fileSizeDiff, long startPosition) public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{ {
#if DEBUG == true
server.Logger.WriteDebug($"Begin reading {fileSizeDiff} from http log");
#endif
string log; string log;
using (var cl = new HttpClient()) using (var cl = new HttpClient())
{ {
using (var re = cl.GetAsync($"{LogFile}?start={fileSizeDiff}").Result) using (var re = cl.GetAsync($"{LogFile}&start={fileSizeDiff}").Result)
{ {
using (var content = re.Content) using (var content = re.Content)
{ {
@ -55,18 +59,29 @@ namespace IW4MAdmin.Application.IO
} }
} }
} }
#if DEBUG == true
server.Logger.WriteDebug($"retrieved events from http log");
#endif
List<GameEvent> events = new List<GameEvent>(); List<GameEvent> events = new List<GameEvent>();
string[] lines = log.Split(Environment.NewLine);
#if DEBUG == true
server.Logger.WriteDebug($"Begin parse of {lines.Length} lines from http log");
#endif
// parse each line // parse each line
foreach (string eventLine in log.Split(Environment.NewLine)) foreach (string eventLine in lines)
{ {
if (eventLine.Length > 0) if (eventLine.Length > 0)
{ {
try try
{ {
// todo: catch elsewhere // todo: catch elsewhere
events.Add(Parser.GetEvent(server, eventLine)); var e = Parser.GetEvent(server, eventLine);
#if DEBUG == true
server.Logger.WriteDebug($"Parsed event with id {e.Id} from http");
#endif
events.Add(e);
} }
catch (Exception e) catch (Exception e)

View File

@ -106,7 +106,7 @@ namespace IW4MAdmin.Application
ServerManager.Init().Wait(); ServerManager.Init().Wait();
var consoleTask = Task.Run(() => var consoleTask = Task.Run(async () =>
{ {
String userInput; String userInput;
Player Origin = ServerManager.GetClientService().Get(1).Result.AsPlayer(); Player Origin = ServerManager.GetClientService().Get(1).Result.AsPlayer();
@ -136,7 +136,7 @@ namespace IW4MAdmin.Application
}; };
ServerManager.GetEventHandler().AddEvent(E); ServerManager.GetEventHandler().AddEvent(E);
E.OnProcessed.Wait(5000); await E.OnProcessed.WaitAsync(30 * 1000);
} }
Console.Write('>'); Console.Write('>');

View File

@ -22,6 +22,7 @@ using System.Text;
using IW4MAdmin.Application.API.Master; using IW4MAdmin.Application.API.Master;
using System.Reflection; using System.Reflection;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using SharedLibraryCore.Events;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
@ -33,7 +34,6 @@ namespace IW4MAdmin.Application
public ILogger Logger { get; private set; } public ILogger Logger { get; private set; }
public bool Running { get; private set; } public bool Running { get; private set; }
public bool IsInitialized { get; private set; } public bool IsInitialized { get; private set; }
//public EventHandler<GameEvent> ServerEventOccurred { get; private set; }
// define what the delagate function looks like // define what the delagate function looks like
public delegate void OnServerEventEventHandler(object sender, GameEventArgs e); public delegate void OnServerEventEventHandler(object sender, GameEventArgs e);
// expose the event handler so we can execute the events // expose the event handler so we can execute the events
@ -48,22 +48,10 @@ namespace IW4MAdmin.Application
readonly AliasService AliasSvc; readonly AliasService AliasSvc;
readonly PenaltyService PenaltySvc; readonly PenaltyService PenaltySvc;
public BaseConfigurationHandler<ApplicationConfiguration> ConfigHandler; public BaseConfigurationHandler<ApplicationConfiguration> ConfigHandler;
EventApi Api;
GameEventHandler Handler; GameEventHandler Handler;
ManualResetEventSlim OnQuit; ManualResetEventSlim OnQuit;
readonly IPageList PageList; 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() private ApplicationManager()
{ {
Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log"); Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log");
@ -75,13 +63,12 @@ namespace IW4MAdmin.Application
AliasSvc = new AliasService(); AliasSvc = new AliasService();
PenaltySvc = new PenaltyService(); PenaltySvc = new PenaltyService();
PrivilegedClients = new Dictionary<int, Player>(); PrivilegedClients = new Dictionary<int, Player>();
Api = new EventApi();
//ServerEventOccurred += Api.OnServerEvent;
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings"); ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
StartTime = DateTime.UtcNow; StartTime = DateTime.UtcNow;
OnQuit = new ManualResetEventSlim(); OnQuit = new ManualResetEventSlim();
PageList = new PageList(); PageList = new PageList();
OnServerEvent += OnServerEventAsync; OnServerEvent += OnServerEventAsync;
OnServerEvent += EventApi.OnGameEvent;
} }
private async void OnServerEventAsync(object sender, GameEventArgs args) private async void OnServerEventAsync(object sender, GameEventArgs args)
@ -108,7 +95,7 @@ namespace IW4MAdmin.Application
return; return;
} }
await newEvent.Owner.ExecuteEvent(newEvent);
//// todo: this is a hacky mess //// todo: this is a hacky mess
if (newEvent.Origin?.DelayedEvents.Count > 0 && if (newEvent.Origin?.DelayedEvents.Count > 0 &&
@ -119,7 +106,20 @@ namespace IW4MAdmin.Application
// add the delayed event to the queue // add the delayed event to the queue
while(events.Count > 0) while(events.Count > 0)
{ {
var e = events.Dequeue(); var oldEvent = events.Dequeue();
var e = new GameEvent()
{
Type = oldEvent.Type,
Origin = newEvent.Origin,
Data = oldEvent.Data,
Extra = oldEvent.Extra,
Owner = oldEvent.Owner,
Message = oldEvent.Message,
Target = oldEvent.Target,
OnProcessed = oldEvent.OnProcessed,
Remote = oldEvent.Remote
};
e.Origin = newEvent.Origin; e.Origin = newEvent.Origin;
// check if the target was assigned // check if the target was assigned
@ -140,9 +140,10 @@ namespace IW4MAdmin.Application
} }
} }
Api.OnServerEvent(this, newEvent); await newEvent.Owner.ExecuteEvent(newEvent);
#if DEBUG #if DEBUG
Logger.WriteDebug("Processed Event"); Logger.WriteDebug($"Processed event with id {newEvent.Id}");
#endif #endif
} }
@ -169,7 +170,7 @@ namespace IW4MAdmin.Application
Logger.WriteDebug("Error Trace: " + ex.StackTrace); Logger.WriteDebug("Error Trace: " + ex.StackTrace);
} }
// tell anyone waiting for the output that we're done // tell anyone waiting for the output that we're done
newEvent.OnProcessed.Set(); newEvent.OnProcessed.Release();
} }
public IList<Server> GetServers() public IList<Server> GetServers()
@ -423,7 +424,15 @@ namespace IW4MAdmin.Application
Logger.WriteVerbose($"{Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"]} {ServerInstance.Hostname}"); Logger.WriteVerbose($"{Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"]} {ServerInstance.Hostname}");
// add the start event for this server // add the start event for this server
Handler.AddEvent(new GameEvent(GameEvent.EventType.Start, "Server started", null, null, ServerInstance));
var e = new GameEvent()
{
Type = GameEvent.EventType.Start,
Data = $"{ServerInstance.GameName} started",
Owner = ServerInstance
};
Handler.AddEvent(e);
} }
catch (ServerException e) catch (ServerException e)
@ -562,7 +571,6 @@ namespace IW4MAdmin.Application
public PenaltyService GetPenaltyService() => PenaltySvc; public PenaltyService GetPenaltyService() => PenaltySvc;
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings() => ConfigHandler; public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings() => ConfigHandler;
public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients; public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients;
public IEventApi GetEventApi() => Api;
public bool ShutdownRequested() => !Running; public bool ShutdownRequested() => !Running;
public IEventHandler GetEventHandler() => Handler; public IEventHandler GetEventHandler() => Handler;

View File

@ -27,11 +27,9 @@ namespace IW4MAdmin
{ {
private static readonly Index loc = Utilities.CurrentLocalization.LocalizationIndex; private static readonly Index loc = Utilities.CurrentLocalization.LocalizationIndex;
private GameLogEventDetection LogEvent; private GameLogEventDetection LogEvent;
private ClientAuthentication AuthQueue;
public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg) public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg)
{ {
AuthQueue = new ClientAuthentication();
} }
public override int GetHashCode() public override int GetHashCode()
@ -603,7 +601,7 @@ namespace IW4MAdmin
try try
{ {
var polledClients = await PollPlayersAsync(); var polledClients = await PollPlayersAsync();
var waiterList = new List<ManualResetEventSlim>(); var waiterList = new List<SemaphoreSlim>();
foreach (var disconnectingClient in polledClients[1]) foreach (var disconnectingClient in polledClients[1])
{ {
@ -625,7 +623,7 @@ namespace IW4MAdmin
waiterList.Add(e.OnProcessed); waiterList.Add(e.OnProcessed);
} }
// wait for all the disconnect tasks to finish // wait for all the disconnect tasks to finish
await Task.WhenAll(waiterList.Select(t => Task.Run(() => t.Wait(5000)))); await Task.WhenAll(waiterList.Select(t => t.WaitAsync()));
waiterList.Clear(); waiterList.Clear();
// this are our new connecting clients // this are our new connecting clients
@ -643,7 +641,7 @@ namespace IW4MAdmin
} }
// wait for all the connect tasks to finish // wait for all the connect tasks to finish
await Task.WhenAll(waiterList.Select(t => Task.Run(() => t.Wait(5000)))); await Task.WhenAll(waiterList.Select(t => t.WaitAsync()));
if (ConnectionErrors > 0) if (ConnectionErrors > 0)
{ {
@ -835,7 +833,7 @@ namespace IW4MAdmin
logPath = Regex.Replace($"{Path.DirectorySeparatorChar}{logPath}", @"[A-Z]:", ""); logPath = Regex.Replace($"{Path.DirectorySeparatorChar}{logPath}", @"[A-Z]:", "");
} }
if (!File.Exists(logPath)) if (!File.Exists(logPath) && !logPath.StartsWith("http"))
{ {
Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}"); Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}");
#if !DEBUG #if !DEBUG
@ -850,6 +848,8 @@ namespace IW4MAdmin
} }
Logger.WriteInfo($"Log file is {logPath}"); Logger.WriteInfo($"Log file is {logPath}");
Task.Run(() => LogEvent.PollForChanges());
#if !DEBUG #if !DEBUG
await Broadcast(loc["BROADCAST_ONLINE"]); await Broadcast(loc["BROADCAST_ONLINE"]);
#endif #endif
@ -1002,15 +1002,17 @@ namespace IW4MAdmin
// this is set only because they're still in the server. // this is set only because they're still in the server.
Target.Level = Player.Permission.Banned; Target.Level = Player.Permission.Banned;
// let the api know that a ban occured var e = new GameEvent()
Manager.GetEventHandler().AddEvent(new GameEvent()
{ {
Type = GameEvent.EventType.Ban, Type = GameEvent.EventType.Ban,
Data = Message, Data = Message,
Origin = Origin, Origin = Origin,
Target = Target, Target = Target,
Owner = this Owner = this
}); };
// let the api know that a ban occured
Manager.GetEventHandler().AddEvent(e);
#if !DEBUG #if !DEBUG
string formattedString = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{Message} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7"); string formattedString = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{Message} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7");

View File

@ -10,7 +10,7 @@ const plugin = {
checkForVpn(origin) { checkForVpn(origin) {
let exempt = false; let exempt = false;
// prevent players that are exempt from being kicked // prevent players that are exempt from being kicked
this.vpnExceptionIds.forEach(function(id) { this.vpnExceptionIds.forEach(function (id) {
if (id === origin.ClientId) { if (id === origin.ClientId) {
exempt = true; exempt = true;
return false; return false;
@ -48,7 +48,7 @@ const plugin = {
onEventAsync(gameEvent, server) { onEventAsync(gameEvent, server) {
// connect event // connect event
if (gameEvent.Type === 3) { if (gameEvent.Type === 3) {
this.checkForVpn(gameEvent.Origin) this.checkForVpn(gameEvent.Origin);
} }
}, },
@ -57,7 +57,7 @@ const plugin = {
this.logger = manager.GetLogger(); this.logger = manager.GetLogger();
}, },
onUnloadAsync() {}, onUnloadAsync() { },
onTickAsync(server) {} onTickAsync(server) { }
} };

View File

@ -53,7 +53,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET && kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) || kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
kill.HitLoc == IW4Info.HitLocation.none) kill.HitLoc == IW4Info.HitLocation.none || kill.TimeOffset - LastOffset < 0)
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Any, ClientPenalty = Penalty.PenaltyType.Any,
@ -131,7 +131,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
double currentStrain = Strain.GetStrain(isDamage, kill.Damage, kill.Distance / 0.0254, kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset)); double currentStrain = Strain.GetStrain(isDamage, kill.Damage, kill.Distance / 0.0254, kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset));
//double currentWeightedStrain = (currentStrain * ClientStats.SPM) / 170.0;
LastOffset = kill.TimeOffset; LastOffset = kill.TimeOffset;
if (currentStrain > ClientStats.MaxStrain) if (currentStrain > ClientStats.MaxStrain)

View File

@ -470,6 +470,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Owner = attacker.CurrentServer, Owner = attacker.CurrentServer,
Type = GameEvent.EventType.Flag Type = GameEvent.EventType.Flag
}; };
// because we created an event it must be processed by the manager
// even if it didn't really do anything
Manager.GetEventHandler().AddEvent(e);
await new CFlag().ExecuteAsync(e); await new CFlag().ExecuteAsync(e);
break; break;
} }
@ -834,9 +837,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// calculate how much the KDR should weigh // calculate how much the KDR should weigh
// 1.637 is a Eddie-Generated number that weights the KDR nicely // 1.637 is a Eddie-Generated number that weights the KDR nicely
double currentKDR = clientStats.SessionDeaths == 0 ? clientStats.SessionKills : clientStats.SessionKills / clientStats.SessionDeaths; double currentKDR = clientStats.SessionDeaths == 0 ? clientStats.SessionKills : clientStats.SessionKills / clientStats.SessionDeaths;
double alpha = Math.Sqrt(2) / Math.Min(600, clientStats.Kills + clientStats.Deaths); double alpha = Math.Sqrt(2) / Math.Min(600, Math.Max(clientStats.Kills + clientStats.Deaths, 1));
clientStats.RollingWeightedKDR = (alpha * currentKDR) + (1.0 - alpha) * clientStats.KDR; clientStats.RollingWeightedKDR = (alpha * currentKDR) + (1.0 - alpha) * clientStats.KDR;
double KDRWeight = clientStats.RollingWeightedKDR != 0 ? Math.Round(Math.Pow(clientStats.RollingWeightedKDR, 1.637 / Math.E), 3) : 0; double KDRWeight = Math.Round(Math.Pow(clientStats.RollingWeightedKDR, 1.637 / Math.E), 3);
// calculate the weight of the new play time against last 10 hours of gameplay // calculate the weight of the new play time against last 10 hours of gameplay
int totalPlayTime = (clientStats.TimePlayed == 0) ? int totalPlayTime = (clientStats.TimePlayed == 0) ?

View File

@ -76,8 +76,12 @@ namespace IW4MAdmin.Plugins.Stats
case GameEvent.EventType.ScriptKill: case GameEvent.EventType.ScriptKill:
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 13) if (killInfo.Length >= 13)
{
// todo: remove me
E.Owner.Logger.WriteDebug($"Starting Add script hit (kill) for event with id {E.Id}");
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13]); killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13]);
}
break; break;
case GameEvent.EventType.Kill: case GameEvent.EventType.Kill:
if (!E.Owner.CustomCallback) if (!E.Owner.CustomCallback)
@ -90,8 +94,12 @@ namespace IW4MAdmin.Plugins.Stats
case GameEvent.EventType.ScriptDamage: case GameEvent.EventType.ScriptDamage:
killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 13) if (killInfo.Length >= 13)
{
// todo: remove me
E.Owner.Logger.WriteDebug($"Starting Add script hit (damage) for event with id {E.Id}");
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13]); killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13]);
}
break; break;
} }
} }

View File

@ -43,7 +43,7 @@ namespace Tests
public void AddAndRemoveClientsViaJoinShouldSucceed() public void AddAndRemoveClientsViaJoinShouldSucceed()
{ {
var server = Manager.GetServers().First(); var server = Manager.GetServers().First();
var waiters = new Queue<ManualResetEventSlim>(); var waiters = new Queue<SemaphoreSlim>();
int clientStartIndex = 4; int clientStartIndex = 4;
int clientNum = 10; int clientNum = 10;
@ -103,7 +103,7 @@ namespace Tests
public void AddAndRemoveClientsViaRconShouldSucceed() public void AddAndRemoveClientsViaRconShouldSucceed()
{ {
var server = Manager.GetServers().First(); var server = Manager.GetServers().First();
var waiters = new Queue<ManualResetEventSlim>(); var waiters = new Queue<SemaphoreSlim>();
int clientIndexStart = 1; int clientIndexStart = 1;
int clientNum = 8; int clientNum = 8;
@ -187,7 +187,6 @@ namespace Tests
resetEvent.Wait(5000); resetEvent.Wait(5000);
} }
} }
} }

View File

@ -119,7 +119,17 @@ namespace SharedLibraryCore.Commands
{ {
await E.Target.Kick(E.Data, E.Origin); await E.Target.Kick(E.Data, E.Origin);
await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"]}"); await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"]}");
E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Kick, E.Data, E.Origin, E.Target, E.Owner));
var e = new GameEvent()
{
Type = GameEvent.EventType.Kick,
Data = E.Data,
Origin = E.Origin,
Target = E.Target,
Owner = E.Owner
};
E.Owner.Manager.GetEventHandler().AddEvent(e);
} }
else else
await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"]} {E.Target.Name}"); await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"]} {E.Target.Name}");
@ -179,14 +189,17 @@ namespace SharedLibraryCore.Commands
{ {
await E.Target.TempBan(Message, length, E.Origin); await E.Target.TempBan(Message, length, E.Origin);
await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}"); await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}");
E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent()
var e = new GameEvent()
{ {
Type = GameEvent.EventType.TempBan, Type = GameEvent.EventType.TempBan,
Data = E.Data, Data = E.Data,
Origin = E.Origin, Origin = E.Origin,
Target = E.Target, Target = E.Target,
Owner = E.Owner Owner = E.Owner
}); };
E.Owner.Manager.GetEventHandler().AddEvent(e);
} }
else else
await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}"); await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}");
@ -732,7 +745,17 @@ namespace SharedLibraryCore.Commands
}; };
await E.Owner.Manager.GetPenaltyService().Create(newPenalty); await E.Owner.Manager.GetPenaltyService().Create(newPenalty);
E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Flag, E.Data, E.Origin, E.Target, E.Owner));
var e = new GameEvent()
{
Type = GameEvent.EventType.Flag,
Data = E.Data,
Origin = E.Origin,
Target = E.Target,
Owner = E.Owner
};
E.Owner.Manager.GetEventHandler().AddEvent(e);
await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_SUCCESS"]} ^5{E.Target.Name}"); await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_SUCCESS"]} ^5{E.Target.Name}");
} }
@ -766,15 +789,16 @@ namespace SharedLibraryCore.Commands
await E.Owner.Manager.GetClientService().Update(E.Target); await E.Owner.Manager.GetClientService().Update(E.Target);
await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"]} ^5{E.Target.Name}"); await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"]} ^5{E.Target.Name}");
E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent() var e = new GameEvent()
{ {
Data = E.Data, Data = E.Data,
Origin = E.Origin, Origin = E.Origin,
Target = E.Target, Target = E.Target,
Owner = E.Owner, Owner = E.Owner,
Type = GameEvent.EventType.Unflag Type = GameEvent.EventType.Unflag
}); };
E.Owner.Manager.GetEventHandler().AddEvent(e);
} }
else else
@ -846,7 +870,17 @@ namespace SharedLibraryCore.Commands
await E.Owner.Manager.GetPenaltyService().Create(newReport); await E.Owner.Manager.GetPenaltyService().Create(newReport);
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_SUCCESS"]); await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_SUCCESS"]);
E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Report, E.Data, E.Origin, E.Target, E.Owner));
var e = new GameEvent()
{
Type = GameEvent.EventType.Report,
Data = E.Data,
Origin = E.Origin,
Target = E.Target,
Owner = E.Owner
};
E.Owner.Manager.GetEventHandler().AddEvent(e);
await E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data)); await E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data));
} }
} }

View File

@ -1,19 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using SharedLibraryCore;
using SharedLibraryCore.Dtos; using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Application.API namespace SharedLibraryCore.Events
{ {
class EventApi : IEventApi public class EventApi
{ {
private const int MaxEvents = 100; const int MaxEvents = 100;
private Queue<EventInfo> RecentEvents = new Queue<EventInfo>(); static Queue<EventInfo> RecentEvents = new Queue<EventInfo>();
public IEnumerable<EventInfo> GetEvents(bool shouldConsume) public static IEnumerable<EventInfo> GetEvents(bool shouldConsume)
{ {
var eventList = RecentEvents.ToArray(); var eventList = RecentEvents.ToArray();
@ -26,8 +22,9 @@ namespace IW4MAdmin.Application.API
return eventList; return eventList;
} }
public void OnServerEvent(object sender, GameEvent E) public static void OnGameEvent(object sender, GameEventArgs eventState)
{ {
var E = eventState.Event;
// don't want to clog up the api with unknown events // don't want to clog up the api with unknown events
if (E.Type == GameEvent.EventType.Unknown) if (E.Type == GameEvent.EventType.Unknown)
return; return;
@ -71,7 +68,7 @@ namespace IW4MAdmin.Application.API
/// Adds event to the list and removes first added if reached max capacity /// Adds event to the list and removes first added if reached max capacity
/// </summary> /// </summary>
/// <param name="info">EventInfo to add</param> /// <param name="info">EventInfo to add</param>
private void AddNewEvent(EventInfo info) private static void AddNewEvent(EventInfo info)
{ {
// remove the first added event // remove the first added event
if (RecentEvents.Count >= MaxEvents) if (RecentEvents.Count >= MaxEvents)

View File

@ -44,29 +44,17 @@ namespace SharedLibraryCore
StatusUpdate StatusUpdate
} }
public GameEvent(EventType t, string d, Player O, Player T, Server S) static long NextEventId;
{ static long GetNextEventId() => Interlocked.Increment(ref NextEventId);
Type = t;
Data = d?.Trim();
Origin = O;
Target = T;
Owner = S;
OnProcessed = new ManualResetEventSlim();
Time = DateTime.UtcNow;
CurrentEventId++;
Id = CurrentEventId;
}
public GameEvent() public GameEvent()
{ {
OnProcessed = new ManualResetEventSlim(); OnProcessed = new SemaphoreSlim(0);
OnProcessed.Release();
Time = DateTime.UtcNow; Time = DateTime.UtcNow;
CurrentEventId++; Id = GetNextEventId();
Id = CurrentEventId;
} }
private static long CurrentEventId;
public EventType Type; public EventType Type;
public string Data; // Data is usually the message sent by player public string Data; // Data is usually the message sent by player
public string Message; public string Message;
@ -75,8 +63,8 @@ namespace SharedLibraryCore
public Server Owner; public Server Owner;
public Boolean Remote = false; public Boolean Remote = false;
public object Extra { get; set; } public object Extra { get; set; }
public ManualResetEventSlim OnProcessed { get; set; } public SemaphoreSlim OnProcessed { get; set; }
public DateTime Time { get; private set; } public DateTime Time { get; set; }
public long Id { get; private set; } public long Id { get; private set; }
/// <summary> /// <summary>

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Events
{
/// <summary>
/// represents the state of a game event for event processing
/// </summary>
public class GameEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
public GameEventArgs(Exception error, bool cancelled, GameEvent userState) : base(error, cancelled, userState)
{
Event = userState;
}
/// <summary>
/// Game event that occured on a server
/// </summary>
public GameEvent Event { get; }
}
}

View File

@ -1,21 +0,0 @@
using SharedLibraryCore.Dtos;
using System.Collections.Generic;
namespace SharedLibraryCore.Interfaces
{
public interface IEventApi
{
/// <summary>
/// Processes event from server as event info
/// </summary>
/// <param name="sender">Object state from Delegate method call</param>
/// <param name="E">Event to process</param>
void OnServerEvent(object sender, GameEvent E);
/// <summary>
/// Get list of recent events
/// </summary>
/// <param name="shouldConsume">specify wether the request should clear all events after retrieving</param>
/// <returns>List of recent event</returns>
IEnumerable<EventInfo> GetEvents(bool shouldConsume);
}
}

View File

@ -13,6 +13,6 @@ namespace SharedLibraryCore.Interfaces
/// Add a game event event to the queue to be processed /// Add a game event event to the queue to be processed
/// </summary> /// </summary>
/// <param name="gameEvent">Game event</param> /// <param name="gameEvent">Game event</param>
void AddEvent(GameEvent gameEvent); bool AddEvent(GameEvent gameEvent);
} }
} }

View File

@ -16,7 +16,7 @@ namespace SharedLibraryCore.Interfaces
/// <param name="fileSizeDiff"></param> /// <param name="fileSizeDiff"></param>
/// <param name="startPosition"></param> /// <param name="startPosition"></param>
/// <returns></returns> /// <returns></returns>
ICollection<GameEvent> EventsFromLog(Server server, long fileSizeDiff, long startPosition); ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition);
/// <summary> /// <summary>
/// how long the log file is /// how long the log file is
/// </summary> /// </summary>

View File

@ -23,7 +23,6 @@ namespace SharedLibraryCore.Interfaces
AliasService GetAliasService(); AliasService GetAliasService();
PenaltyService GetPenaltyService(); PenaltyService GetPenaltyService();
IDictionary<int, Player> GetPrivilegedClients(); IDictionary<int, Player> GetPrivilegedClients();
IEventApi GetEventApi();
/// <summary> /// <summary>
/// Get the event handlers /// Get the event handlers
/// </summary> /// </summary>

View File

@ -150,7 +150,8 @@ namespace SharedLibraryCore.RCon
// will this really prevent flooding? // will this really prevent flooding?
if ((DateTime.Now - LastQuery).TotalMilliseconds < 350) if ((DateTime.Now - LastQuery).TotalMilliseconds < 350)
{ {
await Task.Delay(350); //await Task.Delay(350);
Thread.Sleep(350);
} }
LastQuery = DateTime.Now; LastQuery = DateTime.Now;

View File

@ -118,25 +118,17 @@ namespace SharedLibraryCore
{ {
#if !DEBUG #if !DEBUG
string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, Message); string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, Message);
var e = new GameEvent()
{
Message = formattedMessage,
Data = formattedMessage,
Owner = this,
Type = GameEvent.EventType.Broadcast,
};
Manager.GetEventHandler().AddEvent(e);
#else #else
Logger.WriteVerbose(Message.StripColors()); Logger.WriteVerbose(Message.StripColors());
#endif #endif
Manager.GetEventHandler().AddEvent(new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.Broadcast, Type = GameEvent.EventType.Broadcast,
Data = Message, Data = Message,
Owner = this Owner = this
}); };
Manager.GetEventHandler().AddEvent(e);
await Task.CompletedTask; await Task.CompletedTask;
} }

View File

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Events;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -14,7 +15,7 @@ namespace WebfrontCore.Controllers.API
[HttpGet] [HttpGet]
public IActionResult Event(bool shouldConsume = true) public IActionResult Event(bool shouldConsume = true)
{ {
var events = Manager.GetEventApi().GetEvents(shouldConsume); var events = EventApi.GetEvents(shouldConsume);
return Json(events); return Json(events);
} }

View File

@ -48,7 +48,7 @@ namespace WebfrontCore.Controllers
Manager.GetEventHandler().AddEvent(remoteEvent); Manager.GetEventHandler().AddEvent(remoteEvent);
// wait for the event to process // wait for the event to process
await Task.Run(() => remoteEvent.OnProcessed.Wait(5000)); await remoteEvent.OnProcessed.WaitAsync(60*1000);
var response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList(); var response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList();
// remove the added command response // remove the added command response