/** Balances teams. * This function balances the teams according to the arrival order of the players. * It is executed on new connections and when a player changes team (if AutoTeams is enabled in plugin config). * It only changes the team of new players. * * \param $player The player who send the command. (if is player has send command) * * \return Nothing. */ private function _balance($player = null) { //We take player count of each team. $teams_count = Server::getTeamCount(); if ($teams_count[Server::TEAM_RED] >= $teams_count[Server::TEAM_BLUE] + 2) { $too_many_player = floor(($teams_count[Server::TEAM_RED] - $teams_count[Server::TEAM_BLUE]) / 2); $src_team = Server::TEAM_RED; $dest_team = strtolower(Server::getTeamName(Server::TEAM_BLUE)); $balance = TRUE; } elseif ($teams_count[Server::TEAM_BLUE] >= $teams_count[Server::TEAM_RED] + 2) { $too_many_player = floor(($teams_count[Server::TEAM_BLUE] - $teams_count[Server::TEAM_RED]) / 2); $src_team = Server::TEAM_BLUE; $dest_team = strtolower(Server::getTeamName(Server::TEAM_RED)); $balance = TRUE; } else { $balance = FALSE; } //If the teams are unbalanced if ($balance) { //We get player list $players = Server::getPlayerList($src_team); $last = array(); foreach ($players as $player) { $last[$player->time] = $player->id; } //We sorts the players of the team by time spent on the server. krsort($last); //Processing loop while ($too_many_player > 0) { //We take the last player of the team $player = array_shift($last); //Add on ClientUserinfoChanged ignore list $this->_ClientUserinfoChangedIgnore[$player] = $player; //We change the team of the player RCon::forceteam($player, $dest_team); --$too_many_player; } RCon::say('Teams are now balanced'); } else { if ($player !== NULL) { RCon::tell($player, 'Teams are already balanced'); } } }
/** Executes a step of parsing. * This function executes a step of parsing, i.e. reads the log, parses the read lines and calls the adapted events. * * \return TRUE if all gone correctly, FALSE otherwise. */ public function step() { $time = time(); if (!$this->_enabled || $this->_hold && $time == $this->_lastRead) { return TRUE; } if ($this->_shutdownTime !== FALSE && $time - 10 > $this->_shutdownTime && !empty($this->players)) { //Properly sending the events, allowing plugins to perform their actions. foreach ($this->players as $id => $player) { $this->_leelabot->plugins->callServerEvent('ClientDisconnect', $id); } $this->players = array(); Leelabot::message('Deleted player data for server $0', array($this->_name), E_DEBUG); } $this->_lastRead = time(); $data = $this->readLog(); if ($data) { $data = explode("\n", trim($data, "\n")); foreach ($data as $line) { //Parsing line for event and arguments $line = substr($line, 7); //Remove the time (todo : see if elapsed time for the map is useful (more than when we count it ourselves)) Leelabot::printText('[' . $this->_name . '] ' . $line); $line = explode(':', $line, 2); if (isset($line[1])) { $line[1] = trim($line[1]); } switch ($line[0]) { //A client connects case 'ClientConnect': $id = intval($line[1]); $this->players[$id] = new Storage(array('id' => $id, 'begin' => FALSE, 'team' => Server::TEAM_SPEC, 'level' => $this->_defaultLevel, 'time' => time(), 'uuid' => Leelabot::UUID())); Leelabot::message('Client connected: $0', array($id), E_DEBUG); $this->_leelabot->plugins->callServerEvent('ClientConnect', $id); break; //A client has disconnected //A client has disconnected case 'ClientDisconnect': $id = intval($line[1]); $this->_leelabot->plugins->callServerEvent('ClientDisconnect', $id); Leelabot::message('Client disconnected: $0', array($id), E_DEBUG); unset($this->players[$id]); break; //The client has re-sent his personal info (like credit name, weapons, weapmodes, card number) //The client has re-sent his personal info (like credit name, weapons, weapmodes, card number) case 'ClientUserinfo': list($id, $infostr) = explode(' ', $line[1], 2); $userinfo = Server::parseInfo($infostr); Leelabot::message('Client send user info: $0', array($id), E_DEBUG); Leelabot::message(' Name: $0', array($userinfo['name']), E_DEBUG); //Basically it's a copypasta of the code user above for initial data storage if (isset($userinfo['characterfile'])) { $playerData['isBot'] = TRUE; Leelabot::message(' Is a bot.', array(), E_DEBUG); } else { $playerData['isBot'] = FALSE; $address = explode(':', $userinfo['ip']); $playerData['addr'] = $address[0]; $playerData['port'] = $address[1]; $playerData['guid'] = $userinfo['cl_guid']; Leelabot::message(' GUID: $0', array($playerData['guid'])); Leelabot::message(' IP: $0', array($playerData['addr'])); } $playerData['name'] = preg_replace('#^[0-9]#', '', $userinfo['name']); $playerData['id'] = $id; $this->_leelabot->plugins->callServerEvent('ClientUserinfo', array($id, $userinfo)); $this->players[$id]->merge($playerData); //Binding IP pointer if (!isset($this->players[$id]->ip)) { $this->players[$id]->ip =& $this->players[$id]->addr; } if (!isset($this->players[$id]->other)) { $this->players[$id]->other = $userinfo; } else { $this->players[$id]->other = array_merge($this->players[$id]->other, $userinfo); } break; //Change in client userinfo, useful for gathering teams //Change in client userinfo, useful for gathering teams case 'ClientUserinfoChanged': list($id, $infostr) = explode(' ', $line[1], 2); $userinfo = Server::parseInfo($infostr); Leelabot::message('Client has send changed user info: $0', array($id), E_DEBUG); Leelabot::message(' Name: $0', array($userinfo['n']), E_DEBUG); Leelabot::message(' Team: $0', array(Server::getTeamName($userinfo['t'])), E_DEBUG); $this->_leelabot->plugins->callServerEvent('ClientUserinfoChanged', array($id, $userinfo)); $this->players[$id]->team = $userinfo['t']; $this->players[$id]->other['cg_rgb'] = $userinfo['a0'] . ' ' . $userinfo['a1'] . ' ' . $userinfo['a2']; $this->players[$id]->name = preg_replace('#^[0-9]#', '', $userinfo['n']); break; //Player start to play //Player start to play case 'ClientBegin': $id = intval($line[1]); Leelabot::message('Client has begun: $0', array($id), E_DEBUG); $this->_leelabot->plugins->callServerEvent('ClientBegin', $id); $this->players[$id]->begin = TRUE; break; //New round, map info is re-sended //New round, map info is re-sended case 'InitRound': $serverinfo = Server::parseInfo($line[1]); Leelabot::message('New round started', array(), E_DEBUG); $this->_leelabot->plugins->callServerEvent('InitRound', $serverinfo); //New map, with new info //New map, with new info case 'InitGame': if ($line[0] == 'InitGame') { $this->enable(); $this->_shutdownTime = FALSE; $serverinfo = Server::parseInfo($line[1]); Leelabot::message('New map started: $0', array($serverinfo['mapname']), E_DEBUG); $this->_leelabot->plugins->callServerEvent('InitGame', $serverinfo); $this->scores = array(1 => 0, 2 => 0); } if (!empty($this->serverInfo)) { $this->serverInfo = array_merge($this->serverInfo, $serverinfo); } else { $this->serverInfo = $serverinfo; } break; //The game has ended. Usually, next to that line are written the scores, but we just don't care about them //The game has ended. Usually, next to that line are written the scores, but we just don't care about them case 'Exit': Leelabot::message('Map ended', array(), E_DEBUG); $this->_leelabot->plugins->callServerEvent('Exit', $line[1]); break; //Survivor round has ended, with the winner //Survivor round has ended, with the winner case 'SurvivorWinner': Leelabot::message('Round ended, winner: $0', array($line[1]), E_DEBUG); $winner = Server::getTeamNumber($line[1]); $this->_leelabot->plugins->callServerEvent('SurvivorWinner', $winner); if ($winner) { $this->scores[$winner]++; } break; //The server goes down (it occurs also with a map change) //The server goes down (it occurs also with a map change) case 'ShutdownGame': Leelabot::message('The server is going down', array(), E_DEBUG); $this->_leelabot->plugins->callServerEvent('ShutdownGame'); //Starting the countdown before resetting user data. $this->_shutdownTime = time(); if ($this->_autoHold) { $this->hold(); } break; //One player kills another, probably the most common action that will occur ? //One player kills another, probably the most common action that will occur ? case 'Kill': $kill = explode(':', $line[1]); $kill = explode(' ', $kill[0]); $this->_leelabot->plugins->callServerEvent('Kill', $kill); break; //One player hit another, OMG FLOOD OF GAME LOG ! //One player hit another, OMG FLOOD OF GAME LOG ! case 'Hit': $hit = explode(':', $line[1]); $hit = explode(' ', $hit[0]); $this->_leelabot->plugins->callServerEvent('Hit', $hit); break; //Item of player, example : take kevlar. (utility ?) //Item of player, example : take kevlar. (utility ?) case 'Item': $item = explode(' ', $line[1]); $this->_leelabot->plugins->callServerEvent('Item', $item); break; //Actions on flag //Actions on flag case 'Flag': $flag = explode(':', $line[1]); $flag = explode(' ', $flag[0]); $this->_leelabot->plugins->callServerEvent('Flag', $flag); //If flag has been captured if ($flag[1] == 2) { $player = Server::getPlayer($flag[0]); $this->scores[$player->team]++; } break; //Player message //Player message case 'say': case 'sayteam': $message = explode(' ', $line[1], 2); $id = intval($message[0]); $contents = explode(':', $message[1], 2); $contents = substr($contents[1], 1); Leelabot::message('$0 sended a message: $1', array($this->players[$id]->name, $contents), E_DEBUG); $this->_leelabot->plugins->callServerEvent('Say', array($id, $contents)); //We check if it's a command if ($contents[0] == '!' && !$this->_hold) { $contents = substr($contents, 1); $args = explode(' ', $contents); $command = array_shift($args); Leelabot::message('Command catched: !$0', array($command), E_DEBUG); $this->_leelabot->plugins->callCommand($command, $id, $args); } break; // Hotpotato // Hotpotato case 'Hotpotato': $this->_leelabot->plugins->callServerEvent('Hotpotato'); break; // Player call a vote // Player call a vote case 'Callvote': $param = explode('-', $line[1]); array_walk($param, 'trim'); $id = intval($param[0]); $votestring = explode('"', $param[1]); $votestring = $vote[1]; $this->_leelabot->plugins->callServerEvent('Callvote', array($id, $votestring)); break; // Player vote (1=yes, 2=no) // Player vote (1=yes, 2=no) case 'Vote': $param = explode('-', $line[1]); array_walk($param, 'trim'); array_walk($param, 'intval'); $id = $param[0]; $vote = $param[1]; $this->_leelabot->plugins->callServerEvent('Vote', array($id, $vote)); break; // Player call a radio alert // Player call a radio alert case 'Radio': // idnum - msg_group - msg_id - "player_location" - "message" $param = explode('-', $line[1]); array_walk($param, 'trim'); $id = intval($param[0]); $groupid = intval($param[1]); $msgid = intval($param[2]); $location = explode('"', $param[3]); $location = $location[1]; $message = explode('"', $param[4]); $message = $message[1]; $this->_leelabot->plugins->callServerEvent('Radio', array($id, $groupid, $msgid, $location, $message)); break; case 'AccountKick': $param = explode('-', $line[1]); array_walk($param, 'trim'); $id = intval($param[0]); $reason = explode(':', $param[1]); $reason = trim($reason[1]); $this->_leelabot->plugins->callServerEvent('AccountKick', $id, $reason); break; case 'AccountBan': // idnum - login - [nb_days]d - [nb_hours]h - [nb_mins]m $param = explode('-', $line[1]); array_walk($param, 'trim'); $id = intval($param[0]); $login = $param[1]; $days = intval(str_replace('d', '', $param[2])); $hours = intval(str_replace('h', '', $param[3])); $mins = intval(str_replace('m', '', $param[4])); $this->_leelabot->plugins->callServerEvent('AccountBan', array($id, $login, $days, $hours, $mins)); break; case 'AccountValidated': // idnum - login - rcon_level - "notoriety" $param = explode(' - ', $line[1]); array_walk($param, 'trim'); $id = intval($param[0]); $login = $param[1]; $rconlevel = intval($param[2]); $notoriety = explode('"', $param[3]); $notoriety = $notoriety[1]; //Setting player properties $this->players[$id]->authLogin = $login; $this->players[$id]->authRConLevel = $rconlevel; $this->players[$id]->authNotoriety = $notoriety; $this->_leelabot->plugins->callServerEvent('AccountValidated', array($id, $login, $rconlevel, $notoriety)); break; case 'AccountRejected': // idnum - login - "reason" $param = explode('-', $line[1]); array_walk($param, 'trim'); $id = intval($param[0]); $login = $param[1]; $reason = explode('"', $param[2]); $reason = $reason[1]; $this->_leelabot->plugins->callServerEvent('AccountRejected', array($id, $login, $reason)); break; } } } //Then, we read incoming data on the RCon socket if any, only every 100ms if (microtime(TRUE) - $this->_rconLastRead >= 0.1) { $data = RCon::getReply(); //If there is incoming data which is not an error if (!empty($data)) { Leelabot::message('RCon data received: $0', array($data), E_DEBUG); if (strpos($data, 'rconPassword') === 0) { $split = explode(' ', $data, 2); $this->_rconpassword = $split[1]; $this->_rcon->setRConPassword($this->_rconpassword); Leelabot::message('Updated RCon password.', array(), E_NOTICE); $this->_rcon->resend(); //FIXME: See if we have to re-send the command or not (re-sending can cause random behavior) } } elseif ($data === FALSE && $this->_rcon->lastError() != Quake3RCon::E_NOREPLY) { Leelabot::message('RCon error: $0', array($this->_rcon->getErrorString($this->_rcon->lastError())), E_WARNING); if ($this->_rcon->lastError() == Quake3RCon::E_BADRCON && !empty($this->_recoverPassword)) { $this->_rcon->send('rconRecovery ' . $this->_recoverPassword); } } } //Before returning, we execute all routines $this->_leelabot->plugins->callAllRoutines(); return TRUE; }
/** Client user info change : notify it in the connection log. * This function is triggered by the ClientUserinfoChanged event. It will log his data in the connection log if necessary. * * \param $id The player's ID. * \param $userinfo The player's user info. * * \return Nothing. */ public function SrvEventClientUserinfoChanged($id, $userinfo) { $player = Server::getPlayer($id); $this->log('connection', 'Player ' . $player->name . ' <' . $player->uuid . '> changed :'); $this->log('connection', "\tName: " . $userinfo['n']); $this->log('connection', "\tTeam: " . Server::getTeamName($userinfo['t'])); }