//////////////////////////////////////////////////////////////////////////////// // Scorched3D (c) 2000-2003 // // This file is part of Scorched3D. // // Scorched3D 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. // // Scorched3D 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 Scorched3D; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ServerAddPlayerHandler *ServerAddPlayerHandler::instance_ = 0; ServerAddPlayerHandler *ServerAddPlayerHandler::instance() { if (!instance_) { instance_ = new ServerAddPlayerHandler; } return instance_; } ServerAddPlayerHandler::ServerAddPlayerHandler() { ScorchedServer::instance()->getComsMessageHandler().addHandler( "ComsAddPlayerMessage", this); } ServerAddPlayerHandler::~ServerAddPlayerHandler() { } bool ServerAddPlayerHandler::processMessage(unsigned int destinationId, const char *messageType, NetBufferReader &reader) { ComsAddPlayerMessage message; if (!message.readMessage(reader)) return false; // Validate player unsigned int playerId = message.getPlayerId(); Tank *tank = ScorchedServer::instance()->getTankContainer().getTankById(playerId); if (!tank || (tank->getState().getState() != TankState::sDead) && (tank->getState().getState() != TankState::sPending)) { ServerCommon::sendString(destinationId, "Can only change tank when dead.", false); return true; } // Add a computer player (if chosen and a single player match) if (0 != strcmp(message.getPlayerType(), "Human")) { if (ScorchedServer::instance()->getGameState().getState() != ServerState::ServerStateTooFewPlayers) { ServerCommon::sendString(destinationId, "Can only change type before game starts.", false); return true; } // Only allow this on a single player game if (OptionsParam::instance()->getDedicatedServer()) return true; // Check tank ai is valid TankAI *ai = ScorchedServer::instance()->getTankAIs(). getAIByName(message.getPlayerType()); if (!ai) return true; // Set the tank to have the ai tank->setTankAI(ai->getCopy(tank)); tank->setDestinationId(0); } else { tank->setDestinationId(destinationId); tank->setTankAI(0); } // Setup the new player std::string name(message.getPlayerName()); filterName(tank, name); if (name != tank->getName()) { getUniqueName(tank, name); // Tell this computer that a new tank has connected if (OptionsParam::instance()->getDedicatedServer()) { Logger::log(formatString("Player playing dest=\"%i\" id=\"%i\" \"%s\"->\"%s\"", tank->getDestinationId(), tank->getPlayerId(), tank->getName(), name.c_str())); if (name != tank->getName()) { ServerCommon::sendString(0, formatString("Player changed name \"%s\"->\"%s\"", tank->getName(), name.c_str())); } } } tank->setName(name.c_str()); // Player has set a new color if (tank->getTeam() == 0 && message.getPlayerColor() != tank->getColor()) { // Check the color is not already in use std::map &tanks = ScorchedServer::instance()->getTankContainer().getPlayingTanks(); if (TankColorGenerator::instance()->colorAvailable( message.getPlayerColor(), tanks, tank)) { // Set this color tank->setColor(message.getPlayerColor()); } } bool noAvatar = !tank->getAvatar().getName()[0] && message.getPlayerIconName()[0]; if (noAvatar) // Currently we can only set the avatar once { if (message.getPlayerIcon().getBufferUsed() <= (unsigned) ScorchedServer::instance()->getOptionsGame().getMaxAvatarSize()) { tank->getAvatar().setFromBuffer( message.getPlayerIconName(), message.getPlayerIcon()); // Send a new add message to all clients (this contains the avatar) // the client will not add the player but will update the avatar instead // Note: this can be removed if we ever have enough bandwidth to send // the avatar in each state message along with the rest of the tanks // attributes. ComsMessageSender::sendToAllConnectedClients(message); } } // Tell the logger about a new tank StatsLogger::instance()->tankJoined(tank); if (OptionsParam::instance()->getDedicatedServer()) { char *rank = StatsLogger::instance()->tankRank(tank); if (strcmp(rank, "-") != 0) { ServerCommon::sendString(0, formatString("Welcome back %s, you are ranked %s", tank->getName(), rank), false); } if (tank->getState().getSpectator()) { ServerCommon::sendString(0, formatString("Player playing \"%s\"", tank->getName())); if (ScorchedServer::instance()->getGameState().getState() == ServerState::ServerStateStarting) { // Reset the starting timer ScorchedServer::instance()->getGameState().stimulate( ServerState::ServerStimulusStarting); } } } // Make sure the tank state is as we expected // This also fixes setting the state after loading // a saved game tank->getState().setSpectator(false); tank->getState().setLoading(false); tank->getState().setInitializing(false); // Choose a team (if applicable) if (ScorchedServer::instance()->getOptionsGame().getTeams() > 1) { if (message.getPlayerTeam() > 0 && message.getPlayerTeam() <= (unsigned int) ScorchedServer::instance()->getOptionsGame().getTeams()) { tank->setTeam(message.getPlayerTeam()); } else { tank->setTeam(ScorchedServer::instance()->getOptionsTransient().getLeastUsedTeam( ScorchedServer::instance()->getTankContainer())); } } // Make sure the model is available and for the correct team // Do this AFTER the team has been set TankModel *tankModel = ScorchedServer::instance()->getTankModels(). getModelByName(message.getModelName(), tank->getTeam(), tank->isTemp()); tank->getModelContainer().setTankModelName( tankModel->getName(), message.getModelName(), tankModel->getTypeName()); // If we are in a waiting for players state then we can // send the state of these new players if (ScorchedServer::instance()->getGameState().getState() == ServerState::ServerStateTooFewPlayers || ScorchedServer::instance()->getGameState().getState() == ServerState::ServerStateStarting) { ComsPlayerStateMessage message(true); ComsMessageSender::sendToAllConnectedClients(message); } return true; } void ServerAddPlayerHandler::filterName(Tank *tank, std::string &sentname) { // Ensure this name does not have any "bad" words in it ScorchedServerUtil::instance()->textFilter.filterString(sentname); // Form the correct player name // Remove spaces from the front of the name and // unwanted characters from middle char *nameBeginning = (char *) sentname.c_str(); while (*nameBeginning == ' ') nameBeginning++; char *nameEnding = nameBeginning + strlen(nameBeginning); while (*nameEnding == ' ' || *nameEnding == '\0') { *nameEnding = '\0'; nameEnding--; } char *nameMiddle = nameBeginning; for (;*nameMiddle; nameMiddle++) { if (*nameMiddle == '\"') *nameMiddle = '\''; else if (*nameMiddle == ']') *nameMiddle = ')'; else if (*nameMiddle == '[') *nameMiddle = '('; else if (*nameMiddle == '%') *nameMiddle = '$'; // Save problems with special chars } // Ensure this name does not have the bot name in it const char *botPrefix = ScorchedServer::instance()->getOptionsGame().getBotNamePrefix(); char *botPrefixPos = s3d_stristr(sentname.c_str(), botPrefix); if (botPrefixPos) { for (int i=0; i<(int) strlen(botPrefix); i++, botPrefixPos++) { (*botPrefixPos) = ' '; } } // Check the client provides a name with a least 1 char in it // and the name is less than 16 chars std::string playerName; int nameLen = strlen(nameBeginning); if (nameLen == 0) playerName = "NoName"; else if (nameLen < 22) playerName = nameBeginning; else playerName.append(nameBeginning, 22); sentname = playerName; } void ServerAddPlayerHandler::getUniqueName(Tank *tank, std::string &playerName) { // Make sure no-one has the same name while (ScorchedServer::instance()->getTankContainer(). getTankByName(playerName.c_str())) { playerName += "(2)"; } // Make sure that no-one else has the same registered name // except the prefered user that has this name if (ScorchedServer::instance()->getOptionsGame().getRegisteredUserNames()) { ServerAuthHandler *authHandler = ScorchedServerUtil::instance()->getAuthHandler(); if (authHandler) { while (!authHandler->authenticateUserName(tank->getUniqueId(), playerName.c_str())) { playerName += "(2)"; } } } }