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", "");
if (message[0] == '!' || message[1] == '@')
if (message[0] == '!' || message[0] == '@')
{
return new GameEvent()
{

View File

@ -138,7 +138,7 @@ namespace IW4MAdmin.Application
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
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;

View File

@ -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<int, Detection> 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);
}
}
}
}

View File

@ -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<IW4Info.MeansOfDeath>.Get(match.Groups[12].Value, typeof(IW4Info.MeansOfDeath));
var hitLocation = ParseEnum<IW4Info.HitLocation>.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;
}
}
/// <summary>
@ -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;
}
}
}

View File

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

View File

@ -94,5 +94,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
}
[NotMapped]
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);
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];

View File

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

View File

@ -127,6 +127,9 @@ namespace SharedLibraryCore.Services
};
var foundClient = await iqClient.FirstOrDefaultAsync();
if (foundClient == null)
return null;
foundClient.Client.LinkedAccounts = new Dictionary<int, long>();
// 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);
}

View File

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

View File

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