diff --git a/Application/EventParsers/BaseEventParser.cs b/Application/EventParsers/BaseEventParser.cs index 94f92f46a..71faa2906 100644 --- a/Application/EventParsers/BaseEventParser.cs +++ b/Application/EventParsers/BaseEventParser.cs @@ -105,6 +105,7 @@ namespace IW4MAdmin.Application.EventParsers Data = message, Origin = new EFClient() { NetworkId = originId }, Message = message, + Extra = logLine, RequiredEntity = GameEvent.EventRequiredEntity.Origin }; } @@ -115,6 +116,7 @@ namespace IW4MAdmin.Application.EventParsers Data = message, Origin = new EFClient() { NetworkId = originId }, Message = message, + Extra = logLine, RequiredEntity = GameEvent.EventRequiredEntity.Origin }; } @@ -181,7 +183,8 @@ namespace IW4MAdmin.Application.EventParsers ClientNumber = Convert.ToInt32(regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()), State = EFClient.ClientState.Connecting, }, - RequiredEntity = GameEvent.EventRequiredEntity.None + RequiredEntity = GameEvent.EventRequiredEntity.None, + IsBlocking = true }; } } @@ -205,7 +208,8 @@ namespace IW4MAdmin.Application.EventParsers ClientNumber = Convert.ToInt32(regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()), State = EFClient.ClientState.Disconnecting }, - RequiredEntity = GameEvent.EventRequiredEntity.Origin + RequiredEntity = GameEvent.EventRequiredEntity.None, + IsBlocking = true }; } } @@ -215,7 +219,7 @@ namespace IW4MAdmin.Application.EventParsers return new GameEvent() { Type = GameEvent.EventType.MapEnd, - Data = lineSplit[0], + Data = logLine, Origin = Utilities.IW4MAdminClient(), Target = Utilities.IW4MAdminClient(), RequiredEntity = GameEvent.EventRequiredEntity.None @@ -229,7 +233,7 @@ namespace IW4MAdmin.Application.EventParsers return new GameEvent() { Type = GameEvent.EventType.MapChange, - Data = lineSplit[0], + Data = logLine, Origin = Utilities.IW4MAdminClient(), Target = Utilities.IW4MAdminClient(), Extra = dump.DictionaryFromKeyValue(), diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index 5f25e7503..d886e81f8 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -1,7 +1,7 @@ using SharedLibraryCore; using SharedLibraryCore.Interfaces; using System; -using System.Threading; +using System.Linq; using System.Threading.Tasks; namespace IW4MAdmin.Application.IO @@ -12,6 +12,7 @@ namespace IW4MAdmin.Application.IO private readonly Server _server; private readonly IGameLogReader _reader; private readonly string _gameLogFile; + private readonly bool _ignoreBots; class EventState { @@ -24,6 +25,7 @@ namespace IW4MAdmin.Application.IO _gameLogFile = gameLogPath; _reader = gameLogServerUri != null ? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser) : _reader = new GameLogReader(gameLogPath, server.EventParser); _server = server; + _ignoreBots = server.Manager.GetApplicationSettings().Configuration().IgnoreBots; } public async Task PollForChanges() @@ -67,9 +69,56 @@ namespace IW4MAdmin.Application.IO var events = await _reader.ReadEventsFromLog(_server, fileDiff, previousFileSize); - foreach (var ev in events) + foreach (var gameEvent in events) { - _server.Manager.GetEventHandler().AddEvent(ev); + try + { +#if DEBUG + _server.Logger.WriteVerbose(gameEvent.Data); +#endif + + // we don't want to add the event if ignoreBots is on and the event comes from a bot + if (!_ignoreBots || (_ignoreBots && !((gameEvent.Origin?.IsBot ?? false) || (gameEvent.Target?.IsBot ?? false)))) + { + gameEvent.Owner = _server; + + if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != 1) + { + gameEvent.Origin = _server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId); + } + + if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Target) == GameEvent.EventRequiredEntity.Target) + { + gameEvent.Target = _server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Target?.NetworkId); + } + + if (gameEvent.Origin != null) + { + gameEvent.Origin.CurrentServer = _server; + } + + if (gameEvent.Target != null) + { + gameEvent.Target.CurrentServer = _server; + } + + _server.Manager.GetEventHandler().AddEvent(gameEvent); + + if (gameEvent.IsBlocking) + { + await gameEvent.WaitAsync(Utilities.DefaultCommandTimeout, _server.Manager.CancellationToken); + } + } + } + + catch (InvalidOperationException) + { + if (!_ignoreBots) + { + _server.Logger.WriteWarning("Could not find client in client list when parsing event line"); + _server.Logger.WriteDebug(gameEvent.Data); + } + } } previousFileSize = fileSize; diff --git a/Application/IO/GameLogReader.cs b/Application/IO/GameLogReader.cs index 658e8ff2a..bf50cb915 100644 --- a/Application/IO/GameLogReader.cs +++ b/Application/IO/GameLogReader.cs @@ -11,32 +11,26 @@ namespace IW4MAdmin.Application.IO { class GameLogReader : IGameLogReader { - IEventParser Parser; - readonly string LogFile; - private bool? ignoreBots; + private readonly IEventParser _parser; + private readonly string _logFile; - public long Length => new FileInfo(LogFile).Length; + public long Length => new FileInfo(_logFile).Length; public int UpdateInterval => 300; public GameLogReader(string logFile, IEventParser parser) { - LogFile = logFile; - Parser = parser; + _logFile = logFile; + _parser = parser; } public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) { - if (!ignoreBots.HasValue) - { - ignoreBots = server.Manager.GetApplicationSettings().Configuration().IgnoreBots; - } - // allocate the bytes for the new log lines List logLines = new List(); // open the file as a stream - using (FileStream fs = new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (FileStream fs = new FileStream(_logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { byte[] buff = new byte[fileSizeDiff]; fs.Seek(startPosition, SeekOrigin.Begin); @@ -67,57 +61,19 @@ namespace IW4MAdmin.Application.IO List events = new List(); // parse each line - foreach (string eventLine in logLines) + foreach (string eventLine in logLines.Where(_line => _line.Length > 0)) { - if (eventLine.Length > 0) + try { - try - { - var gameEvent = Parser.GenerateGameEvent(eventLine); - // we don't want to add the event if ignoreBots is on and the event comes from a bot - if (!ignoreBots.Value || (ignoreBots.Value && !((gameEvent.Origin?.IsBot ?? false) || (gameEvent.Target?.IsBot ?? false)))) - { - gameEvent.Owner = server; + var gameEvent = _parser.GenerateGameEvent(eventLine); + events.Add(gameEvent); + } - if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != 1) - { - gameEvent.Origin = server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId); - } - - if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Target) == GameEvent.EventRequiredEntity.Target) - { - gameEvent.Target = server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Target?.NetworkId); - } - - if (gameEvent.Origin != null) - { - gameEvent.Origin.CurrentServer = server; - } - - if (gameEvent.Target != null) - { - gameEvent.Target.CurrentServer = server; - } - - events.Add(gameEvent); - } - } - - catch (InvalidOperationException) - { - if (!ignoreBots.Value) - { - server.Logger.WriteWarning("Could not find client in client list when parsing event line"); - server.Logger.WriteDebug(eventLine); - } - } - - catch (Exception e) - { - server.Logger.WriteWarning("Could not properly parse event line"); - server.Logger.WriteDebug(e.Message); - server.Logger.WriteDebug(eventLine); - } + catch (Exception e) + { + server.Logger.WriteWarning("Could not properly parse event line"); + server.Logger.WriteDebug(e.Message); + server.Logger.WriteDebug(eventLine); } } diff --git a/Application/IO/GameLogReaderHttp.cs b/Application/IO/GameLogReaderHttp.cs index 23fe5c9b3..2a36e66a4 100644 --- a/Application/IO/GameLogReaderHttp.cs +++ b/Application/IO/GameLogReaderHttp.cs @@ -19,7 +19,6 @@ namespace IW4MAdmin.Application.IO readonly IEventParser Parser; readonly IGameLogServer Api; readonly string logPath; - private bool? ignoreBots; private string lastKey = "next"; public GameLogReaderHttp(Uri gameLogServerUri, string logPath, IEventParser parser) @@ -35,11 +34,6 @@ namespace IW4MAdmin.Application.IO public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) { - if (!ignoreBots.HasValue) - { - ignoreBots = server.Manager.GetApplicationSettings().Configuration().IgnoreBots; - } - var events = new List(); string b64Path = logPath; var response = await Api.Log(b64Path, lastKey); @@ -53,64 +47,25 @@ namespace IW4MAdmin.Application.IO else if (!string.IsNullOrWhiteSpace(response.Data)) { -#if DEBUG - server.Manager.GetLogger(0).WriteInfo(response.Data); -#endif // parse each line - foreach (string eventLine in response.Data.Split(Environment.NewLine)) + foreach (string eventLine in response.Data + .Split(Environment.NewLine) + .Where(_line => _line.Length > 0)) { - if (eventLine.Length > 0) + try { - try - { - var gameEvent = Parser.GenerateGameEvent(eventLine); - // we don't want to add the event if ignoreBots is on and the event comes from a bot - if (!ignoreBots.Value || (ignoreBots.Value && !((gameEvent.Origin?.IsBot ?? false) || (gameEvent.Target?.IsBot ?? false)))) - { - gameEvent.Owner = server; - - if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != 1) - { - gameEvent.Origin = server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId); - } - - if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Target) == GameEvent.EventRequiredEntity.Target) - { - gameEvent.Target = server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Target?.NetworkId); - } - - if (gameEvent.Origin != null) - { - gameEvent.Origin.CurrentServer = server; - } - - if (gameEvent.Target != null) - { - gameEvent.Target.CurrentServer = server; - } - - events.Add(gameEvent); - } + var gameEvent = Parser.GenerateGameEvent(eventLine); + events.Add(gameEvent); #if DEBUG == true - server.Logger.WriteDebug($"Parsed event with id {gameEvent.Id} from http"); + server.Logger.WriteDebug($"Parsed event with id {gameEvent.Id} from http"); #endif - } + } - catch (InvalidOperationException) - { - if (!ignoreBots.Value) - { - server.Logger.WriteWarning("Could not find client in client list when parsing event line"); - server.Logger.WriteDebug(eventLine); - } - } - - catch (Exception e) - { - server.Logger.WriteWarning("Could not properly parse remote event line"); - server.Logger.WriteDebug(e.Message); - server.Logger.WriteDebug(eventLine); - } + catch (Exception e) + { + server.Logger.WriteError("Could not properly parse event line from http"); + server.Logger.WriteDebug(e.Message); + server.Logger.WriteDebug(eventLine); } } } diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index b424f7eb3..40c8c9228 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -668,7 +668,8 @@ namespace IW4MAdmin { Type = GameEvent.EventType.PreConnect, Origin = client, - Owner = this + Owner = this, + IsBlocking = true }; Manager.GetEventHandler().AddEvent(e); diff --git a/Application/RconParsers/BaseRConParser.cs b/Application/RconParsers/BaseRConParser.cs index 99f758c5a..14d6ca653 100644 --- a/Application/RconParsers/BaseRConParser.cs +++ b/Application/RconParsers/BaseRConParser.cs @@ -103,6 +103,12 @@ namespace IW4MAdmin.Application.RconParsers public virtual async Task> GetStatusAsync(Connection connection) { string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS); +#if DEBUG + foreach (var line in response) + { + Console.WriteLine(line); + } +#endif return ClientsFromStatus(response); } diff --git a/Plugins/Stats/Commands/ResetStats.cs b/Plugins/Stats/Commands/ResetStats.cs index 51df65fb8..1e426cd2b 100644 --- a/Plugins/Stats/Commands/ResetStats.cs +++ b/Plugins/Stats/Commands/ResetStats.cs @@ -36,7 +36,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands clientStats.EloRating = 200.0; // reset the cached version - Plugin.Manager.ResetStats(E.Origin.ClientId, serverId); + Plugin.Manager.ResetStats(E.Origin); // fixme: this doesn't work properly when another context exists await ctx.SaveChangesAsync(); diff --git a/Plugins/Stats/Commands/ViewStats.cs b/Plugins/Stats/Commands/ViewStats.cs index a13688e80..d6b9813a3 100644 --- a/Plugins/Stats/Commands/ViewStats.cs +++ b/Plugins/Stats/Commands/ViewStats.cs @@ -52,7 +52,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Target))) { - pStats = Plugin.Manager.GetClientStats(E.Target.ClientId, serverId); + pStats = E.Target.GetAdditionalProperty(StatManager.CLIENT_STATS_KEY); } else @@ -72,7 +72,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Origin))) { - pStats = Plugin.Manager.GetClientStats(E.Origin.ClientId, serverId); + pStats = E.Origin.GetAdditionalProperty(StatManager.CLIENT_STATS_KEY); } else diff --git a/Plugins/Stats/Helpers/ServerStats.cs b/Plugins/Stats/Helpers/ServerStats.cs index 515a1969e..73b44bc39 100644 --- a/Plugins/Stats/Helpers/ServerStats.cs +++ b/Plugins/Stats/Helpers/ServerStats.cs @@ -1,5 +1,7 @@ using IW4MAdmin.Plugins.Stats.Cheat; using IW4MAdmin.Plugins.Stats.Models; +using SharedLibraryCore; +using SharedLibraryCore.Database.Models; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -7,33 +9,36 @@ using System.Linq; namespace IW4MAdmin.Plugins.Stats.Helpers { - class ServerStats { - public ConcurrentDictionary PlayerStats { get; set; } - public ConcurrentDictionary PlayerDetections { get; set; } + class ServerStats + { public IList HitCache { get; private set; } public EFServerStatistics ServerStatistics { get; private set; } public EFServer Server { get; private set; } + private readonly Server _server; public bool IsTeamBased { get; set; } - public ServerStats(EFServer sv, EFServerStatistics st) + public ServerStats(EFServer sv, EFServerStatistics st, Server server) { - PlayerStats = new ConcurrentDictionary(); - PlayerDetections = new ConcurrentDictionary(); HitCache = new List(); ServerStatistics = st; Server = sv; + _server = server; } public int TeamCount(IW4Info.Team teamName) { - if (PlayerStats.Count(p => p.Value.Team == IW4Info.Team.None) / (double)PlayerStats.Count <= 0.25) + var PlayerStats = _server.GetClientsAsList() + .Select(_c => _c.GetAdditionalProperty(StatManager.CLIENT_STATS_KEY)) + .Where(_c => _c != null); + + if (PlayerStats.Count(p => p.Team == IW4Info.Team.None) / (double)PlayerStats.Count() <= 0.25) { - return IsTeamBased ? Math.Max(PlayerStats.Count(p => p.Value.Team == teamName), 1) : Math.Max(PlayerStats.Count - 1, 1); + return IsTeamBased ? Math.Max(PlayerStats.Count(p => p.Team == teamName), 1) : Math.Max(PlayerStats.Count() - 1, 1); } - + else { - return IsTeamBased ? (int)Math.Max(Math.Floor(PlayerStats.Count / 2.0), 1) : Math.Max(PlayerStats.Count - 1, 1); + return IsTeamBased ? (int)Math.Max(Math.Floor(PlayerStats.Count() / 2.0), 1) : Math.Max(PlayerStats.Count() - 1, 1); } } } diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index f71d29a1e..d6106c9bc 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -23,6 +23,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers private readonly ConcurrentDictionary _servers; private readonly ILogger _log; private static List serverModels; + public static string CLIENT_STATS_KEY = "ClientStats"; + public static string CLIENT_DETECTIONS_KEY = "ClientDetections"; public StatManager(IManager mgr) { @@ -38,11 +40,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } } - public EFClientStatistics GetClientStats(int clientId, long serverId) - { - return _servers[serverId].PlayerStats[clientId]; - } - public static Expression> GetRankingFunc(long? serverId = null) { var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15); @@ -248,7 +245,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // check to see if the stats have ever been initialized var serverStats = InitializeServerStats(server.ServerId); - _servers.TryAdd(serverId, new ServerStats(server, serverStats) + _servers.TryAdd(serverId, new ServerStats(server, serverStats, sv) { IsTeamBased = sv.Gametype != "dm" }); @@ -278,15 +275,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers return null; } - var playerStats = _servers[serverId].PlayerStats; - var detectionStats = _servers[serverId].PlayerDetections; - - if (playerStats.ContainsKey(pl.ClientId)) - { - _log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}"); - return playerStats[pl.ClientId]; - } - // get the client's stats from the database if it exists, otherwise create and attach a new one // if this fails we want to throw an exception @@ -322,20 +310,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // insert if they've not been added clientStats = clientStatsSet.Add(clientStats).Entity; await ctx.SaveChangesAsync(); - - if (!playerStats.TryAdd(clientStats.ClientId, clientStats)) - { - _log.WriteWarning("Adding new client to stats failed"); - } } - else - { - if (!playerStats.TryAdd(clientStats.ClientId, clientStats)) - { - _log.WriteWarning("Adding pre-existing client to stats failed"); - } - } + pl.SetAdditionalProperty(CLIENT_STATS_KEY, clientStats); // migration for previous existing stats if (clientStats.HitLocations.Count == 0) @@ -370,12 +347,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers clientStats.SessionScore = pl.Score; clientStats.LastScore = pl.Score; - if (!detectionStats.TryAdd(pl.ClientId, new Detection(_log, clientStats))) - { - _log.WriteWarning("Could not add client to detection"); - } - - pl.CurrentServer.Logger.WriteInfo($"Adding {pl} to stats"); + pl.SetAdditionalProperty(CLIENT_DETECTIONS_KEY, new Detection(_log, clientStats)); + pl.CurrentServer.Logger.WriteInfo($"Added {pl} to stats"); } return clientStats; @@ -400,30 +373,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers pl.CurrentServer.Logger.WriteInfo($"Removing {pl} from stats"); long serverId = GetIdForServer(pl.CurrentServer); - var playerStats = _servers[serverId].PlayerStats; - var detectionStats = _servers[serverId].PlayerDetections; var serverStats = _servers[serverId].ServerStatistics; - if (!playerStats.ContainsKey(pl.ClientId)) - { - pl.CurrentServer.Logger.WriteWarning($"Client disconnecting not in stats {pl}"); - // remove the client from the stats dictionary as they're leaving - playerStats.TryRemove(pl.ClientId, out _); - detectionStats.TryRemove(pl.ClientId, out _); - return; - } - // get individual client's stats - var clientStats = playerStats[pl.ClientId]; - + var clientStats = pl.GetAdditionalProperty(CLIENT_STATS_KEY); // sync their stats before they leave clientStats = UpdateStats(clientStats); await SaveClientStats(clientStats); - // remove the client from the stats dictionary as they're leaving - playerStats.TryRemove(pl.ClientId, out _); - detectionStats.TryRemove(pl.ClientId, out _); - // increment the total play time serverStats.TotalPlayTime += pl.ConnectionLength; } @@ -516,19 +473,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers return; } - // incase the add player event get delayed - if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) - { - await AddPlayer(attacker); - } - - if (!isDamage) - { - await AddStandardKill(attacker, victim); - } - - var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId]; - var clientStats = _servers[serverId].PlayerStats[attacker.ClientId]; + var clientDetection = attacker.GetAdditionalProperty(CLIENT_DETECTIONS_KEY); + var clientStats = attacker.GetAdditionalProperty(CLIENT_STATS_KEY); waiter = clientStats.ProcessingHit; await waiter.WaitAsync(); @@ -591,12 +537,16 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } } } +#if DEBUG + await Sync(attacker.CurrentServer); +#endif } catch (Exception ex) { _log.WriteError("Could not save hit or AC info"); _log.WriteDebug(ex.GetExceptionInfo()); + _log.WriteDebug($"Attacker: {attacker} Victim: {victim}, ServerId {serverId}"); } finally @@ -672,32 +622,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { long serverId = GetIdForServer(attacker.CurrentServer); - EFClientStatistics attackerStats = null; - if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) - { - attackerStats = await AddPlayer(attacker); - } - - else - { - attackerStats = _servers[serverId].PlayerStats[attacker.ClientId]; - } - - EFClientStatistics victimStats = null; - if (!_servers[serverId].PlayerStats.ContainsKey(victim.ClientId)) - { - victimStats = await AddPlayer(victim); - } - - else - { - victimStats = _servers[serverId].PlayerStats[victim.ClientId]; - } + var attackerStats = attacker.GetAdditionalProperty(CLIENT_STATS_KEY); + var victimStats = victim.GetAdditionalProperty(CLIENT_STATS_KEY); #if DEBUG - _log.WriteDebug("Calculating standard kill"); + _log.WriteDebug("Processing standard kill"); #endif - // update the total stats _servers[serverId].ServerStatistics.TotalKills += 1; @@ -955,47 +885,44 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // process the attacker's stats after the kills attackerStats = UpdateStats(attackerStats); - // calulate elo - if (_servers[attackerStats.ServerId].PlayerStats.Count > 1) - { - #region DEPRECATED - /* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats - .Where(cs => cs.Value.ClientId != attackerStats.ClientId) - .Where(cs => - Servers[attackerStats.ServerId].IsTeamBased ? - cs.Value.Team != attackerStats.Team : - cs.Value.Team != IW4Info.Team.Spectator) - .Where(cs => cs.Value.Team != IW4Info.Team.Spectator); + #region DEPRECATED + /* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats + .Where(cs => cs.Value.ClientId != attackerStats.ClientId) + .Where(cs => + Servers[attackerStats.ServerId].IsTeamBased ? + cs.Value.Team != attackerStats.Team : + cs.Value.Team != IW4Info.Team.Spectator) + .Where(cs => cs.Value.Team != IW4Info.Team.Spectator); - double attackerLobbyRating = validAttackerLobbyRatings.Count() > 0 ? - validAttackerLobbyRatings.Average(cs => cs.Value.EloRating) : - attackerStats.EloRating; + double attackerLobbyRating = validAttackerLobbyRatings.Count() > 0 ? + validAttackerLobbyRatings.Average(cs => cs.Value.EloRating) : + attackerStats.EloRating; - var validVictimLobbyRatings = Servers[victimStats.ServerId].PlayerStats - .Where(cs => cs.Value.ClientId != victimStats.ClientId) - .Where(cs => - Servers[attackerStats.ServerId].IsTeamBased ? - cs.Value.Team != victimStats.Team : - cs.Value.Team != IW4Info.Team.Spectator) - .Where(cs => cs.Value.Team != IW4Info.Team.Spectator); + var validVictimLobbyRatings = Servers[victimStats.ServerId].PlayerStats + .Where(cs => cs.Value.ClientId != victimStats.ClientId) + .Where(cs => + Servers[attackerStats.ServerId].IsTeamBased ? + cs.Value.Team != victimStats.Team : + cs.Value.Team != IW4Info.Team.Spectator) + .Where(cs => cs.Value.Team != IW4Info.Team.Spectator); - double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ? - validVictimLobbyRatings.Average(cs => cs.Value.EloRating) : - victimStats.EloRating;*/ - #endregion + double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ? + validVictimLobbyRatings.Average(cs => cs.Value.EloRating) : + victimStats.EloRating;*/ + #endregion - double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating)); - double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E)); + // calculate elo + double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating)); + double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E)); - // double victimEloDifference = Math.Log(Math.Max(1, attackerStats.EloRating)) - Math.Log(Math.Max(1, victimStats.EloRating)); - // double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference/ Math.E)); + // double victimEloDifference = Math.Log(Math.Max(1, attackerStats.EloRating)) - Math.Log(Math.Max(1, victimStats.EloRating)); + // double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference/ Math.E)); - attackerStats.EloRating += 6.0 * (1 - winPercentage); - victimStats.EloRating -= 6.0 * (1 - winPercentage); + attackerStats.EloRating += 6.0 * (1 - winPercentage); + victimStats.EloRating -= 6.0 * (1 - winPercentage); - attackerStats.EloRating = Math.Max(0, Math.Round(attackerStats.EloRating, 2)); - victimStats.EloRating = Math.Max(0, Math.Round(victimStats.EloRating, 2)); - } + attackerStats.EloRating = Math.Max(0, Math.Round(attackerStats.EloRating, 2)); + victimStats.EloRating = Math.Max(0, Math.Round(victimStats.EloRating, 2)); // update after calculation attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds; @@ -1113,19 +1040,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers return serverStats; } - public void ResetKillstreaks(long serverId) + public void ResetKillstreaks(Server sv) { - var serverStats = _servers[serverId]; - - foreach (var stat in serverStats.PlayerStats.Values) + foreach (var stat in sv.GetClientsAsList() + .Select(_client => _client.GetAdditionalProperty(CLIENT_STATS_KEY))) { stat.StartNewSession(); } } - public void ResetStats(int clientId, long serverId) + public void ResetStats(EFClient client) { - var stats = _servers[serverId].PlayerStats[clientId]; + var stats = client.GetAdditionalProperty(CLIENT_STATS_KEY); stats.Kills = 0; stats.Deaths = 0; stats.SPM = 0; @@ -1160,22 +1086,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { long serverId = GetIdForServer(sv); - using (var ctx = new DatabaseContext(disableTracking: true)) + using (var ctx = new DatabaseContext()) { - var serverSet = ctx.Set(); - serverSet.Update(_servers[serverId].Server); - var serverStatsSet = ctx.Set(); serverStatsSet.Update(_servers[serverId].ServerStatistics); + await ctx.SaveChangesAsync(); } - foreach (var client in sv.GetClientsAsList()) + foreach (var stats in sv.GetClientsAsList() + .Select(_client => _client.GetAdditionalProperty(CLIENT_STATS_KEY)) + .Where(_stats => _stats != null)) { - var stats = GetClientStats(client.ClientId, serverId); - if (stats != null) - { - await SaveClientStats(stats); - } + await SaveClientStats(stats); } await SaveHitCache(serverId); diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index ea9875810..169ed863c 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -58,7 +58,7 @@ namespace IW4MAdmin.Plugins.Stats break; case GameEvent.EventType.MapChange: Manager.SetTeamBased(StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm"); - Manager.ResetKillstreaks(StatManager.GetIdForServer(E.Owner)); + Manager.ResetKillstreaks(E.Owner); await Manager.Sync(E.Owner); break; case GameEvent.EventType.MapEnd: @@ -104,7 +104,7 @@ namespace IW4MAdmin.Plugins.Stats } break; case GameEvent.EventType.Kill: - if (!E.Owner.CustomCallback && !ShouldIgnoreEvent(E.Origin, E.Target)) + if (!ShouldIgnoreEvent(E.Origin, E.Target)) { // this treats "world" damage as self damage if (IsWorldDamage(E.Origin)) @@ -116,7 +116,7 @@ namespace IW4MAdmin.Plugins.Stats } break; case GameEvent.EventType.Damage: - if (!E.Owner.CustomCallback && !ShouldIgnoreEvent(E.Origin, E.Target)) + if (!ShouldIgnoreEvent(E.Origin, E.Target)) { // this treats "world" damage as self damage if (IsWorldDamage(E.Origin)) diff --git a/SharedLibraryCore/Commands/UnlinkClientCommand.cs b/SharedLibraryCore/Commands/UnlinkClientCommand.cs index abc7eb984..326bd0e4d 100644 --- a/SharedLibraryCore/Commands/UnlinkClientCommand.cs +++ b/SharedLibraryCore/Commands/UnlinkClientCommand.cs @@ -3,6 +3,13 @@ using System.Threading.Tasks; namespace SharedLibraryCore.Commands { + /// + /// Provides a way for administrators to "unlink" linked accounts + /// This problem is common in IW4x where the client identifier is a file + /// that is commonly transmitted when uploading and sharing the game files + /// This command creates a new link and assigns the guid, and all aliases with the current IP + /// associated to the provided client ID to the new link + /// public class UnlinkClientCommand : Command { public UnlinkClientCommand() : diff --git a/SharedLibraryCore/Events/GameEvent.cs b/SharedLibraryCore/Events/GameEvent.cs index ff9d6d75c..846a00dec 100644 --- a/SharedLibraryCore/Events/GameEvent.cs +++ b/SharedLibraryCore/Events/GameEvent.cs @@ -224,6 +224,10 @@ namespace SharedLibraryCore public long Id { get; private set; } public EventFailReason FailReason { get; set; } public bool Failed => FailReason != EventFailReason.None; + /// + /// Indicates if the event should block until it is complete + /// + public bool IsBlocking { get; set; } /// /// asynchronously wait for GameEvent to be processed diff --git a/SharedLibraryCore/Migrations/20191004172550_RenameClientHitLocationCountColumns.cs b/SharedLibraryCore/Migrations/20191004172550_RenameClientHitLocationCountColumns.cs index 5ee6446e1..f63f2253d 100644 --- a/SharedLibraryCore/Migrations/20191004172550_RenameClientHitLocationCountColumns.cs +++ b/SharedLibraryCore/Migrations/20191004172550_RenameClientHitLocationCountColumns.cs @@ -77,6 +77,13 @@ PRAGMA foreign_keys = 1; ", true); } + else if (migrationBuilder.ActiveProvider == "Pomelo.EntityFrameworkCore.MySql") + { + migrationBuilder.Sql("ALTER TABLE `EFHitLocationCounts` CHANGE `EFClientStatistics_ClientId` `EFClientStatisticsClientId` INT(11) NOT NULL;"); + migrationBuilder.Sql("ALTER TABLE `EFHitLocationCounts` CHANGE `EFClientStatistics_ServerId` `EFClientStatisticsServerId` INT(11) NOT NULL;"); + migrationBuilder.Sql("CREATE INDEX `IX_EFClientStatisticsClientId_EFClientStatisticsServerId` ON `EFHitLocationCounts` (`EFClientStatisticsClientId`, `EFClientStatisticsServerId`);"); + } + else { migrationBuilder.DropForeignKey( @@ -139,61 +146,6 @@ PRAGMA foreign_keys = 1; protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropForeignKey( - name: "FK_EFHitLocationCounts_EFClients_EFClientStatisticsClientId", - table: "EFHitLocationCounts"); - - migrationBuilder.DropForeignKey( - name: "FK_EFHitLocationCounts_EFServers_EFClientStatisticsServerId", - table: "EFHitLocationCounts"); - - migrationBuilder.DropForeignKey( - name: "FK_EFHitLocationCounts_EFClientStatistics_EFClientStatisticsClientId_EFClientStatisticsServerId", - table: "EFHitLocationCounts"); - - migrationBuilder.RenameColumn( - name: "EFClientStatisticsServerId", - table: "EFHitLocationCounts", - newName: "EFClientStatistics_ServerId"); - - migrationBuilder.RenameColumn( - name: "EFClientStatisticsClientId", - table: "EFHitLocationCounts", - newName: "EFClientStatistics_ClientId"); - - migrationBuilder.RenameIndex( - name: "IX_EFHitLocationCounts_EFClientStatisticsClientId_EFClientStatisticsServerId", - table: "EFHitLocationCounts", - newName: "IX_EFHitLocationCounts_EFClientStatistics_ClientId_EFClientStatistics_ServerId"); - - migrationBuilder.RenameIndex( - name: "IX_EFHitLocationCounts_EFClientStatisticsServerId", - table: "EFHitLocationCounts", - newName: "IX_EFHitLocationCounts_EFClientStatistics_ServerId"); - - migrationBuilder.AddForeignKey( - name: "FK_EFHitLocationCounts_EFClients_EFClientStatistics_ClientId", - table: "EFHitLocationCounts", - column: "EFClientStatistics_ClientId", - principalTable: "EFClients", - principalColumn: "ClientId", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_EFHitLocationCounts_EFServers_EFClientStatistics_ServerId", - table: "EFHitLocationCounts", - column: "EFClientStatistics_ServerId", - principalTable: "EFServers", - principalColumn: "ServerId", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_EFHitLocationCounts_EFClientStatistics_EFClientStatistics_ClientId_EFClientStatistics_ServerId", - table: "EFHitLocationCounts", - columns: new[] { "EFClientStatistics_ClientId", "EFClientStatistics_ServerId" }, - principalTable: "EFClientStatistics", - principalColumns: new[] { "ClientId", "ServerId" }, - onDelete: ReferentialAction.Cascade); } } } diff --git a/SharedLibraryCore/PartialEntities/EFClient.cs b/SharedLibraryCore/PartialEntities/EFClient.cs index 0175753d5..d779c1aef 100644 --- a/SharedLibraryCore/PartialEntities/EFClient.cs +++ b/SharedLibraryCore/PartialEntities/EFClient.cs @@ -376,6 +376,11 @@ namespace SharedLibraryCore.Database.Models e.FailReason = GameEvent.EventFailReason.Permission; } + if (Level == Permission.Banned) + { + e.FailReason = GameEvent.EventFailReason.Invalid; + } + sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; }