Browse Source

Initial import

master
Chris Smith 14 years ago
commit
bb6365f36c
100 changed files with 3609 additions and 0 deletions
  1. 2
    0
      .gitignore
  2. 109
    0
      awards.php
  3. 103
    0
      bulkimport.php
  4. 96
    0
      config-sample.php
  5. 96
    0
      daemon.php
  6. 43
    0
      inc/class.php
  7. 6
    0
      inc/cliheader.php
  8. 29
    0
      inc/daemon.class.php
  9. 14
    0
      inc/database.php
  10. 80
    0
      inc/game.php
  11. 51
    0
      inc/group.php
  12. 35
    0
      inc/parser/handlers/changed.php
  13. 15
    0
      inc/parser/handlers/committed.php
  14. 19
    0
      inc/parser/handlers/connected.php
  15. 17
    0
      inc/parser/handlers/disconnected.php
  16. 17
    0
      inc/parser/handlers/entered.php
  17. 25
    0
      inc/parser/handlers/joined.php
  18. 59
    0
      inc/parser/handlers/killed.php
  19. 22
    0
      inc/parser/handlers/loading.php
  20. 16
    0
      inc/parser/handlers/log.php
  21. 23
    0
      inc/parser/handlers/say.php
  22. 15
    0
      inc/parser/handlers/say_team.php
  23. 15
    0
      inc/parser/handlers/steam.php
  24. 15
    0
      inc/parser/handlers/team.php
  25. 221
    0
      inc/parser/handlers/triggered.php
  26. 15
    0
      inc/parser/handlers/world.php
  27. 140
    0
      inc/parser/parser.php
  28. 109
    0
      inc/player.php
  29. 68
    0
      inc/rcon.class.php
  30. 52
    0
      inc/roleperiod.php
  31. 62
    0
      inc/server.class.php
  32. 108
    0
      inc/session.php
  33. 34
    0
      inc/updateranks.php
  34. 50
    0
      inc/weapon.php
  35. 69
    0
      mapchange.php
  36. 24
    0
      updateservers.php
  37. 14
    0
      www/404.php
  38. 66
    0
      www/about.php
  39. 55
    0
      www/awards.php
  40. 71
    0
      www/class.php
  41. 213
    0
      www/deathmap.php
  42. 47
    0
      www/group.php
  43. 25
    0
      www/groups.php
  44. 21
    0
      www/inc/awards.php
  45. 36
    0
      www/inc/classcomtable.php
  46. 112
    0
      www/inc/classestable.php
  47. 10
    0
      www/inc/common.php
  48. 29
    0
      www/inc/community.php
  49. 5
    0
      www/inc/config.php
  50. 36
    0
      www/inc/deathmap.php
  51. 230
    0
      www/inc/eventhistory.php
  52. 56
    0
      www/inc/ext.php
  53. 11
    0
      www/inc/footer.php
  54. 41
    0
      www/inc/grouptable.php
  55. 35
    0
      www/inc/header.php
  56. 37
    0
      www/inc/mostmaps.php
  57. 80
    0
      www/inc/playertable.php
  58. 17
    0
      www/inc/weaponslist.php
  59. 93
    0
      www/index.php
  60. 90
    0
      www/map.php
  61. 48
    0
      www/maps.php
  62. 178
    0
      www/player.php
  63. 79
    0
      www/players.php
  64. BIN
      www/res/TF2.png
  65. BIN
      www/res/awards/brilliant_backstabber.png
  66. BIN
      www/res/awards/crazy_capturer.png
  67. BIN
      www/res/awards/dastardly_defender.png
  68. BIN
      www/res/awards/deadly_dominator.png
  69. BIN
      www/res/awards/extravagant_engy.png
  70. BIN
      www/res/awards/happy_headshotter.png
  71. BIN
      www/res/awards/mad_medic.png
  72. BIN
      www/res/awards/melee_maniac.png
  73. BIN
      www/res/awards/noimage.png
  74. BIN
      www/res/awards/persistent.png
  75. BIN
      www/res/awards/persistent_player.png
  76. BIN
      www/res/awards/revenge.png
  77. BIN
      www/res/awards/terrific_taunter.png
  78. BIN
      www/res/classes/large/blue/demoman.png
  79. BIN
      www/res/classes/large/blue/engineer.png
  80. BIN
      www/res/classes/large/blue/heavyweapons.png
  81. BIN
      www/res/classes/large/blue/medic.png
  82. BIN
      www/res/classes/large/blue/pyro.png
  83. BIN
      www/res/classes/large/blue/scout.png
  84. BIN
      www/res/classes/large/blue/sniper.png
  85. BIN
      www/res/classes/large/blue/soldier.png
  86. BIN
      www/res/classes/large/blue/spy.png
  87. BIN
      www/res/classes/large/classes.tgz
  88. BIN
      www/res/classes/large/red/demoman.png
  89. BIN
      www/res/classes/large/red/engineer.png
  90. BIN
      www/res/classes/large/red/heavyweapons.png
  91. BIN
      www/res/classes/large/red/medic.png
  92. BIN
      www/res/classes/large/red/pyro.png
  93. BIN
      www/res/classes/large/red/scout.png
  94. BIN
      www/res/classes/large/red/sniper.png
  95. BIN
      www/res/classes/large/red/soldier.png
  96. BIN
      www/res/classes/large/red/spy.png
  97. BIN
      www/res/classes/small/SmallClasses.tar.gz
  98. BIN
      www/res/classes/small/blue/demoman.png
  99. BIN
      www/res/classes/small/blue/engineer.png
  100. 0
    0
      www/res/classes/small/blue/heavyweapons.png

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
1
+/config.php
2
+/cache/*

+ 109
- 0
awards.php View File

@@ -0,0 +1,109 @@
1
+#!/usr/bin/php -q
2
+<?PHP
3
+
4
+ define('SCRIPT_HEAD1', 'awards.php v0.1');
5
+ define('SCRIPT_HEAD3', 'Assigns awards to players');
6
+ require(dirname(__FILE__) . '/inc/cliheader.php');
7
+
8
+ require_once(dirname(__FILE__) . '/config.php');
9
+ require_once(dirname(__FILE__) . '/inc/database.php');
10
+
11
+ if (!ENABLE_AWARDS) {
12
+  die("Error: Awards are not enabled\n");
13
+ }
14
+
15
+ function giveAward($award, $player, $value, $time) {
16
+  if (empty($player)) {
17
+   echo "\tNo winner :(";
18
+   return;
19
+  }
20
+
21
+  echo "\tPlayer $player won!";
22
+
23
+  $sql  = 'INSERT INTO awardwinners (award_id, player_id, winner_awarded, winner_value)';
24
+  $sql .= ' VALUES (' . $award . ', ' . $player . ', FROM_UNIXTIME(';
25
+  $sql .= $time . '), \'' . s($value) . '\')';
26
+  mysql_query($sql) or die(mysql_error() . "\n\n" . $sql . "\n");
27
+ }
28
+
29
+ function doEventAward($id, $field, $from, $to) {
30
+  $sql = '
31
+	SELECT	player_id, COUNT(*) AS res
32
+	FROM 	events
33
+		NATURAL JOIN roleperiods
34
+	WHERE	event_timestamp > FROM_UNIXTIME(' . $from . ')
35
+		AND event_timestamp < FROM_UNIXTIME(' . $to . ')
36
+		AND event_type = \'' . s($field) . '\'
37
+	GROUP BY player_id
38
+	ORDER BY res DESC
39
+	LIMIT 0,1';
40
+  $res = mysql_query($sql) or die(mysql_error() . "\n\n" . $sql . "\n");
41
+  $row = mysql_fetch_assoc($res);
42
+  giveAward($id, $row['player_id'], $row['res'] . ' events', $to);
43
+ }
44
+
45
+ function doWeaponAward($id, $field, $from, $to) {
46
+  $sql = '
47
+	SELECT	player_id, COUNT(*) AS res
48
+	FROM	kills
49
+		INNER JOIN roleperiods ON kill_killer = roleperiod_id
50
+	WHERE	kill_timestamp > FROM_UNIXTIME(' . $from . ')
51
+		AND kill_timestamp < FROM_UNIXTIME(' . $to . ')
52
+		AND weapon_id = ' . $field . '
53
+	GROUP BY player_id
54
+	ORDER BY res DESC
55
+	LIMIT 0,1';
56
+  $res = mysql_query($sql) or die(mysql_error() . "\n\n" . $sql . "\n");
57
+  $row = mysql_fetch_assoc($res);
58
+  giveAward($id, $row['player_id'], $row['res'] . ' kills', $to);
59
+ }
60
+
61
+ function doWeaponclassAward($id, $field, $from, $to) {
62
+  $sql = '
63
+        SELECT  player_id, COUNT(*) AS res
64
+        FROM    kills
65
+                INNER JOIN roleperiods ON kill_killer = roleperiod_id
66
+		NATURAL JOIN weapons
67
+        WHERE   kill_timestamp > FROM_UNIXTIME(' . $from . ')
68
+                AND kill_timestamp < FROM_UNIXTIME(' . $to . ')
69
+                AND weapon_class = \'' . s($field) . '\'
70
+        GROUP BY player_id
71
+        ORDER BY res DESC
72
+        LIMIT 0,1';
73
+  $res = mysql_query($sql) or die(mysql_error() . "\n\n" . $sql . "\n");
74
+  $row = mysql_fetch_assoc($res);
75
+  giveAward($id, $row['player_id'], $row['res'] . ' kills', $to);
76
+ }
77
+
78
+ $sql  = 'SELECT MIN(UNIX_TIMESTAMP(game_starttime)) AS start FROM games';
79
+ $res  = mysql_query($sql);
80
+ $first = mysql_result($res, 0);
81
+
82
+ $sql  = 'SELECT awards.award_id, award_type, award_field, ';
83
+ $sql .= 'MAX(UNIX_TIMESTAMP(winner_awarded)) AS lasttime FROM awards ';
84
+ $sql .= 'LEFT OUTER JOIN awardwinners ON awardwinners.award_id ';
85
+ $sql .= '= awards.award_id GROUP BY awards.award_id';
86
+ $res  = mysql_query($sql) or die(mysql_error() . "\n\n" . $sql . "\n");
87
+
88
+ while ($row = mysql_fetch_assoc($res)) {
89
+  if ($row['lasttime'] > 60 + strtotime('-' . AWARD_FREQUENCY . ' days')) {
90
+   // Already awarded.
91
+   continue;
92
+  }
93
+
94
+  $from = $row['lasttime'] == null ? $first : $row['lasttime'];
95
+  $to = $from + AWARD_FREQUENCY * 60 * 60 * 24;
96
+
97
+  do {
98
+   echo "\nAward ", $row['award_id'], " from $from to $to:";
99
+
100
+   call_user_func('do' . ucfirst($row['award_type']) . 'Award', $row['award_id'], $row['award_field'], $from, $to);
101
+
102
+   $from += AWARD_FREQUENCY * 60 * 60 * 24;
103
+   $to   += AWARD_FREQUENCY * 60 * 60 * 24;
104
+  } while ($to < time());
105
+ }
106
+
107
+ echo "\n";
108
+
109
+?>

+ 103
- 0
bulkimport.php View File

@@ -0,0 +1,103 @@
1
+#!/usr/bin/php -q
2
+<?PHP
3
+
4
+ require_once(dirname(__FILE__) . '/config.php');
5
+ require_once(dirname(__FILE__) . '/inc/database.php');
6
+ require_once(dirname(__FILE__) . '/inc/game.php');
7
+ require_once(dirname(__FILE__) . '/inc/updateranks.php');
8
+ require_once(dirname(__FILE__) . '/inc/parser/parser.php');
9
+
10
+ define('SCRIPT_HEAD1', 'bulkimport.php v0.1');
11
+ define('SCRIPT_HEAD3', 'Imports log files in bulk');
12
+
13
+ require(dirname(__FILE__) . '/inc/cliheader.php');
14
+
15
+ $ranks = $erase = $noupdate = false;
16
+
17
+ foreach ($argv as $arg) {
18
+  if ($arg == '--erase') {
19
+   $erase = true;
20
+  } else if ($arg == '--ranks') {
21
+   $ranks = true;
22
+  } else if ($arg == '--noupdate') {
23
+   $noupdate = true;
24
+  }
25
+ }
26
+
27
+ mysql_query('UPDATE config SET config_value = \'true\' WHERE config_key = \'updating\'');
28
+
29
+ if ($erase) {
30
+  echo "*** Erasing previous data ... ";
31
+
32
+  foreach (array('games', 'maps', 'players', 'roleperiods', 'sessions', 
33
+	         'kills', 'events', 'files', 'awardwinners', 'groups',
34
+                 'groupmemberships') as $table) {
35
+   mysql_query('TRUNCATE ' . $table);
36
+  }
37
+
38
+  echo "Done\n";
39
+ }
40
+
41
+ if (!$ranks && !$noupdate) {
42
+  $sql = 'SELECT server_id, server_logdir FROM servers';
43
+  $servers = mysql_query($sql);
44
+  while ($server = mysql_fetch_assoc($servers)) {
45
+   if ($server['server_logdir'] === null || empty($server['server_logdir'])) {
46
+    // No log dir for this server
47
+    continue;
48
+   }
49
+
50
+   foreach (glob($server['server_logdir'] . '/*') as $file) {
51
+    $offset = $pos = 0;
52
+
53
+    $sql = 'SELECT file_size, file_offset FROM files WHERE file_name = \'' . s($file) . '\'';
54
+    $res = mysql_query($sql);
55
+
56
+    if (mysql_num_rows($res) > 0) {
57
+     $row = mysql_fetch_assoc($res);
58
+     if ((int) $row['file_size'] == filesize($file)) {
59
+      continue;
60
+     } else {
61
+      $offset = (int) $row['file_offset'];
62
+     }
63
+    }
64
+
65
+    Game::setServer($server['server_id']);
66
+
67
+    echo "Reading $file...\n";
68
+
69
+    $fh = fopen($file, 'r');
70
+    fseek($fh, $offset);
71
+
72
+    $line = null;
73
+
74
+    while (!feof($fh)) {
75
+     if ($line != null) {
76
+      Parser::parseLine($line);
77
+     }
78
+     $pos = ftell($fh);
79
+     $line = fgets($fh);
80
+    }
81
+
82
+    if ($offset > 0) {
83
+     $sql = 'UPDATE files SET file_size = ' . filesize($file) . ', file_offset  = ' . $pos . ' WHERE file_name = \'' . s($file) . '\'';
84
+     mysql_query($sql);
85
+    } else {
86
+     $sql = 'INSERT INTO files (file_size, file_offset, file_name) VALUES (' . filesize($file) . ', ' . $pos . ', \'' . s($file) . '\')';
87
+     mysql_query($sql);
88
+    }
89
+   
90
+    fclose($fh);
91
+   }
92
+  }
93
+ }
94
+
95
+ if (!$noupdate) {
96
+  mysql_query('UPDATE groups SET group_score = (SELECT SUM(player_score) FROM groupmemberships NATURAL JOIN players WHERE groupmemberships.group_id = groups.group_id GROUP BY groupmemberships.group_id), group_members = (SELECT COUNT(*) FROM groupmemberships NATURAL JOIN players WHERE groupmemberships.group_id = groups.group_id GROUP BY groupmemberships.group_id)');
97
+
98
+  updateRanks();
99
+ }
100
+ 
101
+ mysql_query('UPDATE config SET config_value = \'false\' WHERE config_key = \'updating\'');
102
+
103
+?>

+ 96
- 0
config-sample.php View File

@@ -0,0 +1,96 @@
1
+<?PHP
2
+
3
+ // --------------------------------------------- Database Settings -----------
4
+
5
+ // MySQL host
6
+ define('DB_HOST', '127.0.0.1');
7
+
8
+ // MySQL username
9
+ define('DB_USER', '');
10
+
11
+ // MySQL password
12
+ define('DB_PASS', '');
13
+
14
+ // MySQL database name
15
+ define('DB_DBNAME', '');
16
+
17
+ // -------------------------------------------- Directory Settings -----------
18
+
19
+ // Directory containing HLDS log files
20
+ define('LOG_DIR', '/home/sourceds/orangebox/tf/logs/');
21
+
22
+ // The directory that the main statistics codebase is located in
23
+ define('STATS_DIR', '/home/sourceds/stats/');
24
+
25
+ // ---------------------------------------------- Ranking Settings -----------
26
+
27
+ // Minimum number of kills needed to be ranked
28
+ define('MIN_KILLS', 50);
29
+
30
+ // ------------------------------------------------ Server Details -----------
31
+
32
+ // Command to use to send player messages
33
+ define('RCON_COMMAND', 'admin_msay "%s" "%s"');
34
+
35
+ // --------------------------------------------- Feature Selection -----------
36
+
37
+ // Death maps
38
+ define('ENABLE_DEATHMAPS', true);
39
+
40
+ // Awards
41
+ define('ENABLE_AWARDS', true);
42
+
43
+ // Group links
44
+ define('ENABLE_GROUPS', true);
45
+
46
+ // Steam community links
47
+ define('ENABLE_COMMUNITY_LINKS', true);
48
+
49
+ // Respond to messages said in global chat (when in daemon mode)
50
+ define('ENABLE_SAY_TRIGGERS', true);
51
+
52
+ // Server list on the overview page
53
+ define('ENABLE_SERVER_LIST', true);
54
+
55
+ // ----------------------------------- Overview Page Configuration -----------
56
+
57
+ // Number of top players to show on the overview page
58
+ define('OVERVIEW_PLAYERS', 15);
59
+
60
+ // ------------------------------------------ Awards Configuration -----------
61
+
62
+ // How often to give out awards (in days)
63
+ define('AWARD_FREQUENCY', 7);
64
+
65
+ // Number of previous winners to show on the award page (per award)
66
+ define('AWARD_NUMBER', 5);
67
+
68
+ // --------------------------------------- Death Map Configuration -----------
69
+
70
+ // The file name for overview images
71
+ define('OVERVIEW_IMAGE', '/home/sourceds/www/stats/res/maps/large/%s/Overview.png');
72
+
73
+ // Arguments for the default death map
74
+ define('DM_ARGS', '?map=%s&alpha=50&noteams');
75
+
76
+ // Cache dir for DM images
77
+ define('DM_CACHE', STATS_DIR . 'cache/');
78
+
79
+ // ---------------------------------------------------------- URLs -----------
80
+
81
+ // The base URL for the stats websites
82
+ define('URL_BASE', '/stats/');
83
+
84
+ // The URL for map images. Passed image size (tiny, small, large) and map name.
85
+ define('URL_MAP', '/stats/res/maps/%s/%s/Main.png');
86
+
87
+ // The URL for class images. Passed team and class name.
88
+ define('URL_CLASS', '/stats/res/classes/small/%s/%s.png');
89
+
90
+ // The URL for weapon images. Passed weapon name.
91
+ define('URL_WEAPON', '/stats/res/weapons/%s.png');
92
+
93
+ // The URL for award images. Passed award name.
94
+ define('URL_AWARD', '/stats/res/awards/%s.png');
95
+
96
+?>

+ 96
- 0
daemon.php View File

@@ -0,0 +1,96 @@
1
+#!/usr/bin/php -q
2
+<?PHP
3
+
4
+ require_once(dirname(__FILE__) . '/config.php');
5
+ require_once(dirname(__FILE__) . '/inc/database.php');
6
+ require_once(dirname(__FILE__) . '/inc/daemon.class.php');
7
+ require_once(dirname(__FILE__) . '/inc/parser/parser.php');
8
+
9
+ define('SCRIPT_HEAD1', 'daemon.php v0.1');
10
+ define('SCRIPT_HEAD3', 'Analyses server stats in real time ');
11
+ require(dirname(__FILE__) . '/inc/cliheader.php');
12
+
13
+ $port = false;
14
+ $server = false;
15
+ $force = false;
16
+
17
+ foreach ($argv as $arg) {
18
+  if ($arg == '--port') {
19
+   $port = true;
20
+  } else if ($port === true) {
21
+   $port = (int) $arg;
22
+  } if ($arg == '--server') {
23
+   $server = true;
24
+  } else if ($server === true) {
25
+   $server = (int) $arg;
26
+  } else if ($arg == '--force') {
27
+   $force = true;
28
+  }
29
+ }
30
+
31
+ if ($port === true || $port === false) {
32
+  die('ERROR: Please specify a port with --port <port>' . "\n");
33
+ } else if ($server === false) {
34
+  $server = 1;
35
+ } else if ($server === true) {
36
+  die('ERROR: Please specify a valid server with --server <id>'. "\n");
37
+ }
38
+
39
+ $sql = 'SELECT server_ip, server_port, server_rcon, server_daemon FROM servers WHERE server_id = ' . $server;
40
+ $res = mysql_query($sql);
41
+
42
+ if (mysql_num_rows($res) == 0) {
43
+  die('ERROR: Invalid server. Please specify a valid one with --server <id>' . "\n");
44
+ } else {
45
+  $row = mysql_fetch_assoc($res);
46
+
47
+  if ($row['server_rcon'] === null || empty($row['server_rcon'])) {
48
+   die('ERROR: No rcon password specified for that server.' . "\n");
49
+  } else {
50
+   define('SERVER_IP', $row['server_ip']);
51
+   define('SERVER_PORT', (int) $row['server_port']);
52
+   define('RCON_PW', $row['server_rcon']);
53
+  }
54
+
55
+  if ((int) $row['server_daemon'] == 0) {
56
+   if ($force) {
57
+    echo "Setting server to daemon mode...\n";
58
+    $sql = 'UPDATE servers SET server_daemon = 1 WHERE server_id = ' . $server;
59
+    mysql_query($sql);
60
+   } else {
61
+    echo str_pad(' WARNING ', 80, '@', STR_PAD_BOTH), "\n";
62
+    echo "@                                                                              @\n";
63
+    echo "@  That server is currently set to log parsing mode. If you switch to daemon   @\n";
64
+    echo "@  mode you will no longer be able to use the 'bulk import' facility, and may  @\n";
65
+    echo "@  lose data if the daemon script is not running constantly.                   @\n";
66
+    echo "@                                                                              @\n";
67
+    echo "@  If you're sure you want to start the daemon for this server, add the        @\n";
68
+    echo "@  --force parameter to the arguments for this script and rerun it.            @\n";
69
+    echo "@                                                                              @\n";
70
+    echo str_repeat('@', 80), "\n";
71
+    exit();
72
+   }
73
+  }
74
+ }
75
+
76
+ if (($socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) === false) {
77
+  die('ERROR: Unable to create socket. Error: ' . socket_strerror(socket_last_error()) . "\n");
78
+ }
79
+
80
+ if (!@socket_bind($socket, '0.0.0.0', $port)) {
81
+  die('ERROR: Unable to bind socket. Error: ' . socket_strerror(socket_last_error()) . "\n");
82
+ }
83
+ 
84
+ socket_set_block($socket);
85
+
86
+ Parser::setDaemon(new Daemon(SERVER_IP, SERVER_PORT, RCON_PW));
87
+
88
+ while (true) {
89
+  $buf = $from = '';
90
+  if (socket_recvfrom($socket, $buf, 1024, 0, $from, $port) > -1) {
91
+   echo $buf;
92
+   Parser::parseLine($buf); 
93
+  }
94
+ }
95
+ 
96
+?>

+ 43
- 0
inc/class.php View File

@@ -0,0 +1,43 @@
1
+<?PHP
2
+
3
+ require_once(dirname(__FILE__) . '/database.php');
4
+
5
+ class PlayerClass {
6
+
7
+  private static $classes = array();
8
+  private static $modifiers = array();
9
+
10
+  public static function getID($name) {
11
+   if (is_int($name) || ctype_digit($name)) { return $name; }
12
+
13
+   return self::$classes[$name]['class_id'];
14
+  }
15
+
16
+  public static function getModifier($victim, $killer) {
17
+   if (isset(self::$modifiers[$victim][$killer])) {
18
+    return self::$modifiers[$victim][$killer];
19
+   }
20
+
21
+   if ($victim == NULL || $killer == NULL) {
22
+    throw new Exception("Victim or killer was null");
23
+   }
24
+
25
+   $sql = 'SELECT modifier_modifier FROM class_modifiers WHERE modifier_victimclass = ' . $victim . ' AND modifier_killerclass = ' . $killer;
26
+   $res = mysql_query($sql) or die(mysql_error());
27
+
28
+   return self::$modifiers[$victim][$killer] = mysql_result($res, 0);
29
+  }
30
+
31
+  public static function init() {
32
+   $sql = 'SELECT class_id, class_name, class_displayname FROM classes';
33
+   $res = mysql_query($sql);
34
+   while ($row = mysql_fetch_assoc($res)) {
35
+    self::$classes[$row['class_name']] = $row;
36
+   }
37
+  }
38
+
39
+ }
40
+
41
+ PlayerClass::init();
42
+
43
+?>

+ 6
- 0
inc/cliheader.php View File

@@ -0,0 +1,6 @@
1
+ _____ _____ ____  ____  _         _       
2
+|_   _|  ___|___ \/ ___|| |_  __ _| |_ ___ 
3
+  | | | |_    __) \___ \| __|/ _` | __/ __|  <?PHP if (defined('SCRIPT_HEAD1')) { echo SCRIPT_HEAD1; }; echo "\n"; ?>
4
+  | | |  _)  / __/ ___) | |_( (_| | |_\__ \  <?PHP if (defined('SCRIPT_HEAD2')) { echo SCRIPT_HEAD2; }; echo "\n"; ?>
5
+  |_| |_|   |_____|____/ \__|\__,_|\__|___/  <?PHP if (defined('SCRIPT_HEAD3')) { echo SCRIPT_HEAD3; }; echo "\n"; ?>
6
+

+ 29
- 0
inc/daemon.class.php View File

@@ -0,0 +1,29 @@
1
+<?PHP
2
+
3
+ require_once(dirname(dirname(__FILE__)) . '/config.php');
4
+ require_once(dirname(__FILE__) . '/player.php');
5
+ require_once(dirname(__FILE__) . '/rcon.class.php');
6
+
7
+ class Daemon {
8
+
9
+  private $rcon;
10
+
11
+  public function __construct($server, $port, $rconpass) {
12
+   $this->rcon = new Rcon($server, $port, $rconpass);
13
+  }
14
+
15
+  public function showKPD(&$player) {
16
+   $message  = 'Average kills per death: ';
17
+   $message .= $player->getKPD();
18
+   $this->sendMessage($player, $message);
19
+  }
20
+
21
+  private function sendMessage(&$player, $message) {
22
+   $this->rcon->execute(sprintf(RCON_COMMAND,
23
+                $player->getOpenSession()->getAlias(), $message));
24
+  }
25
+
26
+ }
27
+
28
+
29
+?>

+ 14
- 0
inc/database.php View File

@@ -0,0 +1,14 @@
1
+<?PHP
2
+
3
+ require_once(dirname(dirname(__FILE__)) . '/config.php');
4
+
5
+ mysql_connect(DB_HOST, DB_USER, DB_PASS);
6
+ mysql_select_db(DB_DBNAME);
7
+
8
+ mysql_query("SET NAMES 'utf8'");
9
+
10
+ function s($sql) {
11
+  return mysql_real_escape_string($sql);
12
+ }
13
+
14
+?>

+ 80
- 0
inc/game.php View File

@@ -0,0 +1,80 @@
1
+<?PHP
2
+
3
+ require_once(dirname(__FILE__) . '/database.php');
4
+ require_once(dirname(__FILE__) . '/session.php');
5
+
6
+ class Game {
7
+
8
+  private static $current = null;
9
+  private static $maps = array();
10
+  private static $server = null;
11
+
12
+  private $id;
13
+
14
+  private function __construct($id = null, $map = null, $starttime = null) {
15
+   if ($id != null) {
16
+    $this->id = $id;
17
+   } else {
18
+    assert($map != null && $starttime != null);
19
+
20
+    $sql = 'INSERT INTO games (map_id, game_starttime, server_id) VALUES (' . self::resolveMap($map) . ', FROM_UNIXTIME(' . $starttime . '), ' . self::$server . ')';
21
+    $res = mysql_query($sql);
22
+    $this->id = mysql_insert_id();
23
+   }
24
+  }
25
+
26
+  public static function setServer($server) {
27
+   self::$server = $server;
28
+  }
29
+
30
+  public function getID() {
31
+   return $this->id;
32
+  }
33
+
34
+  private function end($timestamp) {
35
+   $sql = 'UPDATE games SET game_endtime = FROM_UNIXTIME(' . $timestamp . ') WHERE game_id = ' . $this->id;
36
+   $res = mysql_query($sql);
37
+  }
38
+
39
+  public static function init() {
40
+   $sql = 'SELECT game_id FROM games WHERE game_endtime = \'0000-00-00\'';
41
+   $res = mysql_query($sql);
42
+
43
+   if (mysql_num_rows($res) > 0) {
44
+    self::$current = new Game((int) mysql_result($res, 0));
45
+   }
46
+  }
47
+
48
+  public static function changeMap($timestamp, $map) {
49
+   if (self::$current != null) {
50
+    self::$current->end($timestamp);
51
+   }
52
+
53
+   self::$current = new Game(null, $map, $timestamp);
54
+  }
55
+
56
+  public static function resolveMap($name) {
57
+   if (isset(self::$maps[$name])) { return self::$maps[$name]; }
58
+
59
+   $sql = 'SELECT map_id FROM maps WHERE map_name = \'' . s($name) . '\''; 
60
+   $res = mysql_query($sql);
61
+
62
+   if (mysql_num_rows($res) > 0) {
63
+    return self::$maps[$name] = (int) mysql_result($res, 0);
64
+   } else {
65
+    $sql = 'INSERT INTO maps (map_name) VALUES (\'' . s($name) . '\')';
66
+    $res = mysql_query($sql);
67
+
68
+    return self::$maps[$name] = mysql_insert_id();
69
+   }
70
+  }
71
+
72
+  public static function getCurrent() {
73
+   return self::$current;
74
+  }
75
+
76
+ }
77
+
78
+ Game::init();
79
+
80
+?>

+ 51
- 0
inc/group.php View File

@@ -0,0 +1,51 @@
1
+<?PHP
2
+
3
+ require_once(dirname(__FILE__) . '/database.php');
4
+
5
+ class Group {
6
+
7
+  private $id;
8
+  private $players = array();
9
+  private static $cache = array();
10
+
11
+  protected function __construct($id) {
12
+   $this->id = $id;
13
+  }
14
+
15
+  public function ensureMember(&$player) {
16
+   if (!isset($this->players[$player->getID()])) {
17
+    $sql = 'SELECT membership_id FROM groupmemberships WHERE player_id = ' . $player->getID() . ' AND group_id = ' . $this->id;
18
+    $res = mysql_query($sql);
19
+
20
+    if (mysql_num_rows($res) == 0) {
21
+     $sql = 'INSERT INTO groupmemberships (player_id, group_id) VALUES (' . $player->getID() . ', ' . $this->id . ')';
22
+     mysql_query($sql);
23
+    }
24
+
25
+    $this->players[$player->getID()] = true;
26
+   }
27
+  }
28
+
29
+  public static function getGroup($tag) {
30
+   if (!isset(self::$cache[$tag])) {
31
+    $sql = 'SELECT group_id FROM groups WHERE group_name = \'' . s($tag) . '\' AND group_type = \'tag\'';
32
+    $res = mysql_query($sql);
33
+ 
34
+    if (mysql_num_rows($res) > 0) {
35
+     $row = mysql_fetch_assoc($res);
36
+     $id  = (int) $row['group_id'];
37
+    } else {
38
+     $sql = 'INSERT INTO groups (group_name, group_type) VALUES (\'' . s($tag) . '\', \'tag\')';
39
+     $res = mysql_query($sql);
40
+     $id  = mysql_insert_id();
41
+    }
42
+
43
+    self::$cache[$tag] = new Group($id);
44
+   }
45
+
46
+   return self::$cache[$tag];
47
+  }
48
+
49
+}
50
+
51
+?>

+ 35
- 0
inc/parser/handlers/changed.php View File

@@ -0,0 +1,35 @@
1
+<?PHP
2
+
3
+ class ChangedHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles role or name changing events.                                   *
7
+   *                                                                         *
8
+   * Line sample: role to "Sniper"                                           *
9
+   *              name to "Foo"                                              *
10
+  \*-------------------------------------------------------------------------*/
11
+
12
+  public function parseLine($date, $player, $playerdetails, $line) {
13
+   $what = substr($line, 0, 4);
14
+   $line = substr($line, 8); // role to
15
+   $class = Parser::parseString($line);
16
+
17
+   if ($what != 'role') {
18
+    return;
19
+   } 
20
+
21
+   if (Game::getCurrent() == null) {
22
+    echo "No game in progress; discarding class change\n";
23
+    return;
24
+   }
25
+
26
+   if ($player->getOpenSession() == null) {
27
+    $player->openSession($date, $playerdetails['uid'], $playerdetails['alias']);
28
+   }
29
+
30
+   $player->getOpenSession()->changeRole($date, $playerdetails['team'], $class);
31
+  }
32
+
33
+ }
34
+
35
+?>

+ 15
- 0
inc/parser/handlers/committed.php View File

@@ -0,0 +1,15 @@
1
+<?PHP
2
+
3
+ class CommittedHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles suicide events.                                                 *
7
+  \*-------------------------------------------------------------------------*/
8
+
9
+  public function parseLine($date, $player, $playerdetails, $line) {
10
+   // TODO: Implement me
11
+  }
12
+
13
+ }
14
+
15
+?>

+ 19
- 0
inc/parser/handlers/connected.php View File

@@ -0,0 +1,19 @@
1
+<?PHP
2
+
3
+ class ConnectedHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles players connecting                                              *
7
+   *                                                                         *
8
+   * Line sample: address "ip:port"                                          *
9
+   *                                                                         *
10
+   * Note: Steam ID and team will be pending at this point.                  *
11
+  \*-------------------------------------------------------------------------*/
12
+
13
+  public function parseLine($date, $player, $playerdetails, $line) {
14
+   // TODO: Implement me
15
+  }
16
+
17
+ }
18
+
19
+?>

+ 17
- 0
inc/parser/handlers/disconnected.php View File

@@ -0,0 +1,17 @@
1
+<?PHP
2
+
3
+ class DisconnectedHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles players disconnecting                                           *
7
+   *                                                                         *
8
+   * Sample line: (reason "Jasprit timed out")                               *
9
+  \*-------------------------------------------------------------------------*/
10
+
11
+  public function parseLine($date, $player, $playerdetails, $line) {
12
+   $player->closeSession($date); 
13
+  }
14
+
15
+ }
16
+
17
+?>

+ 17
- 0
inc/parser/handlers/entered.php View File

@@ -0,0 +1,17 @@
1
+<?PHP
2
+
3
+ class EnteredHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles players entering the game.                                      *
7
+   *                                                                         *
8
+   * Line sample: the game                                                   *
9
+  \*-------------------------------------------------------------------------*/
10
+
11
+  public function parseLine($date, $player, $playerdetails, $line) {
12
+   $player->openSession($date, $playerdetails['uid'], $playerdetails['alias']); 
13
+  }
14
+
15
+ }
16
+
17
+?>

+ 25
- 0
inc/parser/handlers/joined.php View File

@@ -0,0 +1,25 @@
1
+<?PHP
2
+
3
+ class JoinedHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles team joining events.                                            *
7
+   *                                                                         *
8
+   * Line sample: team "Red"                                                 *
9
+  \*-------------------------------------------------------------------------*/
10
+
11
+  public function parseLine($date, $player, $playerdetails, $line) {
12
+   $line = substr($line, 5); // team
13
+   $team = Parser::parseString($line);
14
+
15
+   if ($player->getOpenSession() == null) {
16
+    echo "Player " . $player->getSteamID() . " has no open session but joined a team\n";
17
+    $player->openSession($date, $playerdetails['uid'], $playerdetails['alias']);
18
+   }
19
+
20
+   $player->getOpenSession()->changeTeam($date, $team);
21
+  }
22
+
23
+ }
24
+
25
+?>

+ 59
- 0
inc/parser/handlers/killed.php View File

@@ -0,0 +1,59 @@
1
+<?PHP
2
+
3
+ require_once(dirname(dirname(dirname(__FILE__))) . '/weapon.php');
4
+
5
+ class KilledHandler {
6
+
7
+  /*-------------------------------------------------------------------------*\
8
+   * Handles kill events.                                                    *
9
+   *                                                                         *
10
+   * Sample line: "Demented-Idiot<3><STEAM_0:1:2867409><Blue>" with          *
11
+   *  "scattergun" (attacker_position "-76 -573 23") (victim_position        *
12
+   *  "33 -853 24")                                                          *
13
+  \*-------------------------------------------------------------------------*/
14
+
15
+  public function parseLine($date, $player, $playerdetails, $line) {
16
+   $victim = Parser::parsePlayer($line);
17
+   $victim = Player::getBySteamID($victim['steamid']);
18
+   
19
+   $line = substr($line, 5); // with
20
+   $weapon = Parser::parseString($line);
21
+   $props = Parser::parseProps($line);
22
+
23
+   if (isset($props['customkill'])) {
24
+    $weapon .= ' (' . $props['customkill'] . ')';
25
+   }
26
+
27
+   $weapon = Weapon::getID($weaponname = $weapon);
28
+
29
+   if ($player == null || $player->getOpenSession() == null
30
+     || $player->getOpenSession()->getRolePeriod() == null) {
31
+    echo "Player " . $player->getSteamID() . " has no role, dropping kill\n";
32
+    return;
33
+   }
34
+
35
+   if ($victim == null || $victim->getOpenSession() == null
36
+     || $victim->getOpenSession()->getRolePeriod() == null) {
37
+    echo "Victim " . $victim->getSteamID() . " has no role, dropping kill\n";
38
+    return;
39
+   }
40
+
41
+   $sql  = 'INSERT INTO kills (kill_killer, kill_killer_position, kill_victim, ';
42
+   $sql .= 'kill_victim_position, weapon_id, kill_distance, kill_timestamp) ';
43
+   $sql .= 'VALUES (' . $player->getOpenSession()->getRolePeriod()->getID() . ', ';
44
+   $sql .= '\'' . s($props['attacker_position']) . '\', ';
45
+   $sql .= $victim->getOpenSession()->getRolePeriod()->getID() . ', ';
46
+   $sql .= '\'' . s($props['victim_position']) . '\', ' . $weapon . ', ' . $props['distance'];
47
+   $sql .= ', FROM_UNIXTIME(' . $date . '))';
48
+   $res  = mysql_query($sql) or die("$sql\n\n" . mysql_error());
49
+
50
+   $score = 5 * ($victim->getScore() / $player->getScore()) * Weapon::getModifier($weaponname)
51
+		 * PlayerClass::getModifier($victim->getOpenSession()->getRolePeriod()->getClass(),
52
+					    $player->getOpenSession()->getRolePeriod()->getClass());
53
+   $player->addScore($score);
54
+   $victim->addScore(-1 * $score);
55
+  }
56
+
57
+ }
58
+
59
+?>

+ 22
- 0
inc/parser/handlers/loading.php View File

@@ -0,0 +1,22 @@
1
+<?PHP
2
+
3
+ require_once(dirname(dirname(dirname(__FILE__))) . '/game.php');
4
+
5
+ class LoadingHandler {
6
+
7
+  /*-------------------------------------------------------------------------*\
8
+   * Handles map loading events.                                             *
9
+   *                                                                         *
10
+   * Line sample: map "cp_dustbowl"                                          *
11
+  \*-------------------------------------------------------------------------*/
12
+
13
+  public function parseLine($date, $player, $playerdetails, $line) {
14
+   $line = substr($line, 4); // map
15
+   $map = Parser::parseString($line);
16
+
17
+   Game::changeMap($date, $map);
18
+  }
19
+
20
+ }
21
+
22
+?>

+ 16
- 0
inc/parser/handlers/log.php View File

@@ -0,0 +1,16 @@
1
+<?PHP
2
+
3
+ class LogHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles log events.                                                     *
7
+   *                                                                         *
8
+   * Line sample: file closed                                                *
9
+  \*-------------------------------------------------------------------------*/
10
+
11
+  public function parseLine($date, $player, $playerdetails, $line) {
12
+  }
13
+
14
+ }
15
+
16
+?>

+ 23
- 0
inc/parser/handlers/say.php View File

@@ -0,0 +1,23 @@
1
+<?PHP
2
+
3
+ class SayHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles chat events.                                                    *
7
+  \*-------------------------------------------------------------------------*/
8
+
9
+  public function parseLine($date, $player, $playerdetails, $line) {
10
+   if (!ENABLE_SAY_TRIGGERS || !Parser::hasDaemon()) {
11
+    return;
12
+   }
13
+
14
+   $line = substr(trim($line), 1, -1); // Cut off quotes
15
+   
16
+   if (strtolower($line) == 'kpd') {
17
+    Parser::getDaemon()->showKPD($player);
18
+   }
19
+  }
20
+
21
+ }
22
+
23
+?>

+ 15
- 0
inc/parser/handlers/say_team.php View File

@@ -0,0 +1,15 @@
1
+<?PHP
2
+
3
+ class SayTeamHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles chat events.                                                    *
7
+  \*-------------------------------------------------------------------------*/
8
+
9
+  public function parseLine($date, $player, $playerdetails, $line) {
10
+   // TODO: Implement me
11
+  }
12
+
13
+ }
14
+
15
+?>

+ 15
- 0
inc/parser/handlers/steam.php View File

@@ -0,0 +1,15 @@
1
+<?PHP
2
+
3
+ class SteamHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles STEAMID validation messages                                     *
7
+  \*-------------------------------------------------------------------------*/
8
+
9
+  public function parseLine($date, $player, $playerdetails, $line) {
10
+   // TODO: Implement me
11
+  }
12
+
13
+ }
14
+
15
+?>

+ 15
- 0
inc/parser/handlers/team.php View File

@@ -0,0 +1,15 @@
1
+<?PHP
2
+
3
+ class TeamHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles team events.                                                    *
7
+  \*-------------------------------------------------------------------------*/
8
+
9
+  public function parseLine($date, $player, $playerdetails, $line) {
10
+   // TODO: Implement me
11
+  }
12
+
13
+ }
14
+
15
+?>

+ 221
- 0
inc/parser/handlers/triggered.php View File

@@ -0,0 +1,221 @@
1
+<?PHP
2
+
3
+ class TriggeredHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles triggered events.                                               *
7
+  \*-------------------------------------------------------------------------*/
8
+
9
+  public function parseLine($date, $player, $playerdetails, $line) {
10
+   $type = Parser::parseString($line);
11
+
12
+   switch($type) {
13
+    case 'chargedeployed':
14
+     $this->chargeDeployed($date, $player);
15
+     break;
16
+    case 'domination':
17
+     $this->domination($date, $player, $line, 'domination');
18
+     break;
19
+    case 'revenge':
20
+     $this->domination($date, $player, $line, 'revenge');
21
+     break;
22
+    case 'kill assist':
23
+     $this->assist($date, $player, $line);
24
+     break;
25
+    case 'builtobject':
26
+     $this->builtObject($date, $player, $line);
27
+     break;
28
+    case 'killedobject':
29
+     $this->killedObject($date, $player, $line);
30
+     break;
31
+    case 'flagevent':
32
+     $this->flagEvent($date, $player, $line);
33
+     break;
34
+    default: 
35
+     //echo "No support for trigger type $type\n";
36
+   }
37
+  }
38
+
39
+  private function flagEvent($date, $player, $line) {
40
+   $props = Parser::parseProps($line);
41
+
42
+   switch ($props['event']) {
43
+    case 'defended':
44
+     $this->doFlagEvent($date, $player, $line, 'intel defended', $props);
45
+     $player->addScore(2);
46
+     break;
47
+    case 'picked up':
48
+     $this->doFlagEvent($date, $player, $line, 'intel picked up', $props);
49
+     $player->addScore(2);
50
+     break;
51
+    case 'dropped':
52
+     $this->doFlagEvent($date, $player, $line, 'intel dropped', $props);
53
+     $player->addScore(-2);
54
+     break;
55
+    case 'captured':
56
+     $this->doFlagEvent($date, $player, $line, 'intel captured', $props);
57
+     $player->addScore(5);
58
+     break;
59
+    default:
60
+     // echo ...
61
+   }
62
+  }
63
+
64
+  private function doFlagEvent($date, $player, $line, $type, $props) {
65
+   if ($player->getOpenSession() == null || $player->getOpenSession()->getRolePeriod() == null) {
66
+    echo "Player ", $player->getSteamID(), " has no session/roleperiod. Dropping flag event\n";
67
+    return;
68
+   }
69
+
70
+   $playerID = $player->getOpenSession()->getRolePeriod()->getID();
71
+
72
+   $sql  = 'INSERT INTO events (event_timestamp, roleperiod_id, event_type, ';
73
+   $sql .= 'event_location) VALUES (FROM_UNIXTIME(' . $date . '), ' . $playerID;
74
+   $sql .= ', \'' . $type . '\', \'' . s($props['position']) . '\')';
75
+   mysql_query($sql);
76
+  }
77
+
78
+  private function killedObject($date, $player, $line) {
79
+   $props = Parser::parseProps($line);
80
+
81
+   if (isset($props['assist'])) {
82
+    // TODO!
83
+    return;
84
+   }
85
+
86
+   $arg = s($props['object']);
87
+   $victim = Parser::parsePlayer($props['objectowner'], false);
88
+   $victim = Player::getBySteamID($victim['steamid']);
89
+
90
+   if ($victim == null || $victim->getOpenSession() == null
91
+       || $victim->getOpenSession()->getRolePeriod() == null) {
92
+    echo "Victim invalid\n";
93
+    return;
94
+   }
95
+
96
+   if ($player == null || $player->getOpenSession() == null
97
+       || $player->getOpenSession()->getRolePeriod() == null) {
98
+    echo "Player invalid\n";
99
+    return;
100
+   }
101
+
102
+
103
+   $victimID = $victim->getOpenSession()->getRolePeriod()->getID();
104
+   $playerID = $player->getOpenSession()->getRolePeriod()->getID();
105
+   $location = s($props['attacker_position']);
106
+   $weapon = Weapon::getID($props['weapon']);
107
+
108
+   $sql  = 'INSERT INTO events (event_timestamp, roleperiod_id, event_type, ';
109
+   $sql .= 'event_location, event_arg, event_victim, event_weapon) VALUES (';
110
+   $sql .= 'FROM_UNIXTIME(' . $date . '), ' . $playerID . ', \'object killed\', ';
111
+   $sql .= '\'' . $location . '\', \'' . $arg . '\', ' . $victimID . ', ';
112
+   $sql .= $weapon . ')';
113
+   mysql_query($sql) or die(mysql_error());
114
+
115
+   $player->addScore( 2);
116
+   $victim->addScore(-1);
117
+  }
118
+
119
+  private function builtObject($date, $player, $line) {
120
+   $props = Parser::parseProps($line);
121
+
122
+   if ($player == null || $player->getOpenSession() == null
123
+       || $player->getOpenSession()->getRolePeriod() == null) {
124
+    echo "Player invalid\n";
125
+    return;
126
+   }
127
+
128
+   $sql  = 'INSERT INTO events (event_timestamp, roleperiod_id, event_type, ';
129
+   $sql .= 'event_arg, event_location) VALUES (FROM_UNIXTIME(' . $date . '), ';
130
+   $sql .= $player->getOpenSession()->getRolePeriod()->getID() . ', ';
131
+   $sql .= '\'object built\', \'' . s($props['object']) . '\', \'';
132
+   $sql .= s($props['position']) . '\')';
133
+   $res  = mysql_query($sql);
134
+
135
+   $player->addScore(2);
136
+  }
137
+
138
+  private function assist($date, $player, $line) {
139
+   $line = substr($line, 8); // against
140
+   $victim = Parser::parsePlayer($line);
141
+   $victim = Player::getBySteamID($victim['steamid']);
142
+   $props = Parser::parseProps($line);
143
+
144
+   if ($victim == null || $victim->getOpenSession() == null
145
+       || $victim->getOpenSession()->getRolePeriod() == null) {
146
+    echo "Victim invalid\n";
147
+    return;
148
+   }
149
+
150
+   if ($player == null || $player->getOpenSession() == null
151
+       || $player->getOpenSession()->getRolePeriod() == null) {
152
+    echo "Player invalid\n";
153
+    return;
154
+   }
155
+
156
+   $sql  = 'SELECT kill_id FROM kills WHERE kill_victim = ';
157
+   $sql .= $victim->getOpenSession()->getRolePeriod()->getID() . ' ORDER BY kill_timestamp DESC LIMIT 0,1';
158
+   $res  = mysql_query($sql);
159
+
160
+   if (mysql_num_rows($res) == 0) {
161
+    echo "Couldn't find kill for assist\n";
162
+    return;
163
+   }
164
+
165
+   $num  = mysql_result($res, 0);
166
+
167
+   $sql  = 'UPDATE kills SET kill_assist = ';
168
+   $sql .= $player->getOpenSession()->getRolePeriod()->getID();
169
+   $sql .= ', kill_assist_position = \'' . $props['assister_position'] . '\' WHERE kill_id = ' . $num;
170
+   $res  = mysql_query($sql);
171
+
172
+   $score = ($victim->getScore() / $player->getScore()) 
173
+                 * PlayerClass::getModifier($victim->getOpenSession()->getRolePeriod()->getClass(),
174
+                                            $player->getOpenSession()->getRolePeriod()->getClass());
175
+   $player->addScore($score);
176
+  }
177
+
178
+  private function chargeDeployed($date, $player) {
179
+   if ($player == null || $player->getOpenSession() == null
180
+       || $player->getOpenSession()->getRolePeriod() == null) {
181
+    echo "Player invalid\n";
182
+    return;
183
+   }
184
+
185
+   $sql  = 'INSERT INTO events (event_timestamp, roleperiod_id, event_type) ';
186
+   $sql .= 'VALUES (FROM_UNIXTIME(' . $date . '), ' . $player->getOpenSession()->getRolePeriod()->getID() . ', \'ubercharge\')';
187
+   $res = mysql_query($sql);
188
+
189
+   $player->addScore(2);
190
+  }
191
+
192
+  private function domination($date, $player, $line, $type) {
193
+   $line = substr($line, 8); // against
194
+
195
+   $victim = Parser::parsePlayer($line);
196
+   $victim = Player::getBySteamID($victim['steamid']);
197
+
198
+   if ($victim == null || $victim->getOpenSession() == null
199
+       || $victim->getOpenSession()->getRolePeriod() == null) {
200
+    echo "Victim invalid\n";
201
+    return;
202
+   }
203
+
204
+   if ($player == null || $player->getOpenSession() == null
205
+       || $player->getOpenSession()->getRolePeriod() == null) {
206
+    echo "Player invalid\n";
207
+    return;
208
+   }
209
+
210
+   $sql  = 'INSERT INTO events (event_timestamp, roleperiod_id, event_type, ';
211
+   $sql .= 'event_victim) VALUES (FROM_UNIXTIME(' . $date . '), ';
212
+   $sql .= $player->getOpenSession()->getRolePeriod()->getID() . ', \'' . $type;
213
+   $sql .= '\', ' . $victim->getOpenSession()->getRolePeriod()->getID() . ')';
214
+   $res = mysql_query($sql);
215
+
216
+   $player->addScore(5);
217
+  }
218
+
219
+ }
220
+
221
+?>

+ 15
- 0
inc/parser/handlers/world.php View File

@@ -0,0 +1,15 @@
1
+<?PHP
2
+
3
+ class WorldHandler {
4
+
5
+  /*-------------------------------------------------------------------------*\
6
+   * Handles world events.                                                   *
7
+  \*-------------------------------------------------------------------------*/
8
+
9
+  public function parseLine($date, $player, $playerdetails, $line) {
10
+   // TODO: Implement me
11
+  }
12
+
13
+ }
14
+
15
+?>

+ 140
- 0
inc/parser/parser.php View File

@@ -0,0 +1,140 @@
1
+<?PHP
2
+
3
+ function sqr($x) {
4
+  return $x * $x;
5
+ }
6
+
7
+ require_once(dirname(dirname(__FILE__)) . '/player.php');
8
+
9
+ class Parser {
10
+
11
+  private static $handlers = array();
12
+  private static $daemon = null;
13
+
14
+  public static function setDaemon(&$daemon) {
15
+   self::$daemon =& $daemon;
16
+  }
17
+
18
+  public static function hasDaemon() {
19
+   return self::$daemon != null;
20
+  }
21
+
22
+  public static function getDaemon() {
23
+   return self::$daemon;
24
+  }
25
+
26
+  public static function parseDate(&$line) {
27
+   $index = strpos($line, ': ');
28
+   $date = substr($line, 0, $index);
29
+   $line = substr($line, $index + 2);
30
+   return strtotime(str_replace(' - ', ' ', $date));
31
+  }
32
+
33
+  public static function parsePlayerString(&$line) {
34
+   if (preg_match('/^"(.*?<[0-9]+><(BOT|Console|STEAM_.*?)><.*?>)"/', $line, $m)) {
35
+    $line = substr($line, strlen($m[0]) + 1);
36
+    return $m[1];
37
+   }
38
+
39
+   if (!preg_match('/^".*?" = .*$/', $line)) {
40
+    echo "Unable to parse player string: $line";
41
+   }
42
+
43
+   return self::parseString($line);
44
+  }
45
+
46
+  public static function parseString(&$line) {
47
+   $index = strpos($line, '"', 1);
48
+   $data = substr($line, 1, $index - 1);
49
+   $line = substr($line, $index + 2);
50
+   return $data;
51
+  }
52
+
53
+  public static function parsePlayer(&$line, $fromLine = true) {
54
+   if ($fromLine) {
55
+    $player = self::parsePlayerString($line);
56
+   } else {
57
+    $player = $line;
58
+   }
59
+
60
+   if (preg_match('((.*?)<([0-9]+)><([A-Za-z_:0-9]+)><([A-Za-z]*)>)', $player, $m)) {
61
+    return array('alias' => $m[1], 'uid' => $m[2], 'steamid' => $m[3], 'team' => $m[4]);
62
+   } else {
63
+    // Unable to parse player
64
+    return null;
65
+   }
66
+  }
67
+
68
+  public static function parseProps(&$line) {
69
+   preg_match_all('(\((.*?) "(.*?)"\))', $line, $matches, PREG_SET_ORDER);
70
+   $res = array();
71
+
72
+   foreach ($matches as $match) {
73
+    $res[$match[1]] = $match[2];
74
+   }
75
+
76
+   if (isset($res['attacker_position']) && isset($res['victim_position'])) {
77
+    // Calculate distance
78
+    $a = explode(" ", $res['attacker_position']); 
79
+    $v = explode(" ", $res['victim_position']);
80
+
81
+    $res['distance'] = sqrt(sqr($a[0] - $v[0]) + sqr($a[1] - $v[1]) + sqr($a[2] - $v[2]));
82
+   }
83
+
84
+   return $res;
85
+  }
86
+
87
+  public static function parseLine($line) {
88
+   if ($line[0] == 'L') {
89
+    $line = substr($line, 2);
90
+   } else if (substr($line, 4, 2) == 'RL') {
91
+    $line = substr($line, 7);
92
+   } else {
93
+    // TODO: log me 
94
+    return; 
95
+   }
96
+
97
+   $date = self::parseDate($line);
98
+   $player = null;
99
+
100
+   // Broken admin mod
101
+   if (substr($line, 0, 2) == 'L ') {
102
+    $line = substr($line, 2);
103
+    $date = self::parseDate($line);
104
+   }
105
+
106
+   if ($line[0] == '"') {
107
+    // Player event
108
+
109
+    if (($player = self::parsePlayer($line)) == null) {
110
+     // Or not?
111
+     return;
112
+    }
113
+   }
114
+
115
+   $parts = explode(' ', $line);
116
+   $handler = strtolower(str_replace(',', '', $parts[0]));
117
+
118
+   if (isset(self::$handlers[$handler])) {
119
+    self::$handlers[$handler]->parseLine($date, Player::getBySteamID($player['steamid']), $player, implode(' ', array_slice($parts, 1)));
120
+   } else {
121
+    // TODO: log me
122
+    return;
123
+   }
124
+  }
125
+
126
+  public static function init() {
127
+   foreach (glob(dirname(__FILE__) . '/handlers/*.php') as $file) {
128
+    require($file);
129
+    
130
+    $handler = basename($file, '.php');
131
+    $name = str_replace(' ', '', ucwords(str_replace('_', ' ', $handler))) . 'Handler';
132
+    self::$handlers[$handler] = new $name();
133
+   }
134
+  }
135
+
136
+ }
137
+
138
+ Parser::init();
139
+
140
+?>

+ 109
- 0
inc/player.php View File

@@ -0,0 +1,109 @@
1
+<?PHP
2
+
3
+ require_once(dirname(__FILE__) . '/database.php');
4
+ require_once(dirname(__FILE__) . '/class.php');
5
+
6
+ class Player {
7
+
8
+  private static $players = array();
9
+
10
+  private $id;
11
+  private $steamid;
12
+  private $score;
13
+  private $session = null;
14
+
15
+  private function __construct($id, $steamid, $score, $session = null) {
16
+   $this->id = $id;
17
+   $this->steamid = $steamid;
18
+   $this->score = $score;
19
+
20
+   if ($session != null) {
21
+    $this->session = new Session($this, $session);
22
+   }
23
+  }
24
+
25
+  public function getKPD() {
26
+   $sql  = 'SELECT SUM(roleperiod_kills)/SUM(roleperiod_deaths) AS kpd FROM ';
27
+   $sql .= 'sessions NATURAL JOIN roleperiods WHERE player_id = ' . $this->id;
28
+   $res  = mysql_query($sql);
29
+   $row  = mysql_fetch_assoc($res);
30
+   return $row['kpd'];
31
+  }
32
+
33
+  public function getScore() {
34
+   return $this->score;
35
+  }
36
+
37
+  public function addScore($score) {
38
+   $this->score += $score;
39
+
40
+   $sql = 'UPDATE players SET player_score = ' . $this->score . ' WHERE player_id = ' . $this->id;
41
+   $res = mysql_query($sql);
42
+  }
43
+
44
+  public function getID() {
45
+   return $this->id;
46
+  }
47
+
48
+  public function getSteamID() {
49
+   return $this->steamid;
50
+  }
51
+
52
+  public function getOpenSession() {
53
+   return $this->session;
54
+  }
55
+
56
+  public function closeSession($timestamp) {
57
+   if ($this->session == null) {
58
+    // Do nothing - broken? 
59
+   } else {
60
+    $this->session->close($timestamp);
61
+    $this->session = null;
62
+   }
63
+  }
64
+
65
+  public function openSession($timestamp, $uid, $alias) {
66
+   if ($this->session != null) { $this->closeSession($timestamp); }
67
+   
68
+   $this->session = new Session($this, null, $timestamp + 1, $uid, $alias);
69
+  }
70
+
71
+  public static function getBySteamID($steamid) {
72
+   if (isset(self::$players[$steamid])) {
73
+    // We already have one cached.
74
+    return self::$players[$steamid];
75
+   }
76
+
77
+   $sql = 'SELECT player_id, player_score FROM players WHERE player_steamid = \'' . s($steamid) . '\'';
78
+   $res = mysql_query($sql);
79
+
80
+   if (mysql_num_rows($res) > 0) {
81
+    // Player already known, grab ID from db.
82
+
83
+    $row = mysql_fetch_assoc($res);
84
+    $id = $row['player_id'];
85
+    $score = $row['player_score']; 
86
+    $session = null;
87
+
88
+    // Check for open sessions
89
+    $sql = 'SELECT session_id FROM sessions WHERE player_id = ' . $id . ' AND session_endtime = \'0000-00-00\'';
90
+    $res = mysql_query($sql);
91
+
92
+    if (mysql_num_rows($res) > 0) {
93
+     $session = (int) mysql_result($res, 0);
94
+    }
95
+    
96
+    return self::$players[$steamid] = new Player($id, $steamid, $score, $session);
97
+   } else {
98
+    // New player, insert them into the db.
99
+
100
+    $sql = 'INSERT INTO players (player_steamid) VALUES (\'' . s($steamid) . '\')';
101
+    $res = mysql_query($sql);
102
+
103
+    return self::$players[$steamid] = new Player(mysql_insert_id(), $steamid, 1000);
104
+   }
105
+  }
106
+
107
+ }
108
+
109
+?>

+ 68
- 0
inc/rcon.class.php View File

@@ -0,0 +1,68 @@
1
+<?PHP
2
+
3
+ class Rcon {
4
+
5
+  const SERVERDATA_AUTH = 3; 
6
+  const SERVERDATA_EXECCOMMAND = 2;
7
+
8
+  private $host, $port, $password;
9
+  private $request = 0;
10
+  private $socket;
11
+
12
+  public function __construct($host, $port, $password) {
13
+   $this->host = $host;
14
+   $this->port = $port;
15
+   $this->password = $password;
16
+
17
+   if (($this->socket = fsockopen($this->host, $this->port)) === false) {
18
+    die("Unable to create RCON connection to server!\n");
19
+   }
20
+
21
+   $this->send(self::SERVERDATA_AUTH, $password);
22
+   $this->receive();
23
+  }
24
+
25
+  public function getStatus() {
26
+   $this->send(self::SERVERDATA_EXECCOMMAND, 'status');
27
+   $data = $this->receive();
28
+   return $data['string1'];
29
+  }
30
+
31
+  public function execute($command) {
32
+   $this->send(self::SERVERDATA_EXECCOMMAND, $command);
33
+  }
34
+
35
+  private function send($type, $string1, $string2 = "") {
36
+   $data = pack('V3a' . (strlen($string1) + 1) . 'a' . (strlen($string2) + 1),
37
+		strlen($string1 . $string2) + 10, ++$this->request, $type, $string1, $string2); 
38
+   fputs($this->socket, $data);
39
+  }
40
+
41
+  private function receive() {
42
+   $size = unpack('V', fread($this->socket, 4));
43
+   $size = $size[1];
44
+   $data = fread($this->socket, $size);
45
+   $data = unpack('Vid/Vresponse/a*string1/a*string2', $data);
46
+
47
+   if ($data['id'] == -1) {
48
+    throw new Exception('Authentication failed');
49
+   } else if ($data['id'] < $this->request) {
50
+    return $this->receive();
51
+   }
52
+
53
+   return $data;
54
+  }
55
+
56
+  private function hexdump($data) {
57
+   $data = unpack('H*', $data);
58
+   $data = $data[1];
59
+   for ($i = 0; $i < strlen($data); $i += 2) {
60
+    if ($i % 32 == 0) { echo "\n"; } else if ($i % 32 == 0) { echo ' '; }
61
+    echo substr($data, $i, 2) . ' ';
62
+   } 
63
+   echo "\n";
64
+  }
65
+
66
+ }
67
+
68
+?>

+ 52
- 0
inc/roleperiod.php View File

@@ -0,0 +1,52 @@
1
+<?PHP
2
+
3
+require_once(dirname(__FILE__) . '/class.php');
4
+
5
+class RolePeriod {
6
+
7
+ private static $teams = array('Red' => 1, 'Blue' => 2, 'Spectator' => 3, '' => 0, 'Unassigned' => 0);
8
+
9
+ private $id;
10
+ private $class;
11
+
12
+ public function __construct(Player &$player, Session &$session, $id = null, $timestamp = null, $team = null, $class = null) {
13
+  if ($id == null) {
14
+   assert($timestamp != null && $team != null && $class != null);
15
+
16
+   $team = self::$teams[$team];
17
+   $class = $this->class = PlayerClass::getID($class);
18
+
19
+   $sql  = 'INSERT INTO roleperiods (session_id, player_id, roleperiod_team, class_id, roleperiod_starttime)';
20
+   $sql .= ' VALUES(' . $session->getID() . ', ' . $player->getID() . ', ' . $team . ', ' . $class . ', FROM_UNIXTIME(' . $timestamp . '))';
21
+   $res  = mysql_query($sql);
22
+
23
+   $this->id = mysql_insert_id();
24
+  } else {
25
+   $this->id = $id;
26
+
27
+   $sql = 'SELECT class_id FROM roleperiods WHERE roleperiod_id = ' . $id;
28
+   $res = mysql_query($sql);
29
+   $this->class = mysql_result($res, 0);
30
+  }
31
+ }
32
+
33
+ public function getID() {
34
+  return $this->id;
35
+ }
36
+
37
+ public function getClass() {
38
+  return $this->class;
39
+ }
40
+
41
+ public function close($timestamp) {
42
+  $sql  = 'UPDATE roleperiods SET roleperiod_endtime = FROM_UNIXTIME(' . $timestamp . '),';
43
+  $sql .= ' roleperiod_kills = (SELECT COUNT(*) FROM kills WHERE kill_killer = roleperiod_id),';
44
+  $sql .= ' roleperiod_deaths = (SELECT COUNT(*) FROM kills WHERE kill_victim = roleperiod_id),';
45
+  $sql .= ' roleperiod_assists = (SELECT COUNT(*) FROM kills WHERE kill_assist = roleperiod_id)';
46
+  $sql .= ' WHERE roleperiod_id = ' . $this->id;
47
+  $res = mysql_query($sql);
48
+ }
49
+
50
+}
51
+
52
+?>

+ 62
- 0
inc/server.class.php View File

@@ -0,0 +1,62 @@
1
+<?PHP
2
+
3
+ class Server {
4
+
5
+  private $host, $port;
6
+  private $socket;
7
+
8
+  public function __construct($host, $port) {
9
+   $this->host = $host;
10
+   $this->port = $port;
11
+
12
+   $this->socket = fsockopen('udp://' . $this->host, $this->port);
13
+   stream_set_timeout($this->socket, 5);
14
+  }
15
+
16
+  public function getInfo() {
17
+   $this->send(pack('LAA*x', -1, 'T', 'Source Engine Query'));
18
+   return $this->receiveInfo();
19
+  }
20
+
21
+  private function send($data) {
22
+   fwrite($this->socket, $data);
23
+  }
24
+
25
+  private function readUntilNull() {
26
+   do {
27
+    $data .= $read = fread($this->socket, 1);
28
+    $info = stream_get_meta_data($this->socket); 
29
+
30
+    if ($info['timed_out']) {
31
+     throw new Exception('Socket time out');
32
+    }
33
+
34
+   } while ($read !== false && $read !== "\0");
35
+
36
+   return $data;
37
+  }
38
+
39
+  private function receiveInfo() {
40
+   $res = unpack('Lheader/ctype/cversion/a*name', $this->readUntilNull());
41
+   $res = array_merge($res, unpack('a*map', $this->readUntilNull()));
42
+   $res = array_merge($res, unpack('a*game_dir', $this->readUntilNull()));
43
+   $res = array_merge($res, unpack('a*game_name', $this->readUntilNull()));
44
+   $res = array_merge($res, unpack('vappid/cplayers/cmaxplayers/cbots/cdedicated/cos/cpassword/csecure', fread($this->socket, 9)));
45
+   $res = array_merge($res, unpack('a*game_version', $this->readUntilNull()));
46
+
47
+   return $res;
48
+  }
49
+
50
+  private function hexdump($data) {
51
+   $data = unpack('H*', $data);
52
+   $data = $data[1];
53
+   for ($i = 0; $i < strlen($data); $i += 2) {
54
+    if ($i % 32 == 0) { echo "\n"; } else if ($i % 32 == 0) { echo ' '; }
55
+    echo substr($data, $i, 2) . ' ';
56
+   } 
57
+   echo "\n";
58
+  }
59
+
60
+ }
61
+
62
+?>

+ 108
- 0
inc/session.php View File

@@ -0,0 +1,108 @@
1
+<?PHP
2
+
3
+ require_once(dirname(__FILE__) . '/database.php');
4
+ require_once(dirname(__FILE__) . '/group.php');
5
+ require_once(dirname(__FILE__) . '/roleperiod.php');
6
+ require_once(dirname(__FILE__) . '/player.php');
7
+
8
+ class Session {
9
+
10
+  private $id;
11
+  private $player;
12
+  private $alias;
13
+
14
+  private $roleperiod;
15
+
16
+  public function __construct(Player &$player, $id = null, $timestamp = null, $uid = null, $alias = null) {
17
+   $this->player =& $player;
18
+
19
+   if ($id == null) {
20
+    assert($timestamp != null && $uid != null && $alias != null);
21
+
22
+    $sql  = 'INSERT INTO sessions (session_starttime, player_id, session_uid, session_alias, game_id) VALUES (';
23
+    $sql .= 'FROM_UNIXTIME(' . $timestamp . '), ' . $player->getID() . ', ' . $uid . ', ';
24
+    $sql .= '\'' . s($alias) . '\', ' . Game::getCurrent()->getID() . ')';
25
+    $res  = mysql_query($sql);
26
+
27
+    $this->id = mysql_insert_id();
28
+    $this->alias = $alias;
29
+    $this->checkGroups();
30
+   } else {
31
+    $this->id = $id;
32
+
33
+    $sql = 'SELECT roleperiod_id FROM roleperiods WHERE session_id = ' . $id . ' AND roleperiod_endtime = \'0000-00-00\'';
34
+    $res = mysql_query($sql);
35
+    
36
+    if (mysql_num_rows($res) > 0) {
37
+     $this->roleperiod = new RolePeriod($this->player, $this, (int) mysql_result($res, 0));
38
+    }
39
+
40
+    $sql = 'SELECT session_alias FROM sessions WHERE session_id = ' . $id;
41
+    $res = mysql_query($sql);
42
+    $this->alias = mysql_result($res, 0);
43
+   }
44
+  }
45
+
46
+  public function getAlias() {
47
+   return $this->alias;
48
+  }
49
+
50
+  protected function checkGroups() {
51
+   foreach ($this->getGroups() as $group) {
52
+    Group::getGroup($group)->ensureMember($this->player);
53
+   }
54
+  }
55
+
56
+  protected function getGroups() {
57
+   $results = array();
58
+
59
+   if (preg_match_all('/^\[.*?\]|\[.*?\]$/', $this->alias, $m)) {
60
+    $results = array_merge($results, $m[0]);
61
+   }
62
+
63
+   if (preg_match_all('/^\|.*?\||\|.*?\|$/', $this->alias, $m)) {
64
+    $results = array_merge($results, $m[0]);
65
+   }
66
+
67
+   if (preg_match('/@.*?$/', $this->alias, $m)) {
68
+    $results[] = $m[0];
69
+   }
70
+
71
+   return $results;
72
+  }
73
+
74
+  public function getID() {
75
+   return $this->id;
76
+  }
77
+
78
+  public function close($timestamp) {
79
+   $sql = 'UPDATE sessions SET session_endtime = FROM_UNIXTIME(' . $timestamp . ') WHERE session_id = ' . $this->id;
80
+   $res = mysql_query($sql);
81
+
82
+   if ($this->roleperiod != null) {
83
+    $this->roleperiod->close($timestamp);
84
+   }
85
+  }
86
+
87
+  public function getRolePeriod() {
88
+   return $this->roleperiod;
89
+  }
90
+
91
+  public function changeRole($timestamp, $team, $class) {
92
+   if ($this->roleperiod != null) {
93
+    $this->roleperiod->close($timestamp);
94
+   }
95
+
96
+   $this->roleperiod = new RolePeriod($this->player, $this, null, $timestamp, $team, $class);
97
+  }
98
+
99
+  public function changeTeam($timestamp, $team) {
100
+   if ($this->roleperiod != null) {
101
+    $this->roleperiod->close($timestamp);
102
+    $this->roleperiod = new RolePeriod($this->player, $this, null, $timestamp, $team, $this->roleperiod->getClass());
103
+   }
104
+  }
105
+
106
+ }
107
+
108
+?>

+ 34
- 0
inc/updateranks.php View File

@@ -0,0 +1,34 @@
1
+<?PHP
2
+
3
+ require_once(dirname(__FILE__) . '/database.php');
4
+
5
+ function updateRanks() {
6
+  $sql = 'SELECT player_id, player_rank, player_score, SUM(roleperiod_kills) AS kills FROM players NATURAL JOIN roleperiods GROUP BY player_id ORDER BY player_score DESC';
7
+  $res = mysql_query($sql);
8
+
9
+  echo "Updating player ranks... ";
10
+
11
+  $i = $updated = $limited = 0;
12
+  $left = array();
13
+
14
+  while ($row = mysql_fetch_assoc($res)) {
15
+   if ($row['kills'] < MIN_KILLS) {
16
+    $left[$row['player_id']] = $row['player_score'];
17
+   } else {
18
+    if ($row['player_rank'] != ++$i) {
19
+     mysql_query('UPDATE players SET player_rank = ' . $i . ' WHERE player_id = ' . $row['player_id']);
20
+     $updated++;
21
+    }
22
+   }
23
+  }
24
+
25
+  arsort($left);
26
+  foreach ($left as $player => $score) {
27
+   mysql_query('UPDATE players SET player_rank = ' . (++$i) . ' WHERE player_id = ' . $player);
28
+   $updated++; $limited++;
29
+  }
30
+
31
+  echo "Done.\n\t$updated player records updated.\n\t$limited players rank-limited by their number of kills.\n";
32
+ }
33
+
34
+?>

+ 50
- 0
inc/weapon.php View File

@@ -0,0 +1,50 @@
1
+<?PHP
2
+
3
+ require_once(dirname(__FILE__) . '/database.php');
4
+
5
+ class Weapon {
6
+
7
+  private static $weapons = array();
8
+
9
+  public static function getID($name) {
10
+   if (!isset(self::$weapons[$name])) {
11
+    $ename = '\'' . s($name) . '\'';
12
+
13
+    $sql  = 'INSERT INTO weapons (weapon_name, weapon_modifier, weapon_displayname) VALUES (';
14
+    $sql .= $ename . ', 1, ' . $ename . ')';
15
+    $res  = mysql_query($sql);
16
+
17
+    self::$weapons[$name] = array(
18
+     'weapon_name' => $name,
19
+     'weapon_modifier' => 1,
20
+     'weapon_displayname' => $name,
21
+     'weapon_id' => mysql_insert_id()
22
+    ); 
23
+
24
+    echo "** Added new weapon: $name\n";
25
+   }
26
+
27
+   return self::$weapons[$name]['weapon_id'];
28
+  }
29
+
30
+  public static function getModifier($name) {
31
+   if (!isset(self::$weapons[$name])) {
32
+    die("No such weapon: $name");
33
+   }
34
+
35
+   return self::$weapons[$name]['weapon_modifier'];
36
+  }
37
+
38
+  public static function init() {
39
+   $sql = 'SELECT weapon_id, weapon_name, weapon_modifier, weapon_displayname FROM weapons';
40
+   $res = mysql_query($sql);
41
+   while ($row = mysql_fetch_assoc($res)) {
42
+    self::$weapons[$row['weapon_name']] = $row;
43
+   }
44
+  }
45
+
46
+ }
47
+
48
+ Weapon::init();
49
+
50
+?>

+ 69
- 0
mapchange.php View File

@@ -0,0 +1,69 @@
1
+#!/usr/bin/php -q
2
+<?PHP
3
+
4
+ require_once(dirname(__FILE__) . '/config.php');
5
+ require_once(dirname(__FILE__) . '/inc/rcon.class.php');
6
+ require_once(dirname(__FILE__) . '/inc/database.php');
7
+
8
+ define('SCRIPT_HEAD1', 'mapchange.php v0.1');
9
+ define('SCRIPT_HEAD3', 'Changes maps on an empty server');
10
+
11
+ require(dirname(__FILE__) . '/inc/cliheader.php');
12
+
13
+ $sql = 'SELECT server_id, server_ip, server_port, server_rcon FROM servers';
14
+ $res = mysql_query($sql);
15
+
16
+ while ($row = mysql_fetch_assoc($res)) {
17
+
18
+  echo str_pad($row['server_ip'] . ':' . $row['server_port'], 80, '-', STR_PAD_BOTH);
19
+  echo "\n\n";
20
+
21
+  if (empty($row['server_rcon'])) {
22
+   echo "No RCON password specified. Skipping.\n";
23
+   continue;
24
+  }
25
+
26
+  $rcon = new Rcon($row['server_ip'], $row['server_port'], $row['server_rcon']);
27
+  foreach (explode("\n", $rcon->getStatus()) as $line) {
28
+   if (preg_match('/^players : ([0-9]+)/', $line, $m)) {
29
+    $players = (int) $m[1];
30
+   } else if (preg_match('/^map\s*: ([^\s]+) at/', $line, $m)) {
31
+    $map = $m[1];
32
+   } else if (preg_match('/^hostname\s*:\s*(.*?)\s*$/', $line, $m)) {
33
+    $hostname = $m[1];
34
+   }
35
+  }
36
+
37
+  if (isset($hostname)) {
38
+   $sql = 'UPDATE servers SET server_name = \'' . s($hostname) . '\' WHERE server_id = ' . $row['server_id'];
39
+   mysql_query($sql);
40
+  }
41
+
42
+  if (!isset($players)) {
43
+   echo "Could not determine the number of players. Skipping.\n";
44
+   continue;
45
+  } else if ($players > 0) {
46
+   echo "Players on the server; not interfering.\n";
47
+   continue;
48
+  }
49
+
50
+  $sql = 'SELECT map_name, SUM(TIMESTAMPDIFF(MINUTE, session_starttime, session_endtime)) AS time FROM maps NATURAL JOIN games NATURAL JOIN sessions GROUP BY map_name ORDER BY time DESC LIMIT 0,10';
51
+  $res = mysql_query($sql);
52
+
53
+  $maps = array();
54
+  while ($row = mysql_fetch_assoc($res)) {
55
+   if ($row['map_name'] == $map) { continue; }
56
+
57
+   $maps[] = $row['map_name'];
58
+  }
59
+
60
+  echo "Currently $players players on map $map.\n";
61
+  $nextmap = $maps[rand(0, count($maps)-1)];
62
+  echo "Selected map $nextmap. Changing... ";
63
+  $rcon->execute('changelevel ' . $nextmap);
64
+  echo "Done\n\n";
65
+ }
66
+
67
+ echo str_repeat('-', 80), "\n";
68
+
69
+?>

+ 24
- 0
updateservers.php View File

@@ -0,0 +1,24 @@
1
+#!/usr/bin/php -q
2
+<?PHP
3
+
4
+ require_once(dirname(__FILE__) . '/inc/database.php');
5
+ require_once(dirname(__FILE__) . '/inc/server.class.php');
6
+
7
+ define('SCRIPT_HEAD1', 'updateservers.php v0.1');
8
+ define('SCRIPT_HEAD3', 'Updates server information');
9
+
10
+ require(dirname(__FILE__) . '/inc/cliheader.php');
11
+
12
+ $sql = 'SELECT server_id, server_ip, server_port FROM servers';
13
+ $res = mysql_query($sql);
14
+ while ($row = mysql_fetch_assoc($res)) {
15
+  try {
16
+   $server = new Server($row['server_ip'], $row['server_port']);
17
+   $info = $server->getInfo();
18
+   mysql_query('UPDATE servers SET server_name = \'' . s($info['name']) . '\' WHERE server_id = ' . $row['server_id']);
19
+  } catch (Exception $ex) {
20
+   // TODO: Record the fact that it's down
21
+  }
22
+ }
23
+
24
+?>

+ 14
- 0
www/404.php View File

@@ -0,0 +1,14 @@
1
+<?PHP
2
+
3
+ define('TITLE', 'Error 404: Page not found');
4
+ require('inc/header.php');
5
+
6
+ echo '<h2>Error 404: Page not found</h2>';
7
+ echo '<p>Sorry, the page you were looking for could not be found.  ';
8
+
9
+ echo 'If you typed the address to this page manually, please check your spelling and try again.  ';
10
+ echo 'You can also try to locate the page you are looking for using the menu above.</p>';
11
+
12
+ require('inc/footer.php');
13
+
14
+?>

+ 66
- 0
www/about.php View File

@@ -0,0 +1,66 @@
1
+<?PHP
2
+
3
+ define('TITLE', 'About');
4
+ require('inc/header.php');
5
+
6
+?>
7
+ <h2>About TF2 Stats</h2>
8
+ <div class="left">
9
+  <h3>About</h3>
10
+  <p>
11
+   TF2 Stats is a web application which creates statistics about
12
+   <a href="http://www.teamfortress.com/">Team Fortress 2</a> servers and
13
+   their players. It is being developed by
14
+   <a href="http://chris.smith.name/">Chris Smith</a>.
15
+  </p>
16
+  <p>
17
+   TF2 Stats works by analysing the log files produced by TF2 servers. It
18
+   records all events that take place (such as kills, team changes, captures,
19
+   and many more), and then analyses those events to produce interesting
20
+   statistics.
21
+  </p>
22
+  <h3>Get in touch</h3>
23
+  <p>
24
+   If you have spotted a bug, want to request a feature, or run a server and want to
25
+   be part of TF2 Stats, please feel free to get in touch. TF2 Stats is developed
26
+   and operated by Chris Smith, whose contact details are available on his
27
+   <a href="http://chris.smith.name/">personal website</a>.
28
+  </p>
29
+  <h3>Frequently Asked Questions</h3>
30
+  <dl>
31
+   <dt>Q. What happens if I change name?</dt>
32
+   <dd>A. TF2 Stats tracks players by their Steam IDs, rather than by their
33
+          aliases, so you can change your nickname as often as you like
34
+          and we'll keep track of you.</dd>
35
+   <dt>Q. How is my score calculated?</dt>
36
+   <dd>A. Your score starts out at 1,000, and increases or decreases with
37
+          each event that you are involved in. Most changes come from kills,
38
+          where the killer takes a certain amount of points from their victim,
39
+          based on the difference between their scores, their classes, and
40
+          the weapons used.
41
+   </dd>
42
+  </dl>
43
+ </div>
44
+ <div class="right">
45
+  <h3>Recent changes</h3>
46
+  <ul>
47
+   <li>Improved the <a href="weapons.php">weapons</a> page to include more information and a chart</li>
48
+   <li>Added a 'Terrific Taunter' award</li>
49
+   <li>Added a very basic <a href="groups.php">groups</a> page</li>
50
+   <li>Created an <a href="about.php">about</a> page which gives a brief overview of TF2 Stats and shows a changelog</li>
51
+   <li>Updated server names for Spec-Ops servers</li>
52
+   <li>Enabled automatic clan detection in the importer</li>
53
+   <li>Fixed issue with Firefox causing the menu to wrap when selecting or clicking links</li>
54
+   <li>Added a new page for groups (clans) — <a href="group.php?group=160">see an example here</a></li>
55
+   <li>Group affiliations are now displayed on player pages (<a href="player.php?id=3778">example</a>)</li>
56
+   <li>Implemented the 'award winners' panel on the <a href="players.php">players</a> page</li>
57
+   <li>Added missing weapon images for heavy unlockables and some pyro deflection types</li>
58
+   <li>Created the <a href="weapons.php">weapons</a> page, which shows the number of kills by every known weapon</li>
59
+   <li>Added logs from two new servers operated by <a href="http://www.special-ops.us/">[Spec-Ops]</a></li>
60
+  </ul>
61
+ </div>
62
+<?PHP
63
+
64
+ require('inc/footer.php');
65
+
66
+?>

+ 55
- 0
www/awards.php View File

@@ -0,0 +1,55 @@
1
+<?PHP
2
+
3
+ require_once('inc/config.php');
4
+ require_once('inc/awards.php');
5
+ require_once(STATS_DIR . '/inc/database.php');
6
+
7
+ if (!ENABLE_AWARDS) {
8
+  require('404.php'); 
9
+  exit;
10
+ }
11
+
12
+ define('TITLE', 'Awards');
13
+ require_once('inc/header.php');
14
+
15
+
16
+ echo '<h2>Awards</h2>';
17
+
18
+ $sql = 'SELECT award_id, award_name, award_displayname, award_type, award_field FROM awards ORDER BY award_displayname';
19
+ $res = mysql_query($sql);
20
+
21
+ while ($row = mysql_fetch_assoc($res)) {
22
+  echo '<h3 class="award">';
23
+  echo '<img src="', sprintf(URL_AWARD, $row['award_name']), '" alt="', $row['award_displayname'], '">';
24
+  echo $row['award_displayname'];
25
+  echo '<span class="info"> &mdash; ' . getAwardDescription($row['award_type'], $row['award_field']) . '</span>';
26
+  echo '</h3>';
27
+  echo '<ul class="award">';
28
+
29
+  $sql2 = '
30
+	SELECT 	awardwinners.player_id,
31
+		session_alias,
32
+		winner_value,
33
+		UNIX_TIMESTAMP(winner_awarded) AS ts
34
+	FROM 	awardwinners 
35
+		NATURAL JOIN players
36
+		LEFT OUTER JOIN sessions ON players.player_id = sessions.player_id
37
+	WHERE 	award_id = ' . $row['award_id'] . '
38
+	GROUP BY winner_awarded
39
+	ORDER BY winner_awarded DESC
40
+ 	LIMIT 0,' . AWARD_NUMBER;
41
+  $res2 = mysql_query($sql2) or print(mysql_error());
42
+
43
+  while ($row2 = mysql_fetch_assoc($res2)) {
44
+   echo '<li>';
45
+   echo date('F jS', $row2['ts']), ': ';
46
+   echo '<a href="player.php?id=' . $row2['player_id'] . '">' . htmlentities($row2['session_alias'], ENT_COMPAT, 'UTF-8') . '</a>';
47
+   echo ' with ' . htmlentities($row2['winner_value']) . '</li>';
48
+  }
49
+
50
+  echo '</ul>';
51
+ }
52
+
53
+ require_once('inc/footer.php');
54
+
55
+?>

+ 71
- 0
www/class.php View File

@@ -0,0 +1,71 @@
1
+<?PHP
2
+
3
+ require_once('inc/config.php');
4
+ require_once('inc/ext.php');
5
+ require_once('inc/classestable.php');
6
+ require_once('inc/mostmaps.php');
7
+ require_once('inc/playertable.php');
8
+ require_once('inc/weaponslist.php');
9
+ require_once(STATS_DIR . '/inc/database.php');
10
+
11
+ $sql = 'SELECT class_id, class_displayname FROM classes WHERE class_name = \'' . s($_GET['class']) . '\'';
12
+ $res = mysql_query($sql);
13
+
14
+ if (mysql_num_rows($res) == 0) {
15
+  require('404.php'); 
16
+  exit;
17
+ }
18
+
19
+ $row = mysql_fetch_assoc($res);
20
+
21
+ define('CLASSID', $row['class_id']); 
22
+ define('NAME', $row['class_displayname']); 
23
+
24
+ define('TITLE', 'Class information :: ' . NAME);
25
+ require_once('inc/header.php');
26
+
27
+ echo '<h2>Class Information: ', NAME, '</h2>';
28
+ echo '<div class="left">';
29
+
30
+ echo '<h3>Most Played On</h3>';
31
+
32
+ showMostMaps('NATURAL JOIN roleperiods', 'class_id = ' . CLASSID);
33
+
34
+ echo '<h3 class="extra">Top Weapons</h3>';
35
+ showWeaponsList('INNER JOIN roleperiods ON roleperiod_id = kill_killer WHERE class_id = ' . CLASSID);
36
+ show_extra_map_info($_GET['map']);
37
+
38
+ echo '</div>';
39
+
40
+ echo '<div class="right"><h3>Top Players</h3>';
41
+
42
+ showPlayerTable('', 'class_id = ' . CLASSID, 10, true);
43
+
44
+ echo '<h3>Class Relationships</h3>';
45
+
46
+ $classes = array();
47
+ $sql = 'SELECT rp1.class_id, class_name, COUNT(*) as num FROM classes NATURAL JOIN roleperiods AS rp1 INNER JOIN kills AS k1 ON kill_killer = roleperiod_id INNER JOIN roleperiods AS rp2 ON kill_victim = rp2.roleperiod_id WHERE rp2.class_id = ' . CLASSID . ' GROUP BY class_id ORDER BY class_name';
48
+ $res = mysql_query($sql) or print(mysql_error());
49
+
50
+ while ($row = mysql_fetch_assoc($res)) {
51
+  $classes[$row['class_id']] = array(
52
+   'name' => $row['class_name'],
53
+   'data1' => $row['num']
54
+  );
55
+ }
56
+
57
+ $sql = 'SELECT rp1.class_id, class_name, COUNT(*) as num FROM classes NATURAL JOIN roleperiods AS rp1 INNER JOIN kills AS k1 ON kill_victim = roleperiod_id INNER JOIN roleperiods AS rp2 ON kill_killer = rp2.roleperiod_id WHERE rp2.class_id = ' . CLASSID . ' GROUP BY class_id ORDER BY class_name';
58
+ $res = mysql_query($sql) or print(mysql_error());
59
+
60
+ while ($row = mysql_fetch_assoc($res)) {
61
+  $classes[$row['class_id']]['data2'] = $row['num'];
62
+  $classes[$row['class_id']]['name'] = $row['class_name'];
63
+ }
64
+
65
+ showClassesTable($classes, 'Killer of ' . NAME, 'Victim of ' . NAME, true);
66
+
67
+ echo '</div>';
68
+
69
+ require_once('inc/footer.php');
70
+
71
+?>

+ 213
- 0
www/deathmap.php View File

@@ -0,0 +1,213 @@
1
+<?PHP
2
+
3
+ require_once('inc/config.php');
4
+ require_once('inc/deathmap.php');
5
+ require_once(STATS_DIR . '/inc/database.php');
6
+
7
+ if (!isset($_GET['map'])) {
8
+  require_once('404.php');
9
+  return; 
10
+ }
11
+
12
+ $sql  = 'SELECT map_id, map_name, MAX(UNIX_TIMESTAMP(game_endtime)) AS time FROM maps ';
13
+ $sql .= 'NATURAL JOIN games WHERE map_name = \'' . s($_GET['map']) . '\' GROUP BY map_name';
14
+ $res = mysql_query($sql);
15
+
16
+ if (mysql_num_rows($res) == 0) {
17
+  require_once('404.php');
18
+  return;
19
+ }
20
+
21
+ $row = mysql_fetch_assoc($res);
22
+
23
+ define('MAP_NAME', $row['map_name']);
24
+ define('MAP_ID', $row['map_id']);
25
+ define('MAP_FILE', sprintf(OVERVIEW_IMAGE, MAP_NAME));
26
+
27
+ if (!file_exists(MAP_FILE) || !isset($coords[MAP_NAME])) {
28
+  require_once('404.php');
29
+  return;
30
+ }
31
+
32
+ if (isset($_GET['killer'])) {
33
+  define('WHAT', 'killer');
34
+ } else if (isset($_GET['sentries'])) {
35
+  define('WHAT', 'sentry');
36
+ } else {
37
+  define('WHAT', 'victim');
38
+ }
39
+
40
+ if (isset($_GET['noteams'])) {
41
+  define('NOTEAMS', true);
42
+ }
43
+
44
+ define('PALE', isset($_GET['pale']) ? (max(1, (int) $_GET['pale'])) : 3);
45
+
46
+ define('ALPHA', isset($_GET['alpha']) ? min(max((int) $_GET['alpha'], 0), 127) : 0);
47
+
48
+ define('RADIUS', isset($_GET['radius']) ? max(1, (int) $_GET['radius']) : 15);
49
+
50
+ define('FILE_NAME', DM_CACHE . MAP_NAME . '.pale-' . PALE . '.radius-' . RADIUS . '.alpha-' . ALPHA . '.target-' . WHAT . (defined('NOTEAMS') ? '.noteams' : '') . '.jpg');
51
+
52
+ if (file_exists(FILE_NAME) && filemtime(FILE_NAME) > (int) $row['time']) {
53
+  // Cached version
54
+
55
+  header('Content-type: image/jpeg');
56
+  readfile(FILE_NAME);
57
+  exit;
58
+ }
59
+
60
+ // Our map co-ords, for convenience
61
+ list($x1,$y1,$i1,$j1,$x2,$y2,$i2,$j2,$x3,$y3,$i3,$j3) = $coords[MAP_NAME];
62
+
63
+ /* Simultaneous equestions for translation co-ordinates:
64
+  *
65
+  * x1 = a * i1 + b * j1 + c
66
+  * x2 = a * i2 + b * j2 + c
67
+  * x3 = a * i3 + b * j3 + c
68
+  *
69
+  * y1 = d * i1 + e * j1 + f ...
70
+  *
71
+  * x1 - x2 = a * (i1 - i2) + b * (j1 - j2)
72
+  * x2 - x3 = a * (i2 - i3) + b * (j2 - j3)
73
+  *       a = (x1 - x2 - b * (j1 - j2)) / (i1 - i2)
74
+  *         = (x2 - x3 - b * (j2 - j3)) / (i2 - i3)
75
+  *  
76
+  * (i2 - i3) (x1 - x2 - b * (j1 - j2)) = (i1 - i2) (x2 - x3 - b * (j2 - j3))
77
+  * (i2 - i3) (x1 - x2) - b * (j1 - j2) (i2 - i3) = (i1 - i2) (x2 - x3) - b * (j2 - j3) (i1 - i2)
78
+  * b (((j2 - j3) (i1 - i2)) - ((j1 - j2) (i2 - i3))) = (i1 - i2) (x2 - x3) - (i2 - i3) (x1 - x2) 
79
+  * b = ((i1 - i2) (x2 - x3) - (i2 - i3) (x1 - x2)) / ((j2 - j3) (i1 - i2) - (j1 - j2) (i2 - i3))
80
+  */
81
+ $i12 = $i1 - $i2;
82
+ $i23 = $i2 - $i3;
83
+ $j12 = $j1 - $j2;
84
+ $j23 = $j2 - $j3;
85
+
86
+ $b = ($i12 * ($x2 - $x3) - $i23 * ($x1 - $x2)) / ($j23 * $i12 - $j12 * $i23);
87
+ $a = (($x1 - $x2) - $b * $j12) / $i12;
88
+ $c = $x1 - $a * $i1 - $b * $j1;
89
+
90
+ if (abs($c - ($x3 - $a * $i3 - $b * $j3)) > 0.1) {
91
+  die('Inconsistency between x co-ord translations for 1 and 3');
92
+ } else if (abs($c - ($x2 - $a * $i2 - $b * $j2)) > 0.1) {
93
+  die('Inconsistency between x co-ord tranlsations for 1 and 2');
94
+ }
95
+
96
+ $e = ($i12 * ($y2 - $y3) - $i23 * ($y1 - $y2)) / ($j23 * $i12 - $j12 * $i23);
97
+ $d = (($y1 - $y2) - $e * $j12) / $i12;
98
+ $f = $y1 - $d * $i1 - $e * $j1;
99
+
100
+ if (abs($f - ($y3 - $d * $i3 - $e * $j3)) > 0.1) {
101
+  die('Inconsistency between y co-ord translations for 1 and 3');
102
+ } else if (abs($f - ($y2 - $d * $i2 - $e * $j2)) > 0.1) {
103
+  die('Inconsistency between y co-ord translations for 1 and 2');
104
+ }
105
+
106
+ function getScreenX($gameX, $gameY) {
107
+  global $a, $b, $c;
108
+  $res = ($a * $gameX + $b * $gameY + $c); 
109
+  return (int) $res;
110
+ }
111
+
112
+ function getScreenY($gameX, $gameY) {
113
+  global $d, $e, $f;
114
+  $res = ($d * $gameX + $e * $gameY + $f);
115
+  return (int) $res;
116
+ }
117
+
118
+ function checkCoords($set, $i1, $j1, $x1, $y1) {
119
+  global $a, $b, $c, $d, $e, $f;
120
+  if (abs(getScreenX($i1, $j1) - $x1) > 1 || abs(getScreenY($i1, $j1) - $y1) > 1) {
121
+   die("Co-ordinate mapping failed for set $set.<br>(i,j) = ($i1,$j1)<br>(x,y) = ($x1,$y1)<br>(ix,iy) = (" . getScreenX($i1, $j1) . "," . getScreenY($i1, $j1) . ")<br>a, b, c = $a, $b, $c<br>d, e, f = $d, $e, $f");
122
+  }
123
+ }
124
+
125
+ checkCoords(1, $i1, $j1, $x1, $y1);
126
+ checkCoords(2, $i2, $j2, $x2, $y2);
127
+ checkCoords(3, $i3, $j3, $x3, $y3);
128
+
129
+ $pixels = array();
130
+ $max = 0;
131
+
132
+ $im = imagecreatefrompng(MAP_FILE);
133
+ imagefill($im, 1, 1, imagecolorallocate($im, 0, 0, 0));
134
+ imagefill($im, imagesx($im) - 2, imagesy($im) - 2, imagecolorallocate($im, 0, 0, 0));
135
+
136
+ if (WHAT == 'killer' || WHAT == 'victim') {
137
+  $sql = 'SELECT kill_id, kill_killer_position, kill_victim_position, roleperiod_team FROM games NATURAL JOIN sessions NATURAL JOIN roleperiods INNER JOIN kills ON kill_killer = roleperiod_id WHERE map_id = ' . MAP_ID;
138
+  $loc = 'kill_' . WHAT . '_position';
139
+ } else {
140
+  $sql = 'SELECT roleperiod_team, event_location FROM games NATURAL JOIN sessions NATURAL JOIN roleperiods NATURAL JOIN events WHERE map_id = ' . MAP_ID;
141
+  $loc = 'event_location'; 
142
+ }
143
+
144
+ $res = mysql_query($sql);
145
+
146
+ while ($row = mysql_fetch_assoc($res)) {
147
+  list($x, $y, $z) = explode(' ', $row[$loc]);
148
+
149
+  $i = getScreenX($x, $y); $j = getScreenY($x, $y);
150
+  for ($n = max(0, $i - RADIUS); $n < min(imagesx($im), $i + RADIUS); $n++) {
151
+   for ($m = max(0, $j - RADIUS); $m < min(imagesy($im), $j + RADIUS); $m++) {
152
+    $distance = sqrt(pow($n - $i, 2) + pow($m - $j, 2));
153
+    if ($distance < RADIUS) {
154
+     $pixels[$n][$m][$row['roleperiod_team']] += RADIUS - $distance;
155
+     $max = max($max, ((int) $pixels[$n][$m][1]) + ((int) $pixels[$n][$m][2]));
156
+    }
157
+   }
158
+  }
159
+ }
160
+
161
+ $x = imagesx($im);
162
+ for($i = 0; $i < imagesy($im); $i++) {
163
+  for($j = 0; $j < imagesx($im); $j++) {
164
+   $pos = imagecolorat($im, $j, $i);
165
+   $f = imagecolorsforindex($im, $pos);
166
+   $gst = $f['red']*0.3 + $f['green']*0.3 + $f['blue']*0.3;
167
+   $col = imagecolorresolve($im, ((PALE-1)*$gst + $f['red']) / PALE, ((PALE-1)*$gst + $f['green']) / PALE, ((PALE-1)*$gst + $f['blue']) / PALE);
168
+   imagesetpixel($im, $j, $i, $col);
169
+  }
170
+ } 
171
+ 
172
+ foreach ($pixels as $x => $dat) {
173
+  foreach ($dat as $y => $count) {
174
+   $red = (int) $count[1];
175
+   $blue = (int) $count[2];
176
+
177
+   // Not sure why they'd exist in this case, but meh
178
+   if ($red + $blue == 0) { continue; }
179
+
180
+   if (defined('NOTEAMS')) {
181
+    $ratio = ($red + $blue) / $max;
182
+
183
+    $c = imagecolorallocatealpha($im,
184
+        max(0, min(255, 255 * sin(pi() * ($ratio - 0.5)))), // Red
185
+        max(0, 255 * sin($ratio * pi())), // Green
186
+        max(0, 128 * cos($ratio * pi())), ALPHA - ALPHA * 0.5 * $ratio); // Blue
187
+
188
+    $count = 1;
189
+   } else {
190
+    $c = imagecolorallocatealpha($im, 255 * $red/($red + $blue), 0, 255 * $blue/($red + $blue), ALPHA);
191
+    $count = RADIUS * ($red + $blue)/$max;
192
+   }
193
+
194
+   imagefilledellipse($im, $x, $y, $count, $count, $c);
195
+  }
196
+ }
197
+
198
+ $im2 = imagecreatetruecolor(imagesx($im), imagesy($im) + 43);
199
+ $im3 = imagecreatefrompng('res/dmheader.png');
200
+ imagecopy($im2, $im3, 0, 0, 0, 0, imagesx($im), 43);
201
+ imagecopy($im2, $im, 0, 43, 0, 0, imagesx($im), imagesy($im)); 
202
+ $c = imagecolorallocate($im2, 157, 83, 33);
203
+ imagestring($im2, 1, imagesx($im) - 310, 30, str_pad('Map of ' . WHAT . ' locations on ' . MAP_NAME, 60, ' ', STR_PAD_LEFT), $c); 
204
+ imageline($im2, 0, 42, imagesx($im), 42, $c);
205
+
206
+ imageinterlace($im2, 1);
207
+
208
+ header('Content-type: image/jpeg');
209
+ imagejpeg($im2, null, 100);
210
+
211
+ imagefilledrectangle($im2, 1, imagesy($im2) - 5, 4, imagesy($im2) - 2, imagecolorallocate($im2, 157, 83, 33));
212
+ imagejpeg($im2, FILE_NAME, 100);
213
+?>

+ 47
- 0
www/group.php View File

@@ -0,0 +1,47 @@
1
+<?PHP
2
+
3
+ require_once('inc/config.php');
4
+ require_once('inc/ext.php');
5
+ require_once('inc/classestable.php');
6
+ require_once('inc/mostmaps.php');
7
+ require_once('inc/playertable.php');
8
+ require_once('inc/weaponslist.php');
9
+ require_once(STATS_DIR . '/inc/database.php');
10
+
11
+ $sql = 'SELECT group_name FROM groups WHERE group_id = ' . ((int) $_GET['group']);
12
+ $res = mysql_query($sql);
13
+
14
+ if (mysql_num_rows($res) == 0) {
15
+  require('404.php'); 
16
+  exit;
17
+ }
18
+
19
+ $row = mysql_fetch_assoc($res);
20
+
21
+ define('GROUPID', (int) $_GET['group']); 
22
+ define('NAME', $row['group_name']); 
23
+
24
+ define('TITLE', 'Group information :: ' . htmlentities(NAME, ENT_QUOTES, 'UTF-8'));
25
+ require_once('inc/header.php');
26
+
27
+ echo '<h2>Group information: ', htmlentities(NAME, ENT_QUOTES, 'UTF-8'), '</h2>';
28
+ echo '<div class="left">';
29
+
30
+ echo '<h3>Favourite maps</h3>';
31
+
32
+ showMostMaps('NATURAL JOIN players LEFT OUTER JOIN groupmemberships ON (groupmemberships.player_id = players.player_id)', 'group_id = ' . GROUPID);
33
+
34
+ echo '<h3 class="extra">Top Weapons</h3>';
35
+ showWeaponsList(', sessions NATURAL JOIN players LEFT OUTER JOIN groupmemberships ON (groupmemberships.player_id = players.player_id) WHERE kill_killer = sessions.session_id AND group_id = ' . GROUPID);
36
+
37
+ echo '</div>';
38
+
39
+ echo '<div class="right"><h3>Top Players</h3>';
40
+
41
+ showPlayerTable('LEFT OUTER JOIN groupmemberships ON (groupmemberships.player_id = players.player_id)', 'group_id = ' . GROUPID, 15, false, true);
42
+
43
+ echo '</div>';
44
+
45
+ require_once('inc/footer.php');
46
+
47
+?>

+ 25
- 0
www/groups.php View File

@@ -0,0 +1,25 @@
1
+<?PHP
2
+
3
+ require_once('inc/config.php');
4
+ require_once('inc/grouptable.php');
5
+ require_once(STATS_DIR . '/inc/database.php');
6
+
7
+ define('TITLE', 'Groups');
8
+
9
+ require_once('inc/header.php');
10
+
11
+ echo '<h2>Groups</h2>';
12
+
13
+ echo '<div class="left">', "\n";
14
+
15
+ echo '</div>', "\n";
16
+ echo '<div class="right">', "\n";
17
+
18
+ echo '<h3>Top groups</h3>';
19
+ showGroupsTable();
20
+ 
21
+ echo '</div>', "\n";
22
+
23
+ require_once('inc/footer.php');
24
+
25
+?>

+ 21
- 0
www/inc/awards.php View File

@@ -0,0 +1,21 @@
1
+<?PHP
2
+
3
+ function getAwardDescription($type, $field) {
4
+  switch($type) {
5
+   case 'weaponclass':
6
+    return 'Most kills with a ' . $field . ' weapon';
7
+   case 'weapon':
8
+    $sql = 'SELECT weapon_displayname FROM weapons WHERE weapon_id = ' .$field;
9
+    $res = mysql_query($sql);
10
+    return 'Most ' . mysql_result($res, 0) . ' kills';
11
+   case 'event':
12
+    $words = explode(' ', $field);
13
+    $title = array_shift($words) . 's';
14
+    $title .= ' ' . implode(' ', $words);
15
+    return 'Most ' . trim($title);
16
+   default:
17
+    return 'Unknown';
18
+  }
19
+ }
20
+
21
+?>

+ 36
- 0
www/inc/classcomtable.php View File

@@ -0,0 +1,36 @@
1
+<?PHP
2
+
3
+function showClassCompTable($classes) {
4
+
5
+ echo '<table class="classcomp">';
6
+ echo '<tr><th rowspan="2">Killer</th><th colspan="9">Victim</th></tr>';
7
+ echo '<tr>';
8
+
9
+ foreach ($classes as $killer => $data) {
10
+  echo '<th>';
11
+  echo '<a href="', URL_BASE, 'class.php?class=', $killer, '">';
12
+  echo '<img src="', sprintf(URL_CLASS, 'blue', $killer), '" alt="', $killer, '">';
13
+  echo '</a></th>';  
14
+ }
15
+
16
+ echo '</tr>';
17
+
18
+
19
+ foreach ($classes as $killer => $data) {
20
+  echo '<tr>';
21
+  echo '<th>';
22
+  echo '<a href="', URL_BASE, 'class.php?class=', $killer, '">';
23
+  echo '<img src="', sprintf(URL_CLASS, 'red', $killer), '" alt="', $killer, '">';
24
+  echo '</a></th>';
25
+
26
+  foreach ($data as $victim => $count) {
27
+   echo '<td>', $count, '</td>';
28
+  }
29
+
30
+  echo '</tr>';
31
+ }
32
+ echo '</table>';
33
+
34
+}
35
+
36
+?>

+ 112
- 0
www/inc/classestable.php View File

@@ -0,0 +1,112 @@
1
+<?PHP
2
+
3
+function getClassTableData($num) {
4
+ if ($num < 1000) {
5
+  return $num;
6
+ } else if ($num < 10000) {
7
+  return round($num/1000,1) . 'k';
8
+ } else if ($num < 100000) {
9
+  return round($num/1000,0) . 'k';
10
+ } else {
11
+  return round($num/1000000,1) . 'm';
12
+ }
13
+}
14
+
15
+function showClassesTable($classes, $label1, $label2, $relative = false, $label3 = null, $label4 = null, $relative2 = false) {
16
+
17
+ $max1 = $max2 = $max3 = $max4 = 0;
18
+ foreach ($classes as $data) {
19
+  $max1 = max($max1, $data['data1']);
20
+  $max2 = max($max2, $data['data2']);
21
+
22
+  if ($label3 != null && $label4 != null) {
23
+   $max3 = max($max3, $data['data3']);
24
+   $max4 = max($max4, $data['data4']);
25
+  }
26
+ }
27
+
28
+ if ($relative) {
29
+  $max1 = $max2 = max($max1, $max2);
30
+ }
31
+
32
+ if ($relative2) {
33
+  $max3 = $max4 = max($max3, $max4);
34
+ }
35
+
36
+ echo '<table class="graph">';
37
+ echo '<tr class="key even"><td colspan="', (count($classes) * 2), '">';
38
+ echo '<div class="graphkey">';
39
+ echo ' <div class="graphkey1">&nbsp;</div><p>', $label1, '</p>';
40
+ echo ' <div class="graphkey2">&nbsp;</div><p>', $label2, '</p>';
41
+ echo '</div>';
42
+ echo '</td></tr>';
43
+
44
+ echo '<tr class="data even">';
45
+ foreach ($classes as $data) {
46
+  if ($data['data1'] > 0) {
47
+   echo '<td><div class="graphbar1" style="height: ', 150 * $data['data1'] / $max1, 'px;" title="', $data['name'], ': ', $label1, ': ', $data['data1'], '">&nbsp;</div></td>';
48
+  } else {
49
+   echo '<td></td>';
50
+  }
51
+
52
+  if ($data['data2'] > 0) {
53
+   echo '<td><div class="graphbar2" style="height: ', 150 * $data['data2'] / $max2, 'px;" title="', $data['name'], ': ', $label2, ': ', $data['data2'], '">&nbsp;</div></td>';
54
+  } else {
55
+   echo '<td></td>';
56
+  }
57
+ }
58
+ echo '</tr>';
59
+
60
+ echo '<tr class="figures">';
61
+ foreach ($classes as $data) {
62
+  echo '<td>', getClassTableData((int) $data['data1']), '</td>';
63
+  echo '<td>', getClassTableData((int) $data['data2']), '</td>';
64
+ }
65
+ echo '</tr>';
66
+
67
+ echo '<tr>';
68
+ foreach ($classes as $data) {
69
+  echo '<th colspan="2">';
70
+  echo '<a href="', URL_BASE, 'class.php?class=', $data['name'], '">';
71
+  echo '<img src="', sprintf(URL_CLASS, 'blue', $data['name']), '" alt="', $data['name'], '">';
72
+  echo '</a></th>';
73
+ }
74
+ echo '</tr>';
75
+
76
+ if ($label3 != null && $label4 != null) {
77
+  echo '<tr class="figures">';
78
+  foreach ($classes as $data) {
79
+   echo '<td>', getClassTableData((int) $data['data3']), '</td>';
80
+   echo '<td>', getClassTableData((int) $data['data4']), '</td>';
81
+  }
82
+  echo '</tr>';
83
+
84
+  echo '<tr class="data even bottom">';
85
+  foreach ($classes as $data) {
86
+   if ($data['data3'] > 0) {
87
+    echo '<td><div class="graphbar1" style="height: ', 150 * $data['data3'] / $max3, 'px;" title="', $data['name'], ': ', $label3, ': ', $data['data3'], '">&nbsp;</div></td>';
88
+   } else {
89
+    echo '<td></td>';
90
+   }
91
+
92
+   if ($data['data4'] > 0) {
93
+    echo '<td><div class="graphbar2" style="height: ', 150 * $data['data4'] / $max4, 'px;" title="', $data['name'], ': ', $label4, ': ', $data['data4'], '">&nbsp;</div></td>';
94
+   } else {
95
+    echo '<td></td>';
96
+   }
97
+  }
98
+  echo '</tr>';
99
+
100
+  echo '<tr class="key even bottom"><td colspan="', (count($classes) * 2), '">';
101
+  echo '<div class="graphkey">';
102
+  echo ' <div class="graphkey1">&nbsp;</div><p>', $label3, '</p>';
103
+  echo ' <div class="graphkey2">&nbsp;</div><p>', $label4, '</p>';
104
+  echo '</div>';
105
+  echo '</td></tr>';
106
+ }
107
+
108
+ echo '</table>';
109
+
110
+}
111
+
112
+?>

+ 10
- 0
www/inc/common.php View File

@@ -0,0 +1,10 @@
1
+<?PHP
2
+
3
+ function getSuffix($number) {
4
+  if ($number % 10 == 1 && $number % 100 != 11) { return 'st'; }
5
+  if ($number % 10 == 2 && $number % 100 != 12) { return 'nd'; }
6
+  if ($number % 10 == 3 && $number % 100 != 13) { return 'rd'; }
7
+  return 'th';
8
+ }
9
+
10
+?>

+ 29
- 0
www/inc/community.php View File

@@ -0,0 +1,29 @@
1
+<?PHP
2
+
3
+ define('COMMUNITY_KEY', '76561197960265729');
4
+
5
+ function getCommunityID($steamID) {
6
+  $parts = explode(':', $steamID);
7
+  $id = array_pop($parts);
8
+
9
+  return stringAdd(COMMUNITY_KEY, (string) ($id * 2));
10
+ }
11
+
12
+ function stringAdd($stringA, $stringB) {
13
+  $carry = 0;
14
+  for ($i = 1; $i <= strlen($stringB) || $carry > 0; $i++) {
15
+   $val = ord($stringA[strlen($stringA) - $i]) + $carry - 48;
16
+
17
+   if ($i <= strlen($stringB)) {
18
+    $val += ord($stringB[strlen($stringB) - $i]) - 48;
19
+   }
20
+
21
+   $carry = floor($val / 10);
22
+   $val = 48 + ($val % 10);
23
+   $stringA[strlen($stringA) - $i] = chr($val);
24
+  }
25
+  
26
+  return $stringA;
27
+ }
28
+
29
+?>

+ 5
- 0
www/inc/config.php View File

@@ -0,0 +1,5 @@
1
+<?PHP
2
+
3
+ require_once('/home/sourceds/stats/config.php');
4
+
5
+?>

+ 36
- 0
www/inc/deathmap.php View File

@@ -0,0 +1,36 @@
1
+<?PHP
2
+
3
+ // Co-ordinates for deathmaps
4
+
5
+ $coords = array(
6
+        'yaaargh' => array(
7
+                528, 143, -1814, -6,
8
+                364, 325, -71, 2016,
9
+                190, 331, 1823, 2057
10
+        ),
11
+
12
+	'ctf_casbah' => array(
13
+                494, 433, 1905, -4733,  // Middle corner
14
+		123, 89, -3925, 512, // Red cap cliffs -3925 x, 512 y, 
15
+		421, 78, 660, 764, // Blue cap
16
+	),
17
+
18
+	'cp_king_of_the_hill_b1' => array(
19
+		188, 69, 1433, 1615, // Blue door
20
+		540, 378, -1439, -1642, // Red door
21
+		141, 446, -2040, 2047, // BL corner
22
+	),
23
+
24
+	'cp_granary' => array(
25
+		92, 226, -1472, -5178, // Red cap
26
+		690, 361, 1022, 6336, // BR corner
27
+		363, 155, -2751, -17, // Middle ramp
28
+	),
29
+ );
30
+
31
+ function hasDeathmap($map) {
32
+  global $coords;
33
+  return isset($coords[$map]);
34
+ }
35
+
36
+?>

+ 230
- 0
www/inc/eventhistory.php View File

@@ -0,0 +1,230 @@
1
+<?PHP
2
+
3
+ function eventSort($a, $b) {
4
+  return strtotime($b['time']) - strtotime($a['time']);
5
+ }
6
+
7
+ function showEventHistory($playerid) {
8
+
9
+  $events = array();
10
+
11
+  // Kills
12
+  $sql = '
13
+	SELECT
14
+		weapon_name,
15
+		kill_timestamp,
16
+
17
+		rp1.player_id AS killerID,
18
+		rp1.roleperiod_team AS killerTeam,
19
+		c1.class_name AS killerClass,
20
+		s1.session_alias AS killerName,
21
+
22
+		rp2.player_id AS victimID,
23
+		rp2.roleperiod_team AS victimTeam,
24
+		c2.class_name AS victimClass,
25
+		s2.session_alias AS victimName,
26
+
27
+		rp3.player_id AS assistID,
28
+		rp3.roleperiod_team AS assistTeam,
29
+		c3.class_name AS assistClass,
30
+		s3.session_alias AS assistName
31
+	FROM
32
+		kills
33
+		NATURAL JOIN weapons
34
+
35
+		INNER JOIN roleperiods AS rp1 ON kill_killer = rp1.roleperiod_id
36
+		INNER JOIN sessions AS s1 ON rp1.session_id = s1.session_id
37
+		INNER JOIN classes AS c1 ON rp1.class_id = c1.class_id
38
+
39
+		INNER JOIN roleperiods AS rp2 ON kill_victim = rp2.roleperiod_id
40
+		INNER JOIN sessions AS s2 ON rp2.session_id = s2.session_id
41
+		INNER JOIN classes AS c2 ON rp2.class_id = c2.class_id
42
+
43
+		LEFT OUTER JOIN roleperiods AS rp3 ON kill_assist = rp3.roleperiod_id
44
+		LEFT OUTER JOIN sessions AS s3 ON rp3.session_id = s3.session_id
45
+		LEFT OUTER JOIN classes AS c3 ON rp3.class_id = c3.class_id
46
+	WHERE
47
+		(
48
+			rp1.player_id = ' . $playerid .'
49
+			OR rp2.player_id = ' . $playerid . '
50
+			OR rp3.player_id = ' . $playerid . '
51
+		)
52
+	ORDER BY kill_timestamp DESC
53
+	LIMIT 0,100';
54
+
55
+  $res = mysql_query($sql) or print(mysql_error());
56
+  while ($row = mysql_fetch_assoc($res)) {
57
+   $row['time'] = $mintime = $row['kill_timestamp'];
58
+   $events[] = $row;
59
+  }
60
+
61
+  // Sessions
62
+  $sql = '
63
+	SELECT
64
+		session_starttime,
65
+		session_endtime,
66
+		session_alias,
67
+		map_name
68
+	FROM
69
+		sessions
70
+		NATURAL JOIN games
71
+		NATURAL JOIN maps
72
+	WHERE
73
+		(
74
+			session_starttime > \'' . $mintime . '\'
75
+			OR session_endtime > \'' . $mintime . '\'
76
+		)
77
+
78
+		AND player_id = ' . $playerid . '
79
+	ORDER BY session_starttime DESC
80
+	LIMIT 0,100';
81
+
82
+  $res = mysql_query($sql) or print(mysql_error());
83
+  while ($row = mysql_fetch_assoc($res)) {
84
+   if (strtotime($row['session_starttime']) > strtotime($mintime)) {
85
+    $row['time'] = $row['session_starttime'];
86
+    $events[] = $row;
87
+   } 
88
+
89
+   if (strtotime($row['session_endtime']) > strtotime($mintime)) {
90
+    $row['time'] = $row['session_endtime'];
91
+    $events[] = $row;
92
+   } 
93
+  }
94
+
95
+  // Roles
96
+  $sql = 'SELECT class_name, class_displayname, session_alias, roleperiod_team, roleperiod_starttime FROM roleperiods NATURAL JOIN sessions NATURAL JOIN classes WHERE player_id = ' . $playerid . ' AND roleperiod_starttime > \'' . $mintime . '\' ORDER BY roleperiod_starttime DESC LIMIT 0,100';
97
+  $res = mysql_query($sql);
98
+  while ($row = mysql_fetch_assoc($res)) {
99
+   $row['time'] = $row['roleperiod_starttime'];
100
+   $events[] = $row;
101
+  }
102
+
103
+  usort($events, 'eventSort');
104
+
105
+  echo '<table class="events"><tr><th>Time</th><th colspan="7">Event</th></tr>';
106
+
107
+  $i = 0;
108
+  foreach (array_slice($events, 0, 100) as $event) {
109
+   echo '<tr', ++$i % 2 == 0 ? ' class="even"' : '', '><td class="time">', str_replace(' ', '<br>', $event['time']), '</td>';
110
+
111
+   if (isset($event['kill_timestamp'])) {
112
+    displayKill($event, $playerid);
113
+   } else if (isset($event['session_starttime'])) {
114
+    displaySession($event);
115
+   } else if (isset($event['roleperiod_starttime'])) {
116
+    displayRoleChange($event);
117
+   }
118
+
119
+   echo '</tr>', "\n";
120
+  }
121
+
122
+  echo '</table>';
123
+ }
124
+
125
+ function displayRoleChange($event) {
126
+  $text = getTeam($event['roleperiod_team']) . ' ' . strtolower($event['class_displayname']);
127
+
128
+  echo '<td colspan="7"><img src="', sprintf(URL_CLASS, getTeam($event['roleperiod_team']), $event['class_name']);
129
+  echo '" alt="', $text, '"> ', htmlentities($event['session_alias'], ENT_COMPAT, 'UTF-8');
130
+  echo ' changed to a ', $text, '</td>';
131
+ }
132
+
133
+ function displayKill($event, $playerid) {
134
+  $me = null; 
135
+
136
+  if ($event['killerID'] == $playerid) {
137
+   $me = 'killer';
138
+  } else if ($event['victimID'] == $playerid) {
139
+   $me = 'victim';
140
+  } else {
141
+   $me = 'assist';
142
+  }
143
+
144
+  call_user_func('displayKill' . ucfirst($me), $event);
145
+ }
146
+
147
+ function getTeam($team) {
148
+  switch ((int) $team) {
149
+   case 1: return 'red';
150
+   case 2: return 'blue';
151
+  }
152
+ }
153
+
154
+ function displayPerson($event, $who, $link = true, $link2 = true) {
155
+  echo '<td', ($who != 'killer' || $event['assistID'] == 0) ? ' colspan="2"' : '', '><img src="', sprintf(URL_CLASS, getTeam($event[$who . 'Team']), $event[$who . 'Class']);
156
+  echo '" alt="' , getTeam($event[$who . 'Team']), ' ', $event[$who . 'Class'], '"> ';
157
+
158
+  if ($link) {
159
+   echo '<a href="player.php?id=' . $event[$who . 'ID'] . '">';
160
+  }
161
+
162
+  echo htmlentities($event[$who . 'Name'], ENT_COMPAT, 'UTF-8');
163
+
164
+  if ($link) {
165
+   echo '</a>';
166
+  }
167
+
168
+  if ($who == 'killer' && $event['assistID'] > 0) {
169
+   echo ',</td><td><img src="', sprintf(URL_CLASS, getTeam($event['assistTeam']), $event['assistClass']);
170
+   echo '" alt="' , getTeam($event['assistTeam']), ' ', $event['assistClass'], '"> ';
171
+
172
+   if ($link2) {
173
+    echo '<a href="player.php?id=' . $event['assistID'] . '">';
174
+   }
175
+
176
+   echo htmlentities($event['assistName'], ENT_COMPAT, 'UTF-8');
177
+
178
+   if ($link2) {
179
+    echo '</a>';
180
+   }
181
+
182
+  }
183
+
184
+  echo '</td>';
185
+ }
186
+
187
+ function displayWeapon($event) {
188
+  echo '<td><img src="', sprintf(URL_WEAPON, $event['weapon_name']), '"></td>';
189
+ }
190
+
191
+ function displayKillKiller($event) {
192
+  displayPerson($event, 'killer', false);
193
+  echo '<td>killed</td>';
194
+  displayPerson($event, 'victim');
195
+  echo '<td>with</td>';
196
+  displayWeapon($event);
197
+ }
198
+
199
+ function displayKillVictim($event) {
200
+  displayPerson($event, 'killer');
201
+  echo '<td>killed</td>';
202
+  displayPerson($event, 'victim', false);
203
+  echo '<td>with</td>';
204
+  displayWeapon($event);
205
+ }
206
+
207
+ function displayKillAssist($event) {
208
+  displayPerson($event, 'killer', true, false);
209
+  echo '<td>killed</td>';
210
+  displayPerson($event, 'victim');
211
+  echo '<td>with</td>';
212
+  displayWeapon($event);
213
+ }
214
+
215
+ function displaySession($event) {
216
+  echo '<td colspan="7">';
217
+
218
+  echo '<img src="', sprintf(URL_MAP, 'tiny', $event['map_name']);
219
+  echo '" alt="', $event['map_name'], '" title="Map: ', $event['map_name'], '"> ';
220
+
221
+  if ($event['session_starttime'] == $event['time']) {
222
+   echo htmlentities($event['session_alias'], ENT_COMPAT, 'UTF-8'), ' joined the server';
223
+  } else {
224
+   echo htmlentities($event['session_alias'], ENT_COMPAT, 'UTF-8'), ' left the server';
225
+  }
226
+
227
+  echo '</td>';
228
+ }
229
+
230
+?>

+ 56
- 0
www/inc/ext.php View File

@@ -0,0 +1,56 @@
1
+<?PHP
2
+
3
+// Displays extra information about a map in the left-hand column
4
+// of the map page. You may want to include information on class
5
+// limits, the times the map is played, or links to downloads or
6
+// reviews.
7
+function show_extra_map_info($name) {
8
+
9
+ $cfgdir = '/opt/sourceds/orangebox/tf/cfg/beetlesmod';
10
+
11
+ if (file_exists($cfgdir.'/'.$name.'.cfg')) {
12
+  $cfg = array();
13
+
14
+  foreach (file($cfgdir . '/' . $name . '.cfg') as $line) {
15
+   if (!empty($line) && strstr($line,'//') != $line) {
16
+    $bits = explode(' ', $line, 2);
17
+    $cfg[strtolower($bits[0])][] = $bits[1];
18
+   }
19
+  }
20
+
21
+  if (isset($cfg['setclasslimit'])) {
22
+   echo '<h3>Class Limits</h3>';
23
+   echo '<ul class="classlimits">';
24
+
25
+   $all = count($cfg['setclasslimit']) == 10;
26
+   foreach ($cfg['setclasslimit'] as $limit) {
27
+     preg_match('/\"(.*)\" \"(.*)\" \"(.*)\"/', $limit, $details);
28
+
29
+     if (strtolower($details[1]) != "random") {
30
+      if ($all && $details[2] == "0") { continue; }
31
+
32
+      if ($details[2] == "-1") {
33
+       $details[2] = 'No Restriction';
34
+      } else if ($details[2] == "0") {
35
+       $details[2] = 'Not Permitted';
36
+      } else {
37
+       $details[2] = 'Maximum of '.$details[2].' per team';
38
+      }
39
+
40
+      echo '<li><img src="' . sprintf(URL_CLASS, 'blue', $details[1]) . '"> ' . $details[2];
41
+       
42
+      if ($details[3] != "0.0") {
43
+       $num = ($maxplayers/2) * ((float)$details[3]);
44
+       echo ' ('.$num.')';
45
+      }
46
+     }
47
+    }
48
+
49
+    if ($all) { echo '<li> No other class permitted'; }
50
+
51
+    echo '</ul>';
52
+   }
53
+  }
54
+}
55
+
56
+?>

+ 11
- 0
www/inc/footer.php View File

@@ -0,0 +1,11 @@
1
+   <div class="clear"></div>
2
+  </div>
3
+  <div id="footer">
4
+   Produced by <a href="http://chris.smith.name/projects/tf2stats">TF2 Stats</a>
5
+   by <a href="http://chris.smith.name/">Chris Smith</a>. Most images produced by
6
+   <a href="http://www.shanemcc.co.uk/">Shane Mc Cormack</a>. Team Fortress 2 and
7
+   all related names and media is copyright (&copy;)
8
+   <a href="http://www.valvesoftware.com/">Valve Corporation</a>.
9
+  </div>
10
+ </body>
11
+</html>

+ 41
- 0
www/inc/grouptable.php View File

@@ -0,0 +1,41 @@
1
+<?PHP
2
+
3
+function showGroupsTable() {
4
+ $sql = 'SELECT group_id, group_name, group_members, group_score / group_members AS group_avgscore FROM groups WHERE group_members > 1 ORDER BY group_avgscore DESC LIMIT 0,20';
5
+ $re1 = mysql_query($sql);
6
+
7
+ echo '<table>';
8
+ echo '<tr>';
9
+ echo '<th>Rank</th>';
10
+ echo '<th>Group</th>';
11
+ echo '<th>Top player</th>';
12
+ echo '<th>Members</th>';
13
+ echo '<th>Avg Score</th>';
14
+ echo '</tr>';
15
+
16
+ if (mysql_num_rows($re1) == 0) {
17
+  echo '<tr><td colspan="5" class="none">No results found</td></tr>';
18
+ }
19
+
20
+ $rank = 1;
21
+ while ($row = mysql_fetch_assoc($re1)) {
22
+  $sql = "SELECT player_id, player_score, session_alias FROM groupmemberships NATURAL JOIN players NATURAL JOIN sessions WHERE group_id = " . $row['group_id'] . " ORDER BY player_score DESC LIMIT 0,1";
23
+  $re2 = mysql_query($sql) or print(mysql_error());
24
+  $ro2 = mysql_fetch_assoc($re2);
25
+
26
+  echo '<tr', $rank % 2 == 0 ? ' class="even"' : '', '><td>', number_format($rank++), '</td><td>';
27
+  echo '<a href="group.php?group=', $row['group_id'], '">';
28
+  echo htmlentities($row['group_name'], ENT_QUOTES, 'UTF-8'), '</a>';
29
+  echo '</td>';
30
+  echo '<td><a href="player.php?id=', $ro2['player_id'], '">', htmlentities($ro2['session_alias'], ENT_QUOTES, 'UTF-8');
31
+  echo '</a></td>';
32
+  echo '<td class="num">', number_format($row['group_members']), '</td>';
33
+  echo '<td class="num">', number_format($row['group_avgscore']), '</td>';
34
+
35
+  echo '</tr>';
36
+ }
37
+
38
+ echo '</table>';
39
+}
40
+
41
+?>

+ 35
- 0
www/inc/header.php View File

@@ -0,0 +1,35 @@
1
+<?PHP
2
+
3
+require_once(dirname(__FILE__) . '/config.php');
4
+require_once(STATS_DIR . '/inc/database.php');
5
+
6
+?>
7
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
8
+<html>
9
+ <head>
10
+  <title>TF2 Stats :: <?PHP echo TITLE; ?></title>
11
+  <link rel="stylesheet" href="res/style.css" type="text/css">
12
+ </head>
13
+ <body>
14
+  <h1 id="header">TF2 Stats</h1>
15
+  <div id="menu">
16
+   <ul>
17
+    <li id="first"><a href="<?PHP echo URL_BASE; ?>">Overview</a></li>
18
+    <li><a href="<?PHP echo URL_BASE; ?>players.php">Players</a></li>
19
+    <li><a href="<?PHP echo URL_BASE; ?>maps.php">Maps</a></li>
20
+    <li><a href="<?PHP echo URL_BASE; ?>weapons.php">Weapons</a></li>
21
+<?PHP if (ENABLE_AWARDS) { ?>    <li><a href="<?PHP echo URL_BASE; ?>awards.php">Awards</a></li> <?PHP } ?>
22
+   </ul>
23
+  </div>
24
+  <div id="content">
25
+<?PHP
26
+
27
+ $sql = 'SELECT COUNT(*) FROM config WHERE config_key = \'updating\' AND config_value = \'true\'';
28
+ $res = mysql_query($sql);
29
+ $num = mysql_result($res, 0);
30
+
31
+ if ($num > 0) {
32
+  echo '<div id="updating">TF2 Stats is currently being updated. Please check back later for the latest stats.</div>';
33
+ }
34
+
35
+?>

+ 37
- 0
www/inc/mostmaps.php View File

@@ -0,0 +1,37 @@
1
+<?PHP
2
+
3
+ require_once(dirname(__FILE__) . '/config.php');
4
+
5
+ if (ENABLE_DEATHMAPS) {
6
+  require_once(dirname(__FILE__) . '/deathmap.php');
7
+ }
8
+
9
+ function showMostMaps($joins = '', $where = '1=1') {
10
+  $sql = "SELECT map_name, SUM(session_endtime - session_starttime) AS time FROM maps NATURAL JOIN games NATURAL JOIN sessions $joins WHERE session_endtime > '0000-00-00' AND $where GROUP BY map_name ORDER BY time desc LIMIT 0,3";
11
+  showMostMapsSQL($sql);
12
+ }
13
+
14
+ function showMostMapsSQL($sql, $showbig = true) {
15
+  $class = $showbig ? 'large' : 'medium';
16
+  $res = mysql_query($sql);
17
+
18
+  while ($row = mysql_fetch_assoc($res)) {
19
+   echo '<div class="map ', $class, '">';
20
+
21
+   if ($row !== false) {
22
+    echo '<a href="map.php?map=', $row['map_name'], '"><img src="';
23
+    echo sprintf(URL_MAP, ($class == 'large' ? 'large' : 'small'), $row['map_name']);
24
+    echo '" class="map ', $class, '"></a>', "\n";
25
+
26
+    if (ENABLE_DEATHMAPS && function_exists('hasDeathmap') && hasDeathmap($row['map_name'])) {
27
+     echo '<img src="res/dead.png" alt="Death" title="This map has a death map" class="deathmap">';
28
+    }
29
+
30
+    echo $row['map_name'];
31
+   }
32
+   echo '</div>', "\n";
33
+   $class = 'medium';
34
+  }
35
+ }
36
+
37
+?>

+ 80
- 0
www/inc/playertable.php View File

@@ -0,0 +1,80 @@
1
+<?PHP
2
+
3
+function showPlayerTable($joins = '', $where = '1=1', $limit = 20, $hideclass = false, $userank = false, $highlight = 0) {
4
+
5
+ $more = false;
6
+
7
+ echo '<table>', "\n";
8
+ echo '<tr>', "\n";
9
+ echo '<th rowspan="2">Rank</th><th rowspan="2">Alias</th><th rowspan="2">Kills</th><th rowspan="2">Deaths</th><th rowspan="2"><abbr title="Kills per Death">KPD</abbr></th><th rowspan="2">Score</th>';
10
+ echo '<th colspan="', $hideclass ? '1' : '2', '">Mostly seen &hellip;</th></tr>', "\n";
11
+ echo '<tr>', $hideclass ? '' : '<th>Playing as</th>', '<th>Killing with</th></tr>', "\n";
12
+
13
+ $sql = "SELECT players.player_id, player_rank, player_score, session_alias, SUM(roleperiod_kills) AS kills, SUM(roleperiod_deaths) AS deaths, SUM(roleperiod_kills)/SUM(roleperiod_deaths) AS kpd FROM players NATURAL JOIN sessions INNER JOIN roleperiods ON roleperiods.session_id = sessions.session_id $joins WHERE player_rank > 0 AND $where GROUP BY player_id ORDER BY " . (($where == '1=1' || $userank) ? 'player_rank' : 'kpd DESC') . " LIMIT $limit";
14
+ $res = mysql_query($sql) or print(mysql_error());
15
+ $i = 0;
16
+
17
+ if (mysql_num_rows($res) == 0) {
18
+  echo '<tr><td colspan="8" class="none">No results found</td></tr>';
19
+ }
20
+
21
+ while ($row = mysql_fetch_assoc($res)) {
22
+  if (!$hideclass) {
23
+   $sql = "SELECT class_name, class_displayname FROM roleperiods NATURAL JOIN classes NATURAL JOIN sessions WHERE roleperiod_endtime > '0000-00-00' AND player_id = " . $row['player_id'] . " GROUP BY class_id ORDER BY SUM(roleperiod_endtime - roleperiod_starttime) DESC LIMIT 0,1";
24
+   $re2 = mysql_query($sql) or print(mysql_error());
25
+  }
26
+
27
+  $sql = "SELECT weapon_name, COUNT(*) AS times FROM roleperiods INNER JOIN kills ON kills.kill_killer = roleperiod_id NATURAL JOIN weapons NATURAL JOIN sessions WHERE player_id = " .$row['player_id'] . " GROUP BY weapon_displayname ORDER BY times DESC LIMIT 0,1";
28
+  $re3 = mysql_query($sql) or print($sql . '<br>' . mysql_error());
29
+
30
+  $i++;
31
+
32
+  $hclass = $row['player_id'] == $highlight ? 'highlight' : '';
33
+
34
+  if ($i % 2 == 0) {
35
+   $class = ' class="even ' . $hclass . '"';
36
+  } else if ($hclass != '') {
37
+   $class = ' class="' . $hclass . '"';
38
+  } else {
39
+   $class = '';
40
+  }
41
+
42
+  echo '<tr', $class, '><td>', ($where == '1=1' || $userank) ? $row['player_rank'] : $i, '</td>';
43
+  echo '<td>';
44
+
45
+  if ($highlight != $row['player_id']) {
46
+    echo '<a href="player.php?id=', $row['player_id'], '">';
47
+  }
48
+
49
+  echo htmlentities($row['session_alias'], ENT_COMPAT, 'UTF-8');
50
+
51
+  if ($highlight != $row['player_id']) {
52
+   echo '</a>';
53
+  }
54
+
55
+  echo '</td><td class="num">', number_format($row['kills']), '</td><td class="num">', number_format($row['deaths']), '</td>';
56
+
57
+  echo '<td class="num">', number_format($row['kpd'], 2), '</td>';
58
+  echo '<td class="num">', number_format($row['player_score'], 0), '</td>';
59
+
60
+  if (!$hideclass) {
61
+   echo '<td class="class">';
62
+   echo '<a href="', URL_BASE, 'class.php?class=', mysql_result($re2, 0), '">';
63
+   echo '<img src="', sprintf(URL_CLASS, 'blue', mysql_result($re2, 0)), '"></a></td>';
64
+  }
65
+  echo '<td class="weapon">';
66
+
67
+  if (mysql_num_rows($re3) > 0) {
68
+   echo '<img src="/stats/res/weapons/', mysql_result($re3, 0), '.png">';
69
+  } else {
70
+   echo 'Nothing';
71
+  }
72
+
73
+  echo '</td>';
74
+  echo '</tr>', "\n";
75
+ }
76
+
77
+ echo '</table>', "\n";
78
+
79
+
80
+}

+ 17
- 0
www/inc/weaponslist.php View File

@@ -0,0 +1,17 @@
1
+<?PHP
2
+
3
+function showWeaponsList($extra = '', $limit = 'LIMIT 0,5') {
4
+ echo '<ol class="weapons">', "\n";
5
+
6
+ $sql = "SELECT weapon_name, weapon_displayname, COUNT(*) as kills FROM weapons NATURAL JOIN kills $extra GROUP BY weapon_displayname ORDER BY kills DESC $limit";
7
+ $res = mysql_query($sql) or print(mysql_error());
8
+ while ($row = mysql_fetch_assoc($res)) {
9
+  echo '<li><img src="/stats/res/weapons/', $row['weapon_name'], '.png" alt="', "\n";
10
+  echo $row['weapon_displayname'], '" title="', $row['weapon_displayname'], '"> ', "\n";
11
+  echo number_format($row['kills']), ' kill', $row['kills'] == 1 ? '' : 's', '</li>', "\n";
12
+ }
13
+
14
+ echo '</ol>', "\n";
15
+}
16
+
17
+?>

+ 93
- 0
www/index.php View File

@@ -0,0 +1,93 @@
1
+<?PHP
2
+
3
+ require_once('inc/config.php');
4
+ require_once('inc/mostmaps.php');
5
+ require_once('inc/playertable.php');
6
+ require_once('inc/weaponslist.php');
7
+ require_once(STATS_DIR . '/inc/database.php');
8
+
9
+ define('TITLE', 'Overview');
10
+
11
+ require_once('inc/header.php');
12
+
13
+ echo '<h2>Overview</h2>';
14
+
15
+ echo '<div class="left">', "\n";
16
+
17
+ /** -- Output most played maps -- **/
18
+ echo '<h3>Most played maps</h3>', "\n";
19
+ showMostMaps(); 
20
+
21
+ /** -- Output overall statistics -- **/
22
+ echo '<h3 class="extra">Overall statistics</h3>', "\n";
23
+ echo '<ul class="stats">', "\n";
24
+
25
+ $sql = 'SELECT COUNT(*) FROM players';
26
+ $res = mysql_query($sql);
27
+ $pcount = mysql_result($res, 0);
28
+ 
29
+ $sql = 'SELECT COUNT(*) FROM kills';
30
+ $res = mysql_query($sql);
31
+ $kcount = mysql_result($res, 0);
32
+
33
+ $sql = 'SELECT HOUR(kill_timestamp) AS thehour, COUNT(*) AS num FROM kills GROUP BY thehour ORDER BY num DESC LIMIT 0,1';
34
+ $res = mysql_query($sql);
35
+ $tophour = mysql_result($res, 0);
36
+
37
+ $sql = "SELECT class_displayname, SUM(roleperiod_endtime - roleperiod_starttime) AS time FROM classes NATURAL JOIN roleperiods WHERE roleperiod_endtime > '0000-00-00' GROUP BY class_name ORDER BY time DESC LIMIT 0,1";
38
+ $res = mysql_query($sql);
39
+ $topclass = mysql_result($res, 0);
40
+
41
+ $sql = "SELECT SUM(session_endtime - session_starttime) FROM sessions WHERE session_endtime > '0000-00-00'";
42
+ $res = mysql_query($sql);
43
+ $time = mysql_result($res, 0);
44
+
45
+ echo '<li><em>', number_format($pcount), '</em> players tracked.</li>', "\n";
46
+ echo '<li><em>', number_format($kcount), '</em> kills logged.</li>', "\n";
47
+ echo '<li>Most popular time of day: <em>', $tophour, '-', (++$tophour % 24), ':00</em>.</li>', "\n";
48
+ echo '<li>Most played class: <em>', $topclass, '</em>.</li>', "\n";
49
+ echo '<li><em>', number_format(round($time/(60*60),0)), '</em> hours of play time.</li>', "\n";
50
+ echo '</ul>', "\n";
51
+
52
+ /** -- Output top weapons -- **/
53
+ echo '<h3>Top weapons</h3>', "\n";
54
+ 
55
+ showWeaponsList();
56
+
57
+ echo '</div>', "\n";
58
+ echo '<div class="right">', "\n";
59
+
60
+ /** -- Output server list -- **/
61
+ if (ENABLE_SERVER_LIST) {
62
+  echo '<h3>Participating servers</h3>';
63
+  
64
+  echo '<table><tr><th>Server name</th><th>Address</th><th>Connect</th></tr>';
65
+
66
+  $sql = 'SELECT server_name, server_ip, server_port FROM servers ORDER BY server_name';
67
+  $res = mysql_query($sql);
68
+
69
+  $i = 0;
70
+  while ($row = mysql_fetch_assoc($res)) {
71
+   $i++;
72
+   echo '<tr class="', ($i & 1) ? '' : 'even', '"><td>', htmlentities($row['server_name'], ENT_COMPAT, 'UTF-8'), '</td>';
73
+   echo '<td>', $row['server_ip'], ':', $row['server_port'], '</td>';
74
+   echo '<td class="connect"><a href="steam://connect/', $row['server_ip'], ':', $row['server_port'], '">';
75
+   echo '<img src="res/steam.png" alt="Steam" title="Connect with Steam">using steam</a>';
76
+   echo '<a href="hlsw://', $row['server_ip'], ':', $row['server_port'], '">';
77
+   echo '<img src="res/hlsw.png" alt="HLSW" title="Connect with HLSW">using hlsw</a>';
78
+   echo '</td></tr>';
79
+  }
80
+
81
+  echo '</table>';
82
+ }
83
+
84
+ /** -- Output top players -- **/
85
+ echo '<h3>Top players</h3>', "\n";
86
+
87
+ showPlayerTable('', '1=1', OVERVIEW_PLAYERS);
88
+ 
89
+ echo '</div>', "\n";
90
+
91
+ require_once('inc/footer.php');
92
+
93
+?>

+ 90
- 0
www/map.php View File

@@ -0,0 +1,90 @@
1
+<?PHP
2
+
3
+ require_once('inc/config.php');
4
+ require_once('inc/ext.php');
5
+ require_once('inc/classestable.php');
6
+ require_once('inc/playertable.php');
7
+ require_once('inc/weaponslist.php');
8
+ require_once(STATS_DIR . '/inc/database.php');
9
+
10
+ $sql = 'SELECT map_id FROM maps WHERE map_name = \'' . s($_GET['map']) . '\'';
11
+ $res = mysql_query($sql);
12
+
13
+ if (mysql_num_rows($res) == 0) {
14
+  require('404.php'); 
15
+  exit;
16
+ }
17
+
18
+ define('MAP', (int) mysql_result($res, 0));
19
+
20
+ define('TITLE', 'Map information :: ' . htmlentities($_GET['map']));
21
+ require_once('inc/header.php');
22
+
23
+ echo '<h2>Map Information: ', htmlentities($_GET['map']), '</h2>';
24
+
25
+ echo '<div class="left"><h3>Map Preview</h3>';
26
+ echo '<div class="map large">';
27
+ echo '<img src="', sprintf(URL_MAP, 'large', $_GET['map']), '" ';
28
+ echo 'alt="Image of ', htmlentities($_GET['map']), '" class="map large">';
29
+ echo htmlentities($_GET['map']) . '</div>';
30
+
31
+ echo '<h3 class="extra">Map Statistics</h3>';
32
+
33
+ $sql = 'SELECT COUNT(*) AS num, AVG(TIMESTAMPDIFF(MINUTE, game_starttime, game_endtime)) AS time FROM games WHERE map_id = ' . MAP;
34
+ $sql .= ' GROUP BY map_id';
35
+ $res = mysql_query($sql) or print(mysql_error());
36
+ $row = mysql_fetch_assoc($res);
37
+ $num = $row['num']; $time = $row['time'];
38
+
39
+ echo '<ul class="stats">';
40
+ echo '<li>Played <em>', $num, '</em> time', ($num != 1 ? 's' : ''), '.</li>';
41
+ echo '<li>Average map length: <em>', round($time,0), '</em> mins.</li>';
42
+ echo '</ul>';
43
+
44
+ echo '<h3>Top Weapons</h3>';
45
+ showWeaponsList('INNER JOIN roleperiods ON roleperiod_id = kill_killer NATURAL JOIN sessions NATURAL JOIN games WHERE map_id = ' . MAP);
46
+ show_extra_map_info($_GET['map']);
47
+
48
+ echo '</div>';
49
+
50
+ echo '<div class="right"><h3>Top Players</h3>';
51
+
52
+ showPlayerTable('NATURAL JOIN games NATURAL JOIN maps', 'map_id = ' . MAP, 10);
53
+
54
+ if (ENABLE_DEATHMAPS && file_exists('deathmap.php') && file_exists('inc/deathmap.php')) {
55
+  require('inc/deathmap.php');
56
+  if (isset($coords[$_GET['map']])) {
57
+   echo '<h3>Death Map</h3>';
58
+   echo '<div class="deathmap">';
59
+   echo ' <img src="deathmap.php', sprintf(DM_ARGS, htmlentities($_GET['map'])), '" alt="Death map">';
60
+   echo '</div>';
61
+  }
62
+ }
63
+
64
+ echo '<h3>Class Performance</h3>';
65
+
66
+ $classes = array();
67
+ $sql = 'SELECT class_id, class_name, COUNT(*) as num FROM classes NATURAL JOIN roleperiods NATURAL JOIN sessions NATURAL JOIN games WHERE map_id = ' . MAP . ' GROUP BY class_id ORDER BY class_name';
68
+ $res = mysql_query($sql) or print(mysql_error());
69
+
70
+ while ($row = mysql_fetch_assoc($res)) {
71
+  $classes[$row['class_id']] = array(
72
+   'name' => $row['class_name'],
73
+   'data1' => $row['num']
74
+  );
75
+ }
76
+
77
+ $sql = 'SELECT class_id, COUNT(*) AS num FROM roleperiods NATURAL JOIN classes NATURAL JOIN sessions NATURAL JOIN games INNER JOIN kills ON kill_killer = roleperiod_id WHERE map_id = ' . MAP . ' GROUP BY class_id';
78
+ $res = mysql_query($sql) or print(mysql_error());
79
+
80
+ while ($row = mysql_fetch_assoc($res)) {
81
+  $classes[$row['class_id']]['data2'] = $row['num'];
82
+ }
83
+
84
+ showClassesTable($classes, 'Times played', 'Kills made');
85
+
86
+ echo '</div>';
87
+
88
+ require_once('inc/footer.php');
89
+
90
+?>

+ 48
- 0
www/maps.php View File

@@ -0,0 +1,48 @@
1
+<?PHP
2
+
3
+ require_once('inc/config.php');
4
+ require_once('inc/mostmaps.php');
5
+ require_once(STATS_DIR . '/inc/database.php');
6
+
7
+ define('TITLE', 'Maps');
8
+
9
+ require_once('inc/header.php');
10
+
11
+ echo '<h2>Maps</h2>';
12
+ echo '<div class="left">';
13
+ echo '<h3>Most played</h3>';
14
+ showMostMaps();
15
+ echo '<h3 class="extra">Most kills per minute</h3>';
16
+ showMostMapsSQL('SELECT map_name, (COUNT(*)/SUM(TIMESTAMPDIFF(MINUTE, game_starttime, game_endtime))) AS kills FROM maps NATURAL JOIN games NATURAL JOIN sessions NATURAL JOIN roleperiods INNER JOIN kills ON roleperiod_id = kill_killer WHERE session_endtime > \'0000-00-00\' GROUP BY map_name ORDER BY kills DESC LIMIT 0,3');
17
+ echo '<h3 class="extra">Most recently played</h3>';
18
+ showMostMapsSQL('SELECT map_name FROM maps NATURAL JOIN games ORDER BY game_endtime DESC LIMIT 0,3');
19
+
20
+ echo '</div>';
21
+
22
+ echo '<div class="right">';
23
+ echo '<h3>Full map list</h3>';
24
+
25
+ echo '<table>';
26
+ echo '<tr><th>Map</th><th>Times Played</th><th>Average Session Length</th><th>Last Played</th></tr>';
27
+
28
+ $sql = 'SELECT map_name, COUNT(*) AS times, AVG(TIMESTAMPDIFF(MINUTE, game_starttime, game_endtime)) AS length, MAX(UNIX_TIMESTAMP(game_endtime)) AS last FROM maps NATURAL JOIN games GROUP BY map_name ORDER BY map_name';
29
+ $res = mysql_query($sql);
30
+ $i = 0;
31
+
32
+ while ($row = mysql_fetch_assoc($res)) {
33
+  $i++;
34
+  echo '<tr class="', ($i & 1 ? '' : 'even'), '">';
35
+  echo '<td><a href="map.php?map=', htmlentities($row['map_name']), '">';
36
+  echo htmlentities($row['map_name']), '</a></td>';
37
+  echo '<td>', $row['times'], '</td>';
38
+  echo '<td>', $row['length'], '</td>';
39
+  echo '<td>', date('r', $row['last']), '</td>';
40
+  echo '</tr>';
41
+ }
42
+ echo '</table>';
43
+
44
+ echo '</div>';
45
+
46
+ require_once('inc/footer.php');
47
+
48
+?>

+ 178
- 0
www/player.php View File

@@ -0,0 +1,178 @@
1
+<?PHP
2
+
3
+ require_once('inc/config.php');
4
+ require_once('inc/common.php');
5
+ require_once('inc/ext.php');
6
+ require_once('inc/classestable.php');
7
+ require_once('inc/eventhistory.php');
8
+ require_once('inc/mostmaps.php');
9
+ require_once('inc/playertable.php');
10
+ require_once('inc/weaponslist.php');
11
+ require_once(STATS_DIR . '/inc/database.php');
12
+
13
+ $sql = 'SELECT player_steamid, player_rank, player_score, session_alias FROM players NATURAL JOIN sessions WHERE player_id = \'' . s($_GET['id']) . '\' ORDER BY session_endtime DESC LIMIT 0,1';
14
+ $res = mysql_query($sql);
15
+
16
+ if (mysql_num_rows($res) == 0) {
17
+  require('404.php'); 
18
+  exit;
19
+ }
20
+
21
+ $row = mysql_fetch_assoc($res);
22
+
23
+ define('PLAYER', (int) $_GET['id']); 
24
+ define('RANK', (int) $row['player_rank']);
25
+ define('STEAMID', $row['player_steamid']);
26
+ define('NAME', $row['session_alias']); 
27
+ define('SCORE', (int) $row['player_score']);
28
+
29
+ if (isset($_GET['class'])) {
30
+  $sql = 'SELECT class_id, class_displayname FROM classes WHERE class_name = \'' . s($_GET['class']) . '\'';
31
+  $res = mysql_query($sql);
32
+
33
+  if (mysql_num_rows($res) == 0) {
34
+   require('404.php');
35
+   exit;
36
+  }
37
+
38
+  $row = mysql_fetch_assoc($res);
39
+
40
+  define('CLASSID', $row['class_id']);
41
+  define('CLASSNAME', $row['class_displayname']);
42
+ }
43
+
44
+ define('TITLE', 'Player information :: ' . htmlentities(NAME, ENT_COMPAT, 'UTF-8')
45
+	 . (defined('CLASSID') ? ' :: ' . CLASSNAME : ''));
46
+ require_once('inc/header.php');
47
+
48
+ echo '<h2>Player Information: ', htmlentities(NAME, ENT_COMPAT, 'UTF-8'), 
49
+	defined('CLASSID') ? ' &raquo; <img src="' . sprintf(URL_CLASS, 'blue', $_GET['class']) . '">' . CLASSNAME : '', '</h2>';
50
+
51
+
52
+ if (!isset($_GET['eventhistory'])) {
53
+
54
+  echo '<div class="left">';
55
+
56
+  echo '<h3>Player links</h3>';
57
+
58
+  if (defined('CLASSID')) {
59
+   echo '<a class="blocklink" href="?id=', htmlentities(PLAYER), '">Player overview</a>';
60
+  }
61
+  
62
+  echo '<a class="blocklink" href="?id=', htmlentities(PLAYER), '&amp;eventhistory">Event History</a>';
63
+
64
+  if (ENABLE_COMMUNITY_LINKS) {
65
+   require_once('inc/community.php');
66
+
67
+   echo '<a class="blocklink" href="http://steamcommunity.com/profiles/', getCommunityID(STEAMID), '">Steam Community Profile</a>';
68
+  }
69
+
70
+  if (ENABLE_GROUPS) {
71
+   $sql = 'SELECT group_id, group_name FROM groupmemberships NATURAL JOIN groups WHERE player_id = ' . PLAYER;
72
+   $res = mysql_query($sql);
73
+
74
+   if (mysql_num_rows($res) > 0) {
75
+    $header = false;
76
+
77
+    while ($row = mysql_fetch_assoc($res)) {
78
+
79
+     $sql2 = 'SELECT COUNT(*) FROM groupmemberships WHERE group_id = ' . $row['group_id'];
80
+     $res2 = mysql_query($sql2);
81
+     $row2 = mysql_fetch_array($res2);
82
+     $total = $row2[0];
83
+
84
+     if ($total == 1) { continue; }
85
+
86
+     if (!$header) {
87
+      echo '<h3>Group affiliations</h3>';
88
+      echo '<ul>';
89
+      $header = true;
90
+     }
91
+
92
+     $sql2 = 'SELECT COUNT(*) FROM groupmemberships NATURAL JOIN players WHERE group_id = ' . $row['group_id'] . ' AND player_score > ' . SCORE;
93
+     $res2 = mysql_query($sql2);
94
+     $row2 = mysql_fetch_array($res2);
95
+     $place = $row2[0];
96
+
97
+     echo '<li><a href="', 'group.php?group=', $row['group_id'], '">', htmlentities($row['group_name'], ENT_QUOTES, 'UTF-8'), '</a> &mdash; ';
98
+     echo 'ranked ', number_format($place), '<sup>', getSuffix($place), '</sup> out of ', number_format($total), '</li>';
99
+    }
100
+
101
+    if ($header) {
102
+     echo '</ul>';
103
+    }
104
+   }
105
+  }
106
+
107
+  echo '<h3>Favourite maps</h3>';
108
+
109
+  showMostMaps(defined('CLASSID') ? 'INNER JOIN roleperiods ON (roleperiods.session_id = sessions.session_id)' : '', 'sessions.player_id = ' . PLAYER . (defined('CLASSID') ? ' AND roleperiods.class_id = ' . CLASSID : ''));
110
+
111
+  $extra = true;
112
+
113
+  echo '<h3 class="extra">Top Weapons</h3>';
114
+  showWeaponsList('INNER JOIN roleperiods ON roleperiod_id = kill_killer WHERE player_id = ' . PLAYER . (defined('CLASSID') ? ' AND class_id = ' . CLASSID : ''));
115
+
116
+  echo '</div><div class="right">';
117
+
118
+  echo '<h3>Ranking</h3>';
119
+
120
+  $min = max(0, RANK - 3); $max = 5 + min(RANK - 3, 0); 
121
+  showPlayerTable('', '1=1', $min . ',' . $max, false, false, PLAYER);
122
+
123
+  echo '<h3>Class Stats</h3>';
124
+
125
+  $classes = array();
126
+
127
+  $sql = 'SELECT rp1.class_id, class_name, COUNT(*) as num FROM classes NATURAL JOIN roleperiods AS rp1 INNER JOIN kills AS k1 ON kill_victim = roleperiod_id INNER JOIN roleperiods AS rp2 ON kill_killer = rp2.roleperiod_id WHERE rp2.player_id = ' . PLAYER . (defined('CLASSID') ? ' AND rp2.class_id = ' . CLASSID : '') . ' GROUP BY class_id';
128
+  $res = mysql_query($sql) or print(mysql_error());
129
+
130
+ while ($row = mysql_fetch_assoc($res)) {
131
+  $classes[$row['class_id']]['name'] = $row['class_name'];
132
+  $classes[$row['class_id']]['data1'] = $row['num'];
133
+ }
134
+
135
+ $sql = 'SELECT rp1.class_id, class_name, COUNT(*) as num FROM classes NATURAL JOIN roleperiods AS rp1 INNER JOIN kills AS k1 ON kill_killer = rp1.roleperiod_id INNER JOIN roleperiods AS rp2 ON kill_victim = rp2.roleperiod_id WHERE rp2.player_id = ' . PLAYER . (defined('CLASSID') ? ' AND rp2.class_id = ' . CLASSID : '') . ' GROUP BY class_id';
136
+ $res = mysql_query($sql) or print(mysql_error());
137
+
138
+ while ($row = mysql_fetch_assoc($res)) {
139
+  $classes[$row['class_id']]['data2'] = $row['num'];
140
+  $classes[$row['class_id']]['name'] = $row['class_name'];
141
+ }
142
+
143
+ if (!defined('CLASSID')) {
144
+  $sql = 'SELECT rp1.class_id, class_name, COUNT(*) AS num FROM classes NATURAL JOIN roleperiods AS rp1 INNER JOIN kills AS k1 ON kill_killer = rp1.roleperiod_id WHERE rp1.player_id = ' . PLAYER . ' GROUP BY class_id';
145
+  $res = mysql_query($sql) or print(mysql_error());
146
+
147
+  while ($row = mysql_fetch_assoc($res)) {
148
+   $classes[$row['class_id']]['data3'] = $row['num'];
149
+   $classes[$row['class_id']]['name'] = $row['class_name'];
150
+  }
151
+
152
+  $sql = 'SELECT rp1.class_id, class_name, COUNT(*) AS num FROM classes NATURAL JOIN roleperiods AS rp1 INNER JOIN kills AS k1 ON kill_victim = rp1.roleperiod_id WHERE rp1.player_id = ' . PLAYER . ' GROUP BY class_id';
153
+  $res = mysql_query($sql) or print(mysql_error());
154
+
155
+  while ($row = mysql_fetch_assoc($res)) {
156
+   $classes[$row['class_id']]['data4'] = $row['num'];
157
+   $classes[$row['class_id']]['name'] = $row['class_name'];
158
+  }
159
+ 
160
+  showClassesTable($classes, 'Victim of ' . htmlentities(NAME, ENT_COMPAT, 'UTF-8'), 
161
+			    'Killer of ' . htmlentities(NAME, ENT_COMPAT, 'UTF-8'), true,
162
+                            htmlentities(NAME, ENT_COMPAT, 'UTF-8') . '\'s kills',
163
+                            htmlentities(NAME, ENT_COMPAT, 'UTF-8') . '\'s deaths', true);
164
+ } else {
165
+  showClassesTable($classes, 'Victim of ' . htmlentities(NAME, ENT_COMPAT, 'UTF-8'),
166
+                            'Killer of ' . htmlentities(NAME, ENT_COMPAT, 'UTF-8'), true);
167
+ }
168
+
169
+ echo '</div>';
170
+
171
+ } else {
172
+  echo '<h3>Event History</h3>';
173
+  showEventHistory(PLAYER);
174
+ }
175
+
176
+ require_once('inc/footer.php');
177
+
178
+?>

+ 79
- 0
www/players.php View File

@@ -0,0 +1,79 @@
1
+<?PHP
2
+
3
+ require_once('inc/config.php');
4
+ require_once('inc/awards.php');
5
+ require_once('inc/playertable.php');
6
+ require_once(STATS_DIR . '/inc/database.php');
7
+
8
+ define('TITLE', 'Players');
9
+
10
+ require_once('inc/header.php');
11
+
12
+ echo '<h2>Players</h2>';
13
+
14
+ echo '<div class="left">', "\n";
15
+
16
+ /** -- Search box -- **/
17
+ echo '<h3>Find a player</h3>', "\n";
18
+
19
+ echo '<p>Enter part of a player\'s name or Steam ID:</p>';
20
+
21
+ echo '<form action="', URL_BASE, 'players.php" method="get">';
22
+ echo '<table class="form">';
23
+ echo '<tr><th><label for="alias">Alias:</label></th><td>';
24
+ echo '<input type="text" name="alias"', (isset($_REQUEST['alias']) ? ' value="' . htmlentities($_REQUEST['alias'], ENT_COMPAT, 'UTF-8') . '"' : ''), '></td></tr>';
25
+ echo '<tr><th><label for="steamid">Steam ID:</label></th><td>';
26
+ echo '<input type="text" name="steamid"', (isset($_REQUEST['steamid']) ? ' value="' . htmlentities($_REQUEST['steamid']) . '"' : ''), '></td></tr>';
27
+ echo '<tr><th></th><td><input type="submit" value="Search"></td></tr>';
28
+ echo '</table>';
29
+ echo '</form>';
30
+
31
+ if (ENABLE_AWARDS) {
32
+  echo '<h3>Award winners</h3>';
33
+
34
+  $sql = 'SELECT award_id, award_name, award_displayname FROM awards ORDER BY award_displayname';
35
+  $res = mysql_query($sql);
36
+
37
+  echo '<ul class="awards">';
38
+  while ($row = mysql_fetch_assoc($res)) {
39
+   echo '<li>';
40
+   echo '<img src="', sprintf(URL_AWARD, $row['award_name']), '" alt="', $row['award_displayname'], '">';
41
+   echo '<strong>', $row['award_displayname'], '</strong>';
42
+
43
+   $sql2 = '
44
+        SELECT  awardwinners.player_id,
45
+                session_alias
46
+        FROM    awardwinners
47
+                NATURAL JOIN players
48
+                LEFT OUTER JOIN sessions ON players.player_id = sessions.player_id
49
+        WHERE   award_id = ' . $row['award_id'] . '
50
+        ORDER BY winner_awarded DESC
51
+        LIMIT 0,1';
52
+
53
+   $res2 = mysql_query($sql2) or print(mysql_error());
54
+   $row2 = mysql_fetch_assoc($res2);
55
+   echo '<a href="player.php?id=' . $row2['player_id'] . '">' . htmlentities($row2['session_alias'], ENT_COMPAT, 'UTF-8') . '</a>';
56
+   echo '</li>';
57
+  }
58
+
59
+  echo '</ul>';
60
+ }
61
+
62
+ echo '</div>', "\n";
63
+ echo '<div class="right">', "\n";
64
+
65
+ /** -- Output top players -- **/
66
+
67
+ if ((isset($_REQUEST['alias']) && !empty($_REQUEST['alias'])) || (isset($_REQUEST['steamid']) && !empty($_REQUEST['steamid']))) {
68
+  echo '<h3>Search results</h3>';
69
+  showPlayerTable('', 'session_alias LIKE \'%' . s($_REQUEST['alias']) . '%\' AND player_steamid LIKE \'%' . s($_REQUEST['steamid']) . '%\'', 20, false, true);
70
+ } else {
71
+  echo '<h3>Top players</h3>';
72
+  showPlayerTable('', '1=1', ((isset($_GET['start']) && ctype_digit($_GET['start'])) ? $_GET['start'] : 0) . ',20');
73
+ }
74
+ 
75
+ echo '</div>', "\n";
76
+
77
+ require_once('inc/footer.php');
78
+
79
+?>

BIN
www/res/TF2.png View File


BIN
www/res/awards/brilliant_backstabber.png View File


BIN
www/res/awards/crazy_capturer.png View File


BIN
www/res/awards/dastardly_defender.png View File


BIN
www/res/awards/deadly_dominator.png View File


BIN
www/res/awards/extravagant_engy.png View File


BIN
www/res/awards/happy_headshotter.png View File


BIN
www/res/awards/mad_medic.png View File


BIN
www/res/awards/melee_maniac.png View File


BIN
www/res/awards/noimage.png View File


BIN
www/res/awards/persistent.png View File


BIN
www/res/awards/persistent_player.png View File


BIN
www/res/awards/revenge.png View File


BIN
www/res/awards/terrific_taunter.png View File


BIN
www/res/classes/large/blue/demoman.png View File


BIN
www/res/classes/large/blue/engineer.png View File


BIN
www/res/classes/large/blue/heavyweapons.png View File


BIN
www/res/classes/large/blue/medic.png View File


BIN
www/res/classes/large/blue/pyro.png View File


BIN
www/res/classes/large/blue/scout.png View File


BIN
www/res/classes/large/blue/sniper.png View File


BIN
www/res/classes/large/blue/soldier.png View File


BIN
www/res/classes/large/blue/spy.png View File


BIN
www/res/classes/large/classes.tgz View File


BIN
www/res/classes/large/red/demoman.png View File


BIN
www/res/classes/large/red/engineer.png View File


BIN
www/res/classes/large/red/heavyweapons.png View File


BIN
www/res/classes/large/red/medic.png View File


BIN
www/res/classes/large/red/pyro.png View File


BIN
www/res/classes/large/red/scout.png View File


BIN
www/res/classes/large/red/sniper.png View File


BIN
www/res/classes/large/red/soldier.png View File


BIN
www/res/classes/large/red/spy.png View File


BIN
www/res/classes/small/SmallClasses.tar.gz View File


BIN
www/res/classes/small/blue/demoman.png View File


BIN
www/res/classes/small/blue/engineer.png View File


+ 0
- 0
www/res/classes/small/blue/heavyweapons.png View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save