docker-tmserver/docker-xaseco/xaseco/aseco.php

2563 lines
88 KiB
PHP

<?php
/* vim: set noexpandtab tabstop=2 softtabstop=2 shiftwidth=2: */
/**
* Projectname: XASECO (formerly ASECO/RASP)
*
* Requires: PHP version 5, MySQL version 4/5
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
*
* Authored & copyright 2006 by Florian Schnell <floschnell@gmail.com>
*
* Re-authored & copyright May 2007 - Jul 2013 by Xymph <tm@gamers.org>
*
* Visit the official site at http://www.xaseco.org/
*/
/**
* Include required classes
*/
require_once('includes/types.inc.php'); // contains classes to store information
require_once('includes/basic.inc.php'); // contains standard functions
require_once('includes/GbxRemote.inc.php'); // needed for dedicated server connections
require_once('includes/xmlparser.inc.php'); // provides an XML parser
require_once('includes/gbxdatafetcher.inc.php'); // provides access to GBX data
require_once('includes/tmndatafetcher.inc.php'); // provides access to TMN world stats
require_once('includes/rasp.settings.php'); // specific to the RASP plugins
/**
* Runtime configuration definitions
*/
// add abbreviations for some chat commands?
// /admin -> /ad, /jukebox -> /jb, /autojuke -> /aj
define('ABBREV_COMMANDS', false);
// disable local & Dedi record relations commands from help lists?
define('INHIBIT_RECCMDS', false);
// separate logs by month in logs/ dir?
define('MONTHLY_LOGSDIR', false);
// keep UTF-8 encoding in config.xml?
define('CONFIG_UTF8ENCODE', false);
/**
* System definitions - no changes below this point
*/
// current project version
define('XASECO_VERSION', '1.16');
define('XASECO_TMN', 'http://www.gamers.org/tmn/');
define('XASECO_TMF', 'http://www.gamers.org/tmf/');
define('XASECO_TM2', 'http://www.gamers.org/tm2/');
define('XASECO_ORG', 'http://www.xaseco.org/');
// required official dedicated server builds
define('TMN_BUILD', '2006-05-30');
define('TMF_BUILD', '2011-02-21');
// check current operating system
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// on Win32/NT use:
define('CRLF', "\r\n");
} else {
// on Unix use:
define('CRLF', "\n");
}
if (!defined('LF')) {
define('LF', "\n");
}
/**
* Error function
* Report errors in a regular way.
*/
set_error_handler('displayError');
function displayError($errno, $errstr, $errfile, $errline) {
global $aseco;
// check for error suppression
if (error_reporting() == 0) return;
switch ($errno) {
case E_USER_ERROR:
$message = "[XASECO Fatal Error] $errstr on line $errline in file $errfile" . CRLF;
echo $message;
doLog($message);
// throw 'shutting down' event
$aseco->releaseEvent('onShutdown', null);
// clear all ManiaLinks
$aseco->client->query('SendHideManialinkPage');
if (function_exists('xdebug_get_function_stack'))
doLog(print_r(xdebug_get_function_stack()), true);
die();
break;
case E_USER_WARNING:
$message = "[XASECO Warning] $errstr" . CRLF;
echo $message;
doLog($message);
break;
case E_ERROR:
$message = "[PHP Error] $errstr on line $errline in file $errfile" . CRLF;
echo $message;
doLog($message);
break;
case E_WARNING:
$message = "[PHP Warning] $errstr on line $errline in file $errfile" . CRLF;
echo $message;
doLog($message);
break;
default:
if (strpos($errstr, 'Function call_user_method') !== false) break;
//$message = "[PHP $errno] $errstr on line $errline in file $errfile" . CRLF;
//echo $message;
//doLog($message);
// do nothing, only treat known errors
}
} // displayError
/**
* Here XASECO actually starts.
*/
class Aseco {
/**
* Public fields
*/
var $client;
var $xml_parser;
var $script_timeout;
var $debug;
var $server;
var $command;
var $events;
var $rpc_calls;
var $rpc_responses;
var $chat_commands;
var $chat_colors;
var $chat_messages;
var $plugins;
var $settings;
var $style;
var $panels;
var $statspanel;
var $titles;
var $masteradmin_list;
var $admin_list;
var $adm_abilities;
var $operator_list;
var $op_abilities;
var $bannedips;
var $startup_phase; // XAseco start-up phase
var $warmup_phase; // warm-up phase
var $restarting; // restarting challenge (0 = not, 1 = instant, 2 = chattime)
var $changingmode; // changing game mode
var $currstatus; // server status changes
var $prevstatus;
var $currsecond; // server time changes
var $prevsecond;
var $uptime; // XAseco start-up time
/**
* Initializes the server.
*/
function Aseco($debug) {
global $maxrecs; // from rasp.settings.php
echo '# initialize XASECO ###########################################################' . CRLF;
// log php & mysql version info
$this->console_text('[XAseco] PHP Version is ' . phpversion() . ' on ' . PHP_OS);
// initialize
$this->uptime = time();
$this->chat_commands = array();
$this->debug = $debug;
$this->client = new IXR_ClientMulticall_Gbx();
$this->xml_parser = new Examsly();
$this->server = new Server('127.0.0.1', 5000, 'SuperAdmin', 'SuperAdmin');
$this->server->challenge = new Challenge();
$this->server->players = new PlayerList();
$this->server->records = new RecordList($maxrecs);
$this->server->mutelist = array();
$this->plugins = array();
$this->titles = array();
$this->masteradmin_list = array();
$this->admin_list = array();
$this->adm_abilities = array();
$this->operator_list = array();
$this->op_abilities = array();
$this->bannedips = array();
$this->startup_phase = true;
$this->warmup_phase = false;
$this->restarting = 0;
$this->changingmode = false;
$this->currstatus = 0;
} // Aseco
/**
* Load settings and apply them on the current instance.
*/
function loadSettings($config_file) {
if ($settings = $this->xml_parser->parseXml($config_file, true, CONFIG_UTF8ENCODE)) {
// read the XML structure into an array
$aseco = $settings['SETTINGS']['ASECO'][0];
// read settings and apply them
$this->chat_colors = $aseco['COLORS'][0];
$this->chat_messages = $aseco['MESSAGES'][0];
$this->masteradmin_list = $aseco['MASTERADMINS'][0];
if (!isset($this->masteradmin_list) || !is_array($this->masteradmin_list))
trigger_error('No MasterAdmin(s) configured in config.xml!', E_USER_ERROR);
// check masteradmin list consistency
if (empty($this->masteradmin_list['IPADDRESS'])) {
// fill <ipaddress> list to same length as <tmlogin> 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 <tmlogin>'s and <ipaddress>'s!", E_USER_WARNING);
}
// set admin lock password
$this->settings['lock_password'] = $aseco['LOCK_PASSWORD'][0];
// set cheater action
$this->settings['cheater_action'] = $aseco['CHEATER_ACTION'][0];
// set script timeout
$this->settings['script_timeout'] = $aseco['SCRIPT_TIMEOUT'][0];
// set minimum number of records to be displayed
$this->settings['show_min_recs'] = $aseco['SHOW_MIN_RECS'][0];
// show records before start of track?
$this->settings['show_recs_before'] = $aseco['SHOW_RECS_BEFORE'][0];
// show records after end of track?
$this->settings['show_recs_after'] = $aseco['SHOW_RECS_AFTER'][0];
// show TMX world record?
$this->settings['show_tmxrec'] = $aseco['SHOW_TMXREC'][0];
// show played time at end of track?
$this->settings['show_playtime'] = $aseco['SHOW_PLAYTIME'][0];
// show current track at start of track?
$this->settings['show_curtrack'] = $aseco['SHOW_CURTRACK'][0];
// set default filename for readtracklist/writetracklist
$this->settings['default_tracklist'] = $aseco['DEFAULT_TRACKLIST'][0];
// set minimum number of ranked players in a clan to be included in /topclans
$this->settings['topclans_minplayers'] = $aseco['TOPCLANS_MINPLAYERS'][0];
// set multiple of win count to show global congrats message
$this->settings['global_win_multiple'] = ($aseco['GLOBAL_WIN_MULTIPLE'][0] > 0 ? $aseco['GLOBAL_WIN_MULTIPLE'][0] : 1);
// timeout of the TMF message window in seconds
$this->settings['window_timeout'] = $aseco['WINDOW_TIMEOUT'][0];
// set filename of admin/operator/ability lists file
$this->settings['adminops_file'] = $aseco['ADMINOPS_FILE'][0];
// set filename of banned IPs list file
$this->settings['bannedips_file'] = $aseco['BANNEDIPS_FILE'][0];
// set filename of blacklist file
$this->settings['blacklist_file'] = $aseco['BLACKLIST_FILE'][0];
// set filename of guestlist file
$this->settings['guestlist_file'] = $aseco['GUESTLIST_FILE'][0];
// set filename of track history file
$this->settings['trackhist_file'] = $aseco['TRACKHIST_FILE'][0];
// set minimum admin client version
$this->settings['admin_client'] = $aseco['ADMIN_CLIENT_VERSION'][0];
// set minimum player client version
$this->settings['player_client'] = $aseco['PLAYER_CLIENT_VERSION'][0];
// set default rounds points system
$this->settings['default_rpoints'] = $aseco['DEFAULT_RPOINTS'][0];
// set windows style (none = old TMN style)
$this->settings['window_style'] = $aseco['WINDOW_STYLE'][0];
// set admin panel (none = no panel)
$this->settings['admin_panel'] = $aseco['ADMIN_PANEL'][0];
// set donate panel (none = no panel)
$this->settings['donate_panel'] = $aseco['DONATE_PANEL'][0];
// set records panel (none = no panel)
$this->settings['records_panel'] = $aseco['RECORDS_PANEL'][0];
// set vote panel (none = no panel)
$this->settings['vote_panel'] = $aseco['VOTE_PANEL'][0];
// display welcome message as window ?
if (strtoupper($aseco['WELCOME_MSG_WINDOW'][0]) == 'TRUE') {
$this->settings['welcome_msg_window'] = true;
} else {
$this->settings['welcome_msg_window'] = false;
}
// log all chat, not just chat commands ?
if (strtoupper($aseco['LOG_ALL_CHAT'][0]) == 'TRUE') {
$this->settings['log_all_chat'] = true;
} else {
$this->settings['log_all_chat'] = false;
}
// show timestamps in /chatlog, /pmlog & /admin pmlog ?
if (strtoupper($aseco['CHATPMLOG_TIMES'][0]) == 'TRUE') {
$this->settings['chatpmlog_times'] = true;
} else {
$this->settings['chatpmlog_times'] = false;
}
// show records range?
if (strtoupper($aseco['SHOW_RECS_RANGE'][0]) == 'TRUE') {
$this->settings['show_recs_range'] = true;
} else {
$this->settings['show_recs_range'] = false;
}
// show records in message window?
if (strtoupper($aseco['RECS_IN_WINDOW'][0]) == 'TRUE') {
$this->settings['recs_in_window'] = true;
} else {
$this->settings['recs_in_window'] = false;
}
// show round reports in message window?
if (strtoupper($aseco['ROUNDS_IN_WINDOW'][0]) == 'TRUE') {
$this->settings['rounds_in_window'] = true;
} else {
$this->settings['rounds_in_window'] = false;
}
// add random filter to /admin writetracklist output
if (strtoupper($aseco['WRITETRACKLIST_RANDOM'][0]) == 'TRUE') {
$this->settings['writetracklist_random'] = true;
} else {
$this->settings['writetracklist_random'] = false;
}
// add explanation to /help output
if (strtoupper($aseco['HELP_EXPLANATION'][0]) == 'TRUE') {
$this->settings['help_explanation'] = true;
} else {
$this->settings['help_explanation'] = false;
}
// color nicknames in the various /top... etc lists?
if (strtoupper($aseco['LISTS_COLORNICKS'][0]) == 'TRUE') {
$this->settings['lists_colornicks'] = true;
} else {
$this->settings['lists_colornicks'] = false;
}
// color tracknames in the various /lists... lists?
if (strtoupper($aseco['LISTS_COLORTRACKS'][0]) == 'TRUE') {
$this->settings['lists_colortracks'] = true;
} else {
$this->settings['lists_colortracks'] = false;
}
// display checkpoints panel (TMF) or pop-up (TMN)?
if (strtoupper($aseco['DISPLAY_CHECKPOINTS'][0]) == 'TRUE') {
$this->settings['display_checkpoints'] = true;
} else {
$this->settings['display_checkpoints'] = false;
}
// enable /cpsspec command (TMF-only)?
if (strtoupper($aseco['ENABLE_CPSSPEC'][0]) == 'TRUE') {
$this->settings['enable_cpsspec'] = true;
} else {
$this->settings['enable_cpsspec'] = false;
}
// automatically enable /cps for new players?
if (strtoupper($aseco['AUTO_ENABLE_CPS'][0]) == 'TRUE') {
$this->settings['auto_enable_cps'] = true;
} else {
$this->settings['auto_enable_cps'] = false;
}
// automatically enable /dedicps for new players?
if (strtoupper($aseco['AUTO_ENABLE_DEDICPS'][0]) == 'TRUE') {
$this->settings['auto_enable_dedicps'] = true;
} else {
$this->settings['auto_enable_dedicps'] = false;
}
// automatically add IP for new admins/operators?
if (strtoupper($aseco['AUTO_ADMIN_ADDIP'][0]) == 'TRUE') {
$this->settings['auto_admin_addip'] = true;
} else {
$this->settings['auto_admin_addip'] = false;
}
// automatically force spectator on player using /afk ?
if (strtoupper($aseco['AFK_FORCE_SPEC'][0]) == 'TRUE') {
$this->settings['afk_force_spec'] = true;
} else {
$this->settings['afk_force_spec'] = false;
}
// provide clickable buttons in TMF lists?
if (strtoupper($aseco['CLICKABLE_LISTS'][0]) == 'TRUE') {
$this->settings['clickable_lists'] = true;
} else {
$this->settings['clickable_lists'] = false;
}
// show logins in /recs on TMF?
if (strtoupper($aseco['SHOW_REC_LOGINS'][0]) == 'TRUE') {
$this->settings['show_rec_logins'] = true;
} else {
$this->settings['show_rec_logins'] = false;
}
// display individual stats panels at TMF scoreboard?
if (strtoupper($aseco['SB_STATS_PANELS'][0]) == 'TRUE') {
$this->settings['sb_stats_panels'] = true;
} else {
$this->settings['sb_stats_panels'] = false;
}
// read the XML structure into an array
$tmserver = $settings['SETTINGS']['TMSERVER'][0];
// read settings and apply them
$this->server->login = $tmserver['LOGIN'][0];
$this->server->pass = $tmserver['PASSWORD'][0];
$this->server->port = $tmserver['PORT'][0];
$this->server->ip = $tmserver['IP'][0];
if (isset($tmserver['TIMEOUT'][0])) {
$this->server->timeout = (int)$tmserver['TIMEOUT'][0];
} else {
$this->server->timeout = null;
trigger_error('Server init timeout not specified in config.xml !', E_USER_WARNING);
}
$this->style = array();
$this->panels = array();
$this->panels['admin'] = '';
$this->panels['donate'] = '';
$this->panels['records'] = '';
$this->panels['vote'] = '';
if ($this->settings['admin_client'] != '' &&
preg_match('/^2\.11\.[12][0-9]$/', $this->settings['admin_client']) != 1 ||
$this->settings['admin_client'] == '2.11.10')
trigger_error('Invalid admin client version : ' . $this->settings['admin_client'] . ' !', E_USER_ERROR);
if ($this->settings['player_client'] != '' &&
preg_match('/^2\.11\.[12][0-9]$/', $this->settings['player_client']) != 1 ||
$this->settings['player_client'] == '2.11.10')
trigger_error('Invalid player client version: ' . $this->settings['player_client'] . ' !', E_USER_ERROR);
} else {
// could not parse XML file
trigger_error('Could not read/parse config file ' . $config_file . ' !', E_USER_ERROR);
}
} // loadSettings
/**
* Read Admin/Operator/Ability lists and apply them on the current instance.
*/
function readLists() {
// get lists file name
$adminops_file = $this->settings['adminops_file'];
if ($lists = $this->xml_parser->parseXml($adminops_file, true, true)) {
// read the XML structure into arrays
$this->titles = $lists['LISTS']['TITLES'][0];
if (is_array($lists['LISTS']['ADMINS'][0])) {
$this->admin_list = $lists['LISTS']['ADMINS'][0];
// check admin list consistency
if (empty($this->admin_list['IPADDRESS'])) {
// fill <ipaddress> list to same length as <tmlogin> list
if (($cnt = count($this->admin_list['TMLOGIN'])) > 0)
$this->admin_list['IPADDRESS'] = array_fill(0, $cnt, '');
} else {
if (count($this->admin_list['TMLOGIN']) != count($this->admin_list['IPADDRESS']))
trigger_error("Admin mismatch between <tmlogin>'s and <ipaddress>'s!", E_USER_WARNING);
}
}
if (is_array($lists['LISTS']['OPERATORS'][0])) {
$this->operator_list = $lists['LISTS']['OPERATORS'][0];
// check operator list consistency
if (empty($this->operator_list['IPADDRESS'])) {
// fill <ipaddress> list to same length as <tmlogin> list
if (($cnt = count($this->operator_list['TMLOGIN'])) > 0)
$this->operator_list['IPADDRESS'] = array_fill(0, $cnt, '');
} else {
if (count($this->operator_list['TMLOGIN']) != count($this->operator_list['IPADDRESS']))
trigger_error("Operators mismatch between <tmlogin>'s and <ipaddress>'s!", E_USER_WARNING);
}
}
$this->adm_abilities = $lists['LISTS']['ADMIN_ABILITIES'][0];
$this->op_abilities = $lists['LISTS']['OPERATOR_ABILITIES'][0];
// convert strings to booleans
foreach ($this->adm_abilities as $ability => $value) {
if (strtoupper($value[0]) == 'TRUE') {
$this->adm_abilities[$ability][0] = true;
} else {
$this->adm_abilities[$ability][0] = false;
}
}
foreach ($this->op_abilities as $ability => $value) {
if (strtoupper($value[0]) == 'TRUE') {
$this->op_abilities[$ability][0] = true;
} else {
$this->op_abilities[$ability][0] = false;
}
}
return true;
} else {
// could not parse XML file
trigger_error('Could not read/parse adminops file ' . $adminops_file . ' !', E_USER_WARNING);
return false;
}
} // readLists
/**
* Write Admin/Operator/Ability lists to save them for future runs.
*/
function writeLists() {
// get lists file name
$adminops_file = $this->settings['adminops_file'];
// compile lists file contents
$lists = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" . CRLF
. "<lists>" . CRLF
. "\t<titles>" . CRLF;
foreach ($this->titles as $title => $value) {
$lists .= "\t\t<" . strtolower($title) . ">" .
$value[0]
. "</" . strtolower($title) . ">" . CRLF;
}
$lists .= "\t</titles>" . CRLF
. CRLF
. "\t<admins>" . CRLF;
$empty = true;
if (isset($this->admin_list['TMLOGIN'])) {
for ($i = 0; $i < count($this->admin_list['TMLOGIN']); $i++) {
if ($this->admin_list['TMLOGIN'][$i] != '') {
$lists .= "\t\t<tmlogin>" . $this->admin_list['TMLOGIN'][$i] . "</tmlogin>"
. " <ipaddress>" . $this->admin_list['IPADDRESS'][$i] . "</ipaddress>" . CRLF;
$empty = false;
}
}
}
if ($empty) {
$lists .= "<!-- format:" . CRLF
. "\t\t<tmlogin>YOUR_ADMIN_LOGIN</tmlogin> <ipaddress></ipaddress>" . CRLF
. "-->" . CRLF;
}
$lists .= "\t</admins>" . CRLF
. CRLF
. "\t<operators>" . CRLF;
$empty = true;
if (isset($this->operator_list['TMLOGIN'])) {
for ($i = 0; $i < count($this->operator_list['TMLOGIN']); $i++) {
if ($this->operator_list['TMLOGIN'][$i] != '') {
$lists .= "\t\t<tmlogin>" . $this->operator_list['TMLOGIN'][$i] . "</tmlogin>"
. " <ipaddress>" . $this->operator_list['IPADDRESS'][$i] . "</ipaddress>" . CRLF;
$empty = false;
}
}
}
if ($empty) {
$lists .= "<!-- format:" . CRLF
. "\t\t<tmlogin>YOUR_OPERATOR_LOGIN</tmlogin> <ipaddress></ipaddress>" . CRLF
. "-->" . CRLF;
}
$lists .= "\t</operators>" . CRLF
. CRLF
. "\t<admin_abilities>" . CRLF;
foreach ($this->adm_abilities as $ability => $value) {
$lists .= "\t\t<" . strtolower($ability) . ">" .
($value[0] ? "true" : "false")
. "</" . strtolower($ability) . ">" . CRLF;
}
$lists .= "\t</admin_abilities>" . CRLF
. CRLF
. "\t<operator_abilities>" . CRLF;
foreach ($this->op_abilities as $ability => $value) {
$lists .= "\t\t<" . strtolower($ability) . ">" .
($value[0] ? "true" : "false")
. "</" . strtolower($ability) . ">" . CRLF;
}
$lists .= "\t</operator_abilities>" . CRLF
. "</lists>" . CRLF;
// write out the lists file
if (!@file_put_contents($adminops_file, $lists)) {
trigger_error('Could not write adminops file ' . $adminops_file . ' !', E_USER_WARNING);
return false;
} else {
return true;
}
} // writeLists
/**
* Read Banned IPs list and apply it on the current instance.
*/
function readIPs() {
// get banned IPs file name
$bannedips_file = $this->settings['bannedips_file'];
if ($list = $this->xml_parser->parseXml($bannedips_file)) {
// read the XML structure into variable
if (isset($list['BAN_LIST']['IPADDRESS']))
$this->bannedips = $list['BAN_LIST']['IPADDRESS'];
else
$this->bannedips = array();
return true;
} else {
// could not parse XML file
trigger_error('Could not read/parse banned IPs file ' . $bannedips_file . ' !', E_USER_WARNING);
return false;
}
} // readIPs
/**
* Write Banned IPs list to save it for future runs.
*/
function writeIPs() {
// get banned IPs file name
$bannedips_file = $this->settings['bannedips_file'];
$empty = true;
// compile banned IPs file contents
$list = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" . CRLF
. "<ban_list>" . CRLF;
for ($i = 0; $i < count($this->bannedips); $i++) {
if ($this->bannedips[$i] != '') {
$list .= "\t\t<ipaddress>" . $this->bannedips[$i] . "</ipaddress>" . CRLF;
$empty = false;
}
}
if ($empty) {
$list .= "<!-- format:" . CRLF
. "\t\t<ipaddress>xx.xx.xx.xx</ipaddress>" . CRLF
. "-->" . CRLF;
}
$list .= "</ban_list>" . CRLF;
// write out the list file
if (!@file_put_contents($bannedips_file, $list)) {
trigger_error('Could not write banned IPs file ' . $bannedips_file . ' !', E_USER_WARNING);
return false;
} else {
return true;
}
} // writeIPs
/**
* Loads files in the plugins directory.
*/
function loadPlugins() {
// load and parse the plugins file
if ($plugins = $this->xml_parser->parseXml('plugins.xml')) {
if (!empty($plugins['ASECO_PLUGINS']['PLUGIN'])) {
// take each plugin tag
foreach ($plugins['ASECO_PLUGINS']['PLUGIN'] as $plugin) {
// log plugin message
$this->console_text('[XAseco] Load plugin [' . $plugin . ']');
// include the plugin
require_once('plugins/' . $plugin);
$this->plugins[] = $plugin;
}
}
} else {
trigger_error('Could not read/parse plugins list plugins.xml !', E_USER_ERROR);
}
} // loadPlugins
/**
* Runs the server.
*/
function run($config_file) {
// load new settings, if available
$this->console_text('[XAseco] Load settings [{1}]', $config_file);
$this->loadSettings($config_file);
// load admin/operator/ability lists, if available
$this->console_text('[XAseco] Load admin/ops lists [{1}]', $this->settings['adminops_file']);
$this->readLists();
// load banned IPs list, if available
$this->console_text('[XAseco] Load banned IPs list [{1}]', $this->settings['bannedips_file']);
$this->readIPs();
// load plugins and register chat commands
$this->console_text('[XAseco] Load plugins list [plugins.xml]');
$this->loadPlugins();
// connect to Trackmania Dedicated Server
if (!$this->connect()) {
// kill program with an error
trigger_error('Connection could not be established !', E_USER_ERROR);
}
// log status message
$this->console('Connection established successfully !');
// log admin lock message
if ($this->settings['lock_password'] != '')
$this->console_text("[XAseco] Locked admin commands & features with password '{1}'", $this->settings['lock_password']);
// get basic server info
$this->client->query('GetVersion');
$response['version'] = $this->client->getResponse();
$this->server->game = $response['version']['Name'];
$this->server->version = $response['version']['Version'];
$this->server->build = $response['version']['Build'];
// throw 'starting up' event
$this->releaseEvent('onStartup', null);
// synchronize information with server
$this->serverSync();
// register all chat commands
if ($this->server->getGame() != 'TMF') {
$this->registerChatCommands();
// set spectator not available outside TMF
if ($this->settings['cheater_action'] == 1)
$this->settings['cheater_action'] = 0;
}
// make a visual header
$this->sendHeader();
// get current game infos if server loaded a track yet
if ($this->currstatus == 100) {
$this->console_text('[XAseco] Waiting for the server to start a challenge');
} else {
$this->beginRace(false);
}
// main loop
$this->startup_phase = false;
while (true) {
$starttime = microtime(true);
// get callbacks from the server
$this->executeCallbacks();
// sends calls to the server
$this->executeCalls();
// throw timing events
$this->releaseEvent('onMainLoop', null);
$this->currsecond = time();
if ($this->prevsecond != $this->currsecond) {
$this->prevsecond = $this->currsecond;
$this->releaseEvent('onEverySecond', null);
}
// reduce CPU usage if main loop has time left
$endtime = microtime(true);
$delay = 200000 - ($endtime - $starttime) * 1000000;
if ($delay > 0)
usleep($delay);
// make sure the script does not timeout
@set_time_limit($this->settings['script_timeout']);
}
// close the client connection
$this->client->Terminate();
} // run
/**
* Authenticates XASECO at the server.
*/
function connect() {
// only if logins are set
if ($this->server->ip && $this->server->port && $this->server->login && $this->server->pass) {
// log console message
$this->console('Try to connect to TM dedicated server on {1}:{2} timeout {3}s',
$this->server->ip, $this->server->port,
($this->server->timeout !== null ? $this->server->timeout : 0));
// connect to the server
if (!$this->client->InitWithIp($this->server->ip, $this->server->port, $this->server->timeout)) {
trigger_error('[' . $this->client->getErrorCode() . '] InitWithIp - ' . $this->client->getErrorMessage(), E_USER_WARNING);
return false;
}
// log console message
$this->console("Try to authenticate with login '{1}' and password '{2}'",
$this->server->login, $this->server->pass);
// check login
if ($this->server->login != 'SuperAdmin') {
trigger_error("Invalid login '" . $this->server->login . "' - must be 'SuperAdmin' in config.xml !", E_USER_WARNING);
return false;
}
// check password
if ($this->server->pass == 'SuperAdmin') {
trigger_error("Insecure password '" . $this->server->pass . "' - should be changed in dedicated config and config.xml !", E_USER_WARNING);
}
// log into the server
if (!$this->client->query('Authenticate', $this->server->login, $this->server->pass)) {
trigger_error('[' . $this->client->getErrorCode() . '] Authenticate - ' . $this->client->getErrorMessage(), E_USER_WARNING);
return false;
}
// enable callback system
$this->client->query('EnableCallbacks', true);
// wait for server to be ready
$this->waitServerReady();
// connection established
return true;
} else {
// connection failed
return false;
}
} // connect
/**
* Waits for the server to be ready (status 4, 'Running - Play')
*/
function waitServerReady() {
$this->client->query('GetStatus');
$status = $this->client->getResponse();
if ($status['Code'] != 4) {
$this->console("Waiting for dedicated server to reach status 'Running - Play'...");
$this->console('Status: ' . $status['Name']);
$timeout = 0;
$laststatus = $status['Name'];
while ($status['Code'] != 4) {
sleep(1);
$this->client->query('GetStatus');
$status = $this->client->getResponse();
if ($laststatus != $status['Name']) {
$this->console('Status: ' . $status['Name']);
$laststatus = $status['Name'];
}
if (isset($this->server->timeout) && $timeout++ > $this->server->timeout)
trigger_error('Timed out while waiting for dedicated server!', E_USER_ERROR);
}
}
} // waitServerReady
/**
* Initializes the server and the player list.
* Reads a list of the players who are on the server already,
* and loads all server variables.
*/
function serverSync() {
// check server build
if (strlen($this->server->build) == 0 ||
($this->server->getGame() != 'TMF' && strcmp($this->server->build, TMN_BUILD) < 0) ||
($this->server->getGame() == 'TMF' && strcmp($this->server->build, TMF_BUILD) < 0)) {
trigger_error("Obsolete server build '" . $this->server->build . "' - must be " .
($this->server->getGame() == 'TMF' ? "at least '" . TMF_BUILD . "' !" : "'" . TMN_BUILD . "' !"), E_USER_ERROR);
}
// get server id, login, nickname, zone & packmask
$this->server->id = 0; // on TMN/TMO/TMS
$this->server->rights = false;
$this->server->isrelay = false;
$this->server->relaymaster = null;
$this->server->relayslist = array();
$this->server->gamestate = Server::RACE;
$this->server->packmask = '';
if ($this->server->getGame() == 'TMF') {
$this->client->query('GetSystemInfo');
$response['system'] = $this->client->getResponse();
$this->server->serverlogin = $response['system']['ServerLogin'];
$this->client->query('GetDetailedPlayerInfo', $this->server->serverlogin);
$response['info'] = $this->client->getResponse();
$this->server->id = $response['info']['PlayerId'];
$this->server->nickname = $response['info']['NickName'];
$this->server->zone = substr($response['info']['Path'], 6); // strip 'World|'
$this->server->rights = ($response['info']['OnlineRights'] == 3); // United = true
$this->client->query('GetLadderServerLimits');
$response['ladder'] = $this->client->getResponse();
$this->server->laddermin = $response['ladder']['LadderServerLimitMin'];
$this->server->laddermax = $response['ladder']['LadderServerLimitMax'];
$this->client->query('IsRelayServer');
$this->server->isrelay = ($this->client->getResponse() > 0);
if ($this->server->isrelay) {
$this->client->query('GetMainServerPlayerInfo', 1);
$this->server->relaymaster = $this->client->getResponse();
}
// TMNF packmask = 'Stadium' for 'nations' or 'stadium'
$this->client->query('GetServerPackMask');
$this->server->packmask = $this->client->getResponse();
// clear possible leftover ManiaLinks
$this->client->query('SendHideManialinkPage');
}
// get mode & limits
$this->client->query('GetCurrentGameInfo', ($this->server->getGame() == 'TMF' ? 1 : 0));
$response['gameinfo'] = $this->client->getResponse();
$this->server->gameinfo = new Gameinfo($response['gameinfo']);
// get status
$this->client->query('GetStatus');
$response['status'] = $this->client->getResponse();
$this->currstatus = $response['status']['Code'];
// get game & trackdir
$this->client->query('GameDataDirectory');
$this->server->gamedir = $this->client->getResponse();
$this->client->query('GetTracksDirectory');
$this->server->trackdir = $this->client->getResponse();
// get server name & options
$this->getServerOptions();
// throw 'synchronisation' event
$this->releaseEvent('onSync', null);
// get current players/servers on the server (hardlimited to 300)
if ($this->server->getGame() == 'TMF')
$this->client->query('GetPlayerList', 300, 0, 2);
else
$this->client->query('GetPlayerList', 300, 0);
$response['playerlist'] = $this->client->getResponse();
// update players/relays lists
if (!empty($response['playerlist'])) {
foreach ($response['playerlist'] as $player) {
// fake it into thinking it's a connecting player:
// it gets team & ladder info this way & will also throw an
// onPlayerConnect event for players (not relays) to all plugins
$this->playerConnect(array($player['Login'], ''));
}
}
} // serverSync
/**
* Sends program header to console and ingame chat.
*/
function sendHeader() {
$this->console_text('###############################################################################');
$this->console_text(' XASECO v' . XASECO_VERSION . ' running on {1}:{2}', $this->server->ip, $this->server->port);
if ($this->server->getGame() == 'TMF') {
$this->console_text(' Name : {1} - {2}', stripColors($this->server->name, false), $this->server->serverlogin);
if ($this->server->isrelay)
$this->console_text(' Relays : {1} - {2}', stripColors($this->server->relaymaster['NickName'], false), $this->server->relaymaster['Login']);
$this->console_text(' Game : {1} {2} - {3} - {4}', $this->server->game,
($this->server->rights ? 'United' : 'Nations'),
$this->server->packmask, $this->server->gameinfo->getMode());
} else {
$this->console_text(' Name : {1}', stripColors($this->server->name, false));
$this->console_text(' Game : {1} - {2}', $this->server->game, $this->server->gameinfo->getMode());
}
$this->console_text(' Version: {1} / {2}', $this->server->version, $this->server->build);
$this->console_text(' Authors: Florian Schnell & Assembler Maniac');
$this->console_text(' Re-Authored: Xymph');
$this->console_text('###############################################################################');
// format the text of the message
$startup_msg = formatText($this->getChatMessage('STARTUP'),
XASECO_VERSION,
$this->server->ip, $this->server->port);
// show startup message
$this->client->query('ChatSendServerMessage', $this->formatColors($startup_msg));
} // sendHeader
/**
* Gets callbacks from the TM Dedicated Server and reacts on them.
*/
function executeCallbacks() {
// receive callbacks with a timeout (default: 2 ms)
$this->client->resetError();
$this->client->readCB();
// now get the responses out of the 'buffer'
$calls = $this->client->getCBResponses();
if ($this->client->isError()) {
trigger_error('ExecCallbacks XMLRPC Error [' . $this->client->getErrorCode() . '] - ' . $this->client->getErrorMessage(), E_USER_ERROR);
}
if (!empty($calls)) {
while ($call = array_shift($calls)) {
switch ($call[0]) {
case 'TrackMania.PlayerConnect': // [0]=Login, [1]=IsSpectator
$this->playerConnect($call[1]);
break;
case 'TrackMania.PlayerDisconnect': // [0]=Login
$this->playerDisconnect($call[1]);
break;
case 'TrackMania.PlayerChat': // [0]=PlayerUid, [1]=Login, [2]=Text, [3]=IsRegistredCmd
$this->playerChat($call[1]);
$this->releaseEvent('onChat', $call[1]);
break;
case 'TrackMania.PlayerServerMessageAnswer': // [0]=PlayerUid, [1]=Login, [2]=Answer
$this->playerServerMessageAnswer($call[1]);
break;
case 'TrackMania.PlayerCheckpoint': // TMN: [0]=PlayerUid, [1]=Login, [2]=Time, [3]=Score, [4]=CheckpointIndex; TMF: [0]=PlayerUid, [1]=Login, [2]=TimeOrScore, [3]=CurLap, [4]=CheckpointIndex
if (!$this->server->isrelay)
$this->releaseEvent('onCheckpoint', $call[1]);
break;
case 'TrackMania.PlayerFinish': // [0]=PlayerUid, [1]=Login, [2]=TimeOrScore
$this->playerFinish($call[1]);
break;
case 'TrackMania.BeginRace': // [0]=Challenge
if ($this->server->getGame() != 'TMF')
$this->beginRace($call[1]);
break;
case 'TrackMania.EndRace': // [0]=Rankings[], [1]=Challenge
if ($this->server->getGame() != 'TMF')
$this->endRace($call[1]);
break;
case 'TrackMania.BeginRound': // none
$this->beginRound();
break;
case 'TrackMania.StatusChanged': // [0]=StatusCode, [1]=StatusName
// update status changes
$this->prevstatus = $this->currstatus;
$this->currstatus = $call[1][0];
// if TMF mode Sync, check WarmUp state
if ($this->server->getGame() == 'TMF') {
if ($this->currstatus == 3 || $this->currstatus == 5) {
$this->client->query('GetWarmUp');
$this->warmup_phase = $this->client->getResponse();
}
} else {
$this->warmup_phase = false;
}
// on TMF, use real EndRound callback
if ($this->server->getGame() != 'TMF') {
// if change from Play (4) to Sync (3) or Finish (5),
// it's the end of a round
if ($this->prevstatus == 4 && ($this->currstatus == 3 || $this->currstatus == 5))
$this->endRound();
}
if ($this->currstatus == 4) { // Running - Play
$this->runningPlay();
}
$this->releaseEvent('onStatusChangeTo' . $this->currstatus, $call[1]);
break;
// new TMF callbacks:
case 'TrackMania.EndRound': // none
$this->endRound();
break;
case 'TrackMania.BeginChallenge': // [0]=Challenge, [1]=WarmUp, [2]=MatchContinuation
$this->beginRace($call[1]);
break;
case 'TrackMania.EndChallenge': // [0]=Rankings[], [1]=Challenge, [2]=WasWarmUp, [3]=MatchContinuesOnNextChallenge, [4]=RestartChallenge
$this->endRace($call[1]);
break;
case 'TrackMania.PlayerManialinkPageAnswer': // [0]=PlayerUid, [1]=Login, [2]=Answer
$this->releaseEvent('onPlayerManialinkPageAnswer', $call[1]);
break;
case 'TrackMania.BillUpdated': // [0]=BillId, [1]=State, [2]=StateName, [3]=TransactionId
$this->releaseEvent('onBillUpdated', $call[1]);
break;
case 'TrackMania.ChallengeListModified': // [0]=CurChallengeIndex, [1]=NextChallengeIndex, [2]=IsListModified
$this->releaseEvent('onChallengeListModified', $call[1]);
break;
case 'TrackMania.PlayerInfoChanged': // [0]=PlayerInfo
$this->playerInfoChanged($call[1][0]);
break;
case 'TrackMania.PlayerIncoherence': // [0]=PlayerUid, [1]=Login
$this->releaseEvent('onPlayerIncoherence', $call[1]);
break;
case 'TrackMania.TunnelDataReceived': // [0]=PlayerUid, [1]=Login, [2]=Data
$this->releaseEvent('onTunnelDataReceived', $call[1]);
break;
case 'TrackMania.Echo': // [0]=Internal, [1]=Public
$this->releaseEvent('onEcho', $call[1]);
break;
case 'TrackMania.ManualFlowControlTransition': // [0]=Transition
$this->releaseEvent('onManualFlowControlTransition', $call[1]);
break;
case 'TrackMania.VoteUpdated': // [0]=StateName, [1]=Login, [2]=CmdName, [3]=CmdParam
$this->releaseEvent('onVoteUpdated', $call[1]);
break;
default:
// do nothing
}
}
return $calls;
} else {
return false;
}
} // executeCallbacks
/**
* Adds calls to a multiquery.
* It's possible to set a callback function which
* will be executed on incoming response.
* You can also set an ID to read response later on.
*/
function addCall($call, $params = array(), $id = 0, $callback_func = false) {
// adds call and registers a callback if needed
$index = $this->client->addCall($call, $params);
$rpc_call = new RPCCall($id, $index, $callback_func, array($call, $params));
$this->rpc_calls[] = $rpc_call;
} // addCall
/**
* Executes a multicall and gets responses.
* Saves responses in array with IDs as keys.
*/
function executeCalls() {
// clear responses
$this->rpc_responses = array();
// stop if there are no rpc calls in query
if (empty($this->client->calls)) {
return true;
}
$this->client->resetError();
$tmpcalls = $this->client->calls; // debugging code to find UTF-8 errors
// sends multiquery to the server and gets the response
if ($this->client->multiquery()) {
if ($this->client->isError()) {
$this->console_text(print_r($tmpcalls, true));
trigger_error('ExecCalls XMLRPC Error [' . $this->client->getErrorCode() . '] - ' . $this->client->getErrorMessage(), E_USER_ERROR);
}
// get new response from server
$responses = $this->client->getResponse();
// handle server responses
foreach ($this->rpc_calls as $call) {
// display error message if needed
$err = false;
if (isset($responses[$call->index]['faultString'])) {
$this->rpcErrorResponse($responses[$call->index]);
print_r($call->call);
$err = true;
}
// if an id was set, then save the response under the specified id
if ($call->id) {
$this->rpc_responses[$call->id] = $responses[$call->index][0];
}
// if a callback function has been set, then execute it
if ($call->callback && !$err) {
if (function_exists($call->callback)) {
// callback the function with the response as parameter
call_user_func($call->callback, $responses[$call->index][0]);
}
// if a function with the name of the callback wasn't found, then
// try to execute a method with its name
elseif (method_exists($this, $call->callback)) {
// callback the method with the response as parameter
call_user_func(array($this, $call->callback), $responses[$call->index][0]);
}
}
}
}
// clear calls
$this->rpc_calls = array();
} // executeCalls
/**
* Documents RPC Errors.
*/
function rpcErrorResponse($response) {
$this->console_text('[RPC Error ' . $response['faultCode'] . '] ' . $response['faultString']);
} // rpcErrorResponse
/**
* Registers functions which are called on specific events.
*/
function registerEvent($event_type, $callback_func) {
// registers a new event
$this->events[$event_type][] = $callback_func;
} // registerEvent
/**
* Executes the functions which were registered for specified events.
*/
function releaseEvent($event_type, $func_param) {
// executes registered event functions
// if there are any events for that type
if (!empty($this->events[$event_type])) {
// for each registered function of this type
foreach ($this->events[$event_type] as $func_name) {
// if function for the specified player connect event can be found
if (is_callable($func_name)) {
// ... execute it!
call_user_func($func_name, $this, $func_param);
}
}
}
} // releaseEvent
/**
* Stores a new user command.
*/
function addChatCommand($command_name, $command_help, $command_is_admin = false) {
$chat_command = new ChatCommand($command_name, $command_help, $command_is_admin);
$this->chat_commands[] = $chat_command;
} // addChatCommand
/**
* Registers all chat commands with the server.
*/
function registerChatCommands() {
// clear the current list of chat commands
$this->client->query('CleanChatCommand');
if (isset($this->chat_commands)) {
foreach ($this->chat_commands as $command) {
// only if it's no admin command
if (!$command->isadmin) {
// log message if debug mode is set to true
if ($this->debug) {
$this->console_text('register chat command: ' . $command->name);
}
// register chat command at server
$this->client->query('AddChatCommand', $command->name);
}
}
}
} // registerChatCommands
/**
* When a round is started, signal the event.
*/
function beginRound() {
$this->console_text('Begin Round');
$this->releaseEvent('onBeginRound', null);
} // beginRound
/**
* When a round is ended, signal the event.
*/
function endRound() {
$this->console_text('End Round');
$this->releaseEvent('onEndRound', null);
} // endRound
/**
* When a TMF player's info changed, signal the event. Fields:
* Login, NickName, PlayerId, TeamId, SpectatorStatus, LadderRanking, Flags
*/
function playerInfoChanged($playerinfo) {
// on relay, check for player from master server
if ($this->server->isrelay && floor($playerinfo['Flags'] / 10000) % 10 != 0)
return;
// check for valid player
if (!$player = $this->server->players->getPlayer($playerinfo['Login']))
return;
// check ladder ranking
if ($playerinfo['LadderRanking'] > 0) {
$player->ladderrank = $playerinfo['LadderRanking'];
$player->isofficial = true;
} else {
$player->isofficial = false;
}
// check spectator status (ignoring temporary changes)
$player->prevstatus = $player->isspectator;
if (($playerinfo['SpectatorStatus'] % 10) != 0)
$player->isspectator = true;
else
$player->isspectator = false;
$this->releaseEvent('onPlayerInfoChanged', $playerinfo);
} // playerInfoChanged
/**
* When a new track is started we have to get information
* about the new track and so on.
*/
function runningPlay() {
// request information about the new challenge
// ... and callback to function newChallenge()
} // runningPlay
/**
* When a new race is started we have to get information
* about the new track and so on.
*/
function beginRace($race) {
// request information about the new challenge
// ... and callback to function newChallenge()
// if TMF new challenge, check WarmUp state
if ($this->server->getGame() == 'TMF' && $race)
$this->warmup_phase = $race[1];
if (!$race) {
$this->addCall('GetCurrentChallengeInfo', array(), '', 'newChallenge');
} else {
$this->newChallenge($race[0]);
}
} // beginRace
/**
* Reacts on new challenges.
* Gets record to current challenge etc.
*/
function newChallenge($challenge) {
// log if not a restart
$this->server->gamestate = Server::RACE;
if ($this->restarting == 0)
$this->console_text('Begin Challenge');
// refresh game info
$this->client->query('GetCurrentGameInfo', ($this->server->getGame() == 'TMF' ? 1 : 0));
$gameinfo = $this->client->getResponse();
$this->server->gameinfo = new Gameinfo($gameinfo);
// check for TMF restarting challenge
$this->changingmode = false;
if ($this->server->getGame() == 'TMF' && $this->restarting > 0) {
// check type of restart and signal an instant one
if ($this->restarting == 2) {
$this->restarting = 0;
} else { // == 1
$this->restarting = 0;
// throw postfix 'restart challenge' event
$this->releaseEvent('onRestartChallenge2', $challenge);
return;
}
}
// refresh server name & options
$this->getServerOptions();
// reset record list
$this->server->records->clear();
// reset player votings
//$this->server->players->resetVotings();
// create new challenge object
$challenge_item = new Challenge($challenge);
// in TMF Rounds/Team/Cup mode if multilap track, get forced laps
if ($this->server->getGame() == 'TMF' && $challenge_item->laprace &&
($this->server->gameinfo->mode == Gameinfo::RNDS ||
$this->server->gameinfo->mode == Gameinfo::TEAM ||
$this->server->gameinfo->mode == Gameinfo::CUP)) {
$challenge_item->forcedlaps = $this->server->gameinfo->forcedlaps;
}
// obtain challenge's GBX data, TMX info & records
$challenge_item->gbx = new GBXChallMapFetcher(true);
try
{
$challenge_item->gbx->processFile($this->server->trackdir . $challenge_item->filename);
}
catch (Exception $e)
{
trigger_error($e->getMessage(), E_USER_WARNING);
}
$challenge_item->tmx = findTMXdata($challenge_item->uid, $challenge_item->environment, $challenge_item->gbx->exeVer, true);
// throw main 'begin challenge' event
$this->releaseEvent('onNewChallenge', $challenge_item);
// log console message
$this->console('track changed [{1}] >> [{2}]',
stripColors($this->server->challenge->name, false),
stripColors($challenge_item->name, false));
/* disabled in favor of RASP's karma system - Xymph
// log track's score
if ($challenge_item->score && $challenge_item->votes) {
// calculate avarage score and display
$this->console('average score of this track is {1}',
$challenge_item->score/$challenge_item->votes);
} else {
// no votings
$this->console('no votings available for this track');
}
disabled */
// check for relay server
if (!$this->server->isrelay) {
// check if record exists on new track
$cur_record = $this->server->records->getRecord(0);
if ($cur_record !== false && $cur_record->score > 0) {
$score = ($this->server->gameinfo->mode == Gameinfo::STNT ?
str_pad($cur_record->score, 5, ' ', STR_PAD_LEFT) :
formatTime($cur_record->score));
// log console message of current record
$this->console('current record on {1} is {2} and held by {3}',
stripColors($challenge_item->name, false),
trim($score),
stripColors($cur_record->player->nickname, false));
// replace parameters
$message = formatText($this->getChatMessage('RECORD_CURRENT'),
stripColors($challenge_item->name),
trim($score),
stripColors($cur_record->player->nickname));
} else {
if ($this->server->gameinfo->mode == Gameinfo::STNT)
$score = ' ---';
else
$score = ' --.--';
// log console message of no record
$this->console('currently no record on {1}',
stripColors($challenge_item->name, false));
// replace parameters
$message = formatText($this->getChatMessage('RECORD_NONE'),
stripColors($challenge_item->name));
}
if (function_exists('setRecordsPanel'))
setRecordsPanel('local', $score);
// if no trackrecs, show the original record message to all players
if (($this->settings['show_recs_before'] & 1) == 1) {
if (($this->settings['show_recs_before'] & 4) == 4 && function_exists('send_window_message'))
send_window_message($this, $message, false);
else
$this->client->query('ChatSendServerMessage', $this->formatColors($message));
}
}
// update the field which contains current challenge
$this->server->challenge = $challenge_item;
// throw postfix 'begin challenge' event (various)
$this->releaseEvent('onNewChallenge2', $challenge_item);
// show top-8 & records of all online players before track
if (($this->settings['show_recs_before'] & 2) == 2 && function_exists('show_trackrecs')) {
show_trackrecs($this, false, 1, $this->settings['show_recs_before']); // from chat.records2.php
}
} // newChallenge
/**
* End of current race.
* Write records to database and/or display final statistics.
*/
function endRace($race) {
// check for TMF RestartChallenge flag
if ($this->server->getGame() == 'TMF' && $race[4]) {
$this->restarting = 1;
// check whether changing game mode or any player has a time/score,
// then there will be ChatTime, otherwise it's an instant restart
if ($this->changingmode)
$this->restarting = 2;
else
foreach ($race[0] as $pl) {
if ($pl['BestTime'] > 0 || $pl['Score'] > 0) {
$this->restarting = 2;
break;
}
}
// log type of restart and signal an instant one
if ($this->restarting == 2) {
$this->console_text('Restart Challenge (with ChatTime)');
} else { // == 1
$this->console_text('Restart Challenge (instant)');
// throw main 'restart challenge' event
$this->releaseEvent('onRestartChallenge', $race);
return;
}
}
// log if not a restart
$this->server->gamestate = Server::SCORE;
if ($this->restarting == 0)
$this->console_text('End Challenge');
// show top-8 & all new records after track
if (($this->settings['show_recs_after'] & 2) == 2 && function_exists('show_trackrecs')) {
show_trackrecs($this, false, 3, $this->settings['show_recs_after']); // from chat.records2.php
} elseif (($this->settings['show_recs_after'] & 1) == 1) {
// fall back on old top-5
$records = '';
if ($this->server->records->count() == 0) {
// display a no-new-record message
$message = formatText($this->getChatMessage('RANKING_NONE'),
stripColors($this->server->challenge->name),
'after');
} else {
// display new records set up this round
$message = formatText($this->getChatMessage('RANKING'),
stripColors($this->server->challenge->name),
'after');
// go through each record
for ($i = 0; $i < 5; $i++) {
$cur_record = $this->server->records->getRecord($i);
// if the record is set then display it
if ($cur_record !== false && $cur_record->score > 0) {
// replace parameters
$record_msg = formatText($this->getChatMessage('RANKING_RECORD_NEW'),
$i+1,
stripColors($cur_record->player->nickname),
($this->server->gameinfo->mode == Gameinfo::STNT ?
$cur_record->score : formatTime($cur_record->score)));
$records .= $record_msg;
}
}
}
// append the records if any
if ($records != '') {
$records = substr($records, 0, strlen($records)-2); // strip trailing ", "
$message .= LF . $records;
}
// show ranking message to all players
if (($this->settings['show_recs_after'] & 4) == 4 && function_exists('send_window_message'))
send_window_message($this, $message, true);
else
$this->client->query('ChatSendServerMessage', $this->formatColors($message));
}
// get rankings and call endRaceRanking as soon as we have them
// $this->addCall('GetCurrentRanking', array(2, 0), false, 'endRaceRanking');
if (!$this->server->isrelay)
$this->endRaceRanking($race[0]);
// throw prefix 'end challenge' event (chat-based votes)
$this->releaseEvent('onEndRace1', $race);
// throw main 'end challenge' event
$this->releaseEvent('onEndRace', $race);
} // endRace
/**
* Check out who won the current track and increment his/her wins by one.
*/
function endRaceRanking($ranking) {
// check for online login
if (isset($ranking[0]['Login']) &&
($player = $this->server->players->getPlayer($ranking[0]['Login'])) !== false) {
// check for winner if there's more than one player
if ($ranking[0]['Rank'] == 1 && count($ranking) > 1 &&
($this->server->gameinfo->mode == Gameinfo::STNT ?
($ranking[0]['Score'] > 0) : ($ranking[0]['BestTime'] > 0))) {
// increase the player's wins
$player->newwins++;
// log console message
$this->console('{1} won for the {2}. time!',
$player->login, $player->getWins());
if ($player->getWins() % $this->settings['global_win_multiple'] == 0) {
// replace parameters
$message = formatText($this->getChatMessage('WIN_MULTI'),
stripColors($player->nickname), $player->getWins());
// show chat message
$this->client->query('ChatSendServerMessage', $this->formatColors($message));
} else {
// replace parameters
$message = formatText($this->getChatMessage('WIN_NEW'),
$player->getWins());
// show chat message
$this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login);
}
// throw 'player wins' event
$this->releaseEvent('onPlayerWins', $player);
}
}
} // endRaceRanking
/**
* Handles connections of new players.
*/
function playerConnect($player) {
// request information about the new player
// (removed callback mechanism here, as GetPlayerInfo occasionally
// returns no data and then the connecting login would be lost)
$login = $player[0];
if ($this->server->getGame() == 'TMF') {
$this->client->query('GetDetailedPlayerInfo', $login);
$playerd = $this->client->getResponse();
$this->client->query('GetPlayerInfo', $login, 1);
} else { // TMN/TMS/TMO
$this->client->query('GetPlayerInfo', $login);
}
$player = $this->client->getResponse();
// check for server
if (isset($player['Flags']) && floor($player['Flags'] / 100000) % 10 != 0) {
// register relay server
if (!$this->server->isrelay && $player['Login'] != $this->server->serverlogin) {
$this->server->relayslist[$player['Login']] = $player;
// log console message
$this->console('<<< relay server {1} ({2}) connected', $player['Login'],
stripColors($player['NickName'], false));
}
// on relay, check for player from master server
} elseif ($this->server->isrelay && floor($player['Flags'] / 10000) % 10 != 0) {
; // ignore
} else {
$ipaddr = isset($playerd['IPAddress']) ? preg_replace('/:\d+/', '', $playerd['IPAddress']) : ''; // strip port
// if no data fetched, notify & kick the player
if (!isset($player['Login']) || $player['Login'] == '') {
$message = str_replace('{br}', LF, $this->getChatMessage('CONNECT_ERROR'));
$message = $this->formatColors($message);
$this->client->query('ChatSendServerMessageToLogin', str_replace(LF.LF, LF, $message), $login);
if ($this->server->getGame() == 'TMN')
$this->client->query('SendDisplayServerMessageToLogin', $login, $message, 'OK', '', 0);
sleep(5); // allow time to connect and see the notice
if ($this->server->getGame() == 'TMF')
$this->client->addCall('Kick', array($login, $this->formatColors($this->getChatMessage('CONNECT_DIALOG'))));
else
$this->client->addCall('Kick', array($login));
// log console message
$this->console('GetPlayerInfo failed for ' . $login . ' -- notified & kicked');
return;
// if player IP in ban list, notify & kick the player
} elseif (!empty($this->bannedips) && in_array($ipaddr, $this->bannedips)) {
$message = str_replace('{br}', LF, $this->getChatMessage('BANIP_ERROR'));
$message = $this->formatColors($message);
$this->client->query('ChatSendServerMessageToLogin', str_replace(LF.LF, LF, $message), $login);
if ($this->server->getGame() == 'TMN')
$this->client->query('SendDisplayServerMessageToLogin', $login, $message, 'OK', '', 0);
sleep(5); // allow time to connect and see the notice
if ($this->server->getGame() == 'TMF')
$this->client->addCall('Ban', array($login, $this->formatColors($this->getChatMessage('BANIP_DIALOG'))));
else
$this->client->addCall('Ban', array($login));
// log console message
$this->console('Player ' . $login . ' banned from ' . $ipaddr . ' -- notified & kicked');
return;
// client version checking on TMF
} elseif ($this->server->getGame() == 'TMF') {
// extract version number
$version = str_replace(')', '', preg_replace('/.*\(/', '', $playerd['ClientVersion']));
if ($version == '') $version = '2.11.11';
$message = str_replace('{br}', LF, $this->getChatMessage('CLIENT_ERROR'));
// if invalid version, notify & kick the player
if ($this->settings['player_client'] != '' &&
strcmp($version, $this->settings['player_client']) < 0) {
$this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $login);
sleep(5); // allow time to connect and see the notice
$this->client->addCall('Kick', array($login, $this->formatColors($this->getChatMessage('CLIENT_DIALOG'))));
// log console message
$this->console('Obsolete player client version ' . $version . ' for ' . $login . ' -- notified & kicked');
return;
}
// if invalid version, notify & kick the admin
if ($this->settings['admin_client'] != '' && $this->isAnyAdminL($player['Login']) &&
strcmp($version, $this->settings['admin_client']) < 0) {
$this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $login);
sleep(5); // allow time to connect and see the notice
$this->client->addCall('Kick', array($login, $this->formatColors($this->getChatMessage('CLIENT_DIALOG'))));
// log console message
$this->console('Obsolete admin client version ' . $version . ' for ' . $login . ' -- notified & kicked');
return;
}
}
// if no TMN team, try again via world stats
if ($this->server->getGame() == 'TMN' && !isLANLogin($login) &&
$player['LadderStats']['TeamName'] == '') {
$data = new TMNDataFetcher($login, false);
if ($data->teamname != '') {
$player['LadderStats']['TeamName'] = $data->teamname;
}
}
// create player object
$player_item = new Player($this->server->getGame() == 'TMF' ? $playerd : $player);
// set default window style & panels
$player_item->style = $this->style;
$player_item->panels['admin'] = $this->panels['admin'];
$player_item->panels['donate'] = $this->panels['donate'];
$player_item->panels['records'] = $this->panels['records'];
$player_item->panels['vote'] = $this->panels['vote'];
// adds a new player to the internal player list
$this->server->players->addPlayer($player_item);
// log console message
$this->console('<< player {1} joined the game [{2} : {3} : {4} : {5} : {6}]',
$player_item->pid,
$player_item->login,
$player_item->nickname,
$player_item->nation,
$player_item->ladderrank,
$player_item->ip);
// replace parameters
$message = formatText($this->getChatMessage('WELCOME'),
stripColors($player_item->nickname),
$this->server->name, XASECO_VERSION);
// hyperlink package name & version number on TMF
if ($this->server->getGame() == 'TMF')
$message = preg_replace('/XASECO.+' . XASECO_VERSION . '/', '$l[' . XASECO_TMN . ']$0$l', $message);
// send welcome popup or chat message
if ($this->settings['welcome_msg_window']) {
if ($this->server->getGame() == 'TMF') {
$message = str_replace('{#highlite}', '{#message}', $message);
$message = preg_split('/{br}/', $this->formatColors($message));
// repack all lines
foreach ($message as &$line)
$line = array($line);
display_manialink($player_item->login, '',
array('Icons64x64_1', 'Inbox'), $message,
array(1.2), 'OK');
} else { // TMN
$message = str_replace('{br}', LF, $this->formatColors($message));
$this->client->query('SendDisplayServerMessageToLogin', $player_item->login, $message, 'OK', '', 0);
}
} else {
$message = str_replace('{br}', LF, $this->formatColors($message));
$this->client->query('ChatSendServerMessageToLogin', str_replace(LF.LF, LF, $message), $player_item->login);
}
// if there's a record on current track
$cur_record = $this->server->records->getRecord(0);
if ($cur_record !== false && $cur_record->score > 0) {
// set message to the current record
$message = formatText($this->getChatMessage('RECORD_CURRENT'),
stripColors($this->server->challenge->name),
($this->server->gameinfo->mode == Gameinfo::STNT ?
$cur_record->score : formatTime($cur_record->score)),
stripColors($cur_record->player->nickname));
} else { // if there should be no record to display
// display a no-record message
$message = formatText($this->getChatMessage('RECORD_NONE'),
stripColors($this->server->challenge->name));
}
// show top-8 & records of all online players before track
if (($this->settings['show_recs_before'] & 2) == 2 && function_exists('show_trackrecs')) {
show_trackrecs($this, $player_item->login, 1, 0); // from chat.records2.php
} elseif (($this->settings['show_recs_before'] & 1) == 1) {
// or show original record message
$this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player_item->login);
}
// throw main 'player connects' event
$this->releaseEvent('onPlayerConnect', $player_item);
// throw postfix 'player connects' event (access control)
$this->releaseEvent('onPlayerConnect2', $player_item);
}
} // playerConnect
/**
* Handles disconnections of players.
*/
function playerDisconnect($player) {
// check for relay server
if (!$this->server->isrelay && array_key_exists($player[0], $this->server->relayslist)) {
// log console message
$this->console('>>> relay server {1} ({2}) disconnected', $player[0],
stripColors($this->server->relayslist[$player[0]]['NickName'], false));
unset($this->server->relayslist[$player[0]]);
return;
}
// delete player and put him into the player item
// ignore event if disconnect fluke after player already left,
// or on relay if player from master server (which wasn't added)
if (!$player_item = $this->server->players->removePlayer($player[0]))
return;
// log console message
$this->console('>> player {1} left the game [{2} : {3} : {4}]',
$player_item->pid,
$player_item->login,
$player_item->nickname,
formatTimeH($player_item->getTimeOnline() * 1000, false));
// throw 'player disconnects' event
$this->releaseEvent('onPlayerDisconnect', $player_item);
} // playerDisconnect
/**
* Handles clicks on server messageboxes.
*/
function playerServerMessageAnswer($answer) {
if ($answer[2]) {
// throw TMN 'click' event
$this->releaseEvent('onPlayerServerMessageAnswer', $answer);
}
} // playerServerMessageAnswer
/**
* Player reaches finish.
*/
function playerFinish($finish) {
// if no track info, or if server 'finish', bail out immediately
if ($this->server->challenge->name == '' || $finish[0] == 0)
return;
// if relay server or not in Play status, bail out immediately
if ($this->server->isrelay || $this->currstatus != 4)
return;
// check for valid player
if ((!$player = $this->server->players->getPlayer($finish[1])) ||
$player->login == '')
return;
// build a record object with the current finish information
$finish_item = new Record();
$finish_item->player = $player;
$finish_item->score = $finish[2];
$finish_item->date = strftime('%Y-%m-%d %H:%M:%S');
$finish_item->new = false;
$finish_item->challenge = clone $this->server->challenge;
unset($finish_item->challenge->gbx); // reduce memory usage
unset($finish_item->challenge->tmx);
// throw prefix 'player finishes' event (checkpoints)
$this->releaseEvent('onPlayerFinish1', $finish_item);
// throw main 'player finishes' event
$this->releaseEvent('onPlayerFinish', $finish_item);
} // playerFinish
/**
* Receives chat messages and reacts on them.
* Reactions are done by the chat plugins.
*/
function playerChat($chat) {
// verify login
if ($chat[1] == '' || $chat[1] == '???') {
trigger_error('playerUid ' . $chat[0] . 'has login [' . $chat[1] . ']!', E_USER_WARNING);
$this->console('playerUid {1} attempted to use chat command "{2}"',
$chat[0], $chat[2]);
return;
}
// ignore master server messages on relay
if ($this->server->isrelay && $chat[1] == $this->server->relaymaster['Login'])
return;
// check for chat command '/' prefix
$command = $chat[2];
if ($command != '' && $command[0] == '/') {
// remove '/' prefix
$command = substr($command, 1);
// split strings at spaces and add them into an array
$params = explode(' ', $command, 2);
$translated_name = str_replace('+', 'plus', $params[0]);
$translated_name = str_replace('-', 'dash', $translated_name);
// check if the function and the command exist
if (function_exists('chat_' . $translated_name)) {
// insure parameter exists & is trimmed
if (isset($params[1]))
$params[1] = trim($params[1]);
else
$params[1] = '';
// get & verify player object
if (($author = $this->server->players->getPlayer($chat[1])) &&
$author->login != '') {
// log console message
$this->console('player {1} used chat command "/{2} {3}"',
$chat[1], $params[0], $params[1]);
// save circumstances in array
$chat_command = array();
$chat_command['author'] = $author;
$chat_command['params'] = $params[1];
// call the function which belongs to the command
call_user_func('chat_' . $translated_name, $this, $chat_command);
} else {
trigger_error('Player object for \'' . $chat[1] . '\' not found!', E_USER_WARNING);
$this->console('player {1} attempted to use chat command "/{2} {3}"',
$chat[1], $params[0], $params[1]);
}
} elseif ($params[0] == 'version' ||
($params[0] == 'serverlogin' && $this->server->getGame() == 'TMF')) {
// log built-in commands
$this->console('player {1} used built-in command "/{2}"',
$chat[1], $command);
} else {
// optionally log bogus chat commands too
if ($this->settings['log_all_chat']) {
if ($chat[0] != $this->server->id) {
$this->console('({1}) {2}', $chat[1], stripColors($chat[2], false));
}
}
}
} else {
// optionally log all normal chat too
if ($this->settings['log_all_chat']) {
if ($chat[0] != $this->server->id && $chat[2] != '') {
$this->console('({1}) {2}', $chat[1], stripColors($chat[2], false));
}
}
}
} // playerChat
/**
* Gets the specified chat message out of the settings file.
*/
function getChatMessage($name) {
return htmlspecialchars_decode($this->chat_messages[$name][0]);
} // getChatMessage
/**
* Checks if an admin is allowed to perform this ability
*/
function allowAdminAbility($ability) {
// map to uppercase before checking list
$ability = strtoupper($ability);
if (isset($this->adm_abilities[$ability])) {
return $this->adm_abilities[$ability][0];
} else {
return false;
}
} // allowAdminAbility
/**
* Checks if an operator is allowed to perform this ability
*/
function allowOpAbility($ability) {
// map to uppercase before checking list
$ability = strtoupper($ability);
if (isset($this->op_abilities[$ability])) {
return $this->op_abilities[$ability][0];
} else {
return false;
}
} // allowOpAbility
/**
* Checks if the given player is allowed to perform this ability
*/
function allowAbility($player, $ability) {
// check for unlocked password
if ($this->settings['lock_password'] != '' && !$player->unlocked)
return false;
// MasterAdmins can always do everything
if ($this->isMasterAdmin($player))
return true;
// check Admins & their abilities
if ($this->isAdmin($player))
return $this->allowAdminAbility($ability);
// check Operators & their abilities
if ($this->isOperator($player))
return $this->allowOpAbility($ability);
return false;
} // allowAbility
/**
* Checks if the given player IP matches the corresponding list IP,
* allowing for class C and B wildcards, and multiple comma-separated
* IPs / wildcards.
*/
function ip_match($playerip, $listip) {
// check for offline player (removeadmin / removeop)
if ($playerip == '')
return true;
$match = false;
// check all comma-separated IPs/wildcards
foreach (explode(',', $listip) as $ip) {
// check for complete list IP
if (preg_match('/^\d+\.\d+\.\d+\.\d+$/', $ip))
$match = ($playerip == $ip);
// check class B wildcard
elseif (substr($ip, -4) == '.*.*')
$match = (preg_replace('/\.\d+\.\d+$/', '', $playerip) == substr($ip, 0, -4));
// check class C wildcard
elseif (substr($ip, -2) == '.*')
$match = (preg_replace('/\.\d+$/', '', $playerip) == substr($ip, 0, -2));
if ($match) return true;
}
return false;
}
/**
* Checks if the given player is in masteradmin list with, optionally,
* an authorized IP.
*/
function isMasterAdmin($player) {
// check for masteradmin list entry
if (isset($player->login) && $player->login != '' && isset($this->masteradmin_list['TMLOGIN']))
if (($i = array_search($player->login, $this->masteradmin_list['TMLOGIN'])) !== false)
// check for matching IP if set
if ($this->masteradmin_list['IPADDRESS'][$i] != '')
if (!$this->ip_match($player->ip, $this->masteradmin_list['IPADDRESS'][$i])) {
trigger_error("Attempt to use MasterAdmin login '" . $player->login . "' from IP " . $player->ip . " !", E_USER_WARNING);
return false;
} else
return true;
else
return true;
else
return false;
else
return false;
} // isMasterAdmin
/**
* Checks if the given player is in admin list with, optionally,
* an authorized IP.
*/
function isAdmin($player) {
// check for admin list entry
if (isset($player->login) && $player->login != '' && isset($this->admin_list['TMLOGIN']))
if (($i = array_search($player->login, $this->admin_list['TMLOGIN'])) !== false)
// check for matching IP if set
if ($this->admin_list['IPADDRESS'][$i] != '')
if (!$this->ip_match($player->ip, $this->admin_list['IPADDRESS'][$i])) {
trigger_error("Attempt to use Admin login '" . $player->login . "' from IP " . $player->ip . " !", E_USER_WARNING);
return false;
} else
return true;
else
return true;
else
return false;
else
return false;
} // isAdmin
/**
* Checks if the given player is in operator list with, optionally,
* an authorized IP.
*/
function isOperator($player) {
// check for operator list entry
if (isset($player->login) && $player->login != '' && isset($this->operator_list['TMLOGIN']))
if (($i = array_search($player->login, $this->operator_list['TMLOGIN'])) !== false)
// check for matching IP if set
if ($this->operator_list['IPADDRESS'][$i] != '')
if (!$this->ip_match($player->ip, $this->operator_list['IPADDRESS'][$i])) {
trigger_error("Attempt to use Operator login '" . $player->login . "' from IP " . $player->ip . " !", E_USER_WARNING);
return false;
} else
return true;
else
return true;
else
return false;
else
return false;
} // isOperator
/**
* Checks if the given player is in any admin tier with, optionally,
* an authorized IP.
*/
function isAnyAdmin($player) {
return ($this->isMasterAdmin($player) || $this->isAdmin($player) || $this->isOperator($player));
} // isAnyAdmin
/**
* Checks if the given player login is in masteradmin list.
*/
function isMasterAdminL($login) {
if ($login != '' && isset($this->masteradmin_list['TMLOGIN'])) {
return in_array($login, $this->masteradmin_list['TMLOGIN']);
} else {
return false;
}
} // isMasterAdminL
/**
* Checks if the given player login is in admin list.
*/
function isAdminL($login) {
if ($login != '' && isset($this->admin_list['TMLOGIN'])) {
return in_array($login, $this->admin_list['TMLOGIN']);
} else {
return false;
}
} // isAdminL
/**
* Checks if the given player login is in operator list.
*/
function isOperatorL($login) {
// check for operator list entry
if ($login != '' && isset($this->operator_list['TMLOGIN']))
return in_array($login, $this->operator_list['TMLOGIN']);
else
return false;
} // isOperatorL
/**
* Checks if the given player login is in any admin tier.
*/
function isAnyAdminL($login) {
return ($this->isMasterAdminL($login) || $this->isAdminL($login) || $this->isOperatorL($login));
} // isAnyAdminL
/**
* Checks if the given player is a spectator.
*/
function isSpectator($player) {
// get current player status
if ($this->server->getGame() != 'TMF') {
$this->client->query('GetPlayerInfo', $player->login);
$info = $this->client->getResponse();
if (isset($info['IsSpectator']))
$player->isspectator = $info['IsSpectator'];
else
$player->isspectator = false;
}
return $player->isspectator;
} // isSpectator
/**
* Handles cheating player.
*/
function processCheater($login, $checkpoints, $chkpt, $finish) {
// collect checkpoints
$cps = '';
foreach ($checkpoints as $cp)
$cps .= formatTime($cp) . '/';
$cps = substr($cps, 0, strlen($cps)-1); // strip trailing '/'
// report cheat
if ($finish == -1)
trigger_error('Cheat by \'' . $login . '\' detected! CPs: ' . $cps . ' Last: ' . formatTime($chkpt[2]) . ' index: ' . $chkpt[4], E_USER_WARNING);
else
trigger_error('Cheat by \'' . $login . '\' detected! CPs: ' . $cps . ' Finish: ' . formatTime($finish), E_USER_WARNING);
// check for valid player
if (!$player = $this->server->players->getPlayer($login)) {
trigger_error('Player object for \'' . $login . '\' not found!', E_USER_WARNING);
return;
}
switch ($this->settings['cheater_action']) {
case 1: // set to spec (TMF only)
$rtn = $this->client->query('ForceSpectator', $login, 1);
if (!$rtn) {
trigger_error('[' . $this->client->getErrorCode() . '] ForceSpectator - ' . $this->client->getErrorMessage(), E_USER_WARNING);
} else {
// allow spectator to switch back to player
$rtn = $this->client->query('ForceSpectator', $login, 0);
}
// force free camera mode on spectator
$this->client->addCall('ForceSpectatorTarget', array($login, '', 2));
// free up player slot
$this->client->addCall('SpectatorReleasePlayerSlot', array($login));
// log console message
$this->console('Cheater [{1} : {2}] forced into free spectator!', $login, stripColors($player->nickname, false));
// show chat message
$message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} forced into spectator!',
str_ireplace('$w', '', $player->nickname));
$this->client->query('ChatSendServerMessage', $this->formatColors($message));
break;
case 2: // kick
// log console message
$this->console('Cheater [{1} : {2}] kicked!', $login, stripColors($player->nickname, false));
// show chat message
$message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} kicked!',
str_ireplace('$w', '', $player->nickname));
$this->client->query('ChatSendServerMessage', $this->formatColors($message));
// kick the cheater
$this->client->query('Kick', $login);
break;
case 3: // ban (& kick)
// log console message
$this->console('Cheater [{1} : {2}] banned!', $login, stripColors($player->nickname, false));
// show chat message
$message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} banned!',
str_ireplace('$w', '', $player->nickname));
$this->client->query('ChatSendServerMessage', $this->formatColors($message));
// update banned IPs file
$this->bannedips[] = $player->ip;
$this->writeIPs();
// ban the cheater and also kick him
$this->client->query('Ban', $player->login);
break;
case 4: // blacklist & kick
// log console message
$this->console('Cheater [{1} : {2}] blacklisted!', $login, stripColors($player->nickname, false));
// show chat message
$message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} blacklisted!',
str_ireplace('$w', '', $player->nickname));
$this->client->query('ChatSendServerMessage', $this->formatColors($message));
// blacklist the cheater and then kick him
$this->client->query('BlackList', $player->login);
$this->client->query('Kick', $player->login);
// update blacklist file
$filename = $this->settings['blacklist_file'];
$rtn = $this->client->query('SaveBlackList', $filename);
if (!$rtn) {
trigger_error('[' . $this->client->getErrorCode() . '] SaveBlackList (kick) - ' . $this->client->getErrorMessage(), E_USER_WARNING);
}
break;
case 5: // blacklist & ban
// log console message
$this->console('Cheater [{1} : {2}] blacklisted & banned!', $login, stripColors($player->nickname, false));
// show chat message
$message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} blacklisted & banned!',
str_ireplace('$w', '', $player->nickname));
$this->client->query('ChatSendServerMessage', $this->formatColors($message));
// update banned IPs file
$this->bannedips[] = $player->ip;
$this->writeIPs();
// blacklist & ban the cheater
$this->client->query('BlackList', $player->login);
$this->client->query('Ban', $player->login);
// update blacklist file
$filename = $this->settings['blacklist_file'];
$rtn = $this->client->query('SaveBlackList', $filename);
if (!$rtn) {
trigger_error('[' . $this->client->getErrorCode() . '] SaveBlackList (ban) - ' . $this->client->getErrorMessage(), E_USER_WARNING);
}
break;
default: // ignore
}
} // processCheater
/**
* Finds a player ID from its login.
*/
function getPlayerId($login, $forcequery = false) {
if (isset($this->server->players->player_list[$login]) &&
$this->server->players->player_list[$login]->id > 0 && !$forcequery) {
$rtn = $this->server->players->player_list[$login]->id;
} else {
$query = 'SELECT id FROM players
WHERE login=' . quotedString($login);
$result = mysql_query($query);
if (mysql_num_rows($result) > 0) {
$row = mysql_fetch_row($result);
$rtn = $row[0];
} else {
$rtn = 0;
}
mysql_free_result($result);
}
return $rtn;
} // getPlayerId
/**
* Finds a player Nickname from its login.
*/
function getPlayerNick($login, $forcequery = false) {
if (isset($this->server->players->player_list[$login]) &&
$this->server->players->player_list[$login]->nickname != '' && !$forcequery) {
$rtn = $this->server->players->player_list[$login]->nickname;
} else {
$query = 'SELECT nickname FROM players
WHERE login=' . quotedString($login);
$result = mysql_query($query);
if (mysql_num_rows($result) > 0) {
$row = mysql_fetch_row($result);
$rtn = $row[0];
} else {
$rtn = '';
}
mysql_free_result($result);
}
return $rtn;
} // getPlayerNick
/**
* Finds an online player object from its login or Player_ID
* If $offline = true, search player database instead
* Returns false if not found
*/
function getPlayerParam($player, $param, $offline = false) {
// if numeric param, find Player_ID from /players list (hardlimited to 300)
if (is_numeric($param) && $param >= 0 && $param < 300) {
if (empty($player->playerlist)) {
$message = '{#server}> {#error}Use {#highlite}$i/players {#error}first (optionally {#highlite}$i/players <string>{#error})';
$this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login);
return false;
}
$pid = ltrim($param, '0');
$pid--;
// find player by given #
if (array_key_exists($pid, $player->playerlist)) {
$param = $player->playerlist[$pid]['login'];
// check online players list
$target = $this->server->players->getPlayer($param);
} else {
// try param as login string as yet
$target = $this->server->players->getPlayer($param);
if (!$target) {
$message = '{#server}> {#error}Player_ID not found! Type {#highlite}$i/players {#error}to see all players.';
$this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login);
return false;
}
}
} else { // otherwise login string
// check online players list
$target = $this->server->players->getPlayer($param);
}
// not found and offline allowed?
if (!$target && $offline) {
// check offline players database
$query = 'SELECT * FROM players
WHERE login=' . quotedString($param);
$result = mysql_query($query);
if (mysql_num_rows($result) > 0) {
$row = mysql_fetch_object($result);
// create dummy player object
$target = new Player();
$target->id = $row->Id;
$target->login = $row->Login;
$target->nickname = $row->NickName;
$target->nation = $row->Nation;
$target->teamname = $row->TeamName;
$target->wins = $row->Wins;
$target->timeplayed = $row->TimePlayed;
}
mysql_free_result($result);
}
// found anyone anywhere?
if (!$target) {
$message = '{#server}> {#highlite}' . $param . ' {#error}is not a valid player! Use {#highlite}$i/players {#error}to find the correct login or Player_ID.';
$this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login);
}
return $target;
} // getPlayerParam
/**
* Finds a challenge ID from its UID.
*/
function getChallengeId($uid) {
$query = 'SELECT Id FROM challenges
WHERE Uid=' . quotedString($uid);
$res = mysql_query($query);
if (mysql_num_rows($res) > 0) {
$row = mysql_fetch_row($res);
$rtn = $row[0];
} else {
$rtn = 0;
}
mysql_free_result($res);
return $rtn;
} // getChallengeId
/**
* Gets current servername
*/
function getServerName() {
$this->client->query('GetServerName');
$this->server->name = $this->client->getResponse();
return $this->server->name;
}
/**
* Gets current server name & options
*/
function getServerOptions() {
$this->client->query('GetServerOptions');
$options = $this->client->getResponse();
$this->server->name = $options['Name'];
$this->server->maxplay = $options['CurrentMaxPlayers'];
$this->server->maxspec = $options['CurrentMaxSpectators'];
$this->server->votetime = $options['CurrentCallVoteTimeOut'];
$this->server->voterate = $options['CallVoteRatio'];
}
/**
* Formats aseco color codes in a string,
* for example '{#server} hello' will end up as '$ff0 hello'.
* It depends on what you've set in the config file.
*/
function formatColors($text) {
// replace all chat colors
foreach ($this->chat_colors as $key => $value) {
$text = str_replace('{#'.strtolower($key).'}', $value[0], $text);
}
return $text;
} // formatColors
/**
* Outputs a formatted string without datetime.
*/
function console_text() {
$args = func_get_args();
$message = call_user_func_array('formatText', $args) . CRLF;
echo $message;
doLog($message);
flush();
} // console_text
/**
* Outputs a string to console with datetime prefix.
*/
function console() {
$args = func_get_args();
$message = '[' . date('m/d,H:i:s') . '] ' . call_user_func_array('formatText', $args) . CRLF;
echo $message;
doLog($message);
flush();
} // console
} // class Aseco
// define process settings
if (function_exists('date_default_timezone_get') && function_exists('date_default_timezone_set'))
date_default_timezone_set(@date_default_timezone_get());
$limit = ini_get('memory_limit');
if (shorthand2bytes($limit) < 128 * 1048576)
ini_set('memory_limit', '128M');
setlocale(LC_NUMERIC, 'C');
// create an instance of XASECO and run it
$aseco = new Aseco(false);
$aseco->run('config.xml');
?>