2017-10-03 19:17:35 -04:00
using SharedLibrary ;
using SharedLibrary.Helpers ;
using System ;
2015-08-20 17:54:38 -04:00
using System.Collections.Generic ;
using System.Data ;
2017-10-03 19:17:35 -04:00
using System.IO ;
using System.Text ;
2017-05-26 18:49:27 -04:00
using System.Threading.Tasks ;
2015-08-28 00:39:36 -04:00
namespace StatsPlugin
2015-08-20 01:06:44 -04:00
{
2017-05-27 00:22:50 -04:00
public class CViewStats : Command
2015-08-20 01:06:44 -04:00
{
2017-11-15 16:04:13 -05:00
public CViewStats ( ) : base ( "stats" , "view your stats" , "xlrstats" , Player . Permission . User , false , new CommandArgument [ ]
{
new CommandArgument ( )
{
Name = "player" ,
Required = true
}
} )
{ }
2015-08-20 01:06:44 -04:00
2017-05-26 18:49:27 -04:00
public override async Task ExecuteAsync ( Event E )
2015-08-20 01:06:44 -04:00
{
2015-09-01 12:00:12 -04:00
String statLine ;
PlayerStats pStats ;
2017-06-01 13:42:28 -04:00
if ( E . Data . Length > 0 & & E . Target = = null )
{
await E . Origin . Tell ( "Cannot find the player you specified" ) ;
return ;
}
2015-09-01 12:00:12 -04:00
if ( E . Target ! = null )
{
2017-06-12 13:50:00 -04:00
pStats = Stats . statLists . Find ( x = > x . Port = = E . Owner . GetPort ( ) ) . playerStats . GetStats ( E . Target ) ;
2015-09-01 12:00:12 -04:00
statLine = String . Format ( "^5{0} ^7KILLS | ^5{1} ^7DEATHS | ^5{2} ^7KDR | ^5{3} ^7SKILL" , pStats . Kills , pStats . Deaths , pStats . KDR , pStats . Skill ) ;
}
else
{
2017-06-12 13:50:00 -04:00
pStats = Stats . statLists . Find ( x = > x . Port = = E . Owner . GetPort ( ) ) . playerStats . GetStats ( E . Origin ) ;
2015-09-01 12:00:12 -04:00
statLine = String . Format ( "^5{0} ^7KILLS | ^5{1} ^7DEATHS | ^5{2} ^7KDR | ^5{3} ^7SKILL" , pStats . Kills , pStats . Deaths , pStats . KDR , pStats . Skill ) ;
}
2017-05-31 01:31:56 -04:00
if ( E . Message . IsBroadcastCommand ( ) )
{
string name = E . Target = = null ? E . Origin . Name : E . Target . Name ;
await E . Owner . Broadcast ( $"Stats for ^5{name}^7" ) ;
await E . Owner . Broadcast ( statLine ) ;
}
2017-06-01 13:42:28 -04:00
2017-05-31 01:31:56 -04:00
else
2017-06-01 13:42:28 -04:00
{
if ( E . Target ! = null )
await E . Origin . Tell ( $"Stats for ^5{E.Target.Name}^7" ) ;
2017-05-31 01:31:56 -04:00
await E . Origin . Tell ( statLine ) ;
2017-06-01 13:42:28 -04:00
}
2015-08-20 01:06:44 -04:00
}
}
2017-05-27 00:22:50 -04:00
public class CViewTopStats : Command
2015-08-20 01:06:44 -04:00
{
2017-11-15 16:04:13 -05:00
public CViewTopStats ( ) :
base ( "topstats" , "view the top 5 players on this server" , "ts" , Player . Permission . User , false )
{ }
2015-08-20 01:06:44 -04:00
2017-05-26 18:49:27 -04:00
public override async Task ExecuteAsync ( Event E )
2015-08-20 01:06:44 -04:00
{
2017-06-12 13:50:00 -04:00
List < KeyValuePair < String , PlayerStats > > pStats = Stats . statLists . Find ( x = > x . Port = = E . Owner . GetPort ( ) ) . playerStats . GetTopStats ( ) ;
2015-08-28 00:39:36 -04:00
StringBuilder msgBlder = new StringBuilder ( ) ;
2015-08-20 17:54:38 -04:00
2017-05-26 18:49:27 -04:00
await E . Origin . Tell ( "^5--Top Players--" ) ;
2015-08-28 00:39:36 -04:00
foreach ( KeyValuePair < String , PlayerStats > pStat in pStats )
{
2017-05-29 22:25:49 -04:00
Player P = E . Owner . Manager . GetClientDatabase ( ) . GetPlayer ( pStat . Key , - 1 ) ;
2015-08-28 00:39:36 -04:00
if ( P = = null )
continue ;
2017-05-26 18:49:27 -04:00
await E . Origin . Tell ( String . Format ( "^3{0}^7 - ^5{1} ^7KDR | ^5{2} ^7SKILL" , P . Name , pStat . Value . KDR , pStat . Value . Skill ) ) ;
2015-08-28 00:39:36 -04:00
}
2015-08-20 17:54:38 -04:00
}
}
2017-06-01 13:42:28 -04:00
2017-05-31 01:31:56 -04:00
public class CResetStats : Command
{
2017-11-15 16:04:13 -05:00
public CResetStats ( ) : base ( "resetstats" , "reset your stats to factory-new" , "rs" , Player . Permission . User , false ) { }
2017-05-31 01:31:56 -04:00
public override async Task ExecuteAsync ( Event E )
{
2017-06-12 13:50:00 -04:00
var stats = Stats . statLists . Find ( x = > x . Port = = E . Owner . GetPort ( ) ) . playerStats . GetStats ( E . Origin ) ;
2017-05-31 01:31:56 -04:00
stats . Deaths = 0 ;
stats . Kills = 0 ;
2017-06-01 13:42:28 -04:00
stats . scorePerMinute = 1.0 ;
stats . Skill = 1 ;
2017-05-31 01:31:56 -04:00
stats . KDR = 0.0 ;
2017-06-12 13:50:00 -04:00
await Task . Run ( ( ) = > { Stats . statLists . Find ( x = > x . Port = = E . Owner . GetPort ( ) ) . playerStats . UpdateStats ( E . Origin , stats ) ; } ) ;
2017-05-31 01:31:56 -04:00
await E . Origin . Tell ( "Your stats have been reset" ) ;
}
}
2017-05-26 18:49:27 -04:00
/// <summary>
/// Each server runs from the same plugin ( for easier reloading and reduced memory usage ).
/// So, to have multiple stat tracking, we must store a stat struct for each server
/// </summary>
2017-05-27 00:22:50 -04:00
public class Stats : SharedLibrary . Interfaces . IPlugin
2015-08-20 17:54:38 -04:00
{
2017-10-03 19:17:35 -04:00
public static SharedLibrary . Interfaces . IManager ManagerInstance ;
public static int MAX_KILLEVENTS = 1000 ;
public static Dictionary < int , ServerStatInfo > ServerStats { get ; private set ; }
2017-11-02 18:20:10 -04:00
public static ChatDatabase ChatDB { get ; private set ; }
2017-10-03 19:17:35 -04:00
public class ServerStatInfo
{
public ServerStatInfo ( )
{
KillQueue = new Queue < KillInfo > ( ) ;
ServerStartTime = DateTime . Now ;
}
public DateTime ServerStartTime { get ; private set ; }
public DateTime RoundStartTime { get ; set ; }
public string Uptime = > Utilities . GetTimePassed ( ServerStartTime , false ) ;
public string ElapsedRoundTime = > Utilities . GetTimePassed ( RoundStartTime ) ;
private Queue < KillInfo > KillQueue { get ; set ; }
public Queue < KillInfo > GetKillQueue ( ) { return KillQueue ; }
}
public class KillInfo
2017-09-29 22:42:24 -04:00
{
public IW4Info . HitLocation HitLoc { get ; set ; }
2017-10-03 19:17:35 -04:00
public string HitLocString = > HitLoc . ToString ( ) ;
2017-09-29 22:42:24 -04:00
public IW4Info . MeansOfDeath DeathType { get ; set ; }
2017-10-03 19:17:35 -04:00
public string DeathTypeString = > DeathType . ToString ( ) ;
2017-09-29 22:42:24 -04:00
public int Damage { get ; set ; }
public IW4Info . WeaponName Weapon { get ; set ; }
2017-10-03 19:17:35 -04:00
public string WeaponString = > Weapon . ToString ( ) ;
2017-09-29 22:42:24 -04:00
public Vector3 KillOrigin { get ; set ; }
public Vector3 DeathOrigin { get ; set ; }
2017-10-03 19:17:35 -04:00
// http://wiki.modsrepository.com/index.php?title=Call_of_Duty_5:_Gameplay_standards for conversion to meters
public double Distance = > Vector3 . Distance ( KillOrigin , DeathOrigin ) * 0.0254 ;
public string KillerPlayer { get ; set ; }
public int KillerPlayerID { get ; set ; }
public string VictimPlayer { get ; set ; }
public int VictimPlayerID { get ; set ; }
public IW4Info . MapName Map { get ; set ; }
public int ID = > GetHashCode ( ) ;
public KillInfo ( ) { }
public KillInfo ( int killer , int victim , string map , string hit , string type , string damage , string weapon , string kOrigin , string dOrigin )
2017-09-29 22:42:24 -04:00
{
2017-10-03 19:17:35 -04:00
KillerPlayerID = killer ;
VictimPlayerID = victim ;
Map = ParseEnum < IW4Info . MapName > . Get ( map , typeof ( IW4Info . MapName ) ) ;
HitLoc = ParseEnum < IW4Info . HitLocation > . Get ( hit , typeof ( IW4Info . HitLocation ) ) ;
DeathType = ParseEnum < IW4Info . MeansOfDeath > . Get ( type , typeof ( IW4Info . MeansOfDeath ) ) ;
2017-09-29 22:42:24 -04:00
Damage = Int32 . Parse ( damage ) ;
2017-10-03 19:17:35 -04:00
Weapon = ParseEnum < IW4Info . WeaponName > . Get ( weapon , typeof ( IW4Info . WeaponName ) ) ;
2017-09-29 22:42:24 -04:00
KillOrigin = Vector3 . Parse ( kOrigin ) ;
DeathOrigin = Vector3 . Parse ( dOrigin ) ;
}
}
2017-05-26 18:49:27 -04:00
public static List < StatTracking > statLists ;
2017-10-15 21:40:27 -04:00
public class StatTracking
2017-05-26 18:49:27 -04:00
{
public StatsDB playerStats ;
public DateTime [ ] lastKill , connectionTime ;
public int [ ] inactiveMinutes , Kills , deathStreaks , killStreaks ;
public int Port ;
public StatTracking ( int port )
{
2017-11-14 16:36:55 -05:00
playerStats = new StatsDB ( "Database/stats_" + port + ".rm" , ManagerInstance . GetLogger ( ) ) ;
2017-05-26 18:49:27 -04:00
inactiveMinutes = new int [ 18 ] ;
Kills = new int [ 18 ] ;
deathStreaks = new int [ 18 ] ;
killStreaks = new int [ 18 ] ;
lastKill = new DateTime [ 18 ] ;
connectionTime = new DateTime [ 18 ] ;
Port = port ;
}
}
2017-11-15 16:04:13 -05:00
public string Name = > "Basic Stats" ;
2017-05-26 18:49:27 -04:00
2017-11-15 16:04:13 -05:00
public float Version = > 1.1f ;
2017-05-26 18:49:27 -04:00
2017-11-15 16:04:13 -05:00
public string Author = > "RaidMax" ;
2017-05-26 18:49:27 -04:00
2017-10-16 23:47:41 -04:00
public async Task OnLoadAsync ( SharedLibrary . Interfaces . IManager manager )
2017-05-26 18:49:27 -04:00
{
statLists = new List < StatTracking > ( ) ;
2017-10-03 19:17:35 -04:00
ServerStats = new Dictionary < int , ServerStatInfo > ( ) ;
2017-10-16 23:47:41 -04:00
ManagerInstance = manager ;
WebService . PageList . Add ( new StatsPage ( ) ) ;
WebService . PageList . Add ( new KillStatsJSON ( ) ) ;
2017-11-02 18:20:10 -04:00
WebService . PageList . Add ( new Chat . WordCloudJSON ( ) ) ;
WebService . PageList . Add ( new Chat . ClientChatJSON ( ) ) ;
WebService . PageList . Add ( new Chat . ChatPage ( ) ) ;
2017-10-16 23:47:41 -04:00
ManagerInstance . GetMessageTokens ( ) . Add ( new MessageToken ( "TOTALKILLS" , GetTotalKills ) ) ;
ManagerInstance . GetMessageTokens ( ) . Add ( new MessageToken ( "TOTALPLAYTIME" , GetTotalPlaytime ) ) ;
2017-11-14 16:36:55 -05:00
ChatDB = new ChatDatabase ( "Database/ChatHistory.rm" , ManagerInstance . GetLogger ( ) ) ;
2017-11-02 18:20:10 -04:00
2017-10-03 19:17:35 -04:00
try
{
var minimapConfig = MinimapConfig . Read ( "Config/minimaps.cfg" ) ;
}
catch ( SharedLibrary . Exceptions . SerializeException e )
{
MinimapConfig . Write ( "Config/minimaps.cfg" , MinimapConfig . IW4Minimaps ( ) ) ;
}
2017-05-26 18:49:27 -04:00
}
2017-06-13 18:33:47 -04:00
public async Task OnUnloadAsync ( )
2017-05-26 18:49:27 -04:00
{
statLists . Clear ( ) ;
}
2017-05-27 18:08:04 -04:00
public async Task OnTickAsync ( Server S )
2017-05-26 18:49:27 -04:00
{
2017-05-27 00:22:50 -04:00
return ;
2017-05-26 18:49:27 -04:00
}
2017-05-27 18:08:04 -04:00
public async Task OnEventAsync ( Event E , Server S )
2017-05-26 18:49:27 -04:00
{
2017-11-13 16:58:23 -05:00
try
2017-05-26 18:49:27 -04:00
{
2017-11-13 16:58:23 -05:00
if ( E . Type = = Event . GType . Start )
{
statLists . Add ( new StatTracking ( S . GetPort ( ) ) ) ;
ServerStats . Add ( S . GetPort ( ) , new ServerStatInfo ( ) ) ;
2017-11-02 12:49:45 -04:00
2017-11-13 16:58:23 -05:00
var config = new ConfigurationManager ( S ) ;
if ( config . GetProperty ( "EnableTrusted" ) = = null )
config . AddProperty ( new KeyValuePair < string , object > ( "EnableTrusted" , true ) ) ;
}
2017-05-26 18:49:27 -04:00
2017-11-13 16:58:23 -05:00
if ( E . Type = = Event . GType . Stop )
{
statLists . RemoveAll ( s = > s . Port = = S . GetPort ( ) ) ;
ServerStats . Remove ( S . GetPort ( ) ) ;
}
2015-08-26 01:49:47 -04:00
2017-11-13 16:58:23 -05:00
if ( E . Type = = Event . GType . Connect )
{
ResetCounters ( E . Origin . ClientID , S . GetPort ( ) ) ;
2016-01-16 17:58:24 -05:00
2017-11-13 16:58:23 -05:00
var config = new ConfigurationManager ( E . Owner ) ;
2017-11-02 12:49:45 -04:00
2017-11-13 16:58:23 -05:00
if ( ! ( bool ) config . GetProperty ( "EnableTrusted" ) )
return ;
2017-11-02 12:49:45 -04:00
2017-11-13 16:58:23 -05:00
PlayerStats checkForTrusted = statLists . Find ( x = > x . Port = = S . GetPort ( ) ) . playerStats . GetStats ( E . Origin ) ;
//todo: move this out of here!!
if ( checkForTrusted . TotalPlayTime > = 4320 & & E . Origin . Level < Player . Permission . Trusted & & E . Origin . Level ! = Player . Permission . Flagged )
{
E . Origin . SetLevel ( Player . Permission . Trusted ) ;
E . Owner . Manager . GetClientDatabase ( ) . UpdatePlayer ( E . Origin ) ;
await E . Origin . Tell ( "Congratulations, you are now a ^5trusted ^7player! Type ^5!help ^7to view new commands." ) ;
await E . Origin . Tell ( "You earned this by playing for ^53 ^7full days!" ) ;
}
2016-01-16 17:58:24 -05:00
}
2015-08-26 01:49:47 -04:00
2017-11-13 16:58:23 -05:00
if ( E . Type = = Event . GType . MapEnd | | E . Type = = Event . GType . Stop )
2015-08-26 01:49:47 -04:00
{
2017-11-13 16:58:23 -05:00
foreach ( Player P in S . GetPlayersAsList ( ) )
{
2015-08-28 00:39:36 -04:00
2017-11-13 16:58:23 -05:00
if ( P = = null )
continue ;
2015-08-28 00:39:36 -04:00
2017-11-13 16:58:23 -05:00
CalculateAndSaveSkill ( P , statLists . Find ( x = > x . Port = = S . GetPort ( ) ) ) ;
ResetCounters ( P . ClientID , S . GetPort ( ) ) ;
2015-08-26 01:49:47 -04:00
2017-11-13 16:58:23 -05:00
E . Owner . Logger . WriteInfo ( $"Updated skill for {P}" ) ;
//E.Owner.Log.Write(String.Format("\r\nJoin: {0}\r\nInactive Minutes: {1}\r\nnewPlayTime: {2}\r\nnewSPM: {3}\r\nkdrWeight: {4}\r\nMultiplier: {5}\r\nscoreWeight: {6}\r\nnewSkillFactor: {7}\r\nprojectedNewSkill: {8}\r\nKills: {9}\r\nDeaths: {10}", connectionTime[P.clientID].ToShortTimeString(), inactiveMinutes[P.clientID], newPlayTime, newSPM, kdrWeight, Multiplier, scoreWeight, newSkillFactor, disconnectStats.Skill, disconnectStats.Kills, disconnectStats.Deaths));
}
2015-08-26 01:49:47 -04:00
}
2017-11-13 16:58:23 -05:00
if ( E . Type = = Event . GType . MapChange )
{
ServerStats [ S . GetPort ( ) ] . GetKillQueue ( ) . Clear ( ) ;
ServerStats [ S . GetPort ( ) ] . RoundStartTime = DateTime . Now ;
}
2017-10-03 19:17:35 -04:00
2017-11-13 16:58:23 -05:00
if ( E . Type = = Event . GType . Disconnect )
{
CalculateAndSaveSkill ( E . Origin , statLists . Find ( x = > x . Port = = S . GetPort ( ) ) ) ;
ResetCounters ( E . Origin . ClientID , S . GetPort ( ) ) ;
E . Owner . Logger . WriteInfo ( $"Updated skill for disconnecting client {E.Origin}" ) ;
}
2015-08-26 01:49:47 -04:00
2017-11-13 16:58:23 -05:00
if ( E . Type = = Event . GType . Kill )
{
if ( E . Origin = = E . Target | | E . Origin = = null )
return ;
2015-08-28 00:39:36 -04:00
2017-11-13 16:58:23 -05:00
string [ ] killInfo = ( E . Data ! = null ) ? E . Data . Split ( ';' ) : new string [ 0 ] ;
2017-10-15 21:40:27 -04:00
2017-11-13 16:58:23 -05:00
if ( killInfo . Length > = 9 & & killInfo [ 0 ] . Contains ( "ScriptKill" ) )
2017-10-03 19:17:35 -04:00
{
2017-11-13 16:58:23 -05:00
var killEvent = new KillInfo ( E . Origin . DatabaseID , E . Target . DatabaseID , S . CurrentMap . Name , killInfo [ 7 ] , killInfo [ 8 ] , killInfo [ 5 ] , killInfo [ 6 ] , killInfo [ 3 ] , killInfo [ 4 ] )
{
KillerPlayer = E . Origin . Name ,
VictimPlayer = E . Target . Name ,
} ;
2017-10-03 19:17:35 -04:00
2017-11-13 16:58:23 -05:00
if ( ServerStats [ S . GetPort ( ) ] . GetKillQueue ( ) . Count > MAX_KILLEVENTS - 1 )
ServerStats [ S . GetPort ( ) ] . GetKillQueue ( ) . Dequeue ( ) ;
ServerStats [ S . GetPort ( ) ] . GetKillQueue ( ) . Enqueue ( killEvent ) ;
//S.Logger.WriteInfo($"{E.Origin.Name} killed {E.Target.Name} with a {killEvent.Weapon} from a distance of {Vector3.Distance(killEvent.KillOrigin, killEvent.DeathOrigin)} with {killEvent.Damage} damage, at {killEvent.HitLoc}");
var cs = statLists . Find ( x = > x . Port = = S . GetPort ( ) ) ;
cs . playerStats . AddKill ( killEvent ) ;
}
2017-10-11 17:49:02 -04:00
2017-11-13 16:58:23 -05:00
Player Killer = E . Origin ;
StatTracking curServer = statLists . Find ( x = > x . Port = = S . GetPort ( ) ) ;
PlayerStats killerStats = curServer . playerStats . GetStats ( Killer ) ;
2015-08-26 01:49:47 -04:00
2017-11-13 16:58:23 -05:00
if ( killerStats = = null )
killerStats = new PlayerStats ( 0 , 0 , 0 , 0 , 0 , 0 ) ;
2017-09-29 22:42:24 -04:00
2017-11-13 16:58:23 -05:00
curServer . lastKill [ E . Origin . ClientID ] = DateTime . Now ;
curServer . Kills [ E . Origin . ClientID ] + + ;
2015-08-20 17:54:38 -04:00
2017-11-13 16:58:23 -05:00
if ( ( DateTime . Now - curServer . lastKill [ E . Origin . ClientID ] ) . TotalSeconds > 120 )
curServer . inactiveMinutes [ E . Origin . ClientID ] + = 2 ;
2017-09-29 22:42:24 -04:00
2017-11-13 16:58:23 -05:00
killerStats . Kills + + ;
2015-08-23 17:58:48 -04:00
2017-11-13 16:58:23 -05:00
killerStats . KDR = ( killerStats . Deaths = = 0 ) ? killerStats . Kills : killerStats . KDR = Math . Round ( ( double ) killerStats . Kills / ( double ) killerStats . Deaths , 2 ) ;
2015-08-23 17:58:48 -04:00
2017-11-13 16:58:23 -05:00
curServer . playerStats . UpdateStats ( Killer , killerStats ) ;
2015-08-20 17:54:38 -04:00
2017-11-13 16:58:23 -05:00
curServer . killStreaks [ Killer . ClientID ] + = 1 ;
curServer . deathStreaks [ Killer . ClientID ] = 0 ;
await Killer . Tell ( MessageOnStreak ( curServer . killStreaks [ Killer . ClientID ] , curServer . deathStreaks [ Killer . ClientID ] ) ) ;
}
if ( E . Type = = Event . GType . Death )
{
if ( E . Origin = = E . Target | | E . Origin = = null )
return ;
Player Victim = E . Origin ;
StatTracking curServer = statLists . Find ( x = > x . Port = = S . GetPort ( ) ) ;
PlayerStats victimStats = curServer . playerStats . GetStats ( Victim ) ;
if ( victimStats = = null )
victimStats = new PlayerStats ( 0 , 0 , 0 , 0 , 0 , 0 ) ;
2015-10-14 23:10:14 -04:00
2017-11-13 16:58:23 -05:00
victimStats . Deaths + + ;
victimStats . KDR = Math . Round ( victimStats . Kills / ( double ) victimStats . Deaths , 2 ) ;
2017-09-29 22:42:24 -04:00
2017-11-13 16:58:23 -05:00
curServer . playerStats . UpdateStats ( Victim , victimStats ) ;
2015-08-22 02:04:30 -04:00
2017-11-13 16:58:23 -05:00
curServer . deathStreaks [ Victim . ClientID ] + = 1 ;
curServer . killStreaks [ Victim . ClientID ] = 0 ;
2015-08-23 17:58:48 -04:00
2017-11-13 16:58:23 -05:00
await Victim . Tell ( MessageOnStreak ( curServer . killStreaks [ Victim . ClientID ] , curServer . deathStreaks [ Victim . ClientID ] ) ) ;
}
2015-08-23 17:58:48 -04:00
2017-11-13 16:58:23 -05:00
if ( E . Type = = Event . GType . Say )
{
ChatDB . AddChatHistory ( E . Origin . DatabaseID , E . Owner . GetPort ( ) , E . Data ) ;
}
2015-08-20 17:54:38 -04:00
}
2017-11-02 18:20:10 -04:00
2017-11-13 16:58:23 -05:00
catch ( Exception e )
2017-11-02 18:20:10 -04:00
{
2017-11-13 16:58:23 -05:00
S . Logger . WriteWarning ( "StatsPlugin::OnEventAsync failed to complete" ) ;
S . Logger . WriteDebug ( $"Server:{S}\r\nOrigin:{E.Origin}\r\nTarget:{E.Target}" ) ;
S . Logger . WriteDebug ( $"Exception: {e.Message}" ) ;
2017-11-02 18:20:10 -04:00
}
2015-08-20 17:54:38 -04:00
}
2017-09-29 22:42:24 -04:00
public static string GetTotalKills ( )
2017-06-01 13:42:28 -04:00
{
long Kills = 0 ;
foreach ( var S in statLists )
Kills + = S . playerStats . GetTotalServerKills ( ) ;
return Kills . ToString ( "#,##0" ) ;
}
public static string GetTotalPlaytime ( )
{
long Playtime = 0 ;
foreach ( var S in statLists )
Playtime + = S . playerStats . GetTotalServerPlaytime ( ) ;
return Playtime . ToString ( "#,##0" ) ;
}
2017-05-31 01:31:56 -04:00
private void CalculateAndSaveSkill ( Player P , StatTracking curServer )
2015-08-26 01:49:47 -04:00
{
2015-08-28 00:39:36 -04:00
if ( P = = null )
return ;
2017-05-31 01:31:56 -04:00
PlayerStats DisconnectingPlayerStats = curServer . playerStats . GetStats ( P ) ;
2017-11-13 16:58:23 -05:00
if ( DisconnectingPlayerStats = = null | | curServer . Kills [ P . ClientID ] = = 0 )
2015-08-26 01:49:47 -04:00
return ;
2017-05-31 01:31:56 -04:00
else if ( curServer . lastKill [ P . ClientID ] > curServer . connectionTime [ P . ClientID ] )
curServer . inactiveMinutes [ P . ClientID ] + = ( int ) ( DateTime . Now - curServer . lastKill [ P . ClientID ] ) . TotalMinutes ;
2015-08-26 01:49:47 -04:00
2017-05-31 01:31:56 -04:00
int newPlayTime = ( int ) ( DateTime . Now - curServer . connectionTime [ P . ClientID ] ) . TotalMinutes - curServer . inactiveMinutes [ P . ClientID ] ;
2015-08-26 01:49:47 -04:00
2015-08-28 00:39:36 -04:00
if ( newPlayTime < 2 )
return ;
2017-09-29 22:42:24 -04:00
2017-05-31 01:31:56 -04:00
// calculate the players Score Per Minute for the current session
double SessionSPM = curServer . Kills [ P . ClientID ] * 100 / Math . Max ( 1 , newPlayTime ) ;
// calculate how much the KDR should way
// 0.81829 is a Eddie-Generated number that weights the KDR nicely
double KDRWeight = Math . Round ( Math . Pow ( DisconnectingPlayerStats . KDR , 1.637 / Math . E ) , 3 ) ;
double SPMWeightAgainstAverage ;
2015-08-26 01:49:47 -04:00
2017-05-31 01:31:56 -04:00
// if no SPM, weight is 1 else the weight is the current sessions spm / lifetime average score per minute
2017-09-29 22:42:24 -04:00
SPMWeightAgainstAverage = ( DisconnectingPlayerStats . scorePerMinute = = 1 ) ? 1 : SessionSPM / DisconnectingPlayerStats . scorePerMinute ;
2015-08-26 01:49:47 -04:00
2017-05-31 01:31:56 -04:00
// calculate the weight of the new play time againmst lifetime playtime
//
2017-06-01 13:42:28 -04:00
double SPMAgainstPlayWeight = newPlayTime / Math . Min ( 600 , DisconnectingPlayerStats . TotalPlayTime + newPlayTime ) ;
2017-05-31 01:31:56 -04:00
// calculate the new weight against average times the weight against play time
2017-06-01 13:42:28 -04:00
double newSkillFactor = SPMWeightAgainstAverage * SPMAgainstPlayWeight * SessionSPM ;
2015-08-26 01:49:47 -04:00
2017-05-31 01:31:56 -04:00
// if the weight is greater than 1, add, else subtract
DisconnectingPlayerStats . scorePerMinute + = ( SPMWeightAgainstAverage > = 1 ) ? newSkillFactor : - newSkillFactor ;
2015-08-26 01:49:47 -04:00
2017-05-31 01:31:56 -04:00
DisconnectingPlayerStats . Skill = DisconnectingPlayerStats . scorePerMinute * KDRWeight / 10 ;
DisconnectingPlayerStats . TotalPlayTime + = newPlayTime ;
2015-08-26 01:49:47 -04:00
2017-05-31 01:31:56 -04:00
curServer . playerStats . UpdateStats ( P , DisconnectingPlayerStats ) ;
2015-08-26 01:49:47 -04:00
}
2017-05-31 01:31:56 -04:00
private void ResetCounters ( int cID , int serverPort )
2015-08-22 12:41:14 -04:00
{
2017-05-26 18:49:27 -04:00
StatTracking selectedPlayers = statLists . Find ( x = > x . Port = = serverPort ) ;
2015-08-22 12:41:14 -04:00
2017-10-15 21:40:27 -04:00
if ( selectedPlayers = = null )
return ;
2017-11-13 16:58:23 -05:00
2017-05-26 18:49:27 -04:00
selectedPlayers . Kills [ cID ] = 0 ;
selectedPlayers . connectionTime [ cID ] = DateTime . Now ;
selectedPlayers . inactiveMinutes [ cID ] = 0 ;
selectedPlayers . deathStreaks [ cID ] = 0 ;
selectedPlayers . killStreaks [ cID ] = 0 ;
2015-08-22 12:41:14 -04:00
}
2017-05-31 01:31:56 -04:00
private String MessageOnStreak ( int killStreak , int deathStreak )
2015-08-23 17:58:48 -04:00
{
String Message = "" ;
switch ( killStreak )
{
case 5 :
Message = "Great job! You're on a ^55 killstreak!" ;
break ;
case 10 :
2015-08-26 01:49:47 -04:00
Message = "Amazing! ^510 kills ^7without dying!" ;
2015-08-23 17:58:48 -04:00
break ;
}
switch ( deathStreak )
{
case 5 :
2015-08-26 01:49:47 -04:00
Message = "Pick it up soldier, you've died ^55 times ^7in a row..." ;
2015-08-23 17:58:48 -04:00
break ;
case 10 :
2015-08-26 01:49:47 -04:00
Message = "Seriously? ^510 deaths ^7without getting a kill?" ;
2015-08-23 17:58:48 -04:00
break ;
}
return Message ;
}
2015-08-20 17:54:38 -04:00
}
public class StatsDB : Database
{
2017-11-14 16:36:55 -05:00
public StatsDB ( String FN , SharedLibrary . Interfaces . ILogger logger ) : base ( FN , logger ) { }
2015-08-20 17:54:38 -04:00
public override void Init ( )
{
if ( ! File . Exists ( FileName ) )
{
2015-08-28 00:39:36 -04:00
String Create = "CREATE TABLE [STATS] ( [npID] TEXT, [KILLS] INTEGER DEFAULT 0, [DEATHS] INTEGER DEFAULT 0, [KDR] REAL DEFAULT 0, [SKILL] REAL DEFAULT 0, [MEAN] REAL DEFAULT 0, [DEV] REAL DEFAULT 0, [SPM] REAL DEFAULT 0, [PLAYTIME] INTEGER DEFAULT 0);" ;
2017-09-29 22:42:24 -04:00
String createKillsTable = @ "CREATE TABLE `KILLS` (
` KillerID ` INTEGER NOT NULL ,
` VictimID ` INTEGER NOT NULL ,
2017-10-03 19:17:35 -04:00
` MapID ` INTEGER NOT NULL ,
2017-09-29 22:42:24 -04:00
` DeathOrigin ` TEXT NOT NULL ,
` MeansOfDeath ` INTEGER NOT NULL ,
` Weapon ` INTEGER NOT NULL ,
` HitLocation ` INTEGER NOT NULL ,
` Damage ` INTEGER ,
` KillOrigin ` TEXT NOT NULL
) ; ";
2015-08-20 17:54:38 -04:00
ExecuteNonQuery ( Create ) ;
2017-09-29 22:42:24 -04:00
ExecuteNonQuery ( createKillsTable ) ;
2015-08-20 17:54:38 -04:00
}
}
2017-10-03 19:17:35 -04:00
public void AddKill ( Stats . KillInfo info )
{
var kill = new Dictionary < string , object >
{
{ "KillerID" , info . KillerPlayerID } ,
{ "VictimID" , info . VictimPlayerID } ,
{ "MapID" , ( int ) info . Map } ,
{ "KillOrigin" , info . KillOrigin . ToString ( ) } ,
{ "DeathOrigin" , info . DeathOrigin . ToString ( ) } ,
{ "MeansOfDeath" , ( int ) info . DeathType } ,
{ "Weapon" , ( int ) info . Weapon } ,
{ "HitLocation" , ( int ) info . HitLoc } ,
{ "Damage" , info . Damage }
} ;
Insert ( "KILLS" , kill ) ;
}
public List < Stats . KillInfo > GetKillsByPlayer ( int databaseID )
{
var queryResult = GetDataTable ( "KILLS" , new KeyValuePair < string , object > ( "KillerID" , databaseID ) ) ;
var resultList = new List < Stats . KillInfo > ( ) ;
if ( queryResult ? . Rows . Count > 0 )
{
foreach ( DataRow resultRow in queryResult . Rows )
{
resultList . Add ( new Stats . KillInfo ( )
{
KillerPlayerID = Convert . ToInt32 ( resultRow [ "KillerID" ] ) ,
VictimPlayerID = Convert . ToInt32 ( resultRow [ "VictimID" ] ) ,
Map = ( IW4Info . MapName ) resultRow [ "MapID" ] ,
HitLoc = ( IW4Info . HitLocation ) resultRow [ "HitLocation" ] ,
DeathType = ( IW4Info . MeansOfDeath ) resultRow [ "MeansOfDeath" ] ,
Damage = ( int ) resultRow [ "Damage" ] ,
Weapon = ( IW4Info . WeaponName ) resultRow [ "Weapon" ] ,
KillOrigin = Vector3 . Parse ( resultRow [ "KillOrigin" ] . ToString ( ) ) ,
DeathOrigin = Vector3 . Parse ( resultRow [ "DeathOrigin" ] . ToString ( ) )
} ) ;
}
}
return resultList ;
}
2017-10-04 19:01:04 -04:00
public List < Stats . KillInfo > GetKillsByMap ( Map map , int count )
2017-10-03 19:17:35 -04:00
{
var mapID = ParseEnum < IW4Info . MapName > . Get ( map . Name , typeof ( IW4Info . MapName ) ) ;
2017-10-16 23:47:41 -04:00
var queryResult = GetDataTable ( $"select * from KILLS where MapID == {(int)mapID} LIMIT {count} OFFSET (SELECT COUNT(*) FROM KILLS) - {count}" ) ;
2017-10-03 19:17:35 -04:00
var resultList = new List < Stats . KillInfo > ( ) ;
if ( queryResult ? . Rows . Count > 0 )
{
foreach ( DataRow resultRow in queryResult . Rows )
{
resultList . Add ( new Stats . KillInfo ( )
{
KillerPlayerID = Convert . ToInt32 ( resultRow [ "KillerID" ] ) ,
VictimPlayerID = Convert . ToInt32 ( resultRow [ "VictimID" ] ) ,
Map = ParseEnum < IW4Info . MapName > . Get ( resultRow [ "MapID" ] . ToString ( ) , typeof ( IW4Info . MapName ) ) ,
HitLoc = ParseEnum < IW4Info . HitLocation > . Get ( resultRow [ "HitLocation" ] . ToString ( ) , typeof ( IW4Info . HitLocation ) ) ,
DeathType = ParseEnum < IW4Info . MeansOfDeath > . Get ( resultRow [ "MeansOfDeath" ] . ToString ( ) , typeof ( IW4Info . MeansOfDeath ) ) ,
Damage = Convert . ToInt32 ( resultRow [ "Damage" ] ) ,
Weapon = ParseEnum < IW4Info . WeaponName > . Get ( resultRow [ "Weapon" ] . ToString ( ) , typeof ( IW4Info . WeaponName ) ) ,
KillOrigin = Vector3 . Parse ( resultRow [ "KillOrigin" ] . ToString ( ) ) ,
DeathOrigin = Vector3 . Parse ( resultRow [ "DeathOrigin" ] . ToString ( ) )
} ) ;
}
}
return resultList ;
}
2017-05-31 01:31:56 -04:00
public void AddPlayer ( Player P )
2015-08-20 17:54:38 -04:00
{
2017-05-31 01:31:56 -04:00
Dictionary < String , object > newPlayer = new Dictionary < String , object >
{
{ "npID" , P . NetworkID } ,
{ "KILLS" , 0 } ,
{ "DEATHS" , 0 } ,
{ "KDR" , 0.0 } ,
{ "SKILL" , 1.0 } ,
{ "SPM" , 1.0 } ,
{ "PLAYTIME" , 1.0 }
} ;
2015-08-20 17:54:38 -04:00
Insert ( "STATS" , newPlayer ) ;
}
2017-05-31 01:31:56 -04:00
public PlayerStats GetStats ( Player P )
2015-08-20 17:54:38 -04:00
{
2017-05-31 01:31:56 -04:00
DataTable Result = GetDataTable ( "STATS" , new KeyValuePair < string , object > ( "npID" , P . NetworkID ) ) ;
2015-08-20 17:54:38 -04:00
if ( Result ! = null & & Result . Rows . Count > 0 )
{
DataRow ResponseRow = Result . Rows [ 0 ] ;
return new PlayerStats (
Convert . ToInt32 ( ResponseRow [ "KILLS" ] ) ,
Convert . ToInt32 ( ResponseRow [ "DEATHS" ] ) ,
Convert . ToDouble ( ResponseRow [ "KDR" ] ) ,
2015-08-26 01:49:47 -04:00
Convert . ToDouble ( ResponseRow [ "SKILL" ] ) ,
Convert . ToDouble ( ResponseRow [ "SPM" ] ) ,
Convert . ToInt32 ( ResponseRow [ "PLAYTIME" ] )
2015-08-20 17:54:38 -04:00
) ;
}
else
{
2017-05-31 01:31:56 -04:00
AddPlayer ( P ) ;
return GetStats ( P ) ;
2015-08-20 17:54:38 -04:00
}
}
2017-06-01 13:42:28 -04:00
public long GetTotalServerKills ( )
2015-08-20 17:54:38 -04:00
{
2017-05-31 01:31:56 -04:00
var Result = GetDataTable ( "SELECT SUM(KILLS) FROM STATS" ) ;
2017-06-01 13:42:28 -04:00
return Result . Rows [ 0 ] [ 0 ] . GetType ( ) = = typeof ( DBNull ) ? 0 : Convert . ToInt64 ( Result . Rows [ 0 ] [ 0 ] ) ;
2017-05-31 01:31:56 -04:00
}
2015-08-20 17:54:38 -04:00
2017-06-01 13:42:28 -04:00
public long GetTotalServerPlaytime ( )
2017-05-31 01:31:56 -04:00
{
var Result = GetDataTable ( "SELECT SUM(PLAYTIME) FROM STATS" ) ;
2017-06-01 13:42:28 -04:00
return Result . Rows [ 0 ] [ 0 ] . GetType ( ) = = typeof ( DBNull ) ? 0 : Convert . ToInt64 ( Result . Rows [ 0 ] [ 0 ] ) / 60 ;
2017-05-31 01:31:56 -04:00
}
2015-08-20 17:54:38 -04:00
2017-05-31 01:31:56 -04:00
public void UpdateStats ( Player P , PlayerStats S )
{
Dictionary < String , object > updatedPlayer = new Dictionary < String , object >
{
{ "KILLS" , S . Kills } ,
{ "DEATHS" , S . Deaths } ,
{ "KDR" , Math . Round ( S . KDR , 2 ) } ,
2017-06-01 13:42:28 -04:00
{ "SKILL" , Math . Round ( S . Skill , 2 ) } ,
{ "SPM" , Math . Round ( S . scorePerMinute , 2 ) } ,
2017-05-31 01:31:56 -04:00
{ "PLAYTIME" , S . TotalPlayTime }
} ;
Update ( "STATS" , updatedPlayer , new KeyValuePair < string , object > ( "npID" , P . NetworkID ) ) ;
2015-08-20 17:54:38 -04:00
}
2015-08-28 00:39:36 -04:00
2017-05-31 01:31:56 -04:00
public List < KeyValuePair < String , PlayerStats > > GetTopStats ( )
2015-08-28 00:39:36 -04:00
{
2017-05-26 18:49:27 -04:00
String Query = String . Format ( "SELECT * FROM STATS WHERE SKILL > 0 AND KDR < '{0}' AND KILLS > '{1}' AND PLAYTIME > '{2}' ORDER BY SKILL DESC LIMIT '{3}'" , 10 , 150 , 60 , 5 ) ;
2015-08-28 00:39:36 -04:00
DataTable Result = GetDataTable ( Query ) ;
List < KeyValuePair < String , PlayerStats > > pStats = new List < KeyValuePair < String , PlayerStats > > ( ) ;
if ( Result ! = null & & Result . Rows . Count > 0 )
{
foreach ( DataRow ResponseRow in Result . Rows )
{
2017-09-29 22:42:24 -04:00
pStats . Add ( new KeyValuePair < String , PlayerStats > ( ResponseRow [ "npID" ] . ToString ( ) ,
2015-08-28 00:39:36 -04:00
new PlayerStats (
Convert . ToInt32 ( ResponseRow [ "KILLS" ] ) ,
Convert . ToInt32 ( ResponseRow [ "DEATHS" ] ) ,
Convert . ToDouble ( ResponseRow [ "KDR" ] ) ,
Convert . ToDouble ( ResponseRow [ "SKILL" ] ) ,
Convert . ToDouble ( ResponseRow [ "SPM" ] ) ,
Convert . ToInt32 ( ResponseRow [ "PLAYTIME" ] )
)
) ) ;
}
}
return pStats ;
}
2015-08-20 17:54:38 -04:00
}
2017-10-15 21:40:27 -04:00
public class PlayerStats
2015-08-20 17:54:38 -04:00
{
2015-08-26 01:49:47 -04:00
public PlayerStats ( int K , int D , double DR , double S , double sc , int P )
2015-08-20 17:54:38 -04:00
{
Kills = K ;
Deaths = D ;
KDR = DR ;
Skill = S ;
2015-08-26 01:49:47 -04:00
scorePerMinute = sc ;
2017-05-31 01:31:56 -04:00
TotalPlayTime = P ;
2015-08-20 17:54:38 -04:00
}
public int Kills ;
public int Deaths ;
public double KDR ;
public double Skill ;
2015-08-26 01:49:47 -04:00
public double scorePerMinute ;
2017-05-31 01:31:56 -04:00
public int TotalPlayTime ;
2015-08-20 17:54:38 -04:00
}
2015-08-20 01:06:44 -04:00
}