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:
parent
36d493f05b
commit
d9a601328c
@ -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()
|
||||
{
|
||||
|
@ -138,7 +138,7 @@ namespace IW4MAdmin.Application
|
||||
sensitiveEvent.OnProcessed.Set();
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
await Task.Delay(2500);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,13 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
{
|
||||
public class IW4Info
|
||||
{
|
||||
public enum Team
|
||||
{
|
||||
Spectator,
|
||||
Axis,
|
||||
Allies
|
||||
}
|
||||
|
||||
public enum MeansOfDeath
|
||||
{
|
||||
NONE,
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -94,8 +94,7 @@
|
||||
}
|
||||
|
||||
.penalties-color-report {
|
||||
color: #749363;
|
||||
color: rgba(116, 147, 99, 1);
|
||||
color: #b3ae8f;
|
||||
}
|
||||
|
||||
.penalties-color-warning {
|
||||
|
Loading…
Reference in New Issue
Block a user