diff --git a/application/configs/authentication.mControl.xml b/application/configs/authentication.mControl.xml new file mode 100644 index 00000000..38659bfb --- /dev/null +++ b/application/configs/authentication.mControl.xml @@ -0,0 +1,36 @@ + + + + + + + + steeffeen + + + + + + gorby + canyondrive + + + + + + eyebo + jojo95183 + xanashea + ardid + gugli + phil13hebert + xcaliber + eole + fix + kremsy + papychampy + titishu + wurstigewurst + + + diff --git a/application/configs/chat.mControl.xml b/application/configs/chat.mControl.xml new file mode 100644 index 00000000..2f1fc9f2 --- /dev/null +++ b/application/configs/chat.mControl.xml @@ -0,0 +1,12 @@ + + + + + + + $fff + $0f0 + $f00 + + + diff --git a/application/configs/commands.mControl.xml b/application/configs/commands.mControl.xml new file mode 100644 index 00000000..5eb30730 --- /dev/null +++ b/application/configs/commands.mControl.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/configs/core.mControl.xml b/application/configs/core.mControl.xml new file mode 100644 index 00000000..ddf27ebb --- /dev/null +++ b/application/configs/core.mControl.xml @@ -0,0 +1,15 @@ + + + + + + 20 + + + + tracklist.txt + + + mx + + diff --git a/application/configs/database.mControl.xml b/application/configs/database.mControl.xml new file mode 100644 index 00000000..39097388 --- /dev/null +++ b/application/configs/database.mControl.xml @@ -0,0 +1,16 @@ + + + + + + localhost + 3306 + + + steff + kjhgvhbjnfih2394ugnjk + + + steff_united + + diff --git a/application/configs/plugins.mControl.xml b/application/configs/plugins.mControl.xml new file mode 100644 index 00000000..7f546f47 --- /dev/null +++ b/application/configs/plugins.mControl.xml @@ -0,0 +1,13 @@ + + + + + + chatlog.plugin.php + karma.plugin.php + records.plugin.php + united.plugin.php + + + + diff --git a/application/configs/server.mControl.xml b/application/configs/server.mControl.xml new file mode 100644 index 00000000..179b9eb5 --- /dev/null +++ b/application/configs/server.mControl.xml @@ -0,0 +1,17 @@ + + + + + + true + + + 144.76.158.111 + localhost + 21003 + + + SuperAdmin + dtcfvgubhnjomkjnbhv + + diff --git a/application/configs/stats.mControl.xml b/application/configs/stats.mControl.xml new file mode 100644 index 00000000..6bd8da92 --- /dev/null +++ b/application/configs/stats.mControl.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + true + + + true + + + true + + + true + + + + + + + + true + + + true + + + true + + + true + + + + + + diff --git a/application/core/authentication.mControl.php b/application/core/authentication.mControl.php new file mode 100644 index 00000000..7d9823be --- /dev/null +++ b/application/core/authentication.mControl.php @@ -0,0 +1,103 @@ + 'none', 0 => 'superadmin', 1 => 'admin', 2 => 'operator', 3 => 'all'); + + /** + * Private properties + */ + private $mControl = null; + + private $config = null; + + /** + * Construct authentication manager + */ + public function __construct($mControl) { + $this->mControl = $mControl; + + // Load config + $this->config = Tools::loadConfig('authentication.mControl.xml'); + } + + /** + * Check if the player has enough rights + * + * @param string $login + * @param string $defaultRight + * @param string $neededRight + * @return bool + */ + public function checkRight($login, $neededRight) { + $right = $this->getRights($login); + return $this->compareRights($right, $neededRight); + } + + /** + * Compare if the rights are enough + * + * @param string $hasRight + * @param string $neededRight + * @return bool + */ + public function compareRights($hasRight, $neededRight) { + if (!in_array($hasRight, $this->RIGHTS_LEVELS) || !in_array($neededRight, $this->RIGHTS_LEVELS)) { + return false; + } + $hasLevel = array_search($hasRight, $this->RIGHTS_LEVELS); + $neededLevel = array_search($neededRight, $this->RIGHTS_LEVELS); + if ($hasLevel > $neededLevel) { + return false; + } + else { + return true; + } + } + + /** + * Get rights of the given login + * + * @param string $login + * @param string $defaultRights + * @return string + */ + public function getRights($login, $defaultRight = 'all') { + $groups = $this->config->xpath('//login[text()="' . $login . '"]/..'); + if (empty($groups)) return $defaultRight; + $right = $defaultRight; + $rightLevel = array_search($right, $this->RIGHTS_LEVELS); + foreach ($groups as $group) { + $level = array_search($group->getName(), $this->RIGHTS_LEVELS); + if ($level === false) continue; + if ($level < $rightLevel || $rightLevel === false) { + $right = $group->getName(); + $rightLevel = $level; + } + } + return $right; + } + + /** + * Sends an error message to the login + * + * @param string $login + */ + public function sendNotAllowed($login) { + if (!$this->iControl->chat->sendError('You do not have the required rights to perform this command!', $login)) { + trigger_error("Couldn't send forbidden message to login '" . $login . "'. " . $this->iControl->getClientErrorText()); + } + } +} + +?> diff --git a/application/core/callbacks.mControl.php b/application/core/callbacks.mControl.php new file mode 100644 index 00000000..18e7a59a --- /dev/null +++ b/application/core/callbacks.mControl.php @@ -0,0 +1,191 @@ +mControl = $mControl; + + // Init values + $this->last1Second = time(); + $this->last5Second = time(); + $this->last1Minute = time(); + $this->last3Minute = time(); + } + + /** + * Perform OnInit callback + */ + public function onInit() { + // On init callback + $this->triggerCallback(self::CB_IC_ONINIT, array(self::CB_IC_ONINIT)); + + // Simulate begin map + $map = $this->iControl->server->getMap(); + if ($map) { + $this->triggerCallback(self::CB_IC_BEGINMAP, array(self::CB_IC_BEGINMAP, array($map))); + } + } + + /** + * Handles the given array of callbacks + */ + public function handleCallbacks() { + // Perform mControl callbacks + if ($this->last1Second <= time() - 1) { + $this->last1Second = time(); + + // 1 second + $this->triggerCallback(self::CB_IC_1_SECOND, array(self::CB_IC_1_SECOND)); + + if ($this->last5Second <= time() - 5) { + $this->last5Second = time(); + + // 5 second + $this->triggerCallback(self::CB_IC_5_SECOND, array(self::CB_IC_5_SECOND)); + + if ($this->last1Minute <= time() - 60) { + $this->last1Minute = time(); + + // 1 minute + $this->triggerCallback(self::CB_IC_1_MINUTE, array(self::CB_IC_1_MINUTE)); + + if ($this->last3Minute <= time() - 180) { + $this->last3Minute = time(); + + // 3 minute + $this->triggerCallback(self::CB_IC_3_MINUTE, array(self::CB_IC_3_MINUTE)); + } + } + } + } + + // Get server callbacks + if (!$this->iControl->client) return; + $this->iControl->client->resetError(); + $this->iControl->client->readCB(); + $callbacks = $this->iControl->client->getCBResponses(); + if (!is_array($callbacks) || $this->iControl->client->isError()) { + trigger_error("Error reading server callbacks. " . $this->iControl->getClientErrorText()); + return; + } + + // Handle callbacks + foreach ($callbacks as $index => $callback) { + $callbackName = $callback[0]; + switch ($callbackName) { + case self::CB_MP_BEGINMAP: + { + // Map begin + $this->triggerCallback($callbackName, $callback); + $this->triggerCallback(self::CB_IC_BEGINMAP, $callback); + break; + } + case self::CB_MP_ENDMAP: + { + // Map end + $this->triggerCallback($callbackName, $callback); + $this->triggerCallback(self::CB_IC_ENDMAP, $callback); + break; + } + default: + { + $this->triggerCallback($callbackName, $callback); + break; + } + } + } + } + + /** + * Trigger a specific callback + * + * @param string $callbackName + * @param mixed $data + */ + public function triggerCallback($callbackName, $data) { + if (!array_key_exists($callbackName, $this->callbackHandlers) || !is_array($this->callbackHandlers[$callbackName])) return; + foreach ($this->callbackHandlers[$callbackName] as $handler) { + call_user_func(array($handler[0], $handler[1]), $data); + } + } + + /** + * Add a new callback handler + */ + public function registerCallbackHandler($callback, $handler, $method) { + if (!is_object($handler) || !method_exists($handler, $method)) { + trigger_error("Given handler can't handle callback '" . $callback . "' (no method '" . $method . "')!"); + return; + } + if (!array_key_exists($callback, $this->callbackHandlers) || !is_array($this->callbackHandlers[$callback])) { + // Init callback handler array + $this->callbackHandlers[$callback] = array(); + } + // Register callback handler + array_push($this->callbackHandlers[$callback], array($handler, $method)); + } +} + +?> diff --git a/application/core/chat.mControl.php b/application/core/chat.mControl.php new file mode 100644 index 00000000..8b9aced2 --- /dev/null +++ b/application/core/chat.mControl.php @@ -0,0 +1,85 @@ +'; + + /** + * Construct mControl chat + */ + public function __construct($mControl) { + $this->mControl = $mControl; + + // Load config + $this->config = Tools::loadConfig('chat.mControl.xml'); + } + + /** + * Send a chat message to the given login + * + * @param string $login + * @param string $message + * @param bool $prefix + */ + public function sendChat($message, $login = null, $prefix = false) { + if (!$this->iControl->client) return false; + if ($login === null) { + return $this->iControl->client->query('ChatSendServerMessage', ($prefix ? $this->prefix : '') . $message); + } + else { + return $this->iControl->client->query('ChatSendServerMessageToLogin', ($prefix ? $this->prefix : '') . $message, $login); + } + } + + /** + * Send an information message to the given login + * + * @param string $login + * @param string $message + * @param bool $prefix + */ + public function sendInformation($message, $login = null, $prefix = false) { + $format = (string) $this->config->messages->information; + return $this->sendChat($format . $message, $login); + } + + /** + * Send a success message to the given login + * + * @param string $message + * @param string $login + * @param bool $prefix + */ + public function sendSuccess($message, $login = null, $prefix = false) { + $format = (string) $this->config->messages->success; + return $this->sendChat($format . $message, $login); + } + + /** + * Send an error message to the given login + * + * @param string $login + * @param string $message + * @param bool $prefix + */ + public function sendError($message, $login = null, $prefix = false) { + $format = (string) $this->config->messages->error; + return $this->sendChat($format . $message, $login); + } +} + +?> diff --git a/application/core/commands.mControl.php b/application/core/commands.mControl.php new file mode 100644 index 00000000..983dd45f --- /dev/null +++ b/application/core/commands.mControl.php @@ -0,0 +1,684 @@ +mControl = $mControl; + + // Load config + $this->config = Tools::loadConfig('commands.mControl.xml'); + + // Register for callbacks + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_5_SECOND, $this, 'each5Seconds'); + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_BILLUPDATED, $this, 'handleBillUpdated'); + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCHAT, $this, 'handleChatCallback'); + + // Register basic commands + $commands = array('help', 'version', 'shutdown', 'shutdownserver', 'networkstats', 'systeminfo', 'getservername', + 'setservername', 'getplanets', 'donate', 'pay', 'kick', 'nextmap', 'restartmap', 'addmap', 'removemap', 'startwarmup', + 'stopwarmup'); + foreach ($commands as $command) { + $this->registerCommandHandler($command, $this, 'command_' . $command); + } + } + + /** + * Register a command handler + * + * @param string $commandName + * @param object $handler + * @param string $method + */ + public function registerCommandHandler($commandName, $handler, $method) { + $command = strtolower($commandName); + if (!is_object($handler) || !method_exists($handler, $method)) { + trigger_error("Given handler can't handle command '" . $command . "' (no method '" . $method . "')!"); + return; + } + if (!array_key_exists($command, $this->commandHandlers) || !is_array($this->commandHandlers[$command])) { + // Init handlers array + $this->commandHandlers[$command] = array(); + } + // Register command handler + array_push($this->commandHandlers[$command], array($handler, $method)); + } + + /** + * Handle chat callback + */ + public function handleChatCallback($callback) { + $chat = $callback[1]; + // Check for command + if (!$chat[3]) return; + // Check for valid player + if ($chat[0] <= 0 || strlen($chat[1]) <= 0) return; + // Handle command + $command = explode(" ", substr($chat[2], 1)); + $command = strtolower($command[0]); + if (!array_key_exists($command, $this->commandHandlers) || !is_array($this->commandHandlers[$command])) { + // No command handler registered + return; + } + // Inform command handlers + foreach ($this->commandHandlers[$command] as $handler) { + call_user_func(array($handler[0], $handler[1]), $callback); + } + } + + /** + * Handle bill updated callback + */ + public function handleBillUpdated($callback) { + $bill = $callback[1]; + if (!array_key_exists($bill[0], $this->openBills)) return; + $login = $this->openBills[$bill[0]]; + switch ($bill[1]) { + case 4: + { + // Payed + $message = 'Success! Thanks.'; + $this->iControl->chat->sendSuccess($message, $login); + unset($this->openBills[$bill[0]]); + break; + } + case 5: + { + // Refused + $message = 'Transaction cancelled.'; + $this->iControl->chat->sendError($message, $login); + unset($this->openBills[$bill[0]]); + break; + } + case 6: + { + // Error + $this->iControl->chat->sendError($bill[2], $login); + unset($this->openBills[$bill[0]]); + break; + } + } + } + + /** + * Retrieve the needed rights level to perform the given command + * + * @param string $commandName + * @param string $defaultLevel + * @return string + */ + private function getRightsLevel($commandName, $defaultLevel) { + $command_rights = $this->config->xpath('//' . strtolower($commandName) . '/..'); + if (empty($command_rights)) return $defaultLevel; + $rights = $this->iControl->authentication->RIGHTS_LEVELS; + $highest_level = null; + foreach ($command_rights as $right) { + $levelName = $right->getName(); + $levelInt = array_search($levelName, $rights); + if ($levelInt !== false && ($highest_level === null || $highest_level < $levelInt)) { + $highest_level = $levelInt; + } + } + if ($highest_level === null || !array_key_exists($highest_level, $rights)) return $defaultLevel; + return $rights[$highest_level]; + } + + /** + * Send mControl version + */ + private function command_version($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('version', 'all'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + if (!$this->iControl->chat->sendInformation('This server is using mControl v' . mControl::VERSION . '!', $login)) { + trigger_error("Couldn't send version to '" . $login . "'. " . $this->iControl->getClientErrorText()); + } + } + + /** + * Send help list + */ + private function command_help($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('help', 'all'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + // TODO: improve help command + // TODO: enable help for specific commands + $list = 'Available commands: '; + $commands = array_keys($this->commandHandlers); + $count = count($commands); + for ($index = 0; $index < $count; $index++) { + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel($commands[$index], 'superadmin'))) { + unset($commands[$index]); + } + } + $count = count($commands); + $index = 0; + foreach ($commands as $command) { + $list .= $command; + if ($index < $count - 1) { + $list .= ', '; + } + $index++; + } + if (!$this->iControl->chat->sendInformation($list, $login)) { + trigger_error("Couldn't send help list to '" . $login . "'. " . $this->iControl->getClientErrorText()); + } + } + + /** + * Handle getplanets command + */ + private function command_getplanets($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('getplanets', 'admin'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + if (!$this->iControl->client->query('GetServerPlanets')) { + trigger_error("Couldn't retrieve server planets. " . $this->iControl->getClientErrorText()); + } + else { + $planets = $this->iControl->client->getResponse(); + if (!$this->iControl->chat->sendInformation('This Server has ' . $planets . ' Planets!', $login)) { + trigger_error("Couldn't send server planets to '" . $login . "'. " . $this->iControl->getClientErrorText()); + } + } + } + + /** + * Handle donate command + */ + private function command_donate($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('donate', 'all'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + $params = explode(' ', $chat[1][2]); + if (count($params) < 2) { + // TODO: send usage information + return; + } + $amount = (int) $params[1]; + if (!$amount || $amount <= 0) { + // TODO: send usage information + return; + } + if (count($params) >= 3) { + $receiver = $params[2]; + $receiverPlayer = $this->iControl->database->getPlayer($receiver); + $receiverName = ($receiverPlayer ? $receiverPlayer['NickName'] : $receiver); + } + else { + $receiver = ''; + $receiverName = $this->iControl->server->getName(); + } + $message = 'Donate ' . $amount . ' Planets to $<' . $receiverName . '$>?'; + if (!$this->iControl->client->query('SendBill', $login, $amount, $message, $receiver)) { + trigger_error( + "Couldn't create donation of " . $amount . " planets from '" . $login . "' for '" . $receiver . "'. " . + $this->iControl->getClientErrorText()); + $this->iControl->chat->sendError("Creating donation failed.", $login); + } + else { + $bill = $this->iControl->client->getResponse(); + $this->openBills[$bill] = $login; + } + } + + /** + * Handle pay command + */ + private function command_pay($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('pay', 'superadmin'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + $params = explode(' ', $chat[1][2]); + if (count($params) < 2) { + // TODO: send usage information + return; + } + $amount = (int) $params[1]; + if (!$amount || $amount <= 0) { + // TODO: send usage information + return; + } + if (count($params) >= 3) { + $receiver = $params[2]; + } + else { + $receiver = $login; + } + $message = 'Payout from $<' . $this->iControl->server->getName() . '$>.'; + if (!$this->iControl->client->query('Pay', $receiver, $amount, $message)) { + trigger_error( + "Couldn't create payout of" . $amount . " planets by '" . $login . "' for '" . $receiver . "'. " . + $this->iControl->getClientErrorText()); + $this->iControl->chat->sendError("Creating payout failed.", $login); + } + else { + $bill = $this->iControl->client->getResponse(); + $this->openBills[$bill] = $login; + } + } + + /** + * Handle networkstats command + */ + private function command_networkstats($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('networkstats', 'superadmin'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + $networkStats = $this->iControl->server->getNetworkStats(); + $message = 'NetworkStats: ' . 'uptime=' . $networkStats['Uptime'] . ', ' . 'nbConn=' . $networkStats['NbrConnection'] . ', ' . + 'recvRate=' . $networkStats['RecvNetRate'] . ', ' . 'sendRate=' . $networkStats['SendNetRate'] . ', ' . 'recvTotal=' . + $networkStats['SendNetRate'] . ', ' . 'sentTotal=' . $networkStats['SendNetRate']; + if (!$this->iControl->chat->sendInformation($message, $login)) { + trigger_error("Couldn't send network stats to '" . $login . "'. " . $this->iControl->getClientErrorText()); + } + } + + /** + * Handle systeminfo command + */ + private function command_systeminfo($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('systeminfo', 'superadmin'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + $systemInfo = $this->iControl->server->getSystemInfo(); + $message = 'SystemInfo: ' . 'ip=' . $systemInfo['PublishedIp'] . ', ' . 'port=' . $systemInfo['Port'] . ', ' . 'p2pPort=' . + $systemInfo['P2PPort'] . ', ' . 'title=' . $systemInfo['TitleId'] . ', ' . 'login=' . $systemInfo['ServerLogin'] . ', '; + if (!$this->iControl->chat->sendInformation($message, $login)) { + trigger_error("Couldn't send system info to '" . $login . "'. " . $this->iControl->getClientErrorText()); + } + } + + /** + * Handle shutdown command + */ + private function command_shutdown($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('shutdown', 'superadmin'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + $this->iControl->quit("mControl shutdown requested by '" . $login . "'"); + } + + /** + * Handle startwarmup command + */ + private function command_startwarmup($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('startwarmup', 'operator'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + if (!$this->iControl->client->query("SetWarmUp", true)) { + trigger_error("Couldn't start warmup. " . $this->iControl->getClientErrorText()); + $player = $this->iControl->database->getPlayer($login); + $this->iControl->chat->sendInformation('$<' . ($player ? $player['NickName'] : $login) . '$> started WarmUp!'); + } + } + + /** + * Handle stopwarmup command + */ + private function command_stopwarmup($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('stopwarmup', 'operator'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + if (!$this->iControl->client->query("SetWarmUp", false)) { + trigger_error("Couldn't stop warmup. " . $this->iControl->getClientErrorText()); + } + else { + $player = $this->iControl->database->getPlayer($login); + $this->iControl->chat->sendInformation('$<' . ($player ? $player['NickName'] : $login) . '$> stopped WarmUp!'); + } + } + + /** + * Handle server shutdown command + */ + private function command_shutdownserver($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('shutdownserver', 'superadmin'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + // Check for delayed shutdown + $params = explode(' ', $chat[1][2]); + if (count($params) >= 2) { + $param = $params[1]; + if ($param == 'empty') { + $this->serverShutdownEmpty = !$this->serverShutdownEmpty; + if ($this->serverShutdownEmpty) { + $this->iControl->chat->sendInformation("The server will shutdown as soon as it's empty!", $login); + } + else { + $this->iControl->chat->sendInformation("Empty-shutdown cancelled!", $login); + } + } + else { + $delay = (int) $param; + if ($delay <= 0) { + // Cancel shutdown + $this->serverShutdownTime = -1; + $this->iControl->chat->sendInformation("Delayed shutdown cancelled!", $login); + } + else { + // Trigger delayed shutdown + $this->serverShutdownTime = time() + $delay * 60.; + $this->iControl->chat->sendInformation("The server will shut down in " . $delay . " minutes!", $login); + } + } + } + else { + $this->shutdownServer($login); + } + } + + /** + * Handle kick command + */ + private function command_kick($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('kick', 'operator'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + $params = explode(' ', $chat[1][2], 3); + if (count($params) < 2) { + // TODO: show usage + return; + } + $target = $params[1]; + $players = $this->iControl->server->getPlayers(); + foreach ($players as $player) { + if ($player['Login'] != $target) continue; + // Kick player + if (isset($params[2])) { + $message = $params[2]; + } + else { + $message = ""; + } + if (!$this->iControl->client->query('Kick', $target, $message)) { + trigger_error("Couldn't kick player '" . $target . "'! " . $this->iControl->getClientErrorText()); + } + return; + } + $this->iControl->chat->sendError("Invalid player login.", $login); + } + + /** + * Handle removemap command + */ + private function command_removemap($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('kick', 'operator'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + // TODO: allow params + // Get map name + $map = $this->iControl->server->getMap(); + if (!$map) { + $this->iControl->chat->sendError("Couldn't remove map.", $login); + } + else { + $mapName = $map['FileName']; + + // Remove map + if (!$this->iControl->client->query('RemoveMap', $mapName)) { + trigger_error("Couldn't remove current map. " . $this->iControl->getClientErrorText()); + } + else { + $this->iControl->chat->sendSuccess('Map removed.', $login); + } + } + } + + /** + * Handle addmap command + */ + private function command_addmap($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('addmap', 'operator'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + $params = explode(' ', $chat[1][2], 2); + if (count($params) < 2) { + // TODO: show usage + return; + } + // Check if mControl can even write to the maps dir + if (!$this->iControl->client->query('GetMapsDirectory')) { + trigger_error("Couldn't get map directory. " . $this->iControl->getClientErrorText()); + $this->iControl->chat->sendError("mControl couldn't retrieve the maps directory.", $login); + return; + } + else { + $mapDir = $this->iControl->client->getResponse(); + if (!is_dir($mapDir)) { + trigger_error("mControl doesn't have have access to the maps directory in '" . $mapDir . "'."); + $this->iControl->chat->sendError("mControl doesn't have access to the maps directory.", $login); + return; + } + $dlDir = (string) $this->iControl->config->maps_dir; + // Create mx directory if necessary + if (!is_dir($mapDir . $dlDir) && !mkdir($mapDir . $dlDir)) { + trigger_error("mControl doesn't have to rights to save maps in'" . $mapDir . $dlDir, "'."); + $this->iControl->chat->sendError("mControl doesn't have to rights to save maps.", $login); + return; + } + $mapDir .= $dlDir . '/'; + // Download the map + if (is_numeric($params[1])) { + $serverInfo = $this->iControl->server->getSystemInfo(); + $title = strtolower(substr($serverInfo['TitleId'], 0, 2)); + // Check if map exists + $url = 'http://' . $title . '.mania-exchange.com/api/tracks/get_track_info/id/' . $params[1] . '?format=json'; + $mapInfo = Tools::loadFile($url); + if (!$mapInfo || strlen($mapInfo) <= 0) { + // Invalid id + $this->iControl->chat->sendError('Invalid MX-Id!', $login); + return; + } + $mapInfo = json_decode($mapInfo, true); + $url = 'http://' . $title . '.mania-exchange.com/tracks/download/' . $params[1]; + $file = Tools::loadFile($url); + if (!$file) { + // Download error + $this->iControl->chat->sendError('Download failed!', $login); + return; + } + // Save map + $fileName = $mapDir . $mapInfo['TrackID'] . '_' . $mapInfo['Name'] . '.Map.Gbx'; + if (!file_put_contents($fileName, $file)) { + // Save error + $this->iControl->chat->sendError('Saving map failed!', $login); + return; + } + // Check for valid map + if (!$this->iControl->client->query('CheckMapForCurrentServerParams', $fileName)) { + trigger_error("Couldn't check if map is valid. " . $this->iControl->getClientErrorText()); + } + else { + $response = $this->iControl->client->getResponse(); + if (!$response) { + // Inalid map type + $this->iControl->chat->sendError("Invalid map type.", $login); + return; + } + } + // Add map to map list + if (!$this->iControl->client->query('InsertMap', $fileName)) { + $this->iControl->chat->sendError("Couldn't add map to match settings!", $login); + return; + } + $this->iControl->chat->sendSuccess('Map $<' . $mapInfo['Name'] . '$> successfully added!'); + } + else { + // TODO: check if map exists locally + // TODO: load map from direct url + } + } + } + + /** + * Handle nextmap command + */ + private function command_nextmap($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('nextmap', 'operator'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + if (!$this->iControl->client->query('NextMap')) { + trigger_error("Couldn't skip map. " . $this->iControl->getClientErrorText()); + } + } + + /** + * Handle retartmap command + */ + private function command_restartmap($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('restartmap', 'operator'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + if (!$this->iControl->client->query('RestartMap')) { + trigger_error("Couldn't restart map. " . $this->iControl->getClientErrorText()); + } + } + + /** + * Handle getservername command + */ + private function command_getservername($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('getservername', 'operator'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + $serverName = $this->iControl->server->getName(); + $this->iControl->chat->sendInformation("Server Name: " . $serverName, $login); + } + + /** + * Handle setservername command + */ + private function command_setservername($chat) { + $login = $chat[1][1]; + if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('setservername', 'admin'))) { + // Not allowed! + $this->iControl->authentication->sendNotAllowed($login); + return; + } + $params = explode(' ', $chat[1][2], 2); + if (count($params) < 2) { + // TODO: show usage + return; + } + $serverName = $params[1]; + if (!$this->iControl->client->query('SetServerName', $serverName)) { + trigger_error("Couldn't set server name. " . $this->iControl->getClientErrorText()); + $this->iControl->chat->sendError("Error!"); + } + else { + $serverName = $this->iControl->server->getName(); + $this->iControl->chat->sendInformation("New Name: " . $serverName); + } + } + + /** + * Check stuff each 5 seconds + */ + public function each5Seconds() { + // Empty shutdown + if ($this->serverShutdownEmpty) { + $players = $this->iControl->server->getPlayers(); + if (count($players) <= 0) { + $this->shutdownServer('empty'); + } + } + + // Delayed shutdown + if ($this->serverShutdownTime > 0) { + if (time() >= $this->serverShutdownTime) { + $this->shutdownServer('delayed'); + } + } + } + + /** + * Perform server shutdown + */ + private function shutdownServer($login = '#') { + $this->iControl->client->resetError(); + if (!$this->iControl->client->query('StopServer') || $this->iControl->client->isError()) { + trigger_error("Server shutdown command from '" . $login . "' failed. " . $this->iControl->getClientErrorText()); + return; + } + $this->iControl->quit("Server shutdown requested by '" . $login . "'"); + } +} + +?> diff --git a/application/core/core.mControl.php b/application/core/core.mControl.php new file mode 100644 index 00000000..23c57aa3 --- /dev/null +++ b/application/core/core.mControl.php @@ -0,0 +1,348 @@ +config = Tools::loadConfig('core.mControl.xml'); + $this->startTime = time(); + + // Load chat tool + $this->chat = new Chat($this); + + // Load callbacks handler + $this->callbacks = new Callbacks($this); + + // Load database + $this->database = new Database($this); + + // Load server + $this->server = new Server($this); + + // Load authentication + $this->authentication = new Authentication($this); + + // Load commands handler + $this->commands = new Commands($this); + + // Load stats manager + $this->stats = new Stats($this); + + // Register for core callbacks + $this->callbacks->registerCallbackHandler(Callbacks::CB_MP_ENDMAP, $this, 'handleEndMap'); + } + + /** + * Return message composed of client error message and error code + * + * @param object $client + * @return string + */ + public function getClientErrorText($client = null) { + if (is_object($client)) { + return $client->getErrorMessage() . ' (' . $client->getErrorCode() . ')'; + } + return $this->client->getErrorMessage() . ' (' . $this->client->getErrorCode() . ')'; + } + + /** + * Quit mControl and log the given message + */ + public function quit($message = false) { + if ($this->shutdownRequested) return; + + if ($this->client) { + // Announce quit + $this->chat->sendInformation('mControl shutting down.'); + + // Hide manialinks + $this->client->query('SendHideManialinkPage'); + } + + // Log quit reason + if ($message) { + error_log($message); + } + + // Shutdown + if ($this->client) $this->client->Terminate(); + + error_log("Quitting mControl!"); + exit(); + } + + /** + * Run mControl + */ + public function run($debug = false) { + error_log('Starting mControl v' . self::VERSION . '!'); + $this->debug = (bool) $debug; + + // Load plugins + $this->loadPlugins(); + + // Connect to server + $this->connect(); + + // Loading finished + error_log("Loading completed!"); + + // Announce mControl + if (!$this->chat->sendInformation('mControl v' . self::VERSION . ' successfully started!')) { + trigger_error("Couldn't announce mControl. " . $this->iControl->getClientErrorText()); + } + + // OnInit + $this->callbacks->onInit(); + + // Main loop + while (!$this->shutdownRequested) { + $loopStart = microtime(true); + + // Disable script timeout + set_time_limit(30); + + // Handle server callbacks + $this->callbacks->handleCallbacks(); + + // Loop plugins + foreach ($this->plugins as $plugin) { + if (!method_exists($plugin, 'loop')) { + continue; + } + $plugin->loop(); + } + + // Yield for next tick + $loopEnd = microtime(true); + $sleepTime = 300000 - $loopEnd + $loopStart; + if ($sleepTime > 0) { + usleep($sleepTime); + } + } + + // Shutdown + $this->client->Terminate(); + } + + /** + * Connect to ManiaPlanet server + */ + private function connect() { + $enable = $this->server->config->xpath('enable'); + $enable = Tools::toBool($enable[0]); + if (!$enable) return; + + // Load remote client + $this->client = new \IXR_ClientMulticall_Gbx(); + + $host = $this->server->config->xpath('host'); + if (!$host) trigger_error("Invalid server configuration (host).", E_USER_ERROR); + $host = (string) $host[0]; + $port = $this->server->config->xpath('port'); + if (!$host) trigger_error("Invalid server configuration (port).", E_USER_ERROR); + $port = (string) $port[0]; + $timeout = $this->config->xpath('timeout'); + if (!$timeout) trigger_error("Invalid core configuration (timeout).", E_USER_ERROR); + $timeout = (int) $timeout[0]; + + error_log("Connecting to server at " . $host . ":" . $port . "..."); + + // Connect + if (!$this->client->InitWithIp($host, $port, $timeout)) { + trigger_error( + "Couldn't connect to server! " . $this->client->getErrorMessage() . "(" . $this->client->getErrorCode() . ")", + E_USER_ERROR); + } + + $login = $this->server->config->xpath('login'); + if (!$login) trigger_error("Invalid server configuration (login).", E_USER_ERROR); + $login = (string) $login[0]; + $pass = $this->server->config->xpath('pass'); + if (!$pass) trigger_error("Invalid server configuration (password).", E_USER_ERROR); + $pass = (string) $pass[0]; + + // Authenticate + if (!$this->client->query('Authenticate', $login, $pass)) { + trigger_error( + "Couldn't authenticate on server with user '" . $login . "'! " . $this->client->getErrorMessage() . "(" . + $this->client->getErrorCode() . ")", E_USER_ERROR); + } + + // Enable callback system + if (!$this->client->query('EnableCallbacks', true)) { + trigger_error("Couldn't enable callbacks! " . $this->client->getErrorMessage() . "(" . $this->client->getErrorCode() . ")", + E_USER_ERROR); + } + + // Wait for server to be ready + if (!$this->server->waitForStatus($this->client, 4)) { + trigger_error("Server couldn't get ready!", E_USER_ERROR); + } + + // Set api version + if (!$this->client->query('SetApiVersion', self::API_VERSION)) { + trigger_error( + "Couldn't set API version '" . self::API_VERSION . "'! This might cause problems. " . + $this->iControl->getClientErrorText()); + } + + // Connect finished + error_log("Server connection succesfully established!"); + + // Enable service announces + if (!$this->client->query("DisableServiceAnnounces", false)) { + trigger_error("Couldn't enable service announces. " . $this->iControl->getClientErrorText()); + } + + // Enable script callbacks if needed + if ($this->server->getGameMode() === 0) { + if (!$this->client->query('GetModeScriptSettings')) { + trigger_error("Couldn't get mode script settings. " . $this->iControl->getClientErrorText()); + } + else { + $scriptSettings = $this->client->getResponse(); + if (array_key_exists('S_UseScriptCallbacks', $scriptSettings)) { + $scriptSettings['S_UseScriptCallbacks'] = true; + if (!$this->client->query('SetModeScriptSettings', $scriptSettings)) { + trigger_error( + "Couldn't set mode script settings to enable script callbacks. " . $this->iControl->getClientErrorText()); + } + else { + error_log("Script callbacks successfully enabled."); + } + } + } + } + } + + /** + * Load mControl plugins + */ + private function loadPlugins() { + $pluginsConfig = Tools::loadConfig('plugins.mControl.xml'); + if (!$pluginsConfig || !isset($pluginsConfig->plugin)) { + trigger_error('Invalid plugins config.'); + return; + } + + // Load plugin classes + $classes = get_declared_classes(); + foreach ($pluginsConfig->xpath('plugin') as $plugin) { + $fileName = mControl . '/plugins/' . $plugin; + if (!file_exists($fileName)) { + trigger_error("Couldn't load plugin '" . $plugin . "'! File doesn't exist. (/plugins/" . $plugin . ")"); + } + else { + require_once $fileName; + error_log("Loading plugin: " . $plugin); + } + } + $plugins = array_diff(get_declared_classes(), $classes); + + // Create plugins + foreach ($plugins as $plugin) { + $nameIndex = stripos($plugin, 'plugin'); + if ($nameIndex === false) continue; + array_push($this->plugins, new $plugin($this)); + } + } + + /** + * Handle EndMap callback + */ + public function handleEndMap($callback) { + // Autosave match settings + $autosaveMatchsettings = $this->config->xpath('autosave_matchsettings'); + if ($autosaveMatchsettings) { + $autosaveMatchsettings = (string) $autosaveMatchsettings[0]; + if ($autosaveMatchsettings) { + if (!$this->client->query('SaveMatchSettings', 'MatchSettings/' . $autosaveMatchsettings)) { + trigger_error("Couldn't autosave match settings. " . $this->iControl->getClientErrorText()); + } + } + } + } + + /** + * Check config settings + */ + public function checkConfig($config, $settings, $name = 'Config XML') { + if (!is_array($settings)) $settings = array($settings); + foreach ($settings as $setting) { + $settingTags = $config->xpath('//' . $setting); + if (empty($settingTags)) { + trigger_error("Missing property '" . $setting . "' in config '" . $name . "'!", E_USER_ERROR); + } + } + } +} + +?> diff --git a/application/core/database.mControl.php b/application/core/database.mControl.php new file mode 100644 index 00000000..40d2ee78 --- /dev/null +++ b/application/core/database.mControl.php @@ -0,0 +1,401 @@ +mControl = $mControl; + + // Load config + $this->config = Tools::loadConfig('database.mControl.xml'); + $this->iControl->checkConfig($this->config, array("host", "user"), 'database.mControl.xml'); + + // Get mysql server information + $host = $this->config->xpath('host'); + if (!$host) trigger_error("Invalid database configuration (host).", E_USER_ERROR); + $host = (string) $host[0]; + + $port = $this->config->xpath('port'); + if (!$port) trigger_error("Invalid database configuration (port).", E_USER_ERROR); + $port = (int) $port[0]; + + $user = $this->config->xpath('user'); + if (!$user) trigger_error("Invalid database configuration (user).", E_USER_ERROR); + $user = (string) $user[0]; + + $pass = $this->config->xpath('pass'); + if (!$pass) trigger_error("Invalid database configuration (pass).", E_USER_ERROR); + $pass = (string) $pass[0]; + + // Open database connection + $this->mysqli = new \mysqli($host, $user, $pass, null, $port); + if ($this->mysqli->connect_error) { + // Connection error + throw new \Exception( + "Error on connecting to mysql server. " . $this->mysqli->connect_error . " (" . $this->mysqli->connect_errno . ")"); + } + + // Set charset + $this->mysqli->set_charset("utf8"); + + // Create/Connect database + $this->initDatabase(); + + // Init tables + $this->initTables(); + + // Register for callbacks + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_5_SECOND, $this, 'handle5Second'); + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_BEGINMAP, $this, 'handleBeginMap'); + } + + /** + * Destruct database connection + */ + public function __destruct() { + $this->mysqli->close(); + } + + /** + * Connect to the defined database (create it if needed) + */ + private function initDatabase() { + $dbname = $this->config->xpath('database'); + if (!$dbname) trigger_error("Invalid database configuration (database).", E_USER_ERROR); + $dbname = (string) $dbname[0]; + + // Try to connect + $result = $this->mysqli->select_db($dbname); + if (!$result) { + // Create database + $query = "CREATE DATABASE `" . $this->escape($dbname) . "`;"; + $result = $this->mysqli->query($query); + if (!$result) { + trigger_error( + "Couldn't create database '" . $dbname . "'. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')', + E_USER_ERROR); + } + else { + // Connect to database + $result = $this->mysqli->select_db($dbname); + if (!$result) { + trigger_error( + "Couldn't select database '" . $dbname . "'. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')', + E_USER_ERROR); + } + } + } + } + + /** + * Create the needed tables + */ + private function initTables() { + $query = ""; + + // Players table + $query .= "CREATE TABLE IF NOT EXISTS `" . self::TABLE_PLAYERS . "` ( + `index` int(11) NOT NULL AUTO_INCREMENT, + `Login` varchar(100) NOT NULL, + `NickName` varchar(250) NOT NULL, + `PlayerId` int(11) NOT NULL, + `LadderRanking` int(11) NOT NULL, + `Flags` varchar(50) NOT NULL, + `changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`index`), + UNIQUE KEY `Login` (`Login`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Store player metadata' AUTO_INCREMENT=1;"; + + // Maps table + $query .= "CREATE TABLE IF NOT EXISTS `ic_maps` ( + `index` int(11) NOT NULL AUTO_INCREMENT, + `UId` varchar(100) NOT NULL, + `Name` varchar(100) NOT NULL, + `FileName` varchar(200) NOT NULL, + `Author` varchar(150) NOT NULL, + `Environnement` varchar(50) NOT NULL, + `Mood` varchar(50) NOT NULL, + `BronzeTime` int(11) NOT NULL DEFAULT '-1', + `SilverTime` int(11) NOT NULL DEFAULT '-1', + `GoldTime` int(11) NOT NULL DEFAULT '-1', + `AuthorTime` int(11) NOT NULL DEFAULT '-1', + `CopperPrice` int(11) NOT NULL DEFAULT '-1', + `LapRace` tinyint(1) NOT NULL, + `NbLaps` int(11) NOT NULL DEFAULT '-1', + `NbCheckpoints` int(11) NOT NULL DEFAULT '-1', + `MapType` varchar(100) NOT NULL, + `MapStyle` varchar(100) NOT NULL, + `changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`index`), + UNIQUE KEY `UId` (`UId`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Store map metadata' AUTO_INCREMENT=1;"; + + // Perform queries + if (!$this->multiQuery($query)) { + trigger_error("Creating basic tables failed. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')', E_USER_ERROR); + } + + // Optimize all existing tables + $query = "SHOW TABLES;"; + $result = $this->query($query); + if (!$result || !is_object($result)) { + trigger_error("Couldn't select tables. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')'); + } + else { + $query = "OPTIMIZE TABLE "; + $count = $result->num_rows; + $index = 0; + while ($row = $result->fetch_row()) { + $query .= "`" . $row[0] . "`"; + if ($index < $count - 1) $query .= ", "; + $index++; + } + $query .= ";"; + if (!$this->query($query)) { + trigger_error("Couldn't optimize tables. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')'); + } + } + } + + /** + * Wrapper for performing a simple query + * + * @param string $query + * @return mixed query result + */ + public function query($query) { + if (!is_string($query)) return false; + if (strlen($query) <= 0) return true; + return $this->mysqli->query($query); + } + + /** + * Perform multi query + * + * @param + * string multi_query + * @return bool whether no error occured during executing the multi query + */ + public function multiQuery($query) { + if (!is_string($query)) return false; + if (strlen($query) <= 0) return true; + $noError = true; + $this->mysqli->multi_query($query); + if ($this->mysqli->error) { + trigger_error("Executing multi query failed. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')'); + $noError = false; + } + while ($this->mysqli->more_results() && $this->mysqli->next_result()) { + if ($this->mysqli->error) { + trigger_error("Executing multi query failed. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')'); + $noError = false; + } + } + return $noError; + } + + /** + * Handle 5Second callback + */ + public function handle5Second($callback = null) { + // Save current players in database + $players = $this->iControl->server->getPlayers(); + if ($players) { + $query = ""; + foreach ($players as $player) { + if (!Tools::isPlayer($player)) continue; + $query .= $this->composeInsertPlayer($player); + } + $this->multiQuery($query); + } + } + + /** + * Handle BeginMap callback + */ + public function handleBeginMap($callback) { + $map = $callback[1][0]; + $query = $this->composeInsertMap($map); + $result = $this->query($query); + if ($this->mysqli->error) { + trigger_error("Couldn't save map. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')'); + } + } + + /** + * Get the player index for the given login + * + * @param string $login + * @return int null + */ + public function getPlayerIndex($login) { + $query = "SELECT `index` FROM `" . self::TABLE_PLAYERS . "` WHERE `Login` = '" . $this->escape($login) . "';"; + $result = $this->query($query); + $result = $result->fetch_assoc(); + if ($result) { + return $result['index']; + } + return null; + } + + /** + * Get the map index for the given UId + * + * @param string $uid + * @return int null + */ + public function getMapIndex($uid) { + $query = "SELECT `index` FROM `" . self::TABLE_MAPS . "` WHERE `UId` = '" . $this->escape($uid) . "';"; + $result = $this->query($query); + $result = $result->fetch_assoc(); + if ($result) { + return $result['index']; + } + return null; + } + + /** + * Compose a query string for inserting the given player + * + * @param array $player + */ + private function composeInsertPlayer($player) { + if (!Tools::isPlayer($player)) return ""; + return "INSERT INTO `" . self::TABLE_PLAYERS . "` ( + `Login`, + `NickName`, + `PlayerId`, + `LadderRanking`, + `Flags` + ) VALUES ( + '" . $this->escape($player['Login']) . "', + '" . $this->escape($player['NickName']) . "', + " . $player['PlayerId'] . ", + " . $player['LadderRanking'] . ", + '" . $this->escape($player['Flags']) . "' + ) ON DUPLICATE KEY UPDATE + `NickName` = VALUES(`NickName`), + `PlayerId` = VALUES(`PlayerId`), + `LadderRanking` = VALUES(`LadderRanking`), + `Flags` = VALUES(`Flags`);"; + } + + /** + * Compose a query string for inserting the given map + * + * @param array $map + */ + private function composeInsertMap($map) { + if (!$map) return ""; + return "INSERT INTO `" . self::TABLE_MAPS . "` ( + `UId`, + `Name`, + `FileName`, + `Author`, + `Environnement`, + `Mood`, + `BronzeTime`, + `SilverTime`, + `GoldTime`, + `AuthorTime`, + `CopperPrice`, + `LapRace`, + `NbLaps`, + `NbCheckpoints`, + `MapType`, + `MapStyle` + ) VALUES ( + '" . $this->escape($map['UId']) . "', + '" . $this->escape($map['Name']) . "', + '" . $this->escape($map['FileName']) . "', + '" . $this->escape($map['Author']) . "', + '" . $this->escape($map['Environnement']) . "', + '" . $this->escape($map['Mood']) . "', + " . $map['BronzeTime'] . ", + " . $map['SilverTime'] . ", + " . $map['GoldTime'] . ", + " . $map['AuthorTime'] . ", + " . $map['CopperPrice'] . ", + " . Tools::boolToInt($map['LapRace']) . ", + " . $map['NbLaps'] . ", + " . $map['NbCheckpoints'] . ", + '" . $this->escape($map['MapType']) . "', + '" . $this->escape($map['MapStyle']) . "' + ) ON DUPLICATE KEY UPDATE + `Name` = VALUES(`Name`), + `FileName` = VALUES(`FileName`), + `Author` = VALUES(`Author`), + `Environnement` = VALUES(`Environnement`), + `Mood` = VALUES(`Mood`), + `BronzeTime` = VALUES(`BronzeTime`), + `SilverTime` = VALUES(`SilverTime`), + `GoldTime` = VALUES(`GoldTime`), + `AuthorTime` = VALUES(`AuthorTime`), + `CopperPrice` = VALUES(`CopperPrice`), + `LapRace` = VALUES(`LapRace`), + `NbLaps` = VALUES(`NbLaps`), + `NbCheckpoints` = VALUES(`NbCheckpoints`), + `MapType` = VALUES(`MapType`), + `MapStyle` = VALUES(`MapStyle`);"; + } + + /** + * Retrieve all information about the player with the given login + */ + public function getPlayer($login) { + if (!$login) return null; + $query = "SELECT * FROM `" . self::TABLE_PLAYERS . "` WHERE `Login` = '" . $this->escape($login) . "';"; + $result = $this->mysqli->query($query); + if ($this->mysqli->error || !$result) { + trigger_error( + "Couldn't select player with login '" . $login . "'. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')'); + return null; + } + else { + while ($player = $result->fetch_assoc()) { + return $player; + } + return null; + } + } + + /** + * Escapes the given string for a mysql query + * + * @param string $string + * @return string + */ + public function escape($string) { + return $this->mysqli->escape_string($string); + } +} + +?> diff --git a/application/core/server.mControl.php b/application/core/server.mControl.php new file mode 100644 index 00000000..5e87f1da --- /dev/null +++ b/application/core/server.mControl.php @@ -0,0 +1,381 @@ +mControl = $mControl; + + // Load config + $this->config = Tools::loadConfig('server.mControl.xml'); + $this->iControl->checkConfig($this->config, array('host', 'port', 'login', 'pass'), 'server'); + + // Register for callbacks + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_1_SECOND, $this, 'eachSecond'); + } + + /** + * Perform actions every second + */ + public function eachSecond() { + // Delete cached information + $this->players = null; + } + + /** + * Fetch game directory of the server + * + * @return string + */ + public function getDataDirectory() { + if (!$this->iControl->client->query('GameDataDirectory')) { + trigger_error("Couldn't get data directory. " . $this->iControl->getClientErrorText()); + return null; + } + return $this->iControl->client->getResponse(); + } + + /** + * Checks if mControl has access to the given directory (server data directory if no param) + * + * @param string $directory + * @return bool + */ + public function checkAccess($directory = null) { + if (!$directory) { + $directory = $this->getDataDirectory(); + } + return is_dir($directory) && is_writable($directory); + } + + /** + * Fetch server login + */ + public function getLogin($client = null) { + $systemInfo = $this->getSystemInfo(false, $client); + if (!$systemInfo) return null; + return $systemInfo['ServerLogin']; + } + + /** + * Get detailed server info + */ + public function getInfo($detailed = false) { + if ($detailed) { + $login = $this->getLogin(); + if (!$this->iControl->client->query('GetDetailedPlayerInfo', $login)) { + trigger_error("Couldn't fetch detailed server player info. " . $this->iControl->getClientErrorText()); + return null; + } + } + else { + if (!$this->iControl->client->query('GetMainServerPlayerInfo')) { + trigger_error("Couldn't fetch server player info. " . $this->iControl->getClientErrorText()); + return null; + } + } + return $this->iControl->client->getResponse(); + } + + /** + * Get server options + */ + public function getOptions() { + if (!$this->iControl->client->query('GetServerOptions')) { + trigger_error("Couldn't fetch server options. " . $this->iControl->getClientErrorText()); + return null; + } + return $this->iControl->client->getResponse(); + } + + /** + * Fetch server name + */ + public function getName() { + if (!$this->iControl->client->query('GetServerName')) { + trigger_error("Couldn't fetch server name. " . $this->iControl->getClientErrorText()); + return null; + } + return $this->iControl->client->getResponse(); + } + + /** + * Fetch server version + */ + public function getVersion($forceRefresh = false) { + if (isset($this->iControl->client->version) && !$forceRefresh) return $this->iControl->client->version; + if (!$this->iControl->client->query('GetVersion')) { + trigger_error("Couldn't fetch server version. " . $this->iControl->getClientErrorText()); + return null; + } + else { + $this->iControl->client->version = $this->iControl->client->getResponse(); + return $this->iControl->client->version; + } + } + + /** + * Fetch server system info + */ + public function getSystemInfo($forceRefresh = false, &$client = null) { + if (!$this->iControl->client && !$client) return null; + if (!$client) $client = $this->iControl->client; + if (isset($client->systemInfo) && !$forceRefresh) return $client->systemInfo; + if (!$client->query('GetSystemInfo')) { + trigger_error("Couldn't fetch server system info. " . $this->iControl->getClientErrorText($client)); + return null; + } + else { + $client->systemInfo = $client->getResponse(); + return $client->systemInfo; + } + } + + /** + * Fetch network status + */ + public function getNetworkStats($forceRefresh = false) { + if (isset($this->iControl->client->networkStats) && !$forceRefresh) return $this->iControl->client->networkStats; + if (!$this->iControl->client->query('GetNetworkStats')) { + trigger_error("Couldn't fetch network stats. " . $this->iControl->getClientErrorText()); + return null; + } + else { + $this->iControl->client->networkStats = $this->iControl->client->getResponse(); + return $this->iControl->client->networkStats; + } + } + + /** + * Fetch current game mode + * + * @param bool $stringValue + * @param int $parseValue + * @return int | string + */ + public function getGameMode($stringValue = false, $parseValue = null) { + if (is_int($parseValue)) { + $gameMode = $parseValue; + } + else { + if (!$this->iControl->client->query('GetGameMode')) { + trigger_error("Couldn't fetch current game mode. " . $this->iControl->getClientErrorText()); + return null; + } + $gameMode = $this->iControl->client->getResponse(); + } + if ($stringValue) { + switch ($gameMode) { + case 0: + { + return 'Script'; + } + case 1: + { + return 'Rounds'; + } + case 2: + { + return 'TimeAttack'; + } + case 3: + { + return 'Team'; + } + case 4: + { + return 'Laps'; + } + case 5: + { + return 'Cup'; + } + case 6: + { + return 'Stunts'; + } + default: + { + return 'Unknown'; + } + } + } + return $gameMode; + } + + /** + * Fetch player info + * + * @param string $login + * @return struct + */ + public function getPlayer($login, $detailed = false) { + if (!$login) return null; + $command = ($detailed ? 'GetDetailedPlayerInfo' : 'GetPlayerInfo'); + if (!$this->iControl->client->query($command, $login)) { + trigger_error("Couldn't player info for '" . $login . "'. " . $this->iControl->getClientErrorText()); + return null; + } + return $this->iControl->client->getResponse(); + } + + /** + * Fetch all players + */ + public function getPlayers(&$client = null, &$purePlayers = null, &$pureSpectators = null) { + if (!$this->iControl->client && !$client) return null; + if (!$client) $client = $this->iControl->client; + $fetchLength = 30; + $offset = 0; + $players = array(); + if (!is_array($purePlayers)) $purePlayers = array(); + if (!is_array($pureSpectators)) $pureSpectators = array(); + $tries = 0; + while ($tries < 10) { + if (!$client->query('GetPlayerList', $fetchLength, $offset)) { + trigger_error("Couldn't get player list. " . $this->iControl->getClientErrorText($client)); + $tries++; + } + else { + $chunk = $client->getResponse(); + $count = count($chunk); + $serverLogin = $this->getLogin($client); + for ($index = 0; $index < $count; $index++) { + $login = $chunk[$index]['Login']; + if ($login === $serverLogin) { + // Ignore server + unset($chunk[$index]); + } + else { + if ($chunk[$index]['SpectatorStatus'] > 0) { + // Pure spectator + array_push($pureSpectators, $chunk[$index]); + } + else { + // Pure player + array_push($purePlayers, $chunk[$index]); + } + } + } + $players = array_merge($players, $chunk); + $offset += $count; + if ($count < $fetchLength) break; + } + } + return $players; + } + + /** + * Retrieve validation replay for given login + * + * @param string $login + * @return string + */ + public function getValidationReplay($login) { + if (!$login) return null; + if (!$this->iControl->client->query('GetValidationReplay', $login)) { + trigger_error("Couldn't get validation replay of '" . $login . "'. " . $this->iControl->getClientErrorText()); + return null; + } + return $this->iControl->client->getResponse(); + } + + public function getGhostReplay($login) { + $dataDir = $this->getDataDirectory(); + if (!$this->checkAccess($dataDir)) { + return null; + } + + // Build file name + $map = $this->getMap(); + $gameMode = $this->getGameMode(); + $time = time(); + $fileName = 'Ghost.' . $login . '.' . $gameMode . '.' . $time . '.' . $map['UId'] . '.Replay.Gbx'; + + // Save ghost replay + if (!$this->iControl->client->query('SaveBestGhostsReplay', $login, self::GHOSTREPLAYDIR . $fileName)) { + trigger_error("Couldn't save ghost replay. " . $this->iControl->getClientErrorText()); + return null; + } + + // Load replay file + $ghostReplay = file_get_contents($dataDir . 'Replays/' . self::GHOSTREPLAYDIR . $fileName); + if (!$ghostReplay) { + trigger_error("Couldn't retrieve saved ghost replay."); + return null; + } + return $ghostReplay; + } + + /** + * Fetch current map + */ + public function getMap() { + if (!$this->iControl->client) return null; + if (!$this->iControl->client->query('GetCurrentMapInfo')) { + trigger_error("Couldn't fetch map info. " . $this->iControl->getClientErrorText()); + return null; + } + return $this->iControl->client->getResponse(); + } + + /** + * Waits for the server to have the given status + */ + public function waitForStatus($client, $statusCode = 4) { + $client->query('GetStatus'); + $response = $client->getResponse(); + // Check if server reached given status + if ($response['Code'] === 4) return true; + // Server not yet in given status -> Wait for it... + $waitBegin = time(); + $timeoutTags = $this->iControl->config->xpath('timeout'); + $maxWaitTime = (!empty($timeoutTags) ? (int) $timeoutTags[0] : 20); + $lastStatus = $response['Name']; + error_log("Waiting for server to reach status " . $statusCode . "..."); + error_log("Current Status: " . $lastStatus); + while ($response['Code'] !== 4) { + sleep(1); + $client->query('GetStatus'); + $response = $client->getResponse(); + if ($lastStatus !== $response['Name']) { + error_log("New Status: " . $response['Name']); + $lastStatus = $response['Name']; + } + if (time() - $maxWaitTime > $waitBegin) { + // It took too long to reach the status + trigger_error( + "Server couldn't reach status " . $statusCode . " after " . $maxWaitTime . " seconds! " . + $this->iControl->getClientErrorText()); + return false; + } + } + return true; + } +} + +?> diff --git a/application/core/stats.mControl.php b/application/core/stats.mControl.php new file mode 100644 index 00000000..f7d4faae --- /dev/null +++ b/application/core/stats.mControl.php @@ -0,0 +1,297 @@ +mControl = $mControl; + + // Load config + $this->config = Tools::loadConfig('stats.mControl.xml'); + $this->loadSettings(); + + // Init database tables + $this->initTables(); + + // Register for needed callbacks + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_ENDMAP, $this, 'handleEndMap'); + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCHAT, $this, 'handlePlayerChat'); + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCONNECT, $this, 'handlePlayerConnect'); + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERDISCONNECT, $this, 'handlePlayerDisconnect'); + $this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_TM_PLAYERFINISH, $this, 'handlePlayerFinish'); + } + + /** + * Create the database tables + */ + private function initTables() { + $query = ""; + + // Server stats + $query .= "CREATE TABLE IF NOT EXISTS `" . self::TABLE_STATS_SERVER . "` ( + `index` int(11) NOT NULL AUTO_INCREMENT, + `day` date NOT NULL, + `connectCount` int(11) NOT NULL DEFAULT '0', + `maxPlayerCount` int(11) NOT NULL DEFAULT '0', + `playedMaps` int(11) NOT NULL DEFAULT '0', + `finishCount` int(11) NOT NULL DEFAULT '0', + `changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`index`), + UNIQUE KEY `day` (`day`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores server stats' AUTO_INCREMENT=1;"; + + // Player stats + $query .= "CREATE TABLE IF NOT EXISTS `" . self::TABLE_STATS_PLAYERS . "` ( + `index` int(11) NOT NULL AUTO_INCREMENT, + `Login` varchar(100) NOT NULL, + `playTime` int(11) NOT NULL DEFAULT '0', + `connectCount` int(11) NOT NULL DEFAULT '0', + `chatCount` int(11) NOT NULL DEFAULT '0', + `finishCount` int(11) NOT NULL DEFAULT '0', + `hitCount` int(11) NOT NULL DEFAULT '0', + `eliminationCount` int(11) NOT NULL DEFAULT '0', + `lastJoin` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`index`), + UNIQUE KEY `Login` (`Login`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Tracks player stats' AUTO_INCREMENT=1;"; + + // Perform queries + if (!$this->iControl->database->multiQuery($query)) { + trigger_error("Creating stats tables failed."); + } + } + + /** + * Load settings from config + */ + private function loadSettings() { + $this->settings = new \stdClass(); + + $this->settings->track_server_connects = Tools::checkSetting($this->config, 'track_server_connects'); + $this->settings->track_server_max_players = Tools::checkSetting($this->config, 'track_server_max_players'); + $this->settings->track_server_played_maps = Tools::checkSetting($this->config, 'track_server_played_maps'); + $this->settings->track_server_finishes = Tools::checkSetting($this->config, 'track_server_finishes'); + + $this->settings->track_player_connects = Tools::checkSetting($this->config, 'track_player_connects'); + $this->settings->track_player_playtime = Tools::checkSetting($this->config, 'track_player_playtime'); + $this->settings->track_player_chats = Tools::checkSetting($this->config, 'track_player_chats'); + $this->settings->track_player_finishes = Tools::checkSetting($this->config, 'track_player_finishes'); + } + + /** + * Handle EndMap callback + */ + public function handleEndMap($callback) { + $multiquery = ""; + + // Track played server maps + if ($this->settings->track_server_played_maps) { + $multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` ( + `day`, + `playedMaps` + ) VALUES ( + CURDATE(), + 1 + ) ON DUPLICATE KEY UPDATE + `playedMaps` = `playedMaps` + VALUES(`playedMaps`) + ;"; + } + + // Perform query + if (!$this->iControl->database->multiQuery($multiquery)) { + trigger_error("Perform queries on end map failed."); + } + } + + /** + * Handle PlayerChat callback + */ + public function handlePlayerChat($callback) { + if ($callback[1][0] <= 0) return; + $multiquery = ""; + $login = $callback[1][1]; + + // Track chats + if ($this->settings->track_player_chats) { + $multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` ( + `Login`, + `chatCount` + ) VALUES ( + '" . $this->iControl->database->escape($login) . "', + 1 + ) ON DUPLICATE KEY UPDATE + `chatCount` = `chatCount` + VALUES(`chatCount`) + ;"; + } + + // Perform query + if (!$this->iControl->database->multiQuery($multiquery)) { + trigger_error("Perform queries on player chat failed."); + } + } + + /** + * Handle PlayerConnect callback + */ + public function handlePlayerConnect($callback) { + $multiquery = ""; + $login = $callback[1][0]; + + // Track server connect + if ($this->settings->track_server_connects) { + $multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` ( + `day`, + `connectCount` + ) VALUES ( + CURDATE(), + 1 + ) ON DUPLICATE KEY UPDATE + `connectCount` = `connectCount` + VALUES(`connectCount`) + ;"; + } + + // Track server max players + if ($this->settings->track_server_max_players) { + $players = $this->iControl->server->getPlayers(); + $multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` ( + `day`, + `maxPlayerCount` + ) VALUES ( + CURDATE(), + " . count($players) . " + ) ON DUPLICATE KEY UPDATE + `maxPlayerCount` = GREATEST(`maxPlayerCount`, VALUES(`maxPlayerCount`)) + ;"; + } + + // Track player connect + if ($this->settings->track_player_connects) { + $multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` ( + `Login`, + `lastJoin`, + `connectCount` + ) VALUES ( + '" . $this->iControl->database->escape($login) . "', + NOW(), + 1 + ) ON DUPLICATE KEY UPDATE + `lastJoin` = VALUES(`lastJoin`), + `connectCount` = `connectCount` + VALUES(`connectCount`) + ;"; + } + + // Perform query + if (!$this->iControl->database->multiQuery($multiquery)) { + trigger_error("Perform queries on player connect failed."); + } + } + + /** + * Handle PlayerDisconnect callback + */ + public function handlePlayerDisconnect($callback) { + $multiquery = ""; + $login = $callback[1][0]; + + // Track player playtime + if ($this->settings->track_player_playtime) { + $query = "SELECT `lastJoin` FROM `" . self::TABLE_STATS_PLAYERS . "` + WHERE `Login` = '" . $this->iControl->database->escape($login) . "' + ;"; + $result = $this->iControl->database->query($query); + if (!$result) { + // Error + trigger_error("Error selecting player join time from '" . $login . "'."); + } + else { + // Add play time + while ($row = $result->fetch_object()) { + if (!property_exists($row, 'lastJoin')) continue; + $lastJoin = strtotime($row->lastJoin); + $lastJoin = ($lastJoin > $this->iControl->startTime ? $lastJoin : $this->iControl->startTime); + $multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` ( + `Login`, + `playTime` + ) VALUES ( + '" . $this->iControl->database->escape($login) . "', + TIMESTAMPDIFF(SECOND, '" . Tools::timeToTimestamp($lastJoin) . "', NOW()) + ) ON DUPLICATE KEY UPDATE + `playTime` = `playTime` + VALUES(`playTime`) + ;"; + break; + } + } + } + + // Perform query + if (!$this->iControl->database->multiQuery($multiquery)) { + trigger_error("Perform queries on player connect failed."); + } + } + + /** + * Handle the PlayerFinish callback + */ + public function handlePlayerFinish($callback) { + if ($callback[1][0] <= 0) return; + if ($callback[1][2] <= 0) return; + + $multiquery = ""; + $login = $callback[1][1]; + + // Track server finishes + if ($this->settings->track_server_finishes) { + $multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` ( + `day`, + `finishCount` + ) VALUES ( + CURDATE(), + 1 + ) ON DUPLICATE KEY UPDATE + `finishCount` = `finishCount` + VALUES(`finishCount`) + ;"; + } + + // Track player finishes + if ($this->settings->track_player_finishes) { + $multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` ( + `Login`, + `finishCount` + ) VALUES ( + '" . $this->iControl->database->escape($login) . "', + 1 + ) ON DUPLICATE KEY UPDATE + `finishCount` = `finishCount` + VALUES(`finishCount`) + ;"; + } + + // Perform query + if (!$this->iControl->database->multiQuery($multiquery)) { + trigger_error("Perform queries on player finish failed."); + } + } +} + +?> diff --git a/application/core/tools.mControl.php b/application/core/tools.mControl.php new file mode 100644 index 00000000..9490a1d0 --- /dev/null +++ b/application/core/tools.mControl.php @@ -0,0 +1,240 @@ +xpath('//' . $setting); + if (empty($settings)) { + return false; + } + else { + foreach ($settings as $setting) { + return self::toBool((string) $setting[0]); + } + } + } + + /** + * Check if the given data describes a player + * + * @param array $player + * @return bool + */ + public static function isPlayer($player) { + if (!$player || !is_array($player)) return false; + if (!array_key_exists('PlayerId', $player) || !is_int($player['PlayerId']) || $player['PlayerId'] <= 0) return false; + return true; + } + + /** + * Convert the given time int to mysql timestamp + * + * @param int $time + * @return string + */ + public static function timeToTimestamp($time) { + return date("Y-m-d H:i:s", $time); + } + + /** + * Add alignment attributes to an xml element + * + * @param simple_xml_element $xml + * @param string $halign + * @param string $valign + */ + public static function addAlignment($xml, $halign = 'center', $valign = 'center2') { + if (!is_object($xml) || !method_exists($xml, 'addAttribute')) return; + if (!property_exists($xml, 'halign')) $xml->addAttribute('halign', $halign); + if (!property_exists($xml, 'valign')) $xml->addAttribute('valign', $valign); + } + + /** + * Add translate attribute to an xml element + * + * @param simple_xml_element $xml + * @param bool $translate + */ + public static function addTranslate($xml, $translate = true) { + if (!is_object($xml) || !method_exists($xml, 'addAttribute')) return; + if (!property_exists($xml, 'translate')) $xml->addAttribute('translate', ($translate ? 1 : 0)); + } + + /** + * Load a remote file + * + * @param string $url + * @return string || null + */ + public static function loadFile($url) { + if (!$url) return false; + $urlData = parse_url($url); + $port = (isset($urlData['port']) ? $urlData['port'] : 80); + + $fsock = fsockopen($urlData['host'], $port); + stream_set_timeout($fsock, 3); + + $query = 'GET ' . $urlData['path'] . ' HTTP/1.0' . PHP_EOL; + $query .= 'Host: ' . $urlData['host'] . PHP_EOL; + $query .= 'Content-Type: UTF-8' . PHP_EOL; + $query .= 'User-Agent: mControl v' . mControl::VERSION . PHP_EOL; + $query .= PHP_EOL; + + fwrite($fsock, $query); + + $buffer = ''; + $info = array('timed_out' => false); + while (!feof($fsock) && !$info['timed_out']) { + $buffer .= fread($fsock, 1024); + $info = stream_get_meta_data($fsock); + } + fclose($fsock); + + if ($info['timed_out'] || !$buffer) { + return null; + } + if (substr($buffer, 9, 3) != "200") { + return null; + } + + $result = explode("\r\n\r\n", $buffer, 2); + + if (count($result) < 2) { + return null; + } + + return $result[1]; + } + + /** + * Formats the given time (milliseconds) + * + * @param int $time + * @return string + */ + public static function formatTime($time) { + if (!is_int($time)) $time = (int) $time; + $milliseconds = $time % 1000; + $seconds = floor($time / 1000); + $minutes = floor($seconds / 60); + $hours = floor($minutes / 60); + $minutes -= $hours * 60; + $seconds -= $hours * 60 + $minutes * 60; + $format = ($hours > 0 ? $hours . ':' : ''); + $format .= ($hours > 0 && $minutes < 10 ? '0' : '') . $minutes . ':'; + $format .= ($seconds < 10 ? '0' : '') . $seconds . ':'; + $format .= ($milliseconds < 100 ? '0' : '') . ($milliseconds < 10 ? '0' : '') . $milliseconds; + return $format; + } + + /** + * Convert given data to real boolean + * + * @param + * mixed data + */ + public static function toBool($var) { + if ($var === true) return true; + if ($var === false) return false; + if ($var === null) return false; + if (is_object($var)) { + $var = (string) $var; + } + if (is_int($var)) { + return ($var > 0); + } + else if (is_string($var)) { + $text = strtolower($var); + if ($text === 'true' || $text === 'yes') { + return true; + } + else if ($text === 'false' || $text === 'no') { + return false; + } + else { + return ((int) $text > 0); + } + } + else { + return (bool) $var; + } + } + + /** + * Converts the given boolean to an int representation + * + * @param bool $bool + * @return int + */ + public static function boolToInt($bool) { + return ($bool ? 1 : 0); + } + + /** + * Build new simple xml element + * + * @param string $name + * @param string $id + * @return \SimpleXMLElement + */ + public static function newManialinkXml($id = null) { + $xml = new \SimpleXMLElement(''); + $xml->addAttribute('version', '1'); + if ($id) $xml->addAttribute('id', $id); + return $xml; + } + + /** + * Load config xml-file + * + * @param string $fileName + * @return \SimpleXMLElement + */ + public static function loadConfig($fileName) { + // Load config file from configs folder + $fileLocation = mControl . '/configs/' . $fileName; + if (!file_exists($fileLocation)) { + trigger_error("Config file doesn't exist! (" . $fileName . ")", E_USER_ERROR); + } + return simplexml_load_file($fileLocation); + } + + /** + * Send the given manialink to players + * + * @param string $manialink + * @param array $logins + */ + public static function sendManialinkPage($client, $manialink, $logins = null, $timeout = 0, $hideOnClick = false) { + if (!$client || !$manialink) return; + if (!$logins) { + // Send manialink to all players + $client->query('SendDisplayManialinkPage', $manialink, $timeout, $hideOnClick); + } + else if (is_array($logins)) { + // Send manialink to players + foreach ($logins as $login) { + $client->query('SendDisplayManialinkPageToLogin', $login, $manialink, $timeout, $hideOnClick); + } + } + else if (is_string($logins)) { + // Send manialink to player + $client->query('SendDisplayManialinkPageToLogin', $logins, $manialink, $timeout, $hideOnClick); + } + } +} + +?> diff --git a/application/mControl.bat b/application/mControl.bat new file mode 100644 index 00000000..e6c8fdb6 --- /dev/null +++ b/application/mControl.bat @@ -0,0 +1,2 @@ +REM set the path to your php.exe here +START "" /B "D:\Programme\xampp\php\php.exe" -f "mControl.php" 2>&1 diff --git a/application/mControl.php b/application/mControl.php new file mode 100644 index 00000000..983ac3cf --- /dev/null +++ b/application/mControl.php @@ -0,0 +1,26 @@ +run(true); + +?> diff --git a/application/mControl.sh b/application/mControl.sh new file mode 100644 index 00000000..90a61ecb --- /dev/null +++ b/application/mControl.sh @@ -0,0 +1,3 @@ +#!/bin/sh +php mControl.php 2>&1 & +echo $! > mControl.pid diff --git a/application/plugins/plugin.mControl.php b/application/plugins/plugin.mControl.php new file mode 100644 index 00000000..d46297da --- /dev/null +++ b/application/plugins/plugin.mControl.php @@ -0,0 +1,37 @@ +mControl = $mControl; + + error_log('Pugin v' . self::VERSION . ' ready!'); + } + + /** + * Perform actions during each loop + */ + public function loop() { + } +} + +?>