diff --git a/xaseco/access.xml b/xaseco/access.xml
new file mode 100644
index 0000000..9b57d03
--- /dev/null
+++ b/xaseco/access.xml
@@ -0,0 +1,93 @@
+
+
+
+ Deny,Allow
+
+
+ all
+
+
+
+
+
+
+
+ {#server}>> {#message}Player {#highlite}{1}$z$s{#message} denied access from {2} {#highlite}{3}{#message} [{#error}Kicked $z$s{#message}]
+
+ {#server}> Player access control reloaded from {#highlite}access.xml
+ {#server}> {#highlite}access.xml {#error}config error, player access control disabled!
+ {#server}> {#error}Missing parameter, use {#highlite}$i /admin access help {#error}!
+
+
diff --git a/xaseco/adminops.xml b/xaseco/adminops.xml
new file mode 100644
index 0000000..2dd5f20
--- /dev/null
+++ b/xaseco/adminops.xml
@@ -0,0 +1,318 @@
+
+
+
+ MasterAdmin
+ Admin
+ Operator
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+ true
+ true
+ true
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ false
+ false
+ true
+ true
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+
+ false
+ false
+ false
+ false
+ true
+ false
+ true
+ true
+ true
+ true
+ false
+ true
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ false
+ false
+ false
+ true
+ true
+ false
+ true
+ true
+
+
+
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ true
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+
+
diff --git a/xaseco/aseco.php b/xaseco/aseco.php
new file mode 100644
index 0000000..b0715bb
--- /dev/null
+++ b/xaseco/aseco.php
@@ -0,0 +1,2562 @@
+
+ *
+ * Re-authored & copyright May 2007 - Jul 2013 by Xymph
+ *
+ * Visit the official site at http://www.xaseco.org/
+ */
+
+/**
+ * Include required classes
+ */
+require_once('includes/types.inc.php'); // contains classes to store information
+require_once('includes/basic.inc.php'); // contains standard functions
+require_once('includes/GbxRemote.inc.php'); // needed for dedicated server connections
+require_once('includes/xmlparser.inc.php'); // provides an XML parser
+require_once('includes/gbxdatafetcher.inc.php'); // provides access to GBX data
+require_once('includes/tmndatafetcher.inc.php'); // provides access to TMN world stats
+require_once('rasp.settings.php'); // specific to the RASP plugins
+
+/**
+ * Runtime configuration definitions
+ */
+
+// add abbreviations for some chat commands?
+// /admin -> /ad, /jukebox -> /jb, /autojuke -> /aj
+define('ABBREV_COMMANDS', false);
+// disable local & Dedi record relations commands from help lists?
+define('INHIBIT_RECCMDS', false);
+// separate logs by month in logs/ dir?
+define('MONTHLY_LOGSDIR', false);
+// keep UTF-8 encoding in config.xml?
+define('CONFIG_UTF8ENCODE', false);
+
+/**
+ * System definitions - no changes below this point
+ */
+
+// current project version
+define('XASECO_VERSION', '1.16');
+define('XASECO_TMN', 'http://www.gamers.org/tmn/');
+define('XASECO_TMF', 'http://www.gamers.org/tmf/');
+define('XASECO_TM2', 'http://www.gamers.org/tm2/');
+define('XASECO_ORG', 'http://www.xaseco.org/');
+
+// required official dedicated server builds
+define('TMN_BUILD', '2006-05-30');
+define('TMF_BUILD', '2011-02-21');
+
+// check current operating system
+if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ // on Win32/NT use:
+ define('CRLF', "\r\n");
+} else {
+ // on Unix use:
+ define('CRLF', "\n");
+}
+if (!defined('LF')) {
+ define('LF', "\n");
+}
+
+/**
+ * Error function
+ * Report errors in a regular way.
+ */
+set_error_handler('displayError');
+function displayError($errno, $errstr, $errfile, $errline) {
+ global $aseco;
+
+ // check for error suppression
+ if (error_reporting() == 0) return;
+
+ switch ($errno) {
+ case E_USER_ERROR:
+ $message = "[XASECO Fatal Error] $errstr on line $errline in file $errfile" . CRLF;
+ echo $message;
+ doLog($message);
+
+ // throw 'shutting down' event
+ $aseco->releaseEvent('onShutdown', null);
+ // clear all ManiaLinks
+ $aseco->client->query('SendHideManialinkPage');
+
+ if (function_exists('xdebug_get_function_stack'))
+ doLog(print_r(xdebug_get_function_stack()), true);
+ die();
+ break;
+ case E_USER_WARNING:
+ $message = "[XASECO Warning] $errstr" . CRLF;
+ echo $message;
+ doLog($message);
+ break;
+ case E_ERROR:
+ $message = "[PHP Error] $errstr on line $errline in file $errfile" . CRLF;
+ echo $message;
+ doLog($message);
+ break;
+ case E_WARNING:
+ $message = "[PHP Warning] $errstr on line $errline in file $errfile" . CRLF;
+ echo $message;
+ doLog($message);
+ break;
+ default:
+ if (strpos($errstr, 'Function call_user_method') !== false) break;
+ //$message = "[PHP $errno] $errstr on line $errline in file $errfile" . CRLF;
+ //echo $message;
+ //doLog($message);
+ // do nothing, only treat known errors
+ }
+} // displayError
+
+/**
+ * Here XASECO actually starts.
+ */
+class Aseco {
+
+ /**
+ * Public fields
+ */
+ var $client;
+ var $xml_parser;
+ var $script_timeout;
+ var $debug;
+ var $server;
+ var $command;
+ var $events;
+ var $rpc_calls;
+ var $rpc_responses;
+ var $chat_commands;
+ var $chat_colors;
+ var $chat_messages;
+ var $plugins;
+ var $settings;
+ var $style;
+ var $panels;
+ var $statspanel;
+ var $titles;
+ var $masteradmin_list;
+ var $admin_list;
+ var $adm_abilities;
+ var $operator_list;
+ var $op_abilities;
+ var $bannedips;
+ var $startup_phase; // XAseco start-up phase
+ var $warmup_phase; // warm-up phase
+ var $restarting; // restarting challenge (0 = not, 1 = instant, 2 = chattime)
+ var $changingmode; // changing game mode
+ var $currstatus; // server status changes
+ var $prevstatus;
+ var $currsecond; // server time changes
+ var $prevsecond;
+ var $uptime; // XAseco start-up time
+
+
+ /**
+ * Initializes the server.
+ */
+ function Aseco($debug) {
+ global $maxrecs; // from rasp.settings.php
+
+ echo '# initialize XASECO ###########################################################' . CRLF;
+
+ // log php & mysql version info
+ $this->console_text('[XAseco] PHP Version is ' . phpversion() . ' on ' . PHP_OS);
+
+ // initialize
+ $this->uptime = time();
+ $this->chat_commands = array();
+ $this->debug = $debug;
+ $this->client = new IXR_ClientMulticall_Gbx();
+ $this->xml_parser = new Examsly();
+ $this->server = new Server('127.0.0.1', 5000, 'SuperAdmin', 'SuperAdmin');
+ $this->server->challenge = new Challenge();
+ $this->server->players = new PlayerList();
+ $this->server->records = new RecordList($maxrecs);
+ $this->server->mutelist = array();
+ $this->plugins = array();
+ $this->titles = array();
+ $this->masteradmin_list = array();
+ $this->admin_list = array();
+ $this->adm_abilities = array();
+ $this->operator_list = array();
+ $this->op_abilities = array();
+ $this->bannedips = array();
+ $this->startup_phase = true;
+ $this->warmup_phase = false;
+ $this->restarting = 0;
+ $this->changingmode = false;
+ $this->currstatus = 0;
+ } // Aseco
+
+
+ /**
+ * Load settings and apply them on the current instance.
+ */
+ function loadSettings($config_file) {
+
+ if ($settings = $this->xml_parser->parseXml($config_file, true, CONFIG_UTF8ENCODE)) {
+ // read the XML structure into an array
+ $aseco = $settings['SETTINGS']['ASECO'][0];
+
+ // read settings and apply them
+ $this->chat_colors = $aseco['COLORS'][0];
+ $this->chat_messages = $aseco['MESSAGES'][0];
+ $this->masteradmin_list = $aseco['MASTERADMINS'][0];
+ if (!isset($this->masteradmin_list) || !is_array($this->masteradmin_list))
+ trigger_error('No MasterAdmin(s) configured in config.xml!', E_USER_ERROR);
+
+ // check masteradmin list consistency
+ if (empty($this->masteradmin_list['IPADDRESS'])) {
+ // fill list to same length as list
+ if (($cnt = count($this->masteradmin_list['TMLOGIN'])) > 0)
+ $this->masteradmin_list['IPADDRESS'] = array_fill(0, $cnt, '');
+ } else {
+ if (count($this->masteradmin_list['TMLOGIN']) != count($this->masteradmin_list['IPADDRESS']))
+ trigger_error("MasterAdmin mismatch between 's and 's!", E_USER_WARNING);
+ }
+
+ // set admin lock password
+ $this->settings['lock_password'] = $aseco['LOCK_PASSWORD'][0];
+ // set cheater action
+ $this->settings['cheater_action'] = $aseco['CHEATER_ACTION'][0];
+ // set script timeout
+ $this->settings['script_timeout'] = $aseco['SCRIPT_TIMEOUT'][0];
+ // set minimum number of records to be displayed
+ $this->settings['show_min_recs'] = $aseco['SHOW_MIN_RECS'][0];
+ // show records before start of track?
+ $this->settings['show_recs_before'] = $aseco['SHOW_RECS_BEFORE'][0];
+ // show records after end of track?
+ $this->settings['show_recs_after'] = $aseco['SHOW_RECS_AFTER'][0];
+ // show TMX world record?
+ $this->settings['show_tmxrec'] = $aseco['SHOW_TMXREC'][0];
+ // show played time at end of track?
+ $this->settings['show_playtime'] = $aseco['SHOW_PLAYTIME'][0];
+ // show current track at start of track?
+ $this->settings['show_curtrack'] = $aseco['SHOW_CURTRACK'][0];
+ // set default filename for readtracklist/writetracklist
+ $this->settings['default_tracklist'] = $aseco['DEFAULT_TRACKLIST'][0];
+ // set minimum number of ranked players in a clan to be included in /topclans
+ $this->settings['topclans_minplayers'] = $aseco['TOPCLANS_MINPLAYERS'][0];
+ // set multiple of win count to show global congrats message
+ $this->settings['global_win_multiple'] = ($aseco['GLOBAL_WIN_MULTIPLE'][0] > 0 ? $aseco['GLOBAL_WIN_MULTIPLE'][0] : 1);
+ // timeout of the TMF message window in seconds
+ $this->settings['window_timeout'] = $aseco['WINDOW_TIMEOUT'][0];
+ // set filename of admin/operator/ability lists file
+ $this->settings['adminops_file'] = $aseco['ADMINOPS_FILE'][0];
+ // set filename of banned IPs list file
+ $this->settings['bannedips_file'] = $aseco['BANNEDIPS_FILE'][0];
+ // set filename of blacklist file
+ $this->settings['blacklist_file'] = $aseco['BLACKLIST_FILE'][0];
+ // set filename of guestlist file
+ $this->settings['guestlist_file'] = $aseco['GUESTLIST_FILE'][0];
+ // set filename of track history file
+ $this->settings['trackhist_file'] = $aseco['TRACKHIST_FILE'][0];
+ // set minimum admin client version
+ $this->settings['admin_client'] = $aseco['ADMIN_CLIENT_VERSION'][0];
+ // set minimum player client version
+ $this->settings['player_client'] = $aseco['PLAYER_CLIENT_VERSION'][0];
+ // set default rounds points system
+ $this->settings['default_rpoints'] = $aseco['DEFAULT_RPOINTS'][0];
+ // set windows style (none = old TMN style)
+ $this->settings['window_style'] = $aseco['WINDOW_STYLE'][0];
+ // set admin panel (none = no panel)
+ $this->settings['admin_panel'] = $aseco['ADMIN_PANEL'][0];
+ // set donate panel (none = no panel)
+ $this->settings['donate_panel'] = $aseco['DONATE_PANEL'][0];
+ // set records panel (none = no panel)
+ $this->settings['records_panel'] = $aseco['RECORDS_PANEL'][0];
+ // set vote panel (none = no panel)
+ $this->settings['vote_panel'] = $aseco['VOTE_PANEL'][0];
+
+ // display welcome message as window ?
+ if (strtoupper($aseco['WELCOME_MSG_WINDOW'][0]) == 'TRUE') {
+ $this->settings['welcome_msg_window'] = true;
+ } else {
+ $this->settings['welcome_msg_window'] = false;
+ }
+
+ // log all chat, not just chat commands ?
+ if (strtoupper($aseco['LOG_ALL_CHAT'][0]) == 'TRUE') {
+ $this->settings['log_all_chat'] = true;
+ } else {
+ $this->settings['log_all_chat'] = false;
+ }
+
+ // show timestamps in /chatlog, /pmlog & /admin pmlog ?
+ if (strtoupper($aseco['CHATPMLOG_TIMES'][0]) == 'TRUE') {
+ $this->settings['chatpmlog_times'] = true;
+ } else {
+ $this->settings['chatpmlog_times'] = false;
+ }
+
+ // show records range?
+ if (strtoupper($aseco['SHOW_RECS_RANGE'][0]) == 'TRUE') {
+ $this->settings['show_recs_range'] = true;
+ } else {
+ $this->settings['show_recs_range'] = false;
+ }
+
+ // show records in message window?
+ if (strtoupper($aseco['RECS_IN_WINDOW'][0]) == 'TRUE') {
+ $this->settings['recs_in_window'] = true;
+ } else {
+ $this->settings['recs_in_window'] = false;
+ }
+
+ // show round reports in message window?
+ if (strtoupper($aseco['ROUNDS_IN_WINDOW'][0]) == 'TRUE') {
+ $this->settings['rounds_in_window'] = true;
+ } else {
+ $this->settings['rounds_in_window'] = false;
+ }
+
+ // add random filter to /admin writetracklist output
+ if (strtoupper($aseco['WRITETRACKLIST_RANDOM'][0]) == 'TRUE') {
+ $this->settings['writetracklist_random'] = true;
+ } else {
+ $this->settings['writetracklist_random'] = false;
+ }
+
+ // add explanation to /help output
+ if (strtoupper($aseco['HELP_EXPLANATION'][0]) == 'TRUE') {
+ $this->settings['help_explanation'] = true;
+ } else {
+ $this->settings['help_explanation'] = false;
+ }
+
+ // color nicknames in the various /top... etc lists?
+ if (strtoupper($aseco['LISTS_COLORNICKS'][0]) == 'TRUE') {
+ $this->settings['lists_colornicks'] = true;
+ } else {
+ $this->settings['lists_colornicks'] = false;
+ }
+
+ // color tracknames in the various /lists... lists?
+ if (strtoupper($aseco['LISTS_COLORTRACKS'][0]) == 'TRUE') {
+ $this->settings['lists_colortracks'] = true;
+ } else {
+ $this->settings['lists_colortracks'] = false;
+ }
+
+ // display checkpoints panel (TMF) or pop-up (TMN)?
+ if (strtoupper($aseco['DISPLAY_CHECKPOINTS'][0]) == 'TRUE') {
+ $this->settings['display_checkpoints'] = true;
+ } else {
+ $this->settings['display_checkpoints'] = false;
+ }
+
+ // enable /cpsspec command (TMF-only)?
+ if (strtoupper($aseco['ENABLE_CPSSPEC'][0]) == 'TRUE') {
+ $this->settings['enable_cpsspec'] = true;
+ } else {
+ $this->settings['enable_cpsspec'] = false;
+ }
+
+ // automatically enable /cps for new players?
+ if (strtoupper($aseco['AUTO_ENABLE_CPS'][0]) == 'TRUE') {
+ $this->settings['auto_enable_cps'] = true;
+ } else {
+ $this->settings['auto_enable_cps'] = false;
+ }
+
+ // automatically enable /dedicps for new players?
+ if (strtoupper($aseco['AUTO_ENABLE_DEDICPS'][0]) == 'TRUE') {
+ $this->settings['auto_enable_dedicps'] = true;
+ } else {
+ $this->settings['auto_enable_dedicps'] = false;
+ }
+
+ // automatically add IP for new admins/operators?
+ if (strtoupper($aseco['AUTO_ADMIN_ADDIP'][0]) == 'TRUE') {
+ $this->settings['auto_admin_addip'] = true;
+ } else {
+ $this->settings['auto_admin_addip'] = false;
+ }
+
+ // automatically force spectator on player using /afk ?
+ if (strtoupper($aseco['AFK_FORCE_SPEC'][0]) == 'TRUE') {
+ $this->settings['afk_force_spec'] = true;
+ } else {
+ $this->settings['afk_force_spec'] = false;
+ }
+
+ // provide clickable buttons in TMF lists?
+ if (strtoupper($aseco['CLICKABLE_LISTS'][0]) == 'TRUE') {
+ $this->settings['clickable_lists'] = true;
+ } else {
+ $this->settings['clickable_lists'] = false;
+ }
+
+ // show logins in /recs on TMF?
+ if (strtoupper($aseco['SHOW_REC_LOGINS'][0]) == 'TRUE') {
+ $this->settings['show_rec_logins'] = true;
+ } else {
+ $this->settings['show_rec_logins'] = false;
+ }
+
+ // display individual stats panels at TMF scoreboard?
+ if (strtoupper($aseco['SB_STATS_PANELS'][0]) == 'TRUE') {
+ $this->settings['sb_stats_panels'] = true;
+ } else {
+ $this->settings['sb_stats_panels'] = false;
+ }
+
+ // read the XML structure into an array
+ $tmserver = $settings['SETTINGS']['TMSERVER'][0];
+
+ // read settings and apply them
+ $this->server->login = $tmserver['LOGIN'][0];
+ $this->server->pass = $tmserver['PASSWORD'][0];
+ $this->server->port = $tmserver['PORT'][0];
+ $this->server->ip = $tmserver['IP'][0];
+ if (isset($tmserver['TIMEOUT'][0])) {
+ $this->server->timeout = (int)$tmserver['TIMEOUT'][0];
+ } else {
+ $this->server->timeout = null;
+ trigger_error('Server init timeout not specified in config.xml !', E_USER_WARNING);
+ }
+
+ $this->style = array();
+ $this->panels = array();
+ $this->panels['admin'] = '';
+ $this->panels['donate'] = '';
+ $this->panels['records'] = '';
+ $this->panels['vote'] = '';
+
+ if ($this->settings['admin_client'] != '' &&
+ preg_match('/^2\.11\.[12][0-9]$/', $this->settings['admin_client']) != 1 ||
+ $this->settings['admin_client'] == '2.11.10')
+ trigger_error('Invalid admin client version : ' . $this->settings['admin_client'] . ' !', E_USER_ERROR);
+ if ($this->settings['player_client'] != '' &&
+ preg_match('/^2\.11\.[12][0-9]$/', $this->settings['player_client']) != 1 ||
+ $this->settings['player_client'] == '2.11.10')
+ trigger_error('Invalid player client version: ' . $this->settings['player_client'] . ' !', E_USER_ERROR);
+ } else {
+ // could not parse XML file
+ trigger_error('Could not read/parse config file ' . $config_file . ' !', E_USER_ERROR);
+ }
+ } // loadSettings
+
+
+ /**
+ * Read Admin/Operator/Ability lists and apply them on the current instance.
+ */
+ function readLists() {
+
+ // get lists file name
+ $adminops_file = $this->settings['adminops_file'];
+
+ if ($lists = $this->xml_parser->parseXml($adminops_file, true, true)) {
+ // read the XML structure into arrays
+ $this->titles = $lists['LISTS']['TITLES'][0];
+
+ if (is_array($lists['LISTS']['ADMINS'][0])) {
+ $this->admin_list = $lists['LISTS']['ADMINS'][0];
+ // check admin list consistency
+ if (empty($this->admin_list['IPADDRESS'])) {
+ // fill list to same length as list
+ if (($cnt = count($this->admin_list['TMLOGIN'])) > 0)
+ $this->admin_list['IPADDRESS'] = array_fill(0, $cnt, '');
+ } else {
+ if (count($this->admin_list['TMLOGIN']) != count($this->admin_list['IPADDRESS']))
+ trigger_error("Admin mismatch between 's and 's!", E_USER_WARNING);
+ }
+ }
+
+ if (is_array($lists['LISTS']['OPERATORS'][0])) {
+ $this->operator_list = $lists['LISTS']['OPERATORS'][0];
+ // check operator list consistency
+ if (empty($this->operator_list['IPADDRESS'])) {
+ // fill list to same length as list
+ if (($cnt = count($this->operator_list['TMLOGIN'])) > 0)
+ $this->operator_list['IPADDRESS'] = array_fill(0, $cnt, '');
+ } else {
+ if (count($this->operator_list['TMLOGIN']) != count($this->operator_list['IPADDRESS']))
+ trigger_error("Operators mismatch between 's and 's!", E_USER_WARNING);
+ }
+ }
+
+ $this->adm_abilities = $lists['LISTS']['ADMIN_ABILITIES'][0];
+ $this->op_abilities = $lists['LISTS']['OPERATOR_ABILITIES'][0];
+
+ // convert strings to booleans
+ foreach ($this->adm_abilities as $ability => $value) {
+ if (strtoupper($value[0]) == 'TRUE') {
+ $this->adm_abilities[$ability][0] = true;
+ } else {
+ $this->adm_abilities[$ability][0] = false;
+ }
+ }
+ foreach ($this->op_abilities as $ability => $value) {
+ if (strtoupper($value[0]) == 'TRUE') {
+ $this->op_abilities[$ability][0] = true;
+ } else {
+ $this->op_abilities[$ability][0] = false;
+ }
+ }
+ return true;
+ } else {
+ // could not parse XML file
+ trigger_error('Could not read/parse adminops file ' . $adminops_file . ' !', E_USER_WARNING);
+ return false;
+ }
+ } // readLists
+
+ /**
+ * Write Admin/Operator/Ability lists to save them for future runs.
+ */
+ function writeLists() {
+
+ // get lists file name
+ $adminops_file = $this->settings['adminops_file'];
+
+ // compile lists file contents
+ $lists = "" . CRLF
+ . "" . CRLF
+ . "\t" . CRLF;
+ foreach ($this->titles as $title => $value) {
+ $lists .= "\t\t<" . strtolower($title) . ">" .
+ $value[0]
+ . "" . strtolower($title) . ">" . CRLF;
+ }
+ $lists .= "\t" . CRLF
+ . CRLF
+ . "\t" . CRLF;
+ $empty = true;
+ if (isset($this->admin_list['TMLOGIN'])) {
+ for ($i = 0; $i < count($this->admin_list['TMLOGIN']); $i++) {
+ if ($this->admin_list['TMLOGIN'][$i] != '') {
+ $lists .= "\t\t" . $this->admin_list['TMLOGIN'][$i] . ""
+ . " " . $this->admin_list['IPADDRESS'][$i] . "" . CRLF;
+ $empty = false;
+ }
+ }
+ }
+ if ($empty) {
+ $lists .= "" . CRLF;
+ }
+ $lists .= "\t" . CRLF
+ . CRLF
+ . "\t" . CRLF;
+ $empty = true;
+ if (isset($this->operator_list['TMLOGIN'])) {
+ for ($i = 0; $i < count($this->operator_list['TMLOGIN']); $i++) {
+ if ($this->operator_list['TMLOGIN'][$i] != '') {
+ $lists .= "\t\t" . $this->operator_list['TMLOGIN'][$i] . ""
+ . " " . $this->operator_list['IPADDRESS'][$i] . "" . CRLF;
+ $empty = false;
+ }
+ }
+ }
+ if ($empty) {
+ $lists .= "" . CRLF;
+ }
+ $lists .= "\t" . CRLF
+ . CRLF
+ . "\t" . CRLF;
+ foreach ($this->adm_abilities as $ability => $value) {
+ $lists .= "\t\t<" . strtolower($ability) . ">" .
+ ($value[0] ? "true" : "false")
+ . "" . strtolower($ability) . ">" . CRLF;
+ }
+ $lists .= "\t" . CRLF
+ . CRLF
+ . "\t" . CRLF;
+ foreach ($this->op_abilities as $ability => $value) {
+ $lists .= "\t\t<" . strtolower($ability) . ">" .
+ ($value[0] ? "true" : "false")
+ . "" . strtolower($ability) . ">" . CRLF;
+ }
+ $lists .= "\t" . CRLF
+ . "" . CRLF;
+
+ // write out the lists file
+ if (!@file_put_contents($adminops_file, $lists)) {
+ trigger_error('Could not write adminops file ' . $adminops_file . ' !', E_USER_WARNING);
+ return false;
+ } else {
+ return true;
+ }
+ } // writeLists
+
+
+ /**
+ * Read Banned IPs list and apply it on the current instance.
+ */
+ function readIPs() {
+
+ // get banned IPs file name
+ $bannedips_file = $this->settings['bannedips_file'];
+
+ if ($list = $this->xml_parser->parseXml($bannedips_file)) {
+ // read the XML structure into variable
+ if (isset($list['BAN_LIST']['IPADDRESS']))
+ $this->bannedips = $list['BAN_LIST']['IPADDRESS'];
+ else
+ $this->bannedips = array();
+ return true;
+ } else {
+ // could not parse XML file
+ trigger_error('Could not read/parse banned IPs file ' . $bannedips_file . ' !', E_USER_WARNING);
+ return false;
+ }
+ } // readIPs
+
+ /**
+ * Write Banned IPs list to save it for future runs.
+ */
+ function writeIPs() {
+
+ // get banned IPs file name
+ $bannedips_file = $this->settings['bannedips_file'];
+ $empty = true;
+
+ // compile banned IPs file contents
+ $list = "" . CRLF
+ . "" . CRLF;
+ for ($i = 0; $i < count($this->bannedips); $i++) {
+ if ($this->bannedips[$i] != '') {
+ $list .= "\t\t" . $this->bannedips[$i] . "" . CRLF;
+ $empty = false;
+ }
+ }
+ if ($empty) {
+ $list .= "" . CRLF;
+ }
+ $list .= "" . CRLF;
+
+ // write out the list file
+ if (!@file_put_contents($bannedips_file, $list)) {
+ trigger_error('Could not write banned IPs file ' . $bannedips_file . ' !', E_USER_WARNING);
+ return false;
+ } else {
+ return true;
+ }
+ } // writeIPs
+
+
+ /**
+ * Loads files in the plugins directory.
+ */
+ function loadPlugins() {
+
+ // load and parse the plugins file
+ if ($plugins = $this->xml_parser->parseXml('plugins.xml')) {
+ if (!empty($plugins['ASECO_PLUGINS']['PLUGIN'])) {
+ // take each plugin tag
+ foreach ($plugins['ASECO_PLUGINS']['PLUGIN'] as $plugin) {
+ // log plugin message
+ $this->console_text('[XAseco] Load plugin [' . $plugin . ']');
+ // include the plugin
+ require_once('plugins/' . $plugin);
+ $this->plugins[] = $plugin;
+ }
+ }
+ } else {
+ trigger_error('Could not read/parse plugins list plugins.xml !', E_USER_ERROR);
+ }
+ } // loadPlugins
+
+
+ /**
+ * Runs the server.
+ */
+ function run($config_file) {
+
+ // load new settings, if available
+ $this->console_text('[XAseco] Load settings [{1}]', $config_file);
+ $this->loadSettings($config_file);
+
+ // load admin/operator/ability lists, if available
+ $this->console_text('[XAseco] Load admin/ops lists [{1}]', $this->settings['adminops_file']);
+ $this->readLists();
+
+ // load banned IPs list, if available
+ $this->console_text('[XAseco] Load banned IPs list [{1}]', $this->settings['bannedips_file']);
+ $this->readIPs();
+
+ // load plugins and register chat commands
+ $this->console_text('[XAseco] Load plugins list [plugins.xml]');
+ $this->loadPlugins();
+
+ // connect to Trackmania Dedicated Server
+ if (!$this->connect()) {
+ // kill program with an error
+ trigger_error('Connection could not be established !', E_USER_ERROR);
+ }
+
+ // log status message
+ $this->console('Connection established successfully !');
+ // log admin lock message
+ if ($this->settings['lock_password'] != '')
+ $this->console_text("[XAseco] Locked admin commands & features with password '{1}'", $this->settings['lock_password']);
+
+ // get basic server info
+ $this->client->query('GetVersion');
+ $response['version'] = $this->client->getResponse();
+ $this->server->game = $response['version']['Name'];
+ $this->server->version = $response['version']['Version'];
+ $this->server->build = $response['version']['Build'];
+
+ // throw 'starting up' event
+ $this->releaseEvent('onStartup', null);
+
+ // synchronize information with server
+ $this->serverSync();
+
+ // register all chat commands
+ if ($this->server->getGame() != 'TMF') {
+ $this->registerChatCommands();
+ // set spectator not available outside TMF
+ if ($this->settings['cheater_action'] == 1)
+ $this->settings['cheater_action'] = 0;
+ }
+
+ // make a visual header
+ $this->sendHeader();
+
+ // get current game infos if server loaded a track yet
+ if ($this->currstatus == 100) {
+ $this->console_text('[XAseco] Waiting for the server to start a challenge');
+ } else {
+ $this->beginRace(false);
+ }
+
+ // main loop
+ $this->startup_phase = false;
+ while (true) {
+ $starttime = microtime(true);
+ // get callbacks from the server
+ $this->executeCallbacks();
+
+ // sends calls to the server
+ $this->executeCalls();
+
+ // throw timing events
+ $this->releaseEvent('onMainLoop', null);
+
+ $this->currsecond = time();
+ if ($this->prevsecond != $this->currsecond) {
+ $this->prevsecond = $this->currsecond;
+ $this->releaseEvent('onEverySecond', null);
+ }
+
+ // reduce CPU usage if main loop has time left
+ $endtime = microtime(true);
+ $delay = 200000 - ($endtime - $starttime) * 1000000;
+ if ($delay > 0)
+ usleep($delay);
+ // make sure the script does not timeout
+ @set_time_limit($this->settings['script_timeout']);
+ }
+
+ // close the client connection
+ $this->client->Terminate();
+ } // run
+
+
+ /**
+ * Authenticates XASECO at the server.
+ */
+ function connect() {
+
+ // only if logins are set
+ if ($this->server->ip && $this->server->port && $this->server->login && $this->server->pass) {
+ // log console message
+ $this->console('Try to connect to TM dedicated server on {1}:{2} timeout {3}s',
+ $this->server->ip, $this->server->port,
+ ($this->server->timeout !== null ? $this->server->timeout : 0));
+
+ // connect to the server
+ if (!$this->client->InitWithIp($this->server->ip, $this->server->port, $this->server->timeout)) {
+ trigger_error('[' . $this->client->getErrorCode() . '] InitWithIp - ' . $this->client->getErrorMessage(), E_USER_WARNING);
+ return false;
+ }
+
+ // log console message
+ $this->console("Try to authenticate with login '{1}' and password '{2}'",
+ $this->server->login, $this->server->pass);
+
+ // check login
+ if ($this->server->login != 'SuperAdmin') {
+ trigger_error("Invalid login '" . $this->server->login . "' - must be 'SuperAdmin' in config.xml !", E_USER_WARNING);
+ return false;
+ }
+ // check password
+ if ($this->server->pass == 'SuperAdmin') {
+ trigger_error("Insecure password '" . $this->server->pass . "' - should be changed in dedicated config and config.xml !", E_USER_WARNING);
+ }
+
+ // log into the server
+ if (!$this->client->query('Authenticate', $this->server->login, $this->server->pass)) {
+ trigger_error('[' . $this->client->getErrorCode() . '] Authenticate - ' . $this->client->getErrorMessage(), E_USER_WARNING);
+ return false;
+ }
+
+ // enable callback system
+ $this->client->query('EnableCallbacks', true);
+
+ // wait for server to be ready
+ $this->waitServerReady();
+
+ // connection established
+ return true;
+ } else {
+ // connection failed
+ return false;
+ }
+ } // connect
+
+
+ /**
+ * Waits for the server to be ready (status 4, 'Running - Play')
+ */
+ function waitServerReady() {
+
+ $this->client->query('GetStatus');
+ $status = $this->client->getResponse();
+ if ($status['Code'] != 4) {
+ $this->console("Waiting for dedicated server to reach status 'Running - Play'...");
+ $this->console('Status: ' . $status['Name']);
+ $timeout = 0;
+ $laststatus = $status['Name'];
+ while ($status['Code'] != 4) {
+ sleep(1);
+ $this->client->query('GetStatus');
+ $status = $this->client->getResponse();
+ if ($laststatus != $status['Name']) {
+ $this->console('Status: ' . $status['Name']);
+ $laststatus = $status['Name'];
+ }
+ if (isset($this->server->timeout) && $timeout++ > $this->server->timeout)
+ trigger_error('Timed out while waiting for dedicated server!', E_USER_ERROR);
+ }
+ }
+ } // waitServerReady
+
+ /**
+ * Initializes the server and the player list.
+ * Reads a list of the players who are on the server already,
+ * and loads all server variables.
+ */
+ function serverSync() {
+
+ // check server build
+ if (strlen($this->server->build) == 0 ||
+ ($this->server->getGame() != 'TMF' && strcmp($this->server->build, TMN_BUILD) < 0) ||
+ ($this->server->getGame() == 'TMF' && strcmp($this->server->build, TMF_BUILD) < 0)) {
+ trigger_error("Obsolete server build '" . $this->server->build . "' - must be " .
+ ($this->server->getGame() == 'TMF' ? "at least '" . TMF_BUILD . "' !" : "'" . TMN_BUILD . "' !"), E_USER_ERROR);
+ }
+
+ // get server id, login, nickname, zone & packmask
+ $this->server->id = 0; // on TMN/TMO/TMS
+ $this->server->rights = false;
+ $this->server->isrelay = false;
+ $this->server->relaymaster = null;
+ $this->server->relayslist = array();
+ $this->server->gamestate = Server::RACE;
+ $this->server->packmask = '';
+ if ($this->server->getGame() == 'TMF') {
+ $this->client->query('GetSystemInfo');
+ $response['system'] = $this->client->getResponse();
+ $this->server->serverlogin = $response['system']['ServerLogin'];
+
+ $this->client->query('GetDetailedPlayerInfo', $this->server->serverlogin);
+ $response['info'] = $this->client->getResponse();
+ $this->server->id = $response['info']['PlayerId'];
+ $this->server->nickname = $response['info']['NickName'];
+ $this->server->zone = substr($response['info']['Path'], 6); // strip 'World|'
+ $this->server->rights = ($response['info']['OnlineRights'] == 3); // United = true
+
+ $this->client->query('GetLadderServerLimits');
+ $response['ladder'] = $this->client->getResponse();
+ $this->server->laddermin = $response['ladder']['LadderServerLimitMin'];
+ $this->server->laddermax = $response['ladder']['LadderServerLimitMax'];
+
+ $this->client->query('IsRelayServer');
+ $this->server->isrelay = ($this->client->getResponse() > 0);
+ if ($this->server->isrelay) {
+ $this->client->query('GetMainServerPlayerInfo', 1);
+ $this->server->relaymaster = $this->client->getResponse();
+ }
+
+ // TMNF packmask = 'Stadium' for 'nations' or 'stadium'
+ $this->client->query('GetServerPackMask');
+ $this->server->packmask = $this->client->getResponse();
+
+ // clear possible leftover ManiaLinks
+ $this->client->query('SendHideManialinkPage');
+ }
+
+ // get mode & limits
+ $this->client->query('GetCurrentGameInfo', ($this->server->getGame() == 'TMF' ? 1 : 0));
+ $response['gameinfo'] = $this->client->getResponse();
+ $this->server->gameinfo = new Gameinfo($response['gameinfo']);
+
+ // get status
+ $this->client->query('GetStatus');
+ $response['status'] = $this->client->getResponse();
+ $this->currstatus = $response['status']['Code'];
+
+ // get game & trackdir
+ $this->client->query('GameDataDirectory');
+ $this->server->gamedir = $this->client->getResponse();
+ $this->client->query('GetTracksDirectory');
+ $this->server->trackdir = $this->client->getResponse();
+
+ // get server name & options
+ $this->getServerOptions();
+
+ // throw 'synchronisation' event
+ $this->releaseEvent('onSync', null);
+
+ // get current players/servers on the server (hardlimited to 300)
+ if ($this->server->getGame() == 'TMF')
+ $this->client->query('GetPlayerList', 300, 0, 2);
+ else
+ $this->client->query('GetPlayerList', 300, 0);
+ $response['playerlist'] = $this->client->getResponse();
+
+ // update players/relays lists
+ if (!empty($response['playerlist'])) {
+ foreach ($response['playerlist'] as $player) {
+ // fake it into thinking it's a connecting player:
+ // it gets team & ladder info this way & will also throw an
+ // onPlayerConnect event for players (not relays) to all plugins
+ $this->playerConnect(array($player['Login'], ''));
+ }
+ }
+ } // serverSync
+
+
+ /**
+ * Sends program header to console and ingame chat.
+ */
+ function sendHeader() {
+
+ $this->console_text('###############################################################################');
+ $this->console_text(' XASECO v' . XASECO_VERSION . ' running on {1}:{2}', $this->server->ip, $this->server->port);
+ if ($this->server->getGame() == 'TMF') {
+ $this->console_text(' Name : {1} - {2}', stripColors($this->server->name, false), $this->server->serverlogin);
+ if ($this->server->isrelay)
+ $this->console_text(' Relays : {1} - {2}', stripColors($this->server->relaymaster['NickName'], false), $this->server->relaymaster['Login']);
+ $this->console_text(' Game : {1} {2} - {3} - {4}', $this->server->game,
+ ($this->server->rights ? 'United' : 'Nations'),
+ $this->server->packmask, $this->server->gameinfo->getMode());
+ } else {
+ $this->console_text(' Name : {1}', stripColors($this->server->name, false));
+ $this->console_text(' Game : {1} - {2}', $this->server->game, $this->server->gameinfo->getMode());
+ }
+ $this->console_text(' Version: {1} / {2}', $this->server->version, $this->server->build);
+ $this->console_text(' Authors: Florian Schnell & Assembler Maniac');
+ $this->console_text(' Re-Authored: Xymph');
+ $this->console_text('###############################################################################');
+
+ // format the text of the message
+ $startup_msg = formatText($this->getChatMessage('STARTUP'),
+ XASECO_VERSION,
+ $this->server->ip, $this->server->port);
+ // show startup message
+ $this->client->query('ChatSendServerMessage', $this->formatColors($startup_msg));
+ } // sendHeader
+
+
+ /**
+ * Gets callbacks from the TM Dedicated Server and reacts on them.
+ */
+ function executeCallbacks() {
+
+ // receive callbacks with a timeout (default: 2 ms)
+ $this->client->resetError();
+ $this->client->readCB();
+
+ // now get the responses out of the 'buffer'
+ $calls = $this->client->getCBResponses();
+ if ($this->client->isError()) {
+ trigger_error('ExecCallbacks XMLRPC Error [' . $this->client->getErrorCode() . '] - ' . $this->client->getErrorMessage(), E_USER_ERROR);
+ }
+
+ if (!empty($calls)) {
+ while ($call = array_shift($calls)) {
+ switch ($call[0]) {
+ case 'TrackMania.PlayerConnect': // [0]=Login, [1]=IsSpectator
+ $this->playerConnect($call[1]);
+ break;
+
+ case 'TrackMania.PlayerDisconnect': // [0]=Login
+ $this->playerDisconnect($call[1]);
+ break;
+
+ case 'TrackMania.PlayerChat': // [0]=PlayerUid, [1]=Login, [2]=Text, [3]=IsRegistredCmd
+ $this->playerChat($call[1]);
+ $this->releaseEvent('onChat', $call[1]);
+ break;
+
+ case 'TrackMania.PlayerServerMessageAnswer': // [0]=PlayerUid, [1]=Login, [2]=Answer
+ $this->playerServerMessageAnswer($call[1]);
+ break;
+
+ case 'TrackMania.PlayerCheckpoint': // TMN: [0]=PlayerUid, [1]=Login, [2]=Time, [3]=Score, [4]=CheckpointIndex; TMF: [0]=PlayerUid, [1]=Login, [2]=TimeOrScore, [3]=CurLap, [4]=CheckpointIndex
+ if (!$this->server->isrelay)
+ $this->releaseEvent('onCheckpoint', $call[1]);
+ break;
+
+ case 'TrackMania.PlayerFinish': // [0]=PlayerUid, [1]=Login, [2]=TimeOrScore
+ $this->playerFinish($call[1]);
+ break;
+
+ case 'TrackMania.BeginRace': // [0]=Challenge
+ if ($this->server->getGame() != 'TMF')
+ $this->beginRace($call[1]);
+ break;
+
+ case 'TrackMania.EndRace': // [0]=Rankings[], [1]=Challenge
+ if ($this->server->getGame() != 'TMF')
+ $this->endRace($call[1]);
+ break;
+
+ case 'TrackMania.BeginRound': // none
+ $this->beginRound();
+ break;
+
+ case 'TrackMania.StatusChanged': // [0]=StatusCode, [1]=StatusName
+ // update status changes
+ $this->prevstatus = $this->currstatus;
+ $this->currstatus = $call[1][0];
+ // if TMF mode Sync, check WarmUp state
+ if ($this->server->getGame() == 'TMF') {
+ if ($this->currstatus == 3 || $this->currstatus == 5) {
+ $this->client->query('GetWarmUp');
+ $this->warmup_phase = $this->client->getResponse();
+ }
+ } else {
+ $this->warmup_phase = false;
+ }
+ // on TMF, use real EndRound callback
+ if ($this->server->getGame() != 'TMF') {
+ // if change from Play (4) to Sync (3) or Finish (5),
+ // it's the end of a round
+ if ($this->prevstatus == 4 && ($this->currstatus == 3 || $this->currstatus == 5))
+ $this->endRound();
+ }
+ if ($this->currstatus == 4) { // Running - Play
+ $this->runningPlay();
+ }
+ $this->releaseEvent('onStatusChangeTo' . $this->currstatus, $call[1]);
+ break;
+
+ // new TMF callbacks:
+
+ case 'TrackMania.EndRound': // none
+ $this->endRound();
+ break;
+
+ case 'TrackMania.BeginChallenge': // [0]=Challenge, [1]=WarmUp, [2]=MatchContinuation
+ $this->beginRace($call[1]);
+ break;
+
+ case 'TrackMania.EndChallenge': // [0]=Rankings[], [1]=Challenge, [2]=WasWarmUp, [3]=MatchContinuesOnNextChallenge, [4]=RestartChallenge
+ $this->endRace($call[1]);
+ break;
+
+ case 'TrackMania.PlayerManialinkPageAnswer': // [0]=PlayerUid, [1]=Login, [2]=Answer
+ $this->releaseEvent('onPlayerManialinkPageAnswer', $call[1]);
+ break;
+
+ case 'TrackMania.BillUpdated': // [0]=BillId, [1]=State, [2]=StateName, [3]=TransactionId
+ $this->releaseEvent('onBillUpdated', $call[1]);
+ break;
+
+ case 'TrackMania.ChallengeListModified': // [0]=CurChallengeIndex, [1]=NextChallengeIndex, [2]=IsListModified
+ $this->releaseEvent('onChallengeListModified', $call[1]);
+ break;
+
+ case 'TrackMania.PlayerInfoChanged': // [0]=PlayerInfo
+ $this->playerInfoChanged($call[1][0]);
+ break;
+
+ case 'TrackMania.PlayerIncoherence': // [0]=PlayerUid, [1]=Login
+ $this->releaseEvent('onPlayerIncoherence', $call[1]);
+ break;
+
+ case 'TrackMania.TunnelDataReceived': // [0]=PlayerUid, [1]=Login, [2]=Data
+ $this->releaseEvent('onTunnelDataReceived', $call[1]);
+ break;
+
+ case 'TrackMania.Echo': // [0]=Internal, [1]=Public
+ $this->releaseEvent('onEcho', $call[1]);
+ break;
+
+ case 'TrackMania.ManualFlowControlTransition': // [0]=Transition
+ $this->releaseEvent('onManualFlowControlTransition', $call[1]);
+ break;
+
+ case 'TrackMania.VoteUpdated': // [0]=StateName, [1]=Login, [2]=CmdName, [3]=CmdParam
+ $this->releaseEvent('onVoteUpdated', $call[1]);
+ break;
+
+ default:
+ // do nothing
+ }
+ }
+ return $calls;
+ } else {
+ return false;
+ }
+ } // executeCallbacks
+
+
+ /**
+ * Adds calls to a multiquery.
+ * It's possible to set a callback function which
+ * will be executed on incoming response.
+ * You can also set an ID to read response later on.
+ */
+ function addCall($call, $params = array(), $id = 0, $callback_func = false) {
+
+ // adds call and registers a callback if needed
+ $index = $this->client->addCall($call, $params);
+ $rpc_call = new RPCCall($id, $index, $callback_func, array($call, $params));
+ $this->rpc_calls[] = $rpc_call;
+ } // addCall
+
+
+ /**
+ * Executes a multicall and gets responses.
+ * Saves responses in array with IDs as keys.
+ */
+ function executeCalls() {
+
+ // clear responses
+ $this->rpc_responses = array();
+
+ // stop if there are no rpc calls in query
+ if (empty($this->client->calls)) {
+ return true;
+ }
+
+ $this->client->resetError();
+ $tmpcalls = $this->client->calls; // debugging code to find UTF-8 errors
+ // sends multiquery to the server and gets the response
+ if ($this->client->multiquery()) {
+ if ($this->client->isError()) {
+ $this->console_text(print_r($tmpcalls, true));
+ trigger_error('ExecCalls XMLRPC Error [' . $this->client->getErrorCode() . '] - ' . $this->client->getErrorMessage(), E_USER_ERROR);
+ }
+
+ // get new response from server
+ $responses = $this->client->getResponse();
+
+ // handle server responses
+ foreach ($this->rpc_calls as $call) {
+ // display error message if needed
+ $err = false;
+ if (isset($responses[$call->index]['faultString'])) {
+ $this->rpcErrorResponse($responses[$call->index]);
+ print_r($call->call);
+ $err = true;
+ }
+
+ // if an id was set, then save the response under the specified id
+ if ($call->id) {
+ $this->rpc_responses[$call->id] = $responses[$call->index][0];
+ }
+
+ // if a callback function has been set, then execute it
+ if ($call->callback && !$err) {
+ if (function_exists($call->callback)) {
+ // callback the function with the response as parameter
+ call_user_func($call->callback, $responses[$call->index][0]);
+ }
+
+ // if a function with the name of the callback wasn't found, then
+ // try to execute a method with its name
+ elseif (method_exists($this, $call->callback)) {
+ // callback the method with the response as parameter
+ call_user_func(array($this, $call->callback), $responses[$call->index][0]);
+ }
+ }
+ }
+ }
+
+ // clear calls
+ $this->rpc_calls = array();
+ } // executeCalls
+
+
+ /**
+ * Documents RPC Errors.
+ */
+ function rpcErrorResponse($response) {
+
+ $this->console_text('[RPC Error ' . $response['faultCode'] . '] ' . $response['faultString']);
+ } // rpcErrorResponse
+
+
+ /**
+ * Registers functions which are called on specific events.
+ */
+ function registerEvent($event_type, $callback_func) {
+
+ // registers a new event
+ $this->events[$event_type][] = $callback_func;
+ } // registerEvent
+
+ /**
+ * Executes the functions which were registered for specified events.
+ */
+ function releaseEvent($event_type, $func_param) {
+
+ // executes registered event functions
+ // if there are any events for that type
+ if (!empty($this->events[$event_type])) {
+ // for each registered function of this type
+ foreach ($this->events[$event_type] as $func_name) {
+ // if function for the specified player connect event can be found
+ if (is_callable($func_name)) {
+ // ... execute it!
+ call_user_func($func_name, $this, $func_param);
+ }
+ }
+ }
+ } // releaseEvent
+
+
+ /**
+ * Stores a new user command.
+ */
+ function addChatCommand($command_name, $command_help, $command_is_admin = false) {
+
+ $chat_command = new ChatCommand($command_name, $command_help, $command_is_admin);
+ $this->chat_commands[] = $chat_command;
+ } // addChatCommand
+
+ /**
+ * Registers all chat commands with the server.
+ */
+ function registerChatCommands() {
+
+ // clear the current list of chat commands
+ $this->client->query('CleanChatCommand');
+
+ if (isset($this->chat_commands)) {
+ foreach ($this->chat_commands as $command) {
+ // only if it's no admin command
+ if (!$command->isadmin) {
+ // log message if debug mode is set to true
+ if ($this->debug) {
+ $this->console_text('register chat command: ' . $command->name);
+ }
+
+ // register chat command at server
+ $this->client->query('AddChatCommand', $command->name);
+ }
+ }
+ }
+ } // registerChatCommands
+
+
+ /**
+ * When a round is started, signal the event.
+ */
+ function beginRound() {
+
+ $this->console_text('Begin Round');
+ $this->releaseEvent('onBeginRound', null);
+ } // beginRound
+
+ /**
+ * When a round is ended, signal the event.
+ */
+ function endRound() {
+
+ $this->console_text('End Round');
+ $this->releaseEvent('onEndRound', null);
+ } // endRound
+
+
+ /**
+ * When a TMF player's info changed, signal the event. Fields:
+ * Login, NickName, PlayerId, TeamId, SpectatorStatus, LadderRanking, Flags
+ */
+ function playerInfoChanged($playerinfo) {
+
+ // on relay, check for player from master server
+ if ($this->server->isrelay && floor($playerinfo['Flags'] / 10000) % 10 != 0)
+ return;
+
+ // check for valid player
+ if (!$player = $this->server->players->getPlayer($playerinfo['Login']))
+ return;
+
+ // check ladder ranking
+ if ($playerinfo['LadderRanking'] > 0) {
+ $player->ladderrank = $playerinfo['LadderRanking'];
+ $player->isofficial = true;
+ } else {
+ $player->isofficial = false;
+ }
+
+ // check spectator status (ignoring temporary changes)
+ $player->prevstatus = $player->isspectator;
+ if (($playerinfo['SpectatorStatus'] % 10) != 0)
+ $player->isspectator = true;
+ else
+ $player->isspectator = false;
+
+ $this->releaseEvent('onPlayerInfoChanged', $playerinfo);
+ } // playerInfoChanged
+
+
+ /**
+ * When a new track is started we have to get information
+ * about the new track and so on.
+ */
+ function runningPlay() {
+ // request information about the new challenge
+ // ... and callback to function newChallenge()
+ } // runningPlay
+
+
+ /**
+ * When a new race is started we have to get information
+ * about the new track and so on.
+ */
+ function beginRace($race) {
+ // request information about the new challenge
+ // ... and callback to function newChallenge()
+
+ // if TMF new challenge, check WarmUp state
+ if ($this->server->getGame() == 'TMF' && $race)
+ $this->warmup_phase = $race[1];
+
+ if (!$race) {
+ $this->addCall('GetCurrentChallengeInfo', array(), '', 'newChallenge');
+ } else {
+ $this->newChallenge($race[0]);
+ }
+ } // beginRace
+
+
+ /**
+ * Reacts on new challenges.
+ * Gets record to current challenge etc.
+ */
+ function newChallenge($challenge) {
+
+ // log if not a restart
+ $this->server->gamestate = Server::RACE;
+ if ($this->restarting == 0)
+ $this->console_text('Begin Challenge');
+
+ // refresh game info
+ $this->client->query('GetCurrentGameInfo', ($this->server->getGame() == 'TMF' ? 1 : 0));
+ $gameinfo = $this->client->getResponse();
+ $this->server->gameinfo = new Gameinfo($gameinfo);
+
+ // check for TMF restarting challenge
+ $this->changingmode = false;
+ if ($this->server->getGame() == 'TMF' && $this->restarting > 0) {
+ // check type of restart and signal an instant one
+ if ($this->restarting == 2) {
+ $this->restarting = 0;
+ } else { // == 1
+ $this->restarting = 0;
+ // throw postfix 'restart challenge' event
+ $this->releaseEvent('onRestartChallenge2', $challenge);
+ return;
+ }
+ }
+ // refresh server name & options
+ $this->getServerOptions();
+
+ // reset record list
+ $this->server->records->clear();
+ // reset player votings
+ //$this->server->players->resetVotings();
+
+ // create new challenge object
+ $challenge_item = new Challenge($challenge);
+
+ // in TMF Rounds/Team/Cup mode if multilap track, get forced laps
+ if ($this->server->getGame() == 'TMF' && $challenge_item->laprace &&
+ ($this->server->gameinfo->mode == Gameinfo::RNDS ||
+ $this->server->gameinfo->mode == Gameinfo::TEAM ||
+ $this->server->gameinfo->mode == Gameinfo::CUP)) {
+ $challenge_item->forcedlaps = $this->server->gameinfo->forcedlaps;
+ }
+
+ // obtain challenge's GBX data, TMX info & records
+ $challenge_item->gbx = new GBXChallMapFetcher(true);
+ try
+ {
+ $challenge_item->gbx->processFile($this->server->trackdir . $challenge_item->filename);
+ }
+ catch (Exception $e)
+ {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ }
+ $challenge_item->tmx = findTMXdata($challenge_item->uid, $challenge_item->environment, $challenge_item->gbx->exeVer, true);
+
+ // throw main 'begin challenge' event
+ $this->releaseEvent('onNewChallenge', $challenge_item);
+
+ // log console message
+ $this->console('track changed [{1}] >> [{2}]',
+ stripColors($this->server->challenge->name, false),
+ stripColors($challenge_item->name, false));
+
+/* disabled in favor of RASP's karma system - Xymph
+ // log track's score
+ if ($challenge_item->score && $challenge_item->votes) {
+ // calculate avarage score and display
+ $this->console('average score of this track is {1}',
+ $challenge_item->score/$challenge_item->votes);
+ } else {
+ // no votings
+ $this->console('no votings available for this track');
+ }
+disabled */
+
+ // check for relay server
+ if (!$this->server->isrelay) {
+ // check if record exists on new track
+ $cur_record = $this->server->records->getRecord(0);
+ if ($cur_record !== false && $cur_record->score > 0) {
+ $score = ($this->server->gameinfo->mode == Gameinfo::STNT ?
+ str_pad($cur_record->score, 5, ' ', STR_PAD_LEFT) :
+ formatTime($cur_record->score));
+
+ // log console message of current record
+ $this->console('current record on {1} is {2} and held by {3}',
+ stripColors($challenge_item->name, false),
+ trim($score),
+ stripColors($cur_record->player->nickname, false));
+
+ // replace parameters
+ $message = formatText($this->getChatMessage('RECORD_CURRENT'),
+ stripColors($challenge_item->name),
+ trim($score),
+ stripColors($cur_record->player->nickname));
+ } else {
+ if ($this->server->gameinfo->mode == Gameinfo::STNT)
+ $score = ' ---';
+ else
+ $score = ' --.--';
+
+ // log console message of no record
+ $this->console('currently no record on {1}',
+ stripColors($challenge_item->name, false));
+
+ // replace parameters
+ $message = formatText($this->getChatMessage('RECORD_NONE'),
+ stripColors($challenge_item->name));
+ }
+ if (function_exists('setRecordsPanel'))
+ setRecordsPanel('local', $score);
+
+ // if no trackrecs, show the original record message to all players
+ if (($this->settings['show_recs_before'] & 1) == 1) {
+ if (($this->settings['show_recs_before'] & 4) == 4 && function_exists('send_window_message'))
+ send_window_message($this, $message, false);
+ else
+ $this->client->query('ChatSendServerMessage', $this->formatColors($message));
+ }
+ }
+
+ // update the field which contains current challenge
+ $this->server->challenge = $challenge_item;
+
+ // throw postfix 'begin challenge' event (various)
+ $this->releaseEvent('onNewChallenge2', $challenge_item);
+
+ // show top-8 & records of all online players before track
+ if (($this->settings['show_recs_before'] & 2) == 2 && function_exists('show_trackrecs')) {
+ show_trackrecs($this, false, 1, $this->settings['show_recs_before']); // from chat.records2.php
+ }
+ } // newChallenge
+
+
+ /**
+ * End of current race.
+ * Write records to database and/or display final statistics.
+ */
+ function endRace($race) {
+
+ // check for TMF RestartChallenge flag
+ if ($this->server->getGame() == 'TMF' && $race[4]) {
+ $this->restarting = 1;
+ // check whether changing game mode or any player has a time/score,
+ // then there will be ChatTime, otherwise it's an instant restart
+ if ($this->changingmode)
+ $this->restarting = 2;
+ else
+ foreach ($race[0] as $pl) {
+ if ($pl['BestTime'] > 0 || $pl['Score'] > 0) {
+ $this->restarting = 2;
+ break;
+ }
+ }
+ // log type of restart and signal an instant one
+ if ($this->restarting == 2) {
+ $this->console_text('Restart Challenge (with ChatTime)');
+ } else { // == 1
+ $this->console_text('Restart Challenge (instant)');
+ // throw main 'restart challenge' event
+ $this->releaseEvent('onRestartChallenge', $race);
+ return;
+ }
+ }
+ // log if not a restart
+ $this->server->gamestate = Server::SCORE;
+ if ($this->restarting == 0)
+ $this->console_text('End Challenge');
+
+ // show top-8 & all new records after track
+ if (($this->settings['show_recs_after'] & 2) == 2 && function_exists('show_trackrecs')) {
+ show_trackrecs($this, false, 3, $this->settings['show_recs_after']); // from chat.records2.php
+ } elseif (($this->settings['show_recs_after'] & 1) == 1) {
+ // fall back on old top-5
+ $records = '';
+
+ if ($this->server->records->count() == 0) {
+ // display a no-new-record message
+ $message = formatText($this->getChatMessage('RANKING_NONE'),
+ stripColors($this->server->challenge->name),
+ 'after');
+ } else {
+ // display new records set up this round
+ $message = formatText($this->getChatMessage('RANKING'),
+ stripColors($this->server->challenge->name),
+ 'after');
+
+ // go through each record
+ for ($i = 0; $i < 5; $i++) {
+ $cur_record = $this->server->records->getRecord($i);
+
+ // if the record is set then display it
+ if ($cur_record !== false && $cur_record->score > 0) {
+ // replace parameters
+ $record_msg = formatText($this->getChatMessage('RANKING_RECORD_NEW'),
+ $i+1,
+ stripColors($cur_record->player->nickname),
+ ($this->server->gameinfo->mode == Gameinfo::STNT ?
+ $cur_record->score : formatTime($cur_record->score)));
+ $records .= $record_msg;
+ }
+ }
+ }
+
+ // append the records if any
+ if ($records != '') {
+ $records = substr($records, 0, strlen($records)-2); // strip trailing ", "
+ $message .= LF . $records;
+ }
+
+ // show ranking message to all players
+ if (($this->settings['show_recs_after'] & 4) == 4 && function_exists('send_window_message'))
+ send_window_message($this, $message, true);
+ else
+ $this->client->query('ChatSendServerMessage', $this->formatColors($message));
+ }
+
+ // get rankings and call endRaceRanking as soon as we have them
+ // $this->addCall('GetCurrentRanking', array(2, 0), false, 'endRaceRanking');
+ if (!$this->server->isrelay)
+ $this->endRaceRanking($race[0]);
+
+ // throw prefix 'end challenge' event (chat-based votes)
+ $this->releaseEvent('onEndRace1', $race);
+ // throw main 'end challenge' event
+ $this->releaseEvent('onEndRace', $race);
+ } // endRace
+
+
+ /**
+ * Check out who won the current track and increment his/her wins by one.
+ */
+ function endRaceRanking($ranking) {
+
+ // check for online login
+ if (isset($ranking[0]['Login']) &&
+ ($player = $this->server->players->getPlayer($ranking[0]['Login'])) !== false) {
+ // check for winner if there's more than one player
+ if ($ranking[0]['Rank'] == 1 && count($ranking) > 1 &&
+ ($this->server->gameinfo->mode == Gameinfo::STNT ?
+ ($ranking[0]['Score'] > 0) : ($ranking[0]['BestTime'] > 0))) {
+ // increase the player's wins
+ $player->newwins++;
+
+ // log console message
+ $this->console('{1} won for the {2}. time!',
+ $player->login, $player->getWins());
+
+ if ($player->getWins() % $this->settings['global_win_multiple'] == 0) {
+ // replace parameters
+ $message = formatText($this->getChatMessage('WIN_MULTI'),
+ stripColors($player->nickname), $player->getWins());
+
+ // show chat message
+ $this->client->query('ChatSendServerMessage', $this->formatColors($message));
+ } else {
+ // replace parameters
+ $message = formatText($this->getChatMessage('WIN_NEW'),
+ $player->getWins());
+
+ // show chat message
+ $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login);
+ }
+
+ // throw 'player wins' event
+ $this->releaseEvent('onPlayerWins', $player);
+ }
+ }
+ } // endRaceRanking
+
+
+ /**
+ * Handles connections of new players.
+ */
+ function playerConnect($player) {
+
+ // request information about the new player
+ // (removed callback mechanism here, as GetPlayerInfo occasionally
+ // returns no data and then the connecting login would be lost)
+ $login = $player[0];
+ if ($this->server->getGame() == 'TMF') {
+ $this->client->query('GetDetailedPlayerInfo', $login);
+ $playerd = $this->client->getResponse();
+ $this->client->query('GetPlayerInfo', $login, 1);
+ } else { // TMN/TMS/TMO
+ $this->client->query('GetPlayerInfo', $login);
+ }
+ $player = $this->client->getResponse();
+
+ // check for server
+ if (isset($player['Flags']) && floor($player['Flags'] / 100000) % 10 != 0) {
+ // register relay server
+ if (!$this->server->isrelay && $player['Login'] != $this->server->serverlogin) {
+ $this->server->relayslist[$player['Login']] = $player;
+
+ // log console message
+ $this->console('<<< relay server {1} ({2}) connected', $player['Login'],
+ stripColors($player['NickName'], false));
+ }
+
+ // on relay, check for player from master server
+ } elseif ($this->server->isrelay && floor($player['Flags'] / 10000) % 10 != 0) {
+ ; // ignore
+ } else {
+ $ipaddr = isset($playerd['IPAddress']) ? preg_replace('/:\d+/', '', $playerd['IPAddress']) : ''; // strip port
+
+ // if no data fetched, notify & kick the player
+ if (!isset($player['Login']) || $player['Login'] == '') {
+ $message = str_replace('{br}', LF, $this->getChatMessage('CONNECT_ERROR'));
+ $message = $this->formatColors($message);
+ $this->client->query('ChatSendServerMessageToLogin', str_replace(LF.LF, LF, $message), $login);
+ if ($this->server->getGame() == 'TMN')
+ $this->client->query('SendDisplayServerMessageToLogin', $login, $message, 'OK', '', 0);
+ sleep(5); // allow time to connect and see the notice
+ if ($this->server->getGame() == 'TMF')
+ $this->client->addCall('Kick', array($login, $this->formatColors($this->getChatMessage('CONNECT_DIALOG'))));
+ else
+ $this->client->addCall('Kick', array($login));
+ // log console message
+ $this->console('GetPlayerInfo failed for ' . $login . ' -- notified & kicked');
+ return;
+
+ // if player IP in ban list, notify & kick the player
+ } elseif (!empty($this->bannedips) && in_array($ipaddr, $this->bannedips)) {
+ $message = str_replace('{br}', LF, $this->getChatMessage('BANIP_ERROR'));
+ $message = $this->formatColors($message);
+ $this->client->query('ChatSendServerMessageToLogin', str_replace(LF.LF, LF, $message), $login);
+ if ($this->server->getGame() == 'TMN')
+ $this->client->query('SendDisplayServerMessageToLogin', $login, $message, 'OK', '', 0);
+ sleep(5); // allow time to connect and see the notice
+ if ($this->server->getGame() == 'TMF')
+ $this->client->addCall('Ban', array($login, $this->formatColors($this->getChatMessage('BANIP_DIALOG'))));
+ else
+ $this->client->addCall('Ban', array($login));
+ // log console message
+ $this->console('Player ' . $login . ' banned from ' . $ipaddr . ' -- notified & kicked');
+ return;
+
+ // client version checking on TMF
+ } elseif ($this->server->getGame() == 'TMF') {
+ // extract version number
+ $version = str_replace(')', '', preg_replace('/.*\(/', '', $playerd['ClientVersion']));
+ if ($version == '') $version = '2.11.11';
+ $message = str_replace('{br}', LF, $this->getChatMessage('CLIENT_ERROR'));
+
+ // if invalid version, notify & kick the player
+ if ($this->settings['player_client'] != '' &&
+ strcmp($version, $this->settings['player_client']) < 0) {
+ $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $login);
+ sleep(5); // allow time to connect and see the notice
+ $this->client->addCall('Kick', array($login, $this->formatColors($this->getChatMessage('CLIENT_DIALOG'))));
+ // log console message
+ $this->console('Obsolete player client version ' . $version . ' for ' . $login . ' -- notified & kicked');
+ return;
+ }
+
+ // if invalid version, notify & kick the admin
+ if ($this->settings['admin_client'] != '' && $this->isAnyAdminL($player['Login']) &&
+ strcmp($version, $this->settings['admin_client']) < 0) {
+ $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $login);
+ sleep(5); // allow time to connect and see the notice
+ $this->client->addCall('Kick', array($login, $this->formatColors($this->getChatMessage('CLIENT_DIALOG'))));
+ // log console message
+ $this->console('Obsolete admin client version ' . $version . ' for ' . $login . ' -- notified & kicked');
+ return;
+ }
+ }
+
+ // if no TMN team, try again via world stats
+ if ($this->server->getGame() == 'TMN' && !isLANLogin($login) &&
+ $player['LadderStats']['TeamName'] == '') {
+ $data = new TMNDataFetcher($login, false);
+ if ($data->teamname != '') {
+ $player['LadderStats']['TeamName'] = $data->teamname;
+ }
+ }
+
+ // create player object
+ $player_item = new Player($this->server->getGame() == 'TMF' ? $playerd : $player);
+ // set default window style & panels
+ $player_item->style = $this->style;
+ $player_item->panels['admin'] = $this->panels['admin'];
+ $player_item->panels['donate'] = $this->panels['donate'];
+ $player_item->panels['records'] = $this->panels['records'];
+ $player_item->panels['vote'] = $this->panels['vote'];
+
+ // adds a new player to the internal player list
+ $this->server->players->addPlayer($player_item);
+
+ // log console message
+ $this->console('<< player {1} joined the game [{2} : {3} : {4} : {5} : {6}]',
+ $player_item->pid,
+ $player_item->login,
+ $player_item->nickname,
+ $player_item->nation,
+ $player_item->ladderrank,
+ $player_item->ip);
+
+ // replace parameters
+ $message = formatText($this->getChatMessage('WELCOME'),
+ stripColors($player_item->nickname),
+ $this->server->name, XASECO_VERSION);
+ // hyperlink package name & version number on TMF
+ if ($this->server->getGame() == 'TMF')
+ $message = preg_replace('/XASECO.+' . XASECO_VERSION . '/', '$l[' . XASECO_TMN . ']$0$l', $message);
+
+ // send welcome popup or chat message
+ if ($this->settings['welcome_msg_window']) {
+ if ($this->server->getGame() == 'TMF') {
+ $message = str_replace('{#highlite}', '{#message}', $message);
+ $message = preg_split('/{br}/', $this->formatColors($message));
+ // repack all lines
+ foreach ($message as &$line)
+ $line = array($line);
+ display_manialink($player_item->login, '',
+ array('Icons64x64_1', 'Inbox'), $message,
+ array(1.2), 'OK');
+ } else { // TMN
+ $message = str_replace('{br}', LF, $this->formatColors($message));
+ $this->client->query('SendDisplayServerMessageToLogin', $player_item->login, $message, 'OK', '', 0);
+ }
+ } else {
+ $message = str_replace('{br}', LF, $this->formatColors($message));
+ $this->client->query('ChatSendServerMessageToLogin', str_replace(LF.LF, LF, $message), $player_item->login);
+ }
+
+ // if there's a record on current track
+ $cur_record = $this->server->records->getRecord(0);
+ if ($cur_record !== false && $cur_record->score > 0) {
+ // set message to the current record
+ $message = formatText($this->getChatMessage('RECORD_CURRENT'),
+ stripColors($this->server->challenge->name),
+ ($this->server->gameinfo->mode == Gameinfo::STNT ?
+ $cur_record->score : formatTime($cur_record->score)),
+ stripColors($cur_record->player->nickname));
+ } else { // if there should be no record to display
+ // display a no-record message
+ $message = formatText($this->getChatMessage('RECORD_NONE'),
+ stripColors($this->server->challenge->name));
+ }
+
+ // show top-8 & records of all online players before track
+ if (($this->settings['show_recs_before'] & 2) == 2 && function_exists('show_trackrecs')) {
+ show_trackrecs($this, $player_item->login, 1, 0); // from chat.records2.php
+ } elseif (($this->settings['show_recs_before'] & 1) == 1) {
+ // or show original record message
+ $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player_item->login);
+ }
+
+ // throw main 'player connects' event
+ $this->releaseEvent('onPlayerConnect', $player_item);
+ // throw postfix 'player connects' event (access control)
+ $this->releaseEvent('onPlayerConnect2', $player_item);
+ }
+ } // playerConnect
+
+ /**
+ * Handles disconnections of players.
+ */
+ function playerDisconnect($player) {
+
+ // check for relay server
+ if (!$this->server->isrelay && array_key_exists($player[0], $this->server->relayslist)) {
+ // log console message
+ $this->console('>>> relay server {1} ({2}) disconnected', $player[0],
+ stripColors($this->server->relayslist[$player[0]]['NickName'], false));
+
+ unset($this->server->relayslist[$player[0]]);
+ return;
+ }
+
+ // delete player and put him into the player item
+ // ignore event if disconnect fluke after player already left,
+ // or on relay if player from master server (which wasn't added)
+ if (!$player_item = $this->server->players->removePlayer($player[0]))
+ return;
+
+ // log console message
+ $this->console('>> player {1} left the game [{2} : {3} : {4}]',
+ $player_item->pid,
+ $player_item->login,
+ $player_item->nickname,
+ formatTimeH($player_item->getTimeOnline() * 1000, false));
+
+ // throw 'player disconnects' event
+ $this->releaseEvent('onPlayerDisconnect', $player_item);
+ } // playerDisconnect
+
+
+ /**
+ * Handles clicks on server messageboxes.
+ */
+ function playerServerMessageAnswer($answer) {
+
+ if ($answer[2]) {
+ // throw TMN 'click' event
+ $this->releaseEvent('onPlayerServerMessageAnswer', $answer);
+ }
+ } // playerServerMessageAnswer
+
+
+ /**
+ * Player reaches finish.
+ */
+ function playerFinish($finish) {
+
+ // if no track info, or if server 'finish', bail out immediately
+ if ($this->server->challenge->name == '' || $finish[0] == 0)
+ return;
+
+ // if relay server or not in Play status, bail out immediately
+ if ($this->server->isrelay || $this->currstatus != 4)
+ return;
+
+ // check for valid player
+ if ((!$player = $this->server->players->getPlayer($finish[1])) ||
+ $player->login == '')
+ return;
+
+ // build a record object with the current finish information
+ $finish_item = new Record();
+ $finish_item->player = $player;
+ $finish_item->score = $finish[2];
+ $finish_item->date = strftime('%Y-%m-%d %H:%M:%S');
+ $finish_item->new = false;
+ $finish_item->challenge = clone $this->server->challenge;
+ unset($finish_item->challenge->gbx); // reduce memory usage
+ unset($finish_item->challenge->tmx);
+
+ // throw prefix 'player finishes' event (checkpoints)
+ $this->releaseEvent('onPlayerFinish1', $finish_item);
+ // throw main 'player finishes' event
+ $this->releaseEvent('onPlayerFinish', $finish_item);
+ } // playerFinish
+
+
+ /**
+ * Receives chat messages and reacts on them.
+ * Reactions are done by the chat plugins.
+ */
+ function playerChat($chat) {
+
+ // verify login
+ if ($chat[1] == '' || $chat[1] == '???') {
+ trigger_error('playerUid ' . $chat[0] . 'has login [' . $chat[1] . ']!', E_USER_WARNING);
+ $this->console('playerUid {1} attempted to use chat command "{2}"',
+ $chat[0], $chat[2]);
+ return;
+ }
+
+ // ignore master server messages on relay
+ if ($this->server->isrelay && $chat[1] == $this->server->relaymaster['Login'])
+ return;
+
+ // check for chat command '/' prefix
+ $command = $chat[2];
+ if ($command != '' && $command[0] == '/') {
+ // remove '/' prefix
+ $command = substr($command, 1);
+
+ // split strings at spaces and add them into an array
+ $params = explode(' ', $command, 2);
+ $translated_name = str_replace('+', 'plus', $params[0]);
+ $translated_name = str_replace('-', 'dash', $translated_name);
+
+ // check if the function and the command exist
+ if (function_exists('chat_' . $translated_name)) {
+ // insure parameter exists & is trimmed
+ if (isset($params[1]))
+ $params[1] = trim($params[1]);
+ else
+ $params[1] = '';
+
+ // get & verify player object
+ if (($author = $this->server->players->getPlayer($chat[1])) &&
+ $author->login != '') {
+ // log console message
+ $this->console('player {1} used chat command "/{2} {3}"',
+ $chat[1], $params[0], $params[1]);
+
+ // save circumstances in array
+ $chat_command = array();
+ $chat_command['author'] = $author;
+ $chat_command['params'] = $params[1];
+
+ // call the function which belongs to the command
+ call_user_func('chat_' . $translated_name, $this, $chat_command);
+ } else {
+ trigger_error('Player object for \'' . $chat[1] . '\' not found!', E_USER_WARNING);
+ $this->console('player {1} attempted to use chat command "/{2} {3}"',
+ $chat[1], $params[0], $params[1]);
+ }
+ } elseif ($params[0] == 'version' ||
+ ($params[0] == 'serverlogin' && $this->server->getGame() == 'TMF')) {
+ // log built-in commands
+ $this->console('player {1} used built-in command "/{2}"',
+ $chat[1], $command);
+ } else {
+ // optionally log bogus chat commands too
+ if ($this->settings['log_all_chat']) {
+ if ($chat[0] != $this->server->id) {
+ $this->console('({1}) {2}', $chat[1], stripColors($chat[2], false));
+ }
+ }
+ }
+ } else {
+ // optionally log all normal chat too
+ if ($this->settings['log_all_chat']) {
+ if ($chat[0] != $this->server->id && $chat[2] != '') {
+ $this->console('({1}) {2}', $chat[1], stripColors($chat[2], false));
+ }
+ }
+ }
+ } // playerChat
+
+
+ /**
+ * Gets the specified chat message out of the settings file.
+ */
+ function getChatMessage($name) {
+
+ return htmlspecialchars_decode($this->chat_messages[$name][0]);
+ } // getChatMessage
+
+
+ /**
+ * Checks if an admin is allowed to perform this ability
+ */
+ function allowAdminAbility($ability) {
+
+ // map to uppercase before checking list
+ $ability = strtoupper($ability);
+ if (isset($this->adm_abilities[$ability])) {
+ return $this->adm_abilities[$ability][0];
+ } else {
+ return false;
+ }
+ } // allowAdminAbility
+
+ /**
+ * Checks if an operator is allowed to perform this ability
+ */
+ function allowOpAbility($ability) {
+
+ // map to uppercase before checking list
+ $ability = strtoupper($ability);
+ if (isset($this->op_abilities[$ability])) {
+ return $this->op_abilities[$ability][0];
+ } else {
+ return false;
+ }
+ } // allowOpAbility
+
+ /**
+ * Checks if the given player is allowed to perform this ability
+ */
+ function allowAbility($player, $ability) {
+
+ // check for unlocked password
+ if ($this->settings['lock_password'] != '' && !$player->unlocked)
+ return false;
+
+ // MasterAdmins can always do everything
+ if ($this->isMasterAdmin($player))
+ return true;
+
+ // check Admins & their abilities
+ if ($this->isAdmin($player))
+ return $this->allowAdminAbility($ability);
+
+ // check Operators & their abilities
+ if ($this->isOperator($player))
+ return $this->allowOpAbility($ability);
+
+ return false;
+ } // allowAbility
+
+
+ /**
+ * Checks if the given player IP matches the corresponding list IP,
+ * allowing for class C and B wildcards, and multiple comma-separated
+ * IPs / wildcards.
+ */
+ function ip_match($playerip, $listip) {
+
+ // check for offline player (removeadmin / removeop)
+ if ($playerip == '')
+ return true;
+
+ $match = false;
+ // check all comma-separated IPs/wildcards
+ foreach (explode(',', $listip) as $ip) {
+ // check for complete list IP
+ if (preg_match('/^\d+\.\d+\.\d+\.\d+$/', $ip))
+ $match = ($playerip == $ip);
+ // check class B wildcard
+ elseif (substr($ip, -4) == '.*.*')
+ $match = (preg_replace('/\.\d+\.\d+$/', '', $playerip) == substr($ip, 0, -4));
+ // check class C wildcard
+ elseif (substr($ip, -2) == '.*')
+ $match = (preg_replace('/\.\d+$/', '', $playerip) == substr($ip, 0, -2));
+
+ if ($match) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the given player is in masteradmin list with, optionally,
+ * an authorized IP.
+ */
+ function isMasterAdmin($player) {
+
+ // check for masteradmin list entry
+ if (isset($player->login) && $player->login != '' && isset($this->masteradmin_list['TMLOGIN']))
+ if (($i = array_search($player->login, $this->masteradmin_list['TMLOGIN'])) !== false)
+ // check for matching IP if set
+ if ($this->masteradmin_list['IPADDRESS'][$i] != '')
+ if (!$this->ip_match($player->ip, $this->masteradmin_list['IPADDRESS'][$i])) {
+ trigger_error("Attempt to use MasterAdmin login '" . $player->login . "' from IP " . $player->ip . " !", E_USER_WARNING);
+ return false;
+ } else
+ return true;
+ else
+ return true;
+ else
+ return false;
+ else
+ return false;
+ } // isMasterAdmin
+
+ /**
+ * Checks if the given player is in admin list with, optionally,
+ * an authorized IP.
+ */
+ function isAdmin($player) {
+
+ // check for admin list entry
+ if (isset($player->login) && $player->login != '' && isset($this->admin_list['TMLOGIN']))
+ if (($i = array_search($player->login, $this->admin_list['TMLOGIN'])) !== false)
+ // check for matching IP if set
+ if ($this->admin_list['IPADDRESS'][$i] != '')
+ if (!$this->ip_match($player->ip, $this->admin_list['IPADDRESS'][$i])) {
+ trigger_error("Attempt to use Admin login '" . $player->login . "' from IP " . $player->ip . " !", E_USER_WARNING);
+ return false;
+ } else
+ return true;
+ else
+ return true;
+ else
+ return false;
+ else
+ return false;
+ } // isAdmin
+
+ /**
+ * Checks if the given player is in operator list with, optionally,
+ * an authorized IP.
+ */
+ function isOperator($player) {
+
+ // check for operator list entry
+ if (isset($player->login) && $player->login != '' && isset($this->operator_list['TMLOGIN']))
+ if (($i = array_search($player->login, $this->operator_list['TMLOGIN'])) !== false)
+ // check for matching IP if set
+ if ($this->operator_list['IPADDRESS'][$i] != '')
+ if (!$this->ip_match($player->ip, $this->operator_list['IPADDRESS'][$i])) {
+ trigger_error("Attempt to use Operator login '" . $player->login . "' from IP " . $player->ip . " !", E_USER_WARNING);
+ return false;
+ } else
+ return true;
+ else
+ return true;
+ else
+ return false;
+ else
+ return false;
+ } // isOperator
+
+ /**
+ * Checks if the given player is in any admin tier with, optionally,
+ * an authorized IP.
+ */
+ function isAnyAdmin($player) {
+
+ return ($this->isMasterAdmin($player) || $this->isAdmin($player) || $this->isOperator($player));
+ } // isAnyAdmin
+
+
+ /**
+ * Checks if the given player login is in masteradmin list.
+ */
+ function isMasterAdminL($login) {
+
+ if ($login != '' && isset($this->masteradmin_list['TMLOGIN'])) {
+ return in_array($login, $this->masteradmin_list['TMLOGIN']);
+ } else {
+ return false;
+ }
+ } // isMasterAdminL
+
+ /**
+ * Checks if the given player login is in admin list.
+ */
+ function isAdminL($login) {
+
+ if ($login != '' && isset($this->admin_list['TMLOGIN'])) {
+ return in_array($login, $this->admin_list['TMLOGIN']);
+ } else {
+ return false;
+ }
+ } // isAdminL
+
+ /**
+ * Checks if the given player login is in operator list.
+ */
+ function isOperatorL($login) {
+
+ // check for operator list entry
+ if ($login != '' && isset($this->operator_list['TMLOGIN']))
+ return in_array($login, $this->operator_list['TMLOGIN']);
+ else
+ return false;
+ } // isOperatorL
+
+ /**
+ * Checks if the given player login is in any admin tier.
+ */
+ function isAnyAdminL($login) {
+
+ return ($this->isMasterAdminL($login) || $this->isAdminL($login) || $this->isOperatorL($login));
+ } // isAnyAdminL
+
+
+ /**
+ * Checks if the given player is a spectator.
+ */
+ function isSpectator($player) {
+
+ // get current player status
+ if ($this->server->getGame() != 'TMF') {
+ $this->client->query('GetPlayerInfo', $player->login);
+ $info = $this->client->getResponse();
+ if (isset($info['IsSpectator']))
+ $player->isspectator = $info['IsSpectator'];
+ else
+ $player->isspectator = false;
+ }
+ return $player->isspectator;
+ } // isSpectator
+
+ /**
+ * Handles cheating player.
+ */
+ function processCheater($login, $checkpoints, $chkpt, $finish) {
+
+ // collect checkpoints
+ $cps = '';
+ foreach ($checkpoints as $cp)
+ $cps .= formatTime($cp) . '/';
+ $cps = substr($cps, 0, strlen($cps)-1); // strip trailing '/'
+
+ // report cheat
+ if ($finish == -1)
+ trigger_error('Cheat by \'' . $login . '\' detected! CPs: ' . $cps . ' Last: ' . formatTime($chkpt[2]) . ' index: ' . $chkpt[4], E_USER_WARNING);
+ else
+ trigger_error('Cheat by \'' . $login . '\' detected! CPs: ' . $cps . ' Finish: ' . formatTime($finish), E_USER_WARNING);
+
+ // check for valid player
+ if (!$player = $this->server->players->getPlayer($login)) {
+ trigger_error('Player object for \'' . $login . '\' not found!', E_USER_WARNING);
+ return;
+ }
+
+ switch ($this->settings['cheater_action']) {
+
+ case 1: // set to spec (TMF only)
+ $rtn = $this->client->query('ForceSpectator', $login, 1);
+ if (!$rtn) {
+ trigger_error('[' . $this->client->getErrorCode() . '] ForceSpectator - ' . $this->client->getErrorMessage(), E_USER_WARNING);
+ } else {
+ // allow spectator to switch back to player
+ $rtn = $this->client->query('ForceSpectator', $login, 0);
+ }
+ // force free camera mode on spectator
+ $this->client->addCall('ForceSpectatorTarget', array($login, '', 2));
+ // free up player slot
+ $this->client->addCall('SpectatorReleasePlayerSlot', array($login));
+
+ // log console message
+ $this->console('Cheater [{1} : {2}] forced into free spectator!', $login, stripColors($player->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} forced into spectator!',
+ str_ireplace('$w', '', $player->nickname));
+ $this->client->query('ChatSendServerMessage', $this->formatColors($message));
+ break;
+
+ case 2: // kick
+ // log console message
+ $this->console('Cheater [{1} : {2}] kicked!', $login, stripColors($player->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} kicked!',
+ str_ireplace('$w', '', $player->nickname));
+ $this->client->query('ChatSendServerMessage', $this->formatColors($message));
+
+ // kick the cheater
+ $this->client->query('Kick', $login);
+ break;
+
+ case 3: // ban (& kick)
+ // log console message
+ $this->console('Cheater [{1} : {2}] banned!', $login, stripColors($player->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} banned!',
+ str_ireplace('$w', '', $player->nickname));
+ $this->client->query('ChatSendServerMessage', $this->formatColors($message));
+
+ // update banned IPs file
+ $this->bannedips[] = $player->ip;
+ $this->writeIPs();
+
+ // ban the cheater and also kick him
+ $this->client->query('Ban', $player->login);
+ break;
+
+ case 4: // blacklist & kick
+ // log console message
+ $this->console('Cheater [{1} : {2}] blacklisted!', $login, stripColors($player->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} blacklisted!',
+ str_ireplace('$w', '', $player->nickname));
+ $this->client->query('ChatSendServerMessage', $this->formatColors($message));
+
+ // blacklist the cheater and then kick him
+ $this->client->query('BlackList', $player->login);
+ $this->client->query('Kick', $player->login);
+
+ // update blacklist file
+ $filename = $this->settings['blacklist_file'];
+ $rtn = $this->client->query('SaveBlackList', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $this->client->getErrorCode() . '] SaveBlackList (kick) - ' . $this->client->getErrorMessage(), E_USER_WARNING);
+ }
+ break;
+
+ case 5: // blacklist & ban
+ // log console message
+ $this->console('Cheater [{1} : {2}] blacklisted & banned!', $login, stripColors($player->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} blacklisted & banned!',
+ str_ireplace('$w', '', $player->nickname));
+ $this->client->query('ChatSendServerMessage', $this->formatColors($message));
+
+ // update banned IPs file
+ $this->bannedips[] = $player->ip;
+ $this->writeIPs();
+
+ // blacklist & ban the cheater
+ $this->client->query('BlackList', $player->login);
+ $this->client->query('Ban', $player->login);
+
+ // update blacklist file
+ $filename = $this->settings['blacklist_file'];
+ $rtn = $this->client->query('SaveBlackList', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $this->client->getErrorCode() . '] SaveBlackList (ban) - ' . $this->client->getErrorMessage(), E_USER_WARNING);
+ }
+ break;
+
+ default: // ignore
+ }
+ } // processCheater
+
+
+ /**
+ * Finds a player ID from its login.
+ */
+ function getPlayerId($login, $forcequery = false) {
+
+ if (isset($this->server->players->player_list[$login]) &&
+ $this->server->players->player_list[$login]->id > 0 && !$forcequery) {
+ $rtn = $this->server->players->player_list[$login]->id;
+ } else {
+ $query = 'SELECT id FROM players
+ WHERE login=' . quotedString($login);
+ $result = mysql_query($query);
+ if (mysql_num_rows($result) > 0) {
+ $row = mysql_fetch_row($result);
+ $rtn = $row[0];
+ } else {
+ $rtn = 0;
+ }
+ mysql_free_result($result);
+ }
+ return $rtn;
+ } // getPlayerId
+
+ /**
+ * Finds a player Nickname from its login.
+ */
+ function getPlayerNick($login, $forcequery = false) {
+
+ if (isset($this->server->players->player_list[$login]) &&
+ $this->server->players->player_list[$login]->nickname != '' && !$forcequery) {
+ $rtn = $this->server->players->player_list[$login]->nickname;
+ } else {
+ $query = 'SELECT nickname FROM players
+ WHERE login=' . quotedString($login);
+ $result = mysql_query($query);
+ if (mysql_num_rows($result) > 0) {
+ $row = mysql_fetch_row($result);
+ $rtn = $row[0];
+ } else {
+ $rtn = '';
+ }
+ mysql_free_result($result);
+ }
+ return $rtn;
+ } // getPlayerNick
+
+
+ /**
+ * Finds an online player object from its login or Player_ID
+ * If $offline = true, search player database instead
+ * Returns false if not found
+ */
+ function getPlayerParam($player, $param, $offline = false) {
+
+ // if numeric param, find Player_ID from /players list (hardlimited to 300)
+ if (is_numeric($param) && $param >= 0 && $param < 300) {
+ if (empty($player->playerlist)) {
+ $message = '{#server}> {#error}Use {#highlite}$i/players {#error}first (optionally {#highlite}$i/players {#error})';
+ $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login);
+ return false;
+ }
+ $pid = ltrim($param, '0');
+ $pid--;
+ // find player by given #
+ if (array_key_exists($pid, $player->playerlist)) {
+ $param = $player->playerlist[$pid]['login'];
+ // check online players list
+ $target = $this->server->players->getPlayer($param);
+ } else {
+ // try param as login string as yet
+ $target = $this->server->players->getPlayer($param);
+ if (!$target) {
+ $message = '{#server}> {#error}Player_ID not found! Type {#highlite}$i/players {#error}to see all players.';
+ $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login);
+ return false;
+ }
+ }
+ } else { // otherwise login string
+ // check online players list
+ $target = $this->server->players->getPlayer($param);
+ }
+
+ // not found and offline allowed?
+ if (!$target && $offline) {
+ // check offline players database
+ $query = 'SELECT * FROM players
+ WHERE login=' . quotedString($param);
+ $result = mysql_query($query);
+ if (mysql_num_rows($result) > 0) {
+ $row = mysql_fetch_object($result);
+ // create dummy player object
+ $target = new Player();
+ $target->id = $row->Id;
+ $target->login = $row->Login;
+ $target->nickname = $row->NickName;
+ $target->nation = $row->Nation;
+ $target->teamname = $row->TeamName;
+ $target->wins = $row->Wins;
+ $target->timeplayed = $row->TimePlayed;
+ }
+ mysql_free_result($result);
+ }
+
+ // found anyone anywhere?
+ if (!$target) {
+ $message = '{#server}> {#highlite}' . $param . ' {#error}is not a valid player! Use {#highlite}$i/players {#error}to find the correct login or Player_ID.';
+ $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login);
+ }
+ return $target;
+ } // getPlayerParam
+
+
+ /**
+ * Finds a challenge ID from its UID.
+ */
+ function getChallengeId($uid) {
+
+ $query = 'SELECT Id FROM challenges
+ WHERE Uid=' . quotedString($uid);
+ $res = mysql_query($query);
+ if (mysql_num_rows($res) > 0) {
+ $row = mysql_fetch_row($res);
+ $rtn = $row[0];
+ } else {
+ $rtn = 0;
+ }
+ mysql_free_result($res);
+ return $rtn;
+ } // getChallengeId
+
+ /**
+ * Gets current servername
+ */
+ function getServerName() {
+
+ $this->client->query('GetServerName');
+ $this->server->name = $this->client->getResponse();
+ return $this->server->name;
+ }
+
+ /**
+ * Gets current server name & options
+ */
+ function getServerOptions() {
+
+ $this->client->query('GetServerOptions');
+ $options = $this->client->getResponse();
+ $this->server->name = $options['Name'];
+ $this->server->maxplay = $options['CurrentMaxPlayers'];
+ $this->server->maxspec = $options['CurrentMaxSpectators'];
+ $this->server->votetime = $options['CurrentCallVoteTimeOut'];
+ $this->server->voterate = $options['CallVoteRatio'];
+ }
+
+
+ /**
+ * Formats aseco color codes in a string,
+ * for example '{#server} hello' will end up as '$ff0 hello'.
+ * It depends on what you've set in the config file.
+ */
+ function formatColors($text) {
+
+ // replace all chat colors
+ foreach ($this->chat_colors as $key => $value) {
+ $text = str_replace('{#'.strtolower($key).'}', $value[0], $text);
+ }
+ return $text;
+ } // formatColors
+
+
+ /**
+ * Outputs a formatted string without datetime.
+ */
+ function console_text() {
+
+ $args = func_get_args();
+ $message = call_user_func_array('formatText', $args) . CRLF;
+ echo $message;
+ doLog($message);
+ flush();
+ } // console_text
+
+ /**
+ * Outputs a string to console with datetime prefix.
+ */
+ function console() {
+
+ $args = func_get_args();
+ $message = '[' . date('m/d,H:i:s') . '] ' . call_user_func_array('formatText', $args) . CRLF;
+ echo $message;
+ doLog($message);
+ flush();
+ } // console
+
+} // class Aseco
+
+// define process settings
+if (function_exists('date_default_timezone_get') && function_exists('date_default_timezone_set'))
+ date_default_timezone_set(@date_default_timezone_get());
+$limit = ini_get('memory_limit');
+if (shorthand2bytes($limit) < 128 * 1048576)
+ ini_set('memory_limit', '128M');
+setlocale(LC_NUMERIC, 'C');
+
+// create an instance of XASECO and run it
+$aseco = new Aseco(false);
+$aseco->run('config.xml');
+?>
diff --git a/xaseco/autotime.xml b/xaseco/autotime.xml
new file mode 100644
index 0000000..1ecf066
--- /dev/null
+++ b/xaseco/autotime.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ 6
+
+ 3.5
+
+ 7
+
+ 5
+
+ 1
+ {#server}>> Set {1} timelimit for {#highlite}{2}{#server} : {#highlite}{3}{#server} (Author time: {#highlite}{4}{#server})
+
diff --git a/xaseco/bannedips.xml b/xaseco/bannedips.xml
new file mode 100644
index 0000000..c636898
--- /dev/null
+++ b/xaseco/bannedips.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/xaseco/config.xml b/xaseco/config.xml
new file mode 100644
index 0000000..c536663
--- /dev/null
+++ b/xaseco/config.xml
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+
+
+ @MASTERADMIN_LOGIN@
+
+
+
+
+ $f00$i
+ $f00
+ $ff0
+ $fff
+ $bbb
+ $0f3
+ $fa0
+ $d80
+ $39f
+ $ff3
+ $f8f
+ $ff0
+ $f0f
+ $ff0
+ $000
+ $888
+ $00f
+ $0c0
+ $f00
+ $ff0$i
+ $28b
+ $0b3
+
+
+
+
+ {#server}*** XASECO {#highlite}v{1}{#server} running on {#highlite}{2}{#server}:{#highlite}{3}{#server} ***
+ {#welcome}Welcome {#highlite}{1}{#welcome} to {#highlite}{2}$z$s{br}{#welcome}This server uses {#highlite}XASECO v{3}{#welcome} to manage your records.
+ $s{#welcome}This is an administrative warning.{br}{br}$gWhatever you wrote is against our server's{br}policy. Not respecting other players, or{br}using offensive language might result in a{br}{#welcome}kick, or ban {#message}the next time.{br}{br}$gThe server administrators.
+
+
+ {#server}>> {#message}Current record on {#highlite}{1}{#message} is {#highlite}{2}{#message} by {#highlite}{3}
+ {#server}>> {#message}Currently no record on {#highlite}{1}{#message} ...
+ {#server}>> {#error}Could not get records from database... No records this round!
+
+
+ {#server}>> {#message}Local Record rankings on {#highlite}{1}{#message} {2} this round:
+ {#server}>> {#message}Local Record rankings on {#highlite}{1}{#message} {2} this round (range {#highlite}{3}{#message}):
+ {#server}>> {#message}Local Record rankings on {#highlite}{1}{#message} {2} this round ({#highlite}{3}{#message} new):
+ {#server}>> {#message}Local Record rankings on {#highlite}{1}{#message} {2} this round: none new so far
+ {#server}>> {#message}Local Record rankings on {#highlite}{1}{#message} {2} this round: no records!
+
+
+ {#rank}{1}{#message}.$i{#highlite}{2}{#message}[{#highlite}{3}{#message}]$i,
+ {#rank}{1}{#message}.{#highlite}{2}{#message}[{#highlite}{3}{#message}],
+ {#rank}{1}{#message}.$i{#timelite}{2}{#message}[{#timelite}{3}{#message}]$i,
+ {#rank}{1}{#message}.{#timelite}{2}{#message}[{#timelite}{3}{#message}],
+ {#rank}{1}{#message}.{#timelite}{2}{#message},
+
+
+ {#server}> {#record}The first Local record is:
+ {#server}> {#record}The last Local record is:
+ {#server}> {#record}Difference between {1}{#record} and {2}{#record} is: {#highlite}{3}
+ {#server}> {#highlite}{1} $z$s{#record}has {#highlite}{2}{#record} Local record{3}, the top {4} being:
+ {#highlite}{1} {#record}rec{2} #{#rank}{3}{#record},
+
+
+ {#server}> {#record}You have already won {#highlite}{1}{#record} race{2}
+ {#server}> {#record}Congratulations, you won your {#highlite}{1}{#record}. race!
+ {#server}>> {#record}Congratulations, {#highlite}{1}{#record} won his/her {#highlite}{2}{#record}. race!
+
+
+ {#server}> Player {#highlite}{1}$z$s{#server} is muted!
+ {#server}> Player {#highlite}{1}$z$s{#server} is unmuted!
+ {#server}> {#highlite}{1}{#error} disabled because you are on the global mute list!
+
+
+ {#donate} Donate {#highlite}{1}{#donate} coppers to {#highlite}{2}$z
+ {#server}>> {#highlite}{1}$z$s{#donate} received a donation of {#highlite}{2}{#donate} coppers from {#highlite}{3}$z$s{#donate}. Thank You!
+ {#server}> {#donate}You made a donation of {#highlite}{1}{#donate} coppers. Thank You!
+ {#server}> {#error}Minimum donation amount is {#highlite}$i {1}{#error} coppers!
+ {#server}> {#error}Use {#highlite}$i /donate <number>{#error} to donate coppers to the server
+ {#donate} Send {#highlite}{1}{#donate} coppers to {#highlite}{2}$z
+ {#server}> {#error}Insufficient server coppers: {#highlite}$i {1}{#error}!
+ {#server}> {#error}Cannot pay this server itself!
+ {#server}> {#donate}Payment of {#highlite}{1}{#donate} coppers to {#highlite}{2}{#donate} confirmed! Remaining coppers: {#highlite}{3}
+ {#server}> {#donate}Payment to {#highlite}{1}{#donate} cancelled!
+ {#server}> {#error}Use {#highlite}$i /admin pay <login> $m<number>{#error} to send server coppers to a login
+
+
+ {#server}> Current track {#highlite}{1}{#server} has been played for {#highlite}{2}
+ {#server}>> Current track {#highlite}{1}{#server} finished after {#highlite}{2}
+ {#server}({#highlite}{1}{#server} replay{2}, total {#highlite}{3}{#server})
+
+ {#server}>> Current track {#highlite}{1} {#server}by {#highlite}{2} {#server}Author: {#highlite}{3}
+
+
+ {#server}> {1}Custom points system set to {#highlite}{2}{3}: {#highlite}{4},...
+ {#server}> {1}Custom points system set to: {#highlite}{2},...
+ {#server}> {1}No custom Rounds points system defined!
+
+
+ {#server}> {#error}No relay servers connected
+ {#server}> This server relays master server: {#highlite}{1}{#server} ({#highlite}{2}{#server})
+ {#server}> {#error}Command unavailable on relay server
+
+
+ {#server}>> {#message}This XASECO version {#highlite}{1}{#message} is up to date
+ {#server}>> {#message}New XASECO version {#highlite}{1}{#message} available from {#highlite}{2}
+
+
+ {#welcome}Your IP was banned from this server.$z
+ {#welcome}Could not connect:{br}{br}Your IP was banned from this server!
+ {#welcome}Obsolete client version, please $l[http://www.tm-forum.com/viewtopic.php?p=139752#p139752]upgrade$l.$z
+ {#welcome}Obsolete client version!{br}Please upgrade to the $l[http://www.tm-forum.com/viewtopic.php?p=139752#p139752]latest version$l.
+ {#welcome}Connection problem, please retry.$z
+ {#welcome}$sThis is an administrative notice.$z{br}{br}XASECO encountered a very rare player connection{br}problem. Please re-join the server to correct it.{br}Apologies for the inconvenience.{br}{br}$sThe server administrators.
+
+
+ {#server}>> IdleKick player {#highlite}{1}$z$s{#server} after {#highlite}{2}{#server} challenge{3}!
+ {#server}>> IdleSpec player {#highlite}{1}$z$s{#server} after {#highlite}{2}{#server} challenge{3}
+ {#server}>> IdleKick spectator {#highlite}{1}$z$s{#server} after {#highlite}{2}{#server} challenge{3}!
+
+
+ {#server}> Track {#highlite}{1} {#server}plays song: {#highlite}{2}
+ {#server}> Track {#highlite}{1} {#server}uses mod: {#highlite}{2} {#server}({#highlite}{3}{#server})
+ {#server}> Server {#highlite}{1}$z$s {#server}owns {#highlite}{2} {#server}coppers!
+
+ {#server}>> {#record}TMX World Record: {#highlite}{1}{#record} by {#highlite}{2}
+ $n{#message}R{#highlite}{1}{#message}>
+ {#server}> {#highlite}/cpsspec{#server} is not currently enabled on this server.
+ {#server}> {#error}You have to be in admin list to do that!
+ {#server}> Press the {#highlite}C{#server} key to see the whole list, and use {#highlite}/helpall{#server} for details
+ {#server}> {#error}This requires a TM United Forever {1}!
+ {#server}> {#error}Command only available on TM Forever!
+
+
+ False
+
+ False
+ True
+ 0
+ 60
+
+ 8
+
+
+
+ 2
+
+
+
+ 2
+ True
+
+ 1
+
+ 1
+
+ 0
+
+ tracklist.txt
+ True
+ False
+ True
+ True
+ 2
+ 50
+ True
+ False
+ True
+ False
+ True
+ adminops.xml
+ bannedips.xml
+ blacklist.txt
+ guestlist.txt
+ trackhist.txt
+
+
+ 2.11.19
+
+
+ True
+ True
+ True
+
+ False
+
+ False
+
+ 6
+
+
+
+ False
+
+
+
+ DarkBlur
+
+
+ AdminBelowChat
+ DonateBelowCPList
+ RecordsRightBottom
+ VoteBelowChat
+
+
+
+ SuperAdmin
+ @SERVER_SA_PASSWORD@
+ @TMSERVER_HOST@
+ @TMSERVER_PORT@
+ 180
+
+
diff --git a/xaseco/dedimania.xml b/xaseco/dedimania.xml
new file mode 100644
index 0000000..8ab1d18
--- /dev/null
+++ b/xaseco/dedimania.xml
@@ -0,0 +1,74 @@
+
+
+
+ {#welcome}Welcome to the Dedimania world record system at www.dedimania.com - see {#highlite}/helpdedi
+ {#dedimsg}Dedimania system timed out - retrying in {#highlite}{1}{#dedimsg} minutes
+ Dedimania
+
+ http://dedimania.net:8002/Dedimania
+
+ True
+
+ True
+
+
+ 8
+
+
+ 1
+
+
+ 1
+
+ True
+
+ True
+
+ True
+
+ False
+
+
+
+ 30
+
+
+
+
+
+
+
+
+ YOUR_SERVER_LOGIN
+ YOUR_SERVER_PASSWORD
+ YOUR_SERVER_NATION
+
+
+
+
+ {#server}>> {#dedimsg}Dedimania Record rankings on {#highlite}{1}{#dedimsg} {2} this round:
+ {#server}>> {#dedimsg}Dedimania Record rankings on {#highlite}{1}{#dedimsg} {2} this round (range {#highlite}{3}{#dedimsg}):
+ {#server}>> {#dedimsg}Dedimania Record rankings on {#highlite}{1}{#dedimsg} {2} this round ({#highlite}{3}{#dedimsg} new):
+ {#server}>> {#dedimsg}Dedimania Record rankings on {#highlite}{1}{#dedimsg} {2} this round: none new so far
+ {#server}>> {#dedimsg}Dedimania Record rankings on {#highlite}{1}{#dedimsg} {2} this round: no records!
+
+
+ {#server}>> {#highlite}{1}{#dedirec} secured his/her {#rank}{2}{#dedirec}. Dedimania Record! {3}: {#highlite}{4}{#dedirec} $n({#rank}{5}{#highlite}{6}{#dedirec})
+ {#server}>> {#highlite}{1}{#dedirec} equaled his/her {#rank}{2}{#dedirec}. Dedimania Record! {3}: {#highlite}{4}
+ {#server}>> {#highlite}{1}{#dedirec} gained the {#rank}{2}{#dedirec}. Dedimania Record! {3}: {#highlite}{4}{#dedirec} $n({#rank}{5}{#highlite}{6}{#dedirec})
+ {#server}>> {#highlite}{1}{#dedirec} claimed the {#rank}{2}{#dedirec}. Dedimania Record! {3}: {#highlite}{4}
+
+
+ {#server}> {#dedirec}The first Dedimania record is:
+ {#server}> {#dedirec}The last Dedimania record is:
+ {#server}> {#dedirec}Difference between {1}{#dedirec} and {2}{#dedirec} is: {#highlite}{3}
+
+
+ {#server}> {#dedirec}Dedimania Personal Best: {#highlite}{1}{#dedirec}({#rank}{2}{#dedirec})
+ {#server}> {#error}You don't have a Dedimania record on this track yet...
+
+
+ {#server}>> {#highlite}{1} {#server}({#highlite}{2}{#server}) {#error}is banned from Dedimania!
+ {#server}> {#error}Finish ignored by Dedimania as you are banned!
+
+
diff --git a/xaseco/includes/GbxRemote.bem.php b/xaseco/includes/GbxRemote.bem.php
new file mode 100644
index 0000000..3b8dafd
--- /dev/null
+++ b/xaseco/includes/GbxRemote.bem.php
@@ -0,0 +1,891 @@
+ htmlspecialchars)
+ Site: http://scripts.incutio.com/xmlrpc/
+ Manual: http://scripts.incutio.com/xmlrpc/manual.php
+ Errors: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
+ Made available under the Artistic License: http://www.opensource.org/licenses/artistic-license.php
+
+ Modified to support protocol 'GbxRemote 2' ('GbxRemote 1')
+ This version is for BigEndian machines. For LittleEndian (e.g. Intel PC)
+ machines use the original GbxRemote.inc.php instead.
+
+ Release 2007-09-22 - Slig:
+ Modified to support >256KB received data (and now >2MB data produce a specific error message)
+ Modified readCB() to wait the initial timeout only before first read packet
+ Modified readCB() to return true if there is data to get with getCBResponses()
+ Modified to support amd64 (for $recvhandle)
+ Modified IXR_ClientMulticall_Gbx::addCall() to fit Aseco 0.6.1
+ Added IXR_Client_Gbx::bytes_sent & bytes_received counters
+ Fix for a changed feature since php5.1.1 about reference parameter assignment (was used in stream_select)
+ Workaround for stream_select return value bug with amd64
+
+ Release 2008-01-20 - Slig / Xymph / Assembler Maniac:
+ Workaround for fread delay bug in some cases
+ Added IXR_Client_Gbx::resetError() method (by Xymph)
+ Some comments and strings code cleanup (by Xymph)
+ Fix stream_set_timeout($this->socket,...) (thx to CavalierDeVache)
+ Added a default timeout value to IXR_Client_Gbx::readCB($timeout)
+ Changed calls with timeout on a stream to use microseconds instead of seconds (by AM)
+ Removed IXR_Client_Gbx::bytes_sent & bytes_received counters - not used (by AM)
+
+ Release 2008-02-05 - Slig:
+ Changed some socket read/write timeouts back to seconds to avoid 'transport error'
+ Changed max data received from 2MB to 4MB
+
+ Release 2008-05-20 - Xymph:
+ Prevented unpack() warnings in IXR_Client_Gbx::query() when the connection dies
+ Changed IXR_Client_Gbx::resetError() to assign 'false' for correct isError()
+ Tweaked some 'transport error' messages
+
+ Release 2009-04-08 - Gou1:
+ Added method IXR_Client_Gbx::queryIgnoreResult()
+ Added methods IXR_Client_Gbx::sendRequest() & IXR_Client_Gbx::getResult()
+ IXR_Client_Gbx::queryIgnoreResult checks if the request is larger than 512KB to avoid errors
+ If larger than 512KB and method is system.multicall, try to divide the request into
+ two separate requests with two separate IXR_Client_Gbx::queryIgnoreResult() calls
+
+ Release 2009-06-03 - Xymph:
+ Suppress possible repetitive CRT warning at stream_select
+
+ Release 2011-04-09 - Xymph / La beuze:
+ Added optional timeout mechanism to IXR_Client_Gbx::InitWithIp()
+
+ Release 2011-05-22 - Xymph:
+ Added non-error (true) return status to IXR_Client_Gbx::queryIgnoreResult()
+ Updated status codes and messages for transport/endian errors
+ Prevented possible PHP warning in IXR_Client_Gbx::getErrorCode() and getErrorMessage()
+
+ Release 2011-12-04 - Xymph:
+ Prevented possible PHP warning in IXR_Value::calculateType
+
+ Release 2013-02-18 - Xymph:
+ Removed unnecessary breaks after returns in IXR_Value::getXml() switch
+*/
+
+if (!defined('LF')) {
+ define('LF', "\n");
+}
+
+class IXR_Value {
+ public $data;
+ public $type;
+
+ function IXR_Value ($data, $type = false) {
+ $this->data = $data;
+ if (!$type) {
+ $type = $this->calculateType();
+ }
+ $this->type = $type;
+ if ($type == 'struct') {
+ // Turn all the values in the array into new IXR_Value objects
+ foreach ($this->data as $key => $value) {
+ $this->data[$key] = new IXR_Value($value);
+ }
+ }
+ if ($type == 'array') {
+ for ($i = 0, $j = count($this->data); $i < $j; $i++) {
+ $this->data[$i] = new IXR_Value($this->data[$i]);
+ }
+ }
+ }
+
+ function calculateType() {
+ if ($this->data === true || $this->data === false) {
+ return 'boolean';
+ }
+ if (is_integer($this->data)) {
+ return 'int';
+ }
+ if (is_double($this->data)) {
+ return 'double';
+ }
+ // Deal with IXR object types base64 and date
+ if (is_object($this->data) && ($this->data instanceof IXR_Date)) {
+ return 'date';
+ }
+ if (is_object($this->data) && ($this->data instanceof IXR_Base64)) {
+ return 'base64';
+ }
+ // If it is a normal PHP object convert it into a struct
+ if (is_object($this->data)) {
+ $this->data = get_object_vars($this->data);
+ return 'struct';
+ }
+ if (!is_array($this->data)) {
+ return 'string';
+ }
+ // We have an array - is it an array or a struct?
+ if ($this->isStruct($this->data)) {
+ return 'struct';
+ } else {
+ return 'array';
+ }
+ }
+
+ function getXml() {
+ // Return XML for this value
+ switch ($this->type) {
+ case 'boolean':
+ return '' . ($this->data ? '1' : '0') . '';
+ case 'int':
+ return '' . $this->data . '';
+ case 'double':
+ return '' . $this->data . '';
+ case 'string':
+ return '' . htmlspecialchars($this->data) . '';
+ case 'array':
+ $return = '' . LF;
+ foreach ($this->data as $item) {
+ $return .= ' ' . $item->getXml() . '' . LF;
+ }
+ $return .= '';
+ return $return;
+ case 'struct':
+ $return = '' . LF;
+ foreach ($this->data as $name => $value) {
+ $return .= ' ' . $name . '';
+ $return .= $value->getXml() . '' . LF;
+ }
+ $return .= '';
+ return $return;
+ case 'date':
+ case 'base64':
+ return $this->data->getXml();
+ }
+ return false;
+ }
+
+ function isStruct($array) {
+ // Nasty function to check if an array is a struct or not
+ $expected = 0;
+ foreach ($array as $key => $value) {
+ if ((string)$key != (string)$expected) {
+ return true;
+ }
+ $expected++;
+ }
+ return false;
+ }
+}
+
+
+class IXR_Message {
+ public $message;
+ public $messageType; // methodCall / methodResponse / fault
+ public $faultCode;
+ public $faultString;
+ public $methodName;
+ public $params;
+ // Current variable stacks
+ protected $_arraystructs = array(); // Stack to keep track of the current array/struct
+ protected $_arraystructstypes = array(); // Stack to keep track of whether things are structs or array
+ protected $_currentStructName = array(); // A stack as well
+ protected $_param;
+ protected $_value;
+ protected $_currentTag;
+ protected $_currentTagContents;
+ // The XML parser
+ protected $_parser;
+
+ function IXR_Message ($message) {
+ $this->message = $message;
+ }
+
+ function parse() {
+ // first remove the XML declaration
+ $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);
+ if (trim($this->message) == '') {
+ return false;
+ }
+ $this->_parser = xml_parser_create();
+ // Set XML parser to take the case of tags into account
+ xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
+ // Set XML parser callback functions
+ xml_set_object($this->_parser, $this);
+ xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
+ xml_set_character_data_handler($this->_parser, 'cdata');
+ if (!xml_parse($this->_parser, $this->message)) {
+ /* die(sprintf('GbxRemote XML error: %s at line %d',
+ xml_error_string(xml_get_error_code($this->_parser)),
+ xml_get_current_line_number($this->_parser))); */
+ return false;
+ }
+ xml_parser_free($this->_parser);
+ // Grab the error messages, if any
+ if ($this->messageType == 'fault') {
+ $this->faultCode = $this->params[0]['faultCode'];
+ $this->faultString = $this->params[0]['faultString'];
+ }
+ return true;
+ }
+
+ function tag_open($parser, $tag, $attr) {
+ $this->currentTag = $tag;
+ switch ($tag) {
+ case 'methodCall':
+ case 'methodResponse':
+ case 'fault':
+ $this->messageType = $tag;
+ break;
+ // Deal with stacks of arrays and structs
+ case 'data': // data is to all intents and purposes more interesting than array
+ $this->_arraystructstypes[] = 'array';
+ $this->_arraystructs[] = array();
+ break;
+ case 'struct':
+ $this->_arraystructstypes[] = 'struct';
+ $this->_arraystructs[] = array();
+ break;
+ }
+ }
+
+ function cdata($parser, $cdata) {
+ $this->_currentTagContents .= $cdata;
+ }
+
+ function tag_close($parser, $tag) {
+ $valueFlag = false;
+ switch ($tag) {
+ case 'int':
+ case 'i4':
+ $value = (int)trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ case 'double':
+ $value = (double)trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ case 'string':
+ $value = (string)trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ case 'dateTime.iso8601':
+ $value = new IXR_Date(trim($this->_currentTagContents));
+ // $value = $iso->getTimestamp();
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ case 'value':
+ // If no type is indicated, the type is string
+ if (trim($this->_currentTagContents) != '') {
+ $value = (string)$this->_currentTagContents;
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ }
+ break;
+ case 'boolean':
+ $value = (boolean)trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ case 'base64':
+ $value = base64_decode($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ // Deal with stacks of arrays and structs
+ case 'data':
+ case 'struct':
+ $value = array_pop($this->_arraystructs);
+ array_pop($this->_arraystructstypes);
+ $valueFlag = true;
+ break;
+ case 'member':
+ array_pop($this->_currentStructName);
+ break;
+ case 'name':
+ $this->_currentStructName[] = trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ break;
+ case 'methodName':
+ $this->methodName = trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ break;
+ }
+
+ if ($valueFlag) {
+ /*
+ if (!is_array($value) && !is_object($value)) {
+ $value = trim($value);
+ }
+ */
+ if (count($this->_arraystructs) > 0) {
+ // Add value to struct or array
+ if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
+ // Add to struct
+ $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
+ } else {
+ // Add to array
+ $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
+ }
+ } else {
+ // Just add as a paramater
+ $this->params[] = $value;
+ }
+ }
+ }
+}
+
+
+class IXR_Request {
+ public $method;
+ public $args;
+ public $xml;
+
+ function IXR_Request($method, $args) {
+ $this->method = $method;
+ $this->args = $args;
+ $this->xml = '' . $this->method . '';
+ foreach ($this->args as $arg) {
+ $this->xml .= '';
+ $v = new IXR_Value($arg);
+ $this->xml .= $v->getXml();
+ $this->xml .= '' . LF;
+ }
+ $this->xml .= '';
+ }
+
+ function getLength() {
+ return strlen($this->xml);
+ }
+
+ function getXml() {
+ return $this->xml;
+ }
+}
+
+
+class IXR_Error {
+ public $code;
+ public $message;
+
+ function IXR_Error($code, $message) {
+ $this->code = $code;
+ $this->message = $message;
+ }
+
+ function getXml() {
+ $xml = <<
+
+
+
+
+ faultCode
+ {$this->code}
+
+
+ faultString
+ {$this->message}
+
+
+
+
+
+EOD;
+ return $xml;
+ }
+}
+
+
+class IXR_Date {
+ public $year;
+ public $month;
+ public $day;
+ public $hour;
+ public $minute;
+ public $second;
+
+ function IXR_Date($time) {
+ // $time can be a PHP timestamp or an ISO one
+ if (is_numeric($time)) {
+ $this->parseTimestamp($time);
+ } else {
+ $this->parseIso($time);
+ }
+ }
+
+ function parseTimestamp($timestamp) {
+ $this->year = date('Y', $timestamp);
+ $this->month = date('Y', $timestamp);
+ $this->day = date('Y', $timestamp);
+ $this->hour = date('H', $timestamp);
+ $this->minute = date('i', $timestamp);
+ $this->second = date('s', $timestamp);
+ }
+
+ function parseIso($iso) {
+ $this->year = substr($iso, 0, 4);
+ $this->month = substr($iso, 4, 2);
+ $this->day = substr($iso, 6, 2);
+ $this->hour = substr($iso, 9, 2);
+ $this->minute = substr($iso, 12, 2);
+ $this->second = substr($iso, 15, 2);
+ }
+
+ function getIso() {
+ return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second;
+ }
+
+ function getXml() {
+ return ''.$this->getIso().'';
+ }
+
+ function getTimestamp() {
+ return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
+ }
+}
+
+
+class IXR_Base64 {
+ public $data;
+
+ function IXR_Base64($data) {
+ $this->data = $data;
+ }
+
+ function getXml() {
+ return ''.base64_encode($this->data).'';
+ }
+}
+
+
+//////////////////////////////////////////////////////////
+// Nadeo modifications //
+// (many thanks to slig for adding callback support) //
+//////////////////////////////////////////////////////////
+class IXR_Client_Gbx {
+ public $socket;
+ public $message = false;
+ public $cb_message = array();
+ public $reqhandle;
+ public $protocol = 0;
+ // Storage place for an error message
+ public $error = false;
+
+ function bigEndianTest() {
+ list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
+ if ($endiantest == 1) {
+ echo "Machine reports itself as LittleEndian, float handling will work correctly with unpack\r\n";
+ echo "Use original GbxRemote.inc.php instead of GbxRemote.bem.php\r\n";
+ die('App Terminated');
+ return false;
+ }
+ return true;
+ } // bigEndianTest
+
+ function IXR_Client_Gbx() {
+ $this->socket = false;
+ $this->reqhandle = 0x80000000;
+ }
+
+ function InitWithIp($ip, $port, $timeout = null) {
+
+ if (!$this->bigEndianTest()) {
+ $this->error = new IXR_Error(-31999, 'endian error - script doesn\'t match machine type');
+ return false;
+ }
+
+ // open connection, with timeout if specified
+ if (!isset($timeout)) {
+ $this->socket = @fsockopen($ip, $port, $errno, $errstr);
+ } else {
+ $init_time = microtime(true);
+ $init_timeout = 5; // retry every 5s
+ while (true) {
+ $this->socket = @fsockopen($ip, $port, $errno, $errstr, $init_timeout);
+ if ($this->socket || (microtime(true) - $init_time >= $timeout))
+ break;
+ }
+ }
+ if (!$this->socket) {
+ $this->error = new IXR_Error(-32300, "transport error - could not open socket (error: $errno, $errstr)");
+ return false;
+ }
+ // handshake
+ $array_result = big_endian_unpack('Vsize', fread($this->socket, 4));
+ $size = $array_result['size'];
+ if ($size > 64) {
+ $this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol header');
+ return false;
+ }
+ $handshake = fread($this->socket, $size);
+ if ($handshake == 'GBXRemote 1') {
+ $this->protocol = 1;
+ } else if ($handshake == 'GBXRemote 2') {
+ $this->protocol = 2;
+ } else {
+ $this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol version');
+ return false;
+ }
+ return true;
+ }
+
+ function Init($port) {
+ return $this->InitWithIp('localhost', $port);
+ }
+
+ function Terminate() {
+ if ($this->socket) {
+ fclose($this->socket);
+ $this->socket = false;
+ }
+ }
+
+ protected function sendRequest(IXR_Request $request) {
+ $xml = $request->getXml();
+
+ @stream_set_timeout($this->socket, 20); // timeout 20s (to write the request)
+ // send request
+ $this->reqhandle++;
+ if ($this->protocol == 1) {
+ $bytes = pack('Va*', strlen($xml), $xml);
+ } else {
+ $bytes = pack('VVa*', strlen($xml), $this->reqhandle, $xml);
+ }
+
+ $bytes_to_write = strlen($bytes);
+ while ($bytes_to_write > 0) {
+ $r = @fwrite($this->socket, $bytes);
+ if ($r === false || $r == 0) {
+ // connection interrupted
+ return false; // or die?
+ }
+
+ $bytes_to_write -= $r;
+ if ($bytes_to_write == 0)
+ break;
+
+ $bytes = substr($bytes, $r);
+ }
+
+ return true;
+ }
+
+ protected function getResult() {
+ $contents = '';
+ $contents_length = 0;
+ do {
+ $size = 0;
+ $recvhandle = 0;
+ @stream_set_timeout($this->socket, 20); // timeout 20s (to read the reply header)
+ // Get result
+ if ($this->protocol == 1) {
+ $contents = fread($this->socket, 4);
+ if (strlen($contents) == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - cannot read size');
+ return false;
+ }
+ $array_result = big_endian_unpack('Vsize', $contents);
+ $size = $array_result['size'];
+ $recvhandle = $this->reqhandle;
+ } else {
+ $contents = fread($this->socket, 8);
+ if (strlen($contents) == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
+ return false;
+ }
+ $array_result = big_endian_unpack('Vsize/Vhandle', $contents);
+ $size = $array_result['size'];
+ $recvhandle = $array_result['handle'];
+ // -- amd64 support --
+ $bits = sprintf('%b', $recvhandle);
+ if (strlen($bits) == 64) {
+ $recvhandle = bindec(substr($bits, 32));
+ }
+ }
+
+ if ($recvhandle == 0 || $size == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
+ return false;
+ }
+ if ($size > 4096*1024) {
+ $this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
+ return false;
+ }
+
+ $contents = '';
+ $contents_length = 0;
+ @stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (for successive reads until end)
+ while ($contents_length < $size) {
+ $contents .= fread($this->socket, $size-$contents_length);
+ $contents_length = strlen($contents);
+ }
+
+ if (($recvhandle & 0x80000000) == 0) {
+ // this is a callback, not our answer! handle= $recvhandle, xml-rpc= $contents
+ // just add it to the message list for the user to read
+ $new_cb_message = new IXR_Message($contents);
+ if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
+ array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
+ }
+ }
+ } while ((int)$recvhandle != (int)$this->reqhandle);
+
+ $this->message = new IXR_Message($contents);
+ if (!$this->message->parse()) {
+ // XML error
+ $this->error = new IXR_Error(-32700, 'parse error. not well formed');
+ return false;
+ }
+ // Is the message a fault?
+ if ($this->message->messageType == 'fault') {
+ $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
+ return false;
+ }
+ // Message must be OK
+ return true;
+ }
+
+
+ function query() {
+ $args = func_get_args();
+ $method = array_shift($args);
+
+ if (!$this->socket || $this->protocol == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - client not initialized');
+ return false;
+ }
+
+ $request = new IXR_Request($method, $args);
+
+ // Check if request is larger than 512 Kbytes
+ if (($size = $request->getLength()) > 512*1024-8) {
+ $this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
+ return false;
+ }
+
+ // Send request
+ $ok = $this->sendRequest($request);
+ if (!$ok) {
+ $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
+ return false;
+ }
+
+ // Get result
+ return $this->getResult();
+ }
+
+ // Non-blocking query method: doesn't read the response
+ function queryIgnoreResult() {
+ $args = func_get_args();
+ $method = array_shift($args);
+
+ if (!$this->socket || $this->protocol == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - client not initialized');
+ return false;
+ }
+
+ $request = new IXR_Request($method, $args);
+
+ // Check if the request is greater than 512 Kbytes to avoid errors
+ // If the method is system.multicall, make two calls (possibly recursively)
+ if (($size = $request->getLength()) > 512*1024-8) {
+ if ($method = 'system.multicall' && isset($args[0])) {
+ $count = count($args[0]);
+ // If count is 1, query cannot be reduced
+ if ($count < 2) {
+ $this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
+ return false;
+ }
+ $length = floor($count/2);
+ $args1 = array_slice($args[0], 0, $length);
+ $args2 = array_slice($args[0], $length, ($count-$length));
+
+ $res1 = $this->queryIgnoreResult('system.multicall', $args1);
+ $res2 = $this->queryIgnoreResult('system.multicall', $args2);
+ return ($res1 && $res2);
+ }
+ // If the method is not a multicall, just stop
+ else {
+ $this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
+ return false;
+ }
+ }
+
+ // Send request
+ $ok = $this->sendRequest($request);
+ if (!$ok) {
+ $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
+ return false;
+ }
+ return true;
+ }
+
+ function readCB($timeout = 2000) { // timeout 2 ms
+ if (!$this->socket || $this->protocol == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - client not initialized');
+ return false;
+ }
+ if ($this->protocol == 1)
+ return false;
+
+ $something_received = count($this->cb_message)>0;
+ $contents = '';
+ $contents_length = 0;
+
+ @stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (to read available data)
+ // (assignment in arguments is forbidden since php 5.1.1)
+ $read = array($this->socket);
+ $write = NULL;
+ $except = NULL;
+ $nb = @stream_select($read, $write, $except, 0, $timeout);
+ // workaround for stream_select bug with amd64
+ if ($nb !== false)
+ $nb = count($read);
+
+ while ($nb !== false && $nb > 0) {
+ $timeout = 0; // we don't want to wait for the full time again, just flush the available data
+
+ $size = 0;
+ $recvhandle = 0;
+ // Get result
+ $contents = fread($this->socket, 8);
+ if (strlen($contents) == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
+ return false;
+ }
+ $array_result = big_endian_unpack('Vsize/Vhandle', $contents);
+ $size = $array_result['size'];
+ $recvhandle = $array_result['handle'];
+
+ if ($recvhandle == 0 || $size == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
+ return false;
+ }
+ if ($size > 4096*1024) {
+ $this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
+ return false;
+ }
+
+ $contents = '';
+ $contents_length = 0;
+ while ($contents_length < $size) {
+ $contents .= fread($this->socket, $size-$contents_length);
+ $contents_length = strlen($contents);
+ }
+
+ if (($recvhandle & 0x80000000) == 0) {
+ // this is a callback. handle= $recvhandle, xml-rpc= $contents
+ //echo 'CALLBACK('.$contents_length.')[ '.$contents.' ]' . LF;
+ $new_cb_message = new IXR_Message($contents);
+ if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
+ array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
+ }
+ $something_received = true;
+ }
+
+ // (assignment in arguments is forbidden since php 5.1.1)
+ $read = array($this->socket);
+ $write = NULL;
+ $except = NULL;
+ $nb = @stream_select($read, $write, $except, 0, $timeout);
+ // workaround for stream_select bug with amd64
+ if ($nb !== false)
+ $nb = count($read);
+ }
+ return $something_received;
+ }
+
+ function getResponse() {
+ // methodResponses can only have one param - return that
+ return $this->message->params[0];
+ }
+
+ function getCBResponses() {
+ // (look at the end of basic.php for an example)
+ $messages = $this->cb_message;
+ $this->cb_message = array();
+ return $messages;
+ }
+
+ function isError() {
+ return is_object($this->error);
+ }
+
+ function resetError() {
+ $this->error = false;
+ }
+
+ function getErrorCode() {
+ if ($this->isError())
+ return $this->error->code;
+ else
+ return 0;
+ }
+
+ function getErrorMessage() {
+ if ($this->isError())
+ return $this->error->message;
+ else
+ return '';
+ }
+}
+
+
+class IXR_ClientMulticall_Gbx extends IXR_Client_Gbx {
+ public $calls = array();
+
+ function addCall($methodName, $args) {
+ $struct = array('methodName' => $methodName, 'params' => $args);
+ $this->calls[] = $struct;
+
+ return (count($this->calls) - 1);
+ }
+
+ function multiquery($ignoreResult = false) {
+ // Prepare multicall, then call the parent::query() (or queryIgnoreResult) method
+ if ($ignoreResult) {
+ $result = parent::queryIgnoreResult('system.multicall', $this->calls);
+ } else {
+ $result = parent::query('system.multicall', $this->calls);
+ }
+ $this->calls = array(); // reset for next calls
+ return $result;
+ }
+}
+
+/**
+ * The following code is a workaround for php's unpack function which
+ * does not have the capability of unpacking double precision floats
+ * that were packed in the opposite byte order of the current machine.
+ */
+function big_endian_unpack($format, $data) {
+ $ar = unpack($format, $data);
+ $vals = array_values($ar);
+ $f = explode('/', $format);
+ $i = 0;
+ foreach ($f as $f_k => $f_v) {
+ $repeater = intval(substr($f_v, 1));
+ if ($repeater == 0)
+ $repeater = 1;
+ if ($f_v{1} == '*') {
+ $repeater = count($ar) - $i;
+ }
+ if ($f_v{0} != 'd') {
+ $i += $repeater;
+ continue;
+ }
+ $j = $i + $repeater;
+ for ($a = $i; $a < $j; ++$a) {
+ $p = pack('d', $vals[$i]);
+ $p = strrev($p);
+ list($vals[$i]) = array_values(unpack('d1d', $p));
+ ++$i;
+ }
+ }
+ $a = 0;
+ foreach ($ar as $ar_k => $ar_v) {
+ $ar[$ar_k] = $vals[$a];
+ ++$a;
+ }
+ return $ar;
+}
+?>
diff --git a/xaseco/includes/GbxRemote.inc.php b/xaseco/includes/GbxRemote.inc.php
new file mode 100644
index 0000000..dbb3247
--- /dev/null
+++ b/xaseco/includes/GbxRemote.inc.php
@@ -0,0 +1,854 @@
+ htmlspecialchars)
+ Site: http://scripts.incutio.com/xmlrpc/
+ Manual: http://scripts.incutio.com/xmlrpc/manual.php
+ Errors: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
+ Made available under the Artistic License: http://www.opensource.org/licenses/artistic-license.php
+
+ Modified to support protocol 'GbxRemote 2' ('GbxRemote 1')
+ This version is for LittleEndian (e.g. Intel PC) machines. For BegEndian
+ machines use GbxRemote.bem.php as GbxRemote.inc.php instead.
+
+ Release 2007-09-22 - Slig:
+ Modified to support >256KB received data (and now >2MB data produce a specific error message)
+ Modified readCB() to wait the initial timeout only before first read packet
+ Modified readCB() to return true if there is data to get with getCBResponses()
+ Modified to support amd64 (for $recvhandle)
+ Modified IXR_ClientMulticall_Gbx::addCall() to fit Aseco 0.6.1
+ Added IXR_Client_Gbx::bytes_sent & bytes_received counters
+ Fix for a changed feature since php5.1.1 about reference parameter assignment (was used in stream_select)
+ Workaround for stream_select return value bug with amd64
+
+ Release 2008-01-20 - Slig / Xymph / Assembler Maniac:
+ Workaround for fread delay bug in some cases
+ Added IXR_Client_Gbx::resetError() method (by Xymph)
+ Some comments and strings code cleanup (by Xymph)
+ Fix stream_set_timeout($this->socket,...) (thx to CavalierDeVache)
+ Added a default timeout value to IXR_Client_Gbx::readCB($timeout)
+ Changed calls with timeout on a stream to use microseconds instead of seconds (by AM)
+ Removed IXR_Client_Gbx::bytes_sent & bytes_received counters - not used (by AM)
+
+ Release 2008-02-05 - Slig:
+ Changed some socket read/write timeouts back to seconds to avoid 'transport error'
+ Changed max data received from 2MB to 4MB
+
+ Release 2008-05-20 - Xymph:
+ Prevented unpack() warnings in IXR_Client_Gbx::query() when the connection dies
+ Changed IXR_Client_Gbx::resetError() to assign 'false' for correct isError()
+ Tweaked some 'transport error' messages
+
+ Release 2009-04-08 - Gou1:
+ Added method IXR_Client_Gbx::queryIgnoreResult()
+ Added methods IXR_Client_Gbx::sendRequest() & IXR_Client_Gbx::getResult()
+ IXR_Client_Gbx::queryIgnoreResult checks if the request is larger than 512KB to avoid errors
+ If larger than 512KB and method is system.multicall, try to divide the request into
+ two separate requests with two separate IXR_Client_Gbx::queryIgnoreResult() calls
+
+ Release 2009-06-03 - Xymph:
+ Suppress possible repetitive CRT warning at stream_select
+
+ Release 2011-04-09 - Xymph / La beuze:
+ Added optional timeout mechanism to IXR_Client_Gbx::InitWithIp()
+
+ Release 2011-05-22 - Xymph:
+ Added non-error (true) return status to IXR_Client_Gbx::queryIgnoreResult()
+ Updated status codes and messages for transport/endian errors
+ Prevented possible PHP warning in IXR_Client_Gbx::getErrorCode() and getErrorMessage()
+
+ Release 2011-12-04 - Xymph:
+ Prevented possible PHP warning in IXR_Value::calculateType
+
+ Release 2013-02-18 - Xymph:
+ Removed unnecessary breaks after returns in IXR_Value::getXml() switch
+*/
+
+if (!defined('LF')) {
+ define('LF', "\n");
+}
+
+class IXR_Value {
+ public $data;
+ public $type;
+
+ function IXR_Value ($data, $type = false) {
+ $this->data = $data;
+ if (!$type) {
+ $type = $this->calculateType();
+ }
+ $this->type = $type;
+ if ($type == 'struct') {
+ // Turn all the values in the array into new IXR_Value objects
+ foreach ($this->data as $key => $value) {
+ $this->data[$key] = new IXR_Value($value);
+ }
+ }
+ if ($type == 'array') {
+ for ($i = 0, $j = count($this->data); $i < $j; $i++) {
+ $this->data[$i] = new IXR_Value($this->data[$i]);
+ }
+ }
+ }
+
+ function calculateType() {
+ if ($this->data === true || $this->data === false) {
+ return 'boolean';
+ }
+ if (is_integer($this->data)) {
+ return 'int';
+ }
+ if (is_double($this->data)) {
+ return 'double';
+ }
+ // Deal with IXR object types base64 and date
+ if (is_object($this->data) && ($this->data instanceof IXR_Date)) {
+ return 'date';
+ }
+ if (is_object($this->data) && ($this->data instanceof IXR_Base64)) {
+ return 'base64';
+ }
+ // If it is a normal PHP object convert it into a struct
+ if (is_object($this->data)) {
+ $this->data = get_object_vars($this->data);
+ return 'struct';
+ }
+ if (!is_array($this->data)) {
+ return 'string';
+ }
+ // We have an array - is it an array or a struct?
+ if ($this->isStruct($this->data)) {
+ return 'struct';
+ } else {
+ return 'array';
+ }
+ }
+
+ function getXml() {
+ // Return XML for this value
+ switch ($this->type) {
+ case 'boolean':
+ return '' . ($this->data ? '1' : '0') . '';
+ case 'int':
+ return '' . $this->data . '';
+ case 'double':
+ return '' . $this->data . '';
+ case 'string':
+ return '' . htmlspecialchars($this->data) . '';
+ case 'array':
+ $return = '' . LF;
+ foreach ($this->data as $item) {
+ $return .= ' ' . $item->getXml() . '' . LF;
+ }
+ $return .= '';
+ return $return;
+ case 'struct':
+ $return = '' . LF;
+ foreach ($this->data as $name => $value) {
+ $return .= ' ' . $name . '';
+ $return .= $value->getXml() . '' . LF;
+ }
+ $return .= '';
+ return $return;
+ case 'date':
+ case 'base64':
+ return $this->data->getXml();
+ }
+ return false;
+ }
+
+ function isStruct($array) {
+ // Nasty function to check if an array is a struct or not
+ $expected = 0;
+ foreach ($array as $key => $value) {
+ if ((string)$key != (string)$expected) {
+ return true;
+ }
+ $expected++;
+ }
+ return false;
+ }
+}
+
+
+class IXR_Message {
+ public $message;
+ public $messageType; // methodCall / methodResponse / fault
+ public $faultCode;
+ public $faultString;
+ public $methodName;
+ public $params;
+ // Current variable stacks
+ protected $_arraystructs = array(); // Stack to keep track of the current array/struct
+ protected $_arraystructstypes = array(); // Stack to keep track of whether things are structs or array
+ protected $_currentStructName = array(); // A stack as well
+ protected $_param;
+ protected $_value;
+ protected $_currentTag;
+ protected $_currentTagContents;
+ // The XML parser
+ protected $_parser;
+
+ function IXR_Message ($message) {
+ $this->message = $message;
+ }
+
+ function parse() {
+ // first remove the XML declaration
+ $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);
+ if (trim($this->message) == '') {
+ return false;
+ }
+ $this->_parser = xml_parser_create();
+ // Set XML parser to take the case of tags into account
+ xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
+ // Set XML parser callback functions
+ xml_set_object($this->_parser, $this);
+ xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
+ xml_set_character_data_handler($this->_parser, 'cdata');
+ if (!xml_parse($this->_parser, $this->message)) {
+ /* die(sprintf('GbxRemote XML error: %s at line %d',
+ xml_error_string(xml_get_error_code($this->_parser)),
+ xml_get_current_line_number($this->_parser))); */
+ return false;
+ }
+ xml_parser_free($this->_parser);
+ // Grab the error messages, if any
+ if ($this->messageType == 'fault') {
+ $this->faultCode = $this->params[0]['faultCode'];
+ $this->faultString = $this->params[0]['faultString'];
+ }
+ return true;
+ }
+
+ function tag_open($parser, $tag, $attr) {
+ $this->currentTag = $tag;
+ switch ($tag) {
+ case 'methodCall':
+ case 'methodResponse':
+ case 'fault':
+ $this->messageType = $tag;
+ break;
+ // Deal with stacks of arrays and structs
+ case 'data': // data is to all intents and purposes more interesting than array
+ $this->_arraystructstypes[] = 'array';
+ $this->_arraystructs[] = array();
+ break;
+ case 'struct':
+ $this->_arraystructstypes[] = 'struct';
+ $this->_arraystructs[] = array();
+ break;
+ }
+ }
+
+ function cdata($parser, $cdata) {
+ $this->_currentTagContents .= $cdata;
+ }
+
+ function tag_close($parser, $tag) {
+ $valueFlag = false;
+ switch ($tag) {
+ case 'int':
+ case 'i4':
+ $value = (int)trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ case 'double':
+ $value = (double)trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ case 'string':
+ $value = (string)trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ case 'dateTime.iso8601':
+ $value = new IXR_Date(trim($this->_currentTagContents));
+ // $value = $iso->getTimestamp();
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ case 'value':
+ // If no type is indicated, the type is string
+ if (trim($this->_currentTagContents) != '') {
+ $value = (string)$this->_currentTagContents;
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ }
+ break;
+ case 'boolean':
+ $value = (boolean)trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ case 'base64':
+ $value = base64_decode($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ $valueFlag = true;
+ break;
+ // Deal with stacks of arrays and structs
+ case 'data':
+ case 'struct':
+ $value = array_pop($this->_arraystructs);
+ array_pop($this->_arraystructstypes);
+ $valueFlag = true;
+ break;
+ case 'member':
+ array_pop($this->_currentStructName);
+ break;
+ case 'name':
+ $this->_currentStructName[] = trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ break;
+ case 'methodName':
+ $this->methodName = trim($this->_currentTagContents);
+ $this->_currentTagContents = '';
+ break;
+ }
+
+ if ($valueFlag) {
+ /*
+ if (!is_array($value) && !is_object($value)) {
+ $value = trim($value);
+ }
+ */
+ if (count($this->_arraystructs) > 0) {
+ // Add value to struct or array
+ if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
+ // Add to struct
+ $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
+ } else {
+ // Add to array
+ $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
+ }
+ } else {
+ // Just add as a paramater
+ $this->params[] = $value;
+ }
+ }
+ }
+}
+
+
+class IXR_Request {
+ public $method;
+ public $args;
+ public $xml;
+
+ function IXR_Request($method, $args) {
+ $this->method = $method;
+ $this->args = $args;
+ $this->xml = '' . $this->method . '';
+ foreach ($this->args as $arg) {
+ $this->xml .= '';
+ $v = new IXR_Value($arg);
+ $this->xml .= $v->getXml();
+ $this->xml .= '' . LF;
+ }
+ $this->xml .= '';
+ }
+
+ function getLength() {
+ return strlen($this->xml);
+ }
+
+ function getXml() {
+ return $this->xml;
+ }
+}
+
+
+class IXR_Error {
+ public $code;
+ public $message;
+
+ function IXR_Error($code, $message) {
+ $this->code = $code;
+ $this->message = $message;
+ }
+
+ function getXml() {
+ $xml = <<
+
+
+
+
+ faultCode
+ {$this->code}
+
+
+ faultString
+ {$this->message}
+
+
+
+
+
+EOD;
+ return $xml;
+ }
+}
+
+
+class IXR_Date {
+ public $year;
+ public $month;
+ public $day;
+ public $hour;
+ public $minute;
+ public $second;
+
+ function IXR_Date($time) {
+ // $time can be a PHP timestamp or an ISO one
+ if (is_numeric($time)) {
+ $this->parseTimestamp($time);
+ } else {
+ $this->parseIso($time);
+ }
+ }
+
+ function parseTimestamp($timestamp) {
+ $this->year = date('Y', $timestamp);
+ $this->month = date('Y', $timestamp);
+ $this->day = date('Y', $timestamp);
+ $this->hour = date('H', $timestamp);
+ $this->minute = date('i', $timestamp);
+ $this->second = date('s', $timestamp);
+ }
+
+ function parseIso($iso) {
+ $this->year = substr($iso, 0, 4);
+ $this->month = substr($iso, 4, 2);
+ $this->day = substr($iso, 6, 2);
+ $this->hour = substr($iso, 9, 2);
+ $this->minute = substr($iso, 12, 2);
+ $this->second = substr($iso, 15, 2);
+ }
+
+ function getIso() {
+ return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second;
+ }
+
+ function getXml() {
+ return ''.$this->getIso().'';
+ }
+
+ function getTimestamp() {
+ return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
+ }
+}
+
+
+class IXR_Base64 {
+ public $data;
+
+ function IXR_Base64($data) {
+ $this->data = $data;
+ }
+
+ function getXml() {
+ return ''.base64_encode($this->data).'';
+ }
+}
+
+
+//////////////////////////////////////////////////////////
+// Nadeo modifications //
+// (many thanks to slig for adding callback support) //
+//////////////////////////////////////////////////////////
+class IXR_Client_Gbx {
+ public $socket;
+ public $message = false;
+ public $cb_message = array();
+ public $reqhandle;
+ public $protocol = 0;
+ // Storage place for an error message
+ public $error = false;
+
+ function bigEndianTest() {
+ list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
+ if ($endiantest != 1) {
+ echo "Machine reports itself as BigEndian, float handling must be altered\r\n";
+ echo "Overwrite GbxRemote.inc.php with GbxRemote.bem.php\r\n";
+ die('App Terminated');
+ return false;
+ }
+ return true;
+ } // bigEndianTest
+
+ function IXR_Client_Gbx() {
+ $this->socket = false;
+ $this->reqhandle = 0x80000000;
+ }
+
+ function InitWithIp($ip, $port, $timeout = null) {
+
+ if (!$this->bigEndianTest()) {
+ $this->error = new IXR_Error(-31999, 'endian error - script doesn\'t match machine type');
+ return false;
+ }
+
+ // open connection, with timeout if specified
+ if (!isset($timeout)) {
+ $this->socket = @fsockopen($ip, $port, $errno, $errstr);
+ } else {
+ $init_time = microtime(true);
+ $init_timeout = 5; // retry every 5s
+ while (true) {
+ $this->socket = @fsockopen($ip, $port, $errno, $errstr, $init_timeout);
+ if ($this->socket || (microtime(true) - $init_time >= $timeout))
+ break;
+ }
+ }
+ if (!$this->socket) {
+ $this->error = new IXR_Error(-32300, "transport error - could not open socket (error: $errno, $errstr)");
+ return false;
+ }
+ // handshake
+ $array_result = unpack('Vsize', fread($this->socket, 4));
+ $size = $array_result['size'];
+ if ($size > 64) {
+ $this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol header');
+ return false;
+ }
+ $handshake = fread($this->socket, $size);
+ if ($handshake == 'GBXRemote 1') {
+ $this->protocol = 1;
+ } else if ($handshake == 'GBXRemote 2') {
+ $this->protocol = 2;
+ } else {
+ $this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol version');
+ return false;
+ }
+ return true;
+ }
+
+ function Init($port) {
+ return $this->InitWithIp('localhost', $port);
+ }
+
+ function Terminate() {
+ if ($this->socket) {
+ fclose($this->socket);
+ $this->socket = false;
+ }
+ }
+
+ protected function sendRequest(IXR_Request $request) {
+ $xml = $request->getXml();
+
+ @stream_set_timeout($this->socket, 20); // timeout 20s (to write the request)
+ // send request
+ $this->reqhandle++;
+ if ($this->protocol == 1) {
+ $bytes = pack('Va*', strlen($xml), $xml);
+ } else {
+ $bytes = pack('VVa*', strlen($xml), $this->reqhandle, $xml);
+ }
+
+ $bytes_to_write = strlen($bytes);
+ while ($bytes_to_write > 0) {
+ $r = @fwrite($this->socket, $bytes);
+ if ($r === false || $r == 0) {
+ // connection interrupted
+ return false; // or die?
+ }
+
+ $bytes_to_write -= $r;
+ if ($bytes_to_write == 0)
+ break;
+
+ $bytes = substr($bytes, $r);
+ }
+
+ return true;
+ }
+
+ protected function getResult() {
+ $contents = '';
+ $contents_length = 0;
+ do {
+ $size = 0;
+ $recvhandle = 0;
+ @stream_set_timeout($this->socket, 20); // timeout 20s (to read the reply header)
+ // Get result
+ if ($this->protocol == 1) {
+ $contents = fread($this->socket, 4);
+ if (strlen($contents) == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - cannot read size');
+ return false;
+ }
+ $array_result = unpack('Vsize', $contents);
+ $size = $array_result['size'];
+ $recvhandle = $this->reqhandle;
+ } else {
+ $contents = fread($this->socket, 8);
+ if (strlen($contents) == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
+ return false;
+ }
+ $array_result = unpack('Vsize/Vhandle', $contents);
+ $size = $array_result['size'];
+ $recvhandle = $array_result['handle'];
+ // -- amd64 support --
+ $bits = sprintf('%b', $recvhandle);
+ if (strlen($bits) == 64) {
+ $recvhandle = bindec(substr($bits, 32));
+ }
+ }
+
+ if ($recvhandle == 0 || $size == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
+ return false;
+ }
+ if ($size > 4096*1024) {
+ $this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
+ return false;
+ }
+
+ $contents = '';
+ $contents_length = 0;
+ @stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (for successive reads until end)
+ while ($contents_length < $size) {
+ $contents .= fread($this->socket, $size-$contents_length);
+ $contents_length = strlen($contents);
+ }
+
+ if (($recvhandle & 0x80000000) == 0) {
+ // this is a callback, not our answer! handle= $recvhandle, xml-rpc= $contents
+ // just add it to the message list for the user to read
+ $new_cb_message = new IXR_Message($contents);
+ if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
+ array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
+ }
+ }
+ } while ((int)$recvhandle != (int)$this->reqhandle);
+
+ $this->message = new IXR_Message($contents);
+ if (!$this->message->parse()) {
+ // XML error
+ $this->error = new IXR_Error(-32700, 'parse error. not well formed');
+ return false;
+ }
+ // Is the message a fault?
+ if ($this->message->messageType == 'fault') {
+ $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
+ return false;
+ }
+ // Message must be OK
+ return true;
+ }
+
+
+ function query() {
+ $args = func_get_args();
+ $method = array_shift($args);
+
+ if (!$this->socket || $this->protocol == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - client not initialized');
+ return false;
+ }
+
+ $request = new IXR_Request($method, $args);
+
+ // Check if request is larger than 512 Kbytes
+ if (($size = $request->getLength()) > 512*1024-8) {
+ $this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
+ return false;
+ }
+
+ // Send request
+ $ok = $this->sendRequest($request);
+ if (!$ok) {
+ $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
+ return false;
+ }
+
+ // Get result
+ return $this->getResult();
+ }
+
+ // Non-blocking query method: doesn't read the response
+ function queryIgnoreResult() {
+ $args = func_get_args();
+ $method = array_shift($args);
+
+ if (!$this->socket || $this->protocol == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - client not initialized');
+ return false;
+ }
+
+ $request = new IXR_Request($method, $args);
+
+ // Check if the request is greater than 512 Kbytes to avoid errors
+ // If the method is system.multicall, make two calls (possibly recursively)
+ if (($size = $request->getLength()) > 512*1024-8) {
+ if ($method = 'system.multicall' && isset($args[0])) {
+ $count = count($args[0]);
+ // If count is 1, query cannot be reduced
+ if ($count < 2) {
+ $this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
+ return false;
+ }
+ $length = floor($count/2);
+ $args1 = array_slice($args[0], 0, $length);
+ $args2 = array_slice($args[0], $length, ($count-$length));
+
+ $res1 = $this->queryIgnoreResult('system.multicall', $args1);
+ $res2 = $this->queryIgnoreResult('system.multicall', $args2);
+ return ($res1 && $res2);
+ }
+ // If the method is not a multicall, just stop
+ else {
+ $this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
+ return false;
+ }
+ }
+
+ // Send request
+ $ok = $this->sendRequest($request);
+ if (!$ok) {
+ $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
+ return false;
+ }
+ return true;
+ }
+
+ function readCB($timeout = 2000) { // timeout 2 ms
+ if (!$this->socket || $this->protocol == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - client not initialized');
+ return false;
+ }
+ if ($this->protocol == 1)
+ return false;
+
+ $something_received = count($this->cb_message)>0;
+ $contents = '';
+ $contents_length = 0;
+
+ @stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (to read available data)
+ // (assignment in arguments is forbidden since php 5.1.1)
+ $read = array($this->socket);
+ $write = NULL;
+ $except = NULL;
+ $nb = @stream_select($read, $write, $except, 0, $timeout);
+ // workaround for stream_select bug with amd64
+ if ($nb !== false)
+ $nb = count($read);
+
+ while ($nb !== false && $nb > 0) {
+ $timeout = 0; // we don't want to wait for the full time again, just flush the available data
+
+ $size = 0;
+ $recvhandle = 0;
+ // Get result
+ $contents = fread($this->socket, 8);
+ if (strlen($contents) == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
+ return false;
+ }
+ $array_result = unpack('Vsize/Vhandle', $contents);
+ $size = $array_result['size'];
+ $recvhandle = $array_result['handle'];
+
+ if ($recvhandle == 0 || $size == 0) {
+ $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
+ return false;
+ }
+ if ($size > 4096*1024) {
+ $this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
+ return false;
+ }
+
+ $contents = '';
+ $contents_length = 0;
+ while ($contents_length < $size) {
+ $contents .= fread($this->socket, $size-$contents_length);
+ $contents_length = strlen($contents);
+ }
+
+ if (($recvhandle & 0x80000000) == 0) {
+ // this is a callback. handle= $recvhandle, xml-rpc= $contents
+ //echo 'CALLBACK('.$contents_length.')[ '.$contents.' ]' . LF;
+ $new_cb_message = new IXR_Message($contents);
+ if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
+ array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
+ }
+ $something_received = true;
+ }
+
+ // (assignment in arguments is forbidden since php 5.1.1)
+ $read = array($this->socket);
+ $write = NULL;
+ $except = NULL;
+ $nb = @stream_select($read, $write, $except, 0, $timeout);
+ // workaround for stream_select bug with amd64
+ if ($nb !== false)
+ $nb = count($read);
+ }
+ return $something_received;
+ }
+
+ function getResponse() {
+ // methodResponses can only have one param - return that
+ return $this->message->params[0];
+ }
+
+ function getCBResponses() {
+ // (look at the end of basic.php for an example)
+ $messages = $this->cb_message;
+ $this->cb_message = array();
+ return $messages;
+ }
+
+ function isError() {
+ return is_object($this->error);
+ }
+
+ function resetError() {
+ $this->error = false;
+ }
+
+ function getErrorCode() {
+ if ($this->isError())
+ return $this->error->code;
+ else
+ return 0;
+ }
+
+ function getErrorMessage() {
+ if ($this->isError())
+ return $this->error->message;
+ else
+ return '';
+ }
+}
+
+
+class IXR_ClientMulticall_Gbx extends IXR_Client_Gbx {
+ public $calls = array();
+
+ function addCall($methodName, $args) {
+ $struct = array('methodName' => $methodName, 'params' => $args);
+ $this->calls[] = $struct;
+
+ return (count($this->calls) - 1);
+ }
+
+ function multiquery($ignoreResult = false) {
+ // Prepare multicall, then call the parent::query() (or queryIgnoreResult) method
+ if ($ignoreResult) {
+ $result = parent::queryIgnoreResult('system.multicall', $this->calls);
+ } else {
+ $result = parent::query('system.multicall', $this->calls);
+ }
+ $this->calls = array(); // reset for next calls
+ return $result;
+ }
+}
+?>
diff --git a/xaseco/includes/GbxRemote.response.php b/xaseco/includes/GbxRemote.response.php
new file mode 100644
index 0000000..b5dfdbd
--- /dev/null
+++ b/xaseco/includes/GbxRemote.response.php
@@ -0,0 +1,218 @@
+ xml-rpc string)
+// -----------------------------------------------------------------------
+// $method is the method name.
+// $params should be an array containing the method parameters.
+// For system.multicall, there should be one parameter, which is an array
+// containing the methods structs {'methodName':string, 'params':array}
+// -----------------------------------------------------------------------
+
+function xmlrpc_request($method, $params = null) {
+
+ // in case the first level array was forgotten in a multicall then add it...
+ if ($method == 'system.multicall' && isset($params[0]['methodName']))
+ $params = array($params);
+
+ $xml = ''
+ . "\n\n$method\n\n";
+
+ if (is_array($params)) {
+ foreach ($params as $param) {
+ $xml .= "\n";
+ $v = new IXR_Value($param);
+ $xml .= $v->getXml();
+ $xml .= "\n\n";
+ }
+ }
+ $xml .= "\n";
+ return $xml;
+}
+
+
+// -----------------------------------------------------------------------
+// Build a methodCall response in xml-rpc format from arrays args
+// (array_args ==> xml-rpc string)
+// -----------------------------------------------------------------------
+// $args should be an array containing the response
+// -----------------------------------------------------------------------
+
+function xmlrpc_response($args = null) {
+
+ $xml = ''
+ . "\n\n\n\n";
+
+ if ($args !== null) {
+ $xml .= '';
+ $v = new IXR_Value($args);
+ $xml .= $v->getXml();
+ $xml .= "\n";
+ }
+ $xml .= "\n\n";
+ return $xml;
+}
+
+
+// -----------------------------------------------------------------------
+// Build an error response in xml-rpc format
+// (error code + message ==> xml-rpc string)
+// -----------------------------------------------------------------------
+// $code is the error code
+// $message is the error string
+// -----------------------------------------------------------------------
+
+function xmlrpc_error($code, $message) {
+
+ $xml = ''
+ . "\n\n\n"
+ . "\n"
+ . "faultCode$code\n\n"
+ . "faultString$message\n"
+ . "\n"
+ . "\n";
+ return $xml;
+}
+
+
+// -----------------------------------------------------------------------
+// Build a method struct (array) usable for a method call in a multicall
+// (method name + params array ==> method struct array
+// -----------------------------------------------------------------------
+// $name is the method name
+// $params is the params array
+// -----------------------------------------------------------------------
+
+function rpc_method($name, $params = null) {
+
+ if (!is_array($params))
+ $params = array();
+ return array('methodName' => $name, 'params' => $params);
+}
+
+
+// -----------------------------------------------------------------------
+// Build a response struct (array) usable as a reply for a method in a multicall
+// (method name + params array ==> method struct array
+// -----------------------------------------------------------------------
+// $params is the methode response array
+// -----------------------------------------------------------------------
+
+function rpc_response($response = null) {
+
+ if (!is_array($response))
+ $response = array();
+ return array($response);
+}
+
+
+// -----------------------------------------------------------------------
+// Build an error struct (array) usable as an error for a method in a multicall
+// (error code + message ==> error struct array)
+// -----------------------------------------------------------------------
+// $code is the error code
+// $message is the error string
+// -----------------------------------------------------------------------
+
+function rpc_error($code, $message) {
+
+ return array('faultCode' => $code, 'faultString' => $message);
+}
+
+
+// -----------------------------------------------------------------------
+// Build a data array from a text xml-rpc
+// (xml-rpc string ==> array)
+// -----------------------------------------------------------------------
+// $xml is the xml-rpc text to decode
+// If there is an error then null is returned, you can then look infos
+// in global $_xmlrpc_decode_obj
+// -----------------------------------------------------------------------
+
+function xml_decode_rpc($xml) {
+ global $_xmlrpc_decode_obj;
+
+ $_xmlrpc_decode_obj = new IXR_Message($xml);
+ if (!$_xmlrpc_decode_obj->parse() || !isset($_xmlrpc_decode_obj->params[0]))
+ return null;
+ return $_xmlrpc_decode_obj->params[0];
+}
+
+
+// -----------------------------------------------------------------------
+// use this class constructor to build a methodCall request
+// -----------------------------------------------------------------------
+
+class IXR_RequestStd {
+ var $method;
+ var $params;
+ var $xml;
+
+ // see xmlrpc_request
+ function IXR_RequestStd($method, $params = null) {
+
+ $this->method = $method;
+ // in case the first level array was forgotten in a multicall then add it...
+ if ($method == 'system.multicall' && isset($params[0]['methodName']))
+ $this->params = array($params);
+ else
+ $this->params = $params;
+
+ $this->xml = xmlrpc_request($this->method, $this->params);
+ }
+
+ function getLength() {
+
+ return strlen($this->xml);
+ }
+
+ function getXml() {
+
+ return $this->xml;
+ }
+}
+
+
+// -----------------------------------------------------------------------
+// use this class constructor to build a methodCall response
+// -----------------------------------------------------------------------
+class IXR_ResponseStd {
+ var $args;
+ var $xml;
+
+ // see xmlrpc_response
+ function IXR_ResponseStd($args) {
+
+ $this->args = $args;
+ $this->xml = xmlrpc_response($this->args);
+ }
+
+ function getLength() {
+
+ return strlen($this->xml);
+ }
+
+ function getXml() {
+
+ return $this->xml;
+ }
+}
+?>
diff --git a/xaseco/includes/basic.inc.php b/xaseco/includes/basic.inc.php
new file mode 100644
index 0000000..2321002
--- /dev/null
+++ b/xaseco/includes/basic.inc.php
@@ -0,0 +1,830 @@
+= $size) {
+ return false;
+ }
+
+ // shift values down
+ for ($i = $size-1; $i >= $pos; $i--) {
+ $array[$i+1] = $array[$i];
+ }
+
+ // now put in the new element
+ $array[$pos] = $value;
+ return true;
+} // insertArrayElement
+
+/**
+ * Removes an element from a specific position in an array.
+ * Decreases original size by one element.
+ */
+function removeArrayElement(&$array, $pos) {
+
+ // get current size
+ $size = count($array);
+
+ // if position is in array range
+ if ($pos < 0 && $pos >= $size) {
+ return false;
+ }
+
+ // remove specified element
+ unset($array[$pos]);
+ // shift values up
+ $array = array_values($array);
+ return true;
+} // removeArrayElement
+
+/**
+ * Moves an element from one position to the other.
+ * All items between are shifted down or up as needed.
+ */
+function moveArrayElement(&$array, $from, $to) {
+
+ // get current size
+ $size = count($array);
+
+ // destination and source have to be among the array borders!
+ if ($from < 0 || $from >= $size || $to < 0 || $to >= $size) {
+ return false;
+ }
+
+ // backup the element we have to move
+ $moving_element = $array[$from];
+
+ if ($from > $to) {
+ // shift values between downwards
+ for ($i = $from-1; $i >= $to; $i--) {
+ $array[$i+1] = $array[$i];
+ }
+ } else { // $from < $to
+ // shift values between upwards
+ for ($i = $from; $i <= $to; $i++) {
+ $array[$i] = $array[$i+1];
+ }
+ }
+
+ // now put in the element which was to move
+ $array[$to] = $moving_element;
+ return true;
+} // moveArrayElement
+
+/**
+ * Formats a string from the format sssshh0
+ * into the format mmm:ss.hh (or mmm:ss if $hsec is false)
+ */
+function formatTime($MwTime, $hsec = true) {
+
+ if ($MwTime == -1) {
+ return '???';
+ } else {
+ $minutes = floor($MwTime/(1000*60));
+ $seconds = floor(($MwTime - $minutes*60*1000)/1000);
+ $hseconds = substr($MwTime, strlen($MwTime)-3, 2);
+ if ($hsec) {
+ $tm = sprintf('%02d:%02d.%02d', $minutes, $seconds, $hseconds);
+ } else {
+ $tm = sprintf('%02d:%02d', $minutes, $seconds);
+ }
+ }
+ if ($tm[0] == '0') {
+ $tm = substr($tm, 1);
+ }
+ return $tm;
+} // formatTime
+
+/**
+ * Formats a string from the format sssshh0
+ * into the format hh:mm:ss.hh (or hh:mm:ss if $hsec is false)
+ */
+function formatTimeH($MwTime, $hsec = true) {
+
+ if ($MwTime == -1) {
+ return '???';
+ } else {
+ $hseconds = substr($MwTime, strlen($MwTime)-3, 2);
+ $MwTime = substr($MwTime, 0, strlen($MwTime)-3);
+ $hours = floor($MwTime / 3600);
+ $MwTime = $MwTime - ($hours * 3600);
+ $minutes = floor($MwTime / 60);
+ $MwTime = $MwTime - ($minutes * 60);
+ $seconds = floor($MwTime);
+ if ($hsec) {
+ return sprintf('%02d:%02d:%02d.%02d', $hours, $minutes, $seconds, $hseconds);
+ } else {
+ return sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
+ }
+ }
+} // formatTimeH
+
+/**
+ * Formats a text.
+ * Replaces parameters in the text which are marked with {n}
+ */
+function formatText($text) {
+
+ // get all function's parameters
+ $args = func_get_args();
+
+ // first parameter is the text to format
+ $text = array_shift($args);
+
+ // further parameters will be replaced in the text
+ $i = 1;
+ foreach ($args as $param)
+ $text = str_replace('{' . $i++ . '}', $param, $text);
+
+ // and return the modified text
+ return $text;
+} // formatText
+
+/**
+ * Make String for SQL use that single quoted & got special chars replaced.
+ */
+function quotedString($input) {
+
+ return "'" . mysql_real_escape_string($input) . "'";
+} // quotedString
+
+/**
+ * Check login string for LAN postfix (pre/post v2.11.21).
+ */
+function isLANLogin($login) {
+
+ $n="(25[0-5]|2[0-4]\d|[01]?\d\d|\d)";
+ return (preg_match("/(\/{$n}\\.{$n}\\.{$n}\\.{$n}:\d+)$/", $login) ||
+ preg_match("/(_{$n}\\.{$n}\\.{$n}\\.{$n}_\d+)$/", $login));
+} // isLANLogin
+
+/**
+ * Summary: Strips all display formatting from an input string, suitable for display
+ * within the game ('$$' escape pairs are preserved) and for logging
+ * Params : $input - The input string to strip formatting from
+ * $for_tm - Optional flag to double up '$' into '$$' (default, for TM) or not (for logs, etc)
+ * Returns: The content portions of $input without formatting
+ * Authors: Bilge/Assembler Maniac/Xymph/Slig
+ *
+ * "$af0Brat$s$fffwurst" will become "Bratwurst".
+ * 2007-08-27 Xymph - replaced with Bilge/AM's code (w/o the H&L tags bit)
+ * http://www.tm-forum.com/viewtopic.php?p=55867#p55867
+ * 2008-04-24 Xymph - extended to handle the H/L/P tags for TMF
+ * http://www.tm-forum.com/viewtopic.php?p=112856#p112856
+ * 2009-05-16 Slig - extended to emit non-TM variant & handle incomplete colors
+ * http://www.tm-forum.com/viewtopic.php?p=153368#p153368
+ * 2010-10-05 Slig - updated to handle incomplete colors & tags better
+ * http://www.tm-forum.com/viewtopic.php?p=183410#p183410
+ * 2010-10-09 Xymph - updated to handle $[ and $] properly
+ * http://www.tm-forum.com/viewtopic.php?p=183410#p183410
+ */
+function stripColors($input, $for_tm = true) {
+
+ return
+ //Replace all occurrences of a null character back with a pair of dollar
+ //signs for displaying in TM, or a single dollar for log messages etc.
+ str_replace("\0", ($for_tm ? '$$' : '$'),
+ //Replace links (introduced in TMU)
+ preg_replace(
+ '/
+ #Strip TMF H, L & P links by stripping everything between each square
+ #bracket pair until another $H, $L or $P sequence (or EoS) is found;
+ #this allows a $H to close a $L and vice versa, as does the game
+ \\$[hlp](.*?)(?:\\[.*?\\](.*?))*(?:\\$[hlp]|$)
+ /ixu',
+ //Keep the first and third capturing groups if present
+ '$1$2',
+ //Replace various patterns beginning with an unescaped dollar
+ preg_replace(
+ '/
+ #Match a single dollar sign and any of the following:
+ \\$
+ (?:
+ #Strip color codes by matching any hexadecimal character and
+ #any other two characters following it (except $)
+ [0-9a-f][^$][^$]
+ #Strip any incomplete color codes by matching any hexadecimal
+ #character followed by another character (except $)
+ |[0-9a-f][^$]
+ #Strip any single style code (including an invisible UTF8 char)
+ #that is not an H, L or P link or a bracket ($[ and $])
+ |[^][hlp]
+ #Strip the dollar sign if it is followed by [ or ], but do not
+ #strip the brackets themselves
+ |(?=[][])
+ #Strip the dollar sign if it is at the end of the string
+ |$
+ )
+ #Ignore alphabet case, ignore whitespace in pattern & use UTF-8 mode
+ /ixu',
+ //Replace any matches with nothing (i.e. strip matches)
+ '',
+ //Replace all occurrences of dollar sign pairs with a null character
+ str_replace('$$', "\0", $input)
+ )
+ )
+ )
+ ;
+} // stripColors
+
+/**
+ * Strips only size tags from TM strings.
+ * "$w$af0Brat$n$fffwurst" will become "$af0Brat$fffwurst".
+ * 2009-03-27 Xymph - derived from stripColors above
+ * http://www.tm-forum.com/viewtopic.php?f=127&t=20602
+ * 2009-05-16 Slig - extended to emit non-TM variant
+ * http://www.tm-forum.com/viewtopic.php?p=153368#p153368
+ */
+function stripSizes($input, $for_tm = true) {
+
+ return
+ //Replace all occurrences of a null character back with a pair of dollar
+ //signs for displaying in TM, or a single dollar for log messages etc.
+ str_replace("\0", ($for_tm ? '$$' : '$'),
+ //Replace various patterns beginning with an unescaped dollar
+ preg_replace(
+ '/
+ #Match a single dollar sign and any of the following:
+ \\$
+ (?:
+ #Strip any size code
+ [nwo]
+ #Strip the dollar sign if it is at the end of the string
+ |$
+ )
+ #Ignore alphabet case, ignore whitespace in pattern & use UTF-8 mode
+ /ixu',
+ //Replace any matches with nothing (i.e. strip matches)
+ '',
+ //Replace all occurrences of dollar sign pairs with a null character
+ str_replace('$$', "\0", $input)
+ )
+ )
+ ;
+} // stripSizes
+
+/**
+ * Strips only newlines from TM strings.
+ */
+function stripNewlines($input) {
+
+ return str_replace(array("\n\n", "\r", "\n"),
+ array(' ', '', ''), $input);
+} // stripNewlines
+
+
+/**
+ * Univeral show help for user, admin & Jfreu commands.
+ * Created by Xymph
+ *
+ * $width is the width of the first column in the ManiaLink window on TMF
+ */
+function showHelp($player, $chat_commands, $head,
+ $showadmin = false, $dispall = false, $width = 0.3) {
+ global $aseco;
+
+ // display full help for TMN
+ if ($aseco->server->getGame() == 'TMN' && $dispall) {
+ $head = "Currently supported $head commands:" . LF;
+
+ if (!empty($chat_commands)) {
+ // define admin or non-admin padding string
+ $pad = ($showadmin ? '$f00... ' : '$f00/');
+ $help = '';
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+ // create list of chat commands
+ foreach ($chat_commands as $cc) {
+ // collect either admin or non-admin commands
+ if ($cc->isadmin == $showadmin) {
+ $help .= $pad . $cc->name . ' $000' . $cc->help . LF;
+ if (++$lines > 14) {
+ $player->msgs[] = $head . $help;
+ $lines = 0;
+ $help = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($help != '')
+ $player->msgs[] = $head . $help;
+
+ // display popup message
+ if (count($player->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $player->login, $player->msgs[1], 'OK', '', 0);
+ } else { // > 2
+ $aseco->client->query('SendDisplayServerMessageToLogin', $player->login, $player->msgs[1], 'Close', 'Next', 0);
+ }
+ }
+
+ // display full help for TMF
+ } elseif ($aseco->server->getGame() == 'TMF' && $dispall) {
+ $head = "Currently supported $head commands:";
+
+ if (!empty($chat_commands)) {
+ // define admin or non-admin padding string
+ $pad = ($showadmin ? '$f00... ' : '$f00/');
+ $help = array();
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = array(1, $head, array(1.3, $width, 1.3 - $width), array('Icons64x64_1', 'TrackInfo', -0.01));
+ // create list of chat commands
+ foreach ($chat_commands as $cc) {
+ // collect either admin or non-admin commands
+ if ($cc->isadmin == $showadmin) {
+ $help[] = array($pad . $cc->name, $cc->help);
+ if (++$lines > 14) {
+ $player->msgs[] = $help;
+ $lines = 0;
+ $help = array();
+ }
+ }
+ }
+ // add if last batch exists
+ if (!empty($help))
+ $player->msgs[] = $help;
+
+ // display ManiaLink message
+ display_manialink_multi($player);
+ }
+
+ // show help for TMS or TMO, and plain help for TMF/TMN
+ } else {
+ $head = "Currently supported $head commands:" . LF;
+ $help = $aseco->formatColors('{#interact}' . $head);
+ foreach ($chat_commands as $cc) {
+ // collect either admin or non-admin commands
+ if ($cc->isadmin == $showadmin) {
+ $help .= $cc->name . ', ';
+ }
+ }
+ // show chat message
+ $help = substr($help, 0, strlen($help) - 2); // strip trailing ", "
+ $aseco->client->query('ChatSendToLogin', $help, $player->login);
+ }
+} // showHelp
+
+/**
+ * Map country names to 3-letter Nation abbreviations
+ * Created by Xymph
+ * Based on http://en.wikipedia.org/wiki/List_of_IOC_country_codes
+ * See also http://en.wikipedia.org/wiki/Comparison_of_IOC,_FIFA,_and_ISO_3166_country_codes
+ */
+function mapCountry($country) {
+
+ $nations = array(
+ 'Afghanistan' => 'AFG',
+ 'Albania' => 'ALB',
+ 'Algeria' => 'ALG',
+ 'Andorra' => 'AND',
+ 'Angola' => 'ANG',
+ 'Argentina' => 'ARG',
+ 'Armenia' => 'ARM',
+ 'Aruba' => 'ARU',
+ 'Australia' => 'AUS',
+ 'Austria' => 'AUT',
+ 'Azerbaijan' => 'AZE',
+ 'Bahamas' => 'BAH',
+ 'Bahrain' => 'BRN',
+ 'Bangladesh' => 'BAN',
+ 'Barbados' => 'BAR',
+ 'Belarus' => 'BLR',
+ 'Belgium' => 'BEL',
+ 'Belize' => 'BIZ',
+ 'Benin' => 'BEN',
+ 'Bermuda' => 'BER',
+ 'Bhutan' => 'BHU',
+ 'Bolivia' => 'BOL',
+ 'Bosnia&Herzegovina' => 'BIH',
+ 'Botswana' => 'BOT',
+ 'Brazil' => 'BRA',
+ 'Brunei' => 'BRU',
+ 'Bulgaria' => 'BUL',
+ 'Burkina Faso' => 'BUR',
+ 'Burundi' => 'BDI',
+ 'Cambodia' => 'CAM',
+ 'Cameroon' => 'CAR', // actually CMR
+ 'Canada' => 'CAN',
+ 'Cape Verde' => 'CPV',
+ 'Central African Republic' => 'CAF',
+ 'Chad' => 'CHA',
+ 'Chile' => 'CHI',
+ 'China' => 'CHN',
+ 'Chinese Taipei' => 'TPE',
+ 'Colombia' => 'COL',
+ 'Congo' => 'CGO',
+ 'Costa Rica' => 'CRC',
+ 'Croatia' => 'CRO',
+ 'Cuba' => 'CUB',
+ 'Cyprus' => 'CYP',
+ 'Czech Republic' => 'CZE',
+ 'Czech republic' => 'CZE',
+ 'DR Congo' => 'COD',
+ 'Denmark' => 'DEN',
+ 'Djibouti' => 'DJI',
+ 'Dominica' => 'DMA',
+ 'Dominican Republic' => 'DOM',
+ 'Ecuador' => 'ECU',
+ 'Egypt' => 'EGY',
+ 'El Salvador' => 'ESA',
+ 'Eritrea' => 'ERI',
+ 'Estonia' => 'EST',
+ 'Ethiopia' => 'ETH',
+ 'Fiji' => 'FIJ',
+ 'Finland' => 'FIN',
+ 'France' => 'FRA',
+ 'Gabon' => 'GAB',
+ 'Gambia' => 'GAM',
+ 'Georgia' => 'GEO',
+ 'Germany' => 'GER',
+ 'Ghana' => 'GHA',
+ 'Greece' => 'GRE',
+ 'Grenada' => 'GRN',
+ 'Guam' => 'GUM',
+ 'Guatemala' => 'GUA',
+ 'Guinea' => 'GUI',
+ 'Guinea-Bissau' => 'GBS',
+ 'Guyana' => 'GUY',
+ 'Haiti' => 'HAI',
+ 'Honduras' => 'HON',
+ 'Hong Kong' => 'HKG',
+ 'Hungary' => 'HUN',
+ 'Iceland' => 'ISL',
+ 'India' => 'IND',
+ 'Indonesia' => 'INA',
+ 'Iran' => 'IRI',
+ 'Iraq' => 'IRQ',
+ 'Ireland' => 'IRL',
+ 'Israel' => 'ISR',
+ 'Italy' => 'ITA',
+ 'Ivory Coast' => 'CIV',
+ 'Jamaica' => 'JAM',
+ 'Japan' => 'JPN',
+ 'Jordan' => 'JOR',
+ 'Kazakhstan' => 'KAZ',
+ 'Kenya' => 'KEN',
+ 'Kiribati' => 'KIR',
+ 'Korea' => 'KOR',
+ 'Kuwait' => 'KUW',
+ 'Kyrgyzstan' => 'KGZ',
+ 'Laos' => 'LAO',
+ 'Latvia' => 'LAT',
+ 'Lebanon' => 'LIB',
+ 'Lesotho' => 'LES',
+ 'Liberia' => 'LBR',
+ 'Libya' => 'LBA',
+ 'Liechtenstein' => 'LIE',
+ 'Lithuania' => 'LTU',
+ 'Luxembourg' => 'LUX',
+ 'Macedonia' => 'MKD',
+ 'Malawi' => 'MAW',
+ 'Malaysia' => 'MAS',
+ 'Mali' => 'MLI',
+ 'Malta' => 'MLT',
+ 'Mauritania' => 'MTN',
+ 'Mauritius' => 'MRI',
+ 'Mexico' => 'MEX',
+ 'Moldova' => 'MDA',
+ 'Monaco' => 'MON',
+ 'Mongolia' => 'MGL',
+ 'Montenegro' => 'MNE',
+ 'Morocco' => 'MAR',
+ 'Mozambique' => 'MOZ',
+ 'Myanmar' => 'MYA',
+ 'Namibia' => 'NAM',
+ 'Nauru' => 'NRU',
+ 'Nepal' => 'NEP',
+ 'Netherlands' => 'NED',
+ 'New Zealand' => 'NZL',
+ 'Nicaragua' => 'NCA',
+ 'Niger' => 'NIG',
+ 'Nigeria' => 'NGR',
+ 'Norway' => 'NOR',
+ 'Oman' => 'OMA',
+ 'Other Countries' => 'OTH',
+ 'Pakistan' => 'PAK',
+ 'Palau' => 'PLW',
+ 'Palestine' => 'PLE',
+ 'Panama' => 'PAN',
+ 'Paraguay' => 'PAR',
+ 'Peru' => 'PER',
+ 'Philippines' => 'PHI',
+ 'Poland' => 'POL',
+ 'Portugal' => 'POR',
+ 'Puerto Rico' => 'PUR',
+ 'Qatar' => 'QAT',
+ 'Romania' => 'ROM', // actually ROU
+ 'Russia' => 'RUS',
+ 'Rwanda' => 'RWA',
+ 'Samoa' => 'SAM',
+ 'San Marino' => 'SMR',
+ 'Saudi Arabia' => 'KSA',
+ 'Senegal' => 'SEN',
+ 'Serbia' => 'SCG', // actually SRB
+ 'Sierra Leone' => 'SLE',
+ 'Singapore' => 'SIN',
+ 'Slovakia' => 'SVK',
+ 'Slovenia' => 'SLO',
+ 'Somalia' => 'SOM',
+ 'South Africa' => 'RSA',
+ 'Spain' => 'ESP',
+ 'Sri Lanka' => 'SRI',
+ 'Sudan' => 'SUD',
+ 'Suriname' => 'SUR',
+ 'Swaziland' => 'SWZ',
+ 'Sweden' => 'SWE',
+ 'Switzerland' => 'SUI',
+ 'Syria' => 'SYR',
+ 'Taiwan' => 'TWN',
+ 'Tajikistan' => 'TJK',
+ 'Tanzania' => 'TAN',
+ 'Thailand' => 'THA',
+ 'Togo' => 'TOG',
+ 'Tonga' => 'TGA',
+ 'Trinidad and Tobago' => 'TRI',
+ 'Tunisia' => 'TUN',
+ 'Turkey' => 'TUR',
+ 'Turkmenistan' => 'TKM',
+ 'Tuvalu' => 'TUV',
+ 'Uganda' => 'UGA',
+ 'Ukraine' => 'UKR',
+ 'United Arab Emirates' => 'UAE',
+ 'United Kingdom' => 'GBR',
+ 'United States of America' => 'USA',
+ 'Uruguay' => 'URU',
+ 'Uzbekistan' => 'UZB',
+ 'Vanuatu' => 'VAN',
+ 'Venezuela' => 'VEN',
+ 'Vietnam' => 'VIE',
+ 'Yemen' => 'YEM',
+ 'Zambia' => 'ZAM',
+ 'Zimbabwe' => 'ZIM',
+ );
+
+ if (array_key_exists($country, $nations)) {
+ $nation = $nations[$country];
+ } else {
+ $nation = 'OTH';
+ if ($country != '')
+ trigger_error('Could not map country: ' . $country, E_USER_WARNING);
+ }
+ return $nation;
+} // mapCountry
+
+/**
+ * Find TMX data for the given track
+ * Created by Xymph
+ */
+require_once('includes/tmxinfofetcher.inc.php'); // provides access to TMX info
+function findTMXdata($uid, $envir, $exever, $records = false) {
+
+ // determine likely search order
+ if ($envir == 'Stadium') {
+ // check for old TMN
+ if (strcmp($exever, '0.1.8.0') < 0)
+ $sections = array('TMN', 'TMNF', 'TMU');
+ // check for new TMF
+ elseif (strcmp($exever, '2.11.0') >= 0)
+ $sections = array('TMNF', 'TMU');
+ else
+ $sections = array('TMU'); // TMNF section opened after TMF beta
+ } elseif ($envir == 'Bay' || $envir == 'Coast' || $envir == 'Island') {
+ // check for old TMS
+ if (strcmp($exever, '0.1.5.0') <= 0)
+ $sections = array('TMS', 'TMU');
+ else
+ $sections = array('TMU'); // TMS section closed after TMU release
+ } else { // $envir == 'Alpine' || 'Snow' || 'Desert' || 'Speed' || 'Rally'
+ // check for old TMO
+ if (strcmp($exever, '0.1.5.0') <= 0)
+ $sections = array('TMO', 'TMU');
+ else
+ $sections = array('TMU'); // TMO section closed after TMU release
+ }
+
+ // search TMX for track
+ foreach ($sections as $section) {
+ $tmxdata = new TMXInfoFetcher($section, $uid, $records);
+ if ($tmxdata->name) {
+ return $tmxdata;
+ }
+ }
+ return false;
+} // findTMXdata
+
+/**
+ * Simple HTTP Get function with timeout
+ * ok: return string || error: return false || timeout: return -1
+ * if $openonly == true, don't read data but return true upon connect
+ */
+function http_get_file($url, $openonly = false) {
+ global $aseco;
+
+ $url = parse_url($url);
+ $port = isset($url['port']) ? $url['port'] : 80;
+ $query = isset($url['query']) ? '?' . $url['query'] : '';
+
+ $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4);
+ if (!$fp)
+ return false;
+ if ($openonly) {
+ fclose($fp);
+ return true;
+ }
+
+ $uri = '';
+ foreach (explode('/', $url['path']) as $subpath)
+ $uri .= rawurlencode($subpath) . '/';
+ $uri = substr($uri, 0, strlen($uri)-1); // strip trailing '/'
+
+ fwrite($fp, 'GET ' . $uri . $query . " HTTP/1.0\r\n" .
+ 'Host: ' . $url['host'] . "\r\n" .
+ 'User-Agent: XASECO-' . XASECO_VERSION . ' (' . PHP_OS . '; ' .
+ $aseco->server->game . ")\r\n\r\n");
+ stream_set_timeout($fp, 2);
+ $res = '';
+ $info['timed_out'] = false;
+ while (!feof($fp) && !$info['timed_out']) {
+ $res .= fread($fp, 512);
+ $info = stream_get_meta_data($fp);
+ }
+ fclose($fp);
+
+ if ($info['timed_out']) {
+ return -1;
+ } else {
+ if (substr($res, 9, 3) != '200')
+ return false;
+ $page = explode("\r\n\r\n", $res, 2);
+ return $page[1];
+ }
+} // http_get_file
+
+/**
+ * Return valid UTF-8 string, replacing faulty byte values with a given string
+ * Created by (OoR-F)~fuckfish (fish@stabb.de)
+ * http://www.tm-forum.com/viewtopic.php?p=117639#p117639
+ * Based on the original tm_substr function by Slig (slig@free.fr)
+ * Updated by Xymph; More info: http://en.wikipedia.org/wiki/UTF-8
+ */
+function validateUTF8String($input, $invalidRepl = '') {
+
+ $str = (string) $input;
+ $len = strlen($str); // byte string length
+ $pos = 0; // current byte pos in string
+ $new = '';
+
+ while ($pos < $len) {
+ $co = ord($str[$pos]);
+
+ // 4-6 bytes UTF8 => unsupported
+ if ($co >= 240) {
+ // bad multibyte char
+ $new .= $invalidRepl;
+ $pos++;
+
+ // 3 bytes UTF8 => 1110bbbb 10bbbbbb 10bbbbbb
+ } elseif ($co >= 224) {
+ if (($pos+2 < $len) &&
+ (ord($str[$pos+1]) >= 128 && ord($str[$pos+1]) < 192) &&
+ (ord($str[$pos+2]) >= 128 && ord($str[$pos+2]) < 192)) {
+ // ok, it was 1 character, increase counters
+ $new .= substr($str, $pos, 3);
+ $pos += 3;
+ } else {
+ // bad multibyte char
+ $new .= $invalidRepl;
+ $pos++;
+ }
+
+ // 2 bytes UTF8 => 110bbbbb 10bbbbbb
+ } elseif ($co >= 194) {
+ if (($pos+1 < $len) &&
+ (ord($str[$pos+1]) >= 128 && ord($str[$pos+1]) < 192)) {
+ // ok, it was 1 character, increase counters
+ $new .= substr($str, $pos, 2);
+ $pos += 2;
+ } else {
+ // bad multibyte char
+ $new .= $invalidRepl;
+ $pos++;
+ }
+
+ // 2 bytes overlong encoding => unsupported
+ } elseif ($co >= 192) {
+ // bad multibyte char 1100000b
+ $new .= $invalidRepl;
+ $pos++;
+
+ // 1 byte ASCII => 0bbbbbbb, or invalid => 10bbbbbb or 11111bbb
+ } else { // $co < 192
+ // erroneous middle multibyte char?
+ if ($co >= 128 || $co == 0)
+ $new .= $invalidRepl;
+ else
+ $new .= $str[$pos];
+
+ $pos++;
+ }
+ }
+ return $new;
+} // validateUTF8String
+
+/**
+ * Convert php.ini memory shorthand string to integer bytes
+ * http://www.php.net/manual/en/function.ini-get.php#96996
+ */
+function shorthand2bytes($size_str) {
+
+ switch (substr($size_str, -1)) {
+ case 'M': case 'm': return (int)$size_str * 1048576;
+ case 'K': case 'k': return (int)$size_str * 1024;
+ case 'G': case 'g': return (int)$size_str * 1073741824;
+ default: return (int)$size_str;
+ }
+} // return_bytes
+
+/**
+ * Convert boolean value to text string
+ */
+function bool2text($boolval) {
+
+ if ($boolval)
+ return 'True';
+ else
+ return 'False';
+} // bool2text
+?>
diff --git a/xaseco/includes/gbxchallinfo.inc.php b/xaseco/includes/gbxchallinfo.inc.php
new file mode 100644
index 0000000..8565002
--- /dev/null
+++ b/xaseco/includes/gbxchallinfo.inc.php
@@ -0,0 +1,131 @@
+
+ *
+ * v1.2: Allow getting info from tracks already on the server w/o removing them
+ * v1.1: Fix PHP notices about redefinition of constants
+ * v1.0: Initial release
+ */
+
+class GBXChallengeInfo {
+
+ public $name, $uid, $filename, $author, $envir, $mood,
+ $bronzetm, $silvertm, $goldtm, $authortm,
+ $coppers, $laprace, $nblaps, $nbcps, $nbrcps, $error;
+
+ /**
+ * Fetches current ChallengeInfo for a GBX challenge
+ * Loads track into server, selects it, gets info, and removes it from server
+ *
+ * @param String $filename
+ * The challenge filename (must be a path below .../GameData/Tracks/)
+ * @return GBXChallengeInfo
+ * If $uid is empty, GBX data couldn't be extracted and $error contains
+ * an error message
+ */
+ public function GBXChallengeInfo($filename) {
+
+ $ip = 'localhost';
+ $port = 5000;
+ $user = 'SuperAdmin';
+ $pass = 'YOUR_SUPERADMIN_PASSWORD';
+
+ $this->uid = '';
+ $this->error = '';
+ $client = new IXR_Client_Gbx;
+
+ // connect to the server
+ if (!$client->InitWithIp($ip, $port)) {
+ $this->error = 'Connection failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage();
+ return false;
+ }
+
+ // log into the server
+ if (!$client->query('Authenticate', $user, $pass)) {
+ $this->error = 'Login failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage();
+
+ } else {
+ // add the challenge
+ $ret = $client->query('AddChallenge', $filename);
+ $already = ($client->getErrorMessage() == 'Challenge already added.');
+ if (!$ret && !$already) {
+ $this->error = 'AddChallenge failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage();
+ } else {
+
+ // select the challenge
+ if (!$client->query('ChooseNextChallenge', $filename)) {
+ $this->error = 'ChooseNextChallenge failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage();
+
+ // switch to our challenge
+ } elseif (!$client->query('NextChallenge')) {
+ $this->error = 'NextChallenge 1 failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage();
+ } else {
+
+ // allow for challenge switch but time out after 5 seconds
+ $retry = 5;
+ while (true) {
+ sleep(1);
+
+ // obtain challenge details
+ if ($client->query('GetCurrentChallengeInfo')) {
+ $info = $client->getResponse();
+ // check for our challenge
+ if ($info['FileName'] == $filename) {
+ break;
+ }
+ } else {
+ $this->error = 'GetCurrentChallengeInfo failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage();
+ break;
+ }
+ if ($retry-- == 0) {
+ $this->error = 'GetCurrentChallengeInfo timed out after 5 seconds';
+ break;
+ }
+ }
+
+ // extract our challenge details
+ if ($this->error == '') {
+ $this->name = $info['Name'];
+ $this->uid = $info['UId'];
+ $this->filename = $info['FileName'];
+ $this->author = $info['Author'];
+ $this->envir = $info['Environnement'];
+ $this->mood = $info['Mood'];
+ $this->bronzetm = $info['BronzeTime'];
+ $this->silvertm = $info['SilverTime'];
+ $this->goldtm = $info['GoldTime'];
+ $this->authortm = $info['AuthorTime'];
+ $this->coppers = $info['CopperPrice'];
+ $this->laprace = $info['LapRace'];
+ $this->nblaps = $info['NbLaps'];
+ $this->nbcps = $info['NbCheckpoints'];
+ $this->nbrcps = $info['NbCheckpoints'];
+
+ if ($this->laprace && $this->nblaps > 1)
+ $this->nbrcps *= $this->nblaps;
+ }
+ }
+ }
+
+ // check if challenge wasn't already there
+ if (!$already) {
+ // remove the challenge
+ if (!$client->query('RemoveChallenge', $filename)) {
+ $this->error = 'RemoveChallenge failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage();
+ }
+ // switch away from our challenge
+ sleep(5);
+ if (!$client->query('NextChallenge')) {
+ $this->error = 'NextChallenge 2 failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage();
+ }
+ }
+ }
+
+ $client->Terminate();
+ }
+}
+?>
diff --git a/xaseco/includes/gbxdatafetcher.inc.php b/xaseco/includes/gbxdatafetcher.inc.php
new file mode 100644
index 0000000..9d6c0dd
--- /dev/null
+++ b/xaseco/includes/gbxdatafetcher.inc.php
@@ -0,0 +1,1431 @@
+
+ * Thanks to Electron for additional input & prototyping
+ * Based on information at http://en.tm-wiki.org/wiki/GBX,
+ * http://www.tm-forum.com/viewtopic.php?p=192817#p192817
+ * and http://en.tm-wiki.org/wiki/PAK#Header_versions_8.2B
+ *
+ * v2.5: Add lookback string Valley; strip optional digits from mood; fix empty
+ * thumbnail warning
+ * v2.4: Update GBXChallMapFetcher & GBXPackFetcher version-dependent processing;
+ * update lookback strings
+ * v2.3: Add UTF-8 decoding of parsed XML chunk's elements; strip UTF-8 BOM
+ * from various string fields
+ * v2.2: Add class GBXPackFetcher; limit loadGBXdata() to first 256KB
+ * v2.1: Add exception codes; add $thumbLen to GBXChallMapFetcher
+ * v2.0: Complete rewrite
+ */
+
+/**
+ * @class GBXBaseFetcher
+ * @brief The base GBX class with all common functionality
+ */
+class GBXBaseFetcher
+{
+ public $parseXml, $xml, $xmlParsed;
+
+ public $authorVer, $authorLogin, $authorNick, $authorZone, $authorEInfo;
+
+ private $_gbxdata, $_gbxlen, $_gbxptr, $_debug, $_error, $_endianess,
+ $_lookbacks, $_parsestack;
+
+ // supported class ID's
+ const GBX_CHALLENGE_TMF = 0x03043000;
+ const GBX_AUTOSAVE_TMF = 0x03093000;
+ const GBX_CHALLENGE_TM = 0x24003000;
+ const GBX_AUTOSAVE_TM = 0x2403F000;
+ const GBX_REPLAY_TM = 0x2407E000;
+
+ const MACHINE_ENDIAN_ORDER = 0;
+ const LITTLE_ENDIAN_ORDER = 1;
+ const BIG_ENDIAN_ORDER = 2;
+
+ const LOAD_LIMIT = 256; // KBs to read in loadGBXdata()
+
+ // initialise new instance
+ public function __construct()
+ {
+ $this->parseXml = false;
+ $this->xml = '';
+ $this->xmlParsed = array();
+ $this->_debug = false;
+ $this->_error = '';
+
+ list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
+ if ($endiantest != 1)
+ $this->_endianess = self::BIG_ENDIAN_ORDER;
+ else
+ $this->_endianess = self::LITTLE_ENDIAN_ORDER;
+
+ $this->clearGBXdata();
+ $this->clearLookbacks();
+ $this->_parsestack = array();
+
+ $this->authorVer = 0;
+ $this->authorLogin = '';
+ $this->authorNick = '';
+ $this->authorZone = '';
+ $this->authorEInfo = '';
+ }
+
+ // enable debug logging
+ protected function enableDebug()
+ {
+ $this->_debug = true;
+ }
+
+ // disable debug logging
+ protected function disableDebug()
+ {
+ $this->_debug = false;
+ }
+
+ // print message to stderr if debugging
+ protected function debugLog($msg)
+ {
+ if ($this->_debug)
+ fwrite(STDERR, $msg."\n");
+ }
+
+ // set error message prefix
+ protected function setError($prefix)
+ {
+ $this->_error = (string)$prefix;
+ }
+
+ // exit with error exception
+ protected function errorOut($msg, $code = 0)
+ {
+ $this->clearGBXdata();
+ throw new Exception($this->_error . $msg, $code);
+ }
+
+ // read in raw GBX data
+ protected function loadGBXdata($filename)
+ {
+ $gbxdata = @file_get_contents($filename, false, null, 0, self::LOAD_LIMIT * 1024);
+ if ($gbxdata !== false)
+ $this->storeGBXdata($gbxdata);
+ else
+ $this->errorOut('Unable to read GBX data from ' . $filename, 1);
+ }
+
+ // store raw GBX data
+ protected function storeGBXdata($gbxdata)
+ {
+ $this->_gbxdata = & $gbxdata;
+ $this->_gbxlen = strlen($gbxdata);
+ $this->_gbxptr = 0;
+ }
+
+ // clear GBX data (to avoid print_r problems & reduce memory usage)
+ protected function clearGBXdata()
+ {
+ $this->storeGBXdata('');
+ }
+
+ // get GBX pointer
+ protected function getGBXptr()
+ {
+ return $this->_gbxptr;
+ }
+
+ // set GBX pointer
+ protected function setGBXptr($ptr)
+ {
+ $this->_gbxptr = (int)$ptr;
+ }
+
+ // move GBX pointer
+ protected function moveGBXptr($len)
+ {
+ $this->_gbxptr += (int)$len;
+ }
+
+ // read $len bytes from GBX data
+ protected function readData($len)
+ {
+ if ($this->_gbxptr + $len > $this->_gbxlen)
+ $this->errorOut(sprintf('Insufficient data for %d bytes at pos 0x%04X',
+ $len, $this->_gbxptr), 2);
+ $data = '';
+ while ($len-- > 0)
+ $data .= $this->_gbxdata[$this->_gbxptr++];
+ return $data;
+ }
+
+ // read signed byte from GBX data
+ protected function readInt8()
+ {
+ $data = $this->readData(1);
+ list(, $int8) = unpack('c*', $data);
+ return $int8;
+ }
+
+ // read signed short from GBX data
+ protected function readInt16()
+ {
+ $data = $this->readData(2);
+ if ($this->_endianess == self::BIG_ENDIAN_ORDER)
+ $data = strrev($data);
+ list(, $int16) = unpack('s*', $data);
+ return $int16;
+ }
+
+ // read signed long from GBX data
+ protected function readInt32()
+ {
+ $data = $this->readData(4);
+ if ($this->_endianess == self::BIG_ENDIAN_ORDER)
+ $data = strrev($data);
+ list(, $int32) = unpack('l*', $data);
+ return $int32;
+ }
+
+ // read string from GBX data
+ protected function readString()
+ {
+ $gbxptr = $this->getGBXptr();
+ $len = $this->readInt32();
+ $len &= 0x7FFFFFFF;
+ if ($len <= 0 || $len >= 0x12000) { // for large XML & Data blocks
+ if ($len != 0)
+ $this->errorOut(sprintf('Invalid string length %d (0x%04X) at pos 0x%04X',
+ $len, $len, $gbxptr), 3);
+ }
+ $data = $this->readData($len);
+ return $data;
+ }
+
+ // strip UTF-8 BOM from string
+ protected function stripBOM($str)
+ {
+ return str_replace("\xEF\xBB\xBF", '', $str);
+ }
+
+ // clear lookback strings
+ protected function clearLookbacks()
+ {
+ $this->_lookbacks = array();
+ }
+
+ // read lookback string from GBX data
+ protected function readLookbackString()
+ {
+ if (empty($this->_lookbacks)) {
+ $version = $this->readInt32();
+ if ($version != 3)
+ $this->errorOut('Unknown lookback strings version: ' . $version, 4);
+ }
+
+ // check index
+ $index = $this->readInt32();
+ if ($index == -1) {
+ // unassigned (empty) string
+ $str = '';
+ } elseif (($index & 0xC0000000) == 0) {
+ // use external reference string
+ switch ($index) {
+ case 11: $str = 'Valley';
+ break;
+ case 12: $str = 'Canyon';
+ break;
+ case 17: $str = 'TMCommon';
+ break;
+ case 202: $str = 'Storm';
+ break;
+ case 299: $str = 'SMCommon';
+ break;
+ case 10003: $str = 'Common';
+ break;
+ default: $str = 'UNKNOWN';
+ }
+ } elseif (($index & 0x3FFFFFFF) == 0) {
+ // read string & add to lookbacks
+ $str = $this->readString();
+ $this->_lookbacks[] = $str;
+ } else {
+ // use string from lookbacks
+ $str = $this->_lookbacks[($index & 0x3FFFFFFF) - 1];
+ }
+
+ return $str;
+ }
+
+ // XML parser functions
+ private function startTag($parser, $name, $attribs)
+ {
+ foreach ($attribs as $key => &$val)
+ $val = utf8_decode($val);
+ //echo 'startTag: ' . $name . "\n"; print_r($attribs);
+ array_push($this->_parsestack, $name);
+ if ($name == 'DEP') {
+ $this->xmlParsed['DEPS'][] = $attribs;
+ } elseif (count($this->_parsestack) <= 2) {
+ // HEADER, IDENT, DESC, TIMES, CHALLENGE/MAP, DEPS, CHECKPOINTS
+ $this->xmlParsed[$name] = $attribs;
+ }
+ }
+
+ private function charData($parser, $data)
+ {
+ //echo 'charData: ' . $data . "\n";
+ if (count($this->_parsestack) == 3)
+ $this->xmlParsed[$this->_parsestack[1]][$this->_parsestack[2]] = $data;
+ elseif (count($this->_parsestack) > 3)
+ $this->debugLog('XML chunk nested too deeply: ', print_r($this->_parsestack, true));
+ }
+
+ private function endTag($parser, $name)
+ {
+ //echo 'endTag: ' . $name . "\n";
+ array_pop($this->_parsestack);
+ }
+
+ protected function parseXMLstring()
+ {
+ // define a dedicated parser to handle the attributes
+ $xml_parser = xml_parser_create();
+ xml_set_object($xml_parser, $this);
+ xml_set_element_handler($xml_parser, 'startTag', 'endTag');
+ xml_set_character_data_handler($xml_parser, 'charData');
+
+ // escape '&' characters unless already a known entity
+ $xml = preg_replace('/&(?!(?:amp|quot|apos|lt|gt);)/', '&', $this->xml);
+
+ if (!xml_parse($xml_parser, utf8_encode($xml), true))
+ $this->errorOut(sprintf('XML chunk parse error: %s at line %d',
+ xml_error_string(xml_get_error_code($xml_parser)),
+ xml_get_current_line_number($xml_parser)), 12);
+
+ xml_parser_free($xml_parser);
+ }
+
+ /**
+ * Check GBX header, main class ID & header block
+ * @param Array $classes
+ * The main class IDs accepted for this GBX
+ * @return Size of GBX header block
+ */
+ protected function checkHeader(array $classes)
+ {
+ // check magic header
+ $data = $this->readData(3);
+ $version = $this->readInt16();
+ if ($data != 'GBX' || $version != 6)
+ $this->errorOut('No magic GBX header', 5);
+
+ // check header block (un)compression
+ $data = $this->readData(4);
+ if ($data[1] != 'U')
+ $this->errorOut('Compressed GBX header block not supported', 6);
+
+ // check main class ID
+ $mainClass = $this->readInt32();
+ if (!in_array($mainClass, $classes))
+ $this->errorOut(sprintf('Main class ID %08X not supported', $mainClass), 7);
+ $this->debugLog(sprintf('GBX main class ID: %08X', $mainClass));
+
+ // get header size
+ $headerSize = $this->readInt32();
+ if ($headerSize == 0)
+ $this->errorOut('No GBX header block', 8);
+
+ $this->debugLog(sprintf('GBX header block size: %d (%.1f KB)',
+ $headerSize, $headerSize / 1024));
+ return $headerSize;
+ } // checkHeader
+
+ /**
+ * Get list of chunks from GBX header block
+ * @param Int $headerSize
+ * Size of header block (chunks list & chunks data)
+ * @param array $chunks
+ * List of chunk IDs & names
+ * @return List of chunk offsets & sizes
+ */
+ protected function getChunksList($headerSize, array $chunks)
+ {
+ // get number of chunks
+ $numChunks = $this->readInt32();
+ if ($numChunks == 0)
+ $this->errorOut('No GBX header chunks', 9);
+
+ $this->debugLog('GBX number of header chunks: ' . $numChunks);
+ $chunkStart = $this->getGBXptr();
+ $this->debugLog(sprintf('GBX start of chunk list: 0x%04X', $chunkStart));
+ $chunkOffset = $chunkStart + $numChunks * 8;
+
+ // get list of all chunks
+ $chunksList = array();
+ for ($i = 0; $i < $numChunks; $i++)
+ {
+ $chunkId = $this->readInt32();
+ $chunkSize = $this->readInt32();
+
+ $chunkId &= 0x00000FFF;
+ $chunkSize &= 0x7FFFFFFF;
+
+ if (array_key_exists($chunkId, $chunks)) {
+ $name = $chunks[$chunkId];
+ $chunksList[$name] = array(
+ 'off' => $chunkOffset,
+ 'size' => $chunkSize
+ );
+ } else {
+ $name = 'UNKNOWN';
+ }
+ $this->debugLog(sprintf('GBX chunk %2d %-8s Id 0x%03X Offset 0x%06X Size %6d',
+ $i, $name, $chunkId, $chunkOffset, $chunkSize));
+ $chunkOffset += $chunkSize;
+ }
+
+ //$this->debugLog(print_r($chunksList, true));
+ $totalSize = $chunkOffset - $chunkStart + 4; // numChunks
+ if ($headerSize != $totalSize)
+ $this->errorOut(sprintf('Chunk list size mismatch: %d <> %d',
+ $headerSize, $totalSize), 10);
+
+ return $chunksList;
+ } // getChunksList
+
+ /**
+ * Initialize for a new chunk
+ * @param int $offset
+ */
+ protected function initChunk($offset)
+ {
+ $this->setGBXptr($offset);
+ $this->clearLookbacks();
+ }
+
+ /**
+ * Get XML chunk from GBX header block & optionally parse it
+ * @param array $chunksList
+ * List of chunk offsets & sizes
+ */
+ protected function getXMLChunk(array $chunksList)
+ {
+ if (!isset($chunksList['XML'])) return;
+
+ $this->initChunk($chunksList['XML']['off']);
+ $this->xml = $this->readString();
+
+ if ($chunksList['XML']['size'] != strlen($this->xml) + 4)
+ $this->errorOut(sprintf('XML chunk size mismatch: %d <> %d',
+ $chunksList['XML']['size'], strlen($this->xml) + 4), 11);
+
+ if ($this->parseXml && $this->xml != '')
+ $this->parseXMLstring();
+ } // getXMLChunk
+
+ /**
+ * Get Author fields from GBX header block
+ */
+ protected function getAuthorFields()
+ {
+ $this->authorVer = $this->readInt32();
+ $this->authorLogin = $this->readString();
+ $this->authorNick = $this->stripBOM($this->readString());
+ $this->authorZone = $this->stripBOM($this->readString());
+ $this->authorEInfo = $this->readString();
+ } // getAuthorFields
+
+ /**
+ * Get Author chunk from GBX header block
+ * @param array $chunksList
+ * List of chunk offsets & sizes
+ */
+ protected function getAuthorChunk(array $chunksList)
+ {
+ if (!isset($chunksList['Author'])) return;
+
+ $this->initChunk($chunksList['Author']['off']);
+ $version = $this->readInt32();
+ $this->debugLog('GBX Author chunk version: ' . $version);
+
+ $this->getAuthorFields();
+ } // getAuthorChunk
+
+} // class GBXBaseFetcher
+
+
+/**
+ * @class GBXChallMapFetcher
+ * @brief The class that fetches all GBX challenge/map info
+ */
+class GBXChallMapFetcher extends GBXBaseFetcher
+{
+ public $tnImage;
+
+ public $headerVersn, $bronzeTime, $silverTime, $goldTime, $authorTime,
+ $cost, $multiLap, $type, $typeName, $authorScore, $simpleEdit,
+ $nbChecks, $nbLaps;
+ public $uid, $envir, $author, $name, $kind, $kindName, $password,
+ $mood, $envirBg, $authorBg, $mapType, $mapStyle, $lightmap, $titleUid;
+ public $xmlVer, $exeVer, $exeBld, $validated, $songFile, $songUrl,
+ $modName, $modFile, $modUrl;
+ public $thumbLen, $thumbnail, $comment;
+
+ const IMAGE_FLIP_HORIZONTAL = 1;
+ const IMAGE_FLIP_VERTICAL = 2;
+ const IMAGE_FLIP_BOTH = 3;
+
+ /**
+ * Mirror (flip) an image across horizontal, vertical or both axis
+ * Source: http://www.php.net/manual/en/function.imagecopy.php#85992
+ * @param String $imgsrc
+ * Source image data
+ * @param Int $dir
+ * Flip direction from the constants above
+ * @return Flipped image data if successful, otherwise source image data
+ */
+ private function imageFlip($imgsrc, $dir)
+ {
+ $width = imagesx($imgsrc);
+ $height = imagesy($imgsrc);
+ $src_x = 0;
+ $src_y = 0;
+ $src_width = $width;
+ $src_height = $height;
+
+ switch ((int)$dir) {
+ case self::IMAGE_FLIP_HORIZONTAL:
+ $src_y = $height;
+ $src_height = -$height;
+ break;
+ case self::IMAGE_FLIP_VERTICAL:
+ $src_x = $width;
+ $src_width = -$width;
+ break;
+ case self::IMAGE_FLIP_BOTH:
+ $src_x = $width;
+ $src_y = $height;
+ $src_width = -$width;
+ $src_height = -$height;
+ break;
+ default:
+ return $imgsrc;
+ }
+
+ $imgdest = imagecreatetruecolor($width, $height);
+ if (imagecopyresampled($imgdest, $imgsrc, 0, 0, $src_x, $src_y,
+ $width, $height, $src_width, $src_height)) {
+ return $imgdest;
+ }
+ return $imgsrc;
+ } // imageFlip
+
+ /**
+ * Instantiate GBX challenge/map fetcher
+ *
+ * @param Boolean $parsexml
+ * If true, the fetcher also parses the XML block
+ * @param Boolean $tnimage
+ * If true, the fetcher also extracts the thumbnail image;
+ * if GD/JPEG libraries are present, image will be flipped upright,
+ * otherwise it will be in the original upside-down format
+ * Warning: this is binary data in JPEG format, 256x256 pixels for
+ * TMU/TMF or 512x512 pixels for MP
+ * @param Boolean $debug
+ * If true, the fetcher prints debug logging to stderr
+ * @return GBXChallMapFetcher
+ * If GBX data couldn't be extracted, an Exception is thrown with
+ * the error message & code
+ */
+ public function __construct($parsexml = false, $tnimage = false, $debug = false)
+ {
+ parent::__construct();
+
+ $this->headerVersn = 0;
+ $this->bronzeTime = 0;
+ $this->silverTime = 0;
+ $this->goldTime = 0;
+ $this->authorTime = 0;
+
+ $this->cost = 0;
+ $this->multiLap = false;
+ $this->type = 0;
+ $this->typeName = '';
+
+ $this->authorScore = 0;
+ $this->simpleEdit = false;
+ $this->nbChecks = 0;
+ $this->nbLaps = 0;
+
+ $this->uid = '';
+ $this->envir = '';
+ $this->author = '';
+ $this->name = '';
+ $this->kind = 0;
+ $this->kindName = '';
+
+ $this->password = '';
+ $this->mood = '';
+ $this->envirBg = '';
+ $this->authorBg = '';
+
+ $this->mapType = '';
+ $this->mapStyle = '';
+ $this->lightmap = 0;
+ $this->titleUid = '';
+
+ $this->xmlVer = '';
+ $this->exeVer = '';
+ $this->exeBld = '';
+ $this->validated = false;
+ $this->songFile = '';
+ $this->songUrl = '';
+ $this->modName = '';
+ $this->modFile = '';
+ $this->modUrl = '';
+
+ $this->thumbLen = 0;
+ $this->thumbnail = '';
+ $this->comment = '';
+
+ $this->parseXml = (bool)$parsexml;
+ $this->tnImage = (bool)$tnimage;
+ if ((bool)$debug)
+ $this->enableDebug();
+
+ $this->setError('GBX map error: ');
+ } // __construct
+
+ /**
+ * Process GBX challenge/map file
+ *
+ * @param String $filename
+ * The challenge filename
+ */
+ public function processFile($filename)
+ {
+ $this->loadGBXdata((string)$filename);
+
+ $this->processGBX();
+ } // processFile
+
+ /**
+ * Process GBX challenge/map data
+ *
+ * @param String $gbxdata
+ * The challenge/map data
+ */
+ public function processData($gbxdata)
+ {
+ $this->storeGBXdata((string)$gbxdata);
+
+ $this->processGBX();
+ } // processData
+
+ // process GBX data
+ private function processGBX()
+ {
+ // supported challenge/map class IDs
+ $challclasses = array(
+ self::GBX_CHALLENGE_TMF,
+ self::GBX_CHALLENGE_TM,
+ );
+
+ $headerSize = $this->checkHeader($challclasses);
+ $headerStart = $headerEnd = $this->getGBXptr();
+
+ // desired challenge/map chunk IDs
+ $chunks = array(
+ 0x002 => 'Info', // TM, MP
+ 0x003 => 'String', // TM, MP
+ 0x004 => 'Version', // TM, MP
+ 0x005 => 'XML', // TM, MP
+ 0x007 => 'Thumbnl', // TM, MP
+ 0x008 => 'Author', // MP
+ );
+
+ $chunksList = $this->getChunksList($headerSize, $chunks);
+
+ $this->getInfoChunk($chunksList);
+ $headerEnd = max($headerEnd, $this->getGBXptr());
+
+ $this->getStringChunk($chunksList);
+ $headerEnd = max($headerEnd, $this->getGBXptr());
+
+ $this->getVersionChunk($chunksList);
+ $headerEnd = max($headerEnd, $this->getGBXptr());
+
+ $this->getXMLChunk($chunksList);
+ $headerEnd = max($headerEnd, $this->getGBXptr());
+
+ $this->getThumbnlChunk($chunksList);
+ $headerEnd = max($headerEnd, $this->getGBXptr());
+
+ $this->getAuthorChunk($chunksList);
+ $headerEnd = max($headerEnd, $this->getGBXptr());
+
+ if ($headerSize != $headerEnd - $headerStart)
+ $this->errorOut(sprintf('Header size mismatch: %d <> %d',
+ $headerSize, $headerEnd - $headerStart), 16);
+
+ if ($this->parseXml) {
+ if (isset($this->xmlParsed['HEADER']['VERSION']))
+ $this->xmlVer = $this->xmlParsed['HEADER']['VERSION'];
+ if (isset($this->xmlParsed['HEADER']['EXEVER']))
+ $this->exeVer = $this->xmlParsed['HEADER']['EXEVER'];
+ if (isset($this->xmlParsed['HEADER']['EXEBUILD']))
+ $this->exeBld = $this->xmlParsed['HEADER']['EXEBUILD'];
+ if ($this->lightmap == 0 && isset($this->xmlParsed['HEADER']['LIGHTMAP']))
+ $this->lightmap = (int)$this->xmlParsed['HEADER']['LIGHTMAP'];
+ if ($this->authorZone == '' && isset($this->xmlParsed['IDENT']['AUTHORZONE']))
+ $this->authorZone = $this->xmlParsed['IDENT']['AUTHORZONE'];
+ if ($this->envir == 'UNKNOWN' && isset($this->xmlParsed['DESC']['ENVIR']))
+ $this->envir = $this->xmlParsed['DESC']['ENVIR'];
+ if ($this->nbLaps == 0 && isset($this->xmlParsed['DESC']['NBLAPS']))
+ $this->nbLaps = (int)$this->xmlParsed['DESC']['NBLAPS'];
+ if (isset($this->xmlParsed['DESC']['VALIDATED']))
+ $this->validated = (bool)$this->xmlParsed['DESC']['VALIDATED'];
+ if (isset($this->xmlParsed['DESC']['MOD']))
+ $this->modName = $this->xmlParsed['DESC']['MOD'];
+
+ // extract optional song & mod filenames
+ if (!empty($this->xmlParsed['DEPS'])) {
+ for ($i = 0; $i < count($this->xmlParsed['DEPS']); $i++) {
+ if (preg_match('/ChallengeMusics\\\\(.+)/', $this->xmlParsed['DEPS'][$i]['FILE'], $path)) {
+ $this->songFile = $path[1];
+ if (isset($this->xmlParsed['DEPS'][$i]['URL']))
+ $this->songUrl = $this->xmlParsed['DEPS'][$i]['URL'];
+ } elseif (preg_match('/.+\\\\Mod\\\\.+/', $this->xmlParsed['DEPS'][$i]['FILE'], $path)) {
+ $this->modFile = $path[0];
+ if (isset($this->xmlParsed['DEPS'][$i]['URL']))
+ $this->modUrl = $this->xmlParsed['DEPS'][$i]['URL'];
+ }
+ }
+ }
+ }
+
+ $this->clearGBXdata();
+ } // processGBX
+
+ /**
+ * Get Info chunk from GBX header block
+ * @param array $chunksList
+ * List of chunk offsets & sizes
+ */
+ protected function getInfoChunk(array $chunksList)
+ {
+ if (!isset($chunksList['Info'])) return;
+
+ $this->initChunk($chunksList['Info']['off']);
+ $version = $this->readInt8();
+ $this->debugLog('GBX Info chunk version: ' . $version);
+
+ if ($version < 3) {
+ $this->uid = $this->readLookbackString();
+
+ $this->envir = $this->readLookbackString();
+ $this->author = $this->readLookbackString();
+
+ $this->name = $this->stripBOM($this->readString());
+ }
+ $this->moveGBXptr(4); // skip bool 0
+
+ if ($version >= 1) {
+ $this->bronzeTime = $this->readInt32();
+
+ $this->silverTime = $this->readInt32();
+
+ $this->goldTime = $this->readInt32();
+
+ $this->authorTime = $this->readInt32();
+
+ if ($version == 2)
+ $this->moveGBXptr(1); // skip unknown byte
+
+ if ($version >= 4) {
+ $this->cost = $this->readInt32();
+
+ if ($version >= 5) {
+ $this->multiLap = (bool)$this->readInt32();
+
+ if ($version == 6)
+ $this->moveGBXptr(4); // skip unknown bool
+
+ if ($version >= 7) {
+ $this->type = $this->readInt32();
+ switch ($this->type) {
+ case 0: $this->typeName = 'Race';
+ break;
+ case 1: $this->typeName = 'Platform';
+ break;
+ case 2: $this->typeName = 'Puzzle';
+ break;
+ case 3: $this->typeName = 'Crazy';
+ break;
+ case 4: $this->typeName = 'Shortcut';
+ break;
+ case 5: $this->typeName = 'Stunts';
+ break;
+ case 6: $this->typeName = 'Script';
+ break;
+ default: $this->typeName = 'UNKNOWN';
+ }
+
+ if ($version >= 9) {
+ $this->moveGBXptr(4); // skip int32 0
+
+ if ($version >= 10) {
+ $this->authorScore = $this->readInt32();
+
+ if ($version >= 11) {
+ $this->simpleEdit = (bool)$this->readInt32();
+
+ if ($version >= 12) {
+ $this->moveGBXptr(4); // skip bool 0
+
+ if ($version >= 13) {
+ $this->nbChecks = $this->readInt32();
+
+ $this->nbLaps = $this->readInt32();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } // getInfoChunk
+
+ /**
+ * Get String chunk from GBX header block
+ * @param array $chunksList
+ * List of chunk offsets & sizes
+ */
+ protected function getStringChunk(array $chunksList)
+ {
+ if (!isset($chunksList['String'])) return;
+
+ $this->initChunk($chunksList['String']['off']);
+ $version = $this->readInt8();
+ $this->debugLog('GBX String chunk version: ' . $version);
+
+ $this->uid = $this->readLookbackString();
+
+ $this->envir = $this->readLookbackString();
+ $this->author = $this->readLookbackString();
+
+ $this->name = $this->stripBOM($this->readString());
+
+ $this->kind = $this->readInt8();
+ switch ($this->kind) {
+ case 0: $this->kindName = '(internal)EndMarker';
+ break;
+ case 1: $this->kindName = '(old)Campaign';
+ break;
+ case 2: $this->kindName = '(old)Puzzle';
+ break;
+ case 3: $this->kindName = '(old)Retro';
+ break;
+ case 4: $this->kindName = '(old)TimeAttack';
+ break;
+ case 5: $this->kindName = '(old)Rounds';
+ break;
+ case 6: $this->kindName = 'InProgress';
+ break;
+ case 7: $this->kindName = 'Campaign';
+ break;
+ case 8: $this->kindName = 'Multi';
+ break;
+ case 9: $this->kindName = 'Solo';
+ break;
+ case 10: $this->kindName = 'Site';
+ break;
+ case 11: $this->kindName = 'SoloNadeo';
+ break;
+ case 12: $this->kindName = 'MultiNadeo';
+ break;
+ default: $this->kindName = 'UNKNOWN';
+ }
+
+ if ($version >= 1) {
+ $this->moveGBXptr(4); // skip locked
+
+ $this->password = $this->readString();
+
+ if ($version >= 2) {
+ $this->mood = $this->readLookbackString();
+ $this->mood = preg_replace('/([A-Za-z]+)\d*/', '\1', $this->mood);
+
+ $this->envirBg = $this->readLookbackString();
+ $this->authorBg = $this->readLookbackString();
+
+ if ($version >= 3) {
+ $this->moveGBXptr(8); // skip mapOrigin
+
+ if ($version >= 4) {
+ $this->moveGBXptr(8); // skip mapTarget
+
+ if ($version >= 5) {
+ $this->moveGBXptr(16); // skip unknown int128
+
+ if ($version >= 6) {
+ $this->mapType = $this->readString();
+ $this->mapStyle = $this->readString();
+
+ if ($version <= 8)
+ $this->moveGBXptr(4); // skip unknown bool
+
+ if ($version >= 8) {
+ $this->moveGBXptr(8); // skip lightmapCacheUID
+
+ if ($version >= 9) {
+ $this->lightmap = $this->readInt8();
+
+ if ($version >= 11) {
+ $this->titleUid = $this->readLookbackString();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } // getStringChunk
+
+ /**
+ * Get Version chunk from GBX header block
+ * @param array $chunksList
+ * List of chunk offsets & sizes
+ */
+ protected function getVersionChunk(array $chunksList)
+ {
+ if (!isset($chunksList['Version'])) return;
+
+ $this->initChunk($chunksList['Version']['off']);
+ $this->headerVersn = $this->readInt32();
+ } // getVersionChunk
+
+ /**
+ * Get Thumbnail/Comments chunk from GBX header block
+ * @param array $chunksList
+ * List of chunk offsets & sizes
+ */
+ protected function getThumbnlChunk(array $chunksList)
+ {
+ if (!isset($chunksList['Thumbnl'])) return;
+
+ $this->initChunk($chunksList['Thumbnl']['off']);
+ $version = $this->readInt32();
+ $this->debugLog('GBX Thumbnail chunk version: ' . $version);
+
+ if ($version == 1) {
+ $thumbSize = $this->readInt32();
+ $this->debugLog(sprintf('GBX Thumbnail size: %d (%.1f KB)',
+ $thumbSize, $thumbSize / 1024));
+
+ $this->moveGBXptr(strlen(''));
+ $this->thumbnail = $this->readData($thumbSize);
+ $this->thumbLen = strlen($this->thumbnail);
+ $this->moveGBXptr(strlen(''));
+
+ $this->moveGBXptr(strlen(''));
+ $this->comment = $this->stripBOM($this->readString());
+ $this->moveGBXptr(strlen(''));
+
+ // return extracted thumbnail image?
+ if ($this->tnImage && $this->thumbLen > 0) {
+ // check for GD/JPEG libraries
+ if (function_exists('imagecreatefromjpeg') &&
+ function_exists('imagecopyresampled')) {
+ // flip thumbnail via temporary file
+ $tmp = tempnam(sys_get_temp_dir(), 'gbxflip');
+ if (@file_put_contents($tmp, $this->thumbnail) !== false) {
+ if ($tn = @imagecreatefromjpeg($tmp)) {
+ $tn = $this->imageFlip($tn, self::IMAGE_FLIP_HORIZONTAL);
+ if (@imagejpeg($tn, $tmp)) {
+ if (($tn = @file_get_contents($tmp)) !== false) {
+ $this->thumbnail = $tn;
+ }
+ }
+ }
+ unlink($tmp);
+ }
+ }
+ } else {
+ $this->thumbnail = '';
+ }
+ }
+ } // getThumbnlChunk
+
+} // class GBXChallMapFetcher
+
+
+/**
+ * @class GBXChallengeFetcher
+ * @brief Wrapper class for backwards compatibility with the old GBXChallengeFetcher
+ * @deprecated Do not use for new development, use GBXChallMapFetcher instead
+ */
+class GBXChallengeFetcher extends GBXChallMapFetcher
+{
+ public $authortm, $goldtm, $silvertm, $bronzetm, $ascore, $azone, $multi, $editor,
+ $pub, $nblaps, $parsedxml, $xmlver, $exever, $exebld, $songfile, $songurl,
+ $modname, $modfile, $modurl;
+
+ /**
+ * Fetches a hell of a lot of data about a GBX challenge
+ *
+ * @param String $filename
+ * The challenge filename (must include full path)
+ * @param Boolean $parsexml
+ * If true, the script also parses the XML block
+ * @param Boolean $tnimage
+ * If true, the script also extracts the thumbnail image; if GD/JPEG
+ * libraries are present, image will be flipped upright, otherwise
+ * it will be in the original upside-down format
+ * Warning: this is binary data in JPEG format, 256x256 pixels for
+ * TMU/TMF or 512x512 pixels for MP
+ * @return GBXChallengeFetcher
+ * If $uid is empty, GBX data couldn't be extracted
+ */
+ public function __construct($filename, $parsexml = false, $tnimage = false)
+ {
+ parent::__construct($parsexml, $tnimage, false);
+
+ try
+ {
+ $this->processFile($filename);
+
+ $this->authortm = $this->authorTime;
+ $this->goldtm = $this->goldTime;
+ $this->silvertm = $this->silverTime;
+ $this->bronzetm = $this->bronzeTime;
+ $this->ascore = $this->authorScore;
+ $this->azone = $this->authorZone;
+ $this->multi = $this->multiLap;
+ $this->editor = $this->simpleEdit;
+ $this->pub = $this->authorBg;
+ $this->nblaps = $this->nbLaps;
+ $this->parsedxml = $this->xmlParsed;
+ $this->xmlver = $this->xmlVer;
+ $this->exever = $this->exeVer;
+ $this->exebld = $this->exeBld;
+ $this->songfile = $this->songFile;
+ $this->songurl = $this->songUrl;
+ $this->modname = $this->modName;
+ $this->modfile = $this->modFile;
+ $this->modurl = $this->modUrl;
+ }
+ catch (Exception $e)
+ {
+ $this->uid = '';
+ }
+ }
+
+} // class GBXChallengeFetcher
+
+
+/**
+ * @class GBXReplayFetcher
+ * @brief The class that fetches all GBX replay info
+ * @note The interface for GBXReplayFetcher has changed compared to the old class,
+ * but there is no wrapper because no third-party XASECO[2] plugins used that
+ */
+class GBXReplayFetcher extends GBXBaseFetcher
+{
+ public $uid, $envir, $author, $replay, $nickname, $login, $titleUid;
+ public $xmlVer, $exeVer, $exeBld, $respawns, $stuntScore, $validable,
+ $cpsCur, $cpsLap;
+
+ /**
+ * Instantiate GBX replay fetcher
+ *
+ * @param Boolean $parsexml
+ * If true, the fetcher also parses the XML block
+ * @param Boolean $debug
+ * If true, the fetcher prints debug logging to stderr
+ * @return GBXReplayFetcher
+ * If GBX data couldn't be extracted, an Exception is thrown with
+ * the error message & code
+ */
+ public function __construct($parsexml = false, $debug = false)
+ {
+ parent::__construct();
+
+ $this->uid = '';
+ $this->envir = '';
+ $this->author = '';
+ $this->replay = 0;
+ $this->nickname = '';
+ $this->login = '';
+ $this->titleUid = '';
+
+ $this->xmlVer = '';
+ $this->exeVer = '';
+ $this->exeBld = '';
+ $this->respawns = 0;
+ $this->stuntScore = 0;
+ $this->validable = false;
+ $this->cpsCur = 0;
+ $this->cpsLap = 0;
+
+ $this->parseXml = (bool)$parsexml;
+ if ((bool)$debug)
+ $this->enableDebug();
+
+ $this->setError('GBX replay error: ');
+ } // __construct
+
+ /**
+ * Process GBX replay file
+ *
+ * @param String $filename
+ * The replay filename
+ */
+ public function processFile($filename)
+ {
+ $this->loadGBXdata((string)$filename);
+
+ $this->processGBX();
+ } // processFile
+
+ /**
+ * Process GBX replay data
+ *
+ * @param String $gbxdata
+ * The replay data
+ */
+ public function processData($gbxdata)
+ {
+ $this->storeGBXdata((string)$gbxdata);
+
+ $this->processGBX();
+ } // processData
+
+ // process GBX data
+ private function processGBX()
+ {
+ // supported replay class IDs
+ $replayclasses = array(
+ self::GBX_AUTOSAVE_TMF,
+ self::GBX_AUTOSAVE_TM,
+ self::GBX_REPLAY_TM,
+ );
+
+ $headerSize = $this->checkHeader($replayclasses);
+ $headerStart = $headerEnd = $this->getGBXptr();
+
+ // desired replay chunk IDs
+ $chunks = array(
+ 0x000 => 'String', // TM, MP
+ 0x001 => 'XML', // TM, MP
+ 0x002 => 'Author', // MP
+ );
+
+ $chunksList = $this->getChunksList($headerSize, $chunks);
+
+ $this->getStringChunk($chunksList);
+ $headerEnd = max($headerEnd, $this->getGBXptr());
+
+ $this->getXMLChunk($chunksList);
+ $headerEnd = max($headerEnd, $this->getGBXptr());
+
+ $this->getAuthorChunk($chunksList);
+ $headerEnd = max($headerEnd, $this->getGBXptr());
+
+ if ($headerSize != $headerEnd - $headerStart)
+ $this->errorOut(sprintf('Header size mismatch: %d <> %d',
+ $headerSize, $headerEnd - $headerStart), 20);
+
+ if ($this->parseXml) {
+ if (isset($this->xmlParsed['HEADER']['VERSION']))
+ $this->xmlVer = $this->xmlParsed['HEADER']['VERSION'];
+ if (isset($this->xmlParsed['HEADER']['EXEVER']))
+ $this->exeVer = $this->xmlParsed['HEADER']['EXEVER'];
+ if (isset($this->xmlParsed['HEADER']['EXEBUILD']))
+ $this->exeBld = $this->xmlParsed['HEADER']['EXEBUILD'];
+ if (isset($this->xmlParsed['TIMES']['RESPAWNS']))
+ $this->respawns = (int)$this->xmlParsed['TIMES']['RESPAWNS'];
+ if (isset($this->xmlParsed['TIMES']['STUNTSCORE']))
+ $this->stuntScore = (int)$this->xmlParsed['TIMES']['STUNTSCORE'];
+ if (isset($this->xmlParsed['TIMES']['VALIDABLE']))
+ $this->validable = (bool)$this->xmlParsed['TIMES']['VALIDABLE'];
+ if (isset($this->xmlParsed['CHECKPOINTS']['CUR']))
+ $this->cpsCur = (int)$this->xmlParsed['CHECKPOINTS']['CUR'];
+ if (isset($this->xmlParsed['CHECKPOINTS']['ONELAP']))
+ $this->cpsLap = (int)$this->xmlParsed['CHECKPOINTS']['ONELAP'];
+ }
+
+ $this->clearGBXdata();
+ } // processGBX
+
+ /**
+ * Get String chunk from GBX header block
+ * @param array $chunksList
+ * List of chunk offsets & sizes
+ */
+ protected function getStringChunk(array $chunksList)
+ {
+ if (!isset($chunksList['String'])) return;
+
+ $this->initChunk($chunksList['String']['off']);
+ $version = $this->readInt32();
+ $this->debugLog('GBX String chunk version: ' . $version);
+
+ if ($version >= 2) {
+ $this->uid = $this->readLookbackString();
+
+ $this->envir = $this->readLookbackString();
+ $this->author = $this->readLookbackString();
+
+ $this->replay = $this->readInt32();
+
+ $this->nickname = $this->stripBOM($this->readString());
+
+ if ($version >= 6) {
+ $this->login = $this->readString();
+
+ if ($version >= 8) {
+ $this->moveGBXptr(1); // skip unknown byte
+
+ $this->titleUid = $this->readLookbackString();
+ }
+ }
+ }
+ } // getStringChunk
+
+} // class GBXReplayFetcher
+
+
+/**
+ * @class GBXPackFetcher
+ * @brief The class that fetches all GBX pack info
+ */
+class GBXPackFetcher extends GBXBaseFetcher
+{
+ public $headerVersn, $flags, $infoMlUrl, $creatDate, $comment, $titleId,
+ $usageSubdir, $buildInfo, $authorUrl, $exeVer, $exeBld, $xmlDate;
+
+ /**
+ * Read Windows FileTime and convert to Unix timestamp
+ * Filetime = 64-bit value with the number of 100-nsec intervals since Jan 1, 1601 (UTC)
+ * Based on http://www.mysqlperformanceblog.com/2007/03/27/integers-in-php-running-with-scissors-and-portability/
+ * @return Unix timestamp, or -1 on error
+ */
+ private function readFiletime()
+ {
+ // Unix epoch (1970-01-01) - Windows epoch (1601-01-01) in 100ns units
+ $EPOCHDIFF = '116444735995904000';
+ $UINT32MAX = '4294967296';
+ $USEC2SEC = 1000000;
+
+ $lo = $this->readInt32();
+ $hi = $this->readInt32();
+
+ // check for 64-bit platform
+ if (PHP_INT_SIZE >= 8) {
+ // use native math
+ if ($lo < 0) $lo += (1 << 32);
+ $date = ($hi << 32) + $lo;
+ $this->debugLog(sprintf('PAK CreationDate source: %016x', $date));
+ if ($date == 0) return -1;
+
+ // convert to Unix timestamp in usec
+ $stamp = ($date - (int)$EPOCHDIFF) / 10;
+ $this->debugLog(sprintf('PAK CreationDate 64-bit: %u.%06u',
+ $stamp / $USEC2SEC, $stamp % $USEC2SEC));
+ return (int)($stamp / $USEC2SEC);
+
+ // check for 32-bit platform
+ } elseif (PHP_INT_SIZE >= 4) {
+ $this->debugLog(sprintf('PAK CreationDate source: %08x%08x', $hi, $lo));
+ if ($lo == 0 && $hi == 0) return -1;
+
+ // workaround signed/unsigned braindamage on x32
+ $lo = sprintf('%u', $lo);
+ $hi = sprintf('%u', $hi);
+
+ // try and use GMP
+ if (function_exists('gmp_mul')) {
+ $date = gmp_add(gmp_mul($hi, $UINT32MAX), $lo);
+ // convert to Unix timestamp in usec
+ $stamp = gmp_div(gmp_sub($date, $EPOCHDIFF), 10);
+ $stamp = gmp_div_qr($stamp, $USEC2SEC);
+ $this->debugLog(sprintf('PAK CreationDate GNU MP: %u.%06u',
+ gmp_strval($stamp[0]), gmp_strval($stamp[1])));
+ return (int)gmp_strval($stamp[0]);
+ }
+
+ // try and use BC Math
+ if (function_exists('bcmul')) {
+ $date = bcadd(bcmul($hi, $UINT32MAX), $lo);
+ // convert to Unix timestamp in usec
+ $stamp = bcdiv(bcsub($date, $EPOCHDIFF), 10, 0);
+ $this->debugLog(sprintf('PAK CreationDate BCMath: %u.%06u',
+ bcdiv($stamp, $USEC2SEC), bcmod($stamp, $USEC2SEC)));
+ return (int)bcdiv($stamp, $USEC2SEC);
+ }
+
+ // compute everything manually
+ $a = substr($hi, 0, -5);
+ $b = substr($hi, -5);
+ // hope that float precision is enough
+ $ac = $a * 42949;
+ $bd = $b * 67296;
+ $adbc = $a * 67296 + $b * 42949;
+ $r4 = substr($bd, -5) + substr($lo, -5);
+ $r3 = substr($bd, 0, -5) + substr($adbc, -5) + substr($lo, 0, -5);
+ $r2 = substr($adbc, 0, -5) + substr($ac, -5);
+ $r1 = substr($ac, 0, -5);
+ while ($r4 >= 100000) { $r4 -= 100000; $r3++; }
+ while ($r3 >= 100000) { $r3 -= 100000; $r2++; }
+ while ($r2 >= 100000) { $r2 -= 100000; $r1++; }
+ $date = ltrim(sprintf('%d%05d%05d%05d', $r1, $r2, $r3, $r4), '0');
+
+ // convert to Unix timestamp in usec
+ $r3 = substr($date, -6) - substr($EPOCHDIFF, -6);
+ $r2 = substr($date, -12, 6) - substr($EPOCHDIFF, -12, 6);
+ $r1 = substr($date, -18, 6) - substr($EPOCHDIFF, -18, 6);
+ if ($r3 < 0) { $r3 += 1000000; $r2--; }
+ if ($r2 < 0) { $r2 += 1000000; $r1--; }
+ $stamp = substr(sprintf('%d%06d%06d', $r1, $r2, $r3), 0, -1);
+ $this->debugLog(sprintf('PAK CreationDate manual: %s.%s',
+ substr($stamp, 0, -6), substr($stamp, -6)));
+ return (int)substr($stamp, 0, -6);
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Instantiate GBX pack fetcher
+ *
+ * @param Boolean $parsexml
+ * If true, the fetcher also parses the XML block
+ * @param Boolean $debug
+ * If true, the fetcher prints debug logging to stderr
+ * @return GBXPackFetcher
+ * If GBX data couldn't be extracted, an Exception is thrown with
+ * the error message & code
+ */
+ public function __construct($parsexml = false, $debug = false)
+ {
+ parent::__construct();
+
+ $this->headerVersn = 0;
+ $this->flags = 0;
+ $this->infoMlUrl = '';
+ $this->creatDate = -1;
+ $this->comment = '';
+ $this->titleId = '';
+ $this->usageSubdir = '';
+ $this->buildInfo = '';
+ $this->authorUrl = '';
+ $this->exeVer = '';
+ $this->exeBld = '';
+ $this->xmlDate = '';
+
+ $this->parseXml = (bool)$parsexml;
+ if ((bool)$debug)
+ $this->enableDebug();
+
+ $this->setError('GBX pack error: ');
+ } // __construct
+
+ /**
+ * Process GBX pack file
+ *
+ * @param String $filename
+ * The pack filename
+ */
+ public function processFile($filename)
+ {
+ $this->loadGBXdata((string)$filename);
+
+ $this->processGBX();
+ } // processFile
+
+ /**
+ * Process GBX pack data
+ *
+ * @param String $gbxdata
+ * The pack data
+ */
+ public function processData($gbxdata)
+ {
+ $this->storeGBXdata((string)$gbxdata);
+
+ $this->processGBX();
+ } // processData
+
+ // process GBX data
+ private function processGBX()
+ {
+ // check magic header
+ $data = $this->readData(8);
+ if ($data != 'NadeoPak')
+ $this->errorOut('No magic NadeoPak header', 5);
+
+ $this->headerVersn = $this->readInt32();
+ if ($this->headerVersn < 6)
+ $this->errorOut(sprintf('Pack version %d not supported', $this->headerVersn), 24);
+
+ $this->moveGBXptr(32); // skip ContentsChecksum
+
+ $this->flags = $this->readInt32();
+
+ if ($this->headerVersn >= 7) {
+ $this->getAuthorFields();
+
+ if ($this->headerVersn < 9) {
+ $this->comment = $this->stripBOM($this->readString());
+
+ $this->moveGBXptr(16); // skip unused uint128
+
+ if ($this->headerVersn >= 8) {
+ $this->buildInfo = $this->readString();
+
+ $this->authorUrl = $this->readString();
+ }
+
+ } else { // >= 9
+ $this->infoMlUrl = $this->readString();
+
+ $this->creatDate = $this->readFiletime();
+
+ $this->comment = $this->stripBOM($this->readString());
+
+ if ($this->headerVersn >= 12) {
+ $this->xml = $this->readString();
+ $this->titleId = $this->readString();
+
+ if ($this->parseXml && $this->xml != '') {
+ $this->parseXMLstring();
+
+ if (isset($this->xmlParsed['HEADER']['EXEVER']))
+ $this->exeVer = $this->xmlParsed['HEADER']['EXEVER'];
+ if (isset($this->xmlParsed['HEADER']['EXEBUILD']))
+ $this->exeBld = $this->xmlParsed['HEADER']['EXEBUILD'];
+ if (isset($this->xmlParsed['IDENT']['CREATIONDATE']))
+ $this->xmlDate = $this->xmlParsed['IDENT']['CREATIONDATE'];
+ }
+ }
+
+ $this->usageSubdir = $this->readString();
+
+ $this->buildInfo = $this->readString();
+
+ $this->moveGBXptr(16); // skip unused uint128
+ // if ($this->headerVersn >= 10) // skip encrypted IncludedPacks
+ }
+ }
+
+ $this->clearGBXdata();
+ } // processGBX
+
+} // class GBXPackFetcher
+?>
diff --git a/xaseco/includes/jfreu.config.php b/xaseco/includes/jfreu.config.php
new file mode 100644
index 0000000..a737c15
--- /dev/null
+++ b/xaseco/includes/jfreu.config.php
@@ -0,0 +1,136 @@
+ paths to config, vip/vip_team & bans files
+ $conf_file = 'plugins/jfreu/jfreu.config.xml';
+ $vips_file = 'plugins/jfreu/jfreu.vips.xml';
+ $bans_file = 'plugins/jfreu/jfreu.bans.xml';
+
+ //-> Server's base name: (ex: '$000Jfreu')
+ // Max. length: 26 chars (incl. colors & tags, and optional "TopXXX")
+ $servername = 'YOUR SERVER NAME';
+ //-> Word between the servername and the limit (usually " Top")
+ $top = ' $449TOP';
+ //-> Change the servername when the limit changes: "Servername TopXXX" (0 = OFF, 1 = ON)
+ $autochangename = 0;
+
+ //-> ranklimit: ranklimiting default state (0 = OFF, 1 = ON)
+ $ranklimit = 0;
+
+ //-> limit: ranklimit default value (when autorank is OFF)
+ $limit = 500000;
+
+ //-> spec ranklimit
+ $hardlimit = 1000000;
+
+ //-> autorank: autorank default state (0 = OFF, 1 = ON)
+ $autorank = 0;
+
+ //-> offset (average + offset = Auto-RankLimit)
+ $offset = 999;
+
+ //-> autorankminplayers (autorank disabled when not enough players)
+ $autorankminplayers = 10;
+ //-> autorankvip: include VIP/unSpec in autorank calculation (0 = OFF, 1 = ON)
+ $autorankvip = 0;
+
+ //-> kick hirank when server is full and new player arrives (0 = OFF, 1 = ON)
+ $kickhirank = 0;
+ //-> maxplayers value for kickhirank (must be less than server's )
+ $maxplayers = 20;
+
+ //-> allow user /unspec vote (0 = OFF, 1 = ON)
+ $unspecvote = 1;
+
+ //-> player join/leave messages
+ $player_join = '{#server}>> {1}: {#highlite}{2}$z$s{#message} Nation: {#highlite}{3}{#message} Ladder: {#highlite}{4}';
+ $player_joins = '{#server}>> {1}: {#highlite}{2}$z$s{#message} Nation: {#highlite}{3}{#message} Ladder: {#highlite}{4}{#message} Server: {#highlite}{5}';
+ $player_left = '{#server}>> {#highlite}{1}$z$s{#message} has left the game. Played: {#highlite}{2}';
+
+ //-> random info messages at the end of the race (0 = OFF, 1 = in chat, 2 = in TMF message window)
+ $infomessages = 1;
+ //-> prefix for info messages
+ $message_start = '$z$s$ff0>> [$f00INFO$ff0] $fff';
+
+ //-> random information messages (if you add a message don't forget to change the number) (999 messages max :-P)
+ // $message1 = 'Jfreu\'s plugin: "http://reload.servegame.com/plugin/"';
+ $message1 = 'Information about and download of this XASECO on ' . XASECO_TMN;
+ $message2 = 'Use "/list" -> "/jukebox ##" to add a track in the jukebox.';
+ $message3 = 'Please don\'t sound your horn throughout the entire track.';
+ $message4 = 'When going AFK, please set your car to Spectator mode.';
+ $message5 = 'Don\'t use Enter to skip intros - instead use Space & Enter';
+ $message6 = 'For player & server info use the "/stats" and "/server" commands.';
+ $message7 = 'Looking for the name of this server? Use the "/server" command.';
+ $message8 = 'Use "/list nofinish" to find tracks you haven\'t completed yet, then /jukebox them!';
+ $message9 = 'Use "/list norank" to find tracks you aren\'t ranked on, then /jukebox them!';
+ $message10 = 'Can you beat the Gold time on all tracks? Use "/list nogold" to find out!';
+ $message11 = 'Can you beat the Author time on all tracks? Use "/list noauthor" to find out!';
+ $message12 = 'Wondering which tracks you haven\'t played recently? Use "/list norecent" to find out!';
+ $message13 = 'Use the "/best" & "/worst" commands to find your best and worst records!';
+ $message14 = 'Use the "/clans" & "/topclans" commands to see clan members and ranks!';
+ $message15 = 'Use the "/ranks" commands to see the server ranks of all online players!';
+ $message16 = 'Who is the most victorious player? Use "/topwins" to find out!';
+ $message17 = 'Who has the most ranked records? Use "/toprecs" to find out!';
+ $message18 = 'Wondering what tracks were played recently? Use the "/history" command.';
+ $message19 = 'Looking for the next better ranked record to beat? Use "/nextrec"!';
+ $message20 = 'Find the difference between your personal best and the track record with the "/diffrec" command!';
+ $message21 = 'Check how many records were driven on the current track with the "/newrecs" command!';
+ $message22 = 'Check how many records, and the 3 best ones, you have with the "/summary" command!';
+ $message23 = 'Who has the most top-3 ranked records? Use "/topsums" to find out!';
+ $message24 = 'Jukeboxed the wrong track? Use "/jukebox drop" to remove it!';
+ $message25 = 'Forgot what someone said? Use "/chatlog" to check the chat history!';
+ $message26 = 'Forgot what someone pm-ed you? Use "/pmlog" to check your PM history!';
+ $message27 = 'Looking for the next better ranked player to beat? Use "/nextrank"!';
+ $message28 = 'Use "/list newest <#>" to find the newest tracks added to the server, then /jukebox them!';
+ $message29 = 'Find the longest and shortest tracks with the "/list longest / shortest" commands!';
+ $message30 = 'Use "/mute" and "/unmute" to mute / unmute other players, and "/mutelist" to list them!';
+ $message31 = 'Wondering when a player was last online? Use "/laston " to find out!';
+ $message32 = 'Looking for any player\'s world stats? Use the "/statsall " command!';
+ $message33 = 'Use checkpoints tracking in Rounds/Team/Cup modes with the "/cps" command!';
+ $message34 = 'Find the TMX info & records for a track with the "/tmxinfo" & "/tmxrecs" commands!';
+ $message35 = 'Looking for the name of the current track\'s song? Use the "/song" command!';
+ $message36 = 'Looking for the name of the current track\'s mod? Use the "/mod" command!';
+ $message37 = 'Use the "/style" command to select your personal window style!';
+ $message38 = 'Use the "/recpanel" command to select your personal records panel!';
+ $message39 = 'Use the "/votepanel" command to select your personal vote panel!';
+ $message40 = 'Find out all about the Dedimania world records system with "/helpdedi"!';
+ $message41 = 'Check out the XASECO[2] site at ' . XASECO_ORG . ' !';
+ global $feature_votes;
+ if ($feature_votes) {
+ $message42 = 'Find out all about the chat-based voting commands with "/helpvote"!';
+ }
+ if (function_exists('send_window_message')) {
+ $message43 = 'Missed a system message? Use "/msglog" to check the message history!';
+ }
+
+ //-> Badwords checking (0 = OFF, 1 = ON)
+ $badwords = 0;
+ //-> Badwords banning (0 = OFF, 1 = ON)
+ $badwordsban = 0;
+ //-> Number of badwords allowed
+ $badwordsnum = 3;
+ //-> Banning period (minutes)
+ $badwordstime = 10;
+
+ //-> Badwords to check for
+ $badwordslist = array(
+ 'putain','ptain','klote','kIote','kanker','kenker',
+ 'arschl','wichs','fick','fikk','salop','siktirgit','gvd',
+ 'hitler','nutte','dick','cock','faitchier','bordel','shit',
+ 'encul','sucks','a.q','conerie','scheise','scheiße','scheis',
+ 'baskasole','cocugu','kodugumun','cazo','hoer','bitch',
+ 'penis','fotze','maul','frese','pizda','gay','fuck','tyfus',
+ 'sugi','cacat','pisat','labagiu','gaozar','muist','orospu',
+ 'pédé','cunt','godve','godfe','kut','kudt','lul','iui');
+
+ //-> novote (auto-cancel votes) (0 = OFF, 1 = ON)
+ $novote = 0;
+?>
diff --git a/xaseco/includes/manialinks.inc.php b/xaseco/includes/manialinks.inc.php
new file mode 100644
index 0000000..dccfe8b
--- /dev/null
+++ b/xaseco/includes/manialinks.inc.php
@@ -0,0 +1,1404 @@
+ block fields & records panel
+global $ml_custom_ui, $ml_records;
+$ml_custom_ui = array('global' => true,
+ 'notice' => true,
+ 'challenge_info' => true,
+ 'net_infos' => true,
+ 'chat' => true,
+ 'checkpoint_list' => true,
+ 'round_scores' => true,
+ 'scoretable' => true
+ );
+$ml_records = array('local' => ' --.--', 'dedi' => ' --.--', 'tmx' => ' --.--');
+
+/**
+ * Displays a single ManiaLink window to a player
+ *
+ * $login : player login to send window to
+ * $header: string
+ * $icon : array( $style, $substyle {, $sizechg} )
+ * $data : array( $line1=array($col1, $col2, ...), $line2=array(...) )
+ * $widths: array( $overal, $col1, $col2, ...)
+ * $button: string
+ *
+ * A $line with one $col will occupy the full window width,
+ * otherwise all $line's must have the same number of columns,
+ * as should $widths (+1 for $overall).
+ * line height=".046" is required minimum to prevent alignment glitches
+ * due to large characters in some cells.
+ * If $colX is an array, it contains the string and the button's action id.
+ */
+function display_manialink($login, $header, $icon, $data, $widths, $button) {
+ global $aseco;
+
+ $player = $aseco->server->players->getPlayer($login);
+ $style = $player->style;
+
+ // check for old TMN-style window
+ if (empty($style)) {
+
+ $tsp = 'B'; // 'F' = solid, '0' = invisible
+ $txt = '333' . $tsp; // dark grey
+ $bgd = 'FFF' . $tsp; // white
+ $spc = 'DDD' . $tsp; // light grey
+
+ // build manialink header
+ $xml = '' .
+ '' . LF .
+ '' . LF;
+
+ // add header
+ $xml .= ' $o' . htmlspecialchars(validateUTF8String($header)) . ' | ' . LF;
+
+ // add spacer
+ $xml .= '' . LF;
+ $xml .= '$ | ' . LF;
+
+ // add lines with optional columns
+ foreach ($data as $line) {
+ $xml .= '';
+ if (!empty($line)) {
+ if (count($line) > 1) {
+ for ($i = 0; $i < count($widths)-1; $i++) {
+ if (is_array($line[$i])) {
+ $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[$i][0])) . ' | ';
+ } else {
+ $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[$i])) . ' | ';
+ }
+ }
+ } else {
+ $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[0])) . ' | ';
+ }
+ } else { // spacer
+ $xml .= '$ | ';
+ }
+ $xml .= '' . LF;
+ }
+
+ // add spacer, button (action "0" = close) & footer
+ $xml .= '$ | ' . LF;
+ $xml .= '$o' . $button . ' | ';
+
+ } else { // TMF-style window
+ $hsize = $style['HEADER'][0]['TEXTSIZE'][0];
+ $bsize = $style['BODY'][0]['TEXTSIZE'][0];
+ $lines = count($data);
+
+ // build manialink header & window
+ $xml = '' .
+ '' . LF;
+
+ // add header and optional icon
+ $xml .= '' . LF;
+ if (is_array($icon)) {
+ $isize = $hsize;
+ if (isset($icon[2]))
+ $isize += $icon[2];
+ $xml .= '' . LF;
+ $xml .= '' . LF;
+ } else {
+ $xml .= '' . LF;
+ }
+ // add body
+ $xml .= '' . LF;
+
+ // add lines with optional columns
+ $xml .= '' . LF;
+ $cnt = 0;
+ foreach ($data as $line) {
+ $cnt++;
+ if (!empty($line)) {
+ if (count($line) > 1) {
+ for ($i = 0; $i < count($widths)-1; $i++) {
+ if (is_array($line[$i])) {
+ $xml .= '' . LF;
+ $xml .= '' . LF;
+ } else {
+ $xml .= '' . LF;
+ }
+ }
+ } else {
+ $xml .= '' . LF;
+ }
+ }
+ }
+
+ // add button (action "0" = close) & footer
+ $xml .= '' . LF;
+ $xml .= '';
+ $xml = str_replace('{#black}', $style['WINDOW'][0]['BLACKCOLOR'][0], $xml);
+ }
+
+ //$aseco->console_text($xml);
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $aseco->formatColors($xml), 0, true));
+} // display_manialink
+
+
+/**
+ * Displays custom TMX track ManiaLink window to a player
+ *
+ * $login : player login to send window to
+ * $header: string
+ * $icon : array( $style, $substyle {, $sizechg} )
+ * $links : array( $image, $square, $page, $download )
+ * $data : array( $line1=array($col1, $col2, ...), $line2=array(...) )
+ * $widths: array( $overal, $col1, $col2, ...)
+ * $button: string
+ *
+ * A $line with one $col will occupy the full window width,
+ * otherwise all $line's must have the same number of columns,
+ * as should $widths (+1 for $overall).
+ * line height=".046" is required minimum to prevent alignment glitches
+ * due to large characters in some cells.
+ */
+function display_manialink_track($login, $header, $icon, $links, $data, $widths, $button) {
+ global $aseco;
+
+ $player = $aseco->server->players->getPlayer($login);
+ $style = $player->style;
+ $square = $links[1];
+
+ // check for old TMN-style window
+ if (empty($style)) {
+
+ $tsp = 'B'; // 'F' = solid, '0' = invisible
+ $txt = '333' . $tsp; // dark grey
+ $bgd = 'FFF' . $tsp; // white
+ $spc = 'DDD' . $tsp; // light grey
+
+ // build manialink header
+ $xml = '' .
+ '' . LF .
+ '' . LF;
+
+ // add header
+ $xml .= ' $o' . htmlspecialchars(validateUTF8String($header)) . ' | ' . LF;
+
+ // add spacers & image
+ $xml .= '' . LF;
+ $xml .= '$ | ' . LF;
+ $xml .= '' . htmlspecialchars($links[0]) . ' | ' . LF;
+ $xml .= '$ | ' . LF;
+
+ // add lines with optional columns
+ foreach ($data as $line) {
+ $xml .= '';
+ if (!empty($line)) {
+ for ($i = 0; $i < count($widths)-1; $i++) {
+ $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[$i])) . ' | ';
+ }
+ } else { // spacer
+ $xml .= '$ | ';
+ }
+ $xml .= '' . LF;
+ }
+
+ // add spacer & links
+ $xml .= '$ | ' . LF;
+ $xml .= '' . LF;
+ $xml .= '$o' . htmlspecialchars($links[2]) . ' | ' .
+ '$o' . htmlspecialchars($links[3]) . ' | ' . LF;
+
+ // add spacer, button (action "0" = close) & footer
+ $xml .= '' . LF;
+ $xml .= '$ | ' . LF;
+ $xml .= '$o' . $button . ' | ';
+
+ } else { // TMF-style window
+ $hsize = $style['HEADER'][0]['TEXTSIZE'][0];
+ $bsize = $style['BODY'][0]['TEXTSIZE'][0];
+ $lines = count($data);
+
+ // build manialink header & window
+ $xml = '' .
+ '' . LF;
+
+ // add header
+ $xml .= '' . LF;
+ if (is_array($icon)) {
+ $isize = $hsize;
+ if (isset($icon[2]))
+ $isize += $icon[2];
+ $xml .= '' . LF;
+ $xml .= '' . LF;
+ } else {
+ $xml .= '' . LF;
+ }
+
+ // add image
+ $xml .= '' . LF;
+ // add body
+ $xml .= '' . LF;
+
+ // add lines with optional columns
+ $xml .= '' . LF;
+ $cnt = 0;
+ foreach ($data as $line) {
+ $cnt++;
+ if (!empty($line)) {
+ for ($i = 0; $i < count($widths)-1; $i++) {
+ $xml .= '' . LF;
+ }
+ }
+ }
+
+ // add links
+ $xml .= '' . LF;
+ $xml .= '' . LF;
+ $xml .= '' . LF;
+
+ // add button (action "0" = close) & footer
+ $xml .= '' . LF;
+ $xml .= '';
+ $xml = str_replace('{#black}', $style['WINDOW'][0]['BLACKCOLOR'][0], $xml);
+ }
+
+ //$aseco->console_text($xml);
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $aseco->formatColors($xml), 0, true));
+} // display_manialink_track
+
+
+/**
+ * Displays a multipage ManiaLink window to a player
+ *
+ * $player: player object to send windows to
+ * ->msgs: array( array( $ptr, $header, $widths, $icon ),
+ * page1: array( $line1=array($col1, $col2, ...), $line2=array(...) ),
+ * 2: array( $line1=array($col1, $col2, ...), $line2=array(...) ),
+ * ... )
+ * $header: string
+ * $widths: array( $overal, $col1, $col2, ...)
+ * $icon : array( $style, $substyle {, $sizechg} )
+ *
+ * A $line with one $col will occupy the full window width,
+ * otherwise all $line's must have the same number of columns,
+ * as should $widths (+1 for $overall).
+ * line height=".046" is required minimum to prevent alignment glitches
+ * due to large characters in some cells.
+ * If $colX is an array, it contains the string and the button's action id.
+ */
+function display_manialink_multi($player) {
+ global $aseco;
+
+ // fake current page event
+ event_manialink($aseco, array(0, $player->login, 1));
+} // display_manialink_multi
+
+// called @ onPlayerManialinkPageAnswer
+// Handles all ManiaLink main system responses,
+// as well as multi-page ManiaLink windows
+// [0]=PlayerUid, [1]=Login, [2]=Answer
+function event_manialink($aseco, $answer) {
+ global $donation_values;
+
+ // leave actions outside -6 - 36 to other handlers
+ if ($answer[2] < -6 || $answer[2] > 36)
+ return;
+
+ // get player
+ $login = $answer[1];
+ $player = $aseco->server->players->getPlayer($login);
+
+ // check player answer
+ switch ($answer[2]) {
+ case 0:
+ // close main pop-up window
+ mainwindow_off($aseco, $login);
+ return;
+
+ // /stats fields
+ case -5:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/active "', $player->login);
+ // /stats field Time Played
+ $command = array();
+ $command['author'] = $player;
+ chat_active($aseco, $command);
+ return;
+ case -6:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/top100 "', $player->login);
+ // /stats field Server Rank
+ $command = array();
+ $command['author'] = $player;
+ chat_top100($aseco, $command);
+ return;
+ case 5:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/toprecs "', $player->login);
+ // /stats field Records
+ $command = array();
+ $command['author'] = $player;
+ chat_toprecs($aseco, $command);
+ return;
+ case 6:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/topwins "', $player->login);
+ // /stats field Races Won
+ $command = array();
+ $command['author'] = $player;
+ chat_topwins($aseco, $command);
+ return;
+
+ // Records panel fields
+ case 7:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/topsums "', $player->login);
+ // records panel PB field
+ $command = array();
+ $command['author'] = $player;
+ chat_topsums($aseco, $command);
+ return;
+ case 8:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/recs "', $player->login);
+ // records panel Local field
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = '';
+ chat_recs($aseco, $command);
+ return;
+ case 9:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/dedirecs "', $player->login);
+ // records panel Dedi field
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = '';
+ if (function_exists('chat_dedirecs')) chat_dedirecs($aseco, $command);
+ return;
+ case 10:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/tmxrecs "', $player->login);
+ // records panel TMX field
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = '';
+ if (function_exists('chat_tmxrecs')) chat_tmxrecs($aseco, $command);
+ return;
+
+ // /list Env fields
+ case 11:
+ // close main window because /list can take a while
+ mainwindow_off($aseco, $login);
+ // log clicked command
+ $aseco->console('player {1} clicked command "/list env:Stadium"', $player->login);
+ // /list Env field Stadium
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'env:Stadium';
+ chat_list($aseco, $command);
+ return;
+ case 12:
+ // close main window because /list can take a while
+ mainwindow_off($aseco, $login);
+ // log clicked command
+ $aseco->console('player {1} clicked command "/list env:Alpine"', $player->login);
+ // /list Env field Alpine
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'env:Alpine';
+ chat_list($aseco, $command);
+ return;
+ case 13:
+ // close main window because /list can take a while
+ mainwindow_off($aseco, $login);
+ // log clicked command
+ $aseco->console('player {1} clicked command "/list env:Bay"', $player->login);
+ // /list Env field Bay
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'env:Bay';
+ chat_list($aseco, $command);
+ return;
+ case 14:
+ // close main window because /list can take a while
+ mainwindow_off($aseco, $login);
+ // log clicked command
+ $aseco->console('player {1} clicked command "/list env:Coast"', $player->login);
+ // /list Env field Coast
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'env:Coast';
+ chat_list($aseco, $command);
+ return;
+ case 15:
+ // close main window because /list can take a while
+ mainwindow_off($aseco, $login);
+ // log clicked command
+ $aseco->console('player {1} clicked command "/list env:Island"', $player->login);
+ // /list Env field Island
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'env:Island';
+ chat_list($aseco, $command);
+ return;
+ case 16:
+ // close main window because /list can take a while
+ mainwindow_off($aseco, $login);
+ // log clicked command
+ $aseco->console('player {1} clicked command "/list env:Rally"', $player->login);
+ // /list Env field Rally
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'env:Rally';
+ chat_list($aseco, $command);
+ return;
+ case 17:
+ // close main window because /list can take a while
+ mainwindow_off($aseco, $login);
+ // log clicked command
+ $aseco->console('player {1} clicked command "/list env:Speed"', $player->login);
+ // /list Env field Speed
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'env:Speed';
+ chat_list($aseco, $command);
+ return;
+
+ // Vote panel buttons/keys
+ case 18:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/y "', $player->login);
+ // /y on chat-based vote
+ $command = array();
+ $command['author'] = $player;
+ chat_y($aseco, $command);
+ return;
+ case 19:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/n " (ignored)', $player->login);
+ // /n on chat-based vote (ignored)
+ return;
+
+ case 20:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin clearjukebox"', $player->login);
+ // close main window
+ mainwindow_off($aseco, $login);
+ // /jukebox display Clear Jukebox button
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'clearjukebox';
+ chat_admin($aseco, $command);
+ return;
+
+ // Admin panel buttons
+ case 21:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin restartmap"', $player->login);
+ // admin panel ClipRewind button
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'restartmap';
+ chat_admin($aseco, $command);
+ return;
+ case 22:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin endround"', $player->login);
+ // admin panel ClipPause button
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'endround';
+ chat_admin($aseco, $command);
+ return;
+ case 23:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin nextmap"', $player->login);
+ // admin panel ClipPlay button
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'nextmap';
+ chat_admin($aseco, $command);
+ return;
+ case 24:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin replaymap"', $player->login);
+ // admin panel Refresh button
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'replaymap';
+ chat_admin($aseco, $command);
+ return;
+ case 25:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin pass"', $player->login);
+ // admin panel ArrowGreen button
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'pass';
+ chat_admin($aseco, $command);
+ return;
+ case 26:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin cancel"', $player->login);
+ // admin panel ArrowRed button
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'cancel';
+ chat_admin($aseco, $command);
+ return;
+ case 27:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players live"', $player->login);
+ // admin panel Buddies button
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'players live';
+ chat_admin($aseco, $command);
+ return;
+
+ // Payment dialog buttons
+ case 28:
+ // log clicked command
+ $aseco->console('player {1} confirmed command "/admin pay"', $player->login);
+ admin_pay($aseco, $player->login, true);
+ return;
+ case 29:
+ // log clicked command
+ $aseco->console('player {1} cancelled command "/admin pay"', $player->login);
+ admin_pay($aseco, $player->login, false);
+ return;
+
+ // Donate panel buttons
+ case 30:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/donate ' . $donation_values[0] . '"', $player->login);
+ // donate panel field 1
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = $donation_values[0];
+ chat_donate($aseco, $command);
+ return;
+ case 31:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/donate ' . $donation_values[1] . '"', $player->login);
+ // donate panel field 2
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = $donation_values[1];
+ chat_donate($aseco, $command);
+ return;
+ case 32:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/donate ' . $donation_values[2] . '"', $player->login);
+ // donate panel field 3
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = $donation_values[2];
+ chat_donate($aseco, $command);
+ return;
+ case 33:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/donate ' . $donation_values[3] . '"', $player->login);
+ // donate panel field 4
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = $donation_values[3];
+ chat_donate($aseco, $command);
+ return;
+ case 34:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/donate ' . $donation_values[4] . '"', $player->login);
+ // donate panel field 5
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = $donation_values[4];
+ chat_donate($aseco, $command);
+ return;
+ case 35:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/donate ' . $donation_values[5] . '"', $player->login);
+ // donate panel field 6
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = $donation_values[5];
+ chat_donate($aseco, $command);
+ return;
+ case 36:
+ // log clicked command
+ $aseco->console('player {1} clicked command "/donate ' . $donation_values[6] . '"', $player->login);
+ // donate panel field 7
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = $donation_values[6];
+ chat_donate($aseco, $command);
+ return;
+ }
+
+ // Handle multi-page ManiaLink windows in all styles
+ // update page pointer
+ $tot = count($player->msgs) - 1;
+ switch ($answer[2]) {
+ case -4: $player->msgs[0][0] = 1; break;
+ case -3: $player->msgs[0][0] -= 5; break;
+ case -2: $player->msgs[0][0] -= 1; break;
+ case 1: break; // stay on current page
+ case 2: $player->msgs[0][0] += 1; break;
+ case 3: $player->msgs[0][0] += 5; break;
+ case 4: $player->msgs[0][0] = $tot; break;
+ }
+
+ // stay within boundaries
+ if ($player->msgs[0][0] < 1)
+ $player->msgs[0][0] = 1;
+ elseif ($player->msgs[0][0] > $tot)
+ $player->msgs[0][0] = $tot;
+
+ // get control variables
+ $ptr = $player->msgs[0][0];
+ $header = $player->msgs[0][1];
+ $widths = $player->msgs[0][2];
+ $icon = $player->msgs[0][3];
+ $style = $player->style;
+
+ // check for old TMN-style window
+ if (empty($style)) {
+
+ $tsp = 'B'; // 'F' = solid, '0' = invisible
+ $txt = '333' . $tsp; // dark grey
+ $bgd = 'FFF' . $tsp; // white
+ $spc = 'DDD' . $tsp; // light grey
+
+ // build manialink header
+ $xml = '' .
+ '' . LF .
+ '' . LF;
+
+ // add header
+ $xml .= ' $o' . htmlspecialchars(validateUTF8String($header)) . ' | ' .
+ '$n(' . $ptr . '/' . $tot . ') | ' . LF;
+
+ // add spacer
+ $xml .= '' . LF;
+ $xml .= '$ | ' . LF;
+
+ // add lines with optional columns
+ foreach ($player->msgs[$ptr] as $line) {
+ $xml .= '';
+ if (!empty($line)) {
+ if (count($line) > 1) {
+ for ($i = 0; $i < count($widths)-1; $i++) {
+ if (is_array($line[$i])) {
+ $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[$i][0])) . ' | ';
+ } else {
+ $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[$i])) . ' | ';
+ }
+ }
+ } else {
+ $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[0])) . ' | ';
+ }
+ } else { // spacer
+ $xml .= '$ | ';
+ }
+ $xml .= '' . LF;
+ }
+
+ // add spacer
+ $xml .= '$ | ' . LF;
+
+ // add button(s)
+ $add5 = ($tot > 5);
+ $butw = ($widths[0] - ($add5 ? 0.22 : 0)) / 3;
+ $xml .= '';
+ // check for preceding page(s), then Prev(5) button(s)
+ if ($ptr > 1) {
+ if ($add5)
+ $xml .= '$oPrev5 | ';
+ $xml .= '$oPrev | ';
+ } else {
+ if ($add5)
+ $xml .= '$ | ';
+ $xml .= '$ | ';
+ }
+ // always a Close button
+ $xml .= '$oClose | ';
+ // check for succeeding page(s), then Next(5) button(s)
+ if ($ptr < $tot) {
+ $xml .= '$oNext | ';
+ if ($add5)
+ $xml .= '$oNext5 | ';
+ } else {
+ $xml .= '$ | ';
+ if ($add5)
+ $xml .= '$ | ';
+ }
+ $xml .= '';
+
+ } else { // TMF-style window
+ $hsize = $style['HEADER'][0]['TEXTSIZE'][0];
+ $bsize = $style['BODY'][0]['TEXTSIZE'][0];
+ $lines = count($player->msgs[$ptr]);
+ // fill up multipage windows
+ if ($tot > 1)
+ $lines = max($lines, count($player->msgs[1]));
+
+ // build manialink header & window
+ $xml = '' .
+ '' . LF;
+
+ // add header
+ $xml .= '' . LF;
+ if (is_array($icon)) {
+ $isize = $hsize;
+ if (isset($icon[2]))
+ $isize += $icon[2];
+ $xml .= '' . LF;
+ $xml .= '' . LF;
+ } else {
+ $xml .= '' . LF;
+ }
+ $xml .= '' . LF;
+ // add body
+ $xml .= '' . LF;
+
+ // add lines with optional columns
+ $xml .= '' . LF;
+ $cnt = 0;
+ foreach ($player->msgs[$ptr] as $line) {
+ $cnt++;
+ if (!empty($line)) {
+ if (count($line) > 1) {
+ for ($i = 0; $i < count($widths)-1; $i++) {
+ if (isset($line[$i])) {
+ // check for action button
+ if (is_array($line[$i])) {
+ $xml .= '' . LF;
+ $xml .= '' . LF;
+ } else {
+ $xml .= '' . LF;
+ }
+ }
+ }
+ } else {
+ $xml .= '' . LF;
+ }
+ }
+ }
+
+ // add button(s) & footer
+ $add5 = ($tot > 5);
+ // check for preceding page(s), then First & Prev(5) button(s)
+ if ($ptr > 1) {
+ $first = '"ArrowFirst" action="-4"';
+ $prev5 = '"ArrowFastPrev" action="-3"';
+ $prev1 = '"ArrowPrev" action="-2"';
+ } else { // first page so dummy buttons
+ $first = '"StarGold"';
+ $prev5 = '"StarGold"';
+ $prev1 = '"StarGold"';
+ }
+ $xml .= '' . LF;
+ if ($add5) {
+ $xml .= '' . LF;
+ }
+ $xml .= '' . LF;
+ // always a Close button
+ $xml .= '' . LF;
+ // check for succeeding page(s), then Next(5) & Last button(s)
+ if ($ptr < $tot) {
+ $next1 = '"ArrowNext" action="2"';
+ $next5 = '"ArrowFastNext" action="3"';
+ $last = '"ArrowLast" action="4"';
+ } else { // last page so dummy buttons
+ $next1 = '"StarGold"';
+ $next5 = '"StarGold"';
+ $last = '"StarGold"';
+ }
+ $xml .= '' . LF;
+ if ($add5) {
+ $xml .= '' . LF;
+ }
+ $xml .= '' . LF;
+
+ $xml .= '';
+ $xml = str_replace('{#black}', $style['WINDOW'][0]['BLACKCOLOR'][0], $xml);
+ }
+
+ //$aseco->console_text($xml);
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $aseco->formatColors($xml), 0, false));
+} // event_manialink
+
+
+/**
+ * Displays a payment dialog
+ *
+ * $login : player login to send dialog to
+ * $server: server name for payment
+ * $label : payment label string
+ */
+function display_payment($aseco, $login, $server, $label) {
+
+ // build manialink
+ $xml = '' .
+ '' .
+ '' .
+ '' .
+ '' .
+ '' .
+ '' .
+ '';
+
+ //$aseco->console_text($xml);
+ // disable dialog once clicked
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, true));
+} // display_payment
+
+/**
+ * Closes main window
+ *
+ * $login: player login to close window for
+ */
+function mainwindow_off($aseco, $login) {
+
+ // close main window
+ $xml = '';
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false));
+} // mainwindow_off
+
+// called @ onEndRace
+function allwindows_off($aseco, $data) {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ // disable all pop-up windows and records & donate panels
+ $xml = '';
+ $aseco->client->query('SendDisplayManialinkPage', $xml, 0, false);
+ }
+} // allwindows_off
+
+
+/**
+ * Displays a CheckPoints panel
+ *
+ * $login: player login(s) to send panel to
+ * $cp : CP number
+ * $diff : color+sign+diff
+ */
+function display_cpspanel($aseco, $login, $cp, $diff) {
+
+ // build manialink
+ $xml = '' .
+ '' .
+ '' .
+ '' .
+ '';
+
+ //$aseco->console_text($xml);
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false));
+} // display_cpspanel
+
+/**
+ * Disables a CheckPoints panel
+ *
+ * $login: player login(s) to disable panel for
+ */
+function cpspanel_off($aseco, $login) {
+
+ $xml = '';
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false));
+} // cpspanel_off
+
+/**
+ * Disables all CheckPoints panels
+ */
+function allcpspanels_off($aseco) {
+
+ $xml = '';
+ $aseco->client->query('SendDisplayManialinkPage', $xml, 0, false);
+} // allcpspanels_off
+
+
+/**
+ * Displays an Admin panel
+ *
+ * $player: player to send panel to
+ */
+function display_admpanel($aseco, $player) {
+
+ // build manialink
+ $xml = $player->panels['admin'];
+
+ //$aseco->console_text($xml);
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $xml, 0, false));
+} // display_admpanel
+
+/**
+ * Disables an Admin panel
+ *
+ * $login: player login to disable panel for
+ */
+function admpanel_off($aseco, $login) {
+
+ $xml = '';
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false));
+} // admpanel_off
+
+
+/**
+ * Displays a Donate panel
+ *
+ * $player : player to send panel to
+ * $coppers: donation values
+ */
+function display_donpanel($aseco, $player, $coppers) {
+
+ // build manialink
+ $xml = $player->panels['donate'];
+ for ($i = 1; $i <= 7; $i++)
+ $xml = str_replace('%COP' . $i . '%', $coppers[$i-1], $xml);
+
+ //$aseco->console_text($xml);
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $xml, 0, false));
+} // display_donpanel
+
+/**
+ * Disables a Donate panel
+ *
+ * $login: player login to disable panel for
+ */
+function donpanel_off($aseco, $login) {
+
+ $xml = '';
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false));
+} // donpanel_off
+
+
+/**
+ * Displays a Records panel
+ *
+ * $player: player to send panel to
+ * $pb : personal best
+ */
+function display_recpanel($aseco, $player, $pb) {
+ global $ml_records;
+
+ // build manialink
+ $xml = str_replace(array('%PB%', '%TMX%', '%LCL%', '%DED%'),
+ array($pb, $ml_records['tmx'], $ml_records['local'], $ml_records['dedi']),
+ $player->panels['records']);
+
+ //$aseco->console_text($xml);
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $xml, 0, false));
+} // display_recpanel
+
+/**
+ * Disables a Records panel
+ *
+ * $login: player login to disable panel for
+ */
+function recpanel_off($aseco, $login) {
+
+ $xml = '';
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false));
+} // recpanel_off
+
+function setRecordsPanel($field, $value) {
+ global $ml_records;
+
+ $ml_records[$field] = $value;
+} // setRecordsPanel
+
+
+/**
+ * Displays a Vote panel
+ *
+ * $player : player to send panel to
+ * $yesstr : string for the Yes button
+ * $nostr : string for the No button
+ * $timeout: timeout for temporary panel (used only by /votepanel list)
+ */
+function display_votepanel($aseco, $player, $yesstr, $nostr, $timeout) {
+
+ // build manialink
+ $xml = str_replace(array('%YES%', '%NO%'),
+ array($yesstr, $nostr), $player->panels['vote']);
+
+ //$aseco->console_text($xml);
+ // disable panel once clicked
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $xml, $timeout, true));
+} // display_votepanel
+
+/**
+ * Disables a Vote panel
+ *
+ * $login: player login to disable panel for
+ */
+function votepanel_off($aseco, $login) {
+
+ $xml = '';
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false));
+} // votepanel_off
+
+/**
+ * Disables all Vote panels
+ */
+function allvotepanels_off($aseco) {
+
+ $xml = '';
+ $aseco->client->addCall('SendDisplayManialinkPage', array($xml, 0, false));
+} // allvotepanels_off
+
+
+/**
+ * Displays the Message window
+ *
+ * $msgs : lines to be displayed
+ * $timeout: timeout for window in msec
+ */
+function display_msgwindow($aseco, $msgs, $timeout) {
+
+ $cnt = count($msgs);
+ $xml = '' . LF .
+ '' . LF;
+ $pos = -1;
+ foreach ($msgs as $msg) {
+ $xml .= '' . LF .
+ $pos -= 2.5;
+ }
+ $xml .= '';
+
+ //$aseco->console_text($xml);
+ $aseco->client->addCall('SendDisplayManialinkPage', array($xml, $timeout, false));
+} // display_msgwindow
+
+/**
+ * Displays the /msglog button
+ *
+ * $login: player login to display button for
+ */
+function display_msglogbutton($aseco, $login) {
+
+ $xml = '' . LF .
+ '' . LF .
+ '';
+
+ //$aseco->console_text($xml);
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false));
+} // display_msglogbutton
+
+
+/**
+ * Displays a Scoreboard Stats panel
+ *
+ * $player : player to send panel to
+ * $rank : server rank
+ * $avg : record average
+ * $recs : records total
+ * $wins : wins total
+ * $play : session play time
+ * $dons : donations total
+ */
+function display_statspanel($aseco, $player, $rank, $avg, $recs, $wins, $play, $dons) {
+
+ // build manialink
+ $xml = str_replace(array('%RANK%', '%AVG%', '%RECS%', '%WINS%', '%PLAY%', '%DONS%'),
+ array($rank, $avg, $recs, $wins, $play, $dons), $aseco->statspanel);
+
+ //$aseco->console_text($xml);
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $xml, 0, false));
+} // display_votepanel
+
+/**
+ * Disables all Scoreboard Stats panels
+ *
+ * called @ onNewChallenge
+ */
+function statspanels_off($aseco, $data) {
+
+ $xml = '';
+ $aseco->client->addCall('SendDisplayManialinkPage', array($xml, 0, false));
+} // statspanels_off
+
+
+// called @ onNewChallenge
+// Disables Automatic Scorepanel at start of track if $auto_scorepanel = off
+function scorepanel_off($aseco, $data) {
+ global $auto_scorepanel;
+
+ if ($aseco->server->getGame() == 'TMF' && !$auto_scorepanel) {
+ setCustomUIField('scoretable', false);
+ // dummy ManiaLink to preserve custom_ui
+ $xml = '' .
+ getCustomUIBlock() . '';
+ $aseco->client->addCall('SendDisplayManialinkPage', array($xml, 0, false));
+ }
+} // scorepanel_off
+
+// called @ onEndRace
+// Enables Automatic Scorepanel at end of track
+function scorepanel_on($aseco, $data) {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ setCustomUIField('scoretable', true);
+ // dummy ManiaLink to preserve custom_ui
+ $xml = '' .
+ getCustomUIBlock() . '';
+ $aseco->client->addCall('SendDisplayManialinkPage', array($xml, 0, false));
+ }
+} // scorepanel_on
+
+// called @ onBeginRound
+// Disables Rounds Finishpanel at start of round if $rounds_finishpanel = off
+function roundspanel_off($aseco) {
+ global $auto_scorepanel, $rounds_finishpanel;
+
+ // check for Rounds/Team/Cup modes
+ if ($aseco->server->gameinfo->mode == Gameinfo::RNDS ||
+ $aseco->server->gameinfo->mode == Gameinfo::TEAM ||
+ $aseco->server->gameinfo->mode == Gameinfo::CUP) {
+ // check whether to disable panel
+ if ($aseco->server->getGame() == 'TMF' && !$rounds_finishpanel) {
+ setCustomUIField('round_scores', false);
+ // dummy ManiaLink to preserve custom_ui
+ $xml = '' .
+ getCustomUIBlock() . '';
+ $aseco->client->addCall('SendDisplayManialinkPage', array($xml, 0, false));
+ }
+ }
+} // roundspanel_off
+
+// called @ onPlayerFinish
+// Enables Rounds Finishpanel at player finish
+function roundspanel_on($aseco, $finish_item) {
+ global $auto_scorepanel, $rounds_finishpanel;
+
+ // check for Rounds/Team/Cup modes
+ if ($aseco->server->gameinfo->mode == Gameinfo::RNDS ||
+ $aseco->server->gameinfo->mode == Gameinfo::TEAM ||
+ $aseco->server->gameinfo->mode == Gameinfo::CUP) {
+ // check whether panel was disabled
+ if ($aseco->server->getGame() == 'TMF' && !$rounds_finishpanel) {
+ setCustomUIField('round_scores', true);
+ // dummy ManiaLink to preserve custom_ui
+ $xml = '' .
+ getCustomUIBlock() . '';
+ $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($finish_item->player->login, $xml, 0, false));
+ }
+ }
+} // roundspanel_on
+
+function setCustomUIField($field, $value) {
+ global $ml_custom_ui;
+
+ $ml_custom_ui[$field] = $value;
+} // setCustomUIField
+
+function getCustomUIBlock() {
+ global $ml_custom_ui;
+
+ return '' .
+ '' .
+ '' .
+ '' .
+ '' .
+ '' .
+ '' .
+ '' .
+ '' .
+ '';
+} // getCustomUIBlock
+?>
diff --git a/xaseco/includes/ogg_comments.inc.php b/xaseco/includes/ogg_comments.inc.php
new file mode 100644
index 0000000..ff43535
--- /dev/null
+++ b/xaseco/includes/ogg_comments.inc.php
@@ -0,0 +1,154 @@
+
+ * Derived from Ogg.class.php v1.3e by Nicolas Ricquemaque
+ * For more info see: http://opensource.grisambre.net/ogg/
+ * Ogg format: http://en.wikipedia.org/wiki/Ogg
+ * Comments : http://www.xiph.org/vorbis/doc/v-comment.html
+ *
+ * v1.1: Improved get_1block URL parsing; added User-Agent to the GET request
+ * v1.0: Initial release
+ */
+
+define('BLOCKSIZE', 512);
+
+class Ogg_Comments {
+
+ public $comments = array();
+
+ /**
+ * Fetches all comments from a .ogg local file or URL
+ *
+ * @param String $path
+ * The .ogg file or URL
+ * @param Boolean $utf8
+ * If true, return fields in UTF-8, otherwise in ASCII/ISO-8859-1
+ * @return Ogg_Comments
+ * If $comments is empty, no .ogg file
+ */
+ public function Ogg_Comments($path, $utf8 = false) {
+
+ // check for local file or URL
+ if (strpos($path, '://') === false) {
+ if (!$fp = @fopen($path, 'rb'))
+ return false;
+ $file = fread($fp, BLOCKSIZE);
+ fclose($fp);
+ if ($file === false)
+ return false;
+ } else {
+ $file = $this->get_1block($path);
+ if ($file === false || $file == -1)
+ return false;
+ }
+
+ // read OGG pages
+ for ($pos = 0; ($pos = strpos($file, 'OggS', $pos)) !== false; $pos++) {
+ // check stream version
+ if (ord($file[$pos+4]) != 0)
+ continue;
+
+ // compute offset of packet after header
+ $offset = $pos + 27 + ord($file[$pos+26]);
+ // check for second (== comments) packet
+ if ($this->read_intle($file, $pos+18) == 1) {
+ // check for vorbis comments
+ if (ord($file[$offset]) != 0x03 ||
+ substr($file, $offset+1, 6) != 'vorbis')
+ continue;
+
+ // read vendor string
+ $offset += 7;
+ $vndlen = $this->read_intle($file, $offset);
+ $this->comments['VENDOR'] = $this->decode_field(substr($file, $offset+4, $vndlen), $utf8);
+ // read comments count
+ $offset += 4 + $vndlen;
+ $cmtcnt = $this->read_intle($file, $offset);
+
+ // read/parse all comment fields
+ $offset += 4;
+ for ($i = 0; $i < $cmtcnt; $i++) {
+ $cmtlen = $this->read_intle($file, $offset);
+ $comment = substr($file, $offset+4, $cmtlen);
+ $offset += 4 + $cmtlen;
+ // store field name=value pair
+ $comment = explode('=', $comment, 2);
+ // check for repeated field & append
+ if (isset($this->comments[strtoupper($comment[0])]))
+ $this->comments[strtoupper($comment[0])] .= ', ' . $this->decode_field($comment[1], $utf8);
+ else
+ $this->comments[strtoupper($comment[0])] = $this->decode_field($comment[1], $utf8);
+ }
+ }
+ // check whether done
+ if (isset($this->comments['VENDOR']))
+ break;
+ }
+ } // Ogg_Comments
+
+ // Read 32-bits Little Endian integer
+ private function read_intle(&$buf, $pos) {
+
+ return (ord($buf[$pos+0]) + (ord($buf[$pos+1]) << 8) +
+ (ord($buf[$pos+2]) << 16) + (ord($buf[$pos+3]) << 24));
+ } // read_intle
+
+ // Decode comment field into specified charset
+ private function decode_field($str, $utf8) {
+
+ if ($utf8)
+ // return UTF8 or encode it in UTF8
+ return ((utf8_encode(utf8_decode($str)) == $str) ?
+ $str : utf8_encode($str));
+ else
+ // decode it to ASCII or return ASCII
+ return ((utf8_encode(utf8_decode($str)) == $str) ?
+ utf8_decode($str) : $str);
+ } // decode_field
+
+ // Simple HTTP Get 1 Block function with timeout
+ // ok: return string || error: return false || timeout: return -1
+ private function get_1block($url) {
+
+ $url = parse_url($url);
+ $port = isset($url['port']) ? $url['port'] : 80;
+ $query = isset($url['query']) ? '?' . $url['query'] : '';
+
+ $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4);
+ if (!$fp)
+ return false;
+
+ $uri = '';
+ foreach (explode('/', $url['path']) as $subpath)
+ $uri .= rawurlencode($subpath) . '/';
+ $uri = substr($uri, 0, strlen($uri)-1); // strip trailing '/'
+
+ fwrite($fp, 'GET ' . $uri . $query . " HTTP/1.0\r\n" .
+ 'Host: ' . $url['host'] . "\r\n" .
+ 'User-Agent: Ogg_Comments (' . PHP_OS . ")\r\n\r\n");
+ stream_set_timeout($fp, 2);
+ $res = '';
+ $info['timed_out'] = false;
+ for ($i = 0; $i < 2; $i++)
+ if (feof($fp) || $info['timed_out']) {
+ break;
+ } else {
+ $res .= fread($fp, BLOCKSIZE);
+ $info = stream_get_meta_data($fp);
+ }
+ fclose($fp);
+
+ if ($info['timed_out']) {
+ return -1;
+ } else {
+ if (substr($res, 9, 3) != '200')
+ return false;
+ $page = explode("\r\n\r\n", $res, 2);
+ return trim($page[1]);
+ }
+ } // get_1block
+} // class Ogg_Comments
+?>
diff --git a/xaseco/includes/rasp.funcs.php b/xaseco/includes/rasp.funcs.php
new file mode 100644
index 0000000..9e974a8
--- /dev/null
+++ b/xaseco/includes/rasp.funcs.php
@@ -0,0 +1,1949 @@
+debug)
+ $aseco->console_text('challenges cache cleared');
+ }
+} // clearChallengesCache
+
+// called @ onTracklistChanged & !TMF
+function clearChallengesCache2($aseco, $event) {
+ global $challengeListCache;
+
+ // clear cache on add/remove/read/juke/unjuke events if not TMF
+ if ($aseco->server->getGame() != 'TMF' &&
+ $event[0] != 'rename' && $event[0] != 'write') {
+ $challengeListCache = array();
+ if ($aseco->debug)
+ $aseco->console_text('challenges cache cleared upon: ' . $event[0]);
+ }
+} // clearChallengesCache2
+
+// called @ onNewChallenge2
+function initChallengesCache($aseco, $challenge) {
+ global $challengeListCache, $reset_cache_start;
+
+ if ($reset_cache_start) {
+ $challengeListCache = array();
+ if ($aseco->debug)
+ $aseco->console_text('challenges cache reset');
+ }
+ getChallengesCache($aseco);
+ if ($aseco->debug)
+ $aseco->console_text('challenges cache inited: ' . count($challengeListCache));
+} // initChallengesCache
+
+function getChallengesCache($aseco) {
+ global $challengeListCache;
+
+ if (empty($challengeListCache)) {
+ if ($aseco->debug)
+ $aseco->console_text('challenges cache loading...');
+ // get new list of all tracks
+ $aseco->client->resetError();
+ $newlist = array();
+ $done = false;
+ $size = 300;
+ $i = 0;
+ while (!$done) {
+ $aseco->client->query('GetChallengeList', $size, $i);
+ $tracks = $aseco->client->getResponse();
+ if (!empty($tracks)) {
+ if ($aseco->client->isError()) {
+ // warning if no tracks found
+ if (empty($newlist))
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetChallengeList - ' . $aseco->client->getErrorMessage() . ' - No tracks found!', E_USER_WARNING);
+ $done = true;
+ break;
+ }
+ foreach ($tracks as $trow) {
+ // obtain various author fields too
+ $trackinfo = getChallengeData($aseco->server->trackdir . $trow['FileName'], false);
+ if ($trackinfo['name'] != 'file not found') {
+ if ($aseco->server->getGame() != 'TMF')
+ $trow['Author'] = $trackinfo['author'];
+ $trow['AuthorTime'] = $trackinfo['authortime'];
+ $trow['AuthorScore'] = $trackinfo['authorscore'];
+ }
+ $trow['Name'] = stripNewlines($trow['Name']);
+ $newlist[$trow['UId']] = $trow;
+ }
+ if (count($tracks) < $size) {
+ // got less than 300 tracks, might as well leave
+ $done = true;
+ } else {
+ $i += $size;
+ }
+ } else {
+ $done = true;
+ }
+ }
+
+ $challengeListCache = $newlist;
+ if ($aseco->debug)
+ $aseco->console_text('challenges cache loaded: ' . count($challengeListCache));
+ }
+
+ return $challengeListCache;
+} // getChallengesCache
+
+
+// calls function get_recs() from chat.records2.php
+function getAllChallenges($player, $wildcard, $env) {
+ global $aseco, $jb_buffer, $maxrecs;
+
+ $player->tracklist = array();
+
+ // get list of ranked records
+ $reclist = get_recs($player->id);
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Tracks On This Server:' . LF . 'Id Rec Name' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+
+ foreach ($newlist as $row) {
+ // check for wildcard, track name or author name
+ if ($wildcard == '*') {
+ $pos = 0;
+ } else {
+ $pos = stripos(stripColors($row['Name']), $wildcard);
+ if ($pos === false) {
+ $pos = stripos($row['Author'], $wildcard);
+ }
+ }
+ // check for any match
+ if ($pos !== false) {
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else
+ $trackname = '{#black}' . $trackname;
+
+ // get corresponding record
+ $pos = isset($reclist[$row['UId']]) ? $reclist[$row['UId']] : 0;
+ $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : ' -- ';
+
+ $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $pos . '. '
+ . $trackname . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17);
+ $head = 'Tracks On This Server:';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Rec', 'Name', 'Author');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $player->msgs[0] = array(1, $head, array(1.39+$extra, 0.12, 0.1, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02));
+ else
+ $player->msgs[0] = array(1, $head, array(1.22+$extra, 0.12, 0.1, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ foreach ($newlist as $row) {
+ // check for wildcard, track name or author name
+ if ($wildcard == '*') {
+ $pos = 0;
+ } else {
+ $pos = stripos(stripColors($row['Name']), $wildcard);
+ if ($pos === false) {
+ $pos = stripos($row['Author'], $wildcard);
+ }
+ }
+ // check for environment
+ if ($env == '*') {
+ $pose = 0;
+ } else {
+ $pose = stripos($row['Environnement'], $env);
+ }
+ // check for any match
+ if ($pos !== false && $pose !== false) {
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ // get corresponding record
+ $pos = isset($reclist[$row['UId']]) ? $reclist[$row['UId']] : 0;
+ $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : '-- ';
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $pos . '.', $trackname, $trackauthor, $trackenv);
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $pos . '.', $trackname, $trackauthor);
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Rec', 'Name', 'Author');
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+} // getAllChallenges
+
+function getChallengesByKarma($player, $karmaval) {
+ global $aseco, $jb_buffer;
+
+ $player->tracklist = array();
+
+ // get list of karma values for all matching tracks
+ $order = ($karmaval <= 0 ? 'ASC' : 'DESC');
+ if ($karmaval == 0) {
+ $sql = '(SELECT uid, SUM(score) AS karma FROM challenges, rs_karma
+ WHERE challenges.id=rs_karma.challengeid
+ GROUP BY uid HAVING karma = 0)
+ UNION
+ (SELECT uid, 0 FROM challenges WHERE id NOT IN
+ (SELECT DISTINCT challengeid FROM rs_karma))
+ ORDER BY karma ' . $order;
+ } else {
+ $sql = 'SELECT uid, SUM(score) AS karma FROM challenges, rs_karma
+ WHERE challenges.id=rs_karma.challengeid
+ GROUP BY uid
+ HAVING karma ' . ($karmaval < 0 ? "<= $karmaval" : ">= $karmaval") . '
+ ORDER BY karma ' . $order;
+ }
+ $result = mysql_query($sql);
+ if (mysql_num_rows($result) == 0) {
+ mysql_free_result($result);
+ return;
+ }
+
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Tracks by Karma (' . $order . '):' . LF . 'Id Karma Name' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else
+ $trackname = '{#black}' . $trackname;
+
+ $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. '
+ . str_pad($dbrow[1], 4, ' ', STR_PAD_LEFT) . ' '
+ . $trackname . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17);
+ $head = 'Tracks by Karma (' . $order . '):';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Karma', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Karma', 'Name', 'Author');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $player->msgs[0] = array(1, $head, array(1.44+$extra, 0.12, 0.15, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02));
+ else
+ $player->msgs[0] = array(1, $head, array(1.27+$extra, 0.12, 0.15, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // format karma
+ $trackkarma = str_pad($dbrow[1], 4, ' ', STR_PAD_LEFT);
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]);
+
+ // add clickable buttons
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900) {
+ $trackname = array($trackname, $tid+100); // action ids
+ $trackauthor = array($trackauthor, -100-$tid);
+ $trackkarma = array($trackkarma, -6000-$tid);
+ }
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackkarma, $trackname, $trackauthor, $trackenv);
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackkarma, $trackname, $trackauthor);
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Karma', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Karma', 'Name', 'Author');
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+
+ mysql_free_result($result);
+} // getChallengesByKarma
+
+function getChallengesNoFinish($player) {
+ global $aseco, $jb_buffer;
+
+ $player->tracklist = array();
+
+ // get list of finished tracks
+ $sql = 'SELECT DISTINCT challengeID FROM rs_times
+ WHERE playerID=' . $player->id . ' ORDER BY challengeID';
+ $result = mysql_query($sql);
+ $finished = array();
+ if (mysql_num_rows($result) > 0) {
+ while ($dbrow = mysql_fetch_array($result))
+ $finished[] = $dbrow[0];
+ }
+ mysql_free_result($result);
+
+ // get list of unfinished tracks
+ // simpler but less efficient query:
+ // $sql = 'SELECT uid FROM challenges WHERE id NOT IN
+ // (SELECT DISTINCT challengeID FROM rs_times, players
+ // WHERE rs_times.playerID=players.id AND players.login=' . quotedString($player->login) . ')';
+ $sql = 'SELECT uid FROM challenges';
+ if (!empty($finished))
+ $sql .= ' WHERE id NOT IN (' . implode(',', $finished) . ')';
+ $result = mysql_query($sql);
+ if (mysql_num_rows($result) == 0) {
+ mysql_free_result($result);
+ return;
+ }
+
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Tracks You Haven\'t Finished:' . LF . 'Id Name' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else
+ $trackname = '{#black}' . $trackname;
+
+ $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. '
+ . $trackname . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17);
+ $head = 'Tracks You Haven\'t Finished:';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Name', 'Author');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $player->msgs[0] = array(1, $head, array(1.29+$extra, 0.12, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02));
+ else
+ $player->msgs[0] = array(1, $head, array(1.12+$extra, 0.12, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor, $trackenv);
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor);
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Name', 'Author');
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+
+ mysql_free_result($result);
+} // getChallengesNoFinish
+
+function getChallengesNoRank($player) {
+ global $aseco, $jb_buffer, $maxrecs;
+
+ $player->tracklist = array();
+
+ // get list of finished tracks
+ $sql = 'SELECT DISTINCT challengeID FROM rs_times
+ WHERE playerID=' . $player->id . ' ORDER BY challengeID';
+ $result = mysql_query($sql);
+ $finished = array();
+ if (mysql_num_rows($result) > 0) {
+ while ($dbrow = mysql_fetch_array($result))
+ $finished[] = $dbrow[0];
+ }
+ mysql_free_result($result);
+
+ // get list of finished tracks
+ // simpler but less efficient query:
+ // $sql = 'SELECT id,uid FROM challenges WHERE id IN
+ // (SELECT DISTINCT challengeID FROM rs_times, players
+ // WHERE rs_times.playerID=players.id AND players.login=' . quotedString($player->login) . ')';
+ $sql = 'SELECT id,uid FROM challenges WHERE id ';
+ if (!empty($finished))
+ $sql .= 'IN (' . implode(',', $finished) . ')';
+ else
+ $sql .= '= 0'; // empty list
+ $result = mysql_query($sql);
+ if (mysql_num_rows($result) == 0) {
+ mysql_free_result($result);
+ return;
+ }
+
+ $order = ($aseco->server->gameinfo->mode == Gameinfo::STNT ? 'DESC' : 'ASC');
+ $unranked = array();
+ $i = 0;
+ // check if player not in top $maxrecs on each track
+ while ($dbrow = mysql_fetch_array($result)) {
+ // more efficient but unsupported query: :(
+ // $sql2 = 'SELECT id FROM players WHERE (id=' . $player->id . ') AND (id NOT IN
+ // (SELECT playerid FROM records WHERE challengeid=' . $dbrow[0] . ' ORDER by score, date LIMIT ' . $maxrecs . '))';
+ $sql2 = 'SELECT playerid FROM records
+ WHERE challengeid=' . $dbrow[0] . '
+ ORDER by score ' . $order . ', date ASC LIMIT ' . $maxrecs;
+ $result2 = mysql_query($sql2);
+ $found = false;
+ if (mysql_num_rows($result2) > 0) {
+ while ($plrow = mysql_fetch_array($result2)) {
+ if ($player->id == $plrow[0]) {
+ $found = true;
+ break;
+ }
+ }
+ }
+ if (!$found) {
+ $unranked[$i++] = $dbrow[1];
+ }
+ mysql_free_result($result2);
+ }
+ if (empty($unranked)) {
+ mysql_free_result($result);
+ return;
+ }
+
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Tracks You Have No Rank On:' . LF . 'Id Name' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+
+ for ($i = 0; $i < count($unranked); $i++) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($unranked[$i], $newlist)) {
+ $row = $newlist[$unranked[$i]];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else
+ $trackname = '{#black}' . $trackname;
+
+ $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. '
+ . $trackname . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17);
+ $head = 'Tracks You Have No Rank On:';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Name', 'Author');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $player->msgs[0] = array(1, $head, array(1.29+$extra, 0.12, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02));
+ else
+ $player->msgs[0] = array(1, $head, array(1.12+$extra, 0.12, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ for ($i = 0; $i < count($unranked); $i++) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($unranked[$i], $newlist)) {
+ $row = $newlist[$unranked[$i]];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor, $trackenv);
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor);
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Name', 'Author');
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg))
+ $player->msgs[] = $msg;
+ }
+
+ mysql_free_result($result);
+} // getChallengesNoRank
+
+function getChallengesNoGold($player) {
+ global $aseco, $jb_buffer;
+
+ $player->tracklist = array();
+
+ // check for Stunts mode
+ if ($aseco->server->gameinfo->mode != Gameinfo::STNT) {
+
+ // get list of finished tracks with their best (minimum) times
+ $sql = 'SELECT DISTINCT c.uid,t1.score FROM rs_times t1, challenges c
+ WHERE (playerID=' . $player->id . ' AND t1.challengeID=c.id AND
+ score=(SELECT MIN(t2.score) FROM rs_times t2
+ WHERE playerID=' . $player->id . ' AND t1.challengeID=t2.challengeID))';
+ $result = mysql_query($sql);
+ if (mysql_num_rows($result) == 0) {
+ mysql_free_result($result);
+ return;
+ }
+
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Tracks You Didn\'t Beat Gold Time On:' . LF . 'Id Name $n(+Time)$m' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // does best time beat track's Gold time?
+ if ($dbrow[1] > $row['GoldTime']) {
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else
+ $trackname = '{#black}' . $trackname;
+
+ // compute difference to Gold time
+ $diff = $dbrow[1] - $row['GoldTime'];
+ $sec = floor($diff/1000);
+ $hun = ($diff - ($sec * 1000)) / 10;
+
+ $msg .= str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $trackname
+ . ' $z$n(+' . sprintf("%d.%02d", $sec, $hun) . ')$m' . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17);
+ $head = 'Tracks You Didn\'t Beat Gold Time On:';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env', 'Time');
+ else
+ $msg[] = array('Id', 'Name', 'Author', 'Time');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $player->msgs[0] = array(1, $head, array(1.42+$extra, 0.12, 0.6+$extra, 0.4, 0.15, 0.15), array('Icons128x128_1', 'NewTrack', 0.02));
+ else
+ $player->msgs[0] = array(1, $head, array(1.27+$extra, 0.12, 0.6+$extra, 0.4, 0.15), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // does best time beat track's Gold time?
+ if ($dbrow[1] > $row['GoldTime']) {
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ // compute difference to Gold time
+ $diff = $dbrow[1] - $row['GoldTime'];
+ $sec = floor($diff/1000);
+ $hun = ($diff - ($sec * 1000)) / 10;
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor, $trackenv,
+ '+' . sprintf("%d.%02d", $sec, $hun));
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor,
+ '+' . sprintf("%d.%02d", $sec, $hun));
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env', 'Time');
+ else
+ $msg[] = array('Id', 'Name', 'Author', 'Time');
+ }
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+
+ } else { // Stunts mode
+
+ // get list of finished tracks with their best (maximum) scores
+ $sql = 'SELECT DISTINCT c.uid,t1.score FROM rs_times t1, challenges c
+ WHERE (playerID=' . $player->id . ' AND t1.challengeID=c.id AND
+ score=(SELECT MAX(t2.score) FROM rs_times t2
+ WHERE playerID=' . $player->id . ' AND t1.challengeID=t2.challengeID))';
+ $result = mysql_query($sql);
+ if (mysql_num_rows($result) == 0) {
+ mysql_free_result($result);
+ return;
+ }
+
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ // only in TMUF anyway
+ {
+ $head = 'Tracks You Didn\'t Beat Gold Score On:';
+ $msg = array();
+ $msg[] = array('Id', 'Name', 'Author', 'Env', 'Score');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ $player->msgs[0] = array(1, $head, array(1.42+$extra, 0.12, 0.6+$extra, 0.4, 0.15, 0.15), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // does best score beat track's Gold score?
+ if ($dbrow[1] < $row['GoldTime']) {
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ // compute difference to Gold score
+ $diff = $row['GoldTime'] - $dbrow[1];
+
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor, $trackenv,
+ '-' . $diff);
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ $msg[] = array('Id', 'Name', 'Author', 'Env', 'Score');
+ }
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+ }
+
+ mysql_free_result($result);
+} // getChallengesNoGold
+
+function getChallengesNoAuthor($player) {
+ global $aseco, $jb_buffer;
+
+ $player->tracklist = array();
+
+ // check for Stunts mode
+ if ($aseco->server->gameinfo->mode != Gameinfo::STNT) {
+
+ // get list of finished tracks with their best (minimum) times
+ $sql = 'SELECT DISTINCT c.uid,t1.score FROM rs_times t1, challenges c
+ WHERE (playerID=' . $player->id . ' AND t1.challengeID=c.id AND
+ score=(SELECT MIN(t2.score) FROM rs_times t2
+ WHERE playerID=' . $player->id . ' AND t1.challengeID=t2.challengeID))';
+ $result = mysql_query($sql);
+ if (mysql_num_rows($result) == 0) {
+ mysql_free_result($result);
+ return;
+ }
+
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Tracks You Didn\'t Beat Author Time On:' . LF . 'Id Name $n(+Time)$m' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // does best time beat track's Author time?
+ if ($dbrow[1] > $row['AuthorTime']) {
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else
+ $trackname = '{#black}' . $trackname;
+
+ // compute difference to Author time
+ $diff = $dbrow[1] - $row['AuthorTime'];
+ $sec = floor($diff/1000);
+ $hun = ($diff - ($sec * 1000)) / 10;
+
+ $msg .= str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $trackname
+ . ' $z$n(+' . sprintf("%d.%02d", $sec, $hun) . ')$m' . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17);
+ $head = 'Tracks You Didn\'t Beat Author Time On:';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env', 'Time');
+ else
+ $msg[] = array('Id', 'Name', 'Author', 'Time');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $player->msgs[0] = array(1, $head, array(1.42+$extra, 0.12, 0.6+$extra, 0.4, 0.15, 0.15), array('Icons128x128_1', 'NewTrack', 0.02));
+ else
+ $player->msgs[0] = array(1, $head, array(1.27+$extra, 0.12, 0.6+$extra, 0.4, 0.15), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // does best time beat track's Author time?
+ if ($dbrow[1] > $row['AuthorTime']) {
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ // compute difference to Author time
+ $diff = $dbrow[1] - $row['AuthorTime'];
+ $sec = floor($diff/1000);
+ $hun = ($diff - ($sec * 1000)) / 10;
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor, $trackenv,
+ '+' . sprintf("%d.%02d", $sec, $hun));
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor,
+ '+' . sprintf("%d.%02d", $sec, $hun));
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env', 'Time');
+ else
+ $msg[] = array('Id', 'Name', 'Author', 'Time');
+ }
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+
+ } else { // Stunts mode
+
+ // get list of finished tracks with their best (maximum) scores
+ $sql = 'SELECT DISTINCT c.uid,t1.score FROM rs_times t1, challenges c
+ WHERE (playerID=' . $player->id . ' AND t1.challengeID=c.id AND
+ score=(SELECT MAX(t2.score) FROM rs_times t2
+ WHERE playerID=' . $player->id . ' AND t1.challengeID=t2.challengeID))';
+ $result = mysql_query($sql);
+ if (mysql_num_rows($result) == 0) {
+ mysql_free_result($result);
+ return;
+ }
+
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ // only in TMUF anyway
+ {
+ $head = 'Tracks You Didn\'t Beat Author Score On:';
+ $msg = array();
+ $msg[] = array('Id', 'Name', 'Author', 'Env', 'Score');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ $player->msgs[0] = array(1, $head, array(1.42+$extra, 0.12, 0.6+$extra, 0.4, 0.15, 0.15), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // does best score beat track's Author score?
+ if ($dbrow[1] < $row['AuthorScore']) {
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ // compute difference to Author score
+ $diff = $row['AuthorScore'] - $dbrow[1];
+
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor, $trackenv,
+ '-' . $diff);
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ $msg[] = array('Id', 'Name', 'Author', 'Env', 'Score');
+ }
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+ }
+
+ mysql_free_result($result);
+} // getChallengesNoAuthor
+
+// calls function get_recs() from chat.records2.php
+function getChallengesNoRecent($player) {
+ global $aseco, $jb_buffer, $maxrecs;
+
+ $player->tracklist = array();
+
+ // get list of finished tracks with their most recent (maximum) dates
+ $sql = 'SELECT DISTINCT c.uid,t1.date FROM rs_times t1, challenges c
+ WHERE (playerID=' . $player->id . ' AND t1.challengeID=c.id AND
+ date=(SELECT MAX(t2.date) FROM rs_times t2
+ WHERE playerID=' . $player->id . ' AND t1.challengeID=t2.challengeID))
+ ORDER BY t1.date';
+ $result = mysql_query($sql);
+ if (mysql_num_rows($result) == 0) {
+ mysql_free_result($result);
+ return;
+ }
+
+ // get list of ranked records
+ $reclist = get_recs($player->id);
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Tracks You Didn\'t Play Recently:' . LF . 'Id Rec Name $n(Date)$m' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else
+ $trackname = '{#black}' . $trackname;
+
+ // get corresponding record
+ $pos = isset($reclist[$dbrow[0]]) ? $reclist[$dbrow[0]] : 0;
+ $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : ' -- ';
+
+ $msg .= str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $pos . '. '
+ . $trackname . ' $z$n(' . date('Y/m/d', $dbrow[1]) . ')$m' . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17);
+ $head = 'Tracks You Didn\'t Play Recently:';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env', 'Date');
+ else
+ $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Date');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $player->msgs[0] = array(1, $head, array(1.58+$extra, 0.12, 0.1, 0.6+$extra, 0.4, 0.15, 0.21), array('Icons128x128_1', 'NewTrack', 0.02));
+ else
+ $player->msgs[0] = array(1, $head, array(1.43+$extra, 0.12, 0.1, 0.6+$extra, 0.4, 0.21), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ // get corresponding record
+ $pos = isset($reclist[$dbrow[0]]) ? $reclist[$dbrow[0]] : 0;
+ $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : '-- ';
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $pos . '.', $trackname, $trackauthor, $trackenv,
+ date('Y/m/d', $dbrow[1]));
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $pos . '.', $trackname, $trackauthor,
+ date('Y/m/d', $dbrow[1]));
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env', 'Date');
+ else
+ $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Date');
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+
+ mysql_free_result($result);
+} // getChallengesNoRecent
+
+function getChallengesByLength($player, $order) {
+ global $aseco, $jb_buffer;
+
+ $player->tracklist = array();
+
+ // if Stunts mode, bail out immediately
+ if ($aseco->server->gameinfo->mode == Gameinfo::STNT) return;
+
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ // build list of author times
+ $times = array();
+ foreach ($newlist as $uid => $row)
+ $times[$uid] = $row['AuthorTime'];
+
+ // sort for shortest or longest author times
+ $order ? asort($times) : arsort($times);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = ($order ? 'Shortest' : 'Longest') . ' Tracks On This Server:' . LF . 'Id Name $n(Author Time)$m' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+
+ foreach ($times as $uid => $time) {
+ $row = $newlist[$uid];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else
+ $trackname = '{#black}' . $trackname;
+
+ $msg .= str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $trackname
+ . ' $z$n(' . formatTime($time) . ')$m' . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17);
+ $head = ($order ? 'Shortest' : 'Longest') . ' Tracks On This Server:';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env', 'AuthTime');
+ else
+ $msg[] = array('Id', 'Name', 'Author', 'AuthTime');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $player->msgs[0] = array(1, $head, array(1.44+$extra, 0.12, 0.6+$extra, 0.4, 0.15, 0.17), array('Icons128x128_1', 'NewTrack', 0.02));
+ else
+ $player->msgs[0] = array(1, $head, array(1.29+$extra, 0.12, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ foreach ($times as $uid => $time) {
+ $row = $newlist[$uid];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor, $trackenv, formatTime($time));
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor, formatTime($time));
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env', 'AuthTime');
+ else
+ $msg[] = array('Id', 'Name', 'Author', 'AuthTime');
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+} // getChallengesByLength
+
+function getChallengesByAdd($player, $order, $count) {
+ global $aseco, $jb_buffer;
+
+ $player->tracklist = array();
+
+ // get list of tracks in reverse order of addition
+ $sql = 'SELECT uid FROM challenges
+ ORDER BY id ' . ($order ? 'DESC' : 'ASC');
+ $result = mysql_query($sql);
+ if (mysql_num_rows($result) == 0) {
+ mysql_free_result($result);
+ return;
+ }
+
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ $tcnt = 0;
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = ($order ? 'Newest' : 'Oldest') . ' Tracks On This Server:' . LF . 'Id Name' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else
+ $trackname = '{#black}' . $trackname;
+
+ $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. '
+ . $trackname . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ // check if we have enough tracks already
+ if (++$tcnt == $count) break;
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17);
+ $head = ($order ? 'Newest' : 'Oldest') . ' Tracks On This Server:';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Name', 'Author');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $player->msgs[0] = array(1, $head, array(1.29+$extra, 0.12, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02));
+ else
+ $player->msgs[0] = array(1, $head, array(1.12+$extra, 0.12, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ while ($dbrow = mysql_fetch_array($result)) {
+ // does the uid exist in the current server track list?
+ if (array_key_exists($dbrow[0], $newlist)) {
+ $row = $newlist[$dbrow[0]];
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor, $trackenv);
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $trackname, $trackauthor);
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Name', 'Author');
+ }
+ // check if we have enough tracks already
+ if (++$tcnt == $count) break;
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+
+ mysql_free_result($result);
+} // getChallengesByAdd
+
+function getChallengesNoVote($player) {
+ global $aseco, $jb_buffer, $maxrecs;
+
+ $player->tracklist = array();
+
+ // get list of ranked records
+ $reclist = get_recs($player->id);
+
+ // get new/cached list of tracks
+ $newlist = getChallengesCache($aseco);
+
+ // get list of voted tracks and remove those
+ $sql = 'SELECT uid FROM challenges c, rs_karma k
+ WHERE c.id=k.challengeID AND k.playerID=' . $player->id;
+ $result = mysql_query($sql);
+ if (mysql_num_rows($result) > 0) {
+ while ($dbrow = mysql_fetch_array($result))
+ unset($newlist[$dbrow[0]]);
+ }
+ mysql_free_result($result);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Tracks You Didn\'t Vote For:' . LF . 'Id Rec Name' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ $player->msgs[0] = 1;
+
+ foreach ($newlist as $row) {
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else
+ $trackname = '{#black}' . $trackname;
+
+ // get corresponding record
+ $pos = isset($reclist[$row['UId']]) ? $reclist[$row['UId']] : 0;
+ $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : ' -- ';
+
+ $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $pos . '. '
+ . $trackname . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $player->msgs[] = $aseco->formatColors($head . $msg);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17);
+ $head = 'Tracks You Didn\'t Vote For:';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Rec', 'Name', 'Author');
+ $tid = 1;
+ $lines = 0;
+ $player->msgs = array();
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $player->msgs[0] = array(1, $head, array(1.39+$extra, 0.12, 0.1, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02));
+ else
+ $player->msgs[0] = array(1, $head, array(1.22+$extra, 0.12, 0.1, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02));
+
+ foreach ($newlist as $row) {
+ // store track in player object for jukeboxing
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['author'] = $row['Author'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $player->tracklist[] = $trkarr;
+
+ // format track name
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+ // grey out if in history
+ if (in_array($row['UId'], $jb_buffer))
+ $trackname = '{#grey}' . stripColors($trackname);
+ else {
+ $trackname = '{#black}' . $trackname;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackname = array($trackname, $tid+100); // action id
+ }
+ // format author name
+ $trackauthor = $row['Author'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $tid <= 1900)
+ $trackauthor = array($trackauthor, -100-$tid); // action id
+ // format env name
+ $trackenv = $row['Environnement'];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'])
+ $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id
+
+ // get corresponding record
+ $pos = isset($reclist[$row['UId']]) ? $reclist[$row['UId']] : 0;
+ $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : '-- ';
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $pos . '.', $trackname, $trackauthor, $trackenv);
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ $pos . '.', $trackname, $trackauthor);
+ $tid++;
+ if (++$lines > 14) {
+ $player->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env');
+ else
+ $msg[] = array('Id', 'Rec', 'Name', 'Author');
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $player->msgs[] = $msg;
+ }
+} // getChallengesNoVote
+
+
+// called @ onPlayerServerMessageAnswer
+// handles all pop-up window responses
+// [0]=PlayerUid, [1]=Login, [2]=Answer
+function event_multi_message($aseco, $answer) {
+
+ $login = $answer[1];
+ $player = $aseco->server->players->getPlayer($login);
+ $cnt = count($player->msgs);
+
+ // check for 'Next' response
+ if ($answer[2] == 2 && $cnt > 0) {
+ $player->msgs[0]++; // primed at 1 in the $player->msgs functions
+ if (($player->msgs[0] + 1) < $cnt) { // multiple pages to display
+ $btn1 = 'Close';
+ $btn2 = 'Next';
+ $msg = $player->msgs[$player->msgs[0]];
+ } elseif (($player->msgs[0] + 1) == $cnt) { // last page to display
+ $btn1 = 'OK';
+ $btn2 = '';
+ $msg = $player->msgs[$player->msgs[0]];
+ } else { // all done
+ return;
+ }
+ // display the next page
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $msg, $btn1, $btn2, 0);
+ // 'Close' response
+ } else {
+ }
+} // event_multi_message
+
+function getChallengeData($filename, $rtnvotes) {
+ global $aseco, $tmxvoteratio;
+
+ $ret = array();
+ if (!file_exists($filename)) {
+ $ret['name'] = 'file not found';
+ $ret['votes'] = 500;
+ return $ret;
+ }
+ // check whether votes are needed
+ if ($rtnvotes) {
+ $ret['votes'] = required_votes($tmxvoteratio); // from plugin.rasp_votes.php
+ if ($aseco->debug) {
+ $ret['votes'] = 1;
+ }
+ } else {
+ $ret['votes'] = 1;
+ }
+
+ $gbx = new GBXChallMapFetcher();
+ try
+ {
+ $gbx->processFile($filename);
+
+ $ret['uid'] = $gbx->uid;
+ $ret['name'] = stripNewlines($gbx->name);
+ $ret['author'] = $gbx->author;
+ $ret['environment'] = $gbx->envir;
+ $ret['authortime'] = $gbx->authorTime;
+ $ret['authorscore'] = $gbx->authorScore;
+ $ret['coppers'] = $gbx->cost;
+ }
+ catch (Exception $e)
+ {
+ $ret['votes'] = 500;
+ $ret['name'] = $e->getMessage();
+ }
+ return $ret;
+} // getChallengeData
+?>
diff --git a/xaseco/includes/tmndatafetcher.inc.php b/xaseco/includes/tmndatafetcher.inc.php
new file mode 100644
index 0000000..691e98d
--- /dev/null
+++ b/xaseco/includes/tmndatafetcher.inc.php
@@ -0,0 +1,290 @@
+
+ *
+ * v1.8: Improved error reporting via $error; fixed file check in extendedInfo
+ * processing; added magic __set_state function to support var_export()
+ * v1.7: Improve handling of empty API responses
+ * v1.6: Fixed $teamrank type into int; added User-Agent to the GET request
+ * v1.5: Tweaked initial get_file return value check
+ * v1.4: Fixed get_file return value checks; fixed $nationrank check
+ * v1.3: Optimized get_file URL parsing
+ * v1.2: Added get_file function to handle master server timeouts
+ * v1.1: General code cleanup; added more comments; added $lastmatch,
+ * $totalplayers, $nationplayers, $nationpos, $nationpoints,
+ * $totalnations, $servernick, $serverdesc, $servernation;
+ * renamed $actualserver to $serverlogin
+ * v1.0: Initial release
+ */
+class TMNDataFetcher {
+
+ public $version, $extended, $error,
+ $login, $nickname, $worldrank, $totalplayers,
+ $points, $lastmatch, $wins, $losses, $draws,
+ $stars, $stardays, $teamname, $teamrank, $totalteams,
+ $nation, $nationrank, $nationplayers,
+ $nationpos, $nationpoints, $totalnations,
+ $online, $serverlogin, $servernick, $serverdesc, $servernation;
+
+ /**
+ * Fetches a hell of a lot of data about a TMN login
+ *
+ * @param String $login
+ * The TMN login to search for
+ * @param Boolean $extendedInfo
+ * If true, the script also searches for the server that the
+ * player is on at the moment (also determines online-state)
+ * @return TMNDataFetcher
+ * If $nickname is empty, login was not found
+ */
+ function TMNDataFetcher($login, $extendedInfo) {
+
+ $this->version = '0.1.7.9';
+ $this->error = '';
+ $this->extended = $extendedInfo;
+ $this->login = strtolower($login);
+ $this->getData();
+ } // TMNDataFetcher
+
+ public static function __set_state($import) {
+
+ $tmn = new TMNDataFetcher('', true);
+
+ $tmn->version = $import['version'];
+ $tmn->extended = $import['extended'];
+ $tmn->error = '';
+ $tmn->login = $import['login'];
+ $tmn->nickname = $import['nickname'];
+ $tmn->worldrank = $import['worldrank'];
+ $tmn->totalplayers = $import['totalplayers'];
+ $tmn->points = $import['points'];
+ $tmn->lastmatch = $import['lastmatch'];
+ $tmn->wins = $import['wins'];
+ $tmn->losses = $import['losses'];
+ $tmn->draws = $import['draws'];
+ $tmn->stars = $import['stars'];
+ $tmn->stardays = $import['stardays'];
+ $tmn->teamname = $import['teamname'];
+ $tmn->teamrank = $import['teamrank'];
+ $tmn->totalteams = $import['totalteams'];
+ $tmn->nation = $import['nation'];
+ $tmn->nationrank = $import['nationrank'];
+ $tmn->nationplayers = $import['nationplayers'];
+ $tmn->nationpos = $import['nationpos'];
+ $tmn->nationpoints = $import['nationpoints'];
+ $tmn->totalnations = $import['totalnations'];
+ $tmn->online = $import['online'];
+ $tmn->serverlogin = $import['serverlogin'];
+ $tmn->servernick = $import['servernick'];
+ $tmn->serverdesc = $import['serverdesc'];
+ $tmn->servernation = $import['servernation'];
+
+ return $tmn;
+ } // __set_state
+
+ private function getData() {
+
+ $url = 'http://game.trackmanianations.com/online_game/getplayerinfos.php?ver=' . $this->version . '&lang=en&login=' . $this->login;
+ $line = $this->get_file($url);
+ if ($line === false) {
+ $this->error = 'Connection or response error on ' . $url;
+ return;
+ } else if ($line === -1) {
+ $this->error = 'Timed out while reading data from ' . $url;
+ return;
+ } else if ($line == '' || strpos($line, '
') !== false) {
+ $this->error = 'No data returned from ' . $url;
+ return;
+ }
+
+ $array = explode(';', $line);
+ if (!isset($array[6])) {
+ $this->error = 'Cannot parse data returned data from ' . $url;
+ return;
+ }
+ // 0 = $array[0];
+ // login = $array[1];
+ $this->nickname = urldecode($array[2]);
+ $this->nation = $array[3];
+ // empty = $array[4];
+ $this->stars = $array[5];
+ $this->stardays = $array[6];
+
+ $url = 'http://ladder.trackmanianations.com/ladder/getstats.php?ver=' . $this->version . '&laddertype=g&login=' . $this->login;
+ $line = $this->get_file($url);
+ if ($line === false) {
+ $this->error = 'Connection or response error on ' . $url;
+ return;
+ } else if ($line === -1) {
+ $this->error = 'Timed out while reading data from ' . $url;
+ return;
+ } else if ($line == '' || strpos($line, '
') !== false) {
+ $this->error = 'No data returned from ' . $url;
+ return;
+ }
+
+ $array = explode(';', $line);
+ if (!isset($array[10])) {
+ $this->error = 'Cannot parse data returned data from ' . $url;
+ return;
+ }
+ // 0 = $array[0];
+ $this->totalplayers = $array[1];
+ $this->wins = $array[2];
+ $this->losses = $array[3];
+ $this->draws = $array[4];
+ $this->worldrank = $array[5];
+ $this->points = $array[6];
+ $this->lastmatch = $array[7];
+ $this->teamname = urldecode($array[8]);
+ $this->teamrank = (int)$array[9];
+ $this->totalteams = $array[10];
+
+ $url = 'http://ladder.trackmanianations.com/ladder/getstats.php?ver=' . $this->version . '&laddertype=g&login=' . $this->login . '&country=' . $this->nation;
+ $line = $this->get_file($url);
+ if ($line === false) {
+ $this->error = 'Connection or response error on ' . $url;
+ return;
+ } else if ($line === -1) {
+ $this->error = 'Timed out while reading data from ' . $url;
+ return;
+ } else if ($line == '' || strpos($line, '
') !== false) {
+ $this->error = 'No data returned from ' . $url;
+ return;
+ }
+
+ $array = explode(';', $line);
+ // 0 = $array[1];
+ if (isset($array[5]))
+ $this->nationrank = $array[5];
+ else
+ $this->nationrank = '';
+ // the remaining fields are the same as the world stats above
+
+ $url = 'http://ladder.trackmanianations.com/ladder/getrankings.php?ver=' . $this->version . '&laddertype=g&start=0&limit=0&country=' . $this->nation;
+ $line = $this->get_file($url);
+ if ($line === false) {
+ $this->error = 'Connection or response error on ' . $url;
+ return;
+ } else if ($line === -1) {
+ $this->error = 'Timed out while reading data from ' . $url;
+ return;
+ } else if ($line == '' || strpos($line, '
') !== false) {
+ $this->error = 'No data returned from ' . $url;
+ return;
+ }
+
+ $array = explode(';', $line);
+ if (!isset($array[1])) {
+ $this->error = 'Cannot parse data returned data from ' . $url;
+ return;
+ }
+ // 0 = $array[1];
+ $this->nationplayers = $array[1];
+ // 1;login;nickname;nation;points = $array[2-6];
+ // 2;login;nickname;nation;points = $array[7-11]; etc.etc.
+
+ $url = 'http://ladder.trackmanianations.com/ladder/getcountriesrankings.php?ver=' . $this->version . '&laddertype=g&lang=en&start=0&limit=100';
+ $line = $this->get_file($url);
+ if ($line === false) {
+ $this->error = 'Connection or response error on ' . $url;
+ return;
+ } else if ($line === -1) {
+ $this->error = 'Timed out while reading data from ' . $url;
+ return;
+ } else if ($line == '' || strpos($line, '
') !== false) {
+ $this->error = 'No data returned from ' . $url;
+ return;
+ }
+
+ $array = explode(';', $line);
+ if (!isset($array[1])) {
+ $this->error = 'Cannot parse data returned data from ' . $url;
+ return;
+ }
+ // 0 = $array[1];
+ $this->totalnations = $array[1];
+ // 1;nation;points = $array[2-4];
+ // 2;nation;points = $array[5-7]; etc.etc.
+ $i = 2;
+ while (isset($array[$i]) && $array[$i] != '') {
+ if ($array[$i+1] == $this->nation) {
+ $this->nationpos = $array[$i];
+ $this->nationpoints = $array[$i+2];
+ break;
+ }
+ $i += 3;
+ }
+
+ $this->online = false;
+ // check online status too?
+ if ($this->extended) {
+ $page = $this->get_file('http://game.trackmanianations.com/online_game/www_serverslist.php');
+ if ($page === false || $page == -1 || $page == '')
+ // no error message if main info was already fetched
+ return;
+
+ $lines = explode('', $page);
+ foreach ($lines as $line) {
+ if (stripos($line, '' . $this->login . '') !== false) {
+ $this->online = true;
+ $this->serverlogin = substr($line, 0, strpos($line, ''));
+ break;
+ }
+ }
+
+ if ($this->online) {
+ $page = $this->get_file('http://game.trackmanianations.com/online_game/browse_top.php?ver=0.1.7.9&lang=en&key=XXXX-XXXX-XXXX-XXXX-XXX&nb=100&page=1&flatall=1');
+ if ($page === false || $page == -1 || $page == '')
+ // no error message if main info was already fetched
+ return;
+
+ $server = $this->serverlogin; // can't use object member inside pattern
+ if (preg_match("/^${server};([^;]+);([^;]+);([A-Z]+);/m", $page, $fields)) {
+ $this->serverdesc = $fields[1];
+ $this->servernick = ($fields[2] != 'x' ? urldecode($fields[2]) : '');
+ $this->servernation = $fields[3];
+ }
+ }
+ }
+ } // getData
+
+ // Simple HTTP Get function with timeout
+ // ok: return string || error: return false || timeout: return -1
+ private function get_file($url) {
+
+ $url = parse_url($url);
+ $port = isset($url['port']) ? $url['port'] : 80;
+ $query = isset($url['query']) ? "?" . $url['query'] : "";
+
+ $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4);
+ if (!$fp)
+ return false;
+
+ fwrite($fp, 'GET ' . $url['path'] . $query . " HTTP/1.0\r\n" .
+ 'Host: ' . $url['host'] . "\r\n" .
+ 'User-Agent: TMNDataFetcher (' . PHP_OS . ")\r\n\r\n");
+ stream_set_timeout($fp, 2);
+ $res = '';
+ $info['timed_out'] = false;
+ while (!feof($fp) && !$info['timed_out']) {
+ $res .= fread($fp, 512);
+ $info = stream_get_meta_data($fp);
+ }
+ fclose($fp);
+
+ if ($info['timed_out']) {
+ return -1;
+ } else {
+ if (substr($res, 9, 3) != '200')
+ return false;
+ $page = explode("\r\n\r\n", $res, 2);
+ return trim($page[1]);
+ }
+ } // get_file
+} // class TMNDataFetcher
+?>
diff --git a/xaseco/includes/tmxinfofetcher.inc.php b/xaseco/includes/tmxinfofetcher.inc.php
new file mode 100644
index 0000000..da09eef
--- /dev/null
+++ b/xaseco/includes/tmxinfofetcher.inc.php
@@ -0,0 +1,297 @@
+
+ * Inspired by TMNDataFetcher & "Stats for TMN Aseco+RASP"
+ *
+ * v1.19: Allowed 24-char UIDs too
+ * v1.18: Added 'replayurl' to the entries in $recordslist
+ * v1.17: Allowed 25-char UIDs too
+ * v1.16: Improved error reporting via $error; parsed more comment formatting
+ * v1.15: Added User-Agent to the GET request
+ * v1.14: Fixed PHP Strict level warning
+ * v1.13: Fixed handling of empty API responses
+ * v1.12: Renamed $worldrec into $custimg to correct its meaning; fixed
+ * $replayid check
+ * v1.11: Added another check for valid $replayid
+ * v1.10: Added magic __set_state function to support var_export()
+ * v1.9: Fixed fetch records run-time warnings
+ * v1.8: Optimized get_file URL parsing
+ * v1.7: Added $worldrec (boolean); changed $visible to boolean; renamed
+ * $download to $dloadurl, $imgurl to $imageurl & $imgurlsmall to
+ * $thumburl; minor tweaks
+ * v1.6: Added check for valid $replayid & $replayurl
+ * v1.5: Added TMNF compatibility
+ * v1.4: Added get_file function to handle TMX site timeouts
+ * v1.3: Added $awards, $comments, $replayid, $replayurl; renamed $comment to
+ * $acomment (to better distinguish author comment from # of comments)
+ * v1.2: Allowed 26-char UIDs too; added $pageurl
+ * v1.1: Allowed TMX IDs too
+ * v1.0: Initial release
+ */
+class TMXInfoFetcher {
+
+ public $section, $prefix, $uid, $id, $records, $error,
+ $name, $userid, $author, $uploaded, $updated,
+ $visible, $type, $envir, $mood, $style, $routes,
+ $length, $diffic, $lbrating, $awards, $comments, $custimg,
+ $game, $acomment, $pageurl, $replayid, $replayurl,
+ $imageurl, $thumburl, $dloadurl, $recordlist;
+
+ /**
+ * Fetches a hell of a lot of data about a TMX track
+ *
+ * @param String $game
+ * TMX section for 'TMO', 'TMS', 'TMN', 'TMU', 'TMNF'
+ * @param String $id
+ * The challenge UID to search for (if a 24-27 char alphanum string),
+ * otherwise the TMX ID to search for (if a number)
+ * @param Boolean $records
+ * If true, the script also returns the world records (max. 10)
+ * @return TMXInfoFetcher
+ * If $error is not an empty string, it's an error message
+ */
+ public function TMXInfoFetcher($game, $id, $records) {
+
+ $this->section = $game;
+ switch ($game) {
+ case 'TMO':
+ $this->prefix = 'original';
+ break;
+ case 'TMS':
+ $this->prefix = 'sunrise';
+ break;
+ case 'TMN':
+ $this->prefix = 'nations';
+ break;
+ case 'TMU':
+ $this->prefix = 'united';
+ break;
+ case 'TMNF':
+ $this->prefix = 'tmnforever';
+ break;
+ default:
+ $this->prefix = '';
+ return;
+ }
+
+ $this->error = '';
+ $this->records = $records;
+ // check for UID string
+ if (preg_match('/^\w{24,27}$/', $id)) {
+ $this->uid = $id;
+ $this->getData(true);
+ // check for TMX ID
+ } elseif (is_numeric($id) && $id > 0) {
+ $this->id = floor($id);
+ $this->getData(false);
+ }
+ } // TMXInfoFetcher
+
+ public static function __set_state($import) {
+
+ $tmx = new TMXInfoFetcher('', 0, false);
+
+ $tmx->section = $import['section'];
+ $tmx->prefix = $import['prefix'];
+ $tmx->uid = $import['uid'];
+ $tmx->id = $import['id'];
+ $tmx->records = $import['records'];
+ $tmx->error = '';
+ $tmx->name = $import['name'];
+ $tmx->userid = $import['userid'];
+ $tmx->author = $import['author'];
+ $tmx->uploaded = $import['uploaded'];
+ $tmx->updated = $import['updated'];
+ $tmx->visible = $import['visible'];
+ $tmx->type = $import['type'];
+ $tmx->envir = $import['envir'];
+ $tmx->mood = $import['mood'];
+ $tmx->style = $import['style'];
+ $tmx->routes = $import['routes'];
+ $tmx->length = $import['length'];
+ $tmx->diffic = $import['diffic'];
+ $tmx->lbrating = $import['lbrating'];
+ $tmx->awards = $import['awards'];
+ $tmx->comments = $import['comments'];
+ $tmx->custimg = $import['custimg'];
+ $tmx->game = $import['game'];
+ $tmx->acomment = $import['acomment'];
+ $tmx->pageurl = $import['pageurl'];
+ $tmx->replayid = $import['replayid'];
+ $tmx->replayurl = $import['replayurl'];
+ $tmx->imageurl = $import['imageurl'];
+ $tmx->thumburl = $import['thumburl'];
+ $tmx->dloadurl = $import['dloadurl'];
+ $tmx->recordlist = null;
+
+ return $tmx;
+ } // __set_state
+
+ private function getData($isuid) {
+
+ // get main track info
+ $url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apitrackinfo&' . ($isuid ? 'u' : '') . 'id=' . ($isuid ? $this->uid : $this->id);
+ $file = $this->get_file($url);
+ if ($file === false) {
+ $this->error = 'Connection or response error on ' . $url;
+ return;
+ } else if ($file === -1) {
+ $this->error = 'Timed out while reading data from ' . $url;
+ return;
+ } else if ($file == '') {
+ $this->error = 'No data returned from ' . $url;
+ return;
+ }
+
+ // check for API error message
+ if (strpos($file, chr(27)) !== false) {
+ $this->error = 'Cannot decode main track info';
+ return;
+ }
+
+ // separate columns on Tabs
+ $fields = explode(chr(9), $file);
+
+ if ($isuid)
+ $this->id = $fields[0];
+
+ $this->name = $fields[1];
+ $this->userid = $fields[2];
+ $this->author = $fields[3];
+ $this->uploaded = $fields[4];
+ $this->updated = $fields[5];
+ $this->visible = (strtolower($fields[6]) == 'true');
+ $this->type = $fields[7];
+ $this->envir = $fields[8];
+ $this->mood = $fields[9];
+ $this->style = $fields[10];
+ $this->routes = $fields[11];
+ $this->length = $fields[12];
+ $this->diffic = $fields[13];
+ $this->lbrating = ($fields[14] > 0 ? $fields[14] : 'Classic!');
+ $this->game = $fields[15];
+
+ $search = array(chr(31), '[b]', '[/b]', '[i]', '[/i]', '[u]', '[/u]', '[url]', '[/url]');
+ $replace = array('
', '', '', '', '', '', '', '', '');
+ $this->acomment = str_ireplace($search, $replace, $fields[16]);
+ $this->acomment = preg_replace('/\[url=".*"\]/', '', $this->acomment);
+
+ $this->pageurl = 'http://' . $this->prefix . '.tm-exchange.com/main.aspx?action=trackshow&id=' . $this->id;
+ $this->imageurl = 'http://' . $this->prefix . '.tm-exchange.com/get.aspx?action=trackscreen&id=' . $this->id;
+ $this->thumburl = 'http://' . $this->prefix . '.tm-exchange.com/get.aspx?action=trackscreensmall&id=' . $this->id;
+ $this->dloadurl = 'http://' . $this->prefix . '.tm-exchange.com/get.aspx?action=trackgbx&id=' . $this->id;
+
+ $this->awards = 0;
+ $this->comments = 0;
+ $this->custimg = false;
+ $this->replayid = 0;
+ $this->replayurl = '';
+
+ // get misc. track info
+ $url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apisearch&trackid=' . $this->id;
+ $file = $this->get_file($url);
+ if ($file === false || $file === -1 || $file == '')
+ // no error message if main info was already fetched
+ return;
+
+ // check for API error message
+ if (strpos($file, chr(27)) !== false)
+ return;
+
+ // separate columns on Tabs
+ $fields = explode(chr(9), $file);
+
+ // id = $fields[0];
+ // name = $fields[1];
+ // userid = $fields[2];
+ // author = $fields[3];
+ // type = $fields[4];
+ // envir = $fields[5];
+ // mood = $fields[6];
+ // style = $fields[7];
+ // routes = $fields[8];
+ // length = $fields[9];
+ // diffic = $fields[10];
+ // lbrating = ($fields[11] > 0 ? $fields[11] : 'Classic!');
+ $this->awards = $fields[12];
+ $this->comments = $fields[13];
+ $this->custimg = (strtolower($fields[14]) == 'true');
+ // game = $fields[15];
+ $this->replayid = $fields[16];
+ // unknown = $fields[17-21];
+ // uploaded = $fields[22];
+ // updated = $fields[23];
+
+ if ($this->replayid > 0) {
+ $this->replayurl = 'http://' . $this->prefix . '.tm-exchange.com/get.aspx?action=recordgbx&id=' . $this->replayid;
+ }
+
+ // fetch records too?
+ $this->recordlist = array();
+ if ($this->records) {
+ $url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apitrackrecords&id=' . $this->id;
+ $file = $this->get_file($url);
+ if ($file === false || $file === -1 || $file == '')
+ // no error message if main info was already fetched
+ return;
+
+ $file = explode("\r\n", $file);
+ $i = 0;
+ while ($i < 10 && isset($file[$i]) && $file[$i] != '') {
+ // separate columns on Tabs
+ $fields = explode(chr(9), $file[$i]);
+ $this->recordlist[$i++] = array(
+ 'replayid' => $fields[0],
+ 'userid' => $fields[1],
+ 'name' => $fields[2],
+ 'time' => $fields[3],
+ 'replayat' => $fields[4],
+ 'trackat' => $fields[5],
+ 'approved' => $fields[6],
+ 'score' => $fields[7],
+ 'expires' => $fields[8],
+ 'lockspan' => $fields[9],
+ 'replayurl'=> 'http://' . $this->prefix . '.tm-exchange.com/get.aspx?action=recordgbx&id=' . $fields[0],
+ );
+ }
+ }
+ } // getData
+
+ // Simple HTTP Get function with timeout
+ // ok: return string || error: return false || timeout: return -1
+ private function get_file($url) {
+
+ $url = parse_url($url);
+ $port = isset($url['port']) ? $url['port'] : 80;
+ $query = isset($url['query']) ? "?" . $url['query'] : "";
+
+ $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4);
+ if (!$fp)
+ return false;
+
+ fwrite($fp, 'GET ' . $url['path'] . $query . " HTTP/1.0\r\n" .
+ 'Host: ' . $url['host'] . "\r\n" .
+ 'User-Agent: TMXInfoFetcher (' . PHP_OS . ")\r\n\r\n");
+ stream_set_timeout($fp, 2);
+ $res = '';
+ $info['timed_out'] = false;
+ while (!feof($fp) && !$info['timed_out']) {
+ $res .= fread($fp, 512);
+ $info = stream_get_meta_data($fp);
+ }
+ fclose($fp);
+
+ if ($info['timed_out']) {
+ return -1;
+ } else {
+ if (substr($res, 9, 3) != '200')
+ return false;
+ $page = explode("\r\n\r\n", $res, 2);
+ return trim($page[1]);
+ }
+ } // get_file
+} // class TMXInfoFetcher
+?>
diff --git a/xaseco/includes/tmxinfosearcher.inc.php b/xaseco/includes/tmxinfosearcher.inc.php
new file mode 100644
index 0000000..b699014
--- /dev/null
+++ b/xaseco/includes/tmxinfosearcher.inc.php
@@ -0,0 +1,288 @@
+
+ * Based on TMXInfoFetcher & http://united.tm-exchange.com/main.aspx?action=threadshow&id=619302
+ *
+ * v1.7: Added Countable interface to searcher class
+ * v1.6: Fixed an error checking bug
+ * v1.5: Improved error reporting via $error
+ * v1.4: Added User-Agent to the GET request
+ * v1.3: Renamed $worldrec into $custimg to correct its meaning; fixed
+ * $replayid check
+ * v1.2: Fixed TMXInfo processing false $track
+ * v1.1: Optimized get_file URL parsing
+ * v1.0: Initial release
+ */
+class TMXInfoSearcher implements Iterator,Countable {
+
+ public $error;
+ protected $tracks = array();
+ private $section;
+ private $prefix;
+
+ /**
+ * Searches TMX for tracks matching name, author and/or environment;
+ * or search TMX for the 10 most recent tracks
+ *
+ * @param String $game
+ * TMX section for 'TMO', 'TMS', 'TMN', 'TMU', 'TMNF'
+ * @param String $name
+ * The track name to search for (partial, case-insensitive match)
+ * @param String $author
+ * The track author to search for (partial, case-insensitive match)
+ * @param String $env
+ * The environment to search for (exact case-insensitive match
+ * from: Desert, Snow, Rally, Bay, Coast, Island, Stadium);
+ * ignored when searching TMN or TMNF
+ * @param Boolean $recent
+ * If true, ignore search parameters and just return 10 newest tracks
+ * (max. one per author)
+ * @return TMXInfoSearcher
+ * If ->valid() is false, no matching track was found;
+ * otherwise, an iterator of TMXInfo objects for a 'foreach' loop.
+ * Returns at most 500 tracks ($maxpage * 20) for TMNF/TMU(F),
+ * and at most 20 tracks for the other TMX sections.
+ */
+ public function __construct($game, $name, $author, $env, $recent) {
+
+ $this->section = $game;
+ switch ($game) {
+ case 'TMO':
+ $this->prefix = 'original';
+ break;
+ case 'TMS':
+ $this->prefix = 'sunrise';
+ break;
+ case 'TMN':
+ $this->prefix = 'nations';
+ $env = ''; // ignore possible environment
+ break;
+ case 'TMU':
+ $this->prefix = 'united';
+ break;
+ case 'TMNF':
+ $this->prefix = 'tmnforever';
+ $env = ''; // ignore possible environment
+ break;
+ default:
+ $this->prefix = '';
+ $this->error = 'Unknown TMX section: ' . $game;
+ return;
+ }
+
+ $this->error = '';
+ if ($recent) {
+ $this->tracks = $this->getRecent();
+ } else {
+ $this->tracks = $this->getList($name, $author, $env);
+ }
+ } // __construct
+
+ // define standard Iterator functions
+ public function rewind() {
+ reset($this->tracks);
+ }
+ public function current() {
+ return new TMXInfo($this->section, $this->prefix, current($this->tracks));
+ }
+ public function next() {
+ return new TMXInfo($this->section, $this->prefix, next($this->tracks));
+ }
+ public function key() {
+ return key($this->tracks);
+ }
+ public function valid() {
+ return (current($this->tracks) !== false);
+ }
+ // define standard Countable function
+ public function count() {
+ return count($this->tracks);
+ }
+
+ private function getRecent() {
+
+ // get 10 most recent tracks
+ $url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apirecent';
+ $file = $this->get_file($url);
+ if ($file === false) {
+ $this->error = 'Connection or response error on ' . $url;
+ return array();
+ } else if ($file === -1) {
+ $this->error = 'Timed out while reading data from ' . $url;
+ return array();
+ } else if ($file == '') {
+ $this->error = 'No data returned from ' . $url;
+ return array();
+ }
+
+ // check for API error message
+ if (strpos($file, chr(27)) !== false) {
+ $this->error = 'Cannot decode recent track info from ' . $url;
+ return array();
+ }
+
+ // return list of tracks as array of strings
+ return explode("\r\n", $file);
+ } // getRecent
+
+ private function getList($name, $author, $env) {
+
+ // older TMX sections don't support multi-page results :(
+ $maxpage = 1;
+ if ($this->prefix == 'tmnforever' || $this->prefix == 'united')
+ $maxpage = 25; // max. 500 tracks
+
+ // compile search URL
+ $url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apisearch';
+ if ($name != '')
+ $url .= '&track=' . $name;
+ if ($author != '')
+ $url .= '&author=' . $author;
+ if ($env != '')
+ $url .= '&env=' . $env;
+ $url .= '&page=';
+
+ $tracks = '';
+ $page = 0;
+ $done = false;
+
+ // get results 20 tracks at a time
+ while ($page < $maxpage && !$done) {
+ $file = $this->get_file($url . $page);
+ if ($file === false) {
+ $this->error = 'Connection or response error on ' . $url;
+ return array();
+ } else if ($file === -1) {
+ $this->error = 'Timed out while reading data from ' . $url;
+ return array();
+ } else if ($file == '') {
+ if ($tracks == '') {
+ $this->error = 'No data returned from ' . $url;
+ return array();
+ } else {
+ break;
+ }
+ }
+
+ // check for API error message
+ if (strpos($file, chr(27)) !== false) {
+ $this->error = 'Cannot decode searched track info from ' . $url;
+ return array();
+ }
+
+ // check for results
+ if ($file != '') {
+ // no line break before first page
+ $tracks .= ($page++ > 0 ? "\r\n" : '') . $file;
+ } else {
+ $done = true;
+ }
+ }
+
+ // return list of tracks as array of strings
+ if ($tracks != '')
+ return explode("\r\n", $tracks);
+ else
+ return array();
+ } // getList
+
+ // Simple HTTP Get function with timeout
+ // ok: return string || error: return false || timeout: return -1
+ private function get_file($url) {
+
+ $url = parse_url($url);
+ $port = isset($url['port']) ? $url['port'] : 80;
+ $query = isset($url['query']) ? "?" . $url['query'] : "";
+
+ $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4);
+ if (!$fp)
+ return false;
+
+ fwrite($fp, 'GET ' . $url['path'] . $query . " HTTP/1.0\r\n" .
+ 'Host: ' . $url['host'] . "\r\n" .
+ 'User-Agent: TMXInfoSearcher (' . PHP_OS . ")\r\n\r\n");
+ stream_set_timeout($fp, 2);
+ $res = '';
+ $info['timed_out'] = false;
+ while (!feof($fp) && !$info['timed_out']) {
+ $res .= fread($fp, 512);
+ $info = stream_get_meta_data($fp);
+ }
+ fclose($fp);
+
+ if ($info['timed_out']) {
+ return -1;
+ } else {
+ if (substr($res, 9, 3) != '200')
+ return false;
+ $page = explode("\r\n\r\n", $res, 2);
+ return trim($page[1]);
+ }
+ } // get_file
+} // class TMXInfoSearcher
+
+
+class TMXInfo {
+
+ public $section, $prefix, $id, $name, $userid, $author,
+ $type, $envir, $mood, $style, $routes, $length, $diffic,
+ $lbrating, $awards, $comments, $custimg, $game, $uploaded, $updated,
+ $pageurl, $replayid, $replayurl, $imageurl, $thumburl, $dloadurl;
+
+ /**
+ * Returns track object with a hell of a lot of data from TMX track string
+ *
+ * @param String $section
+ * TMX section
+ * @param String $prefix
+ * TMX URL prefix
+ * @param String $track
+ * The TMX track string from TMXInfoSearcher
+ * @return TMXInfo
+ */
+ public function TMXInfo($section, $prefix, $track) {
+
+ $this->section = $section;
+ $this->prefix = $prefix;
+ if ($track) {
+ // separate columns on Tabs
+ $fields = explode(chr(9), $track);
+
+ $this->id = $fields[0];
+ $this->name = $fields[1];
+ $this->userid = $fields[2];
+ $this->author = $fields[3];
+ $this->type = $fields[4];
+ $this->envir = $fields[5];
+ $this->mood = $fields[6];
+ $this->style = $fields[7];
+ $this->routes = $fields[8];
+ $this->length = $fields[9];
+ $this->diffic = $fields[10];
+ $this->lbrating = ($fields[11] > 0 ? $fields[11] : 'Classic!');
+ $this->awards = $fields[12];
+ $this->comments = $fields[13];
+ $this->custimg = (strtolower($fields[14]) == 'true');
+ $this->game = $fields[15];
+ $this->replayid = $fields[16];
+ // unknown = $fields[17-21];
+ $this->uploaded = $fields[22];
+ $this->updated = $fields[23];
+
+ $this->pageurl = 'http://' . $prefix . '.tm-exchange.com/main.aspx?action=trackshow&id=' . $this->id;
+ $this->imageurl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=trackscreen&id=' . $this->id;
+ $this->thumburl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=trackscreensmall&id=' . $this->id;
+ $this->dloadurl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=trackgbx&id=' . $this->id;
+
+ if ($this->replayid > 0) {
+ $this->replayurl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=recordgbx&id=' . $this->replayid;
+ } else {
+ $this->replayurl = '';
+ }
+ }
+ } // TMXInfo
+} // class TMXInfo
+?>
diff --git a/xaseco/includes/types.inc.php b/xaseco/includes/types.inc.php
new file mode 100644
index 0000000..277803a
--- /dev/null
+++ b/xaseco/includes/types.inc.php
@@ -0,0 +1,511 @@
+record_list = array();
+ $this->max = $limit;
+ }
+
+ function setLimit($limit) {
+ $this->max = $limit;
+ }
+
+ function getRecord($rank) {
+ if (isset($this->record_list[$rank]))
+ return $this->record_list[$rank];
+ else
+ return false;
+ }
+
+ function setRecord($rank, $record) {
+ if (isset($this->record_list[$rank])) {
+ return $this->record_list[$rank] = $record;
+ } else {
+ return false;
+ }
+ }
+
+ function moveRecord($from, $to) {
+ moveArrayElement($this->record_list, $from, $to);
+ }
+
+ function addRecord($record, $rank = -1) {
+
+ // if no rank was set for this record, then put it to the end of the list
+ if ($rank == -1) {
+ $rank = count($this->record_list);
+ }
+
+ // do not insert a record behind the border of the list
+ if ($rank >= $this->max) return;
+
+ // do not insert a record with no score
+ if ($record->score <= 0) return;
+
+ // if the given object is a record
+ if (get_class($record) == 'Record') {
+
+ // if records are getting too much, drop the last from the list
+ if (count($this->record_list) >= $this->max) {
+ array_pop($this->record_list);
+ }
+
+ // insert the record at the specified position
+ return insertArrayElement($this->record_list, $record, $rank);
+ }
+ }
+
+ function delRecord($rank = -1) {
+
+ // do not remove a record outside the current list
+ if ($rank < 0 || $rank >= count($this->record_list)) return;
+
+ // remove the record from the specified position
+ return removeArrayElement($this->record_list, $rank);
+ }
+
+ function count() {
+ return count($this->record_list);
+ }
+
+ function clear() {
+ $this->record_list = array();
+ }
+} // class RecordList
+
+
+/**
+ * Structure of a Player.
+ * Can be instantiated with an RPC 'GetPlayerInfo' or
+ * 'GetDetailedPlayerInfo' response.
+ */
+class Player {
+ var $id;
+ var $pid;
+ var $login;
+ var $nickname;
+ var $teamname;
+ var $ip;
+ var $client;
+ var $ipport;
+ var $zone;
+ var $nation;
+ var $prevstatus;
+ var $isspectator;
+ var $isofficial;
+ var $rights;
+ var $language;
+ var $avatar;
+ var $teamid;
+ var $unlocked;
+ var $ladderrank;
+ var $ladderscore;
+ var $created;
+ var $wins;
+ var $newwins;
+ var $timeplayed;
+ var $tracklist;
+ var $playerlist;
+ var $msgs;
+ var $pmbuf;
+ var $mutelist;
+ var $mutebuf;
+ var $style;
+ var $panels;
+ var $speclogin;
+ var $dedirank;
+
+ function getWins() {
+ return $this->wins + $this->newwins;
+ }
+
+ function getTimePlayed() {
+ return $this->timeplayed + $this->getTimeOnline();
+ }
+
+ function getTimeOnline() {
+ return $this->created > 0 ? time() - $this->created : 0;
+ }
+
+ // instantiates the player with an RPC response
+ function Player($rpc_infos = null) {
+ $this->id = 0;
+ if ($rpc_infos) {
+ $this->pid = $rpc_infos['PlayerId'];
+ $this->login = $rpc_infos['Login'];
+ $this->nickname = $rpc_infos['NickName'];
+ $this->ipport = $rpc_infos['IPAddress'];
+ $this->ip = preg_replace('/:\d+/', '', $rpc_infos['IPAddress']); // strip port
+ $this->prevstatus = false;
+ $this->isspectator = $rpc_infos['IsSpectator'];
+ $this->isofficial = $rpc_infos['IsInOfficialMode'];
+ $this->teamname = $rpc_infos['LadderStats']['TeamName'];
+ if (isset($rpc_infos['Nation'])) { // TMN (TMS/TMO?)
+ $this->zone = $rpc_infos['Nation'];
+ $this->nation = $rpc_infos['Nation'];
+ $this->ladderrank = $rpc_infos['LadderStats']['Ranking'];
+ $this->ladderscore = $rpc_infos['LadderStats']['Score'];
+ $this->client = '';
+ $this->rights = false;
+ $this->language = '';
+ $this->avatar = '';
+ $this->teamid = 0;
+ } else { // TMF
+ $this->zone = substr($rpc_infos['Path'], 6); // strip 'World|'
+ $this->nation = explode('|', $rpc_infos['Path']);
+ if (isset($this->nation[1]))
+ $this->nation = $this->nation[1];
+ else
+ $this->nation = '';
+ $this->ladderrank = $rpc_infos['LadderStats']['PlayerRankings'][0]['Ranking'];
+ $this->ladderscore = round($rpc_infos['LadderStats']['PlayerRankings'][0]['Score'], 2);
+ $this->client = $rpc_infos['ClientVersion'];
+ $this->rights = ($rpc_infos['OnlineRights'] == 3); // United = true
+ $this->language = $rpc_infos['Language'];
+ $this->avatar = $rpc_infos['Avatar']['FileName'];
+ $this->teamid = $rpc_infos['TeamId'];
+ }
+ $this->created = time();
+ } else {
+ // set defaults
+ $this->pid = 0;
+ $this->login = '';
+ $this->nickname = '';
+ $this->ipport = '';
+ $this->ip = '';
+ $this->prevstatus = false;
+ $this->isspectator = false;
+ $this->isofficial = false;
+ $this->teamname = '';
+ $this->zone = '';
+ $this->nation = '';
+ $this->ladderrank = 0;
+ $this->ladderscore = 0;
+ $this->rights = false;
+ $this->created = 0;
+ }
+ $this->wins = 0;
+ $this->newwins = 0;
+ $this->timeplayed = 0;
+ $this->unlocked = false;
+ $this->pmbuf = array();
+ $this->mutelist = array();
+ $this->mutebuf = array();
+ $this->style = array();
+ $this->panels = array();
+ $this->speclogin = '';
+ $this->dedirank = 0;
+ }
+} // class Player
+
+/**
+ * Manages players on the server.
+ * Add player and remove them.
+ */
+class PlayerList {
+ var $player_list;
+
+ // instantiates the empty player list
+ function PlayerList() {
+ $this->player_list = array();
+ }
+
+ function nextPlayer() {
+ if (is_array($this->player_list)) {
+ $player_item = current($this->player_list);
+ next($this->player_list);
+ return $player_item;
+ } else {
+ $this->resetPlayers();
+ return false;
+ }
+ }
+
+ function resetPlayers() {
+ if (is_array($this->player_list)) {
+ reset($this->player_list);
+ }
+ }
+
+ function addPlayer($player) {
+ if (get_class($player) == 'Player' && $player->login != '') {
+ $this->player_list[$player->login] = $player;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function removePlayer($login) {
+ if (isset($this->player_list[$login])) {
+ $player = $this->player_list[$login];
+ unset($this->player_list[$login]);
+ } else {
+ $player = false;
+ }
+ return $player;
+ }
+
+ function getPlayer($login) {
+ if (isset($this->player_list[$login]))
+ return $this->player_list[$login];
+ else
+ return false;
+ }
+} // class PlayerList
+
+
+/**
+ * Can store challenge information.
+ * You can instantiate with an RPC 'GetChallengeInfo' response.
+ */
+class Challenge {
+ var $id;
+ var $name;
+ var $uid;
+ var $filename;
+ var $author;
+ var $environment;
+ var $mood;
+ var $bronzetime;
+ var $silvertime;
+ var $goldtime;
+ var $authortime;
+ var $copperprice;
+ var $laprace;
+ var $forcedlaps;
+ var $nblaps;
+ var $nbchecks;
+ var $score;
+ var $starttime;
+ var $gbx;
+ var $tmx;
+
+ // instantiates the challenge with an RPC response
+ function Challenge($rpc_infos = null) {
+ $this->id = 0;
+ if ($rpc_infos) {
+ $this->name = stripNewlines($rpc_infos['Name']);
+ $this->uid = $rpc_infos['UId'];
+ $this->filename = $rpc_infos['FileName'];
+ $this->author = $rpc_infos['Author'];
+ $this->environment = $rpc_infos['Environnement'];
+ $this->mood = $rpc_infos['Mood'];
+ $this->bronzetime = $rpc_infos['BronzeTime'];
+ $this->silvertime = $rpc_infos['SilverTime'];
+ $this->goldtime = $rpc_infos['GoldTime'];
+ $this->authortime = $rpc_infos['AuthorTime'];
+ $this->copperprice = $rpc_infos['CopperPrice'];
+ $this->laprace = $rpc_infos['LapRace'];
+ $this->forcedlaps = 0;
+ if (isset($rpc_infos['NbLaps']))
+ $this->nblaps = $rpc_infos['NbLaps'];
+ else
+ $this->nblaps = 0;
+ if (isset($rpc_infos['NbCheckpoints']))
+ $this->nbchecks = $rpc_infos['NbCheckpoints'];
+ else
+ $this->nbchecks = 0;
+ } else {
+ // set defaults
+ $this->name = 'undefined';
+ }
+ }
+} // class Challenge
+
+
+/**
+ * Contains information about an RPC call.
+ */
+class RPCCall {
+ var $index;
+ var $id;
+ var $callback;
+ var $call;
+
+ // instantiates the RPC call with the parameters
+ function RPCCall($id, $index, $callback, $call) {
+ $this->id = $id;
+ $this->index = $index;
+ $this->callback = $callback;
+ $this->call = $call;
+ }
+} // class RPCCall
+
+
+/**
+ * Contains information about a chat command.
+ */
+class ChatCommand {
+ var $name;
+ var $help;
+ var $isadmin;
+
+ // instantiates the chat command with the parameters
+ function ChatCommand($name, $help, $isadmin) {
+ $this->name = $name;
+ $this->help = $help;
+ $this->isadmin = $isadmin;
+ }
+} // class ChatCommand
+
+
+/**
+ * Stores basic information of the server XASECO is running on.
+ */
+class Server {
+ var $id;
+ var $name;
+ var $game;
+ var $serverlogin;
+ var $nickname;
+ var $zone;
+ var $rights;
+ var $ip;
+ var $port;
+ var $timeout;
+ var $version;
+ var $build;
+ var $packmask;
+ var $laddermin;
+ var $laddermax;
+ var $login;
+ var $pass;
+ var $maxplay;
+ var $maxspec;
+ var $challenge;
+ var $records;
+ var $players;
+ var $mutelist;
+ var $gamestate;
+ var $gameinfo;
+ var $gamedir;
+ var $trackdir;
+ var $votetime;
+ var $voterate;
+ var $uptime;
+ var $starttime;
+ var $isrelay;
+ var $relaymaster;
+ var $relayslist;
+
+ // game states
+ const RACE = 'race';
+ const SCORE = 'score';
+
+ function getGame() {
+ switch ($this->game) {
+ case 'TmForever':
+ return 'TMF';
+ case 'TmNationsESWC':
+ return 'TMN';
+ case 'TmSunrise':
+ return 'TMS';
+ case 'TmOriginal':
+ return 'TMO';
+ default: // TMU was never supported
+ return 'Unknown';
+ }
+ }
+
+ // instantiates the server with default parameters
+ function Server($ip, $port, $login, $pass) {
+ $this->ip = $ip;
+ $this->port = $port;
+ $this->login = $login;
+ $this->pass = $pass;
+ $this->starttime = time();
+ }
+} // class Server
+
+/**
+ * Contains information to the current game which is played.
+ */
+class Gameinfo {
+ var $mode;
+ var $numchall;
+ var $rndslimit;
+ var $timelimit;
+ var $teamlimit;
+ var $lapslimit;
+ var $cuplimit;
+ var $forcedlaps;
+
+ const RNDS = 0;
+ const TA = 1;
+ const TEAM = 2;
+ const LAPS = 3;
+ const STNT = 4;
+ const CUP = 5;
+
+ // returns current game mode as string
+ function getMode() {
+ switch ($this->mode) {
+ case self::RNDS:
+ return 'Rounds';
+ case self::TA:
+ return 'TimeAttack';
+ case self::TEAM:
+ return 'Team';
+ case self::LAPS:
+ return 'Laps';
+ case self::STNT:
+ return 'Stunts';
+ case self::CUP:
+ return 'Cup';
+ default:
+ return 'Undefined';
+ }
+ }
+
+ // instantiates the game info with an RPC response
+ function Gameinfo($rpc_infos = null) {
+ if ($rpc_infos) {
+ $this->mode = $rpc_infos['GameMode'];
+ $this->numchall = $rpc_infos['NbChallenge'];
+ if (isset($rpc_infos['RoundsUseNewRules']) && $rpc_infos['RoundsUseNewRules'])
+ $this->rndslimit = $rpc_infos['RoundsPointsLimitNewRules'];
+ else
+ $this->rndslimit = $rpc_infos['RoundsPointsLimit'];
+ $this->timelimit = $rpc_infos['TimeAttackLimit'];
+ if (isset($rpc_infos['TeamUseNewRules']) && $rpc_infos['TeamUseNewRules'])
+ $this->teamlimit = $rpc_infos['TeamPointsLimitNewRules'];
+ else
+ $this->teamlimit = $rpc_infos['TeamPointsLimit'];
+ $this->lapslimit = $rpc_infos['LapsTimeLimit'];
+ if (isset($rpc_infos['CupPointsLimit']))
+ $this->cuplimit = $rpc_infos['CupPointsLimit'];
+ if (isset($rpc_infos['RoundsForcedLaps']))
+ $this->forcedlaps = $rpc_infos['RoundsForcedLaps'];
+ else
+ $this->forcedlaps = 0;
+ } else {
+ $this->mode = -1;
+ }
+ }
+} // class Gameinfo
+?>
diff --git a/xaseco/includes/urlsafebase64.php b/xaseco/includes/urlsafebase64.php
new file mode 100644
index 0000000..958cd7b
--- /dev/null
+++ b/xaseco/includes/urlsafebase64.php
@@ -0,0 +1,26 @@
+
diff --git a/xaseco/includes/web_access.inc.php b/xaseco/includes/web_access.inc.php
new file mode 100644
index 0000000..174b7e3
--- /dev/null
+++ b/xaseco/includes/web_access.inc.php
@@ -0,0 +1,1341 @@
+request($url, array('func_name',xxx), $datas, $is_xmlrpc, $keepalive_min_timeout);
+// $url: the web script URL.
+// $datas: string to send in http body (xml, xml_rpc or POST data)
+// $is_xmlrpc: true if it's an xml or xml-rpc request, false if it's a
+// standard html GET or POST
+// $keepalive_min_timeout: minimal value of server keepalive timeout to
+// send a keepalive request,
+// else make a request with close connection.
+// func_name is the callback function name, which will be called this way:
+// func_name(array('Code'=>code,'Reason'=>reason,'Headers'=>headers,'Message'=>message), xxx)
+// where:
+// xxx is the same as given previously in callback description.
+// code is the returned http code
+// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6)
+// reason is the returned http reason
+// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6)
+// headers are the http headers of the reply
+// message is the returned text body
+//
+// IMPORTANT: to have this work, the main part of your program must include a
+// $_webaccess->select() call periodically, which work exactly like stream_select().
+// This is because the send and receive are asynchronous and will be completed
+// later in the select() when datas are ready to be received and sent.
+
+
+// This class can be used to make a synchronous query too. For that use null
+// for the callback, so make the request this way:
+// $response = $_webaccess->request($url, null, $datas, $is_xmlrpc, $keepalive_min_timeout);
+// where $response is an array('Code'=>code,'Reason'=>reason,'Headers'=>headers,'Message'=>message)
+// like the one passed to the callback function of the asynchronous request.
+// If you use only synchronous queries then there is no need to call select()
+// as the function will return when the reply will be fully returned.
+// If the connection itself fail, the array response will include a 'Error' string.
+
+
+// Other functions:
+// list($host, $port, $path) = getHostPortPath($url);
+// gzdecode() workaround
+
+
+global $_web_access_compress_xmlrpc_request, $_web_access_compress_reply,
+ $_web_access_keepalive, $_web_access_keepalive_timeout,
+ $_web_access_keepalive_max, $_web_access_retry_timeout,
+ $_web_access_retry_timeout_max, $_web_access_post_xmlrpc;
+
+
+// Will compress xmlrpc request ('never','accept','force','force-gzip','force-deflate')
+// If set to 'accept' the first request will be made without, and the eventual
+// 'Accept-Encoding' in reply will permit to decide if request compression can
+// be used (and if gzip or deflate)
+$_web_access_compress_xmlrpc_request = 'accept';
+
+// Will ask server for compressed reply (false, true)
+// If true then will add a 'Accept-Encoding' header to tell the server to
+// compress the reply if it supports it.
+$_web_access_compress_reply = true;
+
+
+// Keep alive connection ? else close it after the reply.
+// Unless false, first request will be with keepalive, to get server timeout
+// and max values after timeout will be compared with the request
+// $keepalive_min_timeout value to decide if keepalive have to be used or not.
+// Note that Apache2 timeout is short (about 15s).
+// The classes will open, re-open or use existing connection as needed.
+$_web_access_keepalive = true;
+// timeout(s) without request before close, for keepalive
+$_web_access_keepalive_timeout = 600;
+// max requests before close, for keepalive
+$_web_access_keepalive_max = 2000;
+
+
+// For asynchronous call, in case of error, timeout before retrying.
+// It will be x2 for each error (on request or auto retry) until max,
+// then stop automatic retry, and next request calls will return false.
+// When stopped, a retry() or synchronous request will force a retry.
+$_web_access_retry_timeout = 20;
+$_web_access_retry_timeout_max = 5*60;
+
+
+// Use text/html with xmlrpc=, instead of of pure text/xml request (false, true)
+// Standard xml-rpc use pure text/xml request, where the xml is simply the body
+// of the http request (and it's how the xml-rpc reply will be made). As a
+// facility Dedimania also supports to get the xml in a html GET or POST,
+// where xmlrpc= will contain a urlsafe base64 of the xml. Default to false,
+// so use pure text/xml.
+$_web_access_post_xmlrpc = false;
+
+// Note that in each request the text/xml or xmlrpc= will be used only if
+// $is_xmlrpc is true. If false then the request will be a standard
+// application/x-www-form-urlencoded html GET or POST request; in that case
+// you have to build the URL (GET) and/or body data (POST) yourself.
+// If $is_xmlrpc is a string, then it's used as the Content-type: value.
+
+
+require_once('includes/urlsafebase64.php');
+
+class Webaccess {
+
+ var $_WebaccessList;
+
+ function Webaccess() {
+ $this->_WebaccessList = array();
+ }
+
+
+ function request($url, $callback, $datas, $is_xmlrpc = false, $keepalive_min_timeout = 300, $opentimeout = 3, $waittimeout = 5, $agent = 'XMLaccess') {
+ global $aseco, $_web_access_keepalive, $_web_access_keepalive_timeout, $_web_access_keepalive_max;
+
+ list($host, $port, $path) = getHostPortPath($url);
+
+ if ($host === false)
+ $aseco->console('Webaccess request(): Bad URL: ' . $url);
+
+ else {
+ $server = $host . ':' . $port;
+ // create object if needed
+ if (!isset($this->_WebaccessList[$server]) || $this->_WebaccessList[$server] === null) {
+ $this->_WebaccessList[$server] = new WebaccessUrl($this, $host, $port,
+ $_web_access_keepalive,
+ $_web_access_keepalive_timeout,
+ $_web_access_keepalive_max,
+ $agent);
+ }
+
+ // increase the default timeout for sync/wait request
+ if ($callback == null && $waittimeout == 5)
+ $waittimeout = 12;
+
+ // call request
+ if ($this->_WebaccessList[$server] !== null) {
+ $query = array('Path' => $path,
+ 'Callback' => $callback,
+ 'QueryData' => $datas,
+ 'IsXmlrpc' => $is_xmlrpc,
+ 'KeepaliveMinTimeout' => $keepalive_min_timeout,
+ 'OpenTimeout' => $opentimeout,
+ 'WaitTimeout' => $waittimeout
+ );
+
+ return $this->_WebaccessList[$server]->request($query);
+ }
+ }
+ return false;
+ } // request
+
+
+ function retry($url) {
+ global $aseco;
+
+ list($host, $port, $path) = getHostPortPath($url);
+ if ($host === false) {
+ $aseco->console('Webaccess retry(): Bad URL: ' . $url);
+ } else {
+ $server = $host . ':' . $port;
+ if (isset($this->_WebaccessList[$server]))
+ $this->_WebaccessList[$server]->retry();
+ }
+ } // retry
+
+
+ function select(&$read, &$write, &$except, $tv_sec, $tv_usec = 0) {
+
+ $timeout = (int)($tv_sec*1000000 + $tv_usec);
+ if ($read == null)
+ $read = array();
+ if ($write == null)
+ $write = array();
+ if ($except == null)
+ $except = array();
+
+ $read = $this->_getWebaccessReadSockets($read);
+ $write = $this->_getWebaccessWriteSockets($write);
+ //$except = $this->_getWebaccessReadSockets($except);
+
+ //print_r($this->_WebaccessList);
+ //print_r($read);
+ //print_r($write);
+ //print_r($except);
+
+ // if no socket to select then return
+ if (count($read) + count($write) + count($except) == 0) {
+ // sleep the asked timeout...
+ if ($timeout > 1000)
+ usleep($timeout);
+ return 0;
+ }
+
+ $utime = (int)(microtime(true)*1000000);
+ $nb = @stream_select($read, $write, $except, $tv_sec, $tv_usec);
+ if ($nb === false) {
+ // in case stream_select "forgot" to wait, sleep the remaining asked timeout...
+ $dtime = (int)(microtime(true)*1000000) - $utime;
+ $timeout -= $dtime;
+ if ($timeout > 1000)
+ usleep($timeout);
+ return false;
+ }
+
+ $this->_manageWebaccessSockets($read, $write, $except);
+ // workaround for stream_select bug with amd64, replace $nb with sum of arrays
+ return count($read) + count($write) + count($except);
+ } // select
+
+
+ private function _manageWebaccessSockets(&$receive, &$send, &$except) {
+
+ // send pending data on all webaccess sockets
+ if (is_array($send) && count($send) > 0) {
+ foreach ($send as $key => $socket) {
+ $i = $this->_findWebaccessSocket($socket);
+ if ($i !== false) {
+ if (isset($this->_WebaccessList[$i]->_spool[0]['State']) &&
+ $this->_WebaccessList[$i]->_spool[0]['State'] == 'OPEN')
+ $this->_WebaccessList[$i]->_open();
+ else
+ $this->_WebaccessList[$i]->_send();
+ unset($send[$key]);
+ }
+ }
+ }
+
+ // read data from all needed webaccess sockets
+ if (is_array($receive) && count($receive) > 0) {
+ foreach ($receive as $key => $socket) {
+ $i = $this->_findWebaccessSocket($socket);
+ if ($i !== false) {
+ $this->_WebaccessList[$i]->_receive();
+ unset($receive[$key]);
+ }
+ }
+ }
+ } // _manageWebaccessSockets
+
+
+ private function _findWebaccessSocket($socket) {
+
+ foreach ($this->_WebaccessList as $key => $wau) {
+ if ($wau->_socket == $socket)
+ return $key;
+ }
+ return false;
+ } // _findWebaccessSocket
+
+
+ private function _getWebaccessReadSockets($socks) {
+
+ foreach ($this->_WebaccessList as $key => $wau) {
+ if ($wau->_state == 'OPENED' && $wau->_socket)
+ $socks[] = $wau->_socket;
+ }
+ return $socks;
+ } // _getWebaccessReadSockets
+
+
+ private function _getWebaccessWriteSockets($socks) {
+
+ foreach ($this->_WebaccessList as $key => $wau) {
+ if (isset($wau->_spool[0]['State']) &&
+ ($wau->_spool[0]['State'] == 'OPEN' ||
+ $wau->_spool[0]['State'] == 'BAD' ||
+ $wau->_spool[0]['State'] == 'SEND')) {
+
+ if (($wau->_state == 'CLOSED' || $wau->_state == 'BAD') && !$wau->_socket)
+ $wau->_open();
+
+ if ($wau->_state == 'OPENED' && $wau->_socket)
+ $socks[] = $wau->_socket;
+ }
+ }
+ return $socks;
+ } // _getWebaccessWriteSockets
+
+
+ function getAllSpools() {
+
+ $num = 0;
+ $bad = 0;
+ foreach ($this->_WebaccessList as $key => $wau) {
+ if ($wau->_state == 'OPENED' || $wau->_state == 'CLOSED')
+ $num += count($wau->_spool);
+ elseif ($wau->_state == 'BAD')
+ $bad += count($wau->_spool);
+ }
+ return array($num, $bad);
+ } // getAllSpools
+} // class Webaccess
+
+
+// useful data to handle received headers
+$_wa_header_separator = array('cookie' => ';', 'set-cookie' => ';');
+$_wa_header_multi = array('set-cookie' => true);
+
+
+class WebaccessUrl {
+ //-----------------------------
+ // Fields
+ //-----------------------------
+
+ var $wa;
+ var $_host;
+ var $_port;
+ var $_compress_request;
+ var $_socket;
+ var $_state;
+ var $_keepalive;
+ var $_keepalive_timeout;
+ var $_keepalive_max;
+ var $_serv_keepalive_timeout;
+ var $_serv_keepalive_max;
+ var $_spool;
+ var $_wait;
+ var $_response;
+ var $_query_num;
+ var $_request_time;
+ var $_cookies;
+ var $_webaccess_str;
+ var $_bad_time;
+ var $_bad_timeout;
+ var $_read_time;
+ var $_agent;
+
+ // $_state values:
+ // 'OPENED' : socket is opened
+ // 'CLOSED' : socket is closed (asked, completed, or closed by server)
+ // 'BAD' : socket is closed, bad/error or beginning state
+
+ // $query['State'] values: (note: $query is added in $_spool, so $this->_spool[0] is the first $query to handle)
+ // 'BAD' : bad/error or beginning state
+ // 'OPEN' : should prepare request data then send them
+ // 'SEND' : request data are prepared, send them
+ // 'RECEIVE': request data are sent, receive reply data
+ // 'DONE' : request completed
+
+ //-----------------------------
+ // Methods
+ //-----------------------------
+
+ function WebaccessUrl(&$wa, $host, $port, $keepalive = true, $keepalive_timeout = 600, $keepalive_max = 300, $agent = 'XMLaccess') {
+ global $_web_access_compress_xmlrpc_request, $_web_access_retry_timeout;
+
+ $this->wa = &$wa;
+ $this->_host = $host;
+ $this->_port = $port;
+ $this->_webaccess_str = 'Webaccess (' . $this->_host . ':' . $this->_port . '): ';
+ $this->_agent = $agent;
+
+ // request compression setting
+ if ($_web_access_compress_xmlrpc_request == 'accept')
+ $this->_compress_request = 'accept';
+ elseif ($_web_access_compress_xmlrpc_request == 'force') {
+ if (function_exists('gzencode'))
+ $this->_compress_request = 'gzip';
+ elseif (function_exists('gzdeflate'))
+ $this->_compress_request = 'deflate';
+ else
+ $this->_compress_request = false;
+ }
+ elseif ($_web_access_compress_xmlrpc_request == 'force-gzip' && function_exists('gzencode'))
+ $this->_compress_request = 'gzip';
+ elseif ($_web_access_compress_xmlrpc_request == 'force-deflate' && function_exists('gzdeflate'))
+ $this->_compress_request = 'deflate';
+ else
+ $this->_compress_request = false;
+
+ $this->_socket = null;
+ $this->_state = 'CLOSED';
+ $this->_keepalive = $keepalive;
+ $this->_keepalive_timeout = $keepalive_timeout;
+ $this->_keepalive_max = $keepalive_max;
+ $this->_serv_keepalive_timeout = $keepalive_timeout;
+ $this->_serv_keepalive_max = $keepalive_max;
+ $this->_spool = array();
+ $this->_wait = false;
+ $this->_response = '';
+ $this->_query_num = 0;
+ $this->_query_time = time();
+ $this->_cookies = array();
+ $this->_bad_time = time();
+ $this->_bad_timeout = 0;
+ $this->_read_time = 0;
+ } // WebaccessUrl
+
+
+ // put connection in BAD state
+ function _bad($errstr, $isbad = true) {
+ global $aseco, $_web_access_retry_timeout;
+
+ $aseco->console($this->_webaccess_str . $errstr);
+ $this->infos();
+
+ if ($this->_socket)
+ @fclose($this->_socket);
+ $this->_socket = null;
+
+ if ($isbad) {
+ if (isset($this->_spool[0]['State']))
+ $this->_spool[0]['State'] = 'BAD';
+ $this->_state = 'BAD';
+
+ $this->_bad_time = time();
+ if ($this->_bad_timeout < $_web_access_retry_timeout)
+ $this->_bad_timeout = $_web_access_retry_timeout;
+ else
+ $this->_bad_timeout *= 2;
+
+ } else {
+ if (isset($this->_spool[0]['State']))
+ $this->_spool[0]['State'] = 'OPEN';
+ $this->_state = 'CLOSED';
+ }
+ $this->_callCallback($this->_webaccess_str . $errstr);
+ } // _bad
+
+
+ function retry() {
+ global $_web_access_retry_timeout;
+
+ if ($this->_state == 'BAD') {
+ $this->_bad_time = time();
+ $this->_bad_timeout = 0;
+ }
+ } // retry
+
+
+ //$query = array('Path' => $path,
+ // 'Callback' => $callback,
+ // 'QueryData' => $datas,
+ // 'IsXmlrpc' => $is_xmlrpc,
+ // 'KeepaliveMinTimeout' => $keepalive_min_timeout,
+ // 'OpenTimeout' => $opentimeout,
+ // 'WaitTimeout' => $waittimeout );
+ // will add: 'State', 'HDatas', 'Datas', 'DatasSize', 'DatasSent',
+ // 'Response', 'ResponseSize', 'Headers', 'Close', 'Times'
+ function request(&$query) {
+ global $aseco, $_web_access_compress_reply, $_web_access_post_xmlrpc, $_web_access_retry_timeout, $_web_access_retry_timeout_max;
+
+ $query['State'] = 'BAD';
+ $query['HDatas'] = '';
+ $query['Datas'] = '';
+ $query['DatasSize'] = 0;
+ $query['DatasSent'] = 0;
+ $query['Response'] = '';
+ $query['ResponseSize'] = 0;
+ $query['Headers'] = array();
+ $query['Close'] = false;
+ $query['Times'] = array('open' => array(-1.0,-1.0), 'send' => array(-1.0,-1.0), 'receive' => array(-1.0,-1.0,0));
+
+ // if asynch, in error, and maximal timeout, then forget the request and return false
+ if (($query['Callback'] != null) && ($this->_state == 'BAD')) {
+ if ($this->_bad_timeout > $_web_access_retry_timeout_max) {
+ $aseco->console($this->_webaccess_str . 'Request refused for consecutive errors (' . $this->_bad_timeout . ' / ' . $_web_access_retry_timeout_max . ')');
+ return false;
+
+ } else {
+ // if not max then accept the request and try a request (minimum $_web_access_retry_timeout/2 after previous try)
+ $time = time();
+ $timeout = ($this->_bad_timeout / 2) - ($time - $this->_bad_time);
+ if ($timeout < 0)
+ $timeout = 0;
+ $this->_bad_time = $time - $this->_bad_timeout + $timeout;
+ }
+ }
+
+ // build data to send
+ if (($query['Callback'] == null) || (is_array($query['Callback']) &&
+ isset($query['Callback'][0]) &&
+ is_callable($query['Callback'][0]))) {
+
+ if (is_string($query['QueryData']) && strlen($query['QueryData']) > 0) {
+ $msg = "POST " . $query['Path'] . " HTTP/1.1\r\n";
+ $msg .= "Host: " . $this->_host . "\r\n";
+ $msg .= "User-Agent: " . $this->_agent . "\r\n";
+ $msg .= "Cache-Control: no-cache\r\n";
+
+ if ($_web_access_compress_reply) {
+ // ask compression of response if gzdecode() and/or gzinflate() is available
+ if (function_exists('gzdecode') && function_exists('gzinflate'))
+ $msg .= "Accept-Encoding: deflate, gzip\r\n";
+ elseif (function_exists('gzdecode'))
+ $msg .= "Accept-Encoding: gzip\r\n";
+ elseif (function_exists('gzinflate'))
+ $msg .= "Accept-Encoding: deflate\r\n";
+ }
+
+ //echo "\nData:\n\n" . $query['QueryData'] . "\n";
+
+ if ($query['IsXmlrpc'] === true) {
+ if ($_web_access_post_xmlrpc) {
+ $msg .= "Content-type: application/x-www-form-urlencoded; charset=UTF-8\r\n";
+
+ //echo "\n=========================== Data =================================\n\n" . $datas . "\n";
+ //$d2 = urlsafe_base64_encode($datas);
+ //$d3 = urlsafe_base64_decode($d2);
+ //echo "\n--------------------------- Data ---------------------------------\n\n" . $d3 . "\n";
+
+ $query['QueryData'] = 'xmlrpc=' . urlsafe_base64_encode($query['QueryData']);
+ }
+ else {
+ $msg .= "Content-type: text/xml; charset=UTF-8\r\n";
+ }
+
+ if ($this->_compress_request == 'gzip' && function_exists('gzencode')) {
+ $msg .= "Content-Encoding: gzip\r\n";
+ $query['QueryData'] = gzencode($query['QueryData']);
+ } elseif ($this->_compress_request == 'deflate' && function_exists('gzdeflate')) {
+ $msg .= "Content-Encoding: deflate\r\n";
+ $query['QueryData'] = gzdeflate($query['QueryData']);
+ }
+
+ }
+ elseif (is_string($query['IsXmlrpc'])) {
+ $msg .= "Content-type: " . $query['IsXmlrpc'] . "\r\n";
+ $msg .= "Accept: */*\r\n";
+ } else {
+ $msg .= "Content-type: application/x-www-form-urlencoded; charset=UTF-8\r\n";
+ }
+ $msg .= "Content-length: " . strlen($query['QueryData']) . "\r\n";
+ $query['HDatas'] = $msg;
+ $query['State'] = 'OPEN';
+ $query['Retries'] = 0;
+
+ //print_r($msg);
+
+ // add the query in spool
+ $this->_spool[] = &$query;
+
+ if ($query['Callback'] == null) {
+ $this->_wait = true;
+ $this->_open($query['OpenTimeout'], $query['WaitTimeout']); // wait more in not callback mode
+ $this->_spool = array();
+ $this->_wait = false;
+ return $query['Response'];
+ } else
+ $this->_open();
+
+ } else {
+ $aseco->console($this->_webaccess_str . 'Bad data');
+ return false;
+ }
+
+ } else {
+ $aseco->console($this->_webaccess_str . 'Bad callback function: ' . $query['Callback']);
+ return false;
+ }
+ return true;
+ } // request
+
+
+ // open the socket (close it before if needed)
+ private function _open_socket($opentimeout = 0.0) {
+ global $aseco;
+
+ // if socket not opened, then open it (2 tries)
+ if (!$this->_socket || $this->_state != 'OPENED') {
+ $time = microtime(true);
+ $this->_spool[0]['Times']['open'][0] = $time;
+
+ $errno = '';
+ $errstr = '';
+ $this->_socket = @fsockopen($this->_host, $this->_port, $errno, $errstr, 1.8); // first try
+ if (!$this->_socket) {
+
+ if ($opentimeout >= 1.0)
+ $this->_socket = @fsockopen($this->_host, $this->_port, $errno, $errstr, $opentimeout);
+ if (!$this->_socket) {
+ $this->_bad('Error(' . $errno . ') ' . $errstr . ', connection failed!');
+ return;
+ }
+ }
+ $this->_state = 'OPENED';
+ //$aseco->console($this->_webaccess_str . 'connection opened!');
+
+ // new socket connection: reset all pending request original values
+ for ($i = 0; $i < count($this->_spool); $i++) {
+ $this->_spool[$i]['State'] = 'OPEN';
+ $this->_spool[$i]['DatasSent'] = 0;
+ $this->_spool[$i]['Response'] = '';
+ $this->_spool[$i]['Headers'] = array();
+ }
+ $this->_response = '';
+ $this->_query_num = 0;
+ $this->_query_time = time();
+ $time = microtime(true);
+ $this->_spool[0]['Times']['open'][1] = $time - $this->_spool[0]['Times']['open'][0];
+ }
+ } // _open_socket
+
+
+ // open the connection (if not already opened) and send
+ function _open($opentimeout = 0.0, $waittimeout = 5.0) {
+ global $aseco, $_web_access_retry_timeout_max;
+
+ if (!isset($this->_spool[0]['State']))
+ return false;
+ $time = time();
+
+ // if asynch, in error, then return false until timeout or if >max)
+ if (!$this->_wait && $this->_state == 'BAD' &&
+ (($this->_bad_timeout > $_web_access_retry_timeout_max) ||
+ (($time - $this->_bad_time) < $this->_bad_timeout))) {
+ //$aseco->console($this->_webaccess_str . 'wait to retry (' . ($time - $this->_bad_time) . ' / ' . $this->_bad_timeout . ')');
+ return false;
+ }
+
+ // if the socket is probably in timeout, close it
+ if ($this->_socket && $this->_state == 'OPENED' &&
+ ($this->_serv_keepalive_timeout <= ($time - $this->_query_time))) {
+ //$aseco->console($this->_webaccess_str . 'timeout, close it!');
+ $this->_state = 'CLOSED';
+ @fclose($this->_socket);
+ $this->_socket = null;
+ }
+
+ // if socket is not opened, open it
+ if (!$this->_socket || $this->_state != 'OPENED')
+ $this->_open_socket($opentimeout);
+
+ // if socket is open, send data if possible
+ if ($this->_socket) {
+ $this->_read_time = microtime(true);
+
+ // if wait (synchronous query) then go on all pending write/read until the last
+ if ($this->_wait) {
+ @stream_set_timeout($this->_socket, 0, 10000); // timeout 10 ms
+
+ while (isset($this->_spool[0]['State']) &&
+ ($this->_spool[0]['State'] == 'OPEN' ||
+ $this->_spool[0]['State'] == 'SEND' ||
+ $this->_spool[0]['State'] == 'RECEIVE')) {
+ //echo 'State=' . $this->_spool[0]['State'] . " (" . count($this->_spool) . ")\n";
+ if (!$this->_socket || $this->_state != 'OPENED')
+ $this->_open_socket($opentimeout);
+
+ if ($this->_spool[0]['State'] == 'OPEN') {
+ $time = microtime(true);
+ $this->_spool[0]['Times']['send'][0] = $time;
+ $this->_send($waittimeout);
+ }
+ elseif ($this->_spool[0]['State'] == 'SEND')
+ $this->_send($waittimeout);
+ elseif ($this->_spool[0]['State'] == 'RECEIVE')
+ $this->_receive($waittimeout*4);
+
+ // if timeout then error
+ if (($difftime = microtime(true) - $this->_read_time) > $waittimeout) {
+ $this->_bad('Request timeout, in _open (' . round($difftime) . ' > ' . $waittimeout . 's) state=' . $this->_spool[0]['State']);
+ return;
+ }
+ }
+ if ($this->_socket)
+ @stream_set_timeout($this->_socket, 0, 2000); // timeout 2 ms
+ }
+
+ // else just do a send on the current
+ elseif (isset($this->_spool[0]['State']) && $this->_spool[0]['State'] == 'OPEN') {
+ @stream_set_timeout($this->_socket, 0, 2000); // timeout 2 ms
+ $this->_send($waittimeout);
+ }
+ }
+ } // _open
+
+
+ function _send($waittimeout = 20) {
+
+ if (!isset($this->_spool[0]['State']))
+ return;
+
+ // if OPEN then become SEND
+ if ($this->_spool[0]['State'] == 'OPEN') {
+
+ $this->_spool[0]['State'] = 'SEND';
+ $time = microtime(true);
+ $this->_spool[0]['Times']['send'][0] = $time;
+ $this->_spool[0]['Response'] = '';
+ $this->_spool[0]['Headers'] = array();
+
+ // finish to prepare header and data to send
+ $msg = $this->_spool[0]['HDatas'];
+ if (!$this->_keepalive || ($this->_spool[0]['KeepaliveMinTimeout'] < 0) ||
+ ($this->_serv_keepalive_timeout < $this->_spool[0]['KeepaliveMinTimeout']) ||
+ ($this->_serv_keepalive_max <= ($this->_query_num + 2)) ||
+ ($this->_serv_keepalive_timeout <= (time() - $this->_query_time + 2))) {
+ $msg .= "Connection: close\r\n";
+ $this->_spool[0]['Close'] = true;
+ }
+ else {
+ $msg .= 'Keep-Alive: timeout=' . $this->_keepalive_timeout . ', max=' . $this->_keepalive_max
+ . "\r\nConnection: Keep-Alive\r\n";
+ }
+
+ // add cookie header
+ if (count($this->_cookies) > 0) {
+ $cookie_msg = '';
+ $sep = '';
+ foreach ($this->_cookies as $name => $cookie) {
+ if (!isset($cookie['path']) ||
+ strncmp($this->_spool[0]['Path'], $cookie['path'], strlen($cookie['path'])) == 0) {
+ $cookie_msg .= $sep . $name . '=' . $cookie['Value'];
+ $sep = '; ';
+ }
+ }
+ if ($cookie_msg != '')
+ $msg .= "Cookie: $cookie_msg\r\n";
+ }
+
+ $msg .= "\r\n";
+ $msg .= $this->_spool[0]['QueryData'];
+ $this->_spool[0]['Datas'] = $msg;
+ $this->_spool[0]['DatasSize'] = strlen($msg);
+ $this->_spool[0]['DatasSent'] = 0;
+
+ //print_r($msg);
+ }
+
+ // if not SEND then stop
+ if ($this->_spool[0]['State'] != 'SEND')
+ return;
+
+ do {
+ $sent = @stream_socket_sendto($this->_socket,
+ substr($this->_spool[0]['Datas'], $this->_spool[0]['DatasSent'],
+ ($this->_spool[0]['DatasSize'] - $this->_spool[0]['DatasSent'])));
+ if ($sent == false) {
+
+ $time = microtime(true);
+ $this->_spool[0]['Times']['send'][1] = $time - $this->_spool[0]['Times']['send'][0];
+ //var_dump($this->_spool[0]['Datas']);
+ $this->_bad('Error(' . $errno . ') ' . $errstr . ', could not send data! ('
+ . $sent . ' / ' . ($this->_spool[0]['DatasSize'] - $this->_spool[0]['DatasSent']) . ', '
+ . $this->_spool[0]['DatasSent'] . ' / ' . $this->_spool[0]['DatasSize'] . ')');
+ if ($this->_wait)
+ return;
+ break;
+
+ } else {
+ $this->_spool[0]['DatasSent'] += $sent;
+ if ($this->_spool[0]['DatasSent'] >= $this->_spool[0]['DatasSize']) {
+ // All is sent, prepare to receive the reply
+ $this->_query_num++;
+ $this->_query_time = time();
+
+ $time = microtime(true);
+ $this->_spool[0]['Times']['send'][1] = $time - $this->_spool[0]['Times']['send'][0];
+
+ //@stream_set_blocking($this->_socket, 0);
+ $this->_spool[0]['State'] = 'RECEIVE';
+ $this->_spool[0]['Times']['receive'][0] = $time;
+ }
+
+ // if timeout then error
+ elseif (($difftime = microtime(true) - $this->_read_time) > $waittimeout) {
+ $this->_bad('Request timeout, in _send (' . round($difftime) . ' > ' . $waittimeout . 's)');
+ }
+ }
+
+ // if not async-callback then continue until all is sent
+ } while ($this->_wait && isset($this->_spool[0]['State']) && ($this->_spool[0]['State'] == 'SEND'));
+ } // _send
+
+
+ function _receive($waittimeout = 40) {
+ global $aseco, $_Webaccess_last_response;
+
+ if (!$this->_socket || $this->_state != 'OPENED')
+ return;
+
+ $state = false;
+ $time0 = microtime(true);
+ $timeout = ($this->_wait) ? $waittimeout : 0;
+ do {
+ $r = array($this->_socket);
+ $w = null;
+ $e = null;
+ $nb = @stream_select($r, $w, $e, $timeout);
+ if ($nb === 0)
+ $nb = count($r);
+
+ while (!@feof($this->_socket) && $nb !== false && $nb > 0) {
+ $timeout = 0;
+
+ if (count($r) > 0) {
+ $res = @stream_socket_recvfrom($this->_socket, 8192);
+
+ if ($res == '') { // should not happen habitually, but...
+ break;
+ } elseif ($res !== false) {
+ $this->_response .= $res;
+ }
+ else {
+ if (isset($this->_spool[0])) {
+ $time = microtime(true);
+ $this->_spool[0]['Times']['receive'][1] = $time - $this->_spool[0]['Times']['receive'][0];
+ }
+ $this->_bad('Error(' . $errno . ') ' . $errstr . ', could not read all data!');
+ return;
+ }
+ }
+
+ // if timeout then error
+ if (($difftime = microtime(true) - $this->_read_time) > $waittimeout) {
+ $this->_bad('Request timeout, in _receive (' . round($difftime) . ' > ' . $waittimeout . 's)');
+ break;
+ }
+
+ $r = array($this->_socket);
+ $w = null;
+ $e = null;
+ $nb = @stream_select($r, $w, $e, $timeout);
+ if ($nb === 0)
+ $nb = count($r);
+ }
+
+ if (isset($this->_spool[0]['Times']['receive'][2])) {
+ $time = microtime(true);
+ $this->_spool[0]['Times']['receive'][2] += ($time - $time0);
+ }
+
+ // get headers and full message
+ $state = $this->_handleHeaders();
+ //echo "receive9\n";
+ //var_dump($state);
+
+ } while ($this->_wait && $state === false && $this->_socket && !@feof($this->_socket));
+
+ if (!isset($this->_spool[0]['State']) || $this->_spool[0]['State'] != 'RECEIVE') {
+ // in case of (probably keepalive) connection closed by server
+ if ($this->_socket && @feof($this->_socket)){
+ //$aseco->console($this->_webaccess_str . 'Socket closed by server (' . $this->_host . ')');
+ $this->_state = 'CLOSED';
+ @fclose($this->_socket);
+ $this->_socket = null;
+ }
+ return;
+ }
+
+ // terminated but incomplete! more than probably closed by server...
+ if ($state === false && $this->_socket && @feof($this->_socket)) {
+ $this->_state = 'CLOSED';
+ if (isset($this->_spool[0])) {
+ $time = microtime(true);
+ $this->_spool[0]['State'] = 'OPEN';
+ $this->_spool[0]['Times']['receive'][1] = $time - $this->_spool[0]['Times']['receive'][0];
+ }
+ if (strlen($this->_response) > 0) // if not 0 sized then show error message
+ $this->_bad('Error: closed with incomplete read: re-open socket and re-send! (' . strlen($this->_response) . ')');
+ else
+ $this->_bad('Closed by server when reading: re-open socket and re-send! (' . strlen($this->_response) . ')', false);
+
+ $this->_spool[0]['Retries']++;
+ if ($this->_spool[0]['Retries'] > 2) {
+ // 3 tries failed, remove entry from spool
+ $aseco->console($this->_webaccess_str . "failed {$this->_spool[0]['Retries']} times: skip current request");
+ array_shift($this->_spool);
+ }
+
+ return;
+ }
+
+ // reply is complete :)
+ if ($state === true) {
+ $this->_bad_timeout = 0; // reset error timeout
+
+ $this->_spool[0]['Times']['receive'][1] = $time - $this->_spool[0]['Times']['receive'][0];
+ $this->_spool[0]['State'] = 'DONE';
+
+ // store http/xml response in global $_Webaccess_last_response for debugging use
+ $_Webaccess_last_response = $this->_spool[0]['Response'];
+ //debugPrint('Webaccess->_receive - Response', $_Webaccess_last_response);
+
+ // call callback func
+ $this->_callCallback();
+ $this->_query_time = time();
+
+ if (!$this->_keepalive || $this->_spool[0]['Close']) {
+ //if ($this->_spool[0]['Close'])
+ // $aseco->console($this->_webaccess_str . 'close connection (asked in headers)');
+ $this->_state = 'CLOSED';
+ @fclose($this->_socket);
+ $this->_socket = null;
+ }
+
+ $this->infos();
+
+ // request completed, remove it from spool!
+ array_shift($this->_spool);
+ }
+ } // _receive
+
+ private function _callCallback($error = null) {
+
+ // store optional error message
+ if ($error !== null)
+ $this->_spool[0]['Response']['Error'] = $error;
+
+ // call callback func
+ if (isset($this->_spool[0]['Callback'])) {
+ $callbackinfo = $this->_spool[0]['Callback'];
+ if (isset($callbackinfo[0]) && is_callable($callbackinfo[0])) {
+ $callback_func = $callbackinfo[0];
+ $callbackinfo[0] = $this->_spool[0]['Response'];
+ call_user_func_array($callback_func, $callbackinfo);
+ }
+ }
+ }
+
+ private function _handleHeaders() {
+ global $aseco, $_wa_header_separator, $_wa_header_multi;
+
+ if (!isset($this->_spool[0]['State']))
+ return false;
+
+ if (strlen($this->_response) < 8) // not enough data, continue read
+ return false;
+ if (strncmp($this->_response, 'HTTP/', 5) != 0) { // not HTTP!
+ $this->_bad("Error, not HTTP response ! **********\n" . substr($this->_response, 0, 300) . "\n***************\n");
+ return null;
+ }
+
+ // separate headers and data
+ $datas = explode("\r\n\r\n", $this->_response, 2);
+ if (count($datas) < 2) {
+ $datas = explode("\n\n", $this->_response, 2);
+ if (count($datas) < 2) {
+ $datas = explode("\r\r", $this->_response, 2);
+ if (count($datas) < 2)
+ return false; // not complete headers, continue read
+ }
+ }
+
+ // get headers if not done on previous read
+ if (!isset($this->_spool[0]['Headers']['Command'][0])) {
+ // separate headers
+ //echo "Get Headers! (" . strlen($datas[0]) . ")\n";
+
+ $headers = array();
+ $heads = explode("\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $datas[0])));
+ if (count($heads) < 2) {
+ $this->_bad("Error, uncomplete headers! **********\n" . $datas[0] . "\n***************\n");
+ return null;
+ }
+
+ $headers['Command'] = explode(' ', $heads[0], 3);
+
+ for ($i = 1; $i < count($heads); $i++) {
+ $header = explode(':', $heads[$i], 2);
+ if (count($header) > 1) {
+ $headername = strtolower(trim($header[0]));
+ if (isset($_wa_header_separator[$headername]))
+ $sep = $_wa_header_separator[$headername];
+ else
+ $sep = ',';
+ if (isset($_wa_header_multi[$headername]) && $_wa_header_multi[$headername]) {
+ if (!isset($headers[$headername]))
+ $headers[$headername] = array();
+ $headers[$headername][] = explode($sep, trim($header[1]));
+ } else
+ $headers[$headername] = explode($sep, trim($header[1]));
+ }
+ }
+
+ if (isset($headers['content-length'][0]))
+ $headers['content-length'][0] += 0; // convert to int
+
+ $this->_spool[0]['Headers'] = $headers;
+
+ // add header specific info in case of Dedimania reply
+ if (isset($headers['server'][0]))
+ $this->_webaccess_str = 'Webaccess (' . $this->_host . ':' . $this->_port .'/'. $headers['server'][0] . '): ';
+ }
+ else {
+ $headers = &$this->_spool[0]['Headers'];
+ //echo "Previous Headers! (" . strlen($datas[0]) . ")\n";
+ }
+
+ // get real message
+ $datasize = strlen($datas[1]);
+ if (isset($headers['content-length'][0]) && $headers['content-length'][0] >= 0) {
+ //echo 'mess_size0=' . strlen($datas[1]) . "\n";
+
+ if ($headers['content-length'][0] > $datasize) // incomplete message
+ return false;
+
+ elseif ($headers['content-length'][0] < $datasize) {
+ $message = substr($datas[1], 0, $headers['content-length'][0]);
+ // remaining buffer for next reply
+ $this->_response = substr($datas[1], $headers['content-length'][0]);
+ }
+ else {
+ $message = $datas[1];
+ $this->_response = '';
+ }
+ $this->_spool[0]['ResponseSize'] = strlen($datas[0]) + 4 + $headers['content-length'][0];
+ }
+
+ // get real message when reply is chunked
+ elseif (isset($headers['transfer-encoding'][0]) && $headers['transfer-encoding'][0] == 'chunked') {
+
+ // get chunk size and make message with chunks data
+ $size = -1;
+ $chunkpos = 0;
+ if (($datapos = strpos($datas[1], "\r\n", $chunkpos)) !== false) {
+ $message = '';
+ $chunk = explode(';', substr($datas[1], $chunkpos, $datapos - $chunkpos));
+ $size = hexdec($chunk[0]);
+ //debugPrint("Webaccess->Response - chunk - $chunkpos, $datapos, $size (" . strlen($datas[1]) . ")", $chunk);
+ while ($size > 0) {
+ if ($datapos + 2 + $size > $datasize) // incomplete message
+ return false;
+ $message .= substr($datas[1], $datapos + 2, $size);
+ $chunkpos = $datapos + 2 + $size + 2;
+ if (($datapos = strpos($datas[1], "\r\n", $chunkpos)) !== false) {
+ $chunk = explode(';', substr($datas[1], $chunkpos, $datapos - $chunkpos));
+ $size = hexdec($chunk[0]);
+ } else
+ $size = -1;
+ //debugPrint("Webaccess->Response - chunk - $chunkpos, $datapos, $size (" . strlen($datas[1]) . ")", $chunk);
+ }
+
+ }
+ if ($size < 0) // error bad size or incomplete message
+ return false;
+
+ if (strpos($datas[1], "\r\n\r\n", $chunkpos) === false) // incomplete message: end is missing
+ return false;
+
+ // store complete message size
+ $msize = strlen($message);
+ $headers['transfer-encoding'][1] = 'total_size=' . $msize; // add message size after 'chunked' for information
+ $this->_spool[0]['ResponseSize'] = strlen($datas[0]) + 4 + $msize;
+
+ // after the message itself...
+ $message_end = explode("\r\n\r\n", substr($datas[1], $chunkpos), 2);
+
+ // add end headers if any
+ $heads = explode("\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $message_end[0])));
+ for ($i = 1; $i < count($heads); $i++) {
+ $header = explode(':', $heads[$i], 2);
+ if (count($header) > 1) {
+ $headername = strtolower(trim($header[0]));
+ if (isset($_wa_header_separator[$headername]))
+ $sep = $_wa_header_separator[$headername];
+ else
+ $sep = ',';
+ if (isset($_wa_header_multi[$headername]) && $_wa_header_multi[$headername]) {
+ if (!isset($headers[$headername]))
+ $headers[$headername] = array();
+ $headers[$headername][] = explode($sep, trim($header[1]));
+ } else
+ $headers[$headername] = explode($sep, trim($header[1]));
+ }
+ }
+ $this->_spool[0]['Headers'] = $headers;
+
+ // remaining buffer for next reply
+ if (isset($message_end[1]) && strlen($message_end[1]) > 0) {
+ $this->_response = $message_end[1];
+ }
+ else
+ $this->_response = '';
+ }
+ // no content-length and not chunked!
+ else {
+ $this->_bad("Error, bad http, no content-length and not chunked! **********\n" . $datas[0] . "\n***************\n");
+ return null;
+ }
+
+ //echo 'mess_size1=' . strlen($message) . "\n";
+
+ // if Content-Encoding: gzip or Content-Encoding: deflate
+ if (isset($headers['content-encoding'][0])) {
+ if ($headers['content-encoding'][0] == 'gzip')
+ $message = @gzdecode($message);
+ elseif ($headers['content-encoding'][0] == 'deflate')
+ $message = @gzinflate($message);
+ }
+
+ // if Accept-Encoding: gzip or deflate
+ if ($this->_compress_request == 'accept' && isset($headers['accept-encoding'][0])) {
+ foreach ($headers['accept-encoding'] as $comp) {
+ $comp = trim($comp);
+ if ($comp == 'gzip' && function_exists('gzencode')) {
+ $this->_compress_request = 'gzip';
+ break;
+ }
+ elseif ($comp == 'deflate' && function_exists('gzdeflate')) {
+ $this->_compress_request = 'deflate';
+ break;
+ }
+ }
+ if ($this->_compress_request == 'accept')
+ $this->_compress_request = false;
+
+ $aseco->console($this->_webaccess_str . 'send: ' . ($this->_compress_request === false ? 'no compression' : $this->_compress_request)
+ . ', receive: ' . (isset($headers['content-encoding'][0]) ? $headers['content-encoding'][0] : 'no compression'));
+ }
+
+ // get cookies values
+ if (isset($headers['set-cookie'])) {
+ foreach ($headers['set-cookie'] as $cookie) {
+ $cook = explode('=', $cookie[0], 2);
+ if (count($cook) > 1) {
+ // set main cookie value
+ $cookname = trim($cook[0]);
+ if (!isset($this->_cookies[$cookname]))
+ $this->_cookies[$cookname] = array();
+ $this->_cookies[$cookname]['Value'] = trim($cook[1]);
+
+ // set cookie options
+ for ($i = 1; $i < count($cookie); $i++) {
+ $cook = explode('=', $cookie[$i], 2);
+ $cookarg = strtolower(trim($cook[0]));
+ if (isset($cook[1]))
+ $this->_cookies[$cookname][$cookarg] = trim($cook[1]);
+ }
+ }
+ }
+ //debugPrint('SET-COOKIES: ', $headers['set-cookie']);
+ //debugPrint('STORED COOKIES: ', $this->_cookies);
+ }
+
+ // if the server reply ask to close, then close
+ if (!isset($headers['connection'][0]) || $headers['connection'][0] == 'close') {
+ //if (!$this->_spool[0]['Close'])
+ // $aseco->console($this->_webaccess_str . 'server ask to close connection');
+ $this->_spool[0]['Close'] = true;
+ }
+
+ // verify server keepalive value and use them if lower
+ if (isset($headers['keep-alive'])) {
+ $kasize = count($headers['keep-alive']);
+ for ($i = 0; $i < $kasize; $i++) {
+ $keep = explode('=', $headers['keep-alive'][$i], 2);
+ if (count($keep) > 1)
+ $headers['keep-alive'][trim(strtolower($keep[0]))] = intval(trim($keep[1]));
+ }
+ if (isset($headers['keep-alive']['timeout']))
+ $this->_serv_keepalive_timeout = $headers['keep-alive']['timeout'];
+ if (isset($headers['keep-alive']['max']))
+ $this->_serv_keepalive_max = $headers['keep-alive']['max'];
+ //$aseco->console($this->_webaccess_str . 'max=' . $this->_serv_keepalive_max . ', timeout=' . $this->_serv_keepalive_timeout . "\n");
+ }
+
+ // store complete reply message for the request
+ $this->_spool[0]['Response'] = array('Code' => intval($headers['Command'][1]),
+ 'Reason' => $headers['Command'][2],
+ 'Headers' => $headers,
+ 'Message' => $message
+ );
+ //echo 'mess_size2=' . strlen($message) . "\n";
+ return true;
+ } // _handleHeaders
+
+
+ function infos() {
+ global $aseco;
+
+ $size = (isset($this->_spool[0]['Response']['Message'])) ? strlen($this->_spool[0]['Response']['Message']) : 0;
+ $msg = $this->_webaccess_str
+ . sprintf('[%s,%s]: %0.3f / %0.3f / %0.3f (%0.3f) / %d [%d,%d,%d]',
+ $this->_state, $this->_spool[0]['State'],
+ $this->_spool[0]['Times']['open'][1],
+ $this->_spool[0]['Times']['send'][1],
+ $this->_spool[0]['Times']['receive'][1],
+ $this->_spool[0]['Times']['receive'][2],
+ $this->_query_num, $this->_spool[0]['DatasSize'],
+ $size, $this->_spool[0]['ResponseSize']);
+ //$aseco->console($msg);
+ } // infos
+} // class WebaccessUrl
+
+
+// use: list($host, $port, $path) = getHostPortPath($url);
+function getHostPortPath($url) {
+
+ $http_pos = strpos($url, 'http://');
+ if ($http_pos !== false) {
+ $script = explode('/', substr($url, $http_pos + 7), 2);
+ if (isset($script[1]))
+ $path = '/' . $script[1];
+ else
+ $path = '/';
+ $serv = explode(':', $script[0], 2);
+ $host = $serv[0];
+ if (isset($serv[1]))
+ $port = (int)$serv[1];
+ else
+ $port = 80;
+ if (strlen($host) > 2)
+ return array($host, $port, $path);
+ }
+ return array(false, false, false);
+} // getHostPortPath
+
+
+// gzdecode() workaround
+if (!function_exists('gzdecode') && function_exists('gzinflate')) {
+
+ function gzdecode($data) {
+
+ $len = strlen($data);
+ if ($len < 18 || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
+ return null; // Not GZIP format (See RFC 1952)
+ }
+ $method = ord(substr($data, 2, 1)); // Compression method
+ $flags = ord(substr($data, 3, 1)); // Flags
+ if ($flags & 31 != $flags) {
+ // Reserved bits are set -- NOT ALLOWED by RFC 1952
+ return null;
+ }
+ // NOTE: $mtime may be negative (PHP integer limitations)
+ $mtime = unpack('V', substr($data, 4, 4));
+ $mtime = $mtime[1];
+ $xfl = substr($data, 8, 1);
+ $os = substr($data, 8, 1);
+ $headerlen = 10;
+ $extralen = 0;
+ $extra = '';
+ if ($flags & 4) {
+ // 2-byte length prefixed EXTRA data in header
+ if ($len - $headerlen - 2 < 8) {
+ return false; // Invalid format
+ }
+ $extralen = unpack('v', substr($data, 8, 2));
+ $extralen = $extralen[1];
+ if ($len - $headerlen - 2 - $extralen < 8) {
+ return false; // Invalid format
+ }
+ $extra = substr($data, 10, $extralen);
+ $headerlen += $extralen + 2;
+ }
+
+ $filenamelen = 0;
+ $filename = '';
+ if ($flags & 8) {
+ // C-style string file NAME data in header
+ if ($len - $headerlen - 1 < 8) {
+ return false; // Invalid format
+ }
+ $filenamelen = strpos(substr($data, 8 + $extralen), chr(0));
+ if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) {
+ return false; // Invalid format
+ }
+ $filename = substr($data, $headerlen, $filenamelen);
+ $headerlen += $filenamelen + 1;
+ }
+
+ $commentlen = 0;
+ $comment = '';
+ if ($flags & 16) {
+ // C-style string COMMENT data in header
+ if ($len - $headerlen - 1 < 8) {
+ return false; // Invalid format
+ }
+ $commentlen = strpos(substr($data, 8 + $extralen + $filenamelen), chr(0));
+ if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) {
+ return false; // Invalid header format
+ }
+ $comment = substr($data, $headerlen, $commentlen);
+ $headerlen += $commentlen + 1;
+ }
+
+ $headercrc = '';
+ if ($flags & 1) {
+ // 2-bytes (lowest order) of CRC32 on header present
+ if ($len - $headerlen - 2 < 8) {
+ return false; // Invalid format
+ }
+ $calccrc = crc32(substr($data, 0, $headerlen)) & 0xffff;
+ $headercrc = unpack('v', substr($data, $headerlen, 2));
+ $headercrc = $headercrc[1];
+ if ($headercrc != $calccrc) {
+ return false; // Bad header CRC
+ }
+ $headerlen += 2;
+ }
+
+ // GZIP FOOTER - These be negative due to PHP's limitations
+ $datacrc = unpack('V', substr($data, -8, 4));
+ $datacrc = $datacrc[1];
+ $isize = unpack('V', substr($data, -4));
+ $isize = $isize[1];
+
+ // Perform the decompression:
+ $bodylen = $len - $headerlen - 8;
+ if ($bodylen < 1) {
+ // This should never happen - IMPLEMENTATION BUG!
+ return null;
+ }
+ $body = substr($data, $headerlen, $bodylen);
+ $data = '';
+ if ($bodylen > 0) {
+ switch ($method) {
+ case 8:
+ // Currently the only supported compression method:
+ $data = gzinflate($body);
+ break;
+ default:
+ // Unknown compression method
+ return false;
+ }
+ } else {
+ // I'm not sure if zero-byte body content is allowed.
+ // Allow it for now... Do nothing...
+ }
+
+ // Verify decompressed size and CRC32:
+ // NOTE: This may fail with large data sizes depending on how
+ // PHP's integer limitations affect strlen() since $isize
+ // may be negative for large sizes
+ if ($isize != strlen($data) || crc32($data) != $datacrc) {
+ // Bad format! Length or CRC doesn't match!
+ return false;
+ }
+ return $data;
+ } // gzdecode
+}
+?>
diff --git a/xaseco/includes/xmlparser.inc.php b/xaseco/includes/xmlparser.inc.php
new file mode 100644
index 0000000..e53d80c
--- /dev/null
+++ b/xaseco/includes/xmlparser.inc.php
@@ -0,0 +1,121 @@
+stack = array();
+ $this->struct = array();
+ $this->utf8enc = $utf8enc;
+
+ // create the parser
+ $this->parser = xml_parser_create();
+ xml_set_object($this->parser, $this);
+ xml_set_element_handler($this->parser, 'openTag', 'closeTag');
+ xml_set_character_data_handler($this->parser, 'tagData');
+
+ // load the xml file
+ if ($isfile)
+ $this->data = @file_get_contents($source);
+ else
+ $this->data = $source;
+
+ // escape '&' characters
+ $this->data = str_replace('&', '', $this->data);
+
+ // parse xml file
+ $parsed = xml_parse($this->parser, $this->data);
+
+ // display errors
+ if (!$parsed) {
+ $code = xml_get_error_code($this->parser);
+ $err = xml_error_string($code);
+ $line = xml_get_current_line_number($this->parser);
+ trigger_error("[XML Error $code] $err on line $line", E_USER_WARNING);
+ return false;
+ }
+ return $this->struct;
+ }
+
+ private function openTag($parser, $name, $attributes) {
+ $this->stack[] = $name;
+ $this->struct[$name] = '';
+ }
+
+ private function tagData($parser, $data) {
+ if (trim($data)) {
+ $index = $this->stack[count($this->stack)-1];
+ // use raw, don't decode '+' into space
+ if ($this->utf8enc)
+ $this->struct[$index] .= rawurldecode($data);
+ else
+ $this->struct[$index] .= utf8_decode(rawurldecode($data));
+ }
+ }
+
+ private function closeTag($parser, $name) {
+ if (count($this->stack) > 1) {
+ $from = array_pop($this->stack);
+ $to = $this->stack[count($this->stack)-1];
+ $this->struct[$to][$from][] = $this->struct[$from];
+ unset($this->struct[$from]);
+ }
+ }
+
+ /**
+ * Parses an array into an XML structure.
+ */
+ function parseArray($array) {
+ $xmlstring = '';
+ $xmlstring .= $this->parseArrayElements($array);
+ return $xmlstring;
+ }
+
+ private function parseArrayElements($array, $opt_tag = '') {
+
+ // read each element of the array
+ for ($i = 0; $i < count($array); $i++) {
+
+ // check if array is associative
+ if (is_numeric(key($array))) {
+ $xml .= '<'.$opt_tag.'>';
+ if (is_array(current($array))) {
+ $xml .= $this->parseArrayElements(current($array), key($array));
+ } else {
+ // use raw, don't encode space into '+'
+ $xml .= rawurlencode(utf8_encode(current($array)));
+ }
+ $xml .= ''.$opt_tag.'>';
+ } else {
+ if (is_array(current($array))) {
+ $xml .= $this->parseArrayElements(current($array), key($array));
+ } else {
+ $xml .= '<'.key($array).'>';
+ // use raw, don't encode space into '+'
+ $xml .= rawurlencode(utf8_encode(current($array)));
+ $xml .= ''.key($array).'>';
+ }
+ }
+ next($array);
+ }
+ return $xml;
+ }
+}
+?>
diff --git a/xaseco/includes/xmlrpc_db.inc.php b/xaseco/includes/xmlrpc_db.inc.php
new file mode 100644
index 0000000..e19a930
--- /dev/null
+++ b/xaseco/includes/xmlrpc_db.inc.php
@@ -0,0 +1,385 @@
+_debug = 0; // max debug level = 3
+ $this->_webaccess = $webaccess;
+ $this->_url = $url;
+ $this->_server = array('Game' => $game,
+ 'Login' => $login,
+ 'Password' => $password,
+ 'Tool' => $tool,
+ 'Version' => $version,
+ 'Nation' => $nation,
+ 'Packmask' => $packmask,
+ 'PlayersGame' => true
+ );
+ $this->_auth_cb = array('xmlrpc_auth_cb');
+
+ $this->_bad = false;
+ $this->_bad_time = -1;
+ // in case webaccess URL connection was previously in error, ask to retry
+ $this->_webaccess->retry($this->_url);
+
+ // prepare to add requests
+ $this->_initRequest();
+ } // XmlrpcDB
+
+ // change the packmask value
+ function setPackmask($packmask = '') {
+
+ $this->_server['Packmask'] = $packmask;
+ } // setPackmask
+
+ // is the connection in recurrent error?
+ function isBad() {
+
+ return $this->_bad;
+ } // isBad
+
+ // get time since the error state was set
+ function badTime() {
+
+ return (time() - $this->_bad_time);
+ } // badTime
+
+ // stop the bad state: will try again at next RequestWait(),
+ // sendRequestsWait() or sendRequests()
+ function retry() {
+
+ $this->_bad = false;
+ $this->_bad_time = -1;
+ // set webaccess object to retry on that URL too
+ $this->_webaccess->retry($this->_url);
+ } // retry
+
+ // clear all requests, and get them if asked
+ function clearRequests($get_requests = false) {
+
+ if ($get_requests) {
+ $return = array($_requests, $_callbacks);
+ $this->_initRequest();
+ return $return;
+ }
+ $this->_initRequest();
+ } // clearRequests
+
+ // add a request
+ function addRequest($callback, $method) {
+
+ $args = func_get_args();
+ $callback = array_shift($args);
+ $method = array_shift($args);
+ return $this->addRequestArray($callback, $method, $args);
+ } // addRequest
+
+ // add a request
+ function addRequestArray($callback, $method, $args) {
+
+ $this->_callbacks[] = $callback;
+ $this->_requests[] = array('methodName' => $method, 'params' => $args);
+ return count($this->_requests) - 1;
+ } // addRequestArray
+
+ // send added requests, callbacks will be called when response come
+ function sendRequests() {
+ global $aseco;
+
+ if (count($this->_callbacks) > 1) {
+ $this->addRequest(null, 'dedimania.WarningsAndTTR');
+ $webdatas = $this->_makeXMLdatas();
+ $response = $this->_webaccess->request($this->_url,
+ array( array($this, '_callCB'), $this->_callbacks, $this->_requests),
+ $webdatas, true);
+ $this->_initRequest();
+ if ($response === false) {
+ if (!$this->_bad) {
+ $this->_bad = true;
+ $this->_bad_time = time();
+ }
+ if ($this->_debug > 2)
+ $aseco->console_text('XmlrpcDB->sendRequests - this' . CRLF . print_r($this, true));
+ return false;
+ }
+ }
+ return true;
+ } // sendRequests
+
+ // send added requests, wait response, then call callbacks
+ function sendRequestsWait() {
+ global $aseco;
+
+ if (count($this->_callbacks) > 1) {
+ $this->addRequest(null, 'dedimania.WarningsAndTTR');
+ $webdatas = $this->_makeXMLdatas();
+ $response = $this->_webaccess->request($this->_url,
+ null, $webdatas, true);
+ if ($response === false) {
+ if (!$this->_bad) {
+ $this->_bad = true;
+ $this->_bad_time = time();
+ }
+ if ($this->_debug > 0)
+ $aseco->console_text('XmlrpcDB->sendRequestsWait - this' . CRLF . print_r($this, true));
+ $this->_initRequest();
+ return false;
+ } else {
+ $this->_callCB($response, $this->_callbacks, $this->_requests);
+ $this->_initRequest();
+ }
+ }
+ return true;
+ } // sendRequestsWait
+
+ // send a request, wait response, and return the response
+ function RequestWait($method) {
+
+ $args = func_get_args();
+ $method = array_shift($args);
+ return $this->RequestWaitArray($method, $args);
+ } // RequestWait
+
+ // send a request, wait response, and return the response
+ function RequestWaitArray($method, $args) {
+ global $aseco;
+
+ if ($this->sendRequestsWait() === false) {
+ if (!$this->_bad) {
+ $this->_bad = true;
+ $this->_bad_time = time();
+ }
+ return false;
+ }
+
+ $reqnum = $this->addRequestArray(null, $method, $args);
+
+ $this->addRequest(null, 'dedimania.WarningsAndTTR');
+ $webdatas = $this->_makeXMLdatas();
+ $response = $this->_webaccess->request($this->_url, null, $webdatas, true);
+ if (isset($response['Message']) && is_string($response['Message'])) {
+ if ($this->_debug > 1)
+ $aseco->console_text('XmlrpcDB->RequestWaitArray() - response[Message]' . CRLF . print_r($response['Message'], true));
+
+ $xmlrpc_message = new IXR_Message($response['Message']);
+ if ($xmlrpc_message->parse() && $xmlrpc_message->messageType != 'fault') {
+ if ($this->_debug > 1) {
+ $aseco->console_text('XmlrpcDB->RequestWaitArray() - message' . CRLF . print_r($xmlrpc_message->message, true));
+ $aseco->console_text('XmlrpcDB->RequestWaitArray() - params' . CRLF . print_r($xmlrpc_message->params, true));
+ }
+
+ //$datas = array('methodName' => $xmlrpc_message->methodName, 'params' => $xmlrpc_message->params);
+ $datas = $this->_makeResponseDatas($xmlrpc_message->methodName, $xmlrpc_message->params, $this->_requests);
+ } else {
+ if ($this->_debug > 0)
+ $aseco->console_text('XmlrpcDB->RequestWaitArray() - message fault' . CRLF . print_r($xmlrpc_message->message, true));
+ $datas = array();
+ }
+ } else {
+ $datas = array();
+ }
+
+ if ($this->_debug > 0)
+ $aseco->console_text('XmlrpcDB->RequestWaitArray() - datas' . CRLF . print_r($datas, true));
+ if (isset($datas['params']) && isset($datas['params'][$reqnum])) {
+ $response['Data'] = $datas['params'][$reqnum];
+ $param_end = end($datas['params']);
+ if (isset($param_end['globalTTR']) && !isset($response['Data']['globalTTR']))
+ $response['Data']['globalTTR'] = $param_end['globalTTR'];
+ } else {
+ $response['Data'] = $datas;
+ }
+ if ($this->_debug > 0)
+ $aseco->console_text('XmlrpcDB->RequestWaitArray() - response[Data]' . CRLF . print_r($response['Data'], true));
+
+ $this->_initRequest();
+ return $response;
+ } // RequestWaitArray
+
+ // init the request and callback array
+ function _initRequest() {
+
+ $this->_callbacks = array();
+ $this->_requests = array();
+ $this->addRequest($this->_auth_cb, 'dedimania.Authenticate', $this->_server);
+ } // _initRequest
+
+ // make the xmlrpc string, encode it in base64, and pass it as value
+ // to xmlrpc URL post parameter
+ function _makeXMLdatas() {
+ global $aseco;
+
+ $xmlrpc_request = new IXR_RequestStd('system.multicall', $this->_requests);
+ if ($this->_debug > 1)
+ $aseco->console_text('XmlrpcDB->_makeXMLdatas() - getXml()' . CRLF . print_r($xmlrpc_request->getXml(), true));
+ return $xmlrpc_request->getXml();
+ } // _makeXMLdatas
+
+ function _callCB($response, $callbacks, $requests) {
+ global $aseco;
+
+ $globalTTR = 0;
+ if (isset($response['Message']) && is_string($response['Message'])) {
+ $xmlrpc_message = new IXR_Message($response['Message']);
+ if ($xmlrpc_message->parse() && $xmlrpc_message->messageType != 'fault') {
+ if ($this->_debug > 1)
+ $aseco->console_text('XmlrpcDB->_callCB() - message' . CRLF . print_r($xmlrpc_message->message, true));
+
+ //$datas = array('methodName' => $xmlrpc_message->methodName, 'params' => $xmlrpc_message->params);
+ $datas = $this->_makeResponseDatas($xmlrpc_message->methodName, $xmlrpc_message->params, $requests);
+
+ if (isset($datas['params']) && is_array($datas['params'])) {
+ $param_end = end($datas['params']);
+ if (isset($param_end['globalTTR']))
+ $globalTTR = $param_end['globalTTR'];
+ } else {
+ if ($this->_debug > 0)
+ $aseco->console_text('XmlrpcDB->_callCB() - message fault' . CRLF . print_r($xmlrpc_message->message, true));
+ $datas = array();
+ }
+ } else {
+ if ($this->_debug > 0)
+ $aseco->console_text('XmlrpcDB->_callCB() - message fault' . CRLF . print_r($xmlrpc_message->message, true));
+ $datas = array();
+ }
+ } else {
+ if (!isset($response['Error']) ||
+ (strpos($response['Error'], 'connection failed') === false && strpos($response['Error'], 'Request timeout') === false)) {
+ $infos = array('Url'=>$this->_url, 'Requests'=>$requests, 'Callbacks'=>$callbacks, 'Response'=>$response);
+ $serinfos = serialize($infos);
+ $aseco->console_text('XmlrpcDB->_callCB() - no response message:' . CRLF . $serinfos);
+ }
+ $datas = array();
+ }
+
+ for ($i = 0; $i < count($callbacks); $i++) {
+ if ($callbacks[$i] != null) {
+ $callback = $callbacks[$i][0];
+ if (isset($datas['params']) && isset($datas['params'][$i])) {
+ $response['Data'] = $datas['params'][$i];
+ if (!isset($response['Data']['globalTTR']))
+ $response['Data']['globalTTR'] = $globalTTR;
+ } else {
+ $response['Data'] = $datas;
+ }
+ $callbacks[$i][0] = $response;
+ call_user_func_array($callback, $callbacks[$i]);
+ }
+ }
+ } // _callCB
+
+ // build the datas array from XAseco or Dedimania server
+ // remove the first array level into params if needed
+ // add methodResponse name if needed
+ // rename sub responses params array from [0] to ['params'] if needed
+ function _makeResponseDatas($methodname, $params, $requests) {
+ global $aseco;
+
+ if (is_array($params) && count($params) == 1 && is_array($params[0]))
+ $params = $params[0];
+ if ($this->_debug > 2)
+ $aseco->console_text('XmlrpcDB->_makeResponseDatas() - requests' . CRLF . print_r($requests, true));
+ if ($this->_debug > 1)
+ $aseco->console_text('XmlrpcDB->_makeResponseDatas() - params' . CRLF . print_r($params, true));
+
+ if (is_array($params) && is_array($params[0]) && !isset($params[0]['methodResponse'])) {
+ $params2 = array();
+ foreach ($params as $key => $param) {
+ $errors = null;
+ if (isset($param['faultCode'])) {
+ $errors[] = array('Code' => $param['faultCode'], 'Message' => $param['faultString']);
+ }
+
+ if (isset($requests[$key]['methodName']))
+ $methodresponse = $requests[$key]['methodName'];
+ else
+ $methodresponse = 'Unknown';
+
+ $ttr = 0.000001;
+
+ if (isset($param[0]))
+ $param = $param[0];
+ else
+ $param = array();
+
+ $params2[$key] = array('methodResponse' => $methodresponse,
+ 'params' => $param,
+ 'errors' => $errors,
+ 'TTR' => $ttr,
+ 'globalTTR' => $ttr
+ );
+
+ if ($methodresponse == 'dedimania.WarningsAndTTR') {
+ if ($this->_debug > 1) {
+ $aseco->console_text('XmlrpcDB->_makeResponseDatas() - param' . CRLF . print_r($param, true));
+ $aseco->console_text('XmlrpcDB->_makeResponseDatas() - params2' . CRLF . print_r($params2, true));
+ }
+ $globalTTR = $param['globalTTR'];
+ foreach ($param['methods'] as $key3 => $param3) {
+ $key2 = 0;
+ while ($key2 < count($params2) && $params2[$key2]['methodResponse'] != $param3['methodName']) {
+ $params2[$key2]['globalTTR'] = $globalTTR;
+ $key2++;
+ }
+ if ($this->_debug > 1)
+ $aseco->console_text("XmlrpcDB->_makeResponseDatas() - key2=$key2 - key3=$key3 - param3" . CRLF . print_r($param3, true));
+ if ($key2 < count($params2)) {
+ $params2[$key2]['errors'] = $param3['errors'];
+ $params2[$key2]['TTR'] = $param3['TTR'];
+ $params2[$key2]['globalTTR'] = $globalTTR;
+ }
+ }
+ }
+ }
+ if ($this->_debug > 1)
+ $aseco->console_text('XmlrpcDB->_makeResponseDatas() - params2' . CRLF . print_r($params2, true));
+ return array('methodName' => $methodname, 'params' => $params2);
+
+ } else {
+ return array('methodName' => $methodname, 'params' => $params);
+ }
+ } // _makeResponseDatas
+} // class XmlrpcDB
+
+
+// Dedimania.Authenticate callback used to catch errors
+function xmlrpc_auth_cb($response) {
+ global $aseco;
+
+ if (isset($response['Data']['errors']) && $response['Data']['errors'] != '') {
+ if (is_array($response['Data']['errors']))
+ $aseco->console('xmlrpc_auth_cb() - response[Data][errors]' . CRLF . print_r($response['Data']['errors'], true));
+ else
+ $aseco->console('xmlrpc_auth_cb() - response[Data][errors]' . CRLF . print_r($response, true));
+ }
+} // xmlrpc_auth_cb
+?>
diff --git a/xaseco/localdatabase.xml b/xaseco/localdatabase.xml
new file mode 100644
index 0000000..48d26db
--- /dev/null
+++ b/xaseco/localdatabase.xml
@@ -0,0 +1,22 @@
+
+
+
+ @MYSQL_HOST@
+ @MYSQL_LOGIN@
+ @MYSQL_PASSWORD@
+ @MYSQL_DATABASE@
+
+ true
+
+
+
+ 50
+
+
+
+ {#server}>> {#highlite}{1}{#record} secured his/her {#rank}{2}{#record}. Local Record! {3}: {#highlite}{4}{#record} $n({#rank}{5}{#highlite}{6}{#record})
+ {#server}>> {#highlite}{1}{#record} equaled his/her {#rank}{2}{#record}. Local Record! {3}: {#highlite}{4}
+ {#server}>> {#highlite}{1}{#record} gained the {#rank}{2}{#record}. Local Record! {3}: {#highlite}{4}{#record} $n({#rank}{5}{#highlite}{6}{#record})
+ {#server}>> {#highlite}{1}{#record} claimed the {#rank}{2}{#record}. Local Record! {3}: {#highlite}{4}
+
+
diff --git a/xaseco/panels/00README.txt b/xaseco/panels/00README.txt
new file mode 100644
index 0000000..391aa9e
--- /dev/null
+++ b/xaseco/panels/00README.txt
@@ -0,0 +1,34 @@
+Panels
+======
+
+This directory holds Admin, Donate, Records and Vote panel templates,
+managed by plugin.panels.php.
+Templates define the complete ManiaLink panel with position, size and
+fonts, so you have full control to develop custom panels.
+
+To create a new template, copy an existing one to a new filename and
+edit that, as the existing set will be overwritten in future releases.
+
+Use ManiaLink http://smurf1.free.fr/mle/index.xml
+and webpage http://smurf1.free.fr/mle/list.php
+to select styles and fonts. Note that not every (sub)style and font
+fits everywhere due to size variations.
+
+New Admin templates must stick to manialink id="3" and preserve
+action="21" through action="27" for the buttons.
+
+New Donate templates must stick to manialink id="6" and preserve
+action="30" through action="36" for the buttons.
+
+New Record templates must stick to manialink id="4" and preserve the
+mapping of text="%PB%" to action="7", "%LCL%" to "8", "%DED%" to "9"
+and "%TMX%" to "10".
+
+New Vote templates must stick to manialink id="5" and preserve
+action="18" for the Yes button and action="19" for the No button.
+
+If you create a nice template for any panel that's sufficiently distinct
+from the standard ones, send it to me and I might include it in the
+next XAseco release. :)
+
+Xymph
diff --git a/xaseco/panels/AdminAboveCPList.xml b/xaseco/panels/AdminAboveCPList.xml
new file mode 100644
index 0000000..5013657
--- /dev/null
+++ b/xaseco/panels/AdminAboveCPList.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminAboveCPListWide.xml b/xaseco/panels/AdminAboveCPListWide.xml
new file mode 100644
index 0000000..ec41084
--- /dev/null
+++ b/xaseco/panels/AdminAboveCPListWide.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminAboveChat.xml b/xaseco/panels/AdminAboveChat.xml
new file mode 100644
index 0000000..f2fea7c
--- /dev/null
+++ b/xaseco/panels/AdminAboveChat.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminAboveChatWide.xml b/xaseco/panels/AdminAboveChatWide.xml
new file mode 100644
index 0000000..120b2b2
--- /dev/null
+++ b/xaseco/panels/AdminAboveChatWide.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminAboveSpeed.xml b/xaseco/panels/AdminAboveSpeed.xml
new file mode 100644
index 0000000..303be95
--- /dev/null
+++ b/xaseco/panels/AdminAboveSpeed.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminAboveSpeed2.xml b/xaseco/panels/AdminAboveSpeed2.xml
new file mode 100644
index 0000000..2582b30
--- /dev/null
+++ b/xaseco/panels/AdminAboveSpeed2.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminBelowChat.xml b/xaseco/panels/AdminBelowChat.xml
new file mode 100644
index 0000000..5a203a1
--- /dev/null
+++ b/xaseco/panels/AdminBelowChat.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminBottomCenter.xml b/xaseco/panels/AdminBottomCenter.xml
new file mode 100644
index 0000000..7925896
--- /dev/null
+++ b/xaseco/panels/AdminBottomCenter.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminBottomCenterWide.xml b/xaseco/panels/AdminBottomCenterWide.xml
new file mode 100644
index 0000000..a3981b0
--- /dev/null
+++ b/xaseco/panels/AdminBottomCenterWide.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminCallVote.xml b/xaseco/panels/AdminCallVote.xml
new file mode 100644
index 0000000..d7a9cd3
--- /dev/null
+++ b/xaseco/panels/AdminCallVote.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminCallVoteBlur.xml b/xaseco/panels/AdminCallVoteBlur.xml
new file mode 100644
index 0000000..002111d
--- /dev/null
+++ b/xaseco/panels/AdminCallVoteBlur.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminLeftEdge.xml b/xaseco/panels/AdminLeftEdge.xml
new file mode 100644
index 0000000..1149266
--- /dev/null
+++ b/xaseco/panels/AdminLeftEdge.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminRightEdge.xml b/xaseco/panels/AdminRightEdge.xml
new file mode 100644
index 0000000..0d24fcd
--- /dev/null
+++ b/xaseco/panels/AdminRightEdge.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminTopCenter.xml b/xaseco/panels/AdminTopCenter.xml
new file mode 100644
index 0000000..8f8bbb8
--- /dev/null
+++ b/xaseco/panels/AdminTopCenter.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/AdminTopCenterWide.xml b/xaseco/panels/AdminTopCenterWide.xml
new file mode 100644
index 0000000..48c7e27
--- /dev/null
+++ b/xaseco/panels/AdminTopCenterWide.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/DonateBelowCPList.xml b/xaseco/panels/DonateBelowCPList.xml
new file mode 100644
index 0000000..9b84e2c
--- /dev/null
+++ b/xaseco/panels/DonateBelowCPList.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/DonateBelowCPListRM.xml b/xaseco/panels/DonateBelowCPListRM.xml
new file mode 100644
index 0000000..aa3207a
--- /dev/null
+++ b/xaseco/panels/DonateBelowCPListRM.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/DonateLeftEdge.xml b/xaseco/panels/DonateLeftEdge.xml
new file mode 100644
index 0000000..1931f0a
--- /dev/null
+++ b/xaseco/panels/DonateLeftEdge.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/DonateLeftEdge2.xml b/xaseco/panels/DonateLeftEdge2.xml
new file mode 100644
index 0000000..0edc2a3
--- /dev/null
+++ b/xaseco/panels/DonateLeftEdge2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/DonateLeftSmall.xml b/xaseco/panels/DonateLeftSmall.xml
new file mode 100644
index 0000000..1b2ddc2
--- /dev/null
+++ b/xaseco/panels/DonateLeftSmall.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/DonateRightEdge.xml b/xaseco/panels/DonateRightEdge.xml
new file mode 100644
index 0000000..9242cee
--- /dev/null
+++ b/xaseco/panels/DonateRightEdge.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/DonateRightEdge2.xml b/xaseco/panels/DonateRightEdge2.xml
new file mode 100644
index 0000000..02e45bd
--- /dev/null
+++ b/xaseco/panels/DonateRightEdge2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/DonateRightSmall.xml b/xaseco/panels/DonateRightSmall.xml
new file mode 100644
index 0000000..1fffe61
--- /dev/null
+++ b/xaseco/panels/DonateRightSmall.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/DonateTopLeft.xml b/xaseco/panels/DonateTopLeft.xml
new file mode 100644
index 0000000..fb88626
--- /dev/null
+++ b/xaseco/panels/DonateTopLeft.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftBlackBold.xml b/xaseco/panels/RecordsLeftBlackBold.xml
new file mode 100644
index 0000000..fd66692
--- /dev/null
+++ b/xaseco/panels/RecordsLeftBlackBold.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftBlue.xml b/xaseco/panels/RecordsLeftBlue.xml
new file mode 100644
index 0000000..cde43be
--- /dev/null
+++ b/xaseco/panels/RecordsLeftBlue.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftBlueBold.xml b/xaseco/panels/RecordsLeftBlueBold.xml
new file mode 100644
index 0000000..09c5862
--- /dev/null
+++ b/xaseco/panels/RecordsLeftBlueBold.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftBlueLight.xml b/xaseco/panels/RecordsLeftBlueLight.xml
new file mode 100644
index 0000000..a5dbd1c
--- /dev/null
+++ b/xaseco/panels/RecordsLeftBlueLight.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftGray.xml b/xaseco/panels/RecordsLeftGray.xml
new file mode 100644
index 0000000..28007db
--- /dev/null
+++ b/xaseco/panels/RecordsLeftGray.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftGrayBold.xml b/xaseco/panels/RecordsLeftGrayBold.xml
new file mode 100644
index 0000000..9837f4f
--- /dev/null
+++ b/xaseco/panels/RecordsLeftGrayBold.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftItalic.xml b/xaseco/panels/RecordsLeftItalic.xml
new file mode 100644
index 0000000..945659e
--- /dev/null
+++ b/xaseco/panels/RecordsLeftItalic.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftOrange.xml b/xaseco/panels/RecordsLeftOrange.xml
new file mode 100644
index 0000000..7963877
--- /dev/null
+++ b/xaseco/panels/RecordsLeftOrange.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftSize1.xml b/xaseco/panels/RecordsLeftSize1.xml
new file mode 100644
index 0000000..8035615
--- /dev/null
+++ b/xaseco/panels/RecordsLeftSize1.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftSize1NoDedi.xml b/xaseco/panels/RecordsLeftSize1NoDedi.xml
new file mode 100644
index 0000000..ef73496
--- /dev/null
+++ b/xaseco/panels/RecordsLeftSize1NoDedi.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftSize2.xml b/xaseco/panels/RecordsLeftSize2.xml
new file mode 100644
index 0000000..e7ab8fb
--- /dev/null
+++ b/xaseco/panels/RecordsLeftSize2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftSize2NoDedi.xml b/xaseco/panels/RecordsLeftSize2NoDedi.xml
new file mode 100644
index 0000000..548720a
--- /dev/null
+++ b/xaseco/panels/RecordsLeftSize2NoDedi.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsLeftWhite.xml b/xaseco/panels/RecordsLeftWhite.xml
new file mode 100644
index 0000000..f418f60
--- /dev/null
+++ b/xaseco/panels/RecordsLeftWhite.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsRightBottom.xml b/xaseco/panels/RecordsRightBottom.xml
new file mode 100644
index 0000000..f0c9688
--- /dev/null
+++ b/xaseco/panels/RecordsRightBottom.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsRightBottomNoDedi.xml b/xaseco/panels/RecordsRightBottomNoDedi.xml
new file mode 100644
index 0000000..4e17240
--- /dev/null
+++ b/xaseco/panels/RecordsRightBottomNoDedi.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/RecordsRightBottomRM.xml b/xaseco/panels/RecordsRightBottomRM.xml
new file mode 100644
index 0000000..e12c740
--- /dev/null
+++ b/xaseco/panels/RecordsRightBottomRM.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/StatsNations.xml b/xaseco/panels/StatsNations.xml
new file mode 100644
index 0000000..edddd8d
--- /dev/null
+++ b/xaseco/panels/StatsNations.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/StatsUnited.xml b/xaseco/panels/StatsUnited.xml
new file mode 100644
index 0000000..2090e0d
--- /dev/null
+++ b/xaseco/panels/StatsUnited.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/VoteBelowChat.xml b/xaseco/panels/VoteBelowChat.xml
new file mode 100644
index 0000000..347590a
--- /dev/null
+++ b/xaseco/panels/VoteBelowChat.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/xaseco/panels/VoteBottomCenter.xml b/xaseco/panels/VoteBottomCenter.xml
new file mode 100644
index 0000000..fecc056
--- /dev/null
+++ b/xaseco/panels/VoteBottomCenter.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/xaseco/panels/VoteBottomCenterTransp.xml b/xaseco/panels/VoteBottomCenterTransp.xml
new file mode 100644
index 0000000..98c15aa
--- /dev/null
+++ b/xaseco/panels/VoteBottomCenterTransp.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/xaseco/panels/VoteCallVote.xml b/xaseco/panels/VoteCallVote.xml
new file mode 100644
index 0000000..b6b51ca
--- /dev/null
+++ b/xaseco/panels/VoteCallVote.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/xaseco/panels/VoteTopCenter.xml b/xaseco/panels/VoteTopCenter.xml
new file mode 100644
index 0000000..58868aa
--- /dev/null
+++ b/xaseco/panels/VoteTopCenter.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/xaseco/plugins/chat.admin.php b/xaseco/plugins/chat.admin.php
new file mode 100644
index 0000000..c7bc75b
--- /dev/null
+++ b/xaseco/plugins/chat.admin.php
@@ -0,0 +1,5772 @@
+... {sec})', true);
+Aseco::addChatCommand('addthis', 'Adds current /add-ed track permanently', true);
+Aseco::addChatCommand('addlocal', 'Adds a local track ()', true);
+Aseco::addChatCommand('warn', 'Sends a kick/ban warning to a player', true);
+Aseco::addChatCommand('kick', 'Kicks a player from server', true);
+Aseco::addChatCommand('kickghost', 'Kicks a ghost player from server', true);
+Aseco::addChatCommand('ban', 'Bans a player from server', true);
+Aseco::addChatCommand('unban', 'UnBans a player from server', true);
+Aseco::addChatCommand('banip', 'Bans an IP address from server', true);
+Aseco::addChatCommand('unbanip', 'UnBans an IP address from server', true);
+Aseco::addChatCommand('black', 'Blacklists a player from server', true);
+Aseco::addChatCommand('unblack', 'UnBlacklists a player from server', true);
+Aseco::addChatCommand('addguest', 'Adds a guest player to server', true);
+Aseco::addChatCommand('removeguest', 'Removes a guest player from server', true);
+Aseco::addChatCommand('pass', 'Passes a chat-based or TMX /add vote', true);
+Aseco::addChatCommand('cancel/can', 'Cancels any running vote', true);
+Aseco::addChatCommand('endround/er', 'Forces end of current round', true);
+Aseco::addChatCommand('players', 'Displays list of known players {string}', true);
+Aseco::addChatCommand('showbanlist/listbans', 'Displays current ban list', true);
+Aseco::addChatCommand('showiplist/listips', 'Displays current banned IPs list', true);
+Aseco::addChatCommand('showblacklist/listblacks', 'Displays current black list', true);
+Aseco::addChatCommand('showguestlist/listguests', 'Displays current guest list', true);
+Aseco::addChatCommand('writeiplist', 'Saves current banned IPs list (def: bannedips.xml)', true);
+Aseco::addChatCommand('readiplist', 'Loads current banned IPs list (def: bannedips.xml)', true);
+Aseco::addChatCommand('writeblacklist', 'Saves current black list (def: blacklist.txt)', true);
+Aseco::addChatCommand('readblacklist', 'Loads current black list (def: blacklist.txt)', true);
+Aseco::addChatCommand('writeguestlist', 'Saves current guest list (def: guestlist.txt)', true);
+Aseco::addChatCommand('readguestlist', 'Loads current guest list (def: guestlist.txt)', true);
+Aseco::addChatCommand('cleanbanlist', 'Cleans current ban list', true);
+Aseco::addChatCommand('cleaniplist', 'Cleans current banned IPs list', true);
+Aseco::addChatCommand('cleanblacklist', 'Cleans current black list', true);
+Aseco::addChatCommand('cleanguestlist', 'Cleans current guest list', true);
+Aseco::addChatCommand('mergegbl', 'Merges a global black list {URL}', true);
+Aseco::addChatCommand('access', 'Handles player access control (see: /admin access help)', true);
+Aseco::addChatCommand('writetracklist', 'Saves current track list (def: tracklist.txt)', true);
+Aseco::addChatCommand('readtracklist', 'Loads current track list (def: tracklist.txt)', true);
+Aseco::addChatCommand('shuffle/shufflemaps', 'Randomizes current track list', true);
+Aseco::addChatCommand('listdupes', 'Displays list of duplicate tracks', true);
+Aseco::addChatCommand('remove', 'Removes a track from rotation', true);
+Aseco::addChatCommand('erase', 'Removes a track from rotation & deletes track file', true);
+Aseco::addChatCommand('removethis', 'Removes this track from rotation', true);
+Aseco::addChatCommand('erasethis', 'Removes this track from rotation & deletes track file', true);
+Aseco::addChatCommand('mute/ignore', 'Adds a player to global mute/ignore list', true);
+Aseco::addChatCommand('unmute/unignore', 'Removes a player from global mute/ignore list', true);
+Aseco::addChatCommand('mutelist/listmutes', 'Displays global mute/ignore list', true);
+Aseco::addChatCommand('ignorelist/listignores', 'Displays global mute/ignore list', true);
+Aseco::addChatCommand('cleanmutes/cleanignores', 'Cleans global mute/ignore list', true);
+Aseco::addChatCommand('addadmin', 'Adds a new admin', true);
+Aseco::addChatCommand('removeadmin', 'Removes an admin', true);
+Aseco::addChatCommand('addop', 'Adds a new operator', true);
+Aseco::addChatCommand('removeop', 'Removes an operator', true);
+Aseco::addChatCommand('listmasters', 'Displays current masteradmin list', true);
+Aseco::addChatCommand('listadmins', 'Displays current admin list', true);
+Aseco::addChatCommand('listops', 'Displays current operator list', true);
+Aseco::addChatCommand('adminability', 'Shows/changes admin ability {ON/OFF}', true);
+Aseco::addChatCommand('opability', 'Shows/changes operator ability {ON/OFF}', true);
+Aseco::addChatCommand('listabilities', 'Displays current abilities list', true);
+Aseco::addChatCommand('writeabilities', 'Saves current abilities list (def: adminops.xml)', true);
+Aseco::addChatCommand('readabilities', 'Loads current abilities list (def: adminops.xml)', true);
+Aseco::addChatCommand('wall/mta', 'Displays popup message to all players', true);
+Aseco::addChatCommand('delrec', 'Deletes specific record on current track', true);
+Aseco::addChatCommand('prunerecs', 'Deletes records for specified track', true);
+Aseco::addChatCommand('rpoints', 'Sets custom Rounds points (see: /admin rpoints help)', true);
+Aseco::addChatCommand('match', '{begin/end} to start/stop match tracking', true);
+Aseco::addChatCommand('acdl', 'Sets AllowChallengeDownload {ON/OFF}', true);
+Aseco::addChatCommand('autotime', 'Sets Auto TimeLimit {ON/OFF}', true);
+Aseco::addChatCommand('disablerespawn', 'Disables respawn at CPs {ON/OFF}', true);
+Aseco::addChatCommand('forceshowopp', 'Forces to show opponents {##/ALL/OFF}', true);
+Aseco::addChatCommand('scorepanel', 'Shows automatic scorepanel {ON/OFF}', true);
+Aseco::addChatCommand('roundsfinish', 'Shows rounds panel upon first finish {ON/OFF}', true);
+Aseco::addChatCommand('forceteam', 'Forces player into {Blue} or {Red} team', true);
+Aseco::addChatCommand('forcespec', 'Forces player into free spectator', true);
+Aseco::addChatCommand('specfree', 'Forces spectator into free mode', true);
+Aseco::addChatCommand('panel', 'Selects admin panel (see: /admin panel help)', true);
+Aseco::addChatCommand('style', 'Selects default window style', true);
+Aseco::addChatCommand('admpanel', 'Selects default admin panel', true);
+Aseco::addChatCommand('donpanel', 'Selects default donate panel', true);
+Aseco::addChatCommand('recpanel', 'Selects default records panel', true);
+Aseco::addChatCommand('votepanel', 'Selects default vote panel', true);
+Aseco::addChatCommand('coppers', 'Shows server\'s coppers amount', true);
+Aseco::addChatCommand('pay', 'Pays server coppers to login', true);
+Aseco::addChatCommand('relays', 'Displays relays list or shows relay master', true);
+Aseco::addChatCommand('server', 'Displays server\'s detailed settings', true);
+Aseco::addChatCommand('pm', 'Sends private message to all available admins', true);
+Aseco::addChatCommand('pmlog', 'Displays log of recent private admin messages', true);
+Aseco::addChatCommand('call', 'Executes direct server call (see: /admin call help)', true);
+Aseco::addChatCommand('unlock', 'Unlocks admin commands & features', true);
+Aseco::addChatCommand('debug', 'Toggles debugging output', true);
+Aseco::addChatCommand('shutdown', 'Shuts down XASECO', true);
+Aseco::addChatCommand('shutdownall', 'Shuts down Server & XASECO', true);
+//Aseco::addChatCommand('uptodate', 'Checks current version of XASECO', true); // already defined in plugin.uptodate.php
+
+global $pmbuf; // pm history buffer
+global $pmlen; // length of pm history
+global $lnlen; // max length of pm line
+
+$pmbuf = array();
+$pmlen = 30;
+$lnlen = 40;
+
+global $method_results, $auto_scorepanel, $rounds_finishpanel;
+$auto_scorepanel = true;
+$rounds_finishpanel = true;
+
+function chat_admin($aseco, $command) {
+ global $jukebox; // from plugin.rasp_jukebox.php
+
+ $admin = $command['author'];
+ $login = $admin->login;
+
+ // split params into arrays & insure optional parameters exist
+ $arglist = explode(' ', $command['params'], 2);
+ if (!isset($arglist[1])) $arglist[1] = '';
+ $command['params'] = explode(' ', preg_replace('/ +/', ' ', $command['params']));
+ if (!isset($command['params'][1])) $command['params'][1] = '';
+
+ // check if chat command was allowed for a masteradmin/admin/operator
+ if ($aseco->isMasterAdmin($admin)) {
+ $logtitle = 'MasterAdmin';
+ $chattitle = $aseco->titles['MASTERADMIN'][0];
+ } else {
+ if ($aseco->isAdmin($admin) && $aseco->allowAdminAbility($command['params'][0])) {
+ $logtitle = 'Admin';
+ $chattitle = $aseco->titles['ADMIN'][0];
+ } else {
+ if ($aseco->isOperator($admin) && $aseco->allowOpAbility($command['params'][0])) {
+ $logtitle = 'Operator';
+ $chattitle = $aseco->titles['OPERATOR'][0];
+ } else {
+ // write warning in console
+ $aseco->console($login . ' tried to use admin chat command (no permission!): ' . $arglist[0] . ' ' . $arglist[1]);
+ // show chat message
+ $aseco->client->query('ChatSendToLogin', $aseco->formatColors('{#error}You don\'t have the required admin rights to do that!'), $login);
+ return false;
+ }
+ }
+ }
+
+ // check for unlocked password (or unlock command)
+ if ($aseco->settings['lock_password'] != '' && !$admin->unlocked &&
+ $command['params'][0] != 'unlock') {
+ // write warning in console
+ $aseco->console($login . ' tried to use admin chat command (not unlocked!): ' . $arglist[0] . ' ' . $arglist[1]);
+ // show chat message
+ $aseco->client->query('ChatSendToLogin', $aseco->formatColors('{#error}You don\'t have the required admin rights to do that!'), $login);
+ return false;
+ }
+
+ /**
+ * Show admin help.
+ */
+ if ($command['params'][0] == 'help') {
+ // build list of currently active commands
+ $active_commands = array();
+ foreach ($aseco->chat_commands as $cc) {
+ // strip off optional abbreviation
+ $name = preg_replace('/\/.*/', '', $cc->name);
+
+ // check if admin command is within this admin's tier
+ if ($cc->isadmin && $aseco->allowAbility($admin, $name)) {
+ $active_command = new ChatCommand($cc->name, $cc->help, true);
+ $active_commands[] = $active_command;
+ }
+ }
+
+ // show active admin commands on command line
+ showHelp($admin, $active_commands, $logtitle, true, false);
+
+ /**
+ * Display admin help.
+ */
+ } elseif ($command['params'][0] == 'helpall') {
+
+ // build list of currently active commands
+ $active_commands = array();
+ foreach ($aseco->chat_commands as $cc) {
+ // strip off optional abbreviation
+ $name = preg_replace('/\/.*/', '', $cc->name);
+
+ // check if admin command is within this admin's tier
+ if ($cc->isadmin && $aseco->allowAbility($admin, $name)) {
+ $active_command = new ChatCommand($cc->name, $cc->help, true);
+ $active_commands[] = $active_command;
+ }
+ }
+
+ // display active admin commands in popup with descriptions
+ showHelp($admin, $active_commands, $logtitle, true, true, 0.42);
+
+ /**
+ * Sets a new server name (on the fly).
+ */
+ } elseif ($command['params'][0] == 'setservername' && $command['params'][1] != '') {
+
+ // set a new servername
+ $aseco->client->query('SetServerName', $arglist[1]);
+
+ // log console message
+ $aseco->console('{1} [{2}] set new server name [{3}]', $logtitle, $login, $arglist[1]);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets servername to {#highlite}{3}',
+ $chattitle, $admin->nickname, $arglist[1]);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ /**
+ * Sets a new server comment (on the fly).
+ */
+ } elseif ($command['params'][0] == 'setcomment' && $command['params'][1] != '') {
+
+ // set a new server comment
+ $aseco->client->query('SetServerComment', $arglist[1]);
+
+ // log console message
+ $aseco->console('{1} [{2}] set new server comment [{3}]', $logtitle, $login, $arglist[1]);
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets server comment to {#highlite}{3}',
+ $chattitle, $admin->nickname, $arglist[1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Sets a new player password (on the fly).
+ */
+ } elseif ($command['params'][0] == 'setpwd') {
+
+ // set a new player password
+ $aseco->client->query('SetServerPassword', $arglist[1]);
+
+ if ($arglist[1] != '') {
+ // log console message
+ $aseco->console('{1} [{2}] set new player password [{3}]', $logtitle, $login, $arglist[1]);
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets player password to {#highlite}{3}',
+ $chattitle, $admin->nickname, $arglist[1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] disabled player password', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} disables player password',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Sets a new spectator password (on the fly).
+ */
+ } elseif ($command['params'][0] == 'setspecpwd') {
+
+ // set a new spectator password
+ $aseco->client->query('SetServerPasswordForSpectator', $arglist[1]);
+
+ if ($arglist[1] != '') {
+ // log console message
+ $aseco->console('{1} [{2}] set new spectator password [{3}]', $logtitle, $login, $arglist[1]);
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets spectator password to {#highlite}{3}',
+ $chattitle, $admin->nickname, $arglist[1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] disabled spectator password', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} disables spectator password',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Sets a new referee password (on the fly).
+ */
+ } elseif ($command['params'][0] == 'setrefpwd') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ // set a new referee password
+ $aseco->client->query('SetRefereePassword', $arglist[1]);
+
+ if ($arglist[1] != '') {
+ // log console message
+ $aseco->console('{1} [{2}] set new referee password [{3}]', $logtitle, $login, $arglist[1]);
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets referee password to {#highlite}{3}',
+ $chattitle, $admin->nickname, $arglist[1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] disabled referee password', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} disables referee password',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Sets a new player maximum that is able to connect to the server.
+ */
+ } elseif ($command['params'][0] == 'setmaxplayers' && is_numeric($command['params'][1]) && $command['params'][1] > 0) {
+
+ // tell server to set new player max
+ $aseco->client->query('SetMaxPlayers', (int) $command['params'][1]);
+
+ // log console message
+ $aseco->console('{1} [{2}] set new player maximum [{3}]', $logtitle, $login, $command['params'][1]);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets new player maximum to {#highlite}{3}{#admin} !',
+ $chattitle, $admin->nickname, $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ /**
+ * Sets a new spectator maximum that is able to connect to the server.
+ */
+ } elseif ($command['params'][0] == 'setmaxspecs' && is_numeric($command['params'][1]) && $command['params'][1] >= 0) {
+
+ // tell server to set new spectator max
+ $aseco->client->query('SetMaxSpectators', (int) $command['params'][1]);
+
+ // log console message
+ $aseco->console('{1} [{2}] set new spectator maximum [{3}]', $logtitle, $login, $command['params'][1]);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets new spectator maximum to {#highlite}{3}{#admin} !',
+ $chattitle, $admin->nickname, $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ /**
+ * Sets new game mode that will be active upon the next track:
+ * ta,rounds,team,laps,stunts
+ */
+ } elseif ($command['params'][0] == 'setgamemode' && $command['params'][1] != '') {
+
+ // check mode parameter
+ switch (strtolower($command['params'][1])) {
+ case 'ta':
+ $mode = Gameinfo::TA;
+ break;
+ case 'round': // permit shortcut
+ case 'rounds':
+ $mode = Gameinfo::RNDS;
+ break;
+ case 'team':
+ $mode = Gameinfo::TEAM;
+ break;
+ case 'laps':
+ $mode = Gameinfo::LAPS;
+ break;
+ case 'stunts':
+ $mode = Gameinfo::STNT;
+ break;
+ case 'cup':
+ if ($aseco->server->getGame() == 'TMF')
+ $mode = Gameinfo::CUP;
+ else
+ $mode = -1;
+ break;
+ default:
+ $mode = -1;
+ }
+
+ if ($mode >= 0) {
+ if ($aseco->changingmode || $mode != $aseco->server->gameinfo->mode) {
+ // tell server to set new game mode
+ $aseco->client->query('SetGameMode', $mode);
+ $aseco->changingmode = true;
+
+ // log console message
+ $aseco->console('{1} [{2}] set new game mode [{3}]', $logtitle, $login, strtoupper($command['params'][1]));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets next game mode to {#highlite}{3}{#admin} !',
+ $chattitle, $admin->nickname, strtoupper($command['params'][1]));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $aseco->changingmode = false;
+ $message = '{#server}> Same game mode {#highlite}' . strtoupper($command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = '{#server}> {#error}Invalid game mode {#highlite}$i ' . strtoupper($command['params'][1]) . '$z$s {#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Sets new referee mode (0 = top3, 1 = all).
+ */
+ } elseif ($command['params'][0] == 'setrefmode') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ if (($mode = $command['params'][1]) != '') {
+ if (is_numeric($mode) && ($mode == 0 || $mode == 1)) {
+ // tell server to set new referee mode
+ $aseco->client->query('SetRefereeMode', (int) $mode);
+
+ // log console message
+ $aseco->console('{1} [{2}] set new referee mode [{3}]', $logtitle, $login, strtoupper($mode));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets referee mode to {#highlite}{3}{#admin} !',
+ $chattitle, $admin->nickname, $mode);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = '{#server}> {#error}Invalid referee mode {#highlite}$i ' . strtoupper($mode) . '$z$s {#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ // tell server to get current referee mode
+ $aseco->client->query('GetRefereeMode');
+ $mode = $aseco->client->getResponse();
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}Referee mode is set to {#highlite}{1}',
+ ($mode == 1 ? 'All' : 'Top-3'));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Forces the server to load next track.
+ */
+ } elseif ($command['params'][0] == 'nextmap' ||
+ $command['params'][0] == 'next' ||
+ $command['params'][0] == 'skipmap' ||
+ $command['params'][0] == 'skip') {
+
+ // load the next map
+ // don't clear scores if in TMF Cup mode
+ if ($aseco->server->gameinfo->mode == Gameinfo::CUP)
+ $aseco->client->query('NextChallenge', true);
+ else
+ $aseco->client->query('NextChallenge');
+
+ // log console message
+ $aseco->console('{1} [{2}] skips challenge!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} skips challenge!',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ /**
+ * Forces the server to load previous track.
+ */
+ } elseif ($command['params'][0] == 'previous' ||
+ $command['params'][0] == 'prev') {
+
+ // get current track
+ $aseco->client->query('GetCurrentChallengeIndex');
+ $current = $aseco->client->getResponse();
+
+ // check if not the first track
+ if ($current > 0) {
+ // find previous track
+ $aseco->client->query('GetChallengeList', 1, --$current);
+ $track = $aseco->client->getResponse();
+ $prev = array();
+ $prev['name'] = $track[0]['Name'];
+ $prev['environment'] = $track[0]['Environnement'];
+ $prev['filename'] = $track[0]['FileName'];
+ $prev['uid'] = $track[0]['UId'];
+ } else {
+ // dummy player to easily obtain entire track list
+ $list = new Player();
+ getAllChallenges($list, '*', '*');
+ // find last track
+ $prev = end($list->tracklist);
+ unset($list);
+ }
+
+ // prepend previous challenge to start of jukebox
+ $uid = $prev['uid'];
+ $jukebox = array_reverse($jukebox, true);
+ $jukebox[$uid]['FileName'] = $prev['filename'];
+ $jukebox[$uid]['Name'] = $prev['name'];
+ $jukebox[$uid]['Env'] = $prev['environment'];
+ $jukebox[$uid]['Login'] = $admin->login;
+ $jukebox[$uid]['Nick'] = $admin->nickname;
+ $jukebox[$uid]['source'] = 'Previous';
+ $jukebox[$uid]['tmx'] = false;
+ $jukebox[$uid]['uid'] = $uid;
+ $jukebox = array_reverse($jukebox, true);
+
+ if ($aseco->debug) {
+ $aseco->console_text('/admin prev jukebox:' . CRLF .
+ print_r($jukebox, true));
+ }
+
+ // load the previous track
+ // don't clear scores if in TMF Cup mode
+ if ($aseco->server->gameinfo->mode == Gameinfo::CUP)
+ $aseco->client->query('NextChallenge', true);
+ else
+ $aseco->client->query('NextChallenge');
+
+ // log console message
+ $aseco->console('{1} [{2}] revisits previous challenge!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} revisits previous challenge!',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // throw 'jukebox changed' event
+ $aseco->releaseEvent('onJukeboxChanged', array('previous', $jukebox[$uid]));
+
+ /**
+ * Loads the next track in the same environment.
+ */
+ } elseif ($command['params'][0] == 'nextenv') {
+
+ // check for TMF United
+ if ($aseco->server->getGame() == 'TMF' &&
+ $aseco->server->packmask != 'Stadium') {
+ // dummy player to easily obtain environment track list
+ $list = new Player();
+ getAllChallenges($list, '*', $aseco->server->challenge->environment);
+
+ // search for current track
+ $next = null;
+ $found = false;
+ foreach ($list->tracklist as $track) {
+ if ($found) {
+ $next = $track;
+ break;
+ }
+ if ($track['uid'] == $aseco->server->challenge->uid)
+ $found = true;
+ }
+ // check for last track and loop back to first
+ if ($next === null)
+ $next = $list->tracklist[0];
+ unset($list);
+
+ // prepend next env challenge to start of jukebox
+ $uid = $next['uid'];
+ $jukebox = array_reverse($jukebox, true);
+ $jukebox[$uid]['FileName'] = $next['filename'];
+ $jukebox[$uid]['Name'] = $next['name'];
+ $jukebox[$uid]['Env'] = $next['environment'];
+ $jukebox[$uid]['Login'] = $admin->login;
+ $jukebox[$uid]['Nick'] = $admin->nickname;
+ $jukebox[$uid]['source'] = 'Previous';
+ $jukebox[$uid]['tmx'] = false;
+ $jukebox[$uid]['uid'] = $uid;
+ $jukebox = array_reverse($jukebox, true);
+
+ if ($aseco->debug) {
+ $aseco->console_text('/admin nextenv jukebox:' . CRLF .
+ print_r($jukebox, true));
+ }
+
+ // load the next environment track
+ // don't clear scores if in TMF Cup mode
+ if ($aseco->server->gameinfo->mode == Gameinfo::CUP)
+ $aseco->client->query('NextChallenge', true);
+ else
+ $aseco->client->query('NextChallenge');
+
+ // log console message
+ $aseco->console('{1} [{2}] skips to next {3} challenge!', $logtitle, $login, $aseco->server->challenge->environment);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} skips to next {#highlite}{3}{#admin} challenge!',
+ $chattitle, $admin->nickname, $aseco->server->challenge->environment);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // throw 'jukebox changed' event
+ $aseco->releaseEvent('onJukeboxChanged', array('nextenv', $jukebox[$uid]));
+
+ } else { // TMN(F)
+ $message = '{#server}> {#error}Command only available on TMU Forever!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Restarts the currently running map.
+ */
+ } elseif ($command['params'][0] == 'restartmap' ||
+ $command['params'][0] == 'res') {
+ global $atl_restart; // from plugin.autotime.php
+
+ // restart the track
+ if (isset($atl_restart)) $atl_restart = true;
+ // don't clear scores if in TMF Cup mode
+ if ($aseco->server->gameinfo->mode == Gameinfo::CUP)
+ $aseco->client->query('ChallengeRestart', true);
+ else
+ $aseco->client->query('ChallengeRestart');
+
+ // log console message
+ $aseco->console('{1} [{2}] restarts challenge!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} restarts challenge!',
+ $chattitle, $admin->nickname);
+
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ /**
+ * Replays the current map (queues it at start of jukebox).
+ */
+ } elseif ($command['params'][0] == 'replaymap' ||
+ $command['params'][0] == 'replay') {
+ global $chatvote; // from plugin.rasp_votes.php
+
+ // cancel possibly ongoing replay/restart vote
+ $aseco->client->query('CancelVote');
+ if (!empty($chatvote) && $chatvote['type'] == 2) {
+ $chatvote = array();
+ // disable all vote panels
+ if ($aseco->server->getGame() == 'TMF')
+ allvotepanels_off($aseco);
+ }
+
+ // check if track already in jukebox
+ if (!empty($jukebox) && array_key_exists($aseco->server->challenge->uid, $jukebox)) {
+ $message = '{#server}> {#error}Track is already getting replayed!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ return;
+ }
+
+ // prepend current challenge to start of jukebox
+ $uid = $aseco->server->challenge->uid;
+ $jukebox = array_reverse($jukebox, true);
+ $jukebox[$uid]['FileName'] = $aseco->server->challenge->filename;
+ $jukebox[$uid]['Name'] = $aseco->server->challenge->name;
+ $jukebox[$uid]['Env'] = $aseco->server->challenge->environment;
+ $jukebox[$uid]['Login'] = $admin->login;
+ $jukebox[$uid]['Nick'] = $admin->nickname;
+ $jukebox[$uid]['source'] = 'AdminReplay';
+ $jukebox[$uid]['tmx'] = false;
+ $jukebox[$uid]['uid'] = $uid;
+ $jukebox = array_reverse($jukebox, true);
+
+ if ($aseco->debug) {
+ $aseco->console_text('/admin replay jukebox:' . CRLF .
+ print_r($jukebox, true));
+ }
+
+ // log console message
+ $aseco->console('{1} [{2}] requeues challenge!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} queues challenge for replay!',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // throw 'jukebox changed' event
+ $aseco->releaseEvent('onJukeboxChanged', array('replay', $jukebox[$uid]));
+
+ /**
+ * Drops a track from the jukebox (for use with rasp jukebox plugin).
+ */
+ } elseif ($command['params'][0] == 'dropjukebox' ||
+ $command['params'][0] == 'djb') {
+
+ // verify parameter
+ if (is_numeric($command['params'][1]) &&
+ $command['params'][1] >= 1 && $command['params'][1] <= count($jukebox)) {
+ $i = 1;
+ foreach ($jukebox as $item) {
+ if ($i++ == $command['params'][1]) {
+ $name = stripColors($item['Name']);
+ $uid = $item['uid'];
+ break;
+ }
+ }
+ $drop = $jukebox[$uid];
+ unset($jukebox[$uid]);
+
+ // log console message
+ $aseco->console('{1} [{2}] drops track {3} from jukebox!', $logtitle, $login, stripColors($name, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} drops track {#highlite}{3}{#admin} from jukebox!',
+ $chattitle, $admin->nickname, $name);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // throw 'jukebox changed' event
+ $aseco->releaseEvent('onJukeboxChanged', array('drop', $drop));
+ } else {
+ $message = '{#server}> {#error}Jukebox entry not found! Type {#highlite}$i /jukebox list{#error} or {#highlite}$i /jukebox display{#error} for its contents.';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Clears the jukebox (for use with rasp jukebox plugin).
+ */
+ } elseif ($command['params'][0] == 'clearjukebox' ||
+ $command['params'][0] == 'cjb') {
+
+ // clear jukebox
+ $jukebox = array();
+
+ // log console message
+ $aseco->console('{1} [{2}] clears jukebox!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} clears jukebox!',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // throw 'jukebox changed' event
+ $aseco->releaseEvent('onJukeboxChanged', array('clear', null));
+
+ /**
+ * Clears (part of) track history.
+ */
+ } elseif ($command['params'][0] == 'clearhist') {
+ global $buffersize, $jb_buffer; // from rasp.settings.php
+
+ // check for optional portion (pos = newest, neg = oldest)
+ if ($command['params'][1] != '' && is_numeric($command['params'][1]) && $command['params'][1] != 0) {
+ $clear = intval($command['params'][1]);
+
+ // log console message
+ $aseco->console('{1} [{2}] clears {3} track{4} from history!', $logtitle, $login,
+ ($clear > 0 ? 'newest ' : 'oldest ') . abs($clear),
+ abs($clear) == 1 ? '' : 's');
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} clears {3}{#admin} track{4} from history!',
+ $chattitle, $admin->nickname,
+ ($clear > 0 ? 'newest {#highlite}' : 'oldest {#highlite}') . abs($clear),
+ abs($clear) == 1 ? '' : 's');
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } elseif (strtolower($command['params'][1]) == 'all') { // entire history
+ $clear = $buffersize;
+
+ // log console message
+ $aseco->console('{1} [{2}] clears entire track history!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} clears entire track history!',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ // show chat message
+ $message = formatText('{#server}> {#admin}The track history contains {#highlite}{3}{#admin} track{4}',
+ $chattitle, $admin->nickname, count($jb_buffer),
+ (count($jb_buffer) == 1 ? '' : 's'));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ return;
+ }
+
+ // clear track history (portion)
+ $i = 0;
+ if ($clear > 0) {
+ if ($clear > $buffersize) $clear = $buffersize;
+ while ($i++ < $clear) array_pop($jb_buffer);
+ } else {
+ if ($clear < -$buffersize) $clear = -$buffersize;
+ while ($i-- > $clear) array_shift($jb_buffer);
+ }
+
+ /**
+ * Adds TMX tracks to the track rotation.
+ */
+ } elseif ($command['params'][0] == 'add') {
+ global $rasp, $tmxdir, $jukebox_adminadd; // from plugin.rasp.php, rasp.settings.php
+
+ $sections = array('TMO' => 'original',
+ 'TMS' => 'sunrise',
+ 'TMN' => 'nations',
+ 'TMU' => 'united',
+ 'TMNF' => 'tmnforever');
+
+ // check last parameter
+ $last = strtoupper(end($command['params']));
+ // try to load the track(s) from TMX
+ $source = 'TMX';
+ $section = $aseco->server->getGame();
+ if ($section == 'TMF' && count($command['params']) > 2 &&
+ substr($last, 0, 2) == 'TM') {
+ $section = $last;
+ array_pop($command['params']);
+ if (!array_key_exists($section, $sections)) {
+ $message = '{#server}> {#error}No such section on TMX!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ return;
+ }
+ } else { // TMN/TMS/TMO or no section
+ if ($section == 'TMF') {
+ if ($aseco->server->packmask == 'Stadium')
+ $section = 'TMNF';
+ else
+ $section = 'TMU';
+ }
+ }
+ $remotelink = 'http://' . $sections[$section] . '.tm-exchange.com/get.aspx?action=trackgbx&id=';
+
+ if (count($command['params']) == 1) {
+ $message = '{#server}> {#error}You must include a TMX Track_ID!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ return;
+ }
+
+ // try all specified tracks
+ for ($id = 1; $id < count($command['params']); $id++) {
+ // check for valid TMX ID
+ if (is_numeric($command['params'][$id]) && $command['params'][$id] >= 0) {
+ $trkid = ltrim($command['params'][$id], '0');
+ $file = http_get_file($remotelink . $trkid);
+ if ($file === false || $file == -1) {
+ $message = '{#server}> {#error}Error downloading, or wrong TMX section, or TMX is down!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // check for maximum online track size (256 KB)
+ if (strlen($file) >= 256 * 1024) {
+ $message = formatText($rasp->messages['TRACK_TOO_LARGE'][0],
+ round(strlen($file) / 1024));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ continue;
+ }
+ $sepchar = substr($aseco->server->trackdir, -1, 1);
+ $partialdir = $tmxdir . $sepchar . $trkid . '.Challenge.gbx';
+ $localfile = $aseco->server->trackdir . $partialdir;
+ if ($nocasepath = file_exists_nocase($localfile)) {
+ if (!unlink($nocasepath)) {
+ $message = '{#server}> {#error}Error erasing old file - unable to erase {#highlite}$i ' . $localfile;
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ continue;
+ }
+ }
+ if (!$lfile = @fopen($localfile, 'wb')) {
+ $message = '{#server}> {#error}Error creating file - unable to create {#highlite}$i ' . $localfile;
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ continue;
+ }
+ if (!fwrite($lfile, $file)) {
+ $message = '{#server}> {#error}Error saving file - unable to write data';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ fclose($lfile);
+ continue;
+ }
+ fclose($lfile);
+ $newtrk = getChallengeData($localfile, false); // 2nd parm is whether or not to get players & votes required
+ if ($newtrk['votes'] == 500 && $newtrk['name'] == 'Not a GBX file') {
+ $message = '{#server}> {#error}No such track on ' . $source;
+ if ($source == 'TMX' && $aseco->server->getGame() == 'TMF')
+ $message .= ' section ' . $section;
+ $message .= '!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ unlink($localfile);
+ continue;
+ }
+ // dummy player to easily obtain entire track list
+ $list = new Player();
+ getAllChallenges($list, '*', '*');
+ // check for track presence on server
+ foreach ($list->tracklist as $key) {
+ if ($key['uid'] == $newtrk['uid']) {
+ $message = $rasp->messages['ADD_PRESENT'][0];
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ unlink($localfile);
+ unset($list);
+ continue 2; // outer for loop
+ }
+ }
+ unset($list);
+ // rename ID filename to track's name
+ $md5new = md5_file($localfile);
+ $filename = trim(utf8_decode(stripColors($newtrk['name'])));
+ $filename = preg_replace('/[^A-Za-z0-9 \'#=+~_,.-]/', '_', $filename);
+ $filename = preg_replace('/ +/', ' ', preg_replace('/_+/', '_', $filename));
+ $partialdir = $tmxdir . $sepchar . $filename . '_' . $trkid . '.Challenge.gbx';
+ // insure unique filename by incrementing sequence number,
+ // if not a duplicate track
+ $i = 1;
+ $dupl = false;
+ while ($nocasepath = file_exists_nocase($aseco->server->trackdir . $partialdir)) {
+ $md5old = md5_file($nocasepath);
+ if ($md5old == $md5new) {
+ $dupl = true;
+ $partialdir = str_replace($aseco->server->trackdir, '', $nocasepath);
+ break;
+ } else {
+ $partialdir = $tmxdir . $sepchar . $filename . '_' . $trkid . '-' . $i++ . '.Challenge.gbx';
+ }
+ }
+ if ($dupl) {
+ unlink($localfile);
+ } else {
+ rename($localfile, $aseco->server->trackdir . $partialdir);
+ }
+
+ // check track vs. server settings
+ if ($aseco->server->getGame() == 'TMF')
+ $rtn = $aseco->client->query('CheckChallengeForCurrentServerParams', $partialdir);
+ else
+ $rtn = true;
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] CheckChallengeForCurrentServerParams - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = formatText($rasp->messages['JUKEBOX_IGNORED'][0],
+ stripColors($newtrk['name']), $aseco->client->getErrorMessage());
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // permanently add the track to the server list
+ $rtn = $aseco->client->query('AddChallenge', $partialdir);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] AddChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ } else {
+ $aseco->client->resetError();
+ $aseco->client->query('GetChallengeInfo', $partialdir);
+ $track = $aseco->client->getResponse();
+ if ($aseco->client->isError()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetChallengeInfo - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = formatText('{#server}> {#error}Error getting info on track {#highlite}$i {1} {#error}!',
+ $partialdir);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $track['Name'] = stripNewlines($track['Name']);
+ // check whether to jukebox as well
+ // overrules /add-ed but not yet played track
+ if ($jukebox_adminadd) {
+ $uid = $track['UId'];
+ $jukebox[$uid]['FileName'] = $track['FileName'];
+ $jukebox[$uid]['Name'] = $track['Name'];
+ $jukebox[$uid]['Env'] = $track['Environnement'];
+ $jukebox[$uid]['Login'] = $login;
+ $jukebox[$uid]['Nick'] = $admin->nickname;
+ $jukebox[$uid]['source'] = $source;
+ $jukebox[$uid]['tmx'] = false;
+ $jukebox[$uid]['uid'] = $uid;
+ }
+
+ // log console message
+ $aseco->console('{1} [{2}] adds track "{3}" from {4}!', $logtitle, $login, stripColors($track['Name'], false), $source);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}adds {3}track: {#highlite}{4} {#admin}from {5}',
+ $chattitle, $admin->nickname,
+ ($jukebox_adminadd ? '& jukeboxes ' : ''),
+ stripColors($track['Name']), $source);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // throw 'tracklist changed' event
+ $aseco->releaseEvent('onTracklistChanged', array('add', $partialdir));
+
+ // throw 'jukebox changed' event
+ if ($jukebox_adminadd)
+ $aseco->releaseEvent('onJukeboxChanged', array('add', $jukebox[$uid]));
+ }
+ }
+ }
+ }
+ } else {
+ $message = formatText('{#server}> {#highlite}{1}{#error} is not a valid TMX Track_ID!',
+ $command['params'][$id]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+
+ /**
+ * Adds current /add-ed track permanently to server's track list
+ * by preventing its removal that normally occurs afterwards
+ */
+ } elseif ($command['params'][0] == 'addthis') {
+ global $tmxplayed, $tmxdir, $tmxtmpdir; // from plugin.rasp_jukebox.php, rasp.settings.php
+
+ // check for TMX /add-ed track
+ if ($tmxplayed) {
+ // remove track with old path
+ $rtn = $aseco->client->query('RemoveChallenge', $tmxplayed);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] RemoveChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ return;
+ } else {
+ // move the track file
+ $tmxnew = str_replace($tmxtmpdir, $tmxdir, $tmxplayed);
+ if (!rename($aseco->server->trackdir . $tmxplayed,
+ $aseco->server->trackdir . $tmxnew)) {
+ trigger_error('Could not rename TMX track ' . $tmxplayed . ' to ' . $tmxnew, E_USER_WARNING);
+ return;
+ } else {
+ // add track with new path
+ $rtn = $aseco->client->query('AddChallenge', $tmxnew);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] AddChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ return;
+ } else { // store new path
+ $aseco->server->challenge->filename = $tmxnew;
+
+ // throw 'tracklist changed' event
+ $aseco->releaseEvent('onTracklistChanged', array('rename', $tmxnew));
+ }
+ }
+ }
+
+ // disable track removal afterwards
+ $tmxplayed = false;
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}permanently adds current track: {#highlite}{3}',
+ $chattitle, $admin->nickname,
+ stripColors($aseco->server->challenge->name));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = formatText('{#server}> {#error}Current track {#highlite}$i {1} {#error}already permanently in track list!',
+ stripColors($aseco->server->challenge->name));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Add a local track to the track rotation.
+ */
+ } elseif ($command['params'][0] == 'addlocal') {
+ global $rasp, $jukebox_adminadd; // from plugin.rasp.php, rasp.settings.php
+
+ // check for local track file
+ if ($arglist[1] != '') {
+ $sepchar = substr($aseco->server->trackdir, -1, 1);
+ $partialdir = 'Challenges' . $sepchar . 'Downloaded' . $sepchar . $arglist[1];
+ if (!stristr($partialdir, '.Challenge.gbx')) {
+ $partialdir .= '.Challenge.gbx';
+ }
+ $localfile = $aseco->server->trackdir . $partialdir;
+ if ($nocasepath = file_exists_nocase($localfile)) {
+ // check for maximum online track size (256 KB)
+ if (filesize($nocasepath) >= 256 * 1024) {
+ $message = formatText($rasp->messages['TRACK_TOO_LARGE'][0],
+ round(filesize($nocasepath) / 1024));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ return;
+ }
+ $partialdir = str_replace($aseco->server->trackdir, '', $nocasepath);
+
+ // check track vs. server settings
+ if ($aseco->server->getGame() == 'TMF')
+ $rtn = $aseco->client->query('CheckChallengeForCurrentServerParams', $partialdir);
+ else
+ $rtn = true;
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] CheckChallengeForCurrentServerParams - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = formatText($rasp->messages['JUKEBOX_IGNORED'][0],
+ stripColors(str_replace('Challenges' . $sepchar . 'Downloaded' . $sepchar, '', $partialdir)), $aseco->client->getErrorMessage());
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // permanently add the track to the server list
+ $rtn = $aseco->client->query('AddChallenge', $partialdir);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] AddChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ } else {
+ $aseco->client->resetError();
+ $aseco->client->query('GetChallengeInfo', $partialdir);
+ $track = $aseco->client->getResponse();
+ if ($aseco->client->isError()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetChallengeInfo - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = formatText('{#server}> {#error}Error getting info on track {#highlite}$i {1} {#error}!',
+ $partialdir);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $track['Name'] = stripNewlines($track['Name']);
+ // check whether to jukebox as well
+ // overrules /add-ed but not yet played track
+ if ($jukebox_adminadd) {
+ $uid = $track['UId'];
+ $jukebox[$uid]['FileName'] = $track['FileName'];
+ $jukebox[$uid]['Name'] = $track['Name'];
+ $jukebox[$uid]['Env'] = $track['Environnement'];
+ $jukebox[$uid]['Login'] = $login;
+ $jukebox[$uid]['Nick'] = $admin->nickname;
+ $jukebox[$uid]['source'] = 'Local';
+ $jukebox[$uid]['tmx'] = false;
+ $jukebox[$uid]['uid'] = $uid;
+ }
+
+ // log console message
+ $aseco->console('{1} [{2}] adds local track {3} !', $logtitle, $login, stripColors($track['Name'], false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}adds {3}track: {#highlite}{4}',
+ $chattitle, $admin->nickname,
+ ($jukebox_adminadd ? '& jukeboxes ' : ''),
+ stripColors($track['Name']));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // throw 'tracklist changed' event
+ $aseco->releaseEvent('onTracklistChanged', array('add', $partialdir));
+
+ // throw 'jukebox changed' event
+ if ($jukebox_adminadd)
+ $aseco->releaseEvent('onJukeboxChanged', array('add', $jukebox[$uid]));
+ }
+ }
+ }
+ } else {
+ $message = '{#server}> {#highlite}' . $partialdir . '{#error} not found!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = '{#server}> {#error}You must include a local track filename!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Warns a player with the specified login/PlayerID.
+ */
+ } elseif ($command['params'][0] == 'warn' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) {
+ // display warning message
+ $message = $aseco->getChatMessage('WARNING');
+ if ($aseco->server->getGame() == 'TMF') {
+ $message = preg_split('/{br}/', $aseco->formatColors($message));
+ foreach ($message as &$line)
+ $line = array($line);
+
+ display_manialink($target->login, $aseco->formatColors('{#welcome}WARNING:'), array('Icons64x64_1', 'TV'),
+ $message, array(0.8), 'OK');
+ } else { // TMN
+ $message = str_replace('{br}', LF, $aseco->formatColors($message));
+ $aseco->client->query('SendDisplayServerMessageToLogin', $target->login, $message, 'OK', '', 0);
+ }
+ // log console message
+ $aseco->console('{1} [{2}] warned player {3}!', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} warned {#highlite}{3}$z$s{#admin} !',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ }
+
+ /**
+ * Kicks a player with the specified login/PlayerID.
+ */
+ } elseif ($command['params'][0] == 'kick' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) {
+ // log console message
+ $aseco->console('{1} [{2}] kicked player {3}!', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} kicked {#highlite}{3}$z$s{#admin} !',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // kick the player
+ $aseco->client->query('Kick', $target->login);
+ }
+
+ /**
+ * Kicks a ghost player with the specified login.
+ * This variant for ghost players that got disconnected doesn't
+ * check the login for validity and doesn't work with Player_IDs.
+ */
+ } elseif ($command['params'][0] == 'kickghost' && $command['params'][1] != '') {
+
+ // get player login without validation
+ $target = $command['params'][1];
+
+ // log console message
+ $aseco->console('{1} [{2}] kicked ghost player {3}!', $logtitle, $login, $target);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} kicked ghost {#highlite}{3}$z$s{#admin} !',
+ $chattitle, $admin->nickname, $target);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // kick the ghost player
+ $aseco->client->query('Kick', $target);
+
+ /**
+ * Ban a player with the specified login/PlayerID.
+ */
+ } elseif ($command['params'][0] == 'ban' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) {
+ // log console message
+ $aseco->console('{1} [{2}] bans player {3}!', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} bans {#highlite}{3}$z$s{#admin} !',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // update banned IPs file
+ $aseco->bannedips[] = $target->ip;
+ $aseco->writeIPs();
+
+ // ban the player and also kick him
+ $aseco->client->query('Ban', $target->login);
+ }
+
+ /**
+ * Un-bans player with the specified login/PlayerID.
+ */
+ } elseif ($command['params'][0] == 'unban' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) {
+ $bans = get_banlist($aseco);
+ // unban the player
+ $rtn = $aseco->client->query('UnBan', $target->login);
+ if (!$rtn) {
+ $message = formatText('{#server}> {#highlite}{1}{#error} is not a banned player!',
+ $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ if (($i = array_search($bans[$target->login][2], $aseco->bannedips)) !== false) {
+ // update banned IPs file
+ $aseco->bannedips[$i] = '';
+ $aseco->writeIPs();
+ }
+
+ // log console message
+ $aseco->console('{1} [{2}] unbans player {3}', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} un-bans {#highlite}{3}',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+
+ /**
+ * Ban a player with the specified IP address.
+ */
+ } elseif ($command['params'][0] == 'banip' && $command['params'][1] != '') {
+
+ // check for valid IP not already banned
+ $ipaddr = $command['params'][1];
+ if (preg_match('/^\d+\.\d+\.\d+\.\d+$/', $ipaddr)) {
+ if (empty($aseco->bannedips) || !in_array($ipaddr, $aseco->bannedips)) {
+ // log console message
+ $aseco->console('{1} [{2}] banned IP {3}!', $logtitle, $login, $ipaddr);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} bans IP {#highlite}{3}$z$s{#admin} !',
+ $chattitle, $admin->nickname, $ipaddr);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // update banned IPs file
+ $aseco->bannedips[] = $ipaddr;
+ $aseco->writeIPs();
+ } else {
+ $message = formatText('{#server}> {#highlite}{1}{#error} is already banned!',
+ $ipaddr);
+ }
+ } else {
+ $message = formatText('{#server}> {#highlite}{1}{#error} is not a valid IP address!',
+ $ipaddr);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Un-bans player with the specified IP address.
+ */
+ } elseif ($command['params'][0] == 'unbanip' && $command['params'][1] != '') {
+
+ // check for banned IP
+ if (($i = array_search($command['params'][1], $aseco->bannedips)) === false) {
+ $message = formatText('{#server}> {#highlite}{1}{#error} is not a banned IP address!',
+ $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // update banned IPs file
+ $aseco->bannedips[$i] = '';
+ $aseco->writeIPs();
+
+ // log console message
+ $aseco->console('{1} [{2}] unbans IP {3}', $logtitle, $login, $command['params'][1]);
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} un-bans IP {#highlite}{3}',
+ $chattitle, $admin->nickname, $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Blacklists a player with the specified login/PlayerID.
+ */
+ } elseif ($command['params'][0] == 'black' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) {
+ // log console message
+ $aseco->console('{1} [{2}] blacklists player {3}!', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} blacklists {#highlite}{3}$z$s{#admin} !',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // blacklist the player and then kick him
+ $aseco->client->query('BlackList', $target->login);
+ $aseco->client->query('Kick', $target->login);
+
+ // update blacklist file
+ $filename = $aseco->settings['blacklist_file'];
+ $rtn = $aseco->client->query('SaveBlackList', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] SaveBlackList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ }
+ }
+
+ /**
+ * Un-blacklists player with the specified login/PlayerID.
+ */
+ } elseif ($command['params'][0] == 'unblack' && $command['params'][1] != '') {
+
+ $target = false;
+ $param = $command['params'][1];
+
+ // get new list of all blacklisted players
+ $blacks = get_blacklist($aseco);
+ // check as login
+ if (array_key_exists($param, $blacks)) {
+ $target = new Player();
+ // check as player ID
+ } elseif (is_numeric($param) && $param > 0) {
+ if (empty($admin->playerlist)) {
+ $message = '{#server}> {#error}Use {#highlite}$i/players {#error}first (optionally {#highlite}$i/players {#error})';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ return false;
+ }
+ $pid = ltrim($param, '0');
+ $pid--;
+ // find player by given #
+ if (array_key_exists($pid, $admin->playerlist)) {
+ $param = $admin->playerlist[$pid]['login'];
+ $target = new Player();
+ } else {
+ $message = '{#server}> {#error}Player_ID not found! Type {#highlite}$i/players {#error}to see all players.';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ return false;
+ }
+ }
+
+ // check for valid param
+ if ($target !== false) {
+ $target->login = $param;
+ $target->nickname = $aseco->getPlayerNick($param);
+ if ($target->nickname == '')
+ $target->nickname = $param;
+
+ // unblacklist the player
+ $rtn = $aseco->client->query('UnBlackList', $target->login);
+ if (!$rtn) {
+ $message = formatText('{#server}> {#highlite}{1}{#error} is not a blacklisted player!',
+ $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] unblacklists player {3}', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} un-blacklists {#highlite}{3}',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ // update blacklist file
+ $filename = $aseco->settings['blacklist_file'];
+ $rtn = $aseco->client->query('SaveBlackList', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] SaveBlackList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ }
+ }
+ } else {
+ $message = '{#server}> {#highlite}' . $param . ' {#error}is not a valid player! Use {#highlite}$i/players {#error}to find the correct login or Player_ID.';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Adds a guest player with the specified login/PlayerID.
+ */
+ } elseif ($command['params'][0] == 'addguest' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) {
+ // add the guest player
+ $aseco->client->query('AddGuest', $target->login);
+
+ // log console message
+ $aseco->console('{1} [{2}] adds guest player {3}', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} adds guest {#highlite}{3}',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ // update guestlist file
+ $filename = $aseco->settings['guestlist_file'];
+ $rtn = $aseco->client->query('SaveGuestList', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] SaveGuestList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ }
+ }
+
+ /**
+ * Removes a guest player with the specified login/PlayerID.
+ */
+ } elseif ($command['params'][0] == 'removeguest' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) {
+ // remove the guest player
+ $rtn = $aseco->client->query('RemoveGuest', $target->login);
+ if (!$rtn) {
+ $message = formatText('{#server}> {#highlite}{1}{#error} is not a guest player!',
+ $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] removes guest player {3}', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} removes guest {#highlite}{3}',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ // update guestlist file
+ $filename = $aseco->settings['guestlist_file'];
+ $rtn = $aseco->client->query('SaveGuestList', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] SaveGuestList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ }
+ }
+ }
+
+ /**
+ * Passes a chat-based or TMX /add vote.
+ */
+ } elseif ($command['params'][0] == 'pass') {
+ global $tmxadd, $chatvote, $plrvotes; // from plugin.rasp_jukebox.php, plugin.rasp_votes.php
+
+ // pass any TMX and chat vote
+ if (!empty($tmxadd)) {
+ // force required votes down to the last one
+ $tmxadd['votes'] = 1;
+ }
+ elseif (!empty($chatvote)) {
+ $chatvote['votes'] = 1;
+ }
+ else { // no vote in progress
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}There is no vote right now!'), $login);
+ return;
+ }
+
+ // log console message
+ $aseco->console('{1} [{2}] passes vote!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} passes vote!',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ // bypass double vote check
+ $plrvotes = array();
+ // enter the last vote
+ chat_y($aseco, $command);
+
+ /**
+ * Cancels any vote.
+ */
+ } elseif ($command['params'][0] == 'cancel' ||
+ $command['params'][0] == 'can') {
+ global $tmxadd, $chatvote; // from plugin.rasp_jukebox.php, plugin.rasp_votes.php
+
+ // cancel any CallVote, TMX and chat vote
+ $aseco->client->query('CancelVote');
+ $tmxadd = array();
+ $chatvote = array();
+ // disable all vote panels
+ if ($aseco->server->getGame() == 'TMF')
+ allvotepanels_off($aseco);
+
+ // log console message
+ $aseco->console('{1} [{2}] cancels vote!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} cancels vote!',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ /**
+ * Forces end of current round.
+ */
+ } elseif ($command['params'][0] == 'endround' ||
+ $command['params'][0] == 'er') {
+ global $chatvote; // from plugin.rasp_votes.php
+
+ // cancel possibly ongoing endround vote
+ if (!empty($chatvote) && $chatvote['type'] == 0) {
+ $chatvote = array();
+ // disable all vote panels
+ if ($aseco->server->getGame() == 'TMF')
+ allvotepanels_off($aseco);
+ }
+
+ // end this round
+ $aseco->client->query('ForceEndRound');
+
+ // log console message
+ $aseco->console('{1} [{2}] forces round end!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} forces round end!',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ /**
+ * Displays the live or known players (on/offline) list.
+ * TMF player management inspired by Mistral.
+ */
+ } elseif ($command['params'][0] == 'players') {
+
+ $admin->playerlist = array();
+ $admin->msgs = array();
+
+ // remember players parameter for possible refresh
+ $admin->panels['plyparam'] = $command['params'][1];
+ $onlineonly = (strtolower($command['params'][1]) == 'live');
+ // get current ignore/ban/black/guest lists
+ if ($aseco->server->getGame() == 'TMF') {
+ $ignores = get_ignorelist($aseco);
+ $bans = get_banlist($aseco);
+ $blacks = get_blacklist($aseco);
+ $guests = get_guestlist($aseco);
+ }
+
+ // create new list of online players
+ $aseco->client->resetError();
+ $onlinelist = array();
+ // get current players on the server (hardlimited to 300)
+ if ($aseco->server->getGame() == 'TMF')
+ $aseco->client->query('GetPlayerList', 300, 0, 1);
+ else
+ $aseco->client->query('GetPlayerList', 300, 0);
+ $players = $aseco->client->getResponse();
+ if ($aseco->client->isError()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetPlayerList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ } else {
+ foreach ($players as $pl)
+ if ($aseco->server->getGame() == 'TMF') {
+ // on relay, check for player from master server
+ if (!$aseco->server->isrelay || floor($pl['Flags'] / 10000) % 10 == 0)
+ $onlinelist[$pl['Login']] = array('login' => $pl['Login'],
+ 'nick' => $pl['NickName'],
+ 'spec' => $pl['SpectatorStatus']);
+ } else {
+ $onlinelist[$pl['Login']] = array('login' => $pl['Login'],
+ 'nick' => $pl['NickName'],
+ 'spec' => $pl['IsSpectator']);
+ }
+ }
+
+ // use online list?
+ if ($onlineonly) {
+ $playerlist = $onlinelist;
+ } else {
+ // search for known players
+ $query = 'SELECT login,nickname FROM players
+ WHERE login LIKE ' . quotedString('%' . $arglist[1] . '%') .
+ ' OR nickname LIKE ' . quotedString('%' . $arglist[1] . '%') .
+ ' LIMIT 5000'; // prevent possible memory overrun
+ $result = mysql_query($query);
+
+ $playerlist = array();
+ if (mysql_num_rows($result) > 0) {
+ while ($row = mysql_fetch_row($result)) {
+ // skip any LAN logins
+ if (!isLANLogin($row[0]))
+ $playerlist[$row[0]] = array('login' => $row[0],
+ 'nick' => $row[1],
+ 'spec' => false);
+ }
+ }
+ mysql_free_result($result);
+ }
+
+ if (!empty($playerlist)) {
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = ($onlineonly ? 'Online' : 'Known') . ' Players On This Server:' . LF .
+ 'Id {#nick}Nick $g/{#login} Login' . LF;
+ $msg = '';
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($playerlist as $pl) {
+ $plarr = array();
+ $plarr['login'] = $pl['login'];
+ $admin->playerlist[] = $plarr;
+
+ $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}'
+ . str_ireplace('$w', '', $pl['nick']) . '$z / '
+ . ($aseco->isAnyAdminL($pl['login']) ? '{#logina}' : '{#login}' )
+ . $pl['login'] . LF;
+ $pid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } elseif (count($admin->msgs) > 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No player(s) found!'), $login);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = ($onlineonly ? 'Online' : 'Known') . ' Players On This Server:';
+ $msg = array();
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login', 'Warn', 'Ignore', 'Kick', 'Ban', 'Black', 'Guest', 'Spec');
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(1.49, 0.15, 0.5, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12), array('Icons128x128_1', 'Buddies'));
+
+ foreach ($playerlist as $lg => $pl) {
+ $plarr = array();
+ $plarr['login'] = $lg;
+ $admin->playerlist[] = $plarr;
+
+ // format nickname & login
+ $ply = '{#black}' . str_ireplace('$w', '', $pl['nick']) . '$z / '
+ . ($aseco->isAnyAdminL($pl['login']) ? '{#logina}' : '{#login}' )
+ . $pl['login'];
+ // define colored column strings
+ $wrn = '$ff3Warn';
+ $ign = '$f93Ignore';
+ $uig = '$d93UnIgn';
+ $kck = '$c3fKick';
+ $ban = '$f30Ban';
+ $ubn = '$c30UnBan';
+ $blk = '$f03Black';
+ $ubk = '$c03UnBlack';
+ $gst = '$3c3Add';
+ $ugt = '$393Remove';
+ $frc = '$09fForce';
+ $off = '$09cOffln';
+ $spc = '$09cSpec';
+
+ // always add clickable buttons
+ if ($pid <= 200) {
+ $ply = array($ply, $pid+2000);
+ if (array_key_exists($lg, $onlinelist)) {
+ // determine online operations
+ $wrn = array($wrn, $pid+2200);
+ if (array_key_exists($lg, $ignores))
+ $ign = array($uig, $pid+2600);
+ else
+ $ign = array($ign, $pid+2400);
+ $kck = array($kck, $pid+2800);
+ if (array_key_exists($lg, $bans))
+ $ban = array($ubn, $pid+3200);
+ else
+ $ban = array($ban, $pid+3000);
+ if (array_key_exists($lg, $blacks))
+ $blk = array($ubk, $pid+3600);
+ else
+ $blk = array($blk, $pid+3400);
+ if (array_key_exists($lg, $guests))
+ $gst = array($ugt, $pid+4000);
+ else
+ $gst = array($gst, $pid+3800);
+ if (!$onlinelist[$lg]['spec'])
+ $spc = array($frc, $pid+4200);
+ } else {
+ // determine offline operations
+ if (array_key_exists($lg, $ignores))
+ $ign = array($uig, $pid+2600);
+ if (array_key_exists($lg, $bans))
+ $ban = array($ubn, $pid+3200);
+ if (array_key_exists($lg, $blacks))
+ $blk = array($ubk, $pid+3600);
+ else
+ $blk = array($blk, $pid+3400);
+ if (array_key_exists($lg, $guests))
+ $gst = array($ugt, $pid+4000);
+ else
+ $gst = array($gst, $pid+3800);
+ $spc = $off;
+ }
+ } else {
+ // no more buttons
+ if (array_key_exists($lg, $ignores))
+ $ign = $uig;
+ if (array_key_exists($lg, $bans))
+ $ban = $ubn;
+ if (array_key_exists($lg, $blacks))
+ $blk = $ubk;
+ if (array_key_exists($lg, $guests))
+ $gst = $ugt;
+ if (array_key_exists($lg, $onlinelist)) {
+ if (!$onlinelist[$lg]['spec'])
+ $spc = $frc;
+ } else {
+ $spc = $off;
+ }
+ }
+
+ $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply,
+ $wrn, $ign, $kck, $ban, $blk, $gst, $spc);
+ $pid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login', 'Warn', 'Ignore', 'Kick', 'Ban', 'Black', 'Guest', 'Spec');
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ if (count($admin->msgs) > 1) {
+ display_manialink_multi($admin);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No player(s) found!'), $login);
+ }
+ }
+ } else {
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No player(s) found!'), $login);
+ }
+
+ /**
+ * Displays the ban list.
+ */
+ } elseif ($command['params'][0] == 'showbanlist' ||
+ $command['params'][0] == 'listbans') {
+
+ $admin->playerlist = array();
+ $admin->msgs = array();
+
+ // get new list of all banned players
+ $newlist = get_banlist($aseco);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Currently Banned Players:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF;
+ $msg = '';
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($newlist as $player) {
+ $plarr = array();
+ $plarr['login'] = $player[0];
+ $admin->playerlist[] = $plarr;
+
+ $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}'
+ . str_ireplace('$w', '', $player[1]) . '$z / {#login}' . $player[0] . LF;
+ $pid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } elseif (count($admin->msgs) > 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No banned player(s) found!'), $login);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Currently Banned Players:';
+ $msg = array();
+ if ($aseco->settings['clickable_lists'])
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnBan)');
+ else
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons64x64_1', 'NotBuddy'));
+ foreach ($newlist as $player) {
+ $plarr = array();
+ $plarr['login'] = $player[0];
+ $admin->playerlist[] = $plarr;
+
+ // format nickname & login
+ $ply = '{#black}' . str_ireplace('$w', '', $player[1])
+ . '$z / {#login}' . $player[0];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $pid <= 200)
+ $ply = array($ply, $pid+4600); // action id
+
+ $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply);
+ $pid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->settings['clickable_lists'])
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnBan)');
+ else
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ if (count($admin->msgs) > 1) {
+ display_manialink_multi($admin);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No banned player(s) found!'), $login);
+ }
+ }
+
+ /**
+ * Displays the banned IPs list.
+ */
+ } elseif ($command['params'][0] == 'showiplist' ||
+ $command['params'][0] == 'listips') {
+
+ $admin->playerlist = array();
+ $admin->msgs = array();
+
+ // get new list of all banned IPs
+ $newlist = $aseco->bannedips;
+ if (empty($newlist)) {
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No banned IP(s) found!'), $login);
+ return;
+ }
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Currently Banned IPs:' . LF . 'Id {#login}IP' . LF;
+ $msg = '';
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($newlist as $ip) {
+ if ($ip != '') {
+ $plarr = array();
+ $plarr['ip'] = $ip;
+ $admin->playerlist[] = $plarr;
+
+ $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#login}' . $ip . LF;
+ $pid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } elseif (count($admin->msgs) > 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No banned IP(s) found!'), $login);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Currently Banned IPs:';
+ $msg = array();
+ if ($aseco->settings['clickable_lists'])
+ $msg[] = array('Id', '{#nick}IP$g (click to UnBan)');
+ else
+ $msg[] = array('Id', '{#nick}IP');
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(0.6, 0.1, 0.5), array('Icons64x64_1', 'NotBuddy'));
+ foreach ($newlist as $ip) {
+ if ($ip != '') {
+ $plarr = array();
+ $plarr['ip'] = $ip;
+ $admin->playerlist[] = $plarr;
+
+ // format IP
+ $ply = '{#black}' . $ip;
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $pid <= 200)
+ $ply = array($ply, -7900-$pid); // action id
+
+ $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply);
+ $pid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->settings['clickable_lists'])
+ $msg[] = array('Id', '{#login}IP$g (click to UnBan)');
+ else
+ $msg[] = array('Id', '{#login}IP');
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ if (count($admin->msgs) > 1) {
+ display_manialink_multi($admin);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No banned IP(s) found!'), $login);
+ }
+ }
+
+ /**
+ * Displays the black list.
+ */
+ } elseif ($command['params'][0] == 'showblacklist' ||
+ $command['params'][0] == 'listblacks') {
+
+ $admin->playerlist = array();
+ $admin->msgs = array();
+
+ // get new list of all blacklisted players
+ $newlist = get_blacklist($aseco);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Currently Blacklisted Players:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF;
+ $msg = '';
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($newlist as $player) {
+ $plarr = array();
+ $plarr['login'] = $player[0];
+ $admin->playerlist[] = $plarr;
+
+ $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}'
+ . str_ireplace('$w', '', $player[1]) . '$z / {#login}' . $player[0] . LF;
+ $pid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } elseif (count($admin->msgs) > 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No blacklisted player(s) found!'), $login);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Currently Blacklisted Players:';
+ $msg = array();
+ if ($aseco->settings['clickable_lists'])
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnBlack)');
+ else
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons64x64_1', 'NotBuddy'));
+ foreach ($newlist as $player) {
+ $plarr = array();
+ $plarr['login'] = $player[0];
+ $admin->playerlist[] = $plarr;
+
+ // format nickname & login
+ $ply = '{#black}' . str_ireplace('$w', '', $player[1])
+ . '$z / {#login}' . $player[0];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $pid <= 200)
+ $ply = array($ply, $pid+4800); // action id
+
+ $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply);
+ $pid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->settings['clickable_lists'])
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnBlack)');
+ else
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ if (count($admin->msgs) > 1) {
+ display_manialink_multi($admin);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No blacklisted player(s) found!'), $login);
+ }
+ }
+
+ /**
+ * Displays the guest list.
+ */
+ } elseif ($command['params'][0] == 'showguestlist' ||
+ $command['params'][0] == 'listguests') {
+
+ $admin->playerlist = array();
+ $admin->msgs = array();
+
+ // get new list of all guest players
+ $newlist = get_guestlist($aseco);
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Current Guest Players:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF;
+ $msg = '';
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($newlist as $player) {
+ $plarr = array();
+ $plarr['login'] = $player[0];
+ $admin->playerlist[] = $plarr;
+
+ $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}'
+ . str_ireplace('$w', '', $player[1]) . '$z / {#login}' . $player[0] . LF;
+ $pid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } elseif (count($admin->msgs) > 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No guest player(s) found!'), $login);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Current Guest Players:';
+ $msg = array();
+ if ($aseco->settings['clickable_lists'])
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to Remove)');
+ else
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons128x128_1', 'Invite'));
+ foreach ($newlist as $player) {
+ $plarr = array();
+ $plarr['login'] = $player[0];
+ $admin->playerlist[] = $plarr;
+
+ // format nickname & login
+ $ply = '{#black}' . str_ireplace('$w', '', $player[1])
+ . '$z / {#login}' . $player[0];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $pid <= 200)
+ $ply = array($ply, $pid+5000); // action id
+
+ $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply);
+ $pid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->settings['clickable_lists'])
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to Remove)');
+ else
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ if (count($admin->msgs) > 1) {
+ display_manialink_multi($admin);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No guest player(s) found!'), $login);
+ }
+ }
+
+ /**
+ * Saves the banned IPs list to bannedips.xml (default).
+ */
+ } elseif ($command['params'][0] == 'writeiplist') {
+
+ // write banned IPs file
+ $filename = $aseco->settings['bannedips_file'];
+ if (!$aseco->writeIPs()) {
+ $message = '{#server}> {#error}Error writing {#highlite}$i ' . $filename . ' {#error}!';
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] wrote ' . $filename . '!', $logtitle, $login);
+
+ $message = '{#server}> {#highlite}' . $filename . ' {#admin}written';
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Loads the banned IPs list from bannedips.xml (default).
+ */
+ } elseif ($command['params'][0] == 'readiplist') {
+
+ // read banned IPs file
+ $filename = $aseco->settings['bannedips_file'];
+ if (!$aseco->readIPs()) {
+ $message = '{#server}> {#highlite}' . $filename . ' {#error}not found, or error reading!';
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] read ' . $filename . '!', $logtitle, $login);
+
+ $message = '{#server}> {#highlite}' . $filename . ' {#admin}read';
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Saves the black list to blacklist.txt (default).
+ */
+ } elseif ($command['params'][0] == 'writeblacklist') {
+
+ $filename = $aseco->settings['blacklist_file'];
+ $rtn = $aseco->client->query('SaveBlackList', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] SaveBlackList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = '{#server}> {#error}Error writing {#highlite}$i ' . $filename . ' {#error}!';
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] wrote ' . $filename . '!', $logtitle, $login);
+
+ $message = '{#server}> {#highlite}' . $filename . ' {#admin}written';
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Loads the black list from blacklist.txt (default).
+ */
+ } elseif ($command['params'][0] == 'readblacklist') {
+
+ $filename = $aseco->settings['blacklist_file'];
+ $rtn = $aseco->client->query('LoadBlackList', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] LoadBlackList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = '{#server}> {#highlite}' . $filename . ' {#error}not found, or error reading!';
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] read ' . $filename . '!', $logtitle, $login);
+
+ $message = '{#server}> {#highlite}' . $filename . ' {#admin}read';
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Saves the guest list to guestlist.txt (default).
+ */
+ } elseif ($command['params'][0] == 'writeguestlist') {
+
+ $filename = $aseco->settings['guestlist_file'];
+ $rtn = $aseco->client->query('SaveGuestList', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] SaveGuestList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = '{#server}> {#error}Error writing {#highlite}$i ' . $filename . ' {#error}!';
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] wrote ' . $filename . '!', $logtitle, $login);
+
+ $message = '{#server}> {#highlite}' . $filename . ' {#admin}written';
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Loads the guest list from guestlist.txt (default).
+ */
+ } elseif ($command['params'][0] == 'readguestlist') {
+
+ $filename = $aseco->settings['guestlist_file'];
+ $rtn = $aseco->client->query('LoadGuestList', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] LoadGuestList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = '{#server}> {#highlite}' . $filename . ' {#error}not found, or error loading!';
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] read ' . $filename . '!', $logtitle, $login);
+
+ $message = '{#server}> {#highlite}' . $filename . ' {#admin}read';
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Cleans the ban list.
+ */
+ } elseif ($command['params'][0] == 'cleanbanlist') {
+
+ // clean server ban list
+ $aseco->client->query('CleanBanList');
+
+ // log console message
+ $aseco->console('{1} [{2}] cleaned ban list!', $logtitle, $login);
+
+ // show chat message
+ $message = '{#server}> {#admin}Cleaned ban list!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Cleans the banned IPs list.
+ */
+ } elseif ($command['params'][0] == 'cleaniplist') {
+
+ // clean banned IPs file
+ $aseco->bannedips = array();
+ $aseco->writeIPs();
+
+ // log console message
+ $aseco->console('{1} [{2}] cleaned banned IPs list!', $logtitle, $login);
+
+ // show chat message
+ $message = '{#server}> {#admin}Cleaned banned IPs list!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Cleans the black list.
+ */
+ } elseif ($command['params'][0] == 'cleanblacklist') {
+
+ // clean server black list
+ $aseco->client->query('CleanBlackList');
+
+ // log console message
+ $aseco->console('{1} [{2}] cleaned black list!', $logtitle, $login);
+
+ // show chat message
+ $message = '{#server}> {#admin}Cleaned black list!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Cleans the guest list.
+ */
+ } elseif ($command['params'][0] == 'cleanguestlist') {
+
+ // clean server guest list
+ $aseco->client->query('CleanGuestList');
+
+ // log console message
+ $aseco->console('{1} [{2}] cleaned guest list!', $logtitle, $login);
+
+ // show chat message
+ $message = '{#server}> {#admin}Cleaned guest list!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Merges a global black list.
+ */
+ } elseif ($command['params'][0] == 'mergegbl') {
+ global $globalbl_url; // from rasp.settings.php
+
+ if (function_exists('admin_mergegbl')) {
+ if (isset($command['params'][1]) && $command['params'][1] != '') {
+ if (preg_match('/^https?:\/\/[-\w:.]+\//i', $command['params'][1])) {
+ admin_mergegbl($aseco, $logtitle, $login, true, $command['params'][1]); // from plugin.uptodate.php
+ } else {
+ $message = '{#server}> {#highlite}' . $command['params'][1] . ' {#error}is an invalid HTTP URL!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ admin_mergegbl($aseco, $logtitle, $login, true, $globalbl_url);
+ }
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Merge Global BL unavailable - include plugins.uptodate.php in plugins.xml';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Shows/reloads player access control.
+ */
+ } elseif ($command['params'][0] == 'access') {
+
+ if (function_exists('admin_access')) {
+ $command['params'] = $command['params'][1];
+ admin_access($aseco, $command); // from plugin.access.php
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Access control unavailable - include plugins.access.php in plugins.xml';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Saves the track list to tracklist.txt (default).
+ */
+ } elseif ($command['params'][0] == 'writetracklist') {
+
+ $filename = $aseco->settings['default_tracklist'];
+ // check for optional alternate filename
+ if ($command['params'][1] != '') {
+ $filename = $command['params'][1];
+ if (!stristr($filename, '.txt')) {
+ $filename .= '.txt';
+ }
+ }
+ $rtn = $aseco->client->query('SaveMatchSettings', 'MatchSettings/' . $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] SaveMatchSettings - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = '{#server}> {#error}Error writing {#highlite}$i ' . $filename . ' {#error}!';
+ } else {
+ // should a random filter be added?
+ if ($aseco->settings['writetracklist_random']) {
+ $tracksfile = $aseco->server->trackdir . 'MatchSettings/' . $filename;
+ // read the match settings file
+ if (!$list = @file_get_contents($tracksfile)) {
+ trigger_error('Could not read match settings file ' . $tracksfile . ' !', E_USER_WARNING);
+ } else {
+ // insert random filter after section
+ $list = preg_replace('/<\/gameinfos>/', '$0' . CRLF . CRLF .
+ "\t" . CRLF .
+ "\t\t1" . CRLF .
+ "\t", $list);
+
+ // write out the match settings file
+ if (!@file_put_contents($tracksfile, $list)) {
+ trigger_error('Could not write match settings file ' . $tracksfile . ' !', E_USER_WARNING);
+ }
+ }
+ }
+
+ // log console message
+ $aseco->console('{1} [{2}] wrote track list: {3} !', $logtitle, $login, $filename);
+
+ $message = '{#server}> {#highlite}' . $filename . '{#admin} written';
+
+ // throw 'tracklist changed' event
+ $aseco->releaseEvent('onTracklistChanged', array('write', null));
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Loads the track list from tracklist.txt (default).
+ */
+ } elseif ($command['params'][0] == 'readtracklist') {
+
+ $filename = $aseco->settings['default_tracklist'];
+ // check for optional alternate filename
+ if ($command['params'][1] != '') {
+ $filename = $command['params'][1];
+ if (!stristr($filename, '.txt')) {
+ $filename .= '.txt';
+ }
+ }
+ if (file_exists($aseco->server->trackdir . 'MatchSettings/' . $filename)) {
+ $rtn = $aseco->client->query('LoadMatchSettings', 'MatchSettings/' . $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] LoadMatchSettings - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = '{#server}> {#error}Error reading {#highlite}$i ' . $filename . ' {#error}!';
+ } else {
+ // get track count
+ $cnt = $aseco->client->getResponse();
+
+ // log console message
+ $aseco->console('{1} [{2}] read track list: {3} ({4} tracks)!', $logtitle, $login, $filename, $cnt);
+
+ $message = '{#server}> {#highlite}' . $filename . '{#admin} read with {#highlite}' . $cnt . '{#admin} track' . ($cnt == 1 ? '' : 's');
+
+ // throw 'tracklist changed' event
+ $aseco->releaseEvent('onTracklistChanged', array('read', null));
+ }
+ } else {
+ $message = '{#server}> {#error}Cannot find {#highlite}$i ' . $filename . ' {#error}!';
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Randomizes current track list.
+ */
+ } elseif ($command['params'][0] == 'shuffle' ||
+ $command['params'][0] == 'shufflemaps') {
+ global $autosave_matchsettings; // from rasp.settings.php
+
+ if ($aseco->settings['writetracklist_random']) {
+ if ($autosave_matchsettings) {
+ if (file_exists($aseco->server->trackdir . 'MatchSettings/' . $autosave_matchsettings)) {
+ $rtn = $aseco->client->query('LoadMatchSettings', 'MatchSettings/' . $autosave_matchsettings);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] LoadMatchSettings - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = '{#server}> {#error}Error reading {#highlite}$i ' . $autosave_matchsettings . ' {#error}!';
+ } else {
+ // get track count
+ $cnt = $aseco->client->getResponse();
+
+ // log console message
+ $aseco->console('{1} [{2}] shuffled track list: {3} ({4} tracks)!', $logtitle, $login, $autosave_matchsettings, $cnt);
+
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} shuffled track list with {#highlite}{3}{#admin} track{4}!',
+ $chattitle, $admin->nickname, $cnt, ($cnt == 1 ? '' : 's'));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ return;
+ }
+ } else {
+ $message = '{#server}> {#error}Cannot find autosave matchsettings file {#highlite}$i ' . $autosave_matchsettings . ' {#error}!';
+ }
+ } else {
+ $message = '{#server}> {#error}No autosave matchsettings file defined in {#highlite}$i rasp.settings.php {#error}!';
+ }
+ } else {
+ $message = '{#server}> {#error}No tracklist randomization defined in {#highlite}$i config.xml {#error}!';
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Displays list of duplicate tracks.
+ */
+ } elseif ($command['params'][0] == 'listdupes') {
+
+ $admin->tracklist = array();
+ $admin->msgs = array();
+
+ // get new list of all tracks
+ $aseco->client->resetError();
+ $dupelist = array();
+ $newlist = array();
+ $done = false;
+ $size = 300;
+ $i = 0;
+ while (!$done) {
+ $aseco->client->query('GetChallengeList', $size, $i);
+ $tracks = $aseco->client->getResponse();
+ if (!empty($tracks)) {
+ if ($aseco->client->isError()) {
+ // warning if no tracks found
+ if (empty($newlist))
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetChallengeList - ' . $aseco->client->getErrorMessage() . ' - No tracks found!', E_USER_WARNING);
+ $done = true;
+ break;
+ }
+ foreach ($tracks as $trow) {
+ $trow['Name'] = stripNewlines($trow['Name']);
+ // store duplicate track
+ if (isset($newlist[$trow['UId']])) {
+ $dupelist[] = $trow;
+ } else {
+ $newlist[$trow['UId']] = $trow;
+ }
+ }
+ if (count($tracks) < $size) {
+ // got less than 300 tracks, might as well leave
+ $done = true;
+ } else {
+ $i += $size;
+ }
+ } else {
+ $done = true;
+ }
+ }
+
+ // check for duplicate tracks
+ if (!empty($dupelist)) {
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Duplicate Tracks On This Server:' . LF . 'Id Name' . LF;
+ $msg = '';
+ $tid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($dupelist as $row) {
+ $trackname = $row['Name'];
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+
+ // store track in player object for remove/erase
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $admin->tracklist[] = $trkarr;
+
+ $msg .= '$g' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. {#black}' . $trackname . LF;
+ $tid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } else { // > 2
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Duplicate Tracks On This Server:';
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Env');
+ else
+ $msg[] = array('Id', 'Name');
+ $tid = 1;
+ $lines = 0;
+ // reserve extra width for $w tags
+ $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0);
+ if ($aseco->server->packmask != 'Stadium')
+ $admin->msgs[0] = array(1, $head, array(0.90+$extra, 0.15, 0.6+$extra, 0.15), array('Icons128x128_1', 'Challenge'));
+ else
+ $admin->msgs[0] = array(1, $head, array(0.75+$extra, 0.15, 0.6+$extra), array('Icons128x128_1', 'Challenge'));
+ foreach ($dupelist as $row) {
+ $trackname = stripColors($row['Name']);
+ if (!$aseco->settings['lists_colortracks'])
+ $trackname = stripColors($trackname);
+
+ // store track in player object for remove/erase
+ $trkarr = array();
+ $trkarr['name'] = $row['Name'];
+ $trkarr['environment'] = $row['Environnement'];
+ $trkarr['filename'] = $row['FileName'];
+ $trkarr['uid'] = $row['UId'];
+ $admin->tracklist[] = $trkarr;
+
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ '{#black}' . $trackname,
+ $trkarr['environment']);
+ else
+ $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.',
+ '{#black}' . $trackname);
+ $tid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->server->packmask != 'Stadium')
+ $msg[] = array('Id', 'Name', 'Env');
+ else
+ $msg[] = array('Id', 'Name');
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ display_manialink_multi($admin);
+ }
+
+ } else {
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No duplicate track(s) found!'), $login);
+ return;
+ }
+
+ /**
+ * Remove a track from the active rotation, optionally erase track file too.
+ * Doesn't update match settings unfortunately - command 'writetracklist' will though.
+ */
+ } elseif (($command['params'][0] == 'remove' && $command['params'][1] != '') ||
+ ($command['params'][0] == 'erase' && $command['params'][1] != '')) {
+ global $rasp; // from plugin.rasp.php
+
+ // verify parameter
+ $param = $command['params'][1];
+ if (is_numeric($param) && $param >= 0) {
+ if (empty($admin->tracklist)) {
+ $message = $rasp->messages['LIST_HELP'][0];
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ return;
+ }
+ // find track by given #
+ $tid = ltrim($param, '0');
+ $tid--;
+ if (array_key_exists($tid, $admin->tracklist)) {
+ $name = stripColors($admin->tracklist[$tid]['name']);
+ $filename = $aseco->server->trackdir . $admin->tracklist[$tid]['filename'];
+ $rtn = $aseco->client->query('RemoveChallenge', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] RemoveChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = formatText('{#server}> {#error}Error removing track {#highlite}$i {1} {#error}!',
+ $filename);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}removes track: {#highlite}{3}',
+ $chattitle, $admin->nickname, $name);
+ if ($command['params'][0] == 'erase' && is_file($filename)) {
+ if (unlink($filename)) {
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}erases track: {#highlite}{3}',
+ $chattitle, $admin->nickname, $name);
+ } else {
+ $message = '{#server}> {#error}Delete file {#highlite}$i ' . $filename . '{#error} failed';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}erase track failed: {#highlite}{3}',
+ $chattitle, $admin->nickname, $name);
+ }
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ // log console message
+ $aseco->console('{1} [{2}] ' . $command['params'][0] . 'd track {3}', $logtitle, $login, stripColors($name, false));
+
+ // throw 'tracklist changed' event
+ $aseco->releaseEvent('onTracklistChanged', array('remove', $filename));
+ }
+ } else {
+ $message = $rasp->messages['JUKEBOX_NOTFOUND'][0];
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $rasp->messages['JUKEBOX_HELP'][0];
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Remove current track from the active rotation, optionally erase track file too.
+ * Doesn't update match settings unfortunately - command 'writetracklist' will though.
+ */
+ } elseif ($command['params'][0] == 'removethis' ||
+ $command['params'][0] == 'erasethis') {
+
+ // get current track info and remove it from rotation
+ $name = stripColors($aseco->server->challenge->name);
+ $filename = $aseco->server->trackdir . $aseco->server->challenge->filename;
+ $rtn = $aseco->client->query('RemoveChallenge', $filename);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] RemoveChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $message = formatText('{#server}> {#error}Error removing track {#highlite}$i {1} {#error}!',
+ $filename);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}removes current track: {#highlite}{3}',
+ $chattitle, $admin->nickname, $name);
+ if ($command['params'][0] == 'erasethis' && is_file($filename)) {
+ if (unlink($filename)) {
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}erases current track: {#highlite}{3}',
+ $chattitle, $admin->nickname, $name);
+ } else {
+ $message = '{#server}> {#error}Delete file {#highlite}$i ' . $filename . '{#error} failed';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}erase track failed: {#highlite}{3}',
+ $chattitle, $admin->nickname, $name);
+ }
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ // log console message
+ $aseco->console('{1} [{2}] ' . $command['params'][0] . '-ed track {3}', $logtitle, $login, stripColors($name, false));
+
+ // throw 'tracklist changed' event
+ $aseco->releaseEvent('onTracklistChanged', array('remove', $filename));
+ }
+
+ /**
+ * Adds a player to global mute/ignore list
+ */
+ } elseif (($command['params'][0] == 'mute' || $command['params'][0] == 'ignore')
+ && $command['params'][1] != '') {
+ global $muting_available; // from plugin.muting.php
+
+ if ($aseco->server->getGame() != 'TMF') {
+ // check for muting plugin
+ if ($muting_available) {
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) {
+ // check if not yet in global mute/ignore list
+ if (!in_array($target->login, $aseco->server->mutelist)) {
+ // mute this player
+ $aseco->server->mutelist[] = $target->login;
+
+ // log console message
+ $aseco->console('{1} [{2}] mutes player [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} mutes {#highlite}{3}$z$s{#admin} !',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = '{#server}> {#error}Player {#highlite}$i ' . stripColors($target->nickname) . '{#error} is already in global mute/ignore list!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = '{#server}> {#error}Player muting not available!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else { // TMF
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) {
+ // ignore the player
+ $aseco->client->query('Ignore', $target->login);
+
+ // check if in global mute/ignore list
+ if (!in_array($target->login, $aseco->server->mutelist)) {
+ // add player to list
+ $aseco->server->mutelist[] = $target->login;
+ }
+
+ // log console message
+ $aseco->console('{1} [{2}] ignores player {3}!', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} ignores {#highlite}{3}$z$s{#admin} !',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ }
+ }
+
+ /**
+ * Removes a player from global mute/ignore list
+ */
+ } elseif (($command['params'][0] == 'unmute' || $command['params'][0] == 'unignore')
+ && $command['params'][1] != '') {
+ global $muting_available; // from plugin.muting.php
+
+ if ($aseco->server->getGame() != 'TMF') {
+ // check for muting plugin
+ if ($muting_available) {
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) {
+ // check if already in global mute/ignore list
+ if (($i = array_search($target->login, $aseco->server->mutelist)) !== false) {
+ // unmute this player
+ $aseco->server->mutelist[$i] = '';
+
+ // log console message
+ $aseco->console('{1} [{2}] unmutes player [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} unmutes {#highlite}{3}$z$s{#admin} !',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = '{#server}> {#error}Player {#highlite}$i ' . stripColors($target->nickname) . '{#error} is not in global mute/ignore list!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = '{#server}> {#error}Player muting not available!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else { // TMF
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) {
+ // unignore the player
+ $rtn = $aseco->client->query('UnIgnore', $target->login);
+ if (!$rtn) {
+ $message = formatText('{#server}> {#highlite}{1}{#error} is not an ignored player!',
+ $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // check if in global mute/ignore list
+ if (($i = array_search($target->login, $aseco->server->mutelist)) !== false) {
+ // remove player from list
+ $aseco->server->mutelist[$i] = '';
+ }
+
+ // log console message
+ $aseco->console('{1} [{2}] unignores player {3}', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} un-ignores {#highlite}{3}',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ }
+
+ /**
+ * Displays the global mute/ignore list.
+ */
+ } elseif ($command['params'][0] == 'mutelist' ||
+ $command['params'][0] == 'listmutes' ||
+ $command['params'][0] == 'ignorelist' ||
+ $command['params'][0] == 'listignores') {
+ global $muting_available; // from plugin.muting.php
+
+ $admin->playerlist = array();
+ $admin->msgs = array();
+
+ if ($aseco->server->getGame() == 'TMN') {
+ // check for muting plugin
+ if ($muting_available) {
+ // check for muted players
+ if (empty($aseco->server->mutelist)) {
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No muted/ignored players found!'), $login);
+ return;
+ }
+
+ $head = 'Globally Muted/Ignored Players:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF;
+ $msg = '';
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($aseco->server->mutelist as $pl) {
+ if ($pl != '') {
+ $plarr = array();
+ $plarr['login'] = $pl;
+ $admin->playerlist[] = $plarr;
+
+ $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}'
+ . $aseco->getPlayerNick($pl) . '$z / {#login}' . $pl . LF;
+ $pid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } elseif (count($admin->msgs) > 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No muted/ignored players found!'), $login);
+ }
+
+ } else {
+ $message = '{#server}> {#error}Player muting not available!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ // get new list of all ignored players
+ $newlist = get_ignorelist($aseco);
+
+ $head = 'Globally Muted/Ignored Players:';
+ $msg = array();
+ if ($aseco->settings['clickable_lists'])
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnIgnore)');
+ else
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons128x128_1', 'Padlock', 0.01));
+ foreach ($newlist as $player) {
+ $plarr = array();
+ $plarr['login'] = $player[0];
+ $admin->playerlist[] = $plarr;
+
+ // format nickname & login
+ $ply = '{#black}' . str_ireplace('$w', '', $player[1])
+ . '$z / {#login}' . $player[0];
+ // add clickable button
+ if ($aseco->settings['clickable_lists'] && $pid <= 200)
+ $ply = array($ply, $pid+4400); // action id
+
+ $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply);
+ $pid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ if ($aseco->settings['clickable_lists'])
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnIgnore)');
+ else
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ if (count($admin->msgs) > 1) {
+ display_manialink_multi($admin);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No muted/ignored players found!'), $login);
+ }
+ }
+
+ /**
+ * Cleans the global mute/ignore list.
+ */
+ } elseif ($command['params'][0] == 'cleanmutes' ||
+ $command['params'][0] == 'cleanignores') {
+
+ // clean internal and server list
+ $aseco->server->mutelist = array();
+ if ($aseco->server->getGame() == 'TMF')
+ $aseco->client->query('CleanIgnoreList');
+
+ // log console message
+ $aseco->console('{1} [{2}] cleaned global mute/ignore list!', $logtitle, $login);
+
+ // show chat message
+ $message = '{#server}> {#admin}Cleaned global mute/ignore list!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Adds a new admin.
+ */
+ } elseif ($command['params'][0] == 'addadmin' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) {
+ // check if player not already admin
+ if (!$aseco->isAdminL($target->login)) {
+ // add the new admin
+ $aseco->admin_list['TMLOGIN'][] = $target->login;
+ $aseco->admin_list['IPADDRESS'][] = ($aseco->settings['auto_admin_addip'] ? $target->ip : '');
+ $aseco->writeLists();
+
+ // log console message
+ $aseco->console('{1} [{2}] adds admin [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} adds new {3}$z$s{#admin}: {#highlite}{4}$z$s{#admin} !',
+ $chattitle, $admin->nickname,
+ $aseco->titles['ADMIN'][0], $target->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = formatText('{#server}> {#error}Login {#highlite}$i {1}{#error} is already in Admin List!', $target->login);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+
+ /**
+ * Removes an admin.
+ */
+ } elseif ($command['params'][0] == 'removeadmin' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) {
+ // check if player is indeed admin
+ if ($aseco->isAdminL($target->login)) {
+ $i = array_search($target->login, $aseco->admin_list['TMLOGIN']);
+ $aseco->admin_list['TMLOGIN'][$i] = '';
+ $aseco->admin_list['IPADDRESS'][$i] = '';
+ $aseco->writeLists();
+
+ // log console message
+ $aseco->console('{1} [{2}] removes admin [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} removes {3}$z$s{#admin}: {#highlite}{4}$z$s{#admin} !',
+ $chattitle, $admin->nickname,
+ $aseco->titles['ADMIN'][0], $target->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = formatText('{#server}> {#error}Login {#highlite}$i {1}{#error} is not in Admin List!', $target->login);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+
+ /**
+ * Adds a new operator.
+ */
+ } elseif ($command['params'][0] == 'addop' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) {
+ // check if player not already operator
+ if (!$aseco->isOperatorL($target->login)) {
+ // add the new operator
+ $aseco->operator_list['TMLOGIN'][] = $target->login;
+ $aseco->operator_list['IPADDRESS'][] = ($aseco->settings['auto_admin_addip'] ? $target->ip : '');
+ $aseco->writeLists();
+
+ // log console message
+ $aseco->console('{1} [{2}] adds operator [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} adds new {3}$z$s{#admin}: {#highlite}{4}$z$s{#admin} !',
+ $chattitle, $admin->nickname,
+ $aseco->titles['OPERATOR'][0], $target->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = formatText('{#server}> {#error}Login {#highlite}$i {1}{#error} is already in Operator List!', $target->login);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+
+ /**
+ * Removes an operator.
+ */
+ } elseif ($command['params'][0] == 'removeop' && $command['params'][1] != '') {
+
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) {
+ // check if player is indeed operator
+ if ($aseco->isOperatorL($target->login)) {
+ $i = array_search($target->login, $aseco->operator_list['TMLOGIN']);
+ $aseco->operator_list['TMLOGIN'][$i] = '';
+ $aseco->operator_list['IPADDRESS'][$i] = '';
+ $aseco->writeLists();
+
+ // log console message
+ $aseco->console('{1} [{2}] removes operator [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} removes {3}$z$s{#admin}: {#highlite}{4}$z$s{#admin} !',
+ $chattitle, $admin->nickname,
+ $aseco->titles['OPERATOR'][0], $target->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = formatText('{#server}> {#error}Login {#highlite}$i {1}{#error} is not in Operator List!', $target->login);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+
+ /**
+ * Displays the masteradmins list.
+ */
+ } elseif ($command['params'][0] == 'listmasters') {
+
+ $admin->playerlist = array();
+ $admin->msgs = array();
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Current MasterAdmins:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF;
+ $msg = '';
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($aseco->masteradmin_list['TMLOGIN'] as $player) {
+ // skip any LAN logins
+ if ($player != '' && !isLANLogin($player)) {
+ $plarr = array();
+ $plarr['login'] = $player;
+ $admin->playerlist[] = $plarr;
+
+ $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}'
+ . $aseco->getPlayerNick($player) . '$z / {#login}' . $player . LF;
+ $pid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } elseif (count($admin->msgs) > 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No masteradmin(s) found!'), $login);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Current MasterAdmins:';
+ $msg = array();
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons128x128_1', 'Solo'));
+ foreach ($aseco->masteradmin_list['TMLOGIN'] as $player) {
+ // skip any LAN logins
+ if ($player != '' && !isLANLogin($player)) {
+ $plarr = array();
+ $plarr['login'] = $player;
+ $admin->playerlist[] = $plarr;
+
+ $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.',
+ '{#black}' . $aseco->getPlayerNick($player)
+ . '$z / {#login}' . $player);
+ $pid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ if (count($admin->msgs) > 1) {
+ display_manialink_multi($admin);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No masteradmin(s) found!'), $login);
+ }
+ }
+
+ /**
+ * Displays the admins list.
+ */
+ } elseif ($command['params'][0] == 'listadmins') {
+
+ if (empty($aseco->admin_list['TMLOGIN'])) {
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No admin(s) found!'), $login);
+ return;
+ }
+
+ $admin->playerlist = array();
+ $admin->msgs = array();
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Current Admins:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF;
+ $msg = '';
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($aseco->admin_list['TMLOGIN'] as $player) {
+ if ($player != '') {
+ $plarr = array();
+ $plarr['login'] = $player;
+ $admin->playerlist[] = $plarr;
+
+ $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}'
+ . $aseco->getPlayerNick($player) . '$z / {#login}' . $player . LF;
+ $pid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } elseif (count($admin->msgs) > 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No admin(s) found!'), $login);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Current Admins:';
+ $msg = array();
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons128x128_1', 'Solo'));
+ foreach ($aseco->admin_list['TMLOGIN'] as $player) {
+ if ($player != '') {
+ $plarr = array();
+ $plarr['login'] = $player;
+ $admin->playerlist[] = $plarr;
+
+ $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.',
+ '{#black}' . $aseco->getPlayerNick($player)
+ . '$z / {#login}' . $player);
+ $pid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ if (count($admin->msgs) > 1) {
+ display_manialink_multi($admin);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No admin(s) found!'), $login);
+ }
+ }
+
+ /**
+ * Displays the operators list.
+ */
+ } elseif ($command['params'][0] == 'listops') {
+
+ if (empty($aseco->operator_list['TMLOGIN'])) {
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No operator(s) found!'), $login);
+ return;
+ }
+
+ $admin->playerlist = array();
+ $admin->msgs = array();
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Current Operators:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF;
+ $msg = '';
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($aseco->operator_list['TMLOGIN'] as $player) {
+ if ($player != '') {
+ $plarr = array();
+ $plarr['login'] = $player;
+ $admin->playerlist[] = $plarr;
+
+ $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}'
+ . $aseco->getPlayerNick($player) . '$z / {#login}' . $player . LF;
+ $pid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } elseif (count($admin->msgs) > 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No operator(s) found!'), $login);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Current Operators:';
+ $msg = array();
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ $pid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons128x128_1', 'Solo'));
+ foreach ($aseco->operator_list['TMLOGIN'] as $player) {
+ if ($player != '') {
+ $plarr = array();
+ $plarr['login'] = $player;
+ $admin->playerlist[] = $plarr;
+
+ $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.',
+ '{#black}' . $aseco->getPlayerNick($player)
+ . '$z / {#login}' . $player);
+ $pid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ $msg[] = array('Id', '{#nick}Nick $g/{#login} Login');
+ }
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ if (count($admin->msgs) > 1) {
+ display_manialink_multi($admin);
+ } else { // == 1
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No operator(s) found!'), $login);
+ }
+ }
+
+ /**
+ * Show/change an admin ability
+ */
+ } elseif ($command['params'][0] == 'adminability') {
+
+ // check for ability parameter
+ if ($command['params'][1] != '') {
+ // map to uppercase before checking list
+ $ability = strtoupper($command['params'][1]);
+
+ // check for valid ability
+ if (isset($aseco->adm_abilities[$ability])) {
+ if (isset($command['params'][2]) && $command['params'][2] != '') {
+ // update ability
+ if (strtoupper($command['params'][2]) == 'ON') {
+ $aseco->adm_abilities[$ability][0] = true;
+ $aseco->writeLists();
+
+ // log console message
+ $aseco->console('{1} [{2}] set new Admin ability: {3} ON', $logtitle, $login, strtolower($ability));
+ }
+ elseif (strtoupper($command['params'][2]) == 'OFF') {
+ $aseco->adm_abilities[$ability][0] = false;
+ $aseco->writeLists();
+
+ // log console message
+ $aseco->console('{1} [{2}] set new Admin ability: {3} OFF', $logtitle, $login, strtolower($ability));
+ } // else ignore bogus parameter
+ }
+ // show current/new ability message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#admin}ability {#highlite}{2}{#admin} is: {#highlite}{3}',
+ $aseco->titles['ADMIN'][0], strtolower($ability),
+ ($aseco->adm_abilities[$ability][0] ? 'ON' : 'OFF'));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $message = formatText('{#server}> {#error}No ability {#highlite}$i {1}{#error} known!',
+ $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = '{#server}> {#error}No ability specified - see {#highlite}$i /admin helpall{#error} and {#highlite}$i /admin listabilities{#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Show/change an operator ability
+ */
+ } elseif ($command['params'][0] == 'opability') {
+
+ // check for ability parameter
+ if ($command['params'][1] != '') {
+ // map to uppercase before checking list
+ $ability = strtoupper($command['params'][1]);
+
+ // check for valid ability
+ if (isset($aseco->op_abilities[$ability])) {
+ if (isset($command['params'][2]) && $command['params'][2] != '') {
+ // update ability
+ if (strtoupper($command['params'][2]) == 'ON') {
+ $aseco->op_abilities[$ability][0] = true;
+ $aseco->writeLists();
+
+ // log console message
+ $aseco->console('{1} [{2}] set new Operator ability: {3} ON', $logtitle, $login, strtolower($ability));
+ }
+ elseif (strtoupper($command['params'][2]) == 'OFF') {
+ $aseco->op_abilities[$ability][0] = false;
+ $aseco->writeLists();
+
+ // log console message
+ $aseco->console('{1} [{2}] set new Operator ability: {3} OFF', $logtitle, $login, strtolower($ability));
+ } // else ignore bogus parameter
+ }
+ // show current/new ability message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#admin}ability {#highlite}{2}{#admin} is: {#highlite}{3}',
+ $aseco->titles['OPERATOR'][0], strtolower($ability),
+ ($aseco->op_abilities[$ability][0] ? 'ON' : 'OFF'));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $message = formatText('{#server}> {#error}No ability {#highlite}$i {1}{#error} known!',
+ $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = '{#server}> {#error}No ability specified - see {#highlite}$i /admin helpall{#error} and {#highlite}$i /admin listabilities{#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Displays Admin and Operator abilities
+ */
+ } elseif ($command['params'][0] == 'listabilities') {
+
+ $master = false;
+ if ($aseco->isMasterAdminL($login)) {
+ if ($command['params'][1] == '') {
+ $master = true;
+ $abilities = $aseco->adm_abilities;
+ $title = 'MasterAdmin';
+ } else {
+ if (stripos('admin', $command['params'][1]) === 0) {
+ $abilities = $aseco->adm_abilities;
+ $title = 'Admin';
+ }
+ elseif (stripos('operator', $command['params'][1]) === 0) {
+ $abilities = $aseco->op_abilities;
+ $title = 'Operator';
+ }
+ // all three above fall through to listing below
+ else {
+ $message = formatText('{#server}> {#highlite}{1}{#error} is not a valid administrator tier!',
+ $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ return;
+ }
+ }
+ }
+ elseif ($aseco->isAdminL($login)) {
+ $abilities = $aseco->adm_abilities;
+ $title = 'Admin';
+ }
+ else { // isOperator
+ $abilities = $aseco->op_abilities;
+ $title = 'Operator';
+ }
+
+ if ($aseco->server->getGame() == 'TMN') {
+ // compile current ability listing
+ $help = 'Current ' . $title . ' abilities:' . LF . LF;
+ $chat = false;
+ foreach ($abilities as $ability => $value) {
+ switch (strtolower($ability)) {
+ case 'chat_pma':
+ if ($value[0] || $master) {
+ $help .= 'chat_pma : {#black}/pma$g sends a PM to player & admins' . LF;
+ $chat = true;
+ }
+ break;
+ case 'chat_bestworst':
+ if ($value[0] || $master) {
+ $help .= 'chat_bestworst : {#black}/best$g & {#black}/worst$g accept login/Player_ID' . LF;
+ $chat = true;
+ }
+ break;
+ case 'chat_statsip':
+ if ($value[0] || $master) {
+ $help .= 'chat_statsip : {#black}/stats$g includes IP address' . LF;
+ $chat = true;
+ }
+ break;
+ case 'chat_summary':
+ if ($value[0] || $master) {
+ $help .= 'chat_summary : {#black}/summary$g accepts login/Player_ID' . LF;
+ $chat = true;
+ }
+ break;
+ case 'chat_jb_multi':
+ if ($value[0] || $master) {
+ $help .= 'chat_jb_multi : {#black}/jukebox$g adds more than one track' . LF;
+ $chat = true;
+ }
+ break;
+ case 'chat_jb_recent':
+ if ($value[0] || $master) {
+ $help .= 'chat_jb_recent : {#black}/jukebox$g adds recently played track' . LF;
+ $chat = true;
+ }
+ break;
+ case 'chat_add_tref':
+ if ($value[0] || $master) {
+ $help .= 'chat_add_tref : {#black}/add trackref$g writes TMX trackref file' . LF;
+ $chat = true;
+ }
+ break;
+ case 'chat_match':
+ if ($value[0] || $master) {
+ $help .= 'chat_match : {#black}/match$g allows match control' . LF;
+ $chat = true;
+ }
+ break;
+ case 'chat_tc_listen':
+ if ($value[0] || $master) {
+ $help .= 'chat_tc_listen : {#black}/tc$g will copy team chat to admins' . LF;
+ $chat = true;
+ }
+ break;
+ case 'chat_jfreu':
+ if ($value[0] || $master) {
+ $help .= 'chat_jfreu : use all {#black}/jfreu$g commands' . LF;
+ $chat = true;
+ }
+ break;
+ case 'noidlekick_play':
+ if ($value[0] || $master) {
+ $help .= 'noidlekick_play : no idlekick when {#black}player$g' . LF;
+ $chat = true;
+ }
+ break;
+ case 'noidlekick_spec':
+ if ($value[0] || $master) {
+ $help .= 'noidlekick_spec: no idlekick when {#black}spectator$g' . LF;
+ $chat = true;
+ }
+ break;
+ }
+ }
+
+ if ($chat) $help .= LF;
+ $help .= 'See {#black}/admin helpall$g for available /admin commands';
+
+ // display popup message
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $aseco->formatColors($help), 'OK', '', 0);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ // compile current ability listing
+ $header = 'Current ' . $title . ' abilities:';
+ $help = array();
+ $chat = false;
+ foreach ($abilities as $ability => $value) {
+ switch (strtolower($ability)) {
+ case 'chat_pma':
+ if ($value[0] || $master) {
+ $help[] = array('chat_pma', '{#black}/pma$g sends a PM to player & admins');
+ $chat = true;
+ }
+ break;
+ case 'chat_bestworst':
+ if ($value[0] || $master) {
+ $help[] = array('chat_bestworst', '{#black}/best$g & {#black}/worst$g accept login/Player_ID');
+ $chat = true;
+ }
+ break;
+ case 'chat_statsip':
+ if ($value[0] || $master) {
+ $help[] = array('chat_statsip', '{#black}/stats$g includes IP address');
+ $chat = true;
+ }
+ break;
+ case 'chat_summary':
+ if ($value[0] || $master) {
+ $help[] = array('chat_summary', '{#black}/summary$g accepts login/Player_ID');
+ $chat = true;
+ }
+ break;
+ case 'chat_jb_multi':
+ if ($value[0] || $master) {
+ $help[] = array('chat_jb_multi', '{#black}/jukebox$g adds more than one track');
+ $chat = true;
+ }
+ break;
+ case 'chat_jb_recent':
+ if ($value[0] || $master) {
+ $help[] = array('chat_jb_recent', '{#black}/jukebox$g adds recently played track');
+ $chat = true;
+ }
+ break;
+ case 'chat_add_tref':
+ if ($value[0] || $master) {
+ $help[] = array('chat_add_tref', '{#black}/add trackref$g writes TMX trackref file');
+ $chat = true;
+ }
+ break;
+ case 'chat_match':
+ if ($value[0] || $master) {
+ $help[] = array('chat_match', '{#black}/match$g allows match control');
+ $chat = true;
+ }
+ break;
+ case 'chat_tc_listen':
+ if ($value[0] || $master) {
+ $help[] = array('chat_tc_listen', '{#black}/tc$g will copy team chat to admins');
+ $chat = true;
+ }
+ break;
+ case 'chat_jfreu':
+ if ($value[0] || $master) {
+ $help[] = array('chat_jfreu', 'use all {#black}/jfreu$g commands');
+ $chat = true;
+ }
+ break;
+ case 'chat_musicadmin':
+ if ($value[0] || $master) {
+ $help[] = array('chat_musicadmin', 'use {#black}/music$g admin commands');
+ $chat = true;
+ }
+ break;
+ case 'noidlekick_play':
+ if ($value[0] || $master) {
+ $help[] = array('noidlekick_play', 'no idlekick when {#black}player$g');
+ $chat = true;
+ }
+ break;
+ case 'noidlekick_spec':
+ if ($value[0] || $master) {
+ $help[] = array('noidlekick_spec', 'no idlekick when {#black}spectator$g');
+ $chat = true;
+ }
+ break;
+ case 'server_coppers':
+ if ($value[0] || $master) {
+ $help[] = array('server_coppers', 'view coppers amount in {#black}/server$g');
+ $chat = true;
+ }
+ break;
+ }
+ }
+
+ if ($chat) $help[] = array();
+ $help[] = array('See {#black}/admin helpall$g for available /admin commands');
+
+ // display ManiaLink message
+ display_manialink($login, $header, array('Icons128x128_1', 'ProfileAdvanced', 0.02), $help, array(1.0, 0.3, 0.7), 'OK');
+ }
+
+ /**
+ * Saves the admins/operators/abilities list to adminops.xml (default).
+ */
+ } elseif ($command['params'][0] == 'writeabilities') {
+
+ // write admins/operators file
+ $filename = $aseco->settings['adminops_file'];
+ if (!$aseco->writeLists()) {
+ $message = '{#server}> {#error}Error writing {#highlite}$i ' . $filename . ' {#error}!';
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] wrote ' . $filename . '!', $logtitle, $login);
+
+ $message = '{#server}> {#highlite}' . $filename . ' {#admin}written';
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Loads the admins/operators/abilities list from adminops.xml (default).
+ */
+ } elseif ($command['params'][0] == 'readabilities') {
+
+ // read admins/operators file
+ $filename = $aseco->settings['adminops_file'];
+ if (!$aseco->readLists()) {
+ $message = '{#server}> {#highlite}' . $filename . ' {#error}not found, or error reading!';
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] read ' . $filename . '!', $logtitle, $login);
+
+ $message = '{#server}> {#highlite}' . $filename . ' {#admin}read';
+ }
+ // show chat message
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Display message in pop-up to all players
+ */
+ } elseif ($command['params'][0] == 'wall' ||
+ $command['params'][0] == 'mta') {
+
+ // check for non-empty message
+ if ($arglist[1] != '') {
+ if ($aseco->server->getGame() == 'TMN') {
+ $message = $aseco->formatColors('{#black}') . $chattitle . ' ' . $admin->nickname . '$z :' . LF;
+ // insure window doesn't become too wide
+ $message .= wordwrap($aseco->formatColors('{#welcome}') . $arglist[1], 30, LF);
+ // display popup message to all players
+ $aseco->client->query('SendDisplayServerMessage', $message, 'OK', '', 0);
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $header = '{#black}' . $chattitle . ' ' . $admin->nickname . '$z :';
+ // insure window doesn't become too wide
+ $message = wordwrap('{#welcome}' . $arglist[1], 40, LF . '{#welcome}');
+ $message = explode(LF, $aseco->formatColors($message));
+ foreach ($message as &$line)
+ $line = array($line);
+
+ // display ManiaLink message to all players
+ foreach ($aseco->server->players->player_list as $target)
+ display_manialink($target->login, $header, array('Icons64x64_1', 'Inbox'), $message, array(0.8), 'OK');
+ }
+
+ // log console message
+ $aseco->console('{1} [{2}] sent wall message: {3}', $logtitle, $login, $arglist[1]);
+ } else {
+ $message = '{#server}> {#error}No message!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Delete records/rs_times database entries for specific record & sync.
+ */
+ } elseif ($command['params'][0] == 'delrec' && $command['params'][1] != '') {
+ global $rasp; // from plugin.rasp.php
+
+ // verify parameter
+ $param = $command['params'][1];
+ if (is_numeric($param) && $param > 0 && $param <= $aseco->server->records->count()) {
+ $param = ltrim($param, '0');
+ $param--;
+ // get record info
+ $record = $aseco->server->records->getRecord($param);
+ $pid = $aseco->getPlayerId($record->player->login);
+
+ // remove times before record
+ if (method_exists($rasp, 'deleteTime'))
+ $rasp->deleteTime($aseco->server->challenge->id, $pid);
+ // remove record and fill up if necessary
+ ldb_removeRecord($aseco, $aseco->server->challenge->id, $pid, $param);
+ $param++;
+
+ // log console message
+ $aseco->console('{1} [{2}] removed record {3} by {4} !', $logtitle, $login, $param, $record->player->login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}removes record {#highlite}{3}{#admin} by {#highlite}{4}',
+ $chattitle, $admin->nickname, $param, stripColors($record->player->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = '{#server}> {#error}No such record {#highlite}$i ' . $param . ' {#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Prune records/rs_times database entries for specific track.
+ */
+ } elseif ($command['params'][0] == 'prunerecs' && $command['params'][1] != '') {
+ global $rasp; // from plugin.rasp.php
+
+ // verify parameter
+ $param = $command['params'][1];
+ if (is_numeric($param) && $param >= 0) {
+ if (empty($admin->tracklist)) {
+ $message = $rasp->messages['LIST_HELP'][0];
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ return;
+ }
+ // find track by given #
+ $jid = ltrim($param, '0');
+ $jid--;
+ if (array_key_exists($jid, $admin->tracklist)) {
+ $uid = $admin->tracklist[$jid]['uid'];
+ $name = stripColors($admin->tracklist[$jid]['name']);
+ $track = $aseco->getChallengeId($uid);
+
+ if ($track > 0) {
+ // delete the records and rs_times
+ $query = 'DELETE FROM records WHERE ChallengeID=' . $track;
+ mysql_query($query);
+ $query = 'DELETE FROM rs_times WHERE challengeID=' . $track;
+ mysql_query($query);
+
+ // log console message
+ $aseco->console('{1} [{2}] pruned records/times for track {3} !', $logtitle, $login, stripColors($name, false));
+
+ // show chat message
+ $message = '{#server}> {#admin}Deleted all records & times for track: {#highlite}' . $name;
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $message = '{#server}> {#error}Can\'t find ChallengeId for track: {#highlite}$i ' . $name . ' / ' . $uid;
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $rasp->messages['JUKEBOX_NOTFOUND'][0];
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $rasp->messages['JUKEBOX_HELP'][0];
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Sets custom rounds points.
+ */
+ } elseif ($command['params'][0] == 'rpoints' && $command['params'][1] != '') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ if (function_exists('admin_rpoints')) {
+ admin_rpoints($aseco, $admin, $logtitle, $chattitle, $arglist[1]); // from plugin.rpoints.php
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Custom Rounds points unavailable - include plugins.rpoints.php in plugins.xml';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Start or stop match tracking.
+ */
+ } elseif ($command['params'][0] == 'match') {
+ global $MatchSettings; // from plugin.matchsave.php
+
+ if (function_exists('match_loadsettings')) {
+ if ($command['params'][1] == 'begin') {
+ match_loadsettings(); // from plugin.matchsave.php
+ $MatchSettings['enable'] = true;
+
+ // log console message
+ $aseco->console('{1} [{2}] started match!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} has started the match',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ }
+ elseif ($command['params'][1] == 'end') {
+ $MatchSettings['enable'] = false;
+
+ // log console message
+ $aseco->console('{1} [{2}] ended match!', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} has ended the match',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ }
+ else {
+ // show chat message
+ $message = '{#server}> {#admin}Match is currently ' . ($MatchSettings['enable'] ? 'Running' : 'Stopped');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Match tracking unavailable - include plugins.matchsave.php in plugins.xml';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Shows or sets AllowChallengeDownload status.
+ */
+ } elseif ($command['params'][0] == 'acdl') {
+
+ $param = strtolower($command['params'][1]);
+ if ($param == 'on' || $param == 'off') {
+ $enabled = ($param == 'on');
+ $aseco->client->query('AllowChallengeDownload', $enabled);
+
+ // log console message
+ $aseco->console('{1} [{2}] set AllowChallengeDownload {3} !', $logtitle, $login, ($enabled ? 'ON' : 'OFF'));
+
+ // show chat message
+ $message = '{#server}> {#admin}AllowChallengeDownload set to ' . ($enabled ? 'Enabled' : 'Disabled');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $aseco->client->query('IsChallengeDownloadAllowed');
+ $enabled = $aseco->client->getResponse();
+
+ // show chat message
+ $message = '{#server}> {#admin}AllowChallengeDownload is currently ' . ($enabled ? 'Enabled' : 'Disabled');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Shows or sets Auto TimeLimit status.
+ */
+ } elseif ($command['params'][0] == 'autotime') {
+ global $atl_active; // from plugin.autotime.php
+
+ // check for autotime plugin
+ if (isset($atl_active)) {
+ $param = strtolower($command['params'][1]);
+ if ($param == 'on' || $param == 'off') {
+ $atl_active = ($param == 'on');
+
+ // log console message
+ $aseco->console('{1} [{2}] set Auto TimeLimit {3} !', $logtitle, $login, ($atl_active ? 'ON' : 'OFF'));
+
+ // show chat message
+ $message = '{#server}> {#admin}Auto TimeLimit set to ' . ($atl_active ? 'Enabled' : 'Disabled');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Auto TimeLimit is currently ' . ($atl_active ? 'Enabled' : 'Disabled');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Auto TimeLimit unavailable - include plugins.autotime.php in plugins.xml';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Shows or sets DisableRespawn status (TMF).
+ */
+ } elseif ($command['params'][0] == 'disablerespawn') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ $param = strtolower($command['params'][1]);
+ if ($param == 'on' || $param == 'off') {
+ $enabled = ($param == 'on');
+ $aseco->client->query('SetDisableRespawn', $enabled);
+
+ // log console message
+ $aseco->console('{1} [{2}] set DisableRespawn {3} !', $logtitle, $login, ($enabled ? 'ON' : 'OFF'));
+
+ // show chat message
+ $message = '{#server}>> {#admin}DisableRespawn set to ' . ($enabled ? 'Enabled' : 'Disabled') . ' on the next track';
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $aseco->client->query('GetDisableRespawn');
+ $enabled = $aseco->client->getResponse();
+
+ // show chat message
+ $message = '{#server}> {#admin}DisableRespawn is currently ' . ($enabled['CurrentValue'] ? 'Enabled' : 'Disabled');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Shows or sets ForceShowAllOpponents status (TMF).
+ */
+ } elseif ($command['params'][0] == 'forceshowopp') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ $param = strtolower($command['params'][1]);
+ if ($param == 'all' || $param == 'off') {
+ $enabled = ($param == 'all' ? 1 : 0);
+ $aseco->client->query('SetForceShowAllOpponents', $enabled);
+
+ // log console message
+ $aseco->console('{1} [{2}] set ForceShowAllOpponents {3} !', $logtitle, $login, ($enabled ? 'ALL' : 'OFF'));
+
+ // show chat message
+ $message = '{#server}>> {#admin}ForceShowAllOpponents set to {#highlite}' . ($enabled ? 'Enabled' : 'Disabled') . '{#admin} on the next track';
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } elseif (is_numeric($param) && $param > 1) {
+ $enabled = intval($param);
+ $aseco->client->query('SetForceShowAllOpponents', $enabled);
+
+ // log console message
+ $aseco->console('{1} [{2}] set ForceShowAllOpponents to {3} !', $logtitle, $login, $enabled);
+
+ // show chat message
+ $message = '{#server}>> {#admin}ForceShowAllOpponents set to {#highlite}' . $enabled . '{#admin} on the next track';
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $aseco->client->query('GetForceShowAllOpponents');
+ $enabled = $aseco->client->getResponse();
+ $enabled = $enabled['CurrentValue'];
+
+ // show chat message
+ $message = '{#server}> {#admin}ForceShowAllOpponents is set to: {#highlite}' . ($enabled != 0 ? ($enabled > 1 ? $enabled : 'All') : 'Off');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Shows or sets Automatic ScorePanel status (TMF).
+ */
+ } elseif ($command['params'][0] == 'scorepanel') {
+ global $auto_scorepanel;
+
+ if ($aseco->server->getGame() == 'TMF') {
+ $param = strtolower($command['params'][1]);
+ if ($param == 'on' || $param == 'off') {
+ $auto_scorepanel = ($param == 'on');
+ scorepanel_off($aseco, null);
+
+ // log console message
+ $aseco->console('{1} [{2}] set Automatic ScorePanel {3} !', $logtitle, $login, ($auto_scorepanel ? 'ON' : 'OFF'));
+
+ // show chat message
+ $message = '{#server}>> {#admin}Automatic ScorePanel set to ' . ($auto_scorepanel ? 'Enabled' : 'Disabled');
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Automatic ScorePanel is currently ' . ($auto_scorepanel ? 'Enabled' : 'Disabled');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Shows or sets Rounds Finishpanel status (TMF).
+ */
+ } elseif ($command['params'][0] == 'roundsfinish') {
+ global $rounds_finishpanel;
+
+ if ($aseco->server->getGame() == 'TMF') {
+ $param = strtolower($command['params'][1]);
+ if ($param == 'on' || $param == 'off') {
+ $rounds_finishpanel = ($param == 'on');
+
+ // log console message
+ $aseco->console('{1} [{2}] set Rounds Finishpanel {3} !', $logtitle, $login, ($rounds_finishpanel ? 'ON' : 'OFF'));
+
+ // show chat message
+ $message = '{#server}>> {#admin}Rounds Finishpanel set to ' . ($rounds_finishpanel ? 'Enabled' : 'Disabled');
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Rounds Finishpanel is currently ' . ($rounds_finishpanel ? 'Enabled' : 'Disabled');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Forces a player into Blue or Red team (TMF).
+ */
+ } elseif ($command['params'][0] == 'forceteam' && $command['params'][1] != '') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ // check for Team mode
+ if ($aseco->server->gameinfo->mode == Gameinfo::TEAM) {
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) {
+ // get player's team
+ $aseco->client->query('GetPlayerInfo', $target->login);
+ $info = $aseco->client->getResponse();
+ // check for new team
+ if (isset($command['params'][2]) && $command['params'][2] != '') {
+ $team = strtolower($command['params'][2]);
+
+ if (strpos('blue', $team) === 0) {
+ if ($info['TeamId'] != 0) {
+ // set player to Blue team
+ $aseco->client->query('ForcePlayerTeam', $target->login, 0);
+
+ // log console message
+ $aseco->console('{1} [{2}] forces {3} into Blue team!', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} forces {#highlite}{3}$z$s{#admin} into $00fBlue{#admin} team!',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = '{#server}> {#admin}Player {#highlite}' .
+ stripColors($target->nickname) .
+ '{#admin} is already in $00fBlue{#admin} team';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ } elseif (strpos('red', $team) === 0) {
+ if ($info['TeamId'] != 1) {
+ // set player to Red team
+ $aseco->client->query('ForcePlayerTeam', $target->login, 1);
+
+ // log console message
+ $aseco->console('{1} [{2}] forces {3} into Red team!', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} forces {#highlite}{3}$z$s{#admin} into $f00Red{#admin} team!',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $message = '{#server}> {#admin}Player {#highlite}' .
+ stripColors($target->nickname) .
+ '{#admin} is already in $f00Red{#admin} team';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ } else {
+ $message = '{#server}> {#highlite}' . $team . '$z$s{#error} is not a valid team!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ // show current team
+ $message = '{#server}> {#admin}Player {#highlite}' .
+ stripColors($target->nickname) . '{#admin} is in ' .
+ ($info['TeamId'] == 0 ? '$00fBlue' : '$f00Red') .
+ '{#admin} team';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = '{#server}> {#error}Command only available in {#highlite}$i Team {#error}mode!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Forces player into free camera spectator (TMF).
+ */
+ } elseif ($command['params'][0] == 'forcespec' && $command['params'][1] != '') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) {
+ if (!$aseco->isSpectator($target)) {
+ // force player into free spectator
+ $rtn = $aseco->client->query('ForceSpectator', $target->login, 1);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] ForceSpectator - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ } else {
+ // allow spectator to switch back to player
+ $rtn = $aseco->client->query('ForceSpectator', $target->login, 0);
+ // force free camera mode on spectator
+ $aseco->client->addCall('ForceSpectatorTarget', array($target->login, '', 2));
+ // free up player slot
+ $aseco->client->addCall('SpectatorReleasePlayerSlot', array($target->login));
+ // log console message
+ $aseco->console('{1} [{2}] forces player {3} into spectator!', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} forces player {#highlite}{3}$z$s{#admin} into spectator!',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ }
+ } else {
+ $message = formatText('{#server}> {#highlite}{1} {#error}is already a spectator!',
+ stripColors($target->nickname));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Forces a spectator into free camera mode (TMF).
+ */
+ } elseif ($command['params'][0] == 'specfree' && $command['params'][1] != '') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ // get player information
+ if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) {
+ if ($aseco->isSpectator($target)) {
+ // force free camera mode on spectator
+ $rtn = $aseco->client->query('ForceSpectatorTarget', $target->login, '', 2);
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] ForceSpectatorTarget - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ } else {
+ // log console message
+ $aseco->console('{1} [{2}] forces spectator free mode on {3}!', $logtitle, $login, stripColors($target->nickname, false));
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} forces spectator free mode on {#highlite}{3}$z$s{#admin} !',
+ $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname));
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ }
+ } else {
+ $message = formatText('{#server}> {#highlite}{1} {#error}is not a spectator!',
+ stripColors($target->nickname));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Selects default window style (TMF).
+ */
+ } elseif ($command['params'][0] == 'panel') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ if (function_exists('admin_panel')) {
+ $command['params'] = $command['params'][1];
+ admin_panel($aseco, $command); // from plugin.panels.php
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Admin panel unavailable - include plugins.panels.php in plugins.xml';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Selects default window style (TMF).
+ */
+ } elseif ($command['params'][0] == 'style' && $command['params'][1] != '') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ if (strtolower($command['params'][1]) == 'off') {
+ $aseco->style = array();
+ $aseco->settings['window_style'] = 'Off';
+
+ // log console message
+ $aseco->console('{1} [{2}] reset default window style', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} reset default window style',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ $style_file = 'styles/' . $command['params'][1] . '.xml';
+ // load default style
+ if (($style = $aseco->xml_parser->parseXml($style_file)) && isset($style['STYLES'])) {
+ $aseco->style = $style['STYLES'];
+
+ // log console message
+ $aseco->console('{1} [{2}] selects default window style [{3}]', $logtitle, $login, $command['params'][1]);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} selects default window style {#highlite}{3}',
+ $chattitle, $admin->nickname, $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ // Could not read/parse XML file
+ $message = '{#server}> {#error}No valid style file, use {#highlite}$i /style list {#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Selects default admin panel (TMF).
+ */
+ } elseif ($command['params'][0] == 'admpanel' && $command['params'][1] != '') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ if (strtolower($command['params'][1]) == 'off') {
+ $aseco->panels['admin'] = '';
+ $aseco->settings['admin_panel'] = 'Off';
+
+ // log console message
+ $aseco->console('{1} [{2}] reset default admin panel', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} reset default admin panel',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // added file prefix
+ $panel = $command['params'][1];
+ if (strtolower(substr($command['params'][1], 0, 5)) != 'admin')
+ $panel = 'Admin' . $panel;
+ $panel_file = 'panels/' . $panel . '.xml';
+ // load default panel
+ if ($panel = @file_get_contents($panel_file)) {
+ $aseco->panels['admin'] = $panel;
+
+ // log console message
+ $aseco->console('{1} [{2}] selects default admin panel [{3}]', $logtitle, $login, $command['params'][1]);
+
+ // show chat message
+ $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} selects default admin panel {#highlite}{3}',
+ $chattitle, $admin->nickname, $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ // Could not read XML file
+ $message = '{#server}> {#error}No valid admin panel file, use {#highlite}$i /admin panel list {#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Selects default donate panel (TMUF).
+ */
+ } elseif ($command['params'][0] == 'donpanel' && $command['params'][1] != '') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ // check for TMUF server
+ if ($aseco->server->rights) {
+ if (strtolower($command['params'][1]) == 'off') {
+ $aseco->panels['donate'] = '';
+ $aseco->settings['donate_panel'] = 'Off';
+
+ // log console message
+ $aseco->console('{1} [{2}] reset default donate panel', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} reset default donate panel',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ // added file prefix
+ $panel = $command['params'][1];
+ if (strtolower(substr($command['params'][1], 0, 6)) != 'donate')
+ $panel = 'Donate' . $panel;
+ $panel_file = 'panels/' . $panel . '.xml';
+ // load default panel
+ if ($panel = @file_get_contents($panel_file)) {
+ $aseco->panels['donate'] = $panel;
+
+ // log console message
+ $aseco->console('{1} [{2}] selects default donate panel [{3}]', $logtitle, $login, $command['params'][1]);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} selects default donate panel {#highlite}{3}',
+ $chattitle, $admin->nickname, $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ // Could not read XML file
+ $message = '{#server}> {#error}No valid donate panel file, use {#highlite}$i /donpanel list {#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = formatText($aseco->getChatMessage('UNITED_ONLY'), 'server');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Selects default records panel (TMF).
+ */
+ } elseif ($command['params'][0] == 'recpanel' && $command['params'][1] != '') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ if (strtolower($command['params'][1]) == 'off') {
+ $aseco->panels['records'] = '';
+ $aseco->settings['records_panel'] = 'Off';
+
+ // log console message
+ $aseco->console('{1} [{2}] reset default records panel', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} reset default records panel',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ // added file prefix
+ $panel = $command['params'][1];
+ if (strtolower(substr($command['params'][1], 0, 7)) != 'records')
+ $panel = 'Records' . $panel;
+ $panel_file = 'panels/' . $panel . '.xml';
+ // load default panel
+ if ($panel = @file_get_contents($panel_file)) {
+ $aseco->panels['records'] = $panel;
+
+ // log console message
+ $aseco->console('{1} [{2}] selects default records panel [{3}]', $logtitle, $login, $command['params'][1]);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} selects default records panel {#highlite}{3}',
+ $chattitle, $admin->nickname, $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ // Could not read XML file
+ $message = '{#server}> {#error}No valid records panel file, use {#highlite}$i /recpanel list {#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Selects default vote panel (TMF).
+ */
+ } elseif ($command['params'][0] == 'votepanel' && $command['params'][1] != '') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ if (strtolower($command['params'][1]) == 'off') {
+ $aseco->panels['vote'] = '';
+ $aseco->settings['vote_panel'] = 'Off';
+
+ // log console message
+ $aseco->console('{1} [{2}] reset default vote panel', $logtitle, $login);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} reset default vote panel',
+ $chattitle, $admin->nickname);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ // added file prefix
+ $panel = $command['params'][1];
+ if (strtolower(substr($command['params'][1], 0, 4)) != 'vote')
+ $panel = 'Vote' . $panel;
+ $panel_file = 'panels/' . $panel . '.xml';
+ // load default panel
+ if ($panel = @file_get_contents($panel_file)) {
+ $aseco->panels['vote'] = $panel;
+
+ // log console message
+ $aseco->console('{1} [{2}] selects default vote panel [{3}]', $logtitle, $login, $command['params'][1]);
+
+ // show chat message
+ $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} selects default vote panel {#highlite}{3}',
+ $chattitle, $admin->nickname, $command['params'][1]);
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+ } else {
+ // Could not read XML file
+ $message = '{#server}> {#error}No valid vote panel file, use {#highlite}$i /votepanel list {#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Shows server's coppers amount (TMUF).
+ */
+ } elseif ($command['params'][0] == 'coppers') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ // check for TMUF server
+ if ($aseco->server->rights) {
+ // get server coppers
+ $aseco->client->query('GetServerCoppers');
+ $coppers = $aseco->client->getResponse();
+
+ // show chat message
+ $message = formatText($aseco->getChatMessage('COPPERS'),
+ $aseco->server->name, $coppers);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $message = formatText($aseco->getChatMessage('UNITED_ONLY'), 'server');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Pays server coppers to login (TMUF).
+ */
+ } elseif ($command['params'][0] == 'pay') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ // check for TMUF server
+ if ($aseco->server->rights) {
+ if (function_exists('admin_payment')) {
+ if (!isset($command['params'][2])) $command['params'][2] = '';
+ admin_payment($aseco, $login, $command['params'][1],
+ $command['params'][2]); // from plugin.donate.php
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Server payment unavailable - include plugins.donate.php in plugins.xml';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = formatText($aseco->getChatMessage('UNITED_ONLY'), 'server');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Displays relays list or shows relay master (TMF).
+ */
+ } elseif ($command['params'][0] == 'relays') {
+
+ if ($aseco->server->getGame() == 'TMF') {
+ if ($aseco->server->isrelay) {
+ // show chat message
+ $message = formatText($aseco->getChatMessage('RELAYMASTER'),
+ $aseco->server->relaymaster['Login'], $aseco->server->relaymaster['NickName']);
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ if (empty($aseco->server->relayslist)) {
+ // show chat message
+ $message = formatText($aseco->getChatMessage('NO_RELAYS'));
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $header = 'Relay servers:';
+ $relays = array();
+ $relays[] = array('{#login}Login', '{#nick}Nick');
+ foreach ($aseco->server->relayslist as $relay)
+ $relays[] = array($relay['Login'], $relay['NickName']);
+
+ // display ManiaLink message
+ display_manialink($login, $header, array('BgRaceScore2', 'Spectator'), $relays, array(1.0, 0.35, 0.65), 'OK');
+ }
+ }
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Shows server's detailed settings (TMF).
+ */
+ } elseif ($command['params'][0] == 'server') {
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $version = $aseco->client->addCall('GetVersion', array());
+ $network = $aseco->client->addCall('GetNetworkStats', array());
+ $options = $aseco->client->addCall('GetServerOptions', array(1));
+ $gameinfo = $aseco->client->addCall('GetCurrentGameInfo', array(1));
+ if (!$aseco->client->multiquery()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetServer (multi) - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ return;
+ } else {
+ $response = $aseco->client->getResponse();
+ $version = $response[$version][0];
+ $network = $response[$network][0];
+ $options = $response[$options][0];
+ $gameinfo = $response[$gameinfo][0];
+ }
+
+ // compile settings overview
+ $admin->msgs = array();
+ $admin->msgs[0] = 1;
+ $head = 'System info for: ' . $options['Name'] . '$z' . LF . LF;
+
+ $stats = $head . '{#black}GetVersion:' . LF;
+ foreach ($version as $key => $val) {
+ $stats .= '$g' . str_pad($key, 30) . '{#black}' . $val . LF;
+ }
+
+ $stats .= '{#black}GetNetworkStats:' . LF;
+ foreach ($network as $key => $val) {
+ if ($key != 'PlayerNetInfos')
+ $stats .= '$g' . str_pad($key, 30) . '{#black}' . $val . LF;
+ }
+
+ $admin->msgs[] = $aseco->formatColors($stats);
+
+ $stats = $head . '{#black}GetServerOptions:' . LF;
+ foreach ($options as $key => $val) {
+ // show only Current values, not Next ones
+ if ($key != 'Name' && $key != 'Comment' && substr($key, 0, 4) != 'Next')
+ if (is_bool($val))
+ $stats .= '$g' . str_pad($key, 30) . '{#black}' . bool2text($val) . LF;
+ else
+ $stats .= '$g' . str_pad($key, 30) . '{#black}' . $val . LF;
+ }
+
+ $admin->msgs[] = $aseco->formatColors($stats);
+
+ $stats = $head . '{#black}GetCurrentGameInfo:' . LF;
+ foreach ($gameinfo as $key => $val) {
+ if (is_bool($val))
+ $stats .= '$g' . str_pad($key, 30) . '{#black}' . bool2text($val) . LF;
+ else
+ if ($key == 'GameMode')
+ $stats .= '$g' . str_pad($key, 30) . '{#black}' . $val . '$g (' . $aseco->server->gameinfo->getMode() . ')' . LF;
+ else
+ $stats .= '$g' . str_pad($key, 30) . '{#black}' . $val . LF;
+ }
+
+ $admin->msgs[] = $aseco->formatColors($stats);
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ // get all server settings in one go
+ $version = $aseco->client->addCall('GetVersion', array());
+ $info = $aseco->client->addCall('GetSystemInfo', array());
+ $coppers = $aseco->client->addCall('GetServerCoppers', array());
+ $ladderlim = $aseco->client->addCall('GetLadderServerLimits', array());
+ $options = $aseco->client->addCall('GetServerOptions', array(1));
+ $gameinfo = $aseco->client->addCall('GetCurrentGameInfo', array(1));
+ $network = $aseco->client->addCall('GetNetworkStats', array());
+ $callvotes = $aseco->client->addCall('GetCallVoteRatios', array());
+ if (!$aseco->client->multiquery()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetServer (multi) - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ return;
+ } else {
+ $response = $aseco->client->getResponse();
+ $version = $response[$version][0];
+ $info = $response[$info][0];
+ $coppers = $response[$coppers][0];
+ $ladderlim = $response[$ladderlim][0];
+ $options = $response[$options][0];
+ $gameinfo = $response[$gameinfo][0];
+ $network = $response[$network][0];
+ $callvotes = $response[$callvotes][0];
+ }
+
+ // compile settings overview
+ $head = 'System info for: ' . $options['Name'];
+ $admin->msgs = array();
+ $admin->msgs[0] = array(1, $head, array(1.1, 0.6, 0.5), array('Icons64x64_1', 'DisplaySettings', 0.01));
+ $stats = array();
+
+ $stats[] = array('{#black}GetVersion:', '');
+ foreach ($version as $key => $val) {
+ $stats[] = array($key, '{#black}' . $val);
+ }
+
+ $stats[] = array();
+ $stats[] = array('{#black}GetSystemInfo:', '');
+ foreach ($info as $key => $val) {
+ $stats[] = array($key, '{#black}' . $val);
+ }
+
+ $stats[] = array();
+ $stats[] = array('Rights', '{#black}' . ($aseco->server->rights ? 'United $gCoppers: {#black}' . $coppers : 'Nations'));
+ $stats[] = array('Packmask', '{#black}' . $aseco->server->packmask);
+ if ($aseco->server->isrelay)
+ $stats[] = array('Relays', '{#black}' . $aseco->server->relaymaster['Login']);
+ else
+ $stats[] = array('Master to', '{#black}' . count($aseco->server->relayslist) .
+ ' $grelay' . (count($aseco->server->relayslist) == 1 ? '' : 's'));
+
+ $stats[] = array();
+ $stats[] = array('{#black}GetLadderServerLimits:', '');
+ foreach ($ladderlim as $key => $val) {
+ $stats[] = array($key, '{#black}' . $val);
+ }
+
+ $admin->msgs[] = $stats;
+ $stats = array();
+
+ $stats[] = array('{#black}GetServerOptions:', '');
+ foreach ($options as $key => $val) {
+ // show only Current values, not Next ones
+ if ($key != 'Name' && $key != 'Comment' && substr($key, 0, 4) != 'Next')
+ if (is_bool($val))
+ $stats[] = array($key, '{#black}' . bool2text($val));
+ else
+ $stats[] = array($key, '{#black}' . $val);
+ }
+
+ $admin->msgs[] = $stats;
+ $stats = array();
+
+ $lines = 0;
+ $stats[] = array('{#black}GetCurrentGameInfo:', '');
+ foreach ($gameinfo as $key => $val) {
+ if (is_bool($val))
+ $stats[] = array($key, '{#black}' . bool2text($val));
+ else
+ if ($key == 'GameMode')
+ $stats[] = array($key, '{#black}' . $val . '$g (' . $aseco->server->gameinfo->getMode() . ')');
+ else
+ $stats[] = array($key, '{#black}' . $val);
+
+ if (++$lines > 18) {
+ $admin->msgs[] = $stats;
+ $stats = array();
+ $stats[] = array('{#black}GetCurrentGameInfo:', '');
+ $lines = 0;
+ }
+ }
+
+ $stats[] = array();
+ $stats[] = array('{#black}GetNetworkStats:', '');
+ foreach ($network as $key => $val) {
+ if ($key != 'PlayerNetInfos')
+ $stats[] = array($key, '{#black}' . $val);
+ }
+
+ $stats[] = array();
+ $stats[] = array('{#black}GetCallVoteRatios:', '');
+ $stats[] = array('Command', 'Ratio');
+ foreach ($callvotes as $entry) {
+ $stats[] = array('{#black}' . $entry['Command'], '{#black}' . round($entry['Ratio'], 2));
+ }
+
+ $admin->msgs[] = $stats;
+ display_manialink_multi($admin);
+ } else {
+ $message = $aseco->getChatMessage('FOREVER_ONLY');
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Send private message to all available admins.
+ */
+ } elseif ($command['params'][0] == 'pm') {
+ global $pmbuf, $pmlen, $muting_available; // from plugin.muting.php
+
+ // check for non-empty message
+ if ($arglist[1] != '') {
+ // drop oldest pm line if buffer full
+ if (count($pmbuf) >= $pmlen) {
+ array_shift($pmbuf);
+ }
+ // append timestamp, admin nickname (but strip wide font) and pm line to history
+ $nick = str_ireplace('$w', '', $admin->nickname);
+ $pmbuf[] = array(date('H:i:s'), $nick, $arglist[1]);
+
+ // find and pm other masteradmins/admins/operators
+ $nicks = '';
+ $msg = '{#error}-pm-$g[' . $nick . '$z$s$i->{#logina}Admins$g]$i {#interact}' . $arglist[1];
+ $msg = $aseco->formatColors($msg);
+ foreach ($aseco->server->players->player_list as $pl) {
+ // check for admin ability
+ if ($pl->login != $login && $aseco->allowAbility($pl, 'pm')) {
+ $nicks .= str_ireplace('$w', '', $pl->nickname) . '$z$s$i,';
+ $aseco->client->addCall('ChatSendServerMessageToLogin', array($msg, $pl->login));
+
+ // check if player muting is enabled
+ if ($muting_available) {
+ // drop oldest message if receiver's mute buffer full
+ if (count($pl->mutebuf) >= 28) { // chat window length
+ array_shift($pl->mutebuf);
+ }
+ // append pm line to receiver's mute buffer
+ $pl->mutebuf[] = $msg;
+ }
+ }
+ }
+
+ // CC message to self
+ if ($nicks) {
+ $nicks = substr($nicks, 0, strlen($nicks)-1); // strip trailing ','
+ $msg = '{#error}-pm-$g[' . $nick . '$z$s$i->' . $nicks . ']$i {#interact}' . $arglist[1];
+ } else {
+ $msg = '{#server}> {#error}No other admins currectly available!';
+ }
+ $msg = $aseco->formatColors($msg);
+ $aseco->client->addCall('ChatSendServerMessageToLogin', array($msg, $login));
+ if (!$aseco->client->multiquery()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] ChatSend PM (multi) - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ }
+
+ // check if player muting is enabled
+ if ($muting_available) {
+ // drop oldest message if sender's mute buffer full
+ if (count($admin->mutebuf) >= 28) { // chat window length
+ array_shift($admin->mutebuf);
+ }
+ // append pm line to sender's mute buffer
+ $admin->mutebuf[] = $msg;
+ }
+ } else {
+ $msg = '{#server}> {#error}No message!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($msg), $login);
+ }
+
+ /**
+ * Displays log of recent private admin messages.
+ */
+ } elseif ($command['params'][0] == 'pmlog') {
+ global $pmbuf, $lnlen;
+
+ if (!empty($pmbuf)) {
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Recent PM history:' . LF;
+ $msg = '';
+ $lines = 0;
+ $admin->msgs = array();
+ $admin->msgs[0] = 1;
+ foreach ($pmbuf as $item) {
+ // break up long lines into chunks with continuation strings
+ $multi = explode(LF, wordwrap(stripColors($item[2]), $lnlen, LF . '...'));
+ foreach ($multi as $line) {
+ $line = substr($line, 0, $lnlen+3); // chop off excessively long words
+ $msg .= '$z' . ($aseco->settings['chatpmlog_times'] ? '$n<{#server}' . $item[0] . '$z$n>$m ' : '') .
+ '[{#black}' . $item[1] . '$z] ' . $line . LF;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } else { // > 2
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Recent Admin PM history:';
+ $msg = array();
+ $lines = 0;
+ $admin->msgs = array();
+ $admin->msgs[0] = array(1, $head, array(1.2), array('Icons64x64_1', 'Outbox'));
+ foreach ($pmbuf as $item) {
+ // break up long lines into chunks with continuation strings
+ $multi = explode(LF, wordwrap(stripColors($item[2]), $lnlen+30, LF . '...'));
+ foreach ($multi as $line) {
+ $line = substr($line, 0, $lnlen+33); // chop off excessively long words
+ $msg[] = array('$z' . ($aseco->settings['chatpmlog_times'] ? '<{#server}' . $item[0] . '$z> ' : '') .
+ '[{#black}' . $item[1] . '$z] ' . $line);
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ }
+ // add if last batch exists
+ if (!empty($msg))
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ display_manialink_multi($admin);
+ }
+ } else {
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No PM history found!'), $login);
+ }
+
+ /**
+ * Executes direct server call
+ */
+ } elseif ($command['params'][0] == 'call') {
+ global $method_results;
+
+ // extra admin tier check
+ if (!$aseco->isMasterAdmin($admin)) {
+ $aseco->client->query('ChatSendToLogin', $aseco->formatColors('{#error}You don\'t have the required admin rights to do that!'), $login);
+ return;
+ }
+
+ // check parameter(s)
+ if ($command['params'][1] != '') {
+ if ($command['params'][1] == 'help') {
+ if (isset($command['params'][2]) && $command['params'][2] != '') {
+ // generate help message for method
+ $method = $command['params'][2];
+ $sign = $aseco->client->addCall('system.methodSignature', array($method));
+ $help = $aseco->client->addCall('system.methodHelp', array($method));
+ if (!$aseco->client->multiquery()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] system.method - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ } else {
+ $response = $aseco->client->getResponse();
+ if (isset($response[0]['faultCode'])) {
+ $message = '{#server}> {#error}No such method {#highlite}$i ' . $method . ' {#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ } else {
+ $sign = $response[$sign][0][0];
+ $help = $response[$help][0];
+
+ // format signature & help
+ $params = '';
+ for ($i = 1; $i < count($sign); $i++)
+ $params .= $sign[$i] . ', ';
+ $params = substr($params, 0, strlen($params)-2); // strip trailing ", "
+ $sign = $sign[0] . ' {#black}' . $method . '$g (' . $params . ')';
+ $sign = explode(LF, wordwrap($sign, 58, LF));
+ $help = str_replace(array('', ''),
+ array('$i', '$i'), $help);
+ $help = explode(LF, wordwrap($help, 58, LF));
+
+ // compile & display help message
+ if ($aseco->server->getGame() == 'TMN') {
+ $info = 'Server Method help for:' . LF . LF;
+ foreach ($sign as $line)
+ $info .= $line . LF;
+ $info .= LF;
+ foreach ($help as $line)
+ $info .= $line . LF;
+
+ // display popup message
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $aseco->formatColors($info), 'OK', '', 0);
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $header = 'Server Method help for:';
+ $info = array();
+ foreach ($sign as $line)
+ $info[] = array($line);
+ $info[] = array();
+ foreach ($help as $line)
+ $info[] = array($line);
+
+ // display ManiaLink message
+ display_manialink($login, $header, array('Icons128x128_1', 'Advanced', 0.02), $info, array(1.05), 'OK');
+ }
+ }
+ }
+
+ } else {
+ // compile & display help message
+ if ($aseco->server->getGame() == 'TMN') {
+ $help = '{#black}/admin call$g executes server method:' . LF;
+ $help .= ' - {#black}help$g, displays this help information' . LF;
+ $help .= ' - {#black}help Method$g, displays help for method' . LF;
+ $help .= ' - {#black}list$g, lists all available methods' . LF;
+ $help .= ' - {#black}Method {params}$g, executes method & displays result' . LF;
+
+ // display popup message
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $aseco->formatColors($help), 'OK', '', 0);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $header = '{#black}/admin call$g executes server method:';
+ $help = array();
+ $help[] = array('...', '{#black}help',
+ 'Displays this help information');
+ $help[] = array('...', '{#black}help Method',
+ 'Displays help for method');
+ $help[] = array('...', '{#black}list',
+ 'Lists all available methods');
+ $help[] = array('...', '{#black}Method {params}',
+ 'Executes method & displays result');
+
+ // display ManiaLink message
+ display_manialink($login, $header, array('Icons64x64_1', 'TrackInfo', -0.01), $help, array(1.0, 0.05, 0.35, 0.6), 'OK');
+ }
+ }
+
+ } elseif ($command['params'][1] == 'list') {
+ // get list of methods
+ $aseco->client->query('system.listMethods');
+ $methods = $aseco->client->getResponse();
+ $admin->msgs = array();
+
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Available Methods on this Server:' . LF . 'Id Method' . LF;
+ $msg = '';
+ $mid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($methods as $method) {
+ $msg .= '$g' . str_pad($mid, 3, '0', STR_PAD_LEFT) . '. {#black}'
+ . $method . LF;
+ $mid++;
+ if (++$lines > 9) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Available Methods on this Server:';
+ $msg = array();
+ $msg[] = array('Id', 'Method');
+ $mid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(0.9, 0.15, 0.75), array('Icons128x128_1', 'Advanced', 0.02));
+ foreach ($methods as $method) {
+ $msg[] = array(str_pad($mid, 2, '0', STR_PAD_LEFT) . '.',
+ '{#black}' . $method);
+ $mid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ $msg[] = array('Id', 'Method');
+ }
+ }
+ // add if last batch exists
+ if (count($msg) > 1)
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ display_manialink_multi($admin);
+ }
+
+ } else { // server method
+ $method = $command['params'][1];
+ // collect parameters with correct types
+ $args = array();
+ $multistr = '';
+ $in_multi = false;
+ for ($i = 2; $i < count($command['params']); $i++) {
+ if (!$in_multi && strtolower($command['params'][$i]) == 'true')
+ $args[] = true;
+ elseif (!$in_multi && strtolower($command['params'][$i]) == 'false')
+ $args[] = false;
+ elseif (!$in_multi && is_numeric($command['params'][$i]))
+ $args[] = intval($command['params'][$i]);
+ else
+ // check for multi-word strings
+ if ($in_multi) {
+ if (substr($command['params'][$i], -1) == '"') {
+ $args[] = $multistr . ' ' . substr($command['params'][$i], 0, -1);
+ $multistr = '';
+ $in_multi = false;
+ } else {
+ $multistr .= ' ' . $command['params'][$i];
+ }
+ } else {
+ if (substr($command['params'][$i], 0, 1) == '"') {
+ $multistr = substr($command['params'][$i], 1);
+ $in_multi = true;
+ } else {
+ $args[] = $command['params'][$i];
+ }
+ }
+ }
+
+ // execute method
+ switch (count($args)) {
+ case 0: $res = $aseco->client->query($method);
+ break;
+ case 1: $res = $aseco->client->query($method, $args[0]);
+ break;
+ case 2: $res = $aseco->client->query($method, $args[0], $args[1]);
+ break;
+ case 3: $res = $aseco->client->query($method, $args[0], $args[1], $args[2]);
+ break;
+ case 4: $res = $aseco->client->query($method, $args[0], $args[1], $args[2], $args[3]);
+ break;
+ case 5: $res = $aseco->client->query($method, $args[0], $args[1], $args[2], $args[3], $args[4]);
+ break;
+ }
+ // process result
+ if ($res) {
+ $res = $aseco->client->getResponse();
+ $admin->msgs = array();
+ $method_results = array();
+ collect_results($method, $res, '');
+
+ // compile & display result message
+ if ($aseco->server->getGame() == 'TMN') {
+ $head = 'Method results for:' . LF . LF;
+ $msg = '';
+ $mid = 1;
+ $lines = 0;
+ $admin->msgs[0] = 1;
+ foreach ($method_results as $line) {
+ $msg .= $line . '$z' . LF;
+ $mid++;
+ if (++$lines > 14) {
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+ $lines = 0;
+ $msg = '';
+ }
+ }
+ // add if last batch exists
+ if ($msg != '')
+ $admin->msgs[] = $aseco->formatColors($head . $msg);
+
+ // display popup message
+ if (count($admin->msgs) == 2) {
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0);
+ } else { // > 2
+ $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0);
+ }
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $head = 'Method results for:';
+ $msg = array();
+ $mid = 1;
+ $lines = 0;
+ $admin->msgs[0] = array(1, $head, array(1.1), array('Icons128x128_1', 'Advanced', 0.02));
+ foreach ($method_results as $line) {
+ $msg[] = array($line);
+ $mid++;
+ if (++$lines > 20) {
+ $admin->msgs[] = $msg;
+ $lines = 0;
+ $msg = array();
+ }
+ }
+ // add if last batch exists
+ if (!empty($msg))
+ $admin->msgs[] = $msg;
+
+ // display ManiaLink message
+ display_manialink_multi($admin);
+ }
+ } else {
+ $message = '{#server}> {#error}Method error for {#highlite}$i ' . $method . '{#error}: [' . $aseco->client->getErrorCode() . '] ' . $aseco->client->getErrorMessage();
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+ }
+ } else {
+ $message = '{#server}> {#error}No call specified - see {#highlite}$i /admin call help{#error} and {#highlite}$i /admin call list{#error}!';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ /**
+ * Unlocks admin commands & features.
+ */
+ } elseif ($command['params'][0] == 'unlock' && $command['params'][1] != '') {
+
+ // check unlock password
+ if ($aseco->settings['lock_password'] == $command['params'][1]) {
+ $admin->unlocked = true;
+ $message = '{#server}> {#admin}Password accepted: admin commands unlocked!';
+ } else {
+ $message = '{#server}> {#error}Invalid password!';
+ }
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Toggle debug on/off.
+ */
+ } elseif ($command['params'][0] == 'debug') {
+
+ $aseco->debug = !$aseco->debug;
+ if ($aseco->debug) {
+ $message = '{#server}> Debug is now enabled';
+ } else {
+ $message = '{#server}> Debug is now disabled';
+ }
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+
+ /**
+ * Shuts down XASECO.
+ */
+ } elseif ($command['params'][0] == 'shutdown') {
+
+ trigger_error('Shutdown XASECO!', E_USER_ERROR);
+
+ /**
+ * Shuts down Server & XASECO.
+ */
+ } elseif ($command['params'][0] == 'shutdownall') {
+
+ $message = '{#server}>> {#error}$wShutting down server now!';
+ $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message));
+
+ $rtn = $aseco->client->query('StopServer');
+ if (!$rtn) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] StopServer - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ } else {
+ // test for /noautoquit
+ sleep(2);
+ $autoquit = new IXR_ClientMulticall_Gbx();
+ if ($autoquit->InitWithIp($aseco->server->ip, $aseco->server->port))
+ $aseco->client->query('QuitGame');
+
+ trigger_error('Shutdown ' . $aseco->server->getGame() . ' server & XASECO!', E_USER_ERROR);
+ }
+
+ /**
+ * Checks current version of XASECO.
+ */
+ } elseif ($command['params'][0] == 'uptodate') {
+
+ if (function_exists('admin_uptodate')) {
+ admin_uptodate($aseco, $command); // from plugin.uptodate.php
+ } else {
+ // show chat message
+ $message = '{#server}> {#admin}Version checking unavailable - include plugins.uptodate.php in plugins.xml';
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+
+ } else {
+ $message = '{#server}> {#error}Unknown admin command or missing parameter(s): {#highlite}$i ' . $arglist[0] . ' ' . $arglist[1];
+ $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login);
+ }
+} // chat_admin
+
+
+function get_ignorelist($aseco) {
+
+ $aseco->client->resetError();
+ $newlist = array();
+ $done = false;
+ $size = 300;
+ $i = 0;
+ while (!$done) {
+ $aseco->client->query('GetIgnoreList', $size, $i);
+ $players = $aseco->client->getResponse();
+ if (!empty($players)) {
+ if ($aseco->client->isError()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetIgnoreList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $done = true;
+ break;
+ }
+ foreach ($players as $prow) {
+ // fetch nickname for this login
+ $lgn = $prow['Login'];
+ $nick = $aseco->getPlayerNick($lgn);
+ $newlist[$lgn] = array($lgn, $nick);
+ }
+ if (count($players) < $size) {
+ // got less than 300 players, might as well leave
+ $done = true;
+ } else {
+ $i += $size;
+ }
+ } else {
+ $done = true;
+ }
+ }
+ return $newlist;
+} // get_ignorelist
+
+function get_banlist($aseco) {
+
+ $aseco->client->resetError();
+ $newlist = array();
+ $done = false;
+ $size = 300;
+ $i = 0;
+ while (!$done) {
+ $aseco->client->query('GetBanList', $size, $i);
+ $players = $aseco->client->getResponse();
+ if (!empty($players)) {
+ if ($aseco->client->isError()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetBanList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $done = true;
+ break;
+ }
+ foreach ($players as $prow) {
+ // fetch nickname for this login
+ $lgn = $prow['Login'];
+ $nick = $aseco->getPlayerNick($lgn);
+ $newlist[$lgn] = array($lgn, $nick,
+ preg_replace('/:\d+/', '', $prow['IPAddress'])); // strip port
+ }
+ if (count($players) < $size) {
+ // got less than 300 players, might as well leave
+ $done = true;
+ } else {
+ $i += $size;
+ }
+ } else {
+ $done = true;
+ }
+ }
+ return $newlist;
+} // get_banlist
+
+function get_blacklist($aseco) {
+
+ $aseco->client->resetError();
+ $newlist = array();
+ $done = false;
+ $size = 300;
+ $i = 0;
+ while (!$done) {
+ $aseco->client->query('GetBlackList', $size, $i);
+ $players = $aseco->client->getResponse();
+ if (!empty($players)) {
+ if ($aseco->client->isError()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetBlackList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $done = true;
+ break;
+ }
+ foreach ($players as $prow) {
+ // fetch nickname for this login
+ $lgn = $prow['Login'];
+ $nick = $aseco->getPlayerNick($lgn);
+ $newlist[$lgn] = array($lgn, $nick);
+ }
+ if (count($players) < $size) {
+ // got less than 300 players, might as well leave
+ $done = true;
+ } else {
+ $i += $size;
+ }
+ } else {
+ $done = true;
+ }
+ }
+ return $newlist;
+} // get_blacklist
+
+function get_guestlist($aseco) {
+
+ $aseco->client->resetError();
+ $newlist = array();
+ $done = false;
+ $size = 300;
+ $i = 0;
+ while (!$done) {
+ $aseco->client->query('GetGuestList', $size, $i);
+ $players = $aseco->client->getResponse();
+ if (!empty($players)) {
+ if ($aseco->client->isError()) {
+ trigger_error('[' . $aseco->client->getErrorCode() . '] GetGuestList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING);
+ $done = true;
+ break;
+ }
+ foreach ($players as $prow) {
+ // fetch nickname for this login
+ $lgn = $prow['Login'];
+ $nick = $aseco->getPlayerNick($lgn);
+ $newlist[$lgn] = array($lgn, $nick);
+ }
+ if (count($players) < $size) {
+ // got less than 300 players, might as well leave
+ $done = true;
+ } else {
+ $i += $size;
+ }
+ } else {
+ $done = true;
+ }
+ }
+ return $newlist;
+} // get_guestlist
+
+function collect_results($key, $val, $indent) {
+ global $method_results;
+
+ if (is_array($val)) {
+ // recursively compile array results
+ $method_results[] = $indent . '*' . $key . ' :';
+ foreach ($val as $key2 => $val2) {
+ collect_results($key2, $val2, ' ' . $indent);
+ }
+ } else {
+ if (!is_string($val))
+ $val = strval($val);
+ // format result key/value pair
+ $val = explode(LF, wordwrap($val, 32, LF . $indent . ' ', true));
+ $firstline = true;
+ foreach ($val as $line) {
+ if ($firstline)
+ $method_results[] = $indent . $key . ' = ' . $line;
+ else
+ $method_results[] = $line;
+ $firstline = false;
+ }
+ }
+} // collect_results
+
+
+// called @ onPlayerManialinkPageAnswer
+// Handles ManiaLink admin responses
+// [0]=PlayerUid, [1]=Login, [2]=Answer
+function event_admin($aseco, $answer) {
+
+ // leave actions outside 2201 - 5200 to other handlers
+ if ($answer[2] < 2201 && $answer[2] > 5200 &&
+ $answer[2] < -8100 && $answer[2] > -7901)
+ return;
+
+ // get player & possible parameter
+ $player = $aseco->server->players->getPlayer($answer[1]);
+ if (isset($player->panels['plyparam']))
+ $param = $player->panels['plyparam'];
+
+ // check for /admin warn command
+ if ($answer[2] >= 2201 && $answer[2] <= 2400) {
+ $target = $player->playerlist[$answer[2]-2201]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin warn {2}"',
+ $player->login, $target);
+
+ // warn selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'warn ' . $target;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin ignore command
+ elseif ($answer[2] >= 2401 && $answer[2] <= 2600) {
+ $target = $player->playerlist[$answer[2]-2401]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin ignore {2}"',
+ $player->login, $target);
+
+ // ignore selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'ignore ' . $target;
+ chat_admin($aseco, $command);
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players {2}"',
+ $player->login, $param);
+
+ // refresh players window
+ $command['params'] = 'players ' . $param;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin unignore command
+ elseif ($answer[2] >= 2601 && $answer[2] <= 2800) {
+ $target = $player->playerlist[$answer[2]-2601]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin unignore {2}"',
+ $player->login, $target);
+
+ // unignore selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'unignore ' . $target;
+ chat_admin($aseco, $command);
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players {2}"',
+ $player->login, $param);
+
+ // refresh players window
+ $command['params'] = 'players ' . $param;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin kick command
+ elseif ($answer[2] >= 2801 && $answer[2] <= 3000) {
+ $target = $player->playerlist[$answer[2]-2801]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin kick {2}"',
+ $player->login, $target);
+
+ // kick selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'kick ' . $target;
+ chat_admin($aseco, $command);
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players {2}"',
+ $player->login, $param);
+
+ // refresh players window
+ $command['params'] = 'players ' . $param;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin ban command
+ elseif ($answer[2] >= 3001 && $answer[2] <= 3200) {
+ $target = $player->playerlist[$answer[2]-3001]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin ban {2}"',
+ $player->login, $target);
+
+ // ban selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'ban ' . $target;
+ chat_admin($aseco, $command);
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players {2}"',
+ $player->login, $param);
+
+ // refresh players window
+ $command['params'] = 'players ' . $param;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin unban command
+ elseif ($answer[2] >= 3201 && $answer[2] <= 3400) {
+ $target = $player->playerlist[$answer[2]-3201]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin unban {2}"',
+ $player->login, $target);
+
+ // unban selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'unban ' . $target;
+ chat_admin($aseco, $command);
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players {2}"',
+ $player->login, $param);
+
+ // refresh players window
+ $command['params'] = 'players ' . $param;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin black command
+ elseif ($answer[2] >= 3401 && $answer[2] <= 3600) {
+ $target = $player->playerlist[$answer[2]-3401]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin black {2}"',
+ $player->login, $target);
+
+ // black selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'black ' . $target;
+ chat_admin($aseco, $command);
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players {2}"',
+ $player->login, $param);
+
+ // refresh players window
+ $command['params'] = 'players ' . $param;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin unblack command
+ elseif ($answer[2] >= 3601 && $answer[2] <= 3800) {
+ $target = $player->playerlist[$answer[2]-3601]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin unblack {2}"',
+ $player->login, $target);
+
+ // unblack selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'unblack ' . $target;
+ chat_admin($aseco, $command);
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players {2}"',
+ $player->login, $param);
+
+ // refresh players window
+ $command['params'] = 'players ' . $param;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin addguest command
+ elseif ($answer[2] >= 3801 && $answer[2] <= 4000) {
+ $target = $player->playerlist[$answer[2]-3801]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin addguest {2}"',
+ $player->login, $target);
+
+ // addguest selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'addguest ' . $target;
+ chat_admin($aseco, $command);
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players {2}"',
+ $player->login, $param);
+
+ // refresh players window
+ $command['params'] = 'players ' . $param;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin removeguest command
+ elseif ($answer[2] >= 4001 && $answer[2] <= 4200) {
+ $target = $player->playerlist[$answer[2]-4001]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin removeguest {2}"',
+ $player->login, $target);
+
+ // removeguest selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'removeguest ' . $target;
+ chat_admin($aseco, $command);
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players {2}"',
+ $player->login, $param);
+
+ // refresh players window
+ $command['params'] = 'players ' . $param;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin forcespec command
+ elseif ($answer[2] >= 4201 && $answer[2] <= 4400) {
+ $target = $player->playerlist[$answer[2]-4201]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin forcespec {2}"',
+ $player->login, $target);
+
+ // forcespec selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'forcespec ' . $target;
+ chat_admin($aseco, $command);
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin players {2}"',
+ $player->login, $param);
+
+ // refresh players window
+ $command['params'] = 'players ' . $param;
+ chat_admin($aseco, $command);
+ }
+
+ // check for /admin unignore command in listignores
+ elseif ($answer[2] >= 4401 && $answer[2] <= 4600) {
+ $target = $player->playerlist[$answer[2]-4401]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin unignore {2}"',
+ $player->login, $target);
+
+ // unignore selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'unignore ' . $target;
+ chat_admin($aseco, $command);
+
+ // check whether last player was unignored
+ $ignores = get_ignorelist($aseco);
+ if (empty($ignores)) {
+ // close main window
+ mainwindow_off($aseco, $player->login);
+ } else {
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin listignores"',
+ $player->login);
+
+ // refresh listignores window
+ $command['params'] = 'listignores';
+ chat_admin($aseco, $command);
+ }
+ }
+
+ // check for /admin unban command in listbans
+ elseif ($answer[2] >= 4601 && $answer[2] <= 4800) {
+ $target = $player->playerlist[$answer[2]-4601]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin unban {2}"',
+ $player->login, $target);
+
+ // unban selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'unban ' . $target;
+ chat_admin($aseco, $command);
+
+ // check whether last player was unbanned
+ $bans = get_banlist($aseco);
+ if (empty($bans)) {
+ // close main window
+ mainwindow_off($aseco, $player->login);
+ } else {
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin listbans"',
+ $player->login);
+
+ // refresh listbans window
+ $command['params'] = 'listbans';
+ chat_admin($aseco, $command);
+ }
+ }
+
+ // check for /admin unblack command in listblacks
+ elseif ($answer[2] >= 4801 && $answer[2] <= 5000) {
+ $target = $player->playerlist[$answer[2]-4801]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin unblack {2}"',
+ $player->login, $target);
+
+ // unblack selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'unblack ' . $target;
+ chat_admin($aseco, $command);
+
+ // check whether last player was unblacked
+ $blacks = get_blacklist($aseco);
+ if (empty($blacks)) {
+ // close main window
+ mainwindow_off($aseco, $player->login);
+ } else {
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin listblacks"',
+ $player->login);
+
+ // refresh listblacks window
+ $command['params'] = 'listblacks';
+ chat_admin($aseco, $command);
+ }
+ }
+
+ // check for /admin removeguest command in listguests
+ elseif ($answer[2] >= 5001 && $answer[2] <= 5200) {
+ $target = $player->playerlist[$answer[2]-5001]['login'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin removeguest {2}"',
+ $player->login, $target);
+
+ // removeguest selected player
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'removeguest ' . $target;
+ chat_admin($aseco, $command);
+
+ // check whether last guest was removed
+ $guests = get_guestlist($aseco);
+ if (empty($guests)) {
+ // close main window
+ mainwindow_off($aseco, $player->login);
+ } else {
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin listguests"',
+ $player->login);
+
+ // refresh listguests window
+ $command['params'] = 'listguests';
+ chat_admin($aseco, $command);
+ }
+ }
+
+ // check for /admin unbanip command
+ elseif ($answer[2] >= -8100 && $answer[2] <= -7901) {
+ $target = $player->playerlist[abs($answer[2])-7901]['ip'];
+
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin unbanip {2}"',
+ $player->login, $target);
+
+ // unbanip selected IP
+ $command = array();
+ $command['author'] = $player;
+ $command['params'] = 'unbanip ' . $target;
+ chat_admin($aseco, $command);
+
+ // check whether last IP was unbanned
+ if (!$empty = empty($aseco->bannedips)) {
+ $empty = true;
+ for ($i = 0; $i < count($aseco->bannedips); $i++)
+ if ($aseco->bannedips[$i] != '') {
+ $empty = false;
+ break;
+ }
+ }
+ if ($empty) {
+ // close main window
+ mainwindow_off($aseco, $player->login);
+ } else {
+ // log clicked command
+ $aseco->console('player {1} clicked command "/admin listips"',
+ $player->login);
+
+ // refresh listips window
+ $command['params'] = 'listips';
+ chat_admin($aseco, $command);
+ }
+ }
+} // event_admin
+?>
diff --git a/xaseco/plugins/chat.dedimania.php b/xaseco/plugins/chat.dedimania.php
new file mode 100644
index 0000000..a89c2c5
--- /dev/null
+++ b/xaseco/plugins/chat.dedimania.php
@@ -0,0 +1,941 @@
+server->getGame() == 'TMN') {
+ $help = '{#dedimsg}Dedimania$g is an online World Records database for {#black}all$g' . LF;
+ $help .= 'TrackMania games. See its official site at:' . LF;
+ $help .= '{#black}http://www.dedimania.com/SITE/$g and the records database:' . LF;
+ $help .= '{#black}http://www.dedimania.com/tmstats/?do=stat$g .' . LF . LF;
+ $help .= 'Dedimania records are stored per game (TMN, TMU, etc)' . LF;
+ $help .= 'and mode (TimeAttack, Rounds, etc) and shared between' . LF;
+ $help .= 'all servers that operate with Dedimania support.' . LF . LF;
+ $help .= 'The available Dedimania commands are similar to local' . LF;
+ $help .= 'record commands:' . LF;
+ $help .= '{#black}/dedirecs$g, {#black}/dedinew$g, {#black}/dedilive$g, {#black}/dedipb$g, {#black}/dedicps$g, {#black}/dedistats$g,' . LF;
+ $help .= '{#black}/dedifirst$g, {#black}/dedilast$g, {#black}/dedinext$g, {#black}/dedidiff$g, {#black}/dedirange$g' . LF;
+ $help .= 'See the {#black}/helpall$g command for detailed descriptions.';
+
+ // display popup message
+ $aseco->client->query('SendDisplayServerMessageToLogin', $command['author']->login, $aseco->formatColors($help), 'OK', '', 0);
+
+ } elseif ($aseco->server->getGame() == 'TMF') {
+ $header = 'Dedimania information:';
+ $data = array();
+ $data[] = array('{#dedimsg}Dedimania$g is an online World Records database for {#black}all');
+ $data[] = array('TrackMania games. See its official site at:');
+ $data[] = array('{#black}$l[http://www.dedimania.com/SITE/]http://www.dedimania.com/SITE/$l$g and the records database:');
+ $data[] = array('{#black}$l[http://www.dedimania.com/tmstats/?do=stat]http://www.dedimania.com/tmstats/?do=stat$l$g .');
+ $data[] = array();
+ $data[] = array('Dedimania records are stored per game (TMN, TMU, etc)');
+ $data[] = array('and mode (TimeAttack, Rounds, etc) and shared between');
+ $data[] = array('all servers that operate with Dedimania support.');
+ $data[] = array();
+ $data[] = array('The available Dedimania commands are similar to local');
+ $data[] = array('record commands:');
+ $data[] = array('{#black}/dedirecs$g, {#black}/dedinew$g, {#black}/dedilive$g, {#black}/dedipb$g, {#black}/dedicps$g, {#black}/dedistats$g,');
+ $data[] = array('{#black}/dedifirst$g, {#black}/dedilast$g, {#black}/dedinext$g, {#black}/dedidiff$g, {#black}/dedirange$g');
+ $data[] = array();
+ $data[] = array('See the {#black}/helpall$g command for detailed descriptions.');
+
+ // display ManiaLink message
+ display_manialink($command['author']->login, $header, array('Icons64x64_1', 'TrackInfo', -0.01), $data, array(0.95), 'OK');
+ }
+} // chat_helpdedi
+
+function chat_dedirecs($aseco, $command) {
+ global $dedi_db;
+
+ $player = $command['author'];
+ $login = $player->login;
+ $dedi_recs = $dedi_db['Challenge']['Records'];
+
+ // split params into array
+ $arglist = explode(' ', strtolower(preg_replace('/ +/', ' ', $command['params'])));
+
+ // process optional relations commands
+ if ($arglist[0] == 'help') {
+ if ($aseco->server->getGame() == 'TMN') {
+ $help = '{#black}/dedirecs