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}] + {#message}Access from zone {#highlite}{1} {#message}denied$z + {#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] + . "" . 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") + . "" . CRLF; + } + $lists .= "\t" . CRLF + . CRLF + . "\t" . CRLF; + foreach ($this->op_abilities as $ability => $value) { + $lists .= "\t\t<" . strtolower($ability) . ">" . + ($value[0] ? "true" : "false") + . "" . 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}Gold: {#highlite}{4} {#server}Silver: {#highlite}{5} {#server}Bronze: {#highlite}{6} {#server}Cost: {#highlite}{7} + {#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 .= ''; + $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 .= ''; + $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 .= ''; + $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 .= ''; + + //$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 .= ''; + } 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 .= ''; + } + } + 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