2015-08-20 01:06:44 -04:00
using System ;
using SharedLibrary ;
using System.Text ;
2015-08-20 17:54:38 -04:00
using System.IO ;
using System.Collections.Generic ;
using System.Data ;
2015-08-20 01:06:44 -04:00
2017-05-26 18:49:27 -04:00
using SharedLibrary.Extensions ;
using System.Threading.Tasks ;
2015-08-28 00:39:36 -04:00
namespace StatsPlugin
2015-08-20 01:06:44 -04:00
{
2015-08-28 00:39:36 -04:00
public class StatCommand : Command
2015-08-20 01:06:44 -04:00
{
2015-08-28 00:39:36 -04:00
public StatCommand ( ) : base ( "stats" , "view your stats. syntax !stats" , "xlrstats" , Player . Permission . User , 0 , 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
{
2015-09-01 12:00:12 -04:00
String statLine ;
PlayerStats pStats ;
if ( E . Target ! = null )
{
2017-05-26 18:49:27 -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-05-26 18:49:27 -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-26 18:49:27 -04:00
await E . Origin . Tell ( statLine ) ;
2015-08-20 01:06:44 -04:00
}
}
2017-05-26 18:49:27 -04:00
public class TopStats : Command
2015-08-20 01:06:44 -04:00
{
2017-05-26 18:49:27 -04:00
public TopStats ( ) : base ( "topstats" , "view the top 5 players on this server. syntax !topstats" , "!ts" , Player . Permission . User , 0 , 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-05-26 18:49:27 -04:00
List < KeyValuePair < String , PlayerStats > > pStats = Stats . statLists . Find ( x = > x . Port = = E . Owner . getPort ( ) ) . playerStats . topStats ( ) ;
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 )
{
Player P = E . Owner . clientDB . getPlayer ( pStat . Key , - 1 ) ;
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-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>
public class Stats : IPlugin
2015-08-20 17:54:38 -04:00
{
2017-05-26 18:49:27 -04:00
public static List < StatTracking > statLists ;
public struct StatTracking
{
public StatsDB playerStats ;
public DateTime [ ] lastKill , connectionTime ;
public int [ ] inactiveMinutes , Kills , deathStreaks , killStreaks ;
public int Port ;
public StatTracking ( int port )
{
playerStats = new StatsDB ( "Database/stats_" + port + ".rm" ) ;
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 ;
}
}
public string Name
{
get { return "Basic Stats" ; }
}
public float Version
{
get { return 1f ; }
}
public string Author
2015-08-20 17:54:38 -04:00
{
2017-05-26 18:49:27 -04:00
get { return "RaidMax" ; }
}
public async Task OnLoad ( )
{
statLists = new List < StatTracking > ( ) ;
await Task . Delay ( 0 ) ;
}
public async Task OnUnload ( )
{
statLists . Clear ( ) ;
await Task . Delay ( 0 ) ;
}
public async Task OnTick ( Server S )
{
await Task . Delay ( 0 ) ;
}
public async Task OnEvent ( Event E , Server S )
{
if ( E . Type = = Event . GType . Start )
{
statLists . Add ( new StatTracking ( S . getPort ( ) ) ) ;
}
if ( E . Type = = Event . GType . Stop )
{
statLists . RemoveAll ( x = > x . Port = = S . getPort ( ) ) ;
}
2015-08-26 01:49:47 -04:00
if ( E . Type = = Event . GType . Connect )
{
2017-05-26 18:49:27 -04:00
resetCounters ( E . Origin . clientID , S . getPort ( ) ) ;
2016-01-16 17:58:24 -05:00
2017-05-26 18:49:27 -04:00
PlayerStats checkForTrusted = statLists . Find ( x = > x . Port = = S . getPort ( ) ) . playerStats . getStats ( E . Origin ) ;
2016-01-16 17:58:24 -05:00
if ( checkForTrusted . playTime > = 4320 & & E . Origin . Level < Player . Permission . Trusted )
{
E . Origin . setLevel ( Player . Permission . Trusted ) ;
E . Owner . clientDB . updatePlayer ( E . Origin ) ;
2017-05-26 18:49:27 -04:00
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-05-26 18:49:27 -04:00
if ( E . Type = = Event . GType . MapEnd | | E . Type = = Event . GType . Stop )
2015-08-26 01:49:47 -04:00
{
2017-05-26 18:49:27 -04:00
foreach ( Player P in S . getPlayers ( ) )
2015-08-26 01:49:47 -04:00
{
2015-08-28 00:39:36 -04:00
if ( P = = null )
continue ;
2017-05-26 18:49:27 -04:00
calculateAndSaveSkill ( P , statLists . Find ( x = > x . Port = = S . getPort ( ) ) ) ;
resetCounters ( P . clientID , S . getPort ( ) ) ;
2015-08-26 01:49:47 -04:00
E . Owner . Log . Write ( "Updated skill for client #" + P . databaseID , Log . Level . Debug ) ;
//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));
}
}
if ( E . Type = = Event . GType . Disconnect )
{
2017-05-26 18:49:27 -04:00
calculateAndSaveSkill ( E . Origin , statLists . Find ( x = > x . Port = = S . getPort ( ) ) ) ;
resetCounters ( E . Origin . clientID , S . getPort ( ) ) ;
2015-08-26 01:49:47 -04:00
E . Owner . Log . Write ( "Updated skill for disconnecting client #" + E . Origin . databaseID , Log . Level . Debug ) ;
}
2015-08-20 17:54:38 -04:00
if ( E . Type = = Event . GType . Kill )
{
2015-10-14 23:10:14 -04:00
if ( E . Origin = = E . Target | | E . Origin = = null )
2015-08-28 00:39:36 -04:00
return ;
2015-08-20 17:54:38 -04:00
Player Killer = E . Origin ;
2017-05-26 18:49:27 -04:00
StatTracking curServer = statLists . Find ( x = > x . Port = = S . getPort ( ) ) ;
PlayerStats killerStats = curServer . playerStats . getStats ( Killer ) ;
2015-08-20 17:54:38 -04:00
2017-05-26 18:49:27 -04:00
curServer . lastKill [ E . Origin . clientID ] = DateTime . Now ;
curServer . Kills [ E . Origin . clientID ] + + ;
2015-08-26 01:49:47 -04:00
2017-05-26 18:49:27 -04:00
if ( ( curServer . lastKill [ E . Origin . clientID ] - DateTime . Now ) . TotalSeconds > 60 )
curServer . inactiveMinutes [ E . Origin . clientID ] + + ;
2015-10-14 23:10:14 -04:00
2015-08-28 00:39:36 -04:00
killerStats . Kills + + ;
2015-08-20 17:54:38 -04:00
2015-08-28 00:39:36 -04:00
if ( killerStats . Deaths = = 0 )
killerStats . KDR = killerStats . Kills ;
else
2017-05-26 18:49:27 -04:00
killerStats . KDR = Math . Round ( ( double ) killerStats . Kills / ( double ) killerStats . Deaths , 2 ) ;
2015-08-20 17:54:38 -04:00
2017-05-26 18:49:27 -04:00
curServer . playerStats . updateStats ( Killer , killerStats ) ;
2015-08-23 17:58:48 -04:00
2017-05-26 18:49:27 -04:00
curServer . killStreaks [ Killer . clientID ] + = 1 ;
curServer . deathStreaks [ Killer . clientID ] = 0 ;
2015-08-23 17:58:48 -04:00
2017-05-26 18:49:27 -04:00
await Killer . Tell ( messageOnStreak ( curServer . killStreaks [ Killer . clientID ] , curServer . deathStreaks [ Killer . clientID ] ) ) ;
2015-08-20 17:54:38 -04:00
}
if ( E . Type = = Event . GType . Death )
{
2015-10-14 23:10:14 -04:00
if ( E . Origin = = E . Target | | E . Origin = = null )
return ;
2015-08-20 17:54:38 -04:00
Player Victim = E . Origin ;
2017-05-26 18:49:27 -04:00
StatTracking curServer = statLists . Find ( x = > x . Port = = S . getPort ( ) ) ;
PlayerStats victimStats = curServer . playerStats . getStats ( Victim ) ;
2015-08-20 17:54:38 -04:00
victimStats . Deaths + + ;
2017-05-26 18:49:27 -04:00
victimStats . KDR = Math . Round ( ( double ) victimStats . Kills / ( double ) victimStats . Deaths , 2 ) ;
2015-08-22 02:04:30 -04:00
2017-05-26 18:49:27 -04:00
curServer . playerStats . updateStats ( Victim , victimStats ) ;
2015-08-23 17:58:48 -04:00
2017-05-26 18:49:27 -04:00
curServer . deathStreaks [ Victim . clientID ] + = 1 ;
curServer . killStreaks [ Victim . clientID ] = 0 ;
2015-08-23 17:58:48 -04:00
2017-05-26 18:49:27 -04:00
await Victim . Tell ( messageOnStreak ( curServer . killStreaks [ Victim . clientID ] , curServer . deathStreaks [ Victim . clientID ] ) ) ;
2015-08-20 17:54:38 -04:00
}
}
2017-05-26 18:49:27 -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-26 18:49:27 -04:00
PlayerStats disconnectStats = curServer . playerStats . getStats ( P ) ;
if ( curServer . Kills [ P . clientID ] = = 0 )
2015-08-26 01:49:47 -04:00
return ;
2017-05-26 18:49:27 -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-26 18:49:27 -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-05-26 18:49:27 -04:00
double newSPM = curServer . Kills [ P . clientID ] * 50 / Math . Max ( 1 , newPlayTime ) ;
2015-08-26 01:49:47 -04:00
double kdrWeight = Math . Round ( Math . Pow ( disconnectStats . KDR , 2 / Math . E ) , 3 ) ;
double Multiplier ;
if ( disconnectStats . scorePerMinute = = 1 )
Multiplier = 1 ;
else
Multiplier = newSPM / disconnectStats . scorePerMinute ;
double scoreWeight = ( newSPM * ( newPlayTime / disconnectStats . playTime ) ) ;
double newSkillFactor = Multiplier * scoreWeight ;
if ( Multiplier > = 1 )
disconnectStats . scorePerMinute + = newSkillFactor ;
else
disconnectStats . scorePerMinute - = ( scoreWeight - newSkillFactor ) ;
disconnectStats . Skill = disconnectStats . scorePerMinute * kdrWeight / 10 ;
disconnectStats . playTime + = newPlayTime ;
2017-05-26 18:49:27 -04:00
curServer . playerStats . updateStats ( P , disconnectStats ) ;
2015-08-26 01:49:47 -04:00
}
2017-05-26 18:49:27 -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-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
}
2015-08-23 17:58:48 -04:00
private String messageOnStreak ( int killStreak , int deathStreak )
{
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
{
public StatsDB ( String FN ) : base ( FN ) { }
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);" ;
2015-08-20 17:54:38 -04:00
ExecuteNonQuery ( Create ) ;
}
}
public void addPlayer ( Player P )
{
Dictionary < String , object > newPlayer = new Dictionary < String , object > ( ) ;
newPlayer . Add ( "npID" , P . npID ) ;
newPlayer . Add ( "KILLS" , 0 ) ;
newPlayer . Add ( "DEATHS" , 0 ) ;
2015-08-26 01:49:47 -04:00
newPlayer . Add ( "KDR" , 0.0 ) ;
newPlayer . Add ( "SKILL" , 1.0 ) ;
newPlayer . Add ( "SPM" , 1.0 ) ;
newPlayer . Add ( "PLAYTIME" , 1.0 ) ;
2015-08-20 17:54:38 -04:00
Insert ( "STATS" , newPlayer ) ;
}
public PlayerStats getStats ( Player P )
{
String Query = String . Format ( "SELECT * FROM STATS WHERE npID = '{0}'" , P . npID ) ;
DataTable Result = GetDataTable ( Query ) ;
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
{
addPlayer ( P ) ;
return getStats ( P ) ;
}
}
public void updateStats ( Player P , PlayerStats S )
{
Dictionary < String , object > updatedPlayer = new Dictionary < String , object > ( ) ;
updatedPlayer . Add ( "KILLS" , S . Kills ) ;
updatedPlayer . Add ( "DEATHS" , S . Deaths ) ;
updatedPlayer . Add ( "KDR" , Math . Round ( S . KDR , 2 ) ) ;
2015-08-26 01:49:47 -04:00
updatedPlayer . Add ( "SKILL" , Math . Round ( S . Skill , 1 ) ) ;
2017-05-26 18:49:27 -04:00
updatedPlayer . Add ( "SPM" , Math . Round ( S . scorePerMinute , 1 ) ) ;
2015-08-26 01:49:47 -04:00
updatedPlayer . Add ( "PLAYTIME" , S . playTime ) ;
2015-08-20 17:54:38 -04:00
2017-05-26 18:49:27 -04:00
Update ( "STATS" , updatedPlayer , new KeyValuePair < string , object > ( "npID" , P . npID ) ) ;
2015-08-20 17:54:38 -04:00
}
2015-08-28 00:39:36 -04:00
public List < KeyValuePair < String , PlayerStats > > topStats ( )
{
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 )
{
pStats . Add ( new KeyValuePair < String , PlayerStats > ( ResponseRow [ "npID" ] . ToString ( ) ,
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
}
public struct PlayerStats
{
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 ;
playTime = 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 ;
public int playTime ;
2015-08-20 17:54:38 -04:00
}
2015-08-20 01:06:44 -04:00
}