More stats polishing

fixed player specification for commands with multiple words
This commit is contained in:
RaidMax 2018-02-09 01:21:25 -06:00
parent f929e606f4
commit b9900707b5
17 changed files with 240 additions and 61 deletions

View File

@ -1,5 +1,4 @@

#define USINGMEMORY
using System;
using System.Runtime.InteropServices;
using SharedLibrary;

View File

@ -199,6 +199,12 @@ namespace IW4MAdmin
var Status = TaskStatuses[i];
if (Status.RequestedTask == null || Status.RequestedTask.Status == TaskStatus.RanToCompletion)
{
if (Status.ElapsedMillisecondsTime() > 60000)
{
Logger.WriteWarning($"Task took longer than 60 seconds to complete, killing");
//Status.RequestedTask.
}
Status.Update(new Task<bool>(() => { return (Status.Dependant as Server).ProcessUpdatesAsync(Status.GetToken()).Result; }));
if (Status.RunAverage > 1000 + UPDATE_FREQUENCY)
Logger.WriteWarning($"Update task average execution is longer than desired for {(Status.Dependant as Server)} [{Status.RunAverage}ms]");

View File

@ -34,6 +34,7 @@ namespace IW4MAdmin
{
// update their ping
Players[polledPlayer.ClientNumber].Ping = polledPlayer.Ping;
Players[polledPlayer.ClientNumber].Score = polledPlayer.Score;
return true;
}
@ -112,6 +113,7 @@ namespace IW4MAdmin
// NewPlayer.Level = Player.Permission.Flagged;
// Do the player specific stuff
player.ClientNumber = polledPlayer.ClientNumber;
player.Score = polledPlayer.Score;
Players[player.ClientNumber] = player;
Logger.WriteInfo($"Client {player} connecting...");
@ -210,7 +212,15 @@ namespace IW4MAdmin
if (C.RequiresTarget || Args.Length > 0)
{
int cNum = -1;
int.TryParse(Args[0], out cNum);
try
{
cNum = Convert.ToInt32(Args[0]);
}
catch(FormatException)
{
}
if (Args[0][0] == '@') // user specifying target by database ID
{
@ -227,7 +237,7 @@ namespace IW4MAdmin
}
}
else if (Args[0].Length < 3 && cNum > -1 && cNum < 18) // user specifying target by client num
else if (Args[0].Length < 3 && cNum > -1 && cNum < MaxClients) // user specifying target by client num
{
if (Players[cNum] != null)
{
@ -250,6 +260,13 @@ namespace IW4MAdmin
{
E.Target = matchingPlayers.First();
E.Data = Regex.Replace(E.Data, $"\"{E.Target.Name}\"", "", RegexOptions.IgnoreCase).Trim();
if (E.Data.ToLower().Trim() == E.Target.Name.ToLower().Trim())
{
await E.Origin.Tell($"Not enough arguments supplied!");
await E.Origin.Tell(C.Syntax);
throw new SharedLibrary.Exceptions.CommandException($"{E.Origin} did not supply enough arguments for \"{C.Name}\"");
}
}
}
@ -267,6 +284,13 @@ namespace IW4MAdmin
{
E.Target = matchingPlayers.First();
E.Data = Regex.Replace(E.Data, $"{E.Target.Name}", "", RegexOptions.IgnoreCase).Trim();
if (E.Data.Trim() == E.Target.Name.ToLower().Trim())
{
await E.Origin.Tell($"Not enough arguments supplied!");
await E.Origin.Tell(C.Syntax);
throw new SharedLibrary.Exceptions.CommandException($"{E.Origin} did not supply enough arguments for \"{C.Name}\"");
}
}
}

Binary file not shown.

View File

@ -11,11 +11,13 @@ namespace StatsPlugin.Helpers
public class ServerStats
{
public Dictionary<int, EFClientStatistics> PlayerStats { get; set; }
public EFServerStatistics ServerStatistics { get; private set; }
public EFServer Server { get; private set; }
public ServerStats(EFServer sv)
public ServerStats(EFServer sv, EFServerStatistics st)
{
PlayerStats = new Dictionary<int, EFClientStatistics>();
ServerStatistics = st;
Server = sv;
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibrary;
using SharedLibrary.Helpers;
@ -19,7 +18,8 @@ namespace StatsPlugin.Helpers
private IManager Manager;
private GenericRepository<EFClientStatistics> ClientStatSvc;
private GenericRepository<EFServer> ServerSvc;
private GenericRepository<EFClientKill> KillSvc;
private GenericRepository<EFClientKill> KillStatsSvc;
private GenericRepository<EFServerStatistics> ServerStatsSvc;
public StatManager(IManager mgr)
{
@ -28,13 +28,13 @@ namespace StatsPlugin.Helpers
Manager = mgr;
ClientStatSvc = new GenericRepository<EFClientStatistics>();
ServerSvc = new GenericRepository<EFServer>();
KillSvc = new GenericRepository<EFClientKill>();
KillStatsSvc = new GenericRepository<EFClientKill>();
ServerStatsSvc = new GenericRepository<EFServerStatistics>();
}
~StatManager()
{
Servers.Clear();
Log.WriteInfo("Cleared StatManager servers");
Log = null;
Servers = null;
}
@ -48,6 +48,7 @@ namespace StatsPlugin.Helpers
try
{
int serverId = sv.GetHashCode();
// get the server from the database if it exists, otherwise create and insert a new one
var server = ServerSvc.Find(c => c.ServerId == serverId).FirstOrDefault();
if (server == null)
@ -64,7 +65,13 @@ namespace StatsPlugin.Helpers
// this doesn't need to be async as it's during initialization
ServerSvc.SaveChanges();
Servers.Add(sv.GetHashCode(), new ServerStats(server));
InitializeServerStats(sv);
ServerStatsSvc.SaveChanges();
var serverStats = ServerStatsSvc.Find(c => c.ServerId == serverId).FirstOrDefault();
// check to see if the stats have ever been initialized
Servers.Add(serverId, new ServerStats(server, serverStats));
}
catch (Exception e)
@ -102,6 +109,10 @@ namespace StatsPlugin.Helpers
clientStats = ClientStatSvc.Insert(clientStats);
}
// set these on connecting
clientStats.LastActive = DateTime.UtcNow;
clientStats.LastStatCalculation = DateTime.UtcNow;
lock (playerStats)
{
if (playerStats.ContainsKey(pl.ClientNumber))
@ -123,17 +134,9 @@ namespace StatsPlugin.Helpers
// remove the client from the stats dictionary as they're leaving
lock (playerStats)
playerStats.Remove(pl.ClientNumber);
// allow accessing certain properties
//clientStats.Client = pl;
// update skill
// clientStats = UpdateStats(clientStats);
// reset for EF cache
//clientStats.SessionDeaths = 0;
// clientStats.SessionKills = 0;
// prevent mismatched primary key
//clientStats.Client = null;
// update in database
//await ClientStatSvc.SaveChangesAsync();
var serverStats = ServerStatsSvc.Find(sv => sv.ServerId == serverId).FirstOrDefault();
serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds;
}
/// <summary>
@ -160,18 +163,29 @@ namespace StatsPlugin.Helpers
Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName))
};
KillSvc.Insert(kill);
await KillSvc.SaveChangesAsync();
KillStatsSvc.Insert(kill);
await KillStatsSvc.SaveChangesAsync();
}
public void AddStandardKill(Player attacker, Player victim)
{
var attackerStats = Servers[attacker.CurrentServer.GetHashCode()].PlayerStats[attacker.ClientNumber];
// set to access total time
int serverId = attacker.CurrentServer.GetHashCode();
var attackerStats = Servers[serverId].PlayerStats[attacker.ClientNumber];
// set to access total time played
attackerStats.Client = attacker;
var victimStats = Servers[victim.CurrentServer.GetHashCode()].PlayerStats[victim.ClientNumber];
var victimStats = Servers[serverId].PlayerStats[victim.ClientNumber];
// update the total stats
Servers[serverId].ServerStatistics.TotalKills += 1;
// calculate for the clients
CalculateKill(attackerStats, victimStats);
// immediately write changes in debug
#if DEBUG
ClientStatSvc.SaveChanges();
ServerStatsSvc.SaveChanges();
#endif
}
/// <summary>
@ -195,10 +209,9 @@ namespace StatsPlugin.Helpers
UpdateStats(attackerStats);
attackerStats.Client = null;
// immediately write changes in debug
#if DEBUG
ClientStatSvc.SaveChanges();
#endif
// update after calculation
attackerStats.LastActive = DateTime.UtcNow;
victimStats.LastActive = DateTime.UtcNow;
}
/// <summary>
@ -208,44 +221,83 @@ namespace StatsPlugin.Helpers
/// <returns></returns>
private EFClientStatistics UpdateStats(EFClientStatistics clientStats)
{
// if it's their first kill we need to set the last kill as the time they joined
clientStats.LastStatCalculation = (clientStats.LastStatCalculation == DateTime.MinValue) ? DateTime.UtcNow : clientStats.LastStatCalculation;
double timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0;
double timeSinceLastActive = (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0;
// each 'session' is one minute
if (timeSinceLastCalc >= 1)
{
Log.WriteDebug($"Updated stats for {clientStats.ClientId} ({clientStats.SessionKills})");
// calculate the players Score Per Minute for the current session
// todo: score should be based on gamemode
double killSPM = clientStats.SessionKills * 100.0;
// prevent NaN or inactive time lowering SPM
if (timeSinceLastCalc == 0 || timeSinceLastActive > 3)
return clientStats;
// calculate how much the KDR should weigh
// 1.637 is a Eddie-Generated number that weights the KDR nicely
double KDRWeight = Math.Round(Math.Pow(clientStats.KDR, 1.637 / Math.E), 3);
// calculate the players Score Per Minute for the current session
int currentScore = Manager.GetActiveClients()
.First(c => c.ClientId == clientStats.ClientId)
.Score;
double killSPM = currentScore / (timeSinceLastCalc * 60.0);
// if no SPM, weight is 1 else the weight ishe current session's spm / lifetime average score per minute
double SPMWeightAgainstAverage = (clientStats.SPM < 1) ? 1 : killSPM / clientStats.SPM;
// calculate how much the KDR should weigh
// 1.637 is a Eddie-Generated number that weights the KDR nicely
double KDRWeight = Math.Round(Math.Pow(clientStats.KDR, 1.637 / Math.E), 3);
// calculate the weight of the new play time against last 10 hours of gameplay
int totalConnectionTime = (clientStats.Client.TotalConnectionTime == 0) ?
(int)(DateTime.UtcNow - clientStats.Client.FirstConnection).TotalSeconds :
clientStats.Client.TotalConnectionTime + (int)(DateTime.UtcNow - clientStats.Client.LastConnection).TotalSeconds;
// if no SPM, weight is 1 else the weight ishe current session's spm / lifetime average score per minute
double SPMWeightAgainstAverage = (clientStats.SPM < 1) ? 1 : killSPM / clientStats.SPM;
double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalConnectionTime / 60.0));
// calculate the weight of the new play time against last 10 hours of gameplay
int totalConnectionTime = (clientStats.Client.TotalConnectionTime == 0) ?
(int)(DateTime.UtcNow - clientStats.Client.FirstConnection).TotalSeconds :
clientStats.Client.TotalConnectionTime + (int)(DateTime.UtcNow - clientStats.Client.LastConnection).TotalSeconds;
// calculate the new weight against average times the weight against play time
clientStats.SPM = (killSPM * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight));
clientStats.SPM = Math.Round(clientStats.SPM, 3);
clientStats.Skill = Math.Round((clientStats.SPM * KDRWeight) / 10.0, 3);
double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalConnectionTime / 60.0));
clientStats.SessionKills = 0;
clientStats.SessionDeaths = 0;
// calculate the new weight against average times the weight against play time
clientStats.SPM = (killSPM * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight));
clientStats.SPM = Math.Round(clientStats.SPM, 3);
clientStats.Skill = Math.Round((clientStats.SPM * KDRWeight), 3);
clientStats.LastStatCalculation = DateTime.UtcNow;
}
clientStats.LastStatCalculation = DateTime.UtcNow;
clientStats.LastScore = currentScore;
return clientStats;
}
public void InitializeServerStats(Server sv)
{
int serverId = sv.GetHashCode();
var serverStats = ServerStatsSvc.Find(s => s.ServerId == serverId).FirstOrDefault();
if (serverStats == null)
{
Log.WriteDebug($"Initializing server stats for {sv}");
// server stats have never been generated before
serverStats = new EFServerStatistics()
{
Active = true,
ServerId = serverId,
TotalKills = 0,
TotalPlayTime = 0,
};
var ieClientStats = ClientStatSvc.Find(cs => cs.ServerId == serverId);
// set these incase they've we've imported settings
serverStats.TotalKills = ieClientStats.Sum(cs => cs.Kills);
serverStats.TotalPlayTime = Manager.GetClientService().GetTotalPlayTime().Result;
ServerStatsSvc.Insert(serverStats);
}
}
public async Task Sync()
{
Log.WriteDebug("Syncing server stats");
await ServerStatsSvc.SaveChangesAsync();
Log.WriteDebug("Syncing client stats");
await ClientStatSvc.SaveChangesAsync();
Log.WriteDebug("Syncing kill stats");
await KillStatsSvc.SaveChangesAsync();
Log.WriteDebug("Syncing servers");
await ServerSvc.SaveChangesAsync();
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StatsPlugin.Helpers
{
public class StreakMessage
{
public static string MessageOnStreak(int killStreak, int deathStreak)
{
String Message = "";
switch (killStreak)
{
case 5:
Message = "Great job! You're on a ^55 killstreak!";
break;
case 10:
Message = "Amazing! ^510 kills ^7without dying!";
break;
}
switch (deathStreak)
{
case 5:
Message = "Pick it up soldier, you've died ^55 times ^7in a row...";
break;
case 10:
Message = "Seriously? ^510 deaths ^7without getting a kill?";
break;
}
return Message;
}
}
}

View File

@ -1333,7 +1333,8 @@ namespace StatsPlugin
ak74u_silencer_thermal_mp,
ak74u_silencer_xmags_mp,
ak74u_thermal_xmags_mp,
m40a3_mp = 1194,
m16_reflex_silencer_mp,
m40a3_mp,
peacekeeper_mp,
dragunov_mp,
cobra_player_minigun_mp

View File

@ -45,5 +45,9 @@ namespace StatsPlugin.Models
public int DeathStreak { get; set; }
[NotMapped]
public DateTime LastStatCalculation { get; set; }
[NotMapped]
public int LastScore { get; set; }
[NotMapped]
public DateTime LastActive { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using SharedLibrary.Database.Models;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace StatsPlugin.Models
{
public class EFServerStatistics : SharedEntity
{
[Key]
public int StatisticId { get; set; }
public int ServerId { get; set; }
[ForeignKey("ServerId")]
public virtual EFServer Server { get; set; }
public long TotalKills { get; set; }
public long TotalPlayTime { get; set; }
}
}

View File

@ -5,8 +5,11 @@ using System.Text;
using System.Threading.Tasks;
using SharedLibrary;
using SharedLibrary.Helpers;
using SharedLibrary.Interfaces;
using SharedLibrary.Services;
using StatsPlugin.Helpers;
using StatsPlugin.Models;
namespace StatsPlugin
{
@ -40,6 +43,7 @@ namespace StatsPlugin
case Event.GType.MapChange:
break;
case Event.GType.MapEnd:
await Manager.Sync();
break;
case Event.GType.Broadcast:
break;
@ -71,6 +75,27 @@ namespace StatsPlugin
public Task OnLoadAsync(IManager manager)
{
/*
*
ManagerInstance.GetMessageTokens().Add(new MessageToken("TOTALKILLS", GetTotalKills));
ManagerInstance.GetMessageTokens().Add(new MessageToken("TOTALPLAYTIME", GetTotalPlaytime));
*/
string totalKills()
{
var serverStats = new GenericRepository<EFServerStatistics>();
return serverStats.GetQuery(s => s.Active)
.Sum(c => c.TotalKills).ToString();
}
string totalPlayTime()
{
var serverStats = new GenericRepository<EFServerStatistics>();
return serverStats.GetQuery(s => s.Active)
.Sum(c => c.TotalPlayTime).ToString();
}
manager.GetMessageTokens().Add(new MessageToken("TOTALKILLS", totalKills));
manager.GetMessageTokens().Add(new MessageToken("TOTALPLAYTIME", totalPlayTime));
return Task.FromResult(
Manager = new StatManager(manager)
);

View File

@ -72,11 +72,13 @@
<None Include="Chat\ChatHistoryPage.cs" />
<Compile Include="Helpers\ServerStats.cs" />
<Compile Include="Helpers\StatManager.cs" />
<Compile Include="Helpers\StreakMessage.cs" />
<Compile Include="IW4Info.cs" />
<Compile Include="MinimapConfig.cs" />
<Compile Include="Models\EFClientKill.cs" />
<Compile Include="Models\EFServer.cs" />
<Compile Include="Models\EFClientStatistics.cs" />
<Compile Include="Models\EFServerStatistics.cs" />
<Compile Include="Plugin.cs" />
<None Include="_Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View File

@ -744,7 +744,7 @@ namespace SharedLibrary.Commands
E.Data = E.Data.RemoveWords(1);
E.Owner.Reports.Add(new Report(E.Target, E.Origin, E.Data));
await E.Origin.Tell($"Thank you for your report, and administrator has been notified");
await E.Origin.Tell($"Thank you for your report, an administrator has been notified");
await E.Owner.ExecuteEvent(new Event(Event.GType.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

@ -73,6 +73,8 @@ namespace SharedLibrary.Objects
public DateTime ConnectionTime { get; set; }
[NotMapped]
public Server CurrentServer { get; set; }
[NotMapped]
public int Score { get; set; }
private string _ipaddress;
public override string IPAddress

View File

@ -26,8 +26,8 @@ namespace SharedLibrary.Network
static string[] SendQuery(QueryType Type, Server QueryServer, string Parameters = "")
{
if ((DateTime.Now - LastQuery).TotalMilliseconds < 100)
Task.Delay(100).Wait();
if ((DateTime.Now - LastQuery).TotalMilliseconds < 300)
Task.Delay(300).Wait();
LastQuery = DateTime.Now;
var ServerOOBConnection = new UdpClient();
ServerOOBConnection.Client.SendTimeout = 1000;

View File

@ -233,6 +233,12 @@ namespace SharedLibrary.Services
{
throw new NotImplementedException();
}
public async Task<int> GetTotalPlayTime()
{
using (var context = new DatabaseContext())
return await context.Clients.SumAsync(c => c.TotalConnectionTime);
}
#endregion
}
}

View File

@ -62,7 +62,9 @@ namespace SharedLibrary
int.TryParse(playerInfo[0], out cID);
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
string cIP = regex.Value.Split(':')[0];
Player P = new Player() { Name = cName, NetworkId = npID, ClientNumber = cID, IPAddress = cIP, Ping = Ping };
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
int score = Int32.Parse(regex.Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]);
Player P = new Player() { Name = cName, NetworkId = npID, ClientNumber = cID, IPAddress = cIP, Ping = Ping, Score = score};
StatusPlayers.Add(P);
}
}