update change tracking and elo

master shows monitoring server count
master can provide individual localizations
This commit is contained in:
RaidMax 2018-05-20 21:35:56 -05:00
parent 4d585e6ab2
commit be68335f70
14 changed files with 176 additions and 97 deletions

View File

@ -8,7 +8,7 @@ using RestEase;
namespace IW4MAdmin.Application.API.Master namespace IW4MAdmin.Application.API.Master
{ {
public class AuthenticationId public class AuthenticationId
{ {
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
} }
@ -62,6 +62,9 @@ namespace IW4MAdmin.Application.API.Master
Task<VersionInfo> GetVersion(); Task<VersionInfo> GetVersion();
[Get("localization")] [Get("localization")]
Task<List<SharedLibraryCore.Localization.Layout>> GetLocalization(); Task<List<SharedLibraryCore.Localization.Layout>> GetLocalization();
[Get("localization/{languageTag}")]
Task<SharedLibraryCore.Localization.Layout> GetLocalization([Path("languageTag")] string languageTag);
} }
} }

View File

@ -19,13 +19,8 @@ namespace IW4MAdmin.Application.Localization
try try
{ {
var api = Endpoint.Get(); var api = Endpoint.Get();
var localizations = api.GetLocalization().Result; var localization = api.GetLocalization(currentLocale).Result;
Utilities.CurrentLocalization = localization;
var usingLocale = localizations.FirstOrDefault(l => l.LocalizationName == currentLocale
|| l.LocalizationName.Substring(0, 2) == currentLocale.Substring(0, 2)) ??
localizations.First();
Utilities.CurrentLocalization = usingLocale;
return; return;
} }

View File

@ -127,7 +127,7 @@ namespace Application.RconParsers
} }
// this happens if status is requested while map is rotating // 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"); throw new ServerException("Server is rotating map");
} }
@ -139,6 +139,7 @@ namespace Application.RconParsers
{ {
IW4MAdmin.Application.Program.ServerManager.Logger.WriteDebug(s); IW4MAdmin.Application.Program.ServerManager.Logger.WriteDebug(s);
} }
throw new ServerException("Bad status received");
} }
return StatusPlayers; return StatusPlayers;

View File

@ -58,25 +58,25 @@
<Compile Include="master\models\__init__.py"> <Compile Include="master\models\__init__.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Master\resources\authenticate.py"> <Compile Include="master\resources\authenticate.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="master\resources\history_graph.py"> <Compile Include="master\resources\history_graph.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Master\resources\instance.py"> <Compile Include="master\resources\instance.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Master\resources\localization.py"> <Compile Include="master\resources\localization.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Master\resources\null.py"> <Compile Include="master\resources\null.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Master\resources\version.py"> <Compile Include="master\resources\version.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Master\resources\__init__.py"> <Compile Include="master\resources\__init__.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="master\routes.py"> <Compile Include="master\routes.py">
@ -98,22 +98,21 @@
<Compile Include="master\views.py" /> <Compile Include="master\views.py" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="C:\Projects\IW4M-Admin\Master\master\" />
<Folder Include="master\" /> <Folder Include="master\" />
<Folder Include="master\context\" /> <Folder Include="master\context\" />
<Folder Include="master\models\" /> <Folder Include="master\models\" />
<Folder Include="master\config\" /> <Folder Include="master\config\" />
<Folder Include="master\schema\" /> <Folder Include="master\schema\" />
<Folder Include="Master\resources\" /> <Folder Include="Master\resources\" />
<Folder Include="Master\static\" /> <Folder Include="master\static\" />
<Folder Include="Master\templates\" /> <Folder Include="master\templates\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="FolderProfile.pubxml" /> <None Include="FolderProfile.pubxml" />
<Content Include="master\config\master.json" /> <Content Include="master\config\master.json" />
<Content Include="requirements.txt" /> <Content Include="requirements.txt" />
<Content Include="Master\templates\index.html" /> <Content Include="master\templates\index.html" />
<Content Include="Master\templates\layout.html" /> <Content Include="master\templates\layout.html" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Interpreter Include="dev_env\"> <Interpreter Include="dev_env\">

View File

@ -8,7 +8,7 @@ import csv
from io import StringIO from io import StringIO
class Localization(Resource): 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') 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') data = response.read().decode('utf-8')
@ -16,14 +16,12 @@ class Localization(Resource):
csv_data = csv.DictReader(StringIO(data)) csv_data = csv.DictReader(StringIO(data))
for language in csv_data.fieldnames[1:]: for language in csv_data.fieldnames[1:]:
localization.append( localization.append({
{
'LocalizationName' : language, 'LocalizationName' : language,
'LocalizationIndex' : { 'LocalizationIndex' : {
'Set' : {} 'Set' : {}
} }
} })
)
for row in csv_data: for row in csv_data:
localization_string = row['STRING'] localization_string = row['STRING']
@ -33,3 +31,29 @@ class Localization(Resource):
count += 1 count += 1
return localization, 200 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

View File

@ -12,4 +12,4 @@ api.add_resource(Instance, '/instance/', '/instance/<string:id>')
api.add_resource(Version, '/version') api.add_resource(Version, '/version')
api.add_resource(Authenticate, '/authenticate') api.add_resource(Authenticate, '/authenticate')
api.add_resource(HistoryGraph, '/history/', '/history/<int:history_count>') api.add_resource(HistoryGraph, '/history/', '/history/<int:history_count>')
api.add_resource(Localization, '/localization') api.add_resource(Localization, '/localization/', '/localization/<string:language_tag>')

View File

@ -57,6 +57,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
ClientPenalty = Penalty.PenaltyType.Any, ClientPenalty = Penalty.PenaltyType.Any,
}; };
DetectionPenaltyResult result = null;
if (LastHit == DateTime.MinValue) if (LastHit == DateTime.MinValue)
LastHit = DateTime.UtcNow; LastHit = DateTime.UtcNow;
@ -90,7 +92,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Log.WriteDebug($"HitCount = {hitLoc.HitCount}"); Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
Log.WriteDebug($"ID = {kill.AttackerId}"); Log.WriteDebug($"ID = {kill.AttackerId}");
return new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Ban, ClientPenalty = Penalty.PenaltyType.Ban,
Value = hitLoc.HitOffsetAverage, Value = hitLoc.HitOffsetAverage,
@ -111,7 +113,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Log.WriteDebug($"HitCount = {HitCount}"); Log.WriteDebug($"HitCount = {HitCount}");
Log.WriteDebug($"ID = {kill.AttackerId}"); Log.WriteDebug($"ID = {kill.AttackerId}");
return new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Ban, ClientPenalty = Penalty.PenaltyType.Ban,
Value = sessAverage, Value = sessAverage,
@ -125,8 +127,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Log.WriteDebug($"PredictVsReal={realAgainstPredict}"); Log.WriteDebug($"PredictVsReal={realAgainstPredict}");
#endif #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; LastOffset = kill.TimeOffset;
if (currentStrain > ClientStats.MaxStrain) if (currentStrain > ClientStats.MaxStrain)
@ -134,42 +137,25 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
ClientStats.MaxStrain = currentStrain; 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 // flag
if (currentWeightedStrain > Thresholds.MaxStrainFlag) if (currentStrain > Thresholds.MaxStrainFlag)
{ {
return new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Flag,
Value = currentWeightedStrain, Value = currentStrain,
HitCount = HitCount, HitCount = HitCount,
Type = DetectionType.Strain Type = DetectionType.Strain
}; };
} }
// ban // ban
if (currentWeightedStrain > Thresholds.MaxStrainBan if (currentStrain > Thresholds.MaxStrainBan)
&& Kills > Thresholds.LowSampleMinKills)
{ {
return new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Ban, ClientPenalty = Penalty.PenaltyType.Ban,
Value = currentWeightedStrain, Value = currentStrain,
HitCount = HitCount, HitCount = HitCount,
Type = DetectionType.Strain Type = DetectionType.Strain
}; };
@ -217,7 +203,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Ban, ClientPenalty = Penalty.PenaltyType.Ban,
Value = currentHeadshotRatio, Value = currentHeadshotRatio,
@ -238,7 +224,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Flag,
Value = currentHeadshotRatio, Value = currentHeadshotRatio,
@ -267,7 +253,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Ban, ClientPenalty = Penalty.PenaltyType.Ban,
Value = currentMaxBoneRatio, Value = currentMaxBoneRatio,
@ -288,7 +274,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Flag,
Value = currentMaxBoneRatio, Value = currentMaxBoneRatio,
@ -329,7 +315,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Ban, ClientPenalty = Penalty.PenaltyType.Ban,
Value = currentChestAbdomenRatio, Value = currentChestAbdomenRatio,
@ -351,7 +337,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Flag, ClientPenalty = Penalty.PenaltyType.Flag,
Value = currentChestAbdomenRatio, Value = currentChestAbdomenRatio,
@ -364,7 +350,19 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
#endregion #endregion
#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, ClientPenalty = Penalty.PenaltyType.Any,
}; };

View File

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

View File

@ -16,7 +16,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public int TimesReachedMaxStrain { get; private set; } 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) if (LastAngle == null)
LastAngle = newAngle; LastAngle = newAngle;
@ -42,16 +42,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
double newStrain = Math.Pow(distance[0] + distance[1], 0.99) / deltaTime; double newStrain = Math.Pow(distance[0] + distance[1], 0.99) / deltaTime;
newStrain *= killDistance / 1000.0;
if (damage < 100 && isDamage)
{
newStrain *= Math.Pow(damage, 2) / 10000.0;
}
else if (damage > 100)
{
newStrain *= damage / 100.0;
}
CurrentStrain += newStrain; CurrentStrain += newStrain;
@ -64,7 +55,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public string GetTrackableValue() 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); private double GetDecay(double deltaTime) => Math.Pow(StrainDecayBase, Math.Pow(2.0, deltaTime / 250.0) / 1000.0);

View File

@ -27,9 +27,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public const int HighSampleMinKills = 100; public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2; 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 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); public static double GetMarginOfError(int numKills) => 1.6455 / Math.Sqrt(numKills);

View File

@ -502,11 +502,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.Where(cs => cs.Value.ClientId != victimStats.ClientId) .Where(cs => cs.Value.ClientId != victimStats.ClientId)
.Average(cs => cs.Value.EloRating); .Average(cs => cs.Value.EloRating);
double attackerEloDifference = Math.Log(attackerLobbyRating) - Math.Log(attackerStats.EloRating); 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 / 0.5)); double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E));
double victimEloDifference = Math.Log(victimLobbyRating) - Math.Log(victimStats.EloRating); 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 / 0.5)); double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference / Math.E));
attackerStats.EloRating += 24.0 * (1 - winPercentage); attackerStats.EloRating += 24.0 * (1 - winPercentage);
victimStats.EloRating -= 24.0 * 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 // 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, 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); 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

View File

@ -14,6 +14,10 @@
<Configurations>Debug;Release;Prerelease</Configurations> <Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="Cheat\Strain.cs~RF16f7b3.TMP" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" /> <ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -16,7 +16,9 @@ namespace SharedLibraryCore.Helpers
public void OnChange(ITrackable value) 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() public void ClearChanges()
@ -24,18 +26,6 @@ namespace SharedLibraryCore.Helpers
Values.Clear(); Values.Clear();
} }
public string[] GetChanges() public string[] GetChanges() => Values.ToArray();
{
List<string> values = new List<string>();
int number = 1;
foreach (string change in Values)
{
values.Add($"{number} {change}");
number++;
}
return values.ToArray();
}
} }
} }

View File

@ -1,9 +1,26 @@
Version 2.1: Version 2.1:
CHANGELOG: CHANGELOG:
-add support for localization -add support for localization (Russian, Spanish, and Portuguese)
-upgraded projects to .NET Core 2.0.7 -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 "ConnectionString" in IW4MAdminSettings.json
-added support for MySQL provider via "ConnectrionString" -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: Version 2.0:
CHANGELOG: CHANGELOG: