diff --git a/Application/API/Master/IMasterApi.cs b/Application/API/Master/IMasterApi.cs index 7f8b9983..240f5fc9 100644 --- a/Application/API/Master/IMasterApi.cs +++ b/Application/API/Master/IMasterApi.cs @@ -8,7 +8,7 @@ using RestEase; namespace IW4MAdmin.Application.API.Master { public class AuthenticationId - { + { [JsonProperty("id")] public string Id { get; set; } } @@ -62,6 +62,9 @@ namespace IW4MAdmin.Application.API.Master Task GetVersion(); [Get("localization")] - Task> GetLocalization(); + Task> GetLocalization(); + + [Get("localization/{languageTag}")] + Task GetLocalization([Path("languageTag")] string languageTag); } } diff --git a/Application/Localization/Configure.cs b/Application/Localization/Configure.cs index 965c4bc7..cdd489f0 100644 --- a/Application/Localization/Configure.cs +++ b/Application/Localization/Configure.cs @@ -19,13 +19,8 @@ namespace IW4MAdmin.Application.Localization try { var api = Endpoint.Get(); - var localizations = api.GetLocalization().Result; - - var usingLocale = localizations.FirstOrDefault(l => l.LocalizationName == currentLocale - || l.LocalizationName.Substring(0, 2) == currentLocale.Substring(0, 2)) ?? - localizations.First(); - - Utilities.CurrentLocalization = usingLocale; + var localization = api.GetLocalization(currentLocale).Result; + Utilities.CurrentLocalization = localization; return; } diff --git a/Application/RconParsers/IW4RConParser.cs b/Application/RconParsers/IW4RConParser.cs index 028316b0..c69df3da 100644 --- a/Application/RconParsers/IW4RConParser.cs +++ b/Application/RconParsers/IW4RConParser.cs @@ -127,7 +127,7 @@ namespace Application.RconParsers } // this happens if status is requested while map is rotating - if (Status[1] == "Server Initialization") + if (Status.Contains("Server Initialization")) { throw new ServerException("Server is rotating map"); } @@ -139,6 +139,7 @@ namespace Application.RconParsers { IW4MAdmin.Application.Program.ServerManager.Logger.WriteDebug(s); } + throw new ServerException("Bad status received"); } return StatusPlayers; diff --git a/Master/Master.pyproj b/Master/Master.pyproj index 1208a6cc..809cc114 100644 --- a/Master/Master.pyproj +++ b/Master/Master.pyproj @@ -58,25 +58,25 @@ Code - + Code Code - + Code - + Code - + Code - + Code - + Code @@ -98,22 +98,21 @@ - - - + + - - + + diff --git a/Master/master/resources/localization.py b/Master/master/resources/localization.py index 1449567b..6c244162 100644 --- a/Master/master/resources/localization.py +++ b/Master/master/resources/localization.py @@ -8,7 +8,7 @@ import csv from io import StringIO class Localization(Resource): - def get(self): + def list(self): response = urllib.request.urlopen('https://docs.google.com/spreadsheets/d/e/2PACX-1vRQjCqPvd0Xqcn86WqpFqp_lx4KKpel9O4OV13NycmV8rmqycorgJQm-8qXMfw37QJHun3pqVZFUKG-/pub?gid=0&single=true&output=csv') data = response.read().decode('utf-8') @@ -16,14 +16,12 @@ class Localization(Resource): csv_data = csv.DictReader(StringIO(data)) for language in csv_data.fieldnames[1:]: - localization.append( - { + localization.append({ 'LocalizationName' : language, 'LocalizationIndex' : { 'Set' : {} } - } - ) + }) for row in csv_data: localization_string = row['STRING'] @@ -33,3 +31,29 @@ class Localization(Resource): count += 1 return localization, 200 + + def get(self, language_tag=None): + response = urllib.request.urlopen('https://docs.google.com/spreadsheets/d/e/2PACX-1vRQjCqPvd0Xqcn86WqpFqp_lx4KKpel9O4OV13NycmV8rmqycorgJQm-8qXMfw37QJHun3pqVZFUKG-/pub?gid=0&single=true&output=csv') + data = response.read().decode('utf-8') + + csv_data = csv.DictReader(StringIO(data)) + + + if language_tag != None: + valid_language_tag = next((l for l in csv_data.fieldnames[1:] if l == language_tag), None) + if valid_language_tag is None: + valid_language_tag = next((l for l in csv_data.fieldnames[1:] if l.startswith(language_tag[:2])), None) + if valid_language_tag is None: + valid_language_tag = 'en-US' + localization = { + 'LocalizationName' : valid_language_tag, + 'LocalizationIndex' : { + 'Set' : {} + } + } + for row in csv_data: + localization_string = row['STRING'] + localization['LocalizationIndex']['Set'][localization_string] = row[valid_language_tag] + return localization, 200 + else: + return self.list()[0][0], 200 diff --git a/Master/master/routes.py b/Master/master/routes.py index 20ceabba..52f618b7 100644 --- a/Master/master/routes.py +++ b/Master/master/routes.py @@ -12,4 +12,4 @@ api.add_resource(Instance, '/instance/', '/instance/') api.add_resource(Version, '/version') api.add_resource(Authenticate, '/authenticate') api.add_resource(HistoryGraph, '/history/', '/history/') -api.add_resource(Localization, '/localization') \ No newline at end of file +api.add_resource(Localization, '/localization/', '/localization/') \ No newline at end of file diff --git a/Plugins/Stats/Cheat/Detection.cs b/Plugins/Stats/Cheat/Detection.cs index aebc15d3..9446a486 100644 --- a/Plugins/Stats/Cheat/Detection.cs +++ b/Plugins/Stats/Cheat/Detection.cs @@ -57,6 +57,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat ClientPenalty = Penalty.PenaltyType.Any, }; + DetectionPenaltyResult result = null; + if (LastHit == DateTime.MinValue) LastHit = DateTime.UtcNow; @@ -90,7 +92,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat Log.WriteDebug($"HitCount = {hitLoc.HitCount}"); Log.WriteDebug($"ID = {kill.AttackerId}"); - return new DetectionPenaltyResult() + result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = hitLoc.HitOffsetAverage, @@ -111,7 +113,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat Log.WriteDebug($"HitCount = {HitCount}"); Log.WriteDebug($"ID = {kill.AttackerId}"); - return new DetectionPenaltyResult() + result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = sessAverage, @@ -125,8 +127,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat Log.WriteDebug($"PredictVsReal={realAgainstPredict}"); #endif } - double currentStrain = Strain.GetStrain(isDamage, kill.Damage, kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset)); - double currentWeightedStrain = (currentStrain * ClientStats.SPM) / 170.0; + + 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; if (currentStrain > ClientStats.MaxStrain) @@ -134,42 +137,25 @@ namespace IW4MAdmin.Plugins.Stats.Cheat ClientStats.MaxStrain = currentStrain; } - if (currentWeightedStrain > Thresholds.MaxStrainFlag) - { - Tracker.OnChange(Strain); - - foreach (string change in Tracker.GetChanges()) - { - Log.WriteDebug(change); - } - Log.WriteDebug(ClientStats.RoundScore.ToString()); - } - - else - { - Tracker.ClearChanges(); - } - // flag - if (currentWeightedStrain > Thresholds.MaxStrainFlag) + if (currentStrain > Thresholds.MaxStrainFlag) { - return new DetectionPenaltyResult() + result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, - Value = currentWeightedStrain, + Value = currentStrain, HitCount = HitCount, Type = DetectionType.Strain }; } // ban - if (currentWeightedStrain > Thresholds.MaxStrainBan - && Kills > Thresholds.LowSampleMinKills) + if (currentStrain > Thresholds.MaxStrainBan) { - return new DetectionPenaltyResult() + result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, - Value = currentWeightedStrain, + Value = currentStrain, HitCount = HitCount, Type = DetectionType.Strain }; @@ -217,7 +203,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); Log.WriteDebug(sb.ToString()); - return new DetectionPenaltyResult() + result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = currentHeadshotRatio, @@ -238,7 +224,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); Log.WriteDebug(sb.ToString()); - return new DetectionPenaltyResult() + result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, Value = currentHeadshotRatio, @@ -267,7 +253,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); Log.WriteDebug(sb.ToString()); - return new DetectionPenaltyResult() + result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = currentMaxBoneRatio, @@ -288,7 +274,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); Log.WriteDebug(sb.ToString()); - return new DetectionPenaltyResult() + result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, Value = currentMaxBoneRatio, @@ -329,7 +315,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); Log.WriteDebug(sb.ToString()); - return new DetectionPenaltyResult() + result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = currentChestAbdomenRatio, @@ -351,7 +337,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat Log.WriteDebug(sb.ToString()); // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); - return new DetectionPenaltyResult() + result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, Value = currentChestAbdomenRatio, @@ -364,7 +350,19 @@ namespace IW4MAdmin.Plugins.Stats.Cheat } #endregion #endregion - return new DetectionPenaltyResult() + + Tracker.OnChange(new DetectionTracking(ClientStats, kill, Strain)); + + if (result != null) + { + foreach (string change in Tracker.GetChanges()) + { + Log.WriteDebug(change); + Log.WriteDebug("--------------SNAPSHOT END-----------"); + } + } + + return result ?? new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Any, }; diff --git a/Plugins/Stats/Cheat/DetectionTracking.cs b/Plugins/Stats/Cheat/DetectionTracking.cs new file mode 100644 index 00000000..c83c8fe0 --- /dev/null +++ b/Plugins/Stats/Cheat/DetectionTracking.cs @@ -0,0 +1,57 @@ +using IW4MAdmin.Plugins.Stats.Cheat; +using IW4MAdmin.Plugins.Stats.Models; +using SharedLibraryCore.Interfaces; +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin.Plugins.Stats.Cheat +{ + class DetectionTracking : ITrackable + { + EFClientStatistics Stats; + EFClientKill Hit; + Strain Strain; + + public DetectionTracking(EFClientStatistics stats, EFClientKill hit, Strain strain) + { + Stats = stats; + Hit = hit; + Strain = strain; + } + + public string GetTrackableValue() + { + var sb = new StringBuilder(); + sb.AppendLine($"SPM = {Stats.SPM}"); + sb.AppendLine($"KDR = {Stats.KDR}"); + sb.AppendLine($"Kills = {Stats.Kills}"); + sb.AppendLine($"Session Score = {Stats.SessionScore}"); + sb.AppendLine($"Elo = {Stats.EloRating}"); + sb.AppendLine($"Max Sess Strain = {Stats.MaxSessionStrain}"); + sb.AppendLine($"MaxStrain = {Stats.MaxStrain}"); + sb.AppendLine($"Avg Offset = {Stats.AverageHitOffset}"); + sb.AppendLine($"TimePlayed, {Stats.TimePlayed}"); + sb.AppendLine($"HitDamage = {Hit.Damage}"); + sb.AppendLine($"HitOrigin = {Hit.KillOrigin}"); + sb.AppendLine($"DeathOrigin = {Hit.DeathOrigin}"); + sb.AppendLine($"ViewAngles = {Hit.ViewAngles}"); + sb.AppendLine($"WeaponId = {Hit.Weapon.ToString()}"); + sb.AppendLine($"Timeoffset = {Hit.TimeOffset}"); + sb.AppendLine($"HitLocation = {Hit.HitLoc.ToString()}"); + sb.AppendLine($"Distance = {Hit.Distance / 0.0254}"); + sb.AppendLine($"HitType = {Hit.DeathType.ToString()}"); + int i = 0; + foreach (var predictedAngle in Hit.AnglesList) + { + sb.AppendLine($"Predicted Angle [{i}] {predictedAngle}"); + i++; + } + sb.AppendLine(Strain.GetTrackableValue()); + sb.AppendLine($"VictimId = {Hit.VictimId}"); + sb.AppendLine($"AttackerId = {Hit.AttackerId}"); + return sb.ToString(); + + } + } +} diff --git a/Plugins/Stats/Cheat/Strain.cs b/Plugins/Stats/Cheat/Strain.cs index 6929fcb6..f0f7b2de 100644 --- a/Plugins/Stats/Cheat/Strain.cs +++ b/Plugins/Stats/Cheat/Strain.cs @@ -16,7 +16,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat public int TimesReachedMaxStrain { get; private set; } - public double GetStrain(bool isDamage, int damage, Vector3 newAngle, double deltaTime) + public double GetStrain(bool isDamage, int damage, double killDistance, Vector3 newAngle, double deltaTime) { if (LastAngle == null) LastAngle = newAngle; @@ -42,16 +42,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat } double newStrain = Math.Pow(distance[0] + distance[1], 0.99) / deltaTime; - - if (damage < 100 && isDamage) - { - newStrain *= Math.Pow(damage, 2) / 10000.0; - } - - else if (damage > 100) - { - newStrain *= damage / 100.0; - } + newStrain *= killDistance / 1000.0; CurrentStrain += newStrain; @@ -64,7 +55,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat public string GetTrackableValue() { - return $"Strain - {CurrentStrain}, Angle - {LastAngle}, Delta Time - {LastDeltaTime}, Distance - {LastDistance}"; + return $"Strain = {CurrentStrain}\r\n, Angle = {LastAngle}\r\n, Delta Time = {LastDeltaTime}\r\n, Angle Between = {LastDistance}"; } private double GetDecay(double deltaTime) => Math.Pow(StrainDecayBase, Math.Pow(2.0, deltaTime / 250.0) / 1000.0); diff --git a/Plugins/Stats/Cheat/Thresholds.cs b/Plugins/Stats/Cheat/Thresholds.cs index b3ca34ca..d9a0d74a 100644 --- a/Plugins/Stats/Cheat/Thresholds.cs +++ b/Plugins/Stats/Cheat/Thresholds.cs @@ -27,9 +27,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat public const int HighSampleMinKills = 100; public const double KillTimeThreshold = 0.2; - public const double MaxStrainBan = 2.5; + public const double MaxStrainBan = 0.4; public const double MaxOffset = 1.2; - public const double MaxStrainFlag = 2.0; + public const double MaxStrainFlag = 0.36; public static double GetMarginOfError(int numKills) => 1.6455 / Math.Sqrt(numKills); diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index cdc16a5d..5defcb3d 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -502,11 +502,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers .Where(cs => cs.Value.ClientId != victimStats.ClientId) .Average(cs => cs.Value.EloRating); - double attackerEloDifference = Math.Log(attackerLobbyRating) - Math.Log(attackerStats.EloRating); - double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / 0.5)); + double attackerEloDifference = Math.Log(attackerLobbyRating <= 0 ? 1 : attackerLobbyRating) - Math.Log(attackerStats.EloRating <= 0 ? 1 : attackerStats.EloRating); + double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E)); - double victimEloDifference = Math.Log(victimLobbyRating) - Math.Log(victimStats.EloRating); - double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference / 0.5)); + 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)); attackerStats.EloRating += 24.0 * (1 - winPercentage); victimStats.EloRating -= 24.0 * winPercentage; @@ -559,7 +559,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // 1.637 is a Eddie-Generated number that weights the KDR nicely 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) * currentKDR; + clientStats.RollingWeightedKDR = (alpha * currentKDR) + (1.0 - alpha) * clientStats.KDR; 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 diff --git a/Plugins/Stats/Stats.csproj b/Plugins/Stats/Stats.csproj index 46e28291..690c5a40 100644 --- a/Plugins/Stats/Stats.csproj +++ b/Plugins/Stats/Stats.csproj @@ -14,6 +14,10 @@ Debug;Release;Prerelease + + + + diff --git a/SharedLibraryCore/Helpers/ChangeTracking.cs b/SharedLibraryCore/Helpers/ChangeTracking.cs index c626c42d..b535f632 100644 --- a/SharedLibraryCore/Helpers/ChangeTracking.cs +++ b/SharedLibraryCore/Helpers/ChangeTracking.cs @@ -16,7 +16,9 @@ namespace SharedLibraryCore.Helpers public void OnChange(ITrackable value) { - Values.Add(value.GetTrackableValue()); + if (Values.Count > 30) + Values.RemoveAt(0); + Values.Add($"{DateTime.Now.ToString("HH:mm:ss.fff")} {value.GetTrackableValue()}"); } public void ClearChanges() @@ -24,18 +26,6 @@ namespace SharedLibraryCore.Helpers Values.Clear(); } - public string[] GetChanges() - { - List values = new List(); - - int number = 1; - foreach (string change in Values) - { - values.Add($"{number} {change}"); - number++; - } - - return values.ToArray(); - } + public string[] GetChanges() => Values.ToArray(); } } diff --git a/version.txt b/version.txt index 5815a964..f40c353d 100644 --- a/version.txt +++ b/version.txt @@ -1,9 +1,26 @@ Version 2.1: CHANGELOG: --add support for localization +-add support for localization (Russian, Spanish, and Portuguese) -upgraded projects to .NET Core 2.0.7 --redid the event system to haev a single line of execution --added support for MySQL provider via "ConnectrionString" +-added support for MySQL provider via "ConnectionString" in IW4MAdminSettings.json +-refactored some stats code to provide a better representation of player skill as "performance" +-added most played command which shows players who have played the most +-added unflag command to more intuitively unflag a client +-added multi-line tokens: {{TOPSTATS}} {{MOSTPLAYED}} +-able to view linked accounts on webfront via dropdown (privileged only) +-multiple privileged accouns are consolidated in the admin list +-Added IW5m/Pluto IW5, T5m/V2, CoD4, and WaW support +-changed event system to use a better pipeline +-IW4x anti-cheat further refined +-kick and temban required privileges adjusted +-fixed issues with RCon responding improperly +-improved IW4x frequency of IW4x servers going offline +-profanity plugin now kicks players with offensive names (if enabled) +-fixed critical bug with CPU usage over time +-discord link has been generalized into a "social link" (website/facebook/vk etc...) +-untold bug fixes +-introduced new bugs to fix in the next version + Version 2.0: CHANGELOG: