// ====================================================================== // // CharacterCreationTracker.cpp // copyright (c) 2001 Sony Online Entertainment // // ====================================================================== #include "FirstCentralServer.h" #include "CharacterCreationTracker.h" #include "ConfigCentralServer.h" #include "UnicodeUtils.h" #include "serverNetworkMessages/CentralConnectionServerMessages.h" #include "serverNetworkMessages/LoginCreateCharacterAckMessage.h" #include "serverNetworkMessages/LoginCreateCharacterMessage.h" #include "serverUtility/ServerClock.h" #include "sharedFoundation/Clock.h" #include "sharedFoundation/ExitChain.h" #include "sharedFoundation/FormattedString.h" #include "sharedLog/Log.h" #include "sharedNetworkMessages/GenericValueTypeMessage.h" #include "sharedNetworkMessages/NameErrors.h" #include "sharedNetworkMessages/NetworkStringIds.h" #include "sharedUtility/StartingLocationData.h" #include "sharedUtility/StartingLocationManager.h" // ====================================================================== CharacterCreationTracker * CharacterCreationTracker::ms_instance = NULL; // ====================================================================== void CharacterCreationTracker::install() { DEBUG_FATAL(ms_instance != NULL,("Called install() twice.\n")); ms_instance = new CharacterCreationTracker; ExitChain::add(CharacterCreationTracker::remove,"CharacterCreationTracker::remove"); } // ---------------------------------------------------------------------- void CharacterCreationTracker::remove() { DEBUG_FATAL(!ms_instance,("Not installed.\n")); delete ms_instance; ms_instance = 0; } // ---------------------------------------------------------------------- CharacterCreationTracker::CharacterCreationTracker() : m_creations() { } // ---------------------------------------------------------------------- CharacterCreationTracker::~CharacterCreationTracker() { for (CreationsType::iterator i = m_creations.begin(); i != m_creations.end(); ++i) delete i->second; } // ---------------------------------------------------------------------- void CharacterCreationTracker::handleCreateNewCharacter(const ConnectionCreateCharacter &msg) { // - Check whether a creation request is already active for this account CreationsType::iterator creationRecord = m_creations.find(msg.getStationId()); if (creationRecord != m_creations.end()) { if (ServerClock::getInstance().getGameTimeSeconds() > (creationRecord->second->m_creationTime + ConfigCentralServer::getCharacterCreationTimeout())) { LOG("TraceCharacterCreation", ("%d allowing character creation because previous one timed out", msg.getStationId())); DEBUG_REPORT_LOG(true,("Allowing character creation for account %li because previous one timed out.\n",msg.getStationId())); unlockAccount(msg.getStationId()); creationRecord = m_creations.end(); } else { LOG("TraceCharacterCreation", ("%d refusing character creation because one is already in progress", msg.getStationId())); DEBUG_REPORT_LOG(true,("Refusing character creation for account %li because one was already in progress.\n",msg.getStationId())); ConnectionCreateCharacterFailed f(msg.getStationId(), msg.getCharacterName(), NameErrors::nameDeclinedRetry, FormattedString<2048>().sprintf("%lu refusing character creation because one is already in progress", msg.getStationId())); //lint !e40 // undeclared identifier nameDeclinedEntry CentralServer::getInstance().sendToConnectionServerForAccount(msg.getStationId(), f, true); return; } } // - Check whether they are creating characters too rapidly // Note that this locks only when creation succeeds. Failing to create a lot of characters in a row does not lock the account. FastCreationLockType::iterator fcl = m_fastCreationLock.find(msg.getStationId()); if (fcl != m_fastCreationLock.end()) { if (!msg.getNoRateLimit() && ((Clock::timeSeconds() - fcl->second) < (msg.getIsForCharacterTransfer() ? static_cast(ConfigCentralServer::getCharacterCtsCreationRateLimitSeconds()) : static_cast(ConfigCentralServer::getCharacterCreationRateLimitSeconds())))) { LOG("TraceCharacterCreation", ("%d refusing character creation because not enough time has passed since the previous one", msg.getStationId())); DEBUG_REPORT_LOG(true,("Refusing character creation for account %li because not enough time has passed since the previous one\n",msg.getStationId())); ConnectionCreateCharacterFailed f(msg.getStationId(), msg.getCharacterName(), NameErrors::nameDeclinedTooFast, FormattedString<2048>().sprintf("%lu refusing character creation because not enough time has passed since the previous one", msg.getStationId())); CentralServer::getInstance().sendToConnectionServerForAccount(msg.getStationId(), f, true); return; } } if (creationRecord == m_creations.end()) creationRecord = m_creations.insert(std::pair(msg.getStationId(),new CreationRecord)).first; // - determine starting location static std::string tutorialPlanetName("tutorial"); std::string planetName; Vector coordinates(0.0f,0.0f,0.0f); NetworkId cellId(NetworkId::cms_invalid); bool useNewbieTutorial = (ConfigCentralServer::getNewbieTutorialEnabled() && msg.getUseNewbieTutorial() && ! msg.getIsForCharacterTransfer()); if (useNewbieTutorial) planetName = tutorialPlanetName; else { if (!getStartLocation(msg.getStartingLocation(), planetName, coordinates, cellId)) { // bad starting location LOG("TraceCharacterCreation", ("%d bad starting location (%s)", msg.getStationId(), msg.getStartingLocation().c_str())); ConnectionCreateCharacterFailed cccf(msg.getStationId(), msg.getCharacterName(), SharedStringIds::character_create_failed_bad_location, FormattedString<2048>().sprintf("%lu bad starting location (%s)", msg.getStationId(), msg.getStartingLocation().c_str())); CentralServer::getInstance().sendToConnectionServerForAccount(msg.getStationId(), cccf, true); unlockAccount(msg.getStationId()); return; } } // - send request to game server creationRecord->second->m_gameCreationRequest = new CentralCreateCharacter( msg.getStationId(), msg.getCharacterName(), msg.getTemplateName(), msg.getScaleFactor(), planetName, coordinates, cellId, msg.getAppearanceData(), msg.getHairTemplateName(), msg.getHairAppearanceData(), msg.getProfession(), msg.getBiography(), useNewbieTutorial, msg.getSkillTemplate(), msg.getWorkingSkill(), msg.getJedi(), msg.getGameFeatures()); uint32 gameServerId = CentralServer::getInstance().sendToRandomGameServer(*(creationRecord->second->m_gameCreationRequest)); if (gameServerId == 0) { DEBUG_REPORT_LOG(true, ("Could not find a game server for character creation, starting a tutorial server\n")); LOG("TraceCharacterCreation", ("%d waiting for game server", msg.getStationId())); CentralServer::getInstance().startPlanetServer(CentralServer::getInstance().getHostForScene(tutorialPlanetName), tutorialPlanetName, 0); creationRecord->second->m_stage = CreationRecord::S_queuedForGameServer; return; } LOG("TraceCharacterCreation", ("%d sending CentralCreateCharacter(%s) to game server %lu", msg.getStationId(), Unicode::wideToNarrow(msg.getCharacterName()).c_str(), gameServerId)); creationRecord->second->m_stage = CreationRecord::S_sentToGameServer; creationRecord->second->m_gameServerId = gameServerId; } // ---------------------------------------------------------------------- void CharacterCreationTracker::unlockAccount(StationId account) { delete m_creations[account]; IGNORE_RETURN(m_creations.erase(account)); LOG("TraceCharacterCreation", ("%d removing character creation lock", account)); } // ---------------------------------------------------------------------- void CharacterCreationTracker::retryGameServerCreates() { for (CreationsType::iterator i=m_creations.begin(); i!=m_creations.end(); ++i) { if (i->second->m_stage == CreationRecord::S_queuedForGameServer) { uint32 gameServerId = CentralServer::getInstance().sendToRandomGameServer(*i->second->m_gameCreationRequest); if (gameServerId != 0) { i->second->m_stage = CreationRecord::S_sentToGameServer; i->second->m_gameServerId = gameServerId; LOG("TraceCharacterCreation", ("%d sending CentralCreateCharacter to game server %lu", i->first,gameServerId)); } else { i->second->m_gameServerId = 0; } } } } // ---------------------------------------------------------------------- void CharacterCreationTracker::retryLoginServerCreates() { for (CreationsType::iterator i=m_creations.begin(); i!=m_creations.end(); ++i) { if (i->second->m_stage == CreationRecord::S_queuedForLoginServer) { uint32 loginServerId = CentralServer::getInstance().sendToArbitraryLoginServer(*i->second->m_loginCreationRequest, false); if (loginServerId != 0 ) { i->second->m_loginServerId = loginServerId; i->second->m_stage = CreationRecord::S_sentToLoginServer; LOG("TraceCharacterCreation", ("%d sending LoginCreateCharacterMessage", i->first)); } else { i->second->m_loginServerId = 0; } } } } // ---------------------------------------------------------------------- const bool CharacterCreationTracker::getStartLocation(const std::string & name, std::string & planetName, Vector & coordinates, NetworkId & cellId) const { const StartingLocationData * const sld = StartingLocationManager::findLocationByName (name); if (sld) { planetName = sld->planet; coordinates.x = sld->x; coordinates.z = sld->z; cellId = NetworkId (sld->cellId); return true; } REPORT_LOG(true, ("The start location \"%s\" could not be found in StartingLocationManager", name.c_str())); return false; } // ---------------------------------------------------------------------- void CharacterCreationTracker::onGameServerDisconnect(uint32 serverId) { for (CreationsType::iterator i=m_creations.begin(); i!=m_creations.end(); ++i) { if (i->second->m_stage == CreationRecord::S_sentToGameServer && i->second->m_gameServerId==serverId) { i->second->m_stage = CreationRecord::S_queuedForGameServer; i->second->m_gameServerId = 0; LOG("TraceCharacterCreation", ("%d requeueing because game server disconnected", i->first)); } } retryGameServerCreates(); } // ---------------------------------------------------------------------- void CharacterCreationTracker::onLoginServerDisconnect(uint32 loginServerId) { for (CreationsType::iterator i=m_creations.begin(); i!=m_creations.end(); ++i) { if (i->second->m_stage == CreationRecord::S_sentToLoginServer && i->second->m_loginServerId==loginServerId) { i->second->m_stage = CreationRecord::S_queuedForLoginServer; i->second->m_loginServerId = 0; LOG("TraceCharacterCreation", ("%d requeueing because login server disconnected", i->first)); } } retryLoginServerCreates(); } // ---------------------------------------------------------------------- void CharacterCreationTracker::handleDatabaseCreateCharacterSuccess(StationId account, const Unicode::String &characterName, const NetworkId &characterObjectId, int templateId, bool jedi) { // - Check we are at the right stage LOG("TraceCharacterCreation", ("%d DatabaseCreateCharacterSuccess(%s)", account, characterObjectId.getValueString().c_str())); CreationsType::iterator creationRecord=m_creations.find(account); if (creationRecord==m_creations.end() || creationRecord->second->m_stage != CreationRecord::S_sentToGameServer) { LOG("TraceCharacterCreation", ("%d DatabaseCreateCharacterSuccess was unexpected - exiting", account)); DEBUG_WARNING(true,("Programmer bug: got GameCreateCharacter message for accout %d, which we weren't expecting.\n",account)); return; } // - Tell the login server to add the character LoginCreateCharacterMessage *msg = new LoginCreateCharacterMessage(account,characterName,characterObjectId,templateId,jedi); creationRecord->second->m_loginCreationRequest = msg; creationRecord->second->m_characterId = characterObjectId; uint32 loginServerId = CentralServer::getInstance().sendToArbitraryLoginServer(*msg, false); if (loginServerId != 0) { creationRecord->second->m_loginServerId = loginServerId; creationRecord->second->m_stage = CreationRecord::S_sentToLoginServer; LOG("TraceCharacterCreation", ("%d sending LoginCreateCharacterMessage", account)); } else { creationRecord->second->m_loginServerId = 0; creationRecord->second->m_stage = CreationRecord::S_queuedForLoginServer; LOG("TraceCharacterCreation", ("%d waiting for login server", account)); } } // ---------------------------------------------------------------------- void CharacterCreationTracker::handleLoginCreateCharacterAck(StationId account) { // - Check we are at the right stage LOG("TraceCharacterCreation", ("%d LoginCreateCharacterAckMessage", account)); CreationsType::iterator creationRecord=m_creations.find(account); if (creationRecord==m_creations.end() || creationRecord->second->m_stage != CreationRecord::S_sentToLoginServer) { LOG("TraceCharacterCreation", ("%d LoginCreateCharacterAckMessage was unexpected - exiting", account)); DEBUG_WARNING(true,("Programmer bug: got LoginCreateCharacterAckMessage message for account %d, which we weren't expecting.\n",account)); return; } // - Tell the client the character has been created LOG("TraceCharacterCreation", ("%d acknowledgeCharacterCreate(%s)", account, creationRecord->second->m_characterId.getValueString().c_str())); LOG("CustomerService", ("Player:created character %s for stationId %u", creationRecord->second->m_characterId.getValueString().c_str(), account)); const ConnectionCreateCharacterSuccess c(account, creationRecord->second->m_characterId); CentralServer::getInstance().sendToConnectionServerForAccount(account, c, true); unlockAccount(account); // let all connection servers know that a new character has been created for the station account GenericValueTypeMessage const ncc("NewCharacterCreated", account); CentralServer::getInstance().sendToAllConnectionServers(ncc, true); setFastCreationLock(account); } // ---------------------------------------------------------------------- void CharacterCreationTracker::handleGameCreateCharacterFailed(StationId account, Unicode::String const &characterName, StringId const &errorMessage, std::string const &optionalDetailedErrorMessage) { CreationsType::iterator creationRecord=m_creations.find(account); if (creationRecord==m_creations.end() || creationRecord->second->m_stage != CreationRecord::S_sentToGameServer) { LOG("TraceCharacterCreation", ("%d got handleGameCreateCharacterFailed, but we weren't in the sentToGameServer stage - ignoring", account)); DEBUG_WARNING(true,("Programmer bug: got handleGameCreateCharacterFailed message for account %d, which we weren't expecting.\n",account)); return; } LOG("TraceCharacterCreation", ("%d received GameCreateCharacterFailed(%s)", account, Unicode::wideToNarrow(characterName).c_str())); ConnectionCreateCharacterFailed cccf(account, characterName, errorMessage, optionalDetailedErrorMessage); CentralServer::getInstance().sendToConnectionServerForAccount(account, cccf, true); unlockAccount(account); } // ---------------------------------------------------------------------- void CharacterCreationTracker::setFastCreationLock(StationId account) { m_fastCreationLock[account]=Clock::timeSeconds(); } // ====================================================================== CharacterCreationTracker::CreationRecord::CreationRecord() : m_stage(S_queuedForGameServer), m_gameCreationRequest(NULL), m_loginCreationRequest(NULL), m_creationTime(ServerClock::getInstance().getGameTimeSeconds()), m_gameServerId(0), m_loginServerId(0), m_characterId(NetworkId::cms_invalid) { } // ---------------------------------------------------------------------- CharacterCreationTracker::CreationRecord::~CreationRecord() { delete m_gameCreationRequest; delete m_loginCreationRequest; m_gameCreationRequest = 0; m_loginCreationRequest = 0; }