* Derived from XAseco (formerly ASECO/RASP) by Xymph, Flo and others * * ---------------------------------------------------------------------------------- * Author: undef.de * Copyright: May 2014 - August 2017 by undef.de * ---------------------------------------------------------------------------------- * * LICENSE: This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ---------------------------------------------------------------------------------- * */ // Current project name, version and website define('UASECO_NAME', 'UASECO'); define('UASECO_VERSION', '0.9.6'); define('UASECO_BUILD', '2017-08-19'); define('UASECO_WEBSITE', 'https://www.UASECO.org'); // Setup required official dedicated server build, Api-Version and PHP-Version define('MANIAPLANET_BUILD_POSIX', '2017-08-04_11_00'); define('MANIAPLANET_BUILD_WINDOWS', '2017-08-02_17_02'); define('XMLRPC_API_VERSION', '2013-04-16'); define('MODESCRIPT_API_VERSION', '2.1.1'); define('MIN_PHP_VERSION', '5.6.0'); define('MIN_MYSQL_VERSION', '5.1.0'); define('MIN_MARIADB_VERSION', '5.5.20'); // Setup misc. define('CRLF', PHP_EOL); define('LF', "\n"); if (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN') { define('OPERATING_SYSTEM', 'WINDOWS'); define('MANIAPLANET_BUILD', MANIAPLANET_BUILD_WINDOWS); } else { define('OPERATING_SYSTEM', 'POSIX'); define('MANIAPLANET_BUILD', MANIAPLANET_BUILD_POSIX); } // Report all error_reporting(-1); date_default_timezone_set(@date_default_timezone_get()); setlocale(LC_NUMERIC, 'C'); mb_internal_encoding('UTF-8'); // Init random generator list($usec, $sec) = explode(' ', microtime()); mt_srand((float) $sec + ((float) $usec * 100000)); // Include required classes require_once('includes/core/baseclass.class.php'); // Base class require_once('includes/core/helper.class.php'); // Misc. functions for UASECO, e.g. $aseco->console()... based upon basic.inc.php require_once('includes/core/XmlRpc/GbxRemote.php'); // https://github.com/maniaplanet/dedicated-server-api require_once('includes/core/webrequest.class.php'); require_once('includes/core/xmlparser.class.php'); require_once('includes/core/gbxdatafetcher.class.php'); // Provides access to GBX data require_once('includes/core/mxinfofetcher.class.php'); // Provides access to ManiaExchange info require_once('includes/core/continent.class.php'); require_once('includes/core/country.class.php'); require_once('includes/core/database.class.php'); require_once('includes/core/locales.class.php'); // Required by includes/core/message.class.php require_once('includes/core/message.class.php'); require_once('includes/core/gameinfo.class.php'); // Required by includes/core/server.class.php require_once('includes/core/server.class.php'); require_once('includes/core/dependence.class.php'); // Required by includes/core/plugin.class.php require_once('includes/core/plugin.class.php'); require_once('includes/core/dialog.class.php'); require_once('includes/core/window.class.php'); // Required by includes/core/windowlist.class.php require_once('includes/core/windowlist.class.php'); require_once('includes/core/player.class.php'); require_once('includes/core/playerlist.class.php'); require_once('includes/core/playlist.class.php'); // Holds the Playlist (aka Jukebox) for Maps require_once('includes/core/checkpoint.class.php'); require_once('includes/core/record.class.php'); require_once('includes/core/recordlist.class.php'); require_once('includes/core/ranking.class.php'); // Required by includes/core/rankinglist.class.php require_once('includes/core/rankinglist.class.php'); require_once('includes/core/map.class.php'); // Required by includes/core/maplist.class.php require_once('includes/core/maplist.class.php'); require_once('includes/core/maphistory.class.php'); // Holds the Maphistory /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ class UASECO extends Helper { public $debug; public $logfile; public $client; public $parser; public $webrequest; public $db; public $locales; public $continent; public $country; public $windows; public $server; public $registered_events; public $registered_chatcmds; public $chat_colors; public $chat_messages; public $plugins; public $settings; public $titles; public $masteradmin_list; public $admin_list; public $admin_abilities; public $operator_list; public $operator_abilities; public $banned_ips; public $uptime; // UASECO start-up time public $startup_phase; // UASECO start-up phase public $shutdown_phase; // UASECO shutdown phase public $warmup_phase; // warm-up phase public $restarting; // restarting map (true or false) public $changing_to_gamemode; public $current_status; // server status changes public $characters; public $environments = array( 'Canyon', 'Stadium', 'Valley', 'Lagoon', ); private $next_second; private $next_tenth; private $next_quarter; private $next_minute; /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Initializes the server. public function __construct () { // Error function, report errors in a regular way. $this->logfile['handle'] = false; set_error_handler(array($this, 'customErrorHandler')); register_shutdown_function(array($this, 'customFatalErrorShutdownHandler')); // Setup logfile $this->setupLogfile(); $this->console('###############################################################################'); $this->console('[UASECO] Initializing...'); if (version_compare(PHP_VERSION, MIN_PHP_VERSION, '<')) { $this->console('[ERROR] UASECO requires min. PHP/'. MIN_PHP_VERSION .' and can not run with current PHP/'. PHP_VERSION .', please update PHP!'); die(); } $extensions = array( array( 'required' => false, 'extension' => 'exif', 'name' => 'Exchangeable Image Information', 'link' => 'http://php.net/manual/en/book.exif.php', ), array( 'required' => true, 'extension' => 'ftp', 'name' => 'File Transfer Protocol', 'link' => 'http://php.net/manual/en/book.ftp.php', ), array( 'required' => true, 'extension' => 'iconv', 'name' => 'ICONV character set conversion facility', 'link' => 'http://php.net/manual/en/book.iconv.php', ), array( 'required' => true, 'extension' => 'gd', 'name' => 'Image Processing and GD', 'link' => 'http://php.net/manual/en/book.image.php', ), array( 'required' => true, 'extension' => 'json', 'name' => 'JavaScript Object Notation', 'link' => 'http://php.net/manual/en/book.json.php', ), array( 'required' => true, 'extension' => 'libxml', 'name' => 'LibXML', 'link' => 'http://php.net/manual/en/book.libxml.php', ), array( 'required' => true, 'extension' => 'mbstring', 'name' => 'Multibyte String', 'link' => 'http://php.net/manual/en/book.mbstring.php', ), array( 'required' => true, 'extension' => 'mysqli', 'name' => 'MySQL Improved', 'link' => 'http://php.net/manual/en/book.mysqli.php', ), array( 'required' => true, 'extension' => 'SimpleXML', 'name' => 'SimpleXML', 'link' => 'http://php.net/manual/en/book.simplexml.php', ), ); $this->console('[PHP] Checking for required and wanted PHP extensions...'); $pass = true; foreach ($extensions as $item) { $found = extension_loaded($item['extension']); $msg = '[PHP] » Checking "'. $item['name'] .'" ('. $item['extension'] .'): '; if ($found === false && $item['required'] == true) { $msg .= 'extension not loaded, it is REQUIRED to enable this extension.. See "'. $item['link'] .'" for installation details.'; $pass = false; } else if ($found === false && $item['required'] == false) { $msg .= 'extension not loaded, it is RECOMMENDED to enable this extension. See "'. $item['link'] .'" for installation details.'; } else if ($found === true) { $msg .= 'OK!'; } $this->console($msg); } if ($pass === false) { $this->console('[PHP] » Please enable the required PHP extensions and try again!'); die(); } // Extended characters list from askuri: https://forum.maniaplanet.com/viewtopic.php?p=266201#p266201 $this->characters = array( 'a' => explode(' ', '@ 4 À Á Â Ã Ä Å ª à á â ã ä å ƛ Ǎ ǎ Ǟ ǟ Ǻ ǻ Ā ā Ă ă Ą ą А Д а д Ѧ ѧ ג Ά Α Δ Λ ά α λ'), 'b' => explode(' ', 'Þ ß þ ƀ Б В Ъ Ь в ь ъ ѣ Ѣ Β β ϐ'), 'c' => explode(' ', '¢ © Ç ç Ć ć Ĉ ĉ Ċ ċ Č č С с Ҁ ҁ Ҫ ҫ ζ ς'), 'd' => explode(' ', 'Ð ð Ď ď Đ đ δ の פ'), 'e' => explode(' ', 'È Ê É Ë è é ê ë Ə Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ё Є Е е ё є Ҽ ҽ Ҿ ҿ Έ Ε Ξ Σ έ ε ξ ミ ɛ ϵ'), 'f' => explode(' ', 'Ƒ ƒ Ŧ Ғ ғ'), 'g' => explode(' ', 'Ǥ Ǧ ǥ ǧ ǵ Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ'), 'h' => explode(' ', 'Ĥ ĥ Ħ ħ Н Ч н ч ђ ћ Ң ң Ҥ ҥ Һ һ Ӈ ӈ Ή Η'), 'i' => explode(' ', 'Ì Í Î Ï ì í î ï Ǐ ǐ ǰ Ĩ ĩ Ī ī Ĭ ĭ Į į İ ı ĺ ļ ľ ŀ ł І Ї ї і ׀ Ί ΐ Ι Ϊ ί ι ϊ エ エ'), 'j' => explode(' ', 'ǰ ĵ Ĵ Ј ј'), 'k' => explode(' ', 'Ǩ ǩ ĸ ķ Ķ Ќ К к ќ Қ қ Ҝ ҝ Ҟ ҟ Ҡ ҡ Ӄ ӄ Κ κ'), 'l' => explode(' ', 'Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł І ׀ し じ ム レ'), 'm' => explode(' ', 'М м Μ'), 'n' => explode(' ', 'Ñ ñ Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Й И Л П и й л п ה ח מ ת Ν ή η'), 'o' => explode(' ', 'Ò Ó Ô Õ Ö Ø ð ò ó ô õ ö ø Ɵ Ơ ơ Ǒ ǒ ǫ Ǫ Ǭ ǭ Ǿ ǿ Ō ō Ŏ ŏ Ő ő Ф О о Ѳ ѳ Ѻ ѻ ט ס Ό Ώ Θ Ο Φ Ω θ ο σ φ ό ϕ 〇 °'), 'p' => explode(' ', 'Þ þ Р р ק Ρ ρ ア ァ ヤ ャ'), 'q' => explode(' ', 'Ǫ ǫ Ǭ ǭ'), 'r' => explode(' ', '® Ŕ ŕ Ŗ ŗ Ř ř Ѓ Г Я г я ѓ Γ'), 's' => explode(' ', '§ Ś ś Ŝ ŝ Ş ş Š š Ѕ ѕ ς'), 't' => explode(' ', 'Ɨ ƚ ƫ Ʈ ł Ţ ţ Ť ť ŧ Т т Ҭ ҭ ד Τ τ て 〒 〶 ィ イ'), 'u' => explode(' ', 'Ù Ú Û Ü ù ú û ü ý Ư ư Ǔ ǔ Ǖ ǖ Ǘ ǘ Ǚ ǚ ǜ Ǜ IJ Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Џ Ц ц ט ΰ μ υ ϋ ύ ひ び ぴ'), 'v' => explode(' ', 'Ѵ ѵ Ѷ ѷ ν'), 'w' => explode(' ', 'Ŵ ŵ Ш Щ ш щ Ѡ ѡ Ѽ ѽ Ѿ ѿ ω ώ ϖ'), 'x' => explode(' ', '× æ ǣ ǽ Ж Х ж х Җ җ Ҳ ҳ Ӂ ӂ Χ χ メ'), 'y' => explode(' ', '¥ Ý ÿ Ŷ ŷ Ÿ Ў У у ў Ү ү Ұ ұ ע ץ Ύ Ϋ γ ϒ ϓ ϔ'), 'z' => explode(' ', 'Ƶ ƶ Ʒ Ǯ ǯ Ź ź Ż ż Ž ž Ζ'), '0' => explode(' ', 'º ʘ'), '1' => explode(' ', '¹'), '2' => explode(' ', '²'), '3' => explode(' ', '³ Ʒ Ǯ ǯ З Э з Ѯ ѯ Ҙ ҙ ɝ ɜ ヨ ョ ʒ ʓ ϶'), '4' => explode(' ', 'Ч ч'), '6' => explode(' ', 'б'), ); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Runs the server. public function run () { // Initialize $this->debug = false; $this->uptime = time(); $this->registered_events = array(); $this->registered_chatcmds = array(); $this->locales = new Locales(); $this->client = new GbxRemote(); // includes/core/XmlRpc/GbxRemote.php $this->parser = new XmlParser(); $this->webrequest = new WebRequest(); $this->continent = new Continent(); $this->country = new Country(); $this->server = new Server('127.0.0.1', 5000, 'SuperAdmin', 'SuperAdmin'); $this->windows = new WindowList($this); $this->plugins = array(); $this->titles = array(); $this->masteradmin_list = array(); $this->admin_list = array(); $this->admin_abilities = array(); $this->operator_list = array(); $this->operator_abilities = array(); $this->banned_ips = array(); $this->startup_phase = true; $this->shutdown_phase = false; $this->warmup_phase = false; $this->restarting = false; $this->changing_to_gamemode = false; $this->current_status = 0; // Setup config file $config_file = 'config/UASECO.xml'; // Load new settings, if available $this->console('[Config] Load settings [{1}]', $config_file); $this->loadSettings($config_file); // Initialize further $this->server->maps = new MapList($this->debug, $this->settings['developer']['force_maplist_update']); $this->server->maps->history = new MapHistory($this->debug, $this->settings['max_history_entries']); $this->server->maps->playlist = new PlayList($this->debug); $this->server->players = new PlayerList($this->debug); $this->server->rankings = new RankingList($this->debug); $this->server->mutelist = array(); // Load admin/operator/ability lists, if available $this->console('[Config] Load admin and operator lists [{1}]', $this->settings['adminops_file']); $this->readLists(); // Load banned IPs list, if available $this->console('[Config] Load banned IPs list [{1}]', $this->settings['bannedips_file']); $this->readIPs(); // Setup PHP memory_limit $limit = $this->shorthand2bytes(ini_get('memory_limit')); if ($limit != -1) { ini_set('memory_limit', $this->settings['memory_limit']); } $limit = $this->shorthand2bytes(ini_get('memory_limit')); if ($limit != -1 && $limit < 256 * 1048576) { ini_set('memory_limit', '256M'); } $limit = $this->shorthand2bytes(ini_get('memory_limit')); if ($limit == -1) { $this->console('[PHP] Setup memory limit to unlimited'); } else { $this->console('[PHP] Setup memory limit to '. $this->bytes2shorthand($limit, 'M')); } // Setup PHP script_timeout @set_time_limit($this->settings['script_timeout']); $this->console('[PHP] Setup script timeout to '. $this->settings['script_timeout'] .' second'. ($this->settings['script_timeout'] == 1 ? '' : 's')); // Connect to Trackmania Dedicated Server if (!$this->connectDedicated()) { // kill program with an error trigger_error('[Dedicated] ...connection could not be established!', E_USER_ERROR); } // Log status message $this->console('[Dedicated] ...connection established successfully!'); // Clear possible leftover ManiaLinks $this->client->query('SendHideManialinkPage'); // Connect to the database $this->displayLoadStatus('Connecting to database...', 0.0); if ($this->settings['mask_password'] == true) { $this->console("[Database] Try to connect to database server on [{1}] with database [{2}], login [{3}] and password [{4}] (masked password)", $this->settings['dbms']['host'], $this->settings['dbms']['database'], $this->settings['dbms']['login'], preg_replace('#.#', '*', $this->settings['dbms']['password']) ); } else { $this->console("[Database] Try to connect to database server on [{1}] with database [{2}], login [{3}] and password [{4}]", $this->settings['dbms']['host'], $this->settings['dbms']['database'], $this->settings['dbms']['login'], $this->settings['dbms']['password'] ); } $this->connectDatabase(); $this->displayLoadStatus('Connection established successfully!', 0.1); // Check database structure $this->displayLoadStatus('Checking database structure...', 0.15); $this->checkDatabaseStructure(); $this->displayLoadStatus('Structure successfully checked!', 1.0); // Load plugins and register chat commands $this->console('[Plugin] Loading plugins [config/plugins.xml]'); $this->loadPlugins(); // Register own onShutdown() function $this->registerEvent('onShutdown', array($this, 'onShutdown')); // Log admin lock message if ($this->settings['lock_password'] != '') { if ($this->settings['mask_password'] == true) { $this->console('[Config] Locked admin commands and features with password "{1}" (masked password)', preg_replace('#.#', '*', $this->settings['lock_password']) ); } else { $this->console('[Config] Locked admin commands and features with password "{1}"', $this->settings['lock_password'] ); } } // Throw 'starting up' event $this->releaseEvent('onStartup', null); // Synchronize information with server $this->serverSync(); // Make a visual header $this->sendHeader(); // Get current players/servers on the server (hardlimited to 300) $playerlist = $this->client->query('GetPlayerList', 300, 0, 2); // Update players/relays lists if (!empty($playerlist)) { foreach ($playerlist as $player) { // Fake it into thinking it's a connecting Player: // It gets team and ladder info this way and will also throw an // onPlayerConnect event for Players (not relays) to all Plugins $this->playerConnect($player['Login'], false); } } unset($playerlist); // Get current game infos if server loaded a map yet if ($this->current_status == 100) { $this->console('[UASECO] Waiting for the server to start a map...'); } else { $this->loadingMap($this->server->maps->current->uid); } // Startup done $this->startup_phase = false; $this->displayLoadStatus(false); // Main loop while (true) { $starttime = microtime(true); if ($this->shutdown_phase == false) { // Get callbacks from the server $this->executeCallbacks(); // Sends calls to the server $this->executeMulticall(); } // Throw timing events $this->releaseEvent('onMainLoop', null); if (time() >= $this->next_second) { $this->webrequest->update(); $this->next_second = (time() + 1); $this->releaseEvent('onEverySecond', null); } if (time() >= $this->next_tenth) { // Check for Database connection and reconnect on lost connection if ($this->db->ping() == false) { $this->console('[Database] Lost connection, try to reconnect...'); $this->connectDatabase(); } $this->next_tenth = (time() + 10); $this->releaseEvent('onEveryTenSeconds', null); } if (time() >= $this->next_quarter) { $this->next_quarter = (time() + 15); $this->releaseEvent('onEveryFifteenSeconds', null); } if (time() >= $this->next_minute) { $this->next_minute = (time() + 60); $this->releaseEvent('onEveryMinute', 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(); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Initializes the server, loads all server variables // and reads all Maps from server. private function serverSync () { // Get basic server info, server id, login, nickname, zone, name, options, mode, limits... $this->server->getServerSettings(); // Add 'POWERED BY UASECO' to server comment if (strpos($this->stripStyles($this->server->comment, true), 'POWERED BY UASECO') === false) { $this->client->query('SetServerComment', $this->server->comment ."\n". '$Z$O$FFFPOWERED BY $0AF$L['. UASECO_WEBSITE .']'. UASECO_NAME .'$L'); } // Check server build if (strlen($this->server->build) == 0 || ($this->server->game == 'ManiaPlanet' && strcmp($this->server->build, MANIAPLANET_BUILD) < 0)) { trigger_error("Obsolete server build '". $this->server->build ."' - must be at least '". MANIAPLANET_BUILD ."'!", E_USER_ERROR); } // Create a USER_AGENT string define('USER_AGENT', UASECO_NAME .'/'. UASECO_VERSION .'_'. UASECO_BUILD .' '. $this->server->game .'/'. $this->server->build .' php/'. phpversion() .' '. php_uname()); // Get status $status = $this->client->query('GetStatus'); $this->current_status = $status['Code']; unset($status); // Get all Maps from server $this->console('[MapList] Reading complete map list from server...'); $this->server->maps->readMapList(); $count = count($this->server->maps->map_list); $this->console('[MapList] ...successfully done, read '. $count .' map'. ($count == 1 ? '' : 's') .' which matches server settings!'); // Load MapHistory $this->console('[Playlist] Reading map history...'); $this->server->maps->history->readMapHistory(); $this->console('[Playlist] ...successfully done!'); // Throw 'synchronisation' event $this->releaseEvent('onSync', null); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Sends program header to console and ingame chat. private function sendHeader () { $max_execution_time = ini_get('max_execution_time') .' second'. (ini_get('max_execution_time') == 1 ? '' : 's'); $wrappers = stream_get_wrappers(); sort($wrappers, SORT_STRING); $gd = gd_info(); $this->console_text('####[UASECO]#########################################################################'); $this->console_text('» Server: {1} ({2}), join link: "maniaplanet://#join={3}@{4}"', $this->stripStyles($this->server->name, false), $this->server->login, $this->server->login, $this->server->title); if ($this->server->isrelay) { $this->console_text('=> Relays: {1} - {2}', $this->stripStyles($this->server->relaymaster['NickName'], false), $this->server->relaymaster['Login']); } $this->console_text('» Title: {1}', $this->server->title); $this->console_text('» Gamemode: "{1}" with script "{2}" version "{3}"', str_replace('_', '', $this->server->gameinfo->getModeName()), $this->server->gameinfo->getModeScriptName(), $this->server->gameinfo->getModeVersion()); $this->console_text('» Dedicated: {1}/{2} build {3}, using Method-API {4}, ModeScript-API {5}', $this->server->game, $this->server->version, $this->server->build, $this->server->api_version, MODESCRIPT_API_VERSION); $this->console_text('» MatchSettings: {1}', $this->settings['default_maplist']); $this->console_text('» Ports: Connections {1}, P2P {2}, XmlRpc {3}', $this->server->port, $this->server->p2pport, $this->server->xmlrpc['port']); $this->console_text('» Network: Send {1} KB, Receive {2} KB', $this->formatNumber($this->server->networkstats['TotalSendingSize'],0,',','.'), $this->formatNumber($this->server->networkstats['TotalReceivingSize'],0,',','.')); $this->console_text('» Uptime: {1}', $this->timeString($this->server->networkstats['Uptime'])); $this->console_text('» -----------------------------------------------------------------------------------'); $this->console_text('» UASECO: Version {1} build {2}, running on {3}:{4}', UASECO_VERSION, UASECO_BUILD, $this->server->xmlrpc['ip'], $this->server->xmlrpc['port'] .','); $this->console_text('» based upon the work of the authors and projects of:'); $this->console_text('» - Xymph (XAseco2),'); $this->console_text('» - Florian Schnell, AssemblerManiac and many others (ASECO),'); $this->console_text('» - Kremsy (MPASECO)'); $this->console_text('» Author: undef.de (UASECO)'); $this->console_text('» Website: {1}', UASECO_WEBSITE); $this->console_text('» -----------------------------------------------------------------------------------'); $this->console_text('» OS: {1}', php_uname()); $this->console_text('» -----------------------------------------------------------------------------------'); $this->console_text('» PHP: PHP/{1}', phpversion()); $this->console_text('» MemoryLimit: {1}', ini_get('memory_limit')); $this->console_text('» MaxExecutionTime: {1}', $max_execution_time); $this->console_text('» AllowUrlFopen: {1}', $this->bool2string((ini_get('allow_url_fopen') == 1 ? true : false))); $this->console_text('» Streams: {1}', implode(', ', $wrappers)); $this->console_text('» GD-Lib: Version: {1}, JPEG: {2}, PNG: {3}, FreeType: {4}', $gd['GD Version'], $this->bool2string($gd['JPEG Support']), $this->bool2string($gd['PNG Support']), $this->bool2string($gd['FreeType Support'])); $this->console_text('» -----------------------------------------------------------------------------------'); $this->console_text('» Database: Server: {1}', $this->db->server_version()); $this->console_text('» Client: {1}', $this->db->client_version()); $this->console_text('» Connect: {1}', $this->db->connection_info()); $this->console_text('» Status: {1}', $this->db->host_status()); $this->console_text('#####################################################################################'); // Format the text of the message $message = $this->formatText($this->getChatMessage('STARTUP'), UASECO_VERSION, UASECO_BUILD, $this->server->xmlrpc['ip'], $this->server->xmlrpc['port'] ); // Show startup message $this->sendChatMessage($message); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ private function logDebugInformations () { $max_execution_time = ini_get('max_execution_time') .' second'. (ini_get('max_execution_time') == 1 ? '' : 's'); $wrappers = stream_get_wrappers(); sort($wrappers, SORT_STRING); $gd = gd_info(); $this->console_text('####[DEBUG]##########################################################################'); $this->console_text('» StartupPhase: {1}', $this->bool2string($this->startup_phase)); $this->console_text('» WarmupPhase: {1}', $this->bool2string($this->warmup_phase)); $this->console_text('» Restarting: {1}', $this->bool2string($this->restarting)); $this->console_text('» CurrentStatus: [{1}] {2}', $this->current_status, $this->server->state_names[$this->current_status]); $this->console_text('» -----------------------------------------------------------------------------------'); $this->console_text('» Uptime: {1}', $this->timeString(time() - $this->uptime)); if ( function_exists('sys_getloadavg') ) { $this->console_text('» System Load: {1}', implode(', ', sys_getloadavg())); } $this->console_text('» MEM-Usage: {1} MB, PEAK: {2} MB', round(memory_get_usage() / pow(1024,2), 4), round(memory_get_peak_usage() / pow(1024,2), 4)); if (function_exists('posix_getuid') && function_exists('posix_getgid')) { $this->console_text('» Process user: {1}:{2} (UID/GID)', posix_getuid(), posix_getgid()); } $this->console_text('» Script owner: {1}:{2} (UID/GID)', getmyuid(), getmygid()); $this->console_text('» -----------------------------------------------------------------------------------'); // $this->console_text('» NbPlugins: {1} : {2} bytes', sprintf("%5s", count($this->plugins)), sprintf("%10s", $this->formatNumber(strlen(serialize($this->plugins)),0,'.','.'))); // serialize() on SimpleXMLElement are bad // $this->console_text('» RegEvents: {1} : {2} bytes', sprintf("%5s", count($this->registered_events)), sprintf("%10s", $this->formatNumber(strlen(serialize($this->registered_events)),0,'.','.'))); // serialize() on SimpleXMLElement are bad // $this->console_text('» RegChatCmds: {1} : {2} bytes', sprintf("%5s", count($this->registered_chatcmds)), sprintf("%10s", $this->formatNumber(strlen(serialize($this->registered_chatcmds)),0,'.','.'))); // serialize() on SimpleXMLElement are bad $this->console_text('» NbPlugins: {1}', sprintf("%5s", count($this->plugins))); $this->console_text('» RegEvents: {1}', sprintf("%5s", count($this->registered_events))); $this->console_text('» RegChatCmds: {1}', sprintf("%5s", count($this->registered_chatcmds))); $this->console_text('» NbMaps: {1} : {2} bytes', sprintf("%5s", $this->server->maps->count()), sprintf("%10s", $this->formatNumber(strlen(serialize($this->server->maps->map_list)),0,'.','.'))); $this->console_text('» NbPlayers: {1} : {2} bytes', sprintf("%5s", $this->server->players->count()), sprintf("%10s", $this->formatNumber(strlen(serialize($this->server->players->player_list)),0,'.','.'))); $this->console_text('» PlayerRanks: {1} : {2} bytes', sprintf("%5s", $this->server->rankings->count()), sprintf("%10s", $this->formatNumber(strlen(serialize($this->server->rankings->ranking_list)),0,'.','.'))); $this->console_text('» -----------------------------------------------------------------------------------'); $this->console_text('» Server: {1} ({2}), join link: "maniaplanet://#join={3}@{4}"', $this->stripStyles($this->server->name, false), $this->server->login, $this->server->login, $this->server->title); if ($this->server->isrelay) { $this->console_text('=> Relays: {1} - {2}', $this->stripStyles($this->server->relaymaster['NickName'], false), $this->server->relaymaster['Login']); } $this->console_text('» Title: {1}', $this->server->title); $this->console_text('» Gamemode: "{1}" with script "{2}" version "{3}"', str_replace('_', '', $this->server->gameinfo->getModeName()), $this->server->gameinfo->getModeScriptName(), $this->server->gameinfo->getModeVersion()); $this->console_text('» Dedicated: {1}/{2} build {3}, using Method-API {4}, ModeScript-API {5}', $this->server->game, $this->server->version, $this->server->build, $this->server->api_version, MODESCRIPT_API_VERSION); $this->console_text('» MatchSettings: {1}', $this->settings['default_maplist']); $this->console_text('» Ports: Connections {1}, P2P {2}, XmlRpc {3}', $this->server->port, $this->server->p2pport, $this->server->xmlrpc['port']); $this->console_text('» Network: Send {1} KB, Receive {2} KB', $this->formatNumber($this->server->networkstats['TotalSendingSize'],0,',','.'), $this->formatNumber($this->server->networkstats['TotalReceivingSize'],0,',','.')); $this->console_text('» Uptime: {1}', $this->timeString($this->server->networkstats['Uptime'])); $this->console_text('» -----------------------------------------------------------------------------------'); $this->console_text('» OS: {1}', php_uname()); $this->console_text('» -----------------------------------------------------------------------------------'); $this->console_text('» PHP: PHP/{1}', phpversion()); $this->console_text('» MemoryLimit: {1}', ini_get('memory_limit')); $this->console_text('» MaxExecutionTime: {1}', $max_execution_time); $this->console_text('» AllowUrlFopen: {1}', $this->bool2string((ini_get('allow_url_fopen') == 1 ? true : false))); $this->console_text('» Streams: {1}', implode(', ', $wrappers)); $this->console_text('» GD-Lib: Version: {1}, JPEG: {2}, PNG: {3}, FreeType: {4}', $gd['GD Version'], $this->bool2string($gd['JPEG Support']), $this->bool2string($gd['PNG Support']), $this->bool2string($gd['FreeType Support'])); $this->console_text('» -----------------------------------------------------------------------------------'); $this->console_text('» Database: Server: {1}', $this->db->server_version()); $this->console_text('» Client: {1}', $this->db->client_version()); $this->console_text('» Connect: {1}', $this->db->connection_info()); $this->console_text('» Status: {1}', $this->db->host_status()); $this->console_text('#####################################################################################'); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ private function logDebugPluginUsage ($list) { $this->console_text('#### DEBUG ##########################################################################'); $this->console_text('» Plugin memory usage on initialization:'); foreach ($list as $plugin => $usage) { $this->console_text('» {1} {2} bytes', str_pad('['.$plugin.']', 30, ' ', STR_PAD_RIGHT), str_pad($this->formatNumber($usage, 0, '.', '.'), 15, ' ', STR_PAD_LEFT)); } $this->console_text('#####################################################################################'); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Load settings and apply them on the current instance. private function loadSettings ($config_file) { if ($settings = $this->parser->xmlToArray($config_file, true, true)) { // read the XML structure into an array $settings = $settings['SETTINGS']; // Read settings and apply them $this->debug = $this->string2bool($settings['DEVELOPER_OPTIONS'][0]['DEBUG'][0]); $this->settings['developer']['log_events']['common'] = $this->string2bool($settings['DEVELOPER_OPTIONS'][0]['LOG_EVENTS'][0]['COMMON'][0]); $this->settings['developer']['log_events']['registered_types'] = $this->string2bool($settings['DEVELOPER_OPTIONS'][0]['LOG_EVENTS'][0]['REGISTERED_TYPES'][0]); $this->settings['developer']['log_events']['all_types'] = $this->string2bool($settings['DEVELOPER_OPTIONS'][0]['LOG_EVENTS'][0]['ALL_TYPES'][0]); $this->settings['developer']['force_maplist_update'] = $this->string2bool($settings['DEVELOPER_OPTIONS'][0]['FORCE_MAPLIST_UPDATE'][0]); $this->settings['developer']['write_documentation'] = $this->string2bool($settings['DEVELOPER_OPTIONS'][0]['WRITE_DOCUMENTATION'][0]); // Read settings and apply them $this->chat_colors = $settings['COLORS'][0]; $this->chat_messages = $settings['MESSAGES'][0]; $this->masteradmin_list = $settings['MASTERADMINS'][0]; if (!isset($this->masteradmin_list) || !is_array($this->masteradmin_list)) { trigger_error('No MasterAdmin(s) configured in [config/UASECO.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 contact $this->settings['admin_contact'] = $settings['ADMIN_CONTACT'][0]; if (strtolower($this->settings['admin_contact']) == 'your@email.com' || filter_var($this->settings['admin_contact'], FILTER_VALIDATE_EMAIL) === false) { $this->console('[UASECO][WARNING] ###############################################################################################################'); $this->console('[UASECO][WARNING] You should setup a working mail to be able to contact you, change it in [config/UASECO.xml] at !'); $this->console('[UASECO][WARNING] ###############################################################################################################'); } // Set admin lock password $this->settings['lock_password'] = $settings['LOCK_PASSWORD'][0]; if (empty($this->settings['lock_password'])) { $this->console('[UASECO][WARNING] To increase security you should setup a lock password at in [config/UASECO.xml]!'); } // Show played time at end of map? $this->settings['show_playtime'] = $settings['SHOW_PLAYTIME'][0]; // Show current map at start? $this->settings['show_curmap'] = $settings['SHOW_CURMAP'][0]; // Set default filename for readmaplist/writemaplist $this->settings['default_maplist'] = $settings['DEFAULT_MAPLIST'][0]; // Add random filter to /admin writemaplist output $this->settings['writemaplist_random'] = $this->string2bool($settings['WRITEMAPLIST_RANDOM'][0]); // Automatic refresh of the maplist when the callback "ManiaPlanet.MapListModified" is received $this->settings['automatic_refresh_maplist'] = $this->string2bool($settings['AUTOMATIC_REFRESH_MAPLIST'][0]); // Specifies how large the Map(List) history buffer is. $this->settings['max_history_entries'] = (int)$settings['MAX_HISTORY_ENTRIES'][0]; // Sets the minimum amount of records required for a player to be ranked: Higher = Faster $this->settings['server_rank_min_records'] = (int)$settings['SERVER_RANK_MIN_RECORDS'][0]; // Setup default storing path for the map images $this->settings['mapimages_path'] = $settings['MAPIMAGES_PATH'][0]; if ((OPERATING_SYSTEM == 'POSIX' && substr($this->settings['mapimages_path'], -1) != '/') || (OPERATING_SYSTEM == 'WINDOWS' && substr($this->settings['mapimages_path'], -1) != '\\')) { $this->console('[Config] Adding missing trailing "'. DIRECTORY_SEPARATOR .'" from [config/UASECO.xml]!'); $this->settings['mapimages_path'] = $this->settings['mapimages_path'] . DIRECTORY_SEPARATOR; } // Set multiple of win count to show global congrats message $this->settings['global_win_multiple'] = ($settings['GLOBAL_WIN_MULTIPLE'][0] > 0 ? $settings['GLOBAL_WIN_MULTIPLE'][0] : 1); // Timeout of the message window in seconds $this->settings['window_timeout'] = $settings['WINDOW_TIMEOUT'][0]; // Set filename of admin/operator/ability lists file $this->settings['adminops_file'] = $settings['ADMINOPS_FILE'][0]; // Set filename of banned IPs list file $this->settings['bannedips_file'] = $settings['BANNEDIPS_FILE'][0]; // Set filename of blacklist file $this->settings['blacklist_file'] = $settings['BLACKLIST_FILE'][0]; // Set filename of guestlist file $this->settings['guestlist_file'] = $settings['GUESTLIST_FILE'][0]; // Add random filter to /admin writemaplist output $this->settings['writemaplist_random'] = $this->string2bool($settings['WRITEMAPLIST_RANDOM'][0]); // Set minimum admin client version $this->settings['admin_client'] = $settings['ADMIN_CLIENT_VERSION'][0]; // Set minimum player client version $this->settings['player_client'] = $settings['PLAYER_CLIENT_VERSION'][0]; // Log all chat, not just chat commands ? $this->settings['log_all_chat'] = $this->string2bool($settings['LOG_ALL_CHAT'][0]); // Show timestamps in /chatlog, /pmlog & /admin pmlog ? $this->settings['chatpmlog_times'] = $this->string2bool($settings['CHATPMLOG_TIMES'][0]); // Show round reports in message window? $this->settings['rounds_in_window'] = $this->string2bool($settings['ROUNDS_IN_WINDOW'][0]); // Color nicknames in the various /top... etc lists? $this->settings['lists_colornicks'] = $this->string2bool($settings['LISTS_COLORNICKS'][0]); // Color mapnames in the various /lists... lists? $this->settings['lists_colormaps'] = $this->string2bool($settings['LISTS_COLORMAPS'][0]); // Automatically add IP for new admins/operators? $this->settings['auto_admin_addip'] = $this->string2bool($settings['AUTO_ADMIN_ADDIP'][0]); // Automatically force spectator on player using /afk ? $this->settings['afk_force_spec'] = $this->string2bool($settings['AFK_FORCE_SPEC'][0]); // Provide clickable buttons in lists? $this->settings['clickable_lists'] = $this->string2bool($settings['CLICKABLE_LISTS'][0]); // Show logins in /recs? $this->settings['show_rec_logins'] = $this->string2bool($settings['SHOW_REC_LOGINS'][0]); // Set stripling path $this->settings['stripling_path'] = $settings['STRIPLING_PATH'][0]; // Set dedicated Server installation path $this->settings['dedicated_installation'] = $settings['DEDICATED_INSTALLATION'][0]; if (strtoupper($this->settings['dedicated_installation']) == 'PATH_TO_DEDICATED_SERVER' || empty($this->settings['dedicated_installation'])) { trigger_error('Please setup in [config/UASECO.xml]!', E_USER_ERROR); } if ((OPERATING_SYSTEM == 'POSIX' && substr($this->settings['dedicated_installation'], -1) != '/') || (OPERATING_SYSTEM == 'WINDOWS' && substr($this->settings['dedicated_installation'], -1) != '\\')) { $this->console('[Config] Adding missing trailing "'. DIRECTORY_SEPARATOR .'" from [config/UASECO.xml]!'); $this->settings['dedicated_installation'] = $this->settings['dedicated_installation'] . DIRECTORY_SEPARATOR; } // Log passwords in logfile? $this->settings['mask_password'] = $this->string2bool($settings['MASK_PASSWORD'][0]); $this->settings['show_load_status'] = $this->string2bool($settings['SHOW_LOAD_STATUS'][0]); // PHP related stuff $this->settings['script_timeout'] = $settings['SCRIPT_TIMEOUT'][0]; $this->settings['memory_limit'] = $settings['MEMORY_LIMIT'][0]; // Read settings and apply them $this->settings['dbms']['host'] = $settings['DBMS'][0]['HOST'][0]; $this->settings['dbms']['login'] = $settings['DBMS'][0]['LOGIN'][0]; $this->settings['dbms']['password'] = $settings['DBMS'][0]['PASSWORD'][0]; $this->settings['dbms']['database'] = $settings['DBMS'][0]['DATABASE'][0]; $this->settings['dbms']['table_prefix'] = $settings['DBMS'][0]['TABLE_PREFIX'][0]; if (empty($this->settings['dbms']['table_prefix'])) { $this->settings['dbms']['table_prefix'] = 'uaseco_'; } // Read settings and apply them $this->server->xmlrpc['login'] = $settings['DEDICATED_SERVER'][0]['LOGIN'][0]; $this->server->xmlrpc['pass'] = $settings['DEDICATED_SERVER'][0]['PASSWORD'][0]; $this->server->xmlrpc['port'] = $settings['DEDICATED_SERVER'][0]['PORT'][0]; $this->server->xmlrpc['ip'] = $settings['DEDICATED_SERVER'][0]['IP'][0]; if (isset($settings['DEDICATED_SERVER'][0]['TIMEOUT'][0])) { $this->server->timeout = (int)$settings['DEDICATED_SERVER'][0]['TIMEOUT'][0]; } else { $this->server->timeout = null; trigger_error('Server init timeout not specified in [config/UASECO.xml]!', E_USER_WARNING); } 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); } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Loads files in the plugins directory. private function loadPlugins () { // Debug $plugin_usage = array(); $this->displayLoadStatus('Loading Plugins...', 0.0); // Load and parse the plugins file if ($plugins = $this->parser->xmlToArray('config/plugins.xml')) { if (!empty($plugins['PLUGINS']['PLUGIN'])) { // Take each plugin tag $count = 0; $amount = count($plugins['PLUGINS']['PLUGIN']); foreach ($plugins['PLUGINS']['PLUGIN'] as $plugin) { // Reset plugin variables... useless, but removes warning! $_PLUGIN = null; unset($_PLUGIN); // Debug $usage_before = memory_get_usage(); // Log plugin message $this->console('[Plugin] » initialize [plugins/'. $plugin .']'); // Include the plugin require_once('plugins/'. $plugin); // Load only plugins that were configured right... if (!isset($_PLUGIN) || get_parent_class($_PLUGIN) != 'Plugin') { trigger_error('require_once() does not load the file [plugins/'. $plugin .'] from position '. ($count + 1) .', which means that this Plugin is probably an old version or it is added twice at [config/plugins.xml]!', E_USER_WARNING); continue; } $count ++; $ratio = (1.0 / $amount) * $count; $this->displayLoadStatus('Loading Plugin '. $_PLUGIN->getClassname() .'...', $ratio); // Store filename from plugin $_PLUGIN->setFilename($plugin); // Register plugin... $this->plugins[$_PLUGIN->getClassname()] = $_PLUGIN; // Debug $usage_after = memory_get_usage(); $plugin_usage[$_PLUGIN->getClassname()] = ($usage_after - $usage_before); } $this->displayLoadStatus('Plugins successfully initialized.', 1.0); // Check if all plugins are working right... $this->checkDependencies(); // Now register events and chat-commands $this->console('[Plugin] Registering events and chat commands...'); $this->displayLoadStatus('Registering events and chat commands...', 0.0); $count = 0; $amount = count($this->plugins); foreach ($this->plugins as $plugin) { $count ++; $ratio = (1.0 / $amount) * $count; $this->displayLoadStatus('Registering for '. $plugin->getClassname() .'...', $ratio); // Register events from plugin... foreach ($plugin->getEvents() as $event => $callback) { $this->registerEvent($event, $callback); } // Register chat commands from plugin... foreach ($plugin->getChatCommands() as $command => $cmd) { $this->registerChatCommand($command, $cmd['callback'], $cmd['help'], $cmd['rights'], $cmd['params']); } } $this->console('[Plugin] ...successfully done!'); $this->displayLoadStatus('Registering events and chat commands successfully done.', 1.0); // Log plugin mem. usage if ($this->debug) { $this->logDebugPluginUsage($plugin_usage); } } } else { trigger_error('[Plugin] Could not read/parse plugins list [config/plugins.xml]!', E_USER_ERROR); } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ /** * Checks the included plugins for their dependencies. * */ private function checkDependencies () { // Go through the list of plugins if there are any ... $this->console('[Plugin] Checking dependencies of Plugins...'); $this->displayLoadStatus('Checking dependencies of Plugins...', 0.0); $count = 0; $amount = count($this->plugins); foreach ($this->plugins as $plugin) { $count ++; $ratio = (1.0 / $amount) * $count; $this->displayLoadStatus('Checking dependencies for '. $plugin->getClassname() .'...', $ratio); // Now check the dependencies of each of them ... foreach ($plugin->getDependencies() as $dependence) { // Check for plugin required version? $check_version = false; if (!isset($this->plugins[$dependence->classname]) && $dependence->permissions == Dependence::REQUIRED) { // Check if required dependence exists... trigger_error('[Plugin] The Plugin ['. $plugin->getClassname() .'] requires the Plugin ['. $dependence->classname .'] to run, disclude this Plugin or add the required Plugin in [config/plugins.xml] to continue!', E_USER_ERROR); } else if (!isset($this->plugins[$dependence->classname]) && $dependence->permissions == Dependence::WANTED) { // Check if wanted dependence exists... // $this->console('[Plugin] The Plugin ['. $plugin->getClassname() .'] wants the Plugin ['. $dependence->classname .'] to run full featured, if you want, add the wanted Plugin in [config/plugins.xml].'); } else { $check_version = true; } // Check if disallowed dependence exists... if (isset($this->plugins[$dependence->classname]) && $dependence->permissions == Dependence::DISALLOWED) { trigger_error('[Plugin] The Plugin ['. $plugin->getClassname() .'] can not run together with the plugin ['. $dependence->classname .'], disclude this or the disallowed Plugin in [config/plugins.xml] to continue!', E_USER_ERROR); } if ($check_version == true) { // Check if dependence has min version... if (isset($dependence->min_version) && isset($this->plugins[$dependence->classname]) && $this->versionCheck($this->plugins[$dependence->classname]->getVersion(), $dependence->min_version, '<')) { trigger_error('[Plugin] The Plugin ['. $plugin->getClassname() .'] requires a more recent version of the Plugin ['. $dependence->classname .'] (current version: '. $this->plugins[$dependence->classname]->getVersion() .', expected version: '. $dependence->min_version .')!', E_USER_ERROR); } // Check if dependence is lower than max version... if (isset($dependence->max_version) && isset($this->plugins[$dependence->classname]) && $this->versionCheck($this->plugins[$dependence->classname]->getVersion(), $dependence->max_version, '>')) { trigger_error('[Plugin] The Plugin ['. $plugin->getClassname() .'] requires an older version of the Plugin ['. $dependence->classname .'] (current version: '. $this->plugins[$dependence->classname]->getVersion() .', expected version: '. $dependence->max_version .')!', E_USER_ERROR); } } } } $this->console('[Plugin] ...successfully done!'); $this->displayLoadStatus('Dependencies of Plugins successfully checked.', 1.0); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Registers functions which are called on specific events. public function registerEvent ($event, $callback_function) { // Registers a new event if (is_callable($callback_function)) { $this->registered_events[$event][] = $callback_function; } else { $this->console('[Plugin] Can not register callback Method "'. $callback_function[1] .'()" of class "'. $callback_function[0]->getClassname() .'" for event "'. $event .'", because the Method was not found, ignoring!'); } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Executes the functions which were registered for specified events. public function releaseEvent ($event_type, $callback_param) { // Executes registered event functions, if there are any events for that type if ( !empty($this->registered_events[$event_type]) ) { if ( ($this->settings['developer']['log_events']['registered_types'] == true) && ($this->settings['developer']['log_events']['all_types'] == false) ) { $skip = array( 'onEverySecond', 'onMainLoop', 'onModeScriptCallbackArray', ); if (!in_array($event_type, $skip)) { $this->console('[Event] Releasing "'. $event_type .'"'); } } $caller = false; if ($event_type == 'onPlayerManialinkPageAnswer') { $caller = explode('?', $callback_param[2]); } // For each registered function of this type if ($this->startup_phase == true) { $count = 0; $amount = count($this->registered_events[$event_type]); } foreach ($this->registered_events[$event_type] as $callback_func) { // If function for the specified player connect event can be found if (is_callable($callback_func)) { if ($this->startup_phase == true) { $count ++; $ratio = (1.0 / $amount) * $count; $this->displayLoadStatus('Event '. $event_type .' calls '. get_class($callback_func[0]) .'...', $ratio); } if ($event_type == 'onPlayerManialinkPageAnswer') { $class = get_class($callback_func[0]); if ($class == $caller[0]) { // Parse get parameter and add them... parse_str(str_replace($class.'?', '', $callback_param[2]), $param); // Handle tags and their attributes foreach ($callback_param[3] as $item) { $param[$item['Name']] = $item['Value']; } // ...execute only the plugin that handles this answer! call_user_func($callback_func, $this, $callback_param[1], $param); } } else { // ... execute it! call_user_func($callback_func, $this, $callback_param); } } } } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Register a new chat command public function registerChatCommand ($chat_command, $callback_function, $help, $rights = Player::PLAYERS, $params = array()) { if (is_callable($callback_function)) { $chat_command = strtolower(trim($chat_command)); if (!isset($this->registered_chatcmds[$chat_command])) { $this->registered_chatcmds[$chat_command] = array( 'callback' => $callback_function, 'help' => $help, 'rights' => $rights, 'params' => $params, ); } else { $this->console('[Plugin] » Can not register chat command "/'. $chat_command .'" for class "'. $callback_function[0]->getClassname() .'", because it is already registered to the callback Method "'. $this->registered_chatcmds[$chat_command]['callback'][1] .'()" of class "'. $this->registered_chatcmds[$chat_command]['callback'][0]->getClassname() .'", ignoring!'); } } else { $this->console('[Plugin] » Can not register chat command "/'. $chat_command .'" because callback Method "'. $callback_function[1] .'()" of class "'. $callback_function[0]->getClassname() .'" is not callable, ignoring!'); } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Release a chat command from a plugin public function releaseChatCommand ($command, $login) { if ($player = $this->server->players->getPlayerByLogin($login)) { $chat = array( $player->pid, $player->login, $command, ); $this->playerChat($chat); } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ /** * Returns a Class Plugin object of the given classname. * * @param string $classname * @return Plugin object */ public function getPlugin ($classname) { return $this->plugins[$classname]; } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ private function connectDatabase () { $settings = array( 'host' => $this->settings['dbms']['host'], 'login' => $this->settings['dbms']['login'], 'password' => $this->settings['dbms']['password'], 'database' => $this->settings['dbms']['database'], 'table_prefix' => $this->settings['dbms']['table_prefix'], 'autocommit' => true, 'charset' => 'utf8mb4', 'collate' => 'utf8mb4_unicode_ci', 'debug' => $this->debug, ); // Connect $this->db = new Database($settings); // Check for minimum required version of the Database-Server if (strtolower($this->db->type) == 'mysql') { if (version_compare($this->db->version, MIN_MYSQL_VERSION, '<')) { $this->console('[ERROR] UASECO requires min. MySQL/'. MIN_MYSQL_VERSION .' and can not run with current MySQL/'. $this->db->version .' ('. $this->db->version_full .'), please update MySQL!'); die(); } else { $this->console('[Database] ...connection established successfully to a MySQL/'. $this->db->version .' server!'); } } else if (strtolower($this->db->type) == 'mariadb') { if (version_compare($this->db->version, MIN_MARIADB_VERSION, '<')) { $this->console('[ERROR] UASECO requires min. MariaDB/'. MIN_MARIADB_VERSION .' and can not run with current MariaDB/'. $this->db->version .' ('. $this->db->version_full .'), please update MariaDB!'); die(); } else { $this->console('[Database] ...connection established successfully to a MariaDB/'. $this->db->version .' server!'); } } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ private function checkDatabaseStructure () { $this->console('[Database] Checking database structure:'); // Check for tables $tables = array(); $result = $this->db->query('SHOW TABLES;'); if ($result) { while ($row = $result->fetch_row()) { $tables[] = $row[0]; } $result->free_result(); } $check_step1 = array(); $check_step1['authors'] = in_array($this->settings['dbms']['table_prefix'] .'authors', $tables); $check_step1['maphistory'] = in_array($this->settings['dbms']['table_prefix'] .'maphistory', $tables); $check_step1['maps'] = in_array($this->settings['dbms']['table_prefix'] .'maps', $tables); $check_step1['players'] = in_array($this->settings['dbms']['table_prefix'] .'players', $tables); $check_step1['playlist'] = in_array($this->settings['dbms']['table_prefix'] .'playlist', $tables); $check_step1['rankings'] = in_array($this->settings['dbms']['table_prefix'] .'rankings', $tables); $check_step1['ratings'] = in_array($this->settings['dbms']['table_prefix'] .'ratings', $tables); $check_step1['records'] = in_array($this->settings['dbms']['table_prefix'] .'records', $tables); $check_step1['settings'] = in_array($this->settings['dbms']['table_prefix'] .'settings', $tables); $check_step1['times'] = in_array($this->settings['dbms']['table_prefix'] .'times', $tables); if ($check_step1['authors'] === true) { $this->console('[Database] » Found table `'. $this->settings['dbms']['table_prefix'] .'authors`'); } else { $this->console('[Database] » Missing table `'. $this->settings['dbms']['table_prefix'] .'authors`'); } if ($check_step1['maphistory'] === true) { $this->console('[Database] » Found table `'. $this->settings['dbms']['table_prefix'] .'maphistory`'); } else { $this->console('[Database] » Missing table `'. $this->settings['dbms']['table_prefix'] .'maphistory`'); } if ($check_step1['maps'] === true) { $this->console('[Database] » Found table `'. $this->settings['dbms']['table_prefix'] .'maps`'); } else { $this->console('[Database] » Missing table `'. $this->settings['dbms']['table_prefix'] .'maps`'); } if ($check_step1['players'] === true) { $this->console('[Database] » Found table `'. $this->settings['dbms']['table_prefix'] .'players`'); } else { $this->console('[Database] » Missing table `'. $this->settings['dbms']['table_prefix'] .'players`'); } if ($check_step1['playlist'] === true) { $this->console('[Database] » Found table `'. $this->settings['dbms']['table_prefix'] .'playlist`'); } else { $this->console('[Database] » Missing table `'. $this->settings['dbms']['table_prefix'] .'playlist`'); } if ($check_step1['rankings'] === true) { $this->console('[Database] » Found table `'. $this->settings['dbms']['table_prefix'] .'rankings`'); } else { $this->console('[Database] » Missing table `'. $this->settings['dbms']['table_prefix'] .'rankings`'); } if ($check_step1['ratings'] === true) { $this->console('[Database] » Found table `'. $this->settings['dbms']['table_prefix'] .'ratings`'); } else { $this->console('[Database] » Missing table `'. $this->settings['dbms']['table_prefix'] .'ratings`'); } if ($check_step1['records'] === true) { $this->console('[Database] » Found table `'. $this->settings['dbms']['table_prefix'] .'records`'); } else { $this->console('[Database] » Missing table `'. $this->settings['dbms']['table_prefix'] .'records`'); } if ($check_step1['settings'] === true) { $this->console('[Database] » Found table `'. $this->settings['dbms']['table_prefix'] .'settings`'); } else { $this->console('[Database] » Missing table `'. $this->settings['dbms']['table_prefix'] .'settings`'); } if ($check_step1['times'] === true) { $this->console('[Database] » Found table `'. $this->settings['dbms']['table_prefix'] .'times`'); } else { $this->console('[Database] » Missing table `'. $this->settings['dbms']['table_prefix'] .'times`'); } if ($check_step1['authors'] && $check_step1['maphistory'] && $check_step1['maps'] && $check_step1['players'] && $check_step1['playlist'] && $check_step1['rankings'] && $check_step1['ratings'] && $check_step1['records'] && $check_step1['settings'] && $check_step1['times']) { $this->console('[Database] ...successfully done!'); return; } // Create tables $this->console('[Database] » '. ($check_step1['authors'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'authors`'); $this->displayLoadStatus(($check_step1['authors'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'authors`', 0.2); $query = " CREATE TABLE IF NOT EXISTS `%prefix%authors` ( `AuthorId` mediumint(3) unsigned AUTO_INCREMENT, `Login` varchar(64) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Nickname` varchar(100) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Zone` varchar(256) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Continent` varchar(2) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Nation` varchar(3) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', PRIMARY KEY (`AuthorId`), UNIQUE KEY `Login` (`Login`), KEY `Continent` (`Continent`), KEY `Nation` (`Nation`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci AUTO_INCREMENT=1; "; $this->db->query($query); $this->console('[Database] » '. ($check_step1['maphistory'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'maphistory`'); $this->displayLoadStatus(($check_step1['maphistory'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'maphistory`', 0.25); $query = " CREATE TABLE IF NOT EXISTS `%prefix%maphistory` ( `MapId` mediumint(3) unsigned NOT NULL, `Date` datetime DEFAULT '1970-01-01 00:00:00', KEY `MapId` (`MapId`), KEY `Date` (`Date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; "; $this->db->query($query); $this->console('[Database] » '. ($check_step1['maps'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'maps`'); $this->displayLoadStatus(($check_step1['maps'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'maps`', 0.3); $query = " CREATE TABLE IF NOT EXISTS `%prefix%maps` ( `MapId` mediumint(3) UNSIGNED AUTO_INCREMENT, `Uid` varchar(27) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Filename` text COLLATE 'utf8mb4_unicode_ci', `Name` varchar(100) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Comment` text COLLATE 'utf8mb4_unicode_ci', `AuthorId` mediumint(3) unsigned DEFAULT '0', `AuthorScore` int(4) unsigned DEFAULT '0', `AuthorTime` int(4) unsigned DEFAULT '0', `GoldTime` int(4) unsigned DEFAULT '0', `SilverTime` int(4) unsigned DEFAULT '0', `BronzeTime` int(4) unsigned DEFAULT '0', `Environment` varchar(10) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Mood` enum('unknown','Sunrise','Day','Sunset','Night') COLLATE 'utf8mb4_unicode_ci' NOT NULL, `Cost` mediumint(3) unsigned DEFAULT '0', `Type` varchar(32) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Style` varchar(32) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `MultiLap` enum('false','true') COLLATE 'utf8mb4_unicode_ci' NOT NULL, `NbLaps` tinyint(1) unsigned DEFAULT '0', `NbCheckpoints` tinyint(1) unsigned DEFAULT '0', `Validated` enum('null','false','true') COLLATE 'utf8mb4_unicode_ci' NOT NULL, `ExeVersion` varchar(16) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `ExeBuild` varchar(32) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `ModName` varchar(64) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `ModFile` varchar(256) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `ModUrl` text COLLATE 'utf8mb4_unicode_ci', `SongFile` varchar(256) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `SongUrl` text COLLATE 'utf8mb4_unicode_ci', PRIMARY KEY (`MapId`), UNIQUE KEY `Uid` (`Uid`), KEY `AuthorId` (`AuthorId`), KEY `AuthorScore` (`AuthorScore`), KEY `AuthorTime` (`AuthorTime`), KEY `GoldTime` (`GoldTime`), KEY `SilverTime` (`SilverTime`), KEY `BronzeTime` (`BronzeTime`), KEY `Environment` (`Environment`), KEY `Mood` (`Mood`), KEY `MultiLap` (`MultiLap`), KEY `NbLaps` (`NbLaps`), KEY `NbCheckpoints` (`NbCheckpoints`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci AUTO_INCREMENT=1; "; $this->db->query($query); $this->console('[Database] » '. ($check_step1['players'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'players`'); $this->displayLoadStatus(($check_step1['players'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'players`', 0.35); $query = " CREATE TABLE IF NOT EXISTS `%prefix%players` ( `PlayerId` mediumint(3) unsigned AUTO_INCREMENT, `Login` varchar(64) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Nickname` varchar(100) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Zone` varchar(256) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Continent` varchar(2) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `Nation` varchar(3) COLLATE 'utf8mb4_unicode_ci' DEFAULT '', `LastVisit` datetime DEFAULT '1970-01-01 00:00:00', `Visits` mediumint(3) unsigned DEFAULT '0', `Wins` mediumint(3) unsigned DEFAULT '0', `Donations` mediumint(3) unsigned DEFAULT '0', `TimePlayed` int(4) unsigned DEFAULT '0', PRIMARY KEY (`PlayerId`), UNIQUE KEY `Login` (`Login`), KEY `Continent` (`Continent`), KEY `Nation` (`Nation`), KEY `LastVisit` (`LastVisit`), KEY `Visits` (`Visits`), KEY `Wins` (`Wins`), KEY `Donations` (`Donations`), KEY `TimePlayed` (`TimePlayed`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci AUTO_INCREMENT=1; "; $this->db->query($query); $this->console('[Database] » '. ($check_step1['playlist'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'playlist`'); $this->displayLoadStatus(($check_step1['playlist'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'playlist`', 0.4); $query = " CREATE TABLE IF NOT EXISTS `%prefix%playlist` ( `Timestamp` decimal(17,3) unsigned DEFAULT '0.000', `MapId` mediumint(3) unsigned DEFAULT '0', `PlayerId` mediumint(3) unsigned DEFAULT '0', `Method` enum('select','vote','pay','add') COLLATE 'utf8mb4_unicode_ci' DEFAULT 'select', KEY `Timestamp` (`Timestamp`), KEY `MapId` (`MapId`), KEY `PlayerId` (`PlayerId`), KEY `Method` (`Method`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; "; $this->db->query($query); $this->console('[Database] » '. ($check_step1['rankings'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'rankings`'); $this->displayLoadStatus(($check_step1['rankings'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'rankings`', 0.45); $query = " CREATE TABLE IF NOT EXISTS `%prefix%rankings` ( `PlayerId` mediumint(3) unsigned DEFAULT '0', `Average` int(4) unsigned DEFAULT '0', PRIMARY KEY (`PlayerId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; "; $this->db->query($query); $this->console('[Database] » '. ($check_step1['ratings'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'ratings`'); $this->displayLoadStatus(($check_step1['ratings'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'ratings`', 0.5); $query = " CREATE TABLE IF NOT EXISTS `%prefix%ratings` ( `MapId` mediumint(3) unsigned DEFAULT '0', `PlayerId` mediumint(3) unsigned DEFAULT '0', `Date` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `Score` tinyint(1) signed DEFAULT '0', PRIMARY KEY (`MapId`,`PlayerId`), KEY `MapId` (`MapId`), KEY `PlayerId` (`PlayerId`), KEY `Date` (`Date`), KEY `Score` (`Score`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; "; $this->db->query($query); $this->console('[Database] » '. ($check_step1['records'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'records`'); $this->displayLoadStatus(($check_step1['records'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'records`', 0.6); $query = " CREATE TABLE IF NOT EXISTS `%prefix%records` ( `MapId` mediumint(3) unsigned DEFAULT '0', `PlayerId` mediumint(3) unsigned DEFAULT '0', `GamemodeId` tinyint(1) unsigned DEFAULT '0', `Date` datetime DEFAULT '1970-01-01 00:00:00', `Score` int(4) unsigned DEFAULT '0', `Checkpoints` text COLLATE 'utf8mb4_unicode_ci', PRIMARY KEY (`MapId`,`PlayerId`,`GamemodeId`), KEY `MapId` (`MapId`), KEY `PlayerId` (`PlayerId`), KEY `GamemodeId` (`GamemodeId`), KEY `Date` (`Date`), KEY `Score` (`Score`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; "; $this->db->query($query); $this->console('[Database] » '. ($check_step1['settings'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'settings`'); $this->displayLoadStatus(($check_step1['settings'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'settings`', 0.7); $query = " CREATE TABLE IF NOT EXISTS `%prefix%settings` ( `Plugin` varchar(64) COLLATE 'utf8mb4_unicode_ci' NOT NULL, `PlayerId` mediumint(3) unsigned DEFAULT '0', `Key` varchar(64) COLLATE 'utf8mb4_unicode_ci' NOT NULL, `Value` text COLLATE 'utf8mb4_unicode_ci', PRIMARY KEY (`Plugin`,`PlayerId`,`Key`), KEY `PlayerId` (`PlayerId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; "; $this->db->query($query); $this->console('[Database] » '. ($check_step1['times'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'times`'); $this->displayLoadStatus(($check_step1['times'] === true ? 'Checking' : 'Creating') .' table `'. $this->settings['dbms']['table_prefix'] .'times`', 0.8); $query = " CREATE TABLE IF NOT EXISTS `%prefix%times` ( `MapId` mediumint(3) unsigned DEFAULT '0', `PlayerId` mediumint(3) unsigned DEFAULT '0', `GamemodeId` tinyint(1) unsigned DEFAULT '0', `Date` datetime DEFAULT '1970-01-01 00:00:00', `Score` int(4) unsigned DEFAULT '0', `Checkpoints` text COLLATE 'utf8mb4_unicode_ci', PRIMARY KEY (`MapId`,`PlayerId`,`GamemodeId`,`Score`), KEY `MapId` (`MapId`), KEY `PlayerId` (`PlayerId`), KEY `GamemodeId` (`GamemodeId`), KEY `Date` (`Date`), KEY `Score` (`Score`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; "; $this->db->query($query); // Check for tables $percentage_done = 0.8; $tables = array(); $result = $this->db->query('SHOW TABLES;'); if ($result) { while ($row = $result->fetch_row()) { $tables[] = $row[0]; } $result->free_result(); } $check_step2 = array(); $check_step2['authors'] = in_array($this->settings['dbms']['table_prefix'] .'authors', $tables); $check_step2['maphistory'] = in_array($this->settings['dbms']['table_prefix'] .'maphistory', $tables); $check_step2['maps'] = in_array($this->settings['dbms']['table_prefix'] .'maps', $tables); $check_step2['players'] = in_array($this->settings['dbms']['table_prefix'] .'players', $tables); $check_step2['playlist'] = in_array($this->settings['dbms']['table_prefix'] .'playlist', $tables); $check_step2['rankings'] = in_array($this->settings['dbms']['table_prefix'] .'rankings', $tables); $check_step2['ratings'] = in_array($this->settings['dbms']['table_prefix'] .'ratings', $tables); $check_step2['records'] = in_array($this->settings['dbms']['table_prefix'] .'records', $tables); $check_step2['settings'] = in_array($this->settings['dbms']['table_prefix'] .'settings', $tables); $check_step2['times'] = in_array($this->settings['dbms']['table_prefix'] .'times', $tables); if (!$check_step2['authors'] && !$check_step2['maphistory'] && !$check_step2['maps'] && !$check_step2['players'] && !$check_step2['playlist'] && !$check_step2['rankings'] && !$check_step2['ratings'] && !$check_step2['records'] && !$check_step2['settings'] && !$check_step2['times']) { trigger_error('[Database] Table structure incorrect, automatic setup failed!', E_USER_ERROR); } if ($check_step1['maphistory'] === false) { $percentage_done += 0.1; $this->displayLoadStatus('Adding foreign key constraints...', $percentage_done); $this->console('[Database] » Adding foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'maphistory`'); $query = " ALTER TABLE `%prefix%maphistory` ADD CONSTRAINT `%prefix%maphistory_ibfk_1` FOREIGN KEY (`MapId`) REFERENCES `%prefix%maps` (`MapId`) ON DELETE CASCADE ON UPDATE CASCADE; "; $result = $this->db->query($query); if (!$result) { trigger_error('[Database] Failed to add required foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'maphistory` '. $this->db->errmsg(), E_USER_ERROR); } } if ($check_step1['maps'] === false) { $percentage_done += 0.1; $this->displayLoadStatus('Adding foreign key constraints...', $percentage_done); $this->console('[Database] » Adding foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'maps`'); $query = " ALTER TABLE `%prefix%maps` ADD CONSTRAINT `%prefix%maps_ibfk_1` FOREIGN KEY (`AuthorId`) REFERENCES `%prefix%authors` (`AuthorId`); "; $result = $this->db->query($query); if (!$result) { trigger_error('[Database] Failed to add required foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'maps` '. $this->db->errmsg(), E_USER_ERROR); } } if ($check_step1['playlist'] === false) { $percentage_done += 0.1; $this->displayLoadStatus('Adding foreign key constraints...', $percentage_done); $this->console('[Database] » Adding foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'playlist`'); $query = " ALTER TABLE `%prefix%playlist` ADD CONSTRAINT `%prefix%playlist_ibfk_1` FOREIGN KEY (`MapId`) REFERENCES `%prefix%maps` (`MapId`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `%prefix%playlist_ibfk_2` FOREIGN KEY (`PlayerId`) REFERENCES `%prefix%players` (`PlayerId`) ON DELETE CASCADE ON UPDATE CASCADE; "; $result = $this->db->query($query); if (!$result) { trigger_error('[Database] Failed to add required foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'playlist` '. $this->db->errmsg(), E_USER_ERROR); } } if ($check_step1['rankings'] === false) { $percentage_done += 0.1; $this->displayLoadStatus('Adding foreign key constraints...', $percentage_done); $this->console('[Database] » Adding foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'rankings`'); $query = " ALTER TABLE `%prefix%rankings` ADD CONSTRAINT `%prefix%ranks_ibfk_1` FOREIGN KEY (`PlayerId`) REFERENCES `%prefix%players` (`PlayerId`) ON DELETE CASCADE ON UPDATE CASCADE; "; $result = $this->db->query($query); if (!$result) { trigger_error('[Database] Failed to add required foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'rankings` '. $this->db->errmsg(), E_USER_ERROR); } } if ($check_step1['ratings'] === false) { $percentage_done += 0.1; $this->displayLoadStatus('Adding foreign key constraints...', $percentage_done); $this->console('[Database] » Adding foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'ratings`'); $query = " ALTER TABLE `%prefix%ratings` ADD CONSTRAINT `%prefix%ratings_ibfk_2` FOREIGN KEY (`PlayerId`) REFERENCES `%prefix%players` (`PlayerId`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `%prefix%ratings_ibfk_1` FOREIGN KEY (`MapId`) REFERENCES `%prefix%maps` (`MapId`) ON DELETE CASCADE ON UPDATE CASCADE; "; $result = $this->db->query($query); if (!$result) { trigger_error('[Database] Failed to add required foreign key constraints: '. $this->db->errmsg(), E_USER_ERROR); } } if ($check_step1['records'] === false) { $percentage_done += 0.1; $this->displayLoadStatus('Adding foreign key constraints...', $percentage_done); $this->console('[Database] » Adding foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'records`'); $query = " ALTER TABLE `%prefix%records` ADD CONSTRAINT `%prefix%records_ibfk_2` FOREIGN KEY (`PlayerId`) REFERENCES `%prefix%players` (`PlayerId`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `%prefix%records_ibfk_1` FOREIGN KEY (`MapId`) REFERENCES `%prefix%maps` (`MapId`) ON DELETE CASCADE ON UPDATE CASCADE; "; $result = $this->db->query($query); if (!$result) { trigger_error('[Database] Failed to add required foreign key constraints: '. $this->db->errmsg(), E_USER_ERROR); } } if ($check_step1['settings'] === false) { $percentage_done += 0.1; $this->displayLoadStatus('Adding foreign key constraints...', $percentage_done); $this->console('[Database] » Adding foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'settings`'); $query = " ALTER TABLE `%prefix%settings` ADD CONSTRAINT `%prefix%settings_ibfk_1` FOREIGN KEY (`PlayerId`) REFERENCES `%prefix%players` (`PlayerId`) ON DELETE CASCADE ON UPDATE CASCADE; "; $result = $this->db->query($query); if (!$result) { trigger_error('[Database] Failed to add required foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'settings` '. $this->db->errmsg(), E_USER_ERROR); } } if ($check_step1['times'] === false) { $percentage_done += 0.1; $this->displayLoadStatus('Adding foreign key constraints...', $percentage_done); $this->console('[Database] » Adding foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'times`'); $query = " ALTER TABLE `%prefix%times` ADD CONSTRAINT `%prefix%times_ibfk_2` FOREIGN KEY (`PlayerId`) REFERENCES `%prefix%players` (`PlayerId`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `%prefix%times_ibfk_1` FOREIGN KEY (`MapId`) REFERENCES `%prefix%maps` (`MapId`) ON DELETE CASCADE ON UPDATE CASCADE; "; $result = $this->db->query($query); if (!$result) { trigger_error('[Database] Failed to add required foreign key constraints for table `'. $this->settings['dbms']['table_prefix'] .'times` '. $this->db->errmsg(), E_USER_ERROR); } } $this->displayLoadStatus('Checking database structure...', 0.95); $query = " SET FOREIGN_KEY_CHECKS=1; "; $result = $this->db->query($query); if (!$result) { trigger_error('[Database] Failed to enable foreign key checks: '. $this->db->errmsg(), E_USER_ERROR); } $this->console('[Database] ...successfully done!'); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Authenticates USASEO at the server. private function connectDedicated () { // Only if logins are set if ($this->server->xmlrpc['ip'] && $this->server->xmlrpc['port'] && $this->server->xmlrpc['login'] && $this->server->xmlrpc['pass']) { // Log console message $this->console('[Dedicated] Try to connect to Maniaplanet dedicated server at {1}:{2} (timeout {3}s)', $this->server->xmlrpc['ip'], $this->server->xmlrpc['port'], ($this->server->timeout !== null ? $this->server->timeout : 0) ); try { // Connect to the server $this->client->connect($this->server->xmlrpc['ip'], $this->server->xmlrpc['port'], $this->server->timeout); } catch (Exception $exception) { trigger_error('[Dedicated] ['. $exception->getCode() .'] connect - '. $exception->getMessage(), E_USER_WARNING); return false; } // Check login if ($this->server->xmlrpc['login'] != 'SuperAdmin') { trigger_error("[Dedicated] Invalid login '". $this->server->xmlrpc['login'] ."' - must be 'SuperAdmin' in [config/UASECO.xml]!", E_USER_WARNING); return false; } // Check password if ($this->server->xmlrpc['pass'] == 'SuperAdmin') { trigger_error("[Dedicated] Insecure (default) password '" . $this->server->xmlrpc['pass'] . "' - should be changed in dedicated config and [config/UASECO.xml]!", E_USER_WARNING); } // Log console message if ($this->settings['mask_password'] == true) { $this->console("[Dedicated] Try to authenticate with login [{1}] and password [{2}] (masked password)", $this->server->xmlrpc['login'], preg_replace('#.#', '*', $this->server->xmlrpc['pass']) ); } else { $this->console("[Dedicated] Try to authenticate with login [{1}] and password [{2}]", $this->server->xmlrpc['login'], $this->server->xmlrpc['pass'] ); } try { // Log into the server $this->client->query('Authenticate', $this->server->xmlrpc['login'], $this->server->xmlrpc['pass']); } catch (Exception $exception) { trigger_error('[Dedicated] ['. $exception->getCode() .'] Authenticate - '. $exception->getMessage(), E_USER_WARNING); return false; } // Enable callback system $this->client->query('EnableCallbacks', true); // Wait for server to be ready $this->waitServerReady(); // Setup API-Version $this->client->query('SetApiVersion', XMLRPC_API_VERSION); // Connection established return true; } else { // Connection failed return false; } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Waits for the server to be ready (status 4, 'Running - Play') private function waitServerReady () { $status = $this->client->query('GetStatus'); if ($status['Code'] != 4) { $this->console("[Dedicated] » Waiting for dedicated server to reach status 'Running - Play'..."); $this->console('[Dedicated] » Status: ['. $status['Code'] .'] '. $status['Name']); $timeout = 0; $laststatus = $status['Name']; while ($status['Code'] != 4) { sleep(1); $status = $this->client->query('GetStatus'); if ($laststatus != $status['Name']) { $this->console('[Dedicated] » Status: ['. $status['Code'] .'] '. $status['Name']); $laststatus = $status['Name']; } if (empty($status['Code'])) { trigger_error('[Dedicated] Connection failed on empty status!', E_USER_ERROR); } if (isset($this->server->timeout) && $timeout++ > $this->server->timeout) { trigger_error('[Dedicated] Timed out while waiting for dedicated server!', E_USER_ERROR); } } } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Gets callbacks from the TM Dedicated Server and reacts on them. private function executeCallbacks () { // Get the responses $calls = array(); try { $calls = $this->client->getCallbacks(); } catch (Exception $exception) { trigger_error('ExecuteCallbacks XmlRpc Error ['. $exception->getCode() .'] - '. $exception->getMessage(), E_USER_ERROR); } if (!empty($calls)) { while ($call = array_shift($calls)) { switch ($call[0]) { case 'ManiaPlanet.ModeScriptCallbackArray': // [0] = string Param1, [1] = string Params[] $this->releaseEvent('onModeScriptCallbackArray', $call[1]); break; case 'ManiaPlanet.PlayerChat': // [0] = int PlayerUid, [1] = string Login, [2] = string Text, [3] = bool IsRegistredCmd if ($call[1][0] === $this->server->id) { $this->releaseEvent('onServerChat', $call[1]); } else { $this->playerChat($call[1]); } break; case 'ManiaPlanet.PlayerInfoChanged': // [0] = SPlayerInfo PlayerInfo $this->playerInfoChanged($call[1][0]); break; case 'ManiaPlanet.PlayerManialinkPageAnswer': // [0] = int PlayerUid, [1] = string Login, [2] = string Answer, [3] = SEntryVal Entries[] $this->releaseEvent('onPlayerManialinkPageAnswer', $call[1]); break; case 'ManiaPlanet.PlayerConnect': // [0] = string Login, [1] = bool IsSpectator $this->playerConnect($call[1][0], $call[1][1]); break; case 'ManiaPlanet.PlayerDisconnect': // [0] = string Login, [1] = string DisconnectionReason $this->playerDisconnect($call[1][0], $call[1][1]); break; // case 'ManiaPlanet.StatusChanged': // // [0] = int StatusCode, [1] = string StatusName // $this->current_status = $call[1][0]; // update status changes // $this->releaseEvent('onStatusChangeTo'. $this->current_status, $call[1]); // break; case 'ManiaPlanet.MapListModified': // [0] = int CurMapIndex, [1] = int NextMapIndex, [2] = bool IsListModified if ($call[1][2] == true && $this->settings['automatic_refresh_maplist'] == true) { $this->console('[MapList] Re-reading complete map list from server...'); $this->server->maps->readMapList(); $count = count($this->server->maps->map_list); $this->console('[MapList] ...successfully done, read '. $count .' map'. ($count == 1 ? '' : 's') .' which matches server settings!'); $this->releaseEvent('onMapListChanged', array('read', null)); } $this->releaseEvent('onMapListModified', $call[1]); break; case 'ManiaPlanet.BillUpdated': // [0] = int BillId, [1] = int State, [2] = string StateName, [3] = int TransactionId $this->releaseEvent('onBillUpdated', $call[1]); break; case 'ManiaPlanet.PlayerAlliesChanged': // [0] = string Login $this->releaseEvent('onPlayerAlliesChanged', $call[1]); break; case 'ManiaPlanet.PlayerIncoherence': // [0] = int PlayerUid, [1] = string Login $this->releaseEvent('onPlayerIncoherence', $call[1]); break; case 'ManiaPlanet.TunnelDataReceived': // [0] = int PlayerUid, [1] = string Login, [2] = base64 Data $this->releaseEvent('onTunnelDataReceived', $call[1]); break; case 'ManiaPlanet.Echo': // [0] = string Internal, [1] = string Public if ($call[1][0] == 'AdminServ.Map.Added') { // Add external added map to our MapList too $param = json_decode($call[1][1]); $this->server->maps->addMapToListByUid($param->map->uid); } else if ($call[1][0] == 'AdminServ.Map.Deleted') { // Remove external removed map fromo our MapList too $param = json_decode($call[1][1]); $this->server->maps->removeMapByUid($param->map->uid); } $this->releaseEvent('onEcho', $call[1][0], $call[1][1]); break; case 'ManiaPlanet.VoteUpdated': // [0] = string StateName, [1] = string Login, [2] = string CmdName, [3] = string CmdParam $this->releaseEvent('onVoteUpdated', $call[1]); break; default: // do nothing } } return $calls; } else { return false; } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ private function executeMulticall () { // Sends multiquery to the server try { $this->client->multiquery(); } catch (Exception $exception) { $errmsg = $exception->getMessage(); if ($errmsg != 'Login unknown.') { $this->console('[UASECO] Exception occurred: ['. $exception->getCode() .'] "'. $errmsg .'" - executeMulticall()'); } } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // When a new map is started we have to get information about the new map, // get record to current map etc. public function loadingMap ($uid) { // Setup race status $this->server->gamestate = Server::RACE; // Check for changing the daily logfile if ($this->logfile['file'] != './logs'. DIRECTORY_SEPARATOR . date('Y-m-d') .'-uaseco-current.log') { // Setup new logfile $this->setupLogfile(); $this->sendHeader(); } // Cleanup Player rankings $this->server->rankings->reset(); // Get current map object $map = $this->server->maps->getCurrentMapInfo(); // Check for restarting map if ($this->restarting == true) { // Throw postfix 'restart map' event if ($this->settings['developer']['log_events']['common'] == true) { $this->console('[Event] Restart Map'); } $this->releaseEvent('onRestartMap', $map); // Reset status $this->restarting = false; return; } if ($this->startup_phase === false) { // Add new Map into MapHistory $this->server->maps->history->addMapToHistory($this->server->maps->current); // (Re-)read MapHistory $this->server->maps->history->readMapHistory(); // Setup previous Map $this->server->maps->previous = $this->server->maps->current; } else { // Setup previous Map (from history) $this->server->maps->previous = $this->server->maps->history->getPreviousMap(); } // Setup next Map $this->server->maps->next = $this->server->maps->getNextMap(); // Search MX for current Map if ($map->mx === false || time() > ($map->mx->timestamp_fetched + $this->server->maps->max_age_mxinfo)) { $response = new MXInfoFetcher('TM2', $map->uid, true); if ($response !== null && empty($response->error)) { $map->mx = $response; } } else if ($this->debug) { $this->console('[Map] MX infos cached, last fetched at '. date('Y-m-d H:i:s', $map->mx->timestamp_fetched)); } $this->releaseEvent('onManiaExchangeBestLoaded', (isset($map->mx->recordlist[0]['replaytime']) ? $map->mx->recordlist[0]['replaytime'] : 0)); // Search MX for previous Map if ($this->server->maps->previous->mx === false || time() > ($this->server->maps->previous->mx->timestamp_fetched + $this->server->maps->max_age_mxinfo)) { $response = new MXInfoFetcher('TM2', $this->server->maps->previous->uid, true); if ($response !== null && empty($response->error)) { $this->server->maps->previous->mx = $response; } } // Search MX for next Map if ($this->server->maps->next->mx === false || time() > ($this->server->maps->next->mx->timestamp_fetched + $this->server->maps->max_age_mxinfo)) { $response = new MXInfoFetcher('TM2', $this->server->maps->next->uid, true); if ($response !== null && empty($response->error)) { $this->server->maps->next->mx = $response; } } // Report usage back to home website and store file for the "stripling.php" $this->reportServerInfo(); // Refresh game info $this->server->getCurrentGameInfo(); // Refresh server name and options $this->server->updateServerOptions(); // Log debug information if ($this->debug) { $this->logDebugInformations(); } // Log console message if ($this->server->maps->current->uid == $map->uid) { $this->console("[Map] Running on Map [{1}] made by [{2}] [Env: {3}, Uid: {4}, Id: {5}]", $map->name_stripped, $map->author, $map->environment, $map->uid, $map->id ); } else { $this->console("[Map] Changing from Map [{1}] to [{2}] [Env: {3}, Uid: {4}, Id: {5}]", $this->server->maps->current->name_stripped, $map->name_stripped, $map->environment, $map->uid, $map->id ); } // Update the field which contains current map $this->server->maps->current = $map; // Throw main 'loading map' event if ($this->settings['developer']['log_events']['common'] == true) { $this->console('[Event] Loading Map'); } $this->releaseEvent('onLoadingMap', $map); // Simulate 'Maniaplanet.StartMap_Start' while start-up phase if ($this->startup_phase === true) { $data['map']['uid'] = $map->uid; $param[0] = 'Maniaplanet.StartMap_Start'; $param[1][0] = json_encode($data); $this->plugins['PluginModescriptHandler']->onModeScriptCallbackArray($this, $param); } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ public function unLoadingMap ($uid) { // Recalculate ranks of each player $this->server->players->recalculateRanks(); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ public function endMap () { $this->server->gamestate = Server::SCORE; // Throw 'end map' event (Records) $this->releaseEvent('onEndMapRanking', $this->server->maps->current); if (!$this->server->isrelay) { // Check out who won the current map and increment his/her wins by one. if ($this->server->rankings->count() > 1) { $rank = $this->server->rankings->getRank(1); if (($player = $this->server->players->getPlayerByLogin($rank->login)) !== false) { // Check for winner if there's more than one player if ($rank->round_points > 0 || $rank->map_points > 0 || $rank->match_points > 0 || $rank->best_race_time > 0 || $rank->best_lap_time > 0) { // Increase the player's wins $player->new_wins++; // Log console message $this->console('[Rank] Player [{1}] won for the {2}. time!', $player->login, $player->getWins() ); if ($player->getWins() % $this->settings['global_win_multiple'] == 0) { // Replace parameters $message = $this->formatText($this->getChatMessage('WIN_MULTI'), $this->stripStyles($player->nickname), $player->getWins() ); // Show chat message $this->sendChatMessage($message); } else { // Replace parameters $message = $this->formatText($this->getChatMessage('WIN_NEW'), $player->getWins() ); // Show chat message $this->sendChatMessage($message, $player->login); } // Throw 'player wins' event $this->releaseEvent('onPlayerWins', $player); } } } } if ($this->settings['developer']['log_events']['common'] == true) { $this->console('[Event] End Map'); } // Throw prefix 'end map' event (e.g. chat-based votes) $this->releaseEvent('onEndMapPrefix', $this->server->maps->current); // Throw main 'end map' event $this->releaseEvent('onEndMap', $this->server->maps->current); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Handles connections of new players. public function playerConnect ($login, $is_spectator) { // Request information about the new player $details = $this->client->query('GetDetailedPlayerInfo', $login); $info = $this->client->query('GetPlayerInfo', $login, 1); $data = @array_merge($details, $info); unset($details, $info); // Check for Server if (isset($data['Flags']) && floor($data['Flags'] / 100000) % 10 != 0) { // Register relay server if (!$this->server->isrelay && $data['Login'] != $this->server->login) { $this->server->relay_list[$data['Login']] = $data; // log console message $this->console('[Relay] Connect of relay server {1} ({2})', $data['Login'], $this->stripStyles($data['NickName'], false) ); } // else: DO NOTHING on master server connect } else if (isset($data['Flags']) && $this->server->isrelay && floor($data['Flags'] / 10000) % 10 != 0) { // DO NOTHING on player from master server on relay } else { $ipaddr = isset($data['IPAddress']) ? preg_replace('/:\d+/', '', $data['IPAddress']) : ''; // strip port // if no data fetched, notify & kick the player if (!isset($data['Login']) || $data['Login'] == '') { $message = str_replace('{br}', LF, $this->getChatMessage('CONNECT_ERROR')); $this->sendChatMessage(str_replace(LF.LF, LF, $message), $login); sleep(5); // allow time to connect and see the notice $this->client->addCall('Kick', $login, $this->formatColors($this->getChatMessage('CONNECT_DIALOG'))); // log console message $this->console('[Player] GetPlayerInfo failed for ['. $login .'] -- notified & kicked'); return; } else if (!empty($this->banned_ips) && in_array($ipaddr, $this->banned_ips)) { // if player IP in ban list, notify & kick the player $message = str_replace('{br}', LF, $this->getChatMessage('BANIP_ERROR')); $this->sendChatMessage(str_replace(LF.LF, LF, $message), $login); sleep(5); // allow time to connect and see the notice $this->client->addCall('Ban', $login, $this->formatColors($this->getChatMessage('BANIP_DIALOG'))); $this->console('[Player] Player ['. $login .'] banned from '. $ipaddr .' -- notified & kicked'); return; } else { // client version checking, extract version number $version = str_replace(')', '', preg_replace('/.*\(/', '', $data['ClientVersion'])); if ($version == '') { $version = '3.3.0'; } $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->sendChatMessage($message, $login); sleep(5); // allow time to connect and see the notice $this->client->addCall('Kick', $login, $this->formatColors($this->getChatMessage('CLIENT_DIALOG'))); $this->console('[Player] Obsolete player client version '. $version .' for ['. $login .'] -- notified & kicked'); return; } // if invalid version, notify & kick the admin if ($this->settings['admin_client'] != '' && $this->isAnyAdminByLogin($data['Login']) && strcmp($version, $this->settings['admin_client']) < 0) { $this->sendChatMessage($message, $login); sleep(5); // allow time to connect and see the notice $this->client->addCall('Kick', $login, $this->formatColors($this->getChatMessage('CLIENT_DIALOG'))); $this->console('[Player] Obsolete admin client version '. $version .' for ['. $login .'] -- notified & kicked'); return; } } // Create Player object, and adds new Player to the Player list $player = new Player($data); $this->server->players->addPlayer($player); // Get the current ranking for this player, required to have the rankings up-to-date on a running race, // but not in TEAM mode (requires a special handling). if ($this->server->gameinfo->mode !== Gameinfo::TEAM) { // Add to ranking list $this->server->rankings->addPlayer($player); if ($this->startup_phase === false) { // Call 'Trackmania.GetScores' to get 'Trackmania.Scores' $this->client->query('TriggerModeScriptEventArray', 'Trackmania.GetScores', array((string)time())); } } // Log console message $this->console('[Player] Connection from Player [{1}] from {2} [Nick: {3}, IP: {4}, Rank: {5}, Id: {6}]', $player->login, $this->country->iocToCountry($player->nation), $this->stripStyles($player->nickname), $player->ip, $player->ladder_rank, $player->id ); // Update the Visits, but only when Player connects and not when UASECO restarts if ($this->startup_phase === false && $this->restarting === false) { $query = "UPDATE `%prefix%players` SET `Visits` = `Visits` + 1 WHERE `PlayerId` = ". $player->id ." LIMIT 1;"; $result = $this->db->query($query); if (!$result) { $this->console('[Player] UPDATE `Visits` at `%prefix%players` failed [for statement "'. $query .'"]!'); } } // Replace parameters $message = $this->formatText($this->getChatMessage('WELCOME'), $this->stripStyles($player->nickname), $this->server->name, UASECO_VERSION ); // Hyperlink package name & version number $message = preg_replace('/UASECO.+'. UASECO_VERSION .'/', '$L['. UASECO_WEBSITE .']$0$L', $message); $message = str_replace('{br}', LF, $message); $this->sendChatMessage(str_replace(LF.LF, LF, $message), $player->login); // Throw main 'player connects' event $this->releaseEvent('onPlayerConnect', $player); // Throw postfix 'player connects' event (access control) $this->releaseEvent('onPlayerConnectPostfix', $player); } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Handles disconnections of Players. public function playerDisconnect ($login, $reason) { // Check for relay server if (!$this->server->isrelay && array_key_exists($login, $this->server->relay_list)) { // log console message $this->console('[Relay] Disconnect of relay server {1} ({2})', $login, $this->stripStyles($this->server->relay_list[$login]['NickName'], false) ); unset($this->server->relay_list[$login]); return; } // Get Player object, if available. Otherwise bail out. if (!$player = $this->server->players->getPlayerByLogin($login)) { return; } // Throw 'prepare player disconnect' event $this->releaseEvent('onPlayerDisconnectPrepare', $player); // Store eventually changed Plugin settings to the database $player->storeDatabasePlayerSettings(); // Delete Player, skip the rest on relay if Player from master server (which was not added) if (!$result = $this->server->players->removePlayer($login)) { return; } // Log console message $this->console('[Player] Disconnection from Player [{1}] after {2} playtime [Nick: {3}, IP: {4}, Rank: {5}, Id: {6}]', $player->login, $this->timeString($player->getTimeOnline(), true), $this->stripStyles($player->nickname, false), $player->ip, $player->ladder_rank, $player->id ); // Throw 'player disconnects' event $this->releaseEvent('onPlayerDisconnect', $player); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Receives chat messages and reacts on them, reactions are done by the chat plugins. public function playerChat ($chat) { // Verify login if ($chat[1] == '' || $chat[1] == '???') { $this->console('[Chat] WARN: PlayerUid [{1}], with login [{2}] attempted to use chat command "{3}"', $chat[0], $chat[1], $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); // Insure parameter exists & is trimmed if (isset($params[1])) { $params[1] = trim($params[1]); } else { $params[1] = ''; } // Make chat-command lowercase and trim it $command = strtolower(trim($params[0])); // Get & verify player object if ($caller = $this->server->players->getPlayerByLogin($chat[1])) { if (!empty($this->registered_chatcmds[$command])) { $allowed = false; if ($this->registered_chatcmds[$command]['rights'] & Player::PLAYERS) { // Chat command is allowed for everyone $allowed = true; } else if ( ($this->registered_chatcmds[$command]['rights'] & Player::OPERATORS) && ($this->isOperator($caller) || $this->isAdmin($caller) || $this->isMasterAdmin($caller)) ) { // Chat command is only allowed for Operators, Admins or MasterAdmins $allowed = true; } else if ( ($this->registered_chatcmds[$command]['rights'] & Player::ADMINS) && ($this->isAdmin($caller) || $this->isMasterAdmin($caller)) ) { // Chat command is only allowed for Admins or MasterAdmins $allowed = true; } else if ( ($this->registered_chatcmds[$command]['rights'] & Player::MASTERADMINS) && ($this->isMasterAdmin($caller)) ) { // Chat command is only allowed for MasterAdmins $allowed = true; } if ($allowed == true) { // log console message if (empty($params[1])) { $this->console('[Chat] Player [{1}] used command "/{2}"', $caller->login, $command ); } else { $this->console('[Chat] Player [{1}] used command "/{2} {3}"', $caller->login, $command, $params[1] ); } // call the function which belongs to the command call_user_func($this->registered_chatcmds[$command]['callback'], $this, // $aseco $caller->login, // Login from caller $command, // "/" stripped chat command ((isset($params[1])) ? trim($params[1]) : '') // given parameters like "/admin setgamemode timeattack" ); } else if ($this->settings['log_all_chat']) { // Optionally log attempts of chat commands from players with insufficient rights too $rights = 'Everyone'; if ($this->registered_chatcmds[$command]['rights'] & Player::OPERATORS) { $rights = 'Operators'; } else if ($this->registered_chatcmds[$command]['rights'] & Player::ADMINS) { $rights = 'Admins'; } else if ($this->registered_chatcmds[$command]['rights'] & Player::MASTERADMINS) { $rights = 'MasterAdmins'; } $this->console('[Chat] RESTRICTED: Player [{1}] attempted to use command "{2}" which requires min. rights of "{3}"!', $caller->login, $this->stripStyles($chat[2], false), $rights ); } } else if ($params[0] == 'version' || $params[0] == 'serverlogin') { // Log built-in commands fomr dedicated server $this->console('[Chat] Player [{1}] used built-in command "/{2}"', $caller->login, $command ); } else if ($this->settings['log_all_chat']) { // Optionally log bogus chat commands too $this->console('[Chat] NOTICE: Player [{1}] (Id: {2}) attempted to use command "{3}"', $caller->login, $chat[0], $this->stripStyles($chat[2], false) ); } } else { $this->console('[Chat] WARN: Player object for [{1}] not found, but was attempted to use command "/{2} {3}"', $chat[1], $command, $params[1] ); } } else { // optionally log all normal chat too if ($this->settings['log_all_chat']) { if ($chat[2] != '') { $this->console('[Chat] NOTICE: Player [{1}] (Id: {2}) attempted to use command "{3}"', $chat[1], $chat[0], $this->stripStyles($chat[2], false) ); } } } $this->releaseEvent('onPlayerChat', $chat); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // When a player's info changed, signal the event. Fields: Login, NickName, PlayerId, TeamId, SpectatorStatus, LadderRanking, Flags public 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->getPlayerByLogin($playerinfo['Login'])) { return; } // Update Player object $player->updateInfo($playerinfo); $this->releaseEvent('onPlayerInfoChanged', $playerinfo['Login']); } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ public function onShutdown ($aseco) { // Simulate a "playerDisconnect" foreach ($this->server->players->player_list as $player) { $this->playerDisconnect($player->login, ''); } } } /* #///////////////////////////////////////////////////////////////////////# # # #///////////////////////////////////////////////////////////////////////# */ // Create an instance of UASECO and run it $aseco = new UASECO(); $aseco->run(); ?>