diff --git a/Application/EventParsers/IW4EventParser.cs b/Application/EventParsers/IW4EventParser.cs index f527c1037..fde869295 100644 --- a/Application/EventParsers/IW4EventParser.cs +++ b/Application/EventParsers/IW4EventParser.cs @@ -34,7 +34,7 @@ namespace IW4MAdmin.Application.EventParsers { string message = lineSplit[4].Replace("\x15", ""); - if (message[0] == '!' || message[1] == '@') + if (message[0] == '!' || message[0] == '@') { return new GameEvent() { diff --git a/Application/Manager.cs b/Application/Manager.cs index 894fbfee0..2e741eb6d 100644 --- a/Application/Manager.cs +++ b/Application/Manager.cs @@ -138,7 +138,7 @@ namespace IW4MAdmin.Application sensitiveEvent.OnProcessed.Set(); } - await Task.Delay(1000); + await Task.Delay(2500); } } diff --git a/Application/RconParsers/IW4RConParser.cs b/Application/RconParsers/IW4RConParser.cs index c69df3da5..f71585b2a 100644 --- a/Application/RconParsers/IW4RConParser.cs +++ b/Application/RconParsers/IW4RConParser.cs @@ -127,19 +127,9 @@ namespace Application.RconParsers } // this happens if status is requested while map is rotating - if (Status.Contains("Server Initialization")) - { - throw new ServerException("Server is rotating map"); - } - if (Status.Length > 5 && validMatches == 0) { - IW4MAdmin.Application.Program.ServerManager.Logger.WriteError("BAD STATUS!"); - foreach (var s in Status) - { - IW4MAdmin.Application.Program.ServerManager.Logger.WriteDebug(s); - } - throw new ServerException("Bad status received"); + throw new ServerException("Server is rotating map"); } return StatusPlayers; diff --git a/Plugins/Stats/Helpers/ServerStats.cs b/Plugins/Stats/Helpers/ServerStats.cs index 5763b566c..d5339bc53 100644 --- a/Plugins/Stats/Helpers/ServerStats.cs +++ b/Plugins/Stats/Helpers/ServerStats.cs @@ -1,6 +1,8 @@ using IW4MAdmin.Plugins.Stats.Cheat; using IW4MAdmin.Plugins.Stats.Models; +using System; using System.Collections.Concurrent; +using System.Linq; namespace IW4MAdmin.Plugins.Stats.Helpers { @@ -9,6 +11,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public ConcurrentDictionary PlayerDetections { get; set; } public EFServerStatistics ServerStatistics { get; private set; } public EFServer Server { get; private set; } + public bool IsTeamBased { get; set; } public ServerStats(EFServer sv, EFServerStatistics st) { @@ -17,5 +20,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers ServerStatistics = st; Server = sv; } + + public int TeamCount(IW4Info.Team teamName) + { + if (PlayerStats.Count(p => p.Value.Team == IW4Info.Team.Spectator) / (double)PlayerStats.Count <= 0.25) + { + return IsTeamBased ? Math.Max(PlayerStats.Count(p => p.Value.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); + } + } } } diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 5defcb3d0..6d38d0fdb 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -10,6 +10,7 @@ using SharedLibraryCore.Interfaces; using SharedLibraryCore.Objects; using SharedLibraryCore.Commands; using IW4MAdmin.Plugins.Stats.Models; +using System.Text.RegularExpressions; namespace IW4MAdmin.Plugins.Stats.Helpers { @@ -68,7 +69,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers statsSvc.ServerStatsSvc.SaveChanges(); var serverStats = statsSvc.ServerStatsSvc.Find(c => c.ServerId == serverId).FirstOrDefault(); - Servers.TryAdd(serverId, new ServerStats(server, serverStats)); + Servers.TryAdd(serverId, new ServerStats(server, serverStats) + { + IsTeamBased = sv.Gametype != "dm" + }); } catch (Exception e) @@ -214,24 +218,21 @@ namespace IW4MAdmin.Plugins.Stats.Helpers serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds; } - public void AddDamageEvent(string eventLine, int clientId, int serverId) + public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, int serverId) { - /* string regex = @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; + string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$"; + var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase); - var match = Regex.Match(damageLine, regex, RegexOptions.IgnoreCase); - - if (match.Success) - { - var meansOfDeath = ParseEnum.Get(match.Groups[12].Value, typeof(IW4Info.MeansOfDeath)); - var hitLocation = ParseEnum.Get(match.Groups[13].Value, typeof(IW4Info.HitLocation)); - - if (meansOfDeath == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || - meansOfDeath == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || - meansOfDeath == IW4Info.MeansOfDeath.MOD_HEAD_SHOT) - { - ClientStats.HitLocations.First(hl => hl.Location == hitLocation).HitCount += 1; - } - }*/ + if (match.Success) + { + // this gives us what time the player is on + var attackerStats = Servers[serverId].PlayerStats[attackerClientId]; + var victimStats = Servers[serverId].PlayerStats[victimClientId]; + IW4Info.Team victimTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[4].ToString()); + IW4Info.Team attackerTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[7].ToString()); + attackerStats.Team = attackerTeam; + victimStats.Team = victimTeam; + } } /// @@ -494,22 +495,38 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // calulate elo if (Servers[attackerStats.ServerId].PlayerStats.Count > 1) { - double attackerLobbyRating = Servers[attackerStats.ServerId].PlayerStats + /* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats .Where(cs => cs.Value.ClientId != attackerStats.ClientId) - .Average(cs => cs.Value.EloRating); + .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 victimLobbyRating = Servers[attackerStats.ServerId].PlayerStats + 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) - .Average(cs => cs.Value.EloRating); + .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 attackerEloDifference = Math.Log(attackerLobbyRating <= 0 ? 1 : attackerLobbyRating) - Math.Log(attackerStats.EloRating <= 0 ? 1 : attackerStats.EloRating); + double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ? + validVictimLobbyRatings.Average(cs => cs.Value.EloRating) : + victimStats.EloRating;*/ + + 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(victimLobbyRating <= 0 ? 1 : victimLobbyRating) - Math.Log(victimStats.EloRating <= 0 ? 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 += 24.0 * (1 - winPercentage); - victimStats.EloRating -= 24.0 * 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)); @@ -548,12 +565,15 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { scoreDifference = clientStats.RoundScore + clientStats.LastScore; } + else if (clientStats.RoundScore > 0 && clientStats.LastScore < clientStats.RoundScore) { scoreDifference = clientStats.RoundScore - clientStats.LastScore; } double killSPM = scoreDifference / timeSinceLastCalc; + double spmMultiplier = 2.934 * Math.Pow(Servers[clientStats.ServerId].TeamCount(clientStats.Team == IW4Info.Team.Allies ? IW4Info.Team.Axis : IW4Info.Team.Allies), -0.454); + killSPM *= Math.Max(1, spmMultiplier); // calculate how much the KDR should weigh // 1.637 is a Eddie-Generated number that weights the KDR nicely @@ -676,5 +696,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // this should prevent the gunk for having a long lasting context. ContextThreads[serverId] = new ThreadSafeStatsService(); } + + public void SetTeamBased(int serverId, bool isTeamBased) + { + Servers[serverId].IsTeamBased = isTeamBased; + } } } diff --git a/Plugins/Stats/IW4Info.cs b/Plugins/Stats/IW4Info.cs index c49e9c7f6..ac3ce7698 100644 --- a/Plugins/Stats/IW4Info.cs +++ b/Plugins/Stats/IW4Info.cs @@ -8,6 +8,13 @@ namespace IW4MAdmin.Plugins.Stats { public class IW4Info { + public enum Team + { + Spectator, + Axis, + Allies + } + public enum MeansOfDeath { NONE, diff --git a/Plugins/Stats/Models/EFClientStatistics.cs b/Plugins/Stats/Models/EFClientStatistics.cs index 158e279f5..670de1c6a 100644 --- a/Plugins/Stats/Models/EFClientStatistics.cs +++ b/Plugins/Stats/Models/EFClientStatistics.cs @@ -94,5 +94,7 @@ namespace IW4MAdmin.Plugins.Stats.Models } [NotMapped] private List SessionScores = new List() { 0 }; + [NotMapped] + public IW4Info.Team Team { get; set; } } } diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index a945ebb9d..77196a0f7 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -49,6 +49,7 @@ namespace IW4MAdmin.Plugins.Stats await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data); break; case GameEvent.EventType.MapChange: + Manager.SetTeamBased(E.Owner.GetHashCode(), E.Owner.Gametype != "dm"); Manager.ResetKillstreaks(S.GetHashCode()); await Manager.Sync(S); break; @@ -83,8 +84,8 @@ namespace IW4MAdmin.Plugins.Stats case GameEvent.EventType.Death: break; case GameEvent.EventType.Damage: - if (!E.Owner.CustomCallback) - Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Owner.GetHashCode()); + // if (!E.Owner.CustomCallback) + Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, E.Owner.GetHashCode()); break; case GameEvent.EventType.ScriptDamage: killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index ce7fab39c..350f3bf70 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -451,6 +451,7 @@ namespace SharedLibraryCore.Commands if (ActiveClient != null) { ActiveClient.Level = newPerm; + await E.Owner.Manager.GetClientService().Update(ActiveClient); await ActiveClient.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS_TARGET"]} {newPerm}"); } @@ -806,6 +807,20 @@ namespace SharedLibraryCore.Commands E.Owner.Reports.Add(new Report(E.Target, E.Origin, E.Data)); + Penalty newReport = new Penalty() + { + Type = Penalty.PenaltyType.Report, + Expires = DateTime.UtcNow, + Offender = E.Target, + Offense = E.Data, + Punisher = E.Origin, + Active = true, + When = DateTime.UtcNow, + Link = E.Target.AliasLink + }; + + await E.Owner.Manager.GetPenaltyService().Create(newReport); + 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)); await E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data)); diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index c2e5294ee..f6fd40988 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -127,6 +127,9 @@ namespace SharedLibraryCore.Services }; var foundClient = await iqClient.FirstOrDefaultAsync(); + if (foundClient == null) + return null; + foundClient.Client.LinkedAccounts = new Dictionary(); // todo: find out the best way to do this // I'm doing this here because I don't know the best way to have multiple awaits in the query @@ -163,12 +166,11 @@ namespace SharedLibraryCore.Services if (entity.Level != client.Level) { // get all clients that use the same aliasId - var matchingClients = await context.Clients - .Where(c => c.CurrentAliasId == client.CurrentAliasId) - .ToListAsync(); + var matchingClients = context.Clients + .Where(c => c.CurrentAliasId == client.CurrentAliasId); // update all related clients level - matchingClients.ForEach(c => c.Level = (client.Level == Player.Permission.Banned) ? + await matchingClients.ForEachAsync(c => c.Level = (client.Level == Player.Permission.Banned) ? client.Level : entity.Level); } diff --git a/WebfrontCore/Controllers/ClientController.cs b/WebfrontCore/Controllers/ClientController.cs index 6dcad4e43..a61cbbff9 100644 --- a/WebfrontCore/Controllers/ClientController.cs +++ b/WebfrontCore/Controllers/ClientController.cs @@ -15,6 +15,10 @@ namespace WebfrontCore.Controllers public async Task ProfileAsync(int id) { var client = await Manager.GetClientService().Get(id); + if (client == null) + { + return NotFound(); + } var clientDto = new PlayerInfo() { diff --git a/WebfrontCore/wwwroot/css/profile.css b/WebfrontCore/wwwroot/css/profile.css index d3ea1572d..7ce2fbe75 100644 --- a/WebfrontCore/wwwroot/css/profile.css +++ b/WebfrontCore/wwwroot/css/profile.css @@ -94,8 +94,7 @@ } .penalties-color-report { - color: #749363; - color: rgba(116, 147, 99, 1); + color: #b3ae8f; } .penalties-color-warning {