stats tweaked to scale SPM based on team size

invalid client id results in 404 rather than exception page
performance based on traditional elo rating
fixed @ (broadcast commands)
added reports to penalty list and profile
This commit is contained in:
RaidMax 2018-05-24 14:48:57 -05:00
parent 36d493f05b
commit d9a601328c
12 changed files with 108 additions and 47 deletions

View File

@ -34,7 +34,7 @@ namespace IW4MAdmin.Application.EventParsers
{ {
string message = lineSplit[4].Replace("\x15", ""); string message = lineSplit[4].Replace("\x15", "");
if (message[0] == '!' || message[1] == '@') if (message[0] == '!' || message[0] == '@')
{ {
return new GameEvent() return new GameEvent()
{ {

View File

@ -138,7 +138,7 @@ namespace IW4MAdmin.Application
sensitiveEvent.OnProcessed.Set(); sensitiveEvent.OnProcessed.Set();
} }
await Task.Delay(1000); await Task.Delay(2500);
} }
} }

View File

@ -127,19 +127,9 @@ 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.Contains("Server Initialization"))
{
throw new ServerException("Server is rotating map");
}
if (Status.Length > 5 && validMatches == 0) if (Status.Length > 5 && validMatches == 0)
{ {
IW4MAdmin.Application.Program.ServerManager.Logger.WriteError("BAD STATUS!"); throw new ServerException("Server is rotating map");
foreach (var s in Status)
{
IW4MAdmin.Application.Program.ServerManager.Logger.WriteDebug(s);
}
throw new ServerException("Bad status received");
} }
return StatusPlayers; return StatusPlayers;

View File

@ -1,6 +1,8 @@
using IW4MAdmin.Plugins.Stats.Cheat; using IW4MAdmin.Plugins.Stats.Cheat;
using IW4MAdmin.Plugins.Stats.Models; using IW4MAdmin.Plugins.Stats.Models;
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Linq;
namespace IW4MAdmin.Plugins.Stats.Helpers namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
@ -9,6 +11,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public ConcurrentDictionary<int, Detection> PlayerDetections { get; set; } public ConcurrentDictionary<int, Detection> PlayerDetections { get; set; }
public EFServerStatistics ServerStatistics { get; private set; } public EFServerStatistics ServerStatistics { get; private set; }
public EFServer Server { get; private set; } public EFServer Server { get; private set; }
public bool IsTeamBased { get; set; }
public ServerStats(EFServer sv, EFServerStatistics st) public ServerStats(EFServer sv, EFServerStatistics st)
{ {
@ -17,5 +20,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ServerStatistics = st; ServerStatistics = st;
Server = sv; 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);
}
}
} }
} }

View File

@ -10,6 +10,7 @@ using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using SharedLibraryCore.Commands; using SharedLibraryCore.Commands;
using IW4MAdmin.Plugins.Stats.Models; using IW4MAdmin.Plugins.Stats.Models;
using System.Text.RegularExpressions;
namespace IW4MAdmin.Plugins.Stats.Helpers namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
@ -68,7 +69,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
statsSvc.ServerStatsSvc.SaveChanges(); statsSvc.ServerStatsSvc.SaveChanges();
var serverStats = statsSvc.ServerStatsSvc.Find(c => c.ServerId == serverId).FirstOrDefault(); 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) catch (Exception e)
@ -214,24 +218,21 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds; 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)
{
if (match.Success) // this gives us what time the player is on
{ var attackerStats = Servers[serverId].PlayerStats[attackerClientId];
var meansOfDeath = ParseEnum<IW4Info.MeansOfDeath>.Get(match.Groups[12].Value, typeof(IW4Info.MeansOfDeath)); var victimStats = Servers[serverId].PlayerStats[victimClientId];
var hitLocation = ParseEnum<IW4Info.HitLocation>.Get(match.Groups[13].Value, typeof(IW4Info.HitLocation)); 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());
if (meansOfDeath == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || attackerStats.Team = attackerTeam;
meansOfDeath == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET || victimStats.Team = victimTeam;
meansOfDeath == IW4Info.MeansOfDeath.MOD_HEAD_SHOT) }
{
ClientStats.HitLocations.First(hl => hl.Location == hitLocation).HitCount += 1;
}
}*/
} }
/// <summary> /// <summary>
@ -494,22 +495,38 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// calulate elo // calulate elo
if (Servers[attackerStats.ServerId].PlayerStats.Count > 1) 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) .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) .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 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 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 lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference/ Math.E));
attackerStats.EloRating += 24.0 * (1 - winPercentage); attackerStats.EloRating += 6.0 * (1 - winPercentage);
victimStats.EloRating -= 24.0 * winPercentage; victimStats.EloRating -= 6.0 * (1 - winPercentage);
attackerStats.EloRating = Math.Max(0, Math.Round(attackerStats.EloRating, 2)); attackerStats.EloRating = Math.Max(0, Math.Round(attackerStats.EloRating, 2));
victimStats.EloRating = Math.Max(0, Math.Round(victimStats.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; scoreDifference = clientStats.RoundScore + clientStats.LastScore;
} }
else if (clientStats.RoundScore > 0 && clientStats.LastScore < clientStats.RoundScore) else if (clientStats.RoundScore > 0 && clientStats.LastScore < clientStats.RoundScore)
{ {
scoreDifference = clientStats.RoundScore - clientStats.LastScore; scoreDifference = clientStats.RoundScore - clientStats.LastScore;
} }
double killSPM = scoreDifference / timeSinceLastCalc; 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 // 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
@ -676,5 +696,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// this should prevent the gunk for having a long lasting context. // this should prevent the gunk for having a long lasting context.
ContextThreads[serverId] = new ThreadSafeStatsService(); ContextThreads[serverId] = new ThreadSafeStatsService();
} }
public void SetTeamBased(int serverId, bool isTeamBased)
{
Servers[serverId].IsTeamBased = isTeamBased;
}
} }
} }

View File

@ -8,6 +8,13 @@ namespace IW4MAdmin.Plugins.Stats
{ {
public class IW4Info public class IW4Info
{ {
public enum Team
{
Spectator,
Axis,
Allies
}
public enum MeansOfDeath public enum MeansOfDeath
{ {
NONE, NONE,

View File

@ -94,5 +94,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
} }
[NotMapped] [NotMapped]
private List<int> SessionScores = new List<int>() { 0 }; private List<int> SessionScores = new List<int>() { 0 };
[NotMapped]
public IW4Info.Team Team { get; set; }
} }
} }

View File

@ -49,6 +49,7 @@ namespace IW4MAdmin.Plugins.Stats
await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data); await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data);
break; break;
case GameEvent.EventType.MapChange: case GameEvent.EventType.MapChange:
Manager.SetTeamBased(E.Owner.GetHashCode(), E.Owner.Gametype != "dm");
Manager.ResetKillstreaks(S.GetHashCode()); Manager.ResetKillstreaks(S.GetHashCode());
await Manager.Sync(S); await Manager.Sync(S);
break; break;
@ -83,8 +84,8 @@ namespace IW4MAdmin.Plugins.Stats
case GameEvent.EventType.Death: case GameEvent.EventType.Death:
break; break;
case GameEvent.EventType.Damage: case GameEvent.EventType.Damage:
if (!E.Owner.CustomCallback) // if (!E.Owner.CustomCallback)
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Owner.GetHashCode()); Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, E.Owner.GetHashCode());
break; break;
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];

View File

@ -451,6 +451,7 @@ namespace SharedLibraryCore.Commands
if (ActiveClient != null) if (ActiveClient != null)
{ {
ActiveClient.Level = newPerm; ActiveClient.Level = newPerm;
await E.Owner.Manager.GetClientService().Update(ActiveClient);
await ActiveClient.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS_TARGET"]} {newPerm}"); 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)); 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"]); 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)); 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)); await E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data));

View File

@ -127,6 +127,9 @@ namespace SharedLibraryCore.Services
}; };
var foundClient = await iqClient.FirstOrDefaultAsync(); var foundClient = await iqClient.FirstOrDefaultAsync();
if (foundClient == null)
return null;
foundClient.Client.LinkedAccounts = new Dictionary<int, long>(); foundClient.Client.LinkedAccounts = new Dictionary<int, long>();
// todo: find out the best way to do this // 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 // 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) if (entity.Level != client.Level)
{ {
// get all clients that use the same aliasId // get all clients that use the same aliasId
var matchingClients = await context.Clients var matchingClients = context.Clients
.Where(c => c.CurrentAliasId == client.CurrentAliasId) .Where(c => c.CurrentAliasId == client.CurrentAliasId);
.ToListAsync();
// update all related clients level // 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); client.Level : entity.Level);
} }

View File

@ -15,6 +15,10 @@ namespace WebfrontCore.Controllers
public async Task<IActionResult> ProfileAsync(int id) public async Task<IActionResult> ProfileAsync(int id)
{ {
var client = await Manager.GetClientService().Get(id); var client = await Manager.GetClientService().Get(id);
if (client == null)
{
return NotFound();
}
var clientDto = new PlayerInfo() var clientDto = new PlayerInfo()
{ {

View File

@ -94,8 +94,7 @@
} }
.penalties-color-report { .penalties-color-report {
color: #749363; color: #b3ae8f;
color: rgba(116, 147, 99, 1);
} }
.penalties-color-warning { .penalties-color-warning {