#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include using Atlas::Objects::Root; using namespace Atlas::Objects::Operation; using Atlas::Objects::Entity::RootEntity; using Atlas::Objects::Entity::Anonymous; typedef Atlas::Objects::Entity::Account AtlasAccount; using Atlas::Objects::smart_dynamic_cast; namespace Eris { class AccountRouter : public Router { public: AccountRouter(Account* pl) : m_account(pl) { m_account->getConnection()->setDefaultRouter(this); } virtual ~AccountRouter() { m_account->getConnection()->clearDefaultRouter(); } virtual RouterResult handleOperation(const RootOperation& op) { // logout if (op->getClassNo() == LOGOUT_NO) { debug() << "Account reciev forced logout from server"; m_account->internalLogout(false); return HANDLED; } if ((op->getClassNo() == SIGHT_NO) && (op->getTo() == m_account->getId())) { const std::vector& args = op->getArgs(); AtlasAccount acc = smart_dynamic_cast(args.front()); m_account->updateFromObject(acc); // refresh character data if it changed if (!acc->isDefaultCharacters()) m_account->refreshCharacterInfo(); return HANDLED; } return IGNORED; } private: Account* m_account; }; #pragma mark - Account::Account(Connection *con) : m_con(con), m_status(DISCONNECTED), m_doingCharacterRefresh(false) { if (!m_con) throw InvalidOperation("invalid Connection passed to Account"); m_router = new AccountRouter(this); m_con->Connected.connect(sigc::mem_fun(this, &Account::netConnected)); m_con->Failure.connect(sigc::mem_fun(this, &Account::netFailure)); } Account::~Account() { ActiveCharacterMap::iterator it; for (it = m_activeCharacters.begin(); it != m_activeCharacters.end(); ) { ActiveCharacterMap::iterator cur = it++; deactivateCharacter(cur->second); // send logout op // cur gets invalidated by innerDeactivateCharacter delete cur->second; } if (isLoggedIn()) logout(); delete m_router; } Result Account::login(const std::string &uname, const std::string &password) { if (!m_con->isConnected()) { error() << "called login on unconnected Connection"; return NOT_CONNECTED; } if (m_status != DISCONNECTED) { error() << "called login, but state is not currently disconnected"; return ALREADY_LOGGED_IN; } return internalLogin(uname, password); } Result Account::createAccount(const std::string &uname, const std::string &fullName, const std::string &pwd) { if (!m_con->isConnected()) return NOT_CONNECTED; if (m_status != DISCONNECTED) return ALREADY_LOGGED_IN; m_status = LOGGING_IN; // okay, build and send the create(account) op AtlasAccount account; account->setPassword(pwd); account->setName(fullName); account->setUsername(uname); Create c; c->setSerialno(getNewSerialno()); c->setArgs1(account); m_con->getResponder()->await(c->getSerialno(), this, &Account::loginResponse); m_con->send(c); // store for re-logins m_username = uname; m_pass = pwd; m_timeout.reset(new Timeout(5000)); m_timeout->Expired.connect(sigc::mem_fun(this, &Account::handleLoginTimeout)); return NO_ERR; } Result Account::logout() { if (!m_con->isConnected()) { error() << "called logout on bad connection ignoring"; return NOT_CONNECTED; } if (m_status == LOGGING_OUT) return NO_ERR; if (m_status != LOGGED_IN) { error() << "called logout on non-logged-in Account"; return NOT_LOGGED_IN; } m_status = LOGGING_OUT; Logout l; Anonymous arg; arg->setId(m_accountId); l->setArgs1(arg); l->setSerialno(getNewSerialno()); m_con->getResponder()->await(l->getSerialno(), this, &Account::logoutResponse); m_con->send(l); m_timeout.reset(new Timeout(5000)); m_timeout->Expired.connect(sigc::mem_fun(this, &Account::handleLogoutTimeout)); return NO_ERR; } const std::vector< std::string > & Account::getCharacterTypes(void) const { return m_characterTypes; } const CharacterMap& Account::getCharacters() { if (m_status != LOGGED_IN) error() << "Not logged into an account : getCharacter returning empty dictionary"; return _characters; } Result Account::refreshCharacterInfo() { if (!m_con->isConnected()) return NOT_CONNECTED; if (m_status != LOGGED_IN) return NOT_LOGGED_IN; // silently ignore overlapping refreshes if (m_doingCharacterRefresh) return NO_ERR; _characters.clear(); if (m_characterIds.empty()) { GotAllCharacters.emit(); // we must emit the done signal return NO_ERR; } // okay, now we know we have at least one character to lookup, set the flag m_doingCharacterRefresh = true; Look lk; Anonymous obj; lk->setFrom(m_accountId); for (StringSet::iterator I=m_characterIds.begin(); I!=m_characterIds.end(); ++I) { obj->setId(*I); lk->setArgs1(obj); lk->setSerialno(getNewSerialno()); m_con->getResponder()->await(lk->getSerialno(), this, &Account::sightCharacter); m_con->send(lk); } return NO_ERR; } Result Account::createCharacter(const Atlas::Objects::Entity::RootEntity &ent) { if (!m_con->isConnected()) return NOT_CONNECTED; if (m_status != LOGGED_IN) { if ((m_status == CREATING_CHAR) || (m_status == TAKING_CHAR)) { error() << "duplicate char creation / take"; return DUPLICATE_CHAR_ACTIVE; } else { error() << "called createCharacter on unconnected Account, ignoring"; return NOT_LOGGED_IN; } } Create c; c->setArgs1(ent); c->setFrom(m_accountId); c->setSerialno(getNewSerialno()); m_con->send(c); m_con->getResponder()->await(c->getSerialno(), this, &Account::avatarResponse); m_status = CREATING_CHAR; return NO_ERR; } /* void Account::createCharacter() { if (!_lobby || _lobby->getAccountID().empty()) throw InvalidOperation("no account exists!"); if (!_con->isConnected()) throw InvalidOperation("Not connected to server"); throw InvalidOperation("No UserInterface handler defined"); // FIXME look up the dialog, create the instance, // hook in a slot to feed the serialno of any Create op // the dialog passes back to createCharacterHandler() } void Account::createCharacterHandler(long serialno) { if (serialno) NewCharacter((new World(this, _con))->createAvatar(serialno)); } */ Result Account::takeCharacter(const std::string &id) { if (m_characterIds.count(id) == 0) { error() << "Character '" << id << "' not owned by Account " << m_username; return BAD_CHARACTER_ID; } if (!m_con->isConnected()) return NOT_CONNECTED; if (m_status != LOGGED_IN) { if ((m_status == CREATING_CHAR) || (m_status == TAKING_CHAR)) { error() << "duplicate char creation / take"; return DUPLICATE_CHAR_ACTIVE; } else { error() << "called createCharacter on unconnected Account, ignoring"; return NOT_LOGGED_IN; } } Anonymous what; what->setId(id); Look l; l->setFrom(id); // should this be m_accountId? l->setArgs1(what); l->setSerialno(getNewSerialno()); m_con->send(l); m_con->getResponder()->await(l->getSerialno(), this, &Account::avatarResponse); m_status = TAKING_CHAR; return NO_ERR; } Result Account::deactivateCharacter(Avatar* av) { av->deactivate(); return NO_ERR; } bool Account::isLoggedIn() const { return ((m_status == LOGGED_IN) || (m_status == TAKING_CHAR) || (m_status == CREATING_CHAR)); } #pragma mark - Result Account::internalLogin(const std::string &uname, const std::string &pwd) { assert(m_status == DISCONNECTED); m_status = LOGGING_IN; m_username = uname; // store for posterity AtlasAccount account; account->setPassword(pwd); account->setUsername(uname); Login l; l->setArgs1(account); l->setSerialno(getNewSerialno()); m_con->getResponder()->await(l->getSerialno(), this, &Account::loginResponse); m_con->send(l); m_timeout.reset(new Timeout(5000)); m_timeout->Expired.connect(sigc::mem_fun(this, &Account::handleLoginTimeout)); return NO_ERR; } void Account::logoutResponse(const RootOperation& op) { if (!op->instanceOf(INFO_NO)) warning() << "received a logout response that is not an INFO"; internalLogout(true); } void Account::internalLogout(bool clean) { if (clean) { if (m_status != LOGGING_OUT) error() << "got clean logout, but not logging out already"; } else { if ((m_status != LOGGED_IN) && (m_status != TAKING_CHAR) && (m_status != CREATING_CHAR)) error() << "got forced logout, but not currently logged in"; } m_con->unregisterRouterForTo(m_router, m_accountId); m_status = DISCONNECTED; m_timeout.reset(); if (m_con->getStatus() == BaseConnection::DISCONNECTING) { m_con->unlock(); } else { LogoutComplete.emit(clean); } } void Account::loginResponse(const RootOperation& op) { if (op->instanceOf(ERROR_NO)) { loginError(smart_dynamic_cast(op)); } else if (op->instanceOf(INFO_NO)) { const std::vector& args = op->getArgs(); loginComplete(smart_dynamic_cast(args.front())); } else warning() << "received malformed login response: " << op->getClassNo(); } void Account::loginComplete(const AtlasAccount &p) { if (m_status != LOGGING_IN) error() << "got loginComplete, but not currently logging in!"; if (p->getUsername() != m_username) error() << "missing or incorrect username on login INFO"; m_status = LOGGED_IN; m_accountId = p->getId(); m_con->registerRouterForTo(m_router, m_accountId); updateFromObject(p); // notify an people watching us LoginSuccess.emit(); m_con->Disconnecting.connect(sigc::mem_fun(this, &Account::netDisconnecting)); m_timeout.reset(); } void Account::updateFromObject(const AtlasAccount &p) { m_characterIds = StringSet(p->getCharacters().begin(), p->getCharacters().end()); if(p->hasAttr("character_types") == true) { Atlas::Message::Element CharacterTypes(p->getAttr("character_types")); if(CharacterTypes.isList() == true) { const Atlas::Message::ListType & CharacterTypesList(CharacterTypes.asList()); Atlas::Message::ListType::const_iterator iCharacterType(CharacterTypesList.begin()); Atlas::Message::ListType::const_iterator iEnd(CharacterTypesList.end()); m_characterTypes.reserve(CharacterTypesList.size()); while(iCharacterType != iEnd) { if(iCharacterType->isString() == true) { m_characterTypes.push_back(iCharacterType->asString()); } else { error() << "An element of the \"character_types\" list is not a String."; } ++iCharacterType; } } else { error() << "Account has attribute \"character_types\" which is not of type List."; } } } void Account::loginError(const Error& err) { assert(err.isValid()); if (m_status != LOGGING_IN) { error() << "got loginError while not logging in"; } const std::vector& args = err->getArgs(); std::string msg = args[0]->getAttr("message").asString(); // update state before emitting signal m_status = DISCONNECTED; m_timeout.reset(); LoginFailure.emit(msg); } void Account::handleLoginTimeout() { m_status = DISCONNECTED; deleteLater(m_timeout.release()); LoginFailure.emit("timed out waiting for server response"); } void Account::avatarResponse(const RootOperation& op) { if (op->instanceOf(ERROR_NO)) { const std::vector& args = op->getArgs(); std::string msg = args[0]->getAttr("message").asString(); // creating or taking a character failed for some reason AvatarFailure(msg); m_status = Account::LOGGED_IN; } else if (op->instanceOf(INFO_NO)) { const std::vector& args = op->getArgs(); RootEntity ent = smart_dynamic_cast(args.front()); if (!ent.isValid()) { warning() << "malformed character create/take response"; return; } Avatar* av = new Avatar(this, ent->getId()); AvatarSuccess.emit(av); m_status = Account::LOGGED_IN; assert(m_activeCharacters.count(av->getId()) == 0); m_activeCharacters[av->getId()] = av; // expect another op with the same refno m_con->getResponder()->ignore(op->getRefno()); } else warning() << "received malformed avatar take response"; } void Account::internalDeactivateCharacter(Avatar* av) { assert(m_activeCharacters.count(av->getId()) == 1); m_activeCharacters.erase(av->getId()); } void Account::sightCharacter(const RootOperation& op) { if (!m_doingCharacterRefresh) { error() << "got sight of character outside a refresh, ignoring"; return; } const std::vector& args = op->getArgs(); assert(!args.empty()); RootEntity ge = smart_dynamic_cast(args.front()); assert(ge.isValid()); CharacterMap::iterator C = _characters.find(ge->getId()); if (C != _characters.end()) { error() << "duplicate sight of character " << ge->getId(); return; } // okay, we can now add it to our map _characters.insert(C, CharacterMap::value_type(ge->getId(), ge)); GotCharacterInfo.emit(ge); // check if we're done if (_characters.size() == m_characterIds.size()) { m_doingCharacterRefresh = false; GotAllCharacters.emit(); } } /* this will only ever get encountered after the connection is initally up; thus we use it to trigger a reconnection. Note that some actions by Lobby and World are also required to get everything back into the correct state */ void Account::netConnected() { // re-connection if (!m_username.empty() && !m_pass.empty() && (m_status == DISCONNECTED)) { debug() << "Account " << m_username << " got netConnected, doing reconnect"; internalLogin(m_username, m_pass); } } bool Account::netDisconnecting() { if (m_status == LOGGED_IN) { m_con->lock(); logout(); return false; } else return true; } void Account::netFailure(const std::string& /*msg*/) { } void Account::handleLogoutTimeout() { error() << "LOGOUT timed out waiting for response"; m_status = DISCONNECTED; deleteLater(m_timeout.release()); LogoutComplete.emit(false); } void Account::avatarLogoutResponse(const RootOperation& op) { if (!op->instanceOf(INFO_NO)) warning() << "received an avatar logout response that is not an INFO"; const std::vector& args(op->getArgs()); if (args.empty() || (args.front()->getClassNo() != LOGOUT_NO)) { warning() << "argument of avatar logout INFO is not a logout op"; return; } RootOperation logout = smart_dynamic_cast(args.front()); const std::vector& args2(logout->getArgs()); assert(!args2.empty()); std::string charId = args2.front()->getId(); debug() << "got logout for character " << charId; if (!m_characterIds.count(charId)) { warning() << "character ID " << charId << " is unknown on account " << m_accountId; } ActiveCharacterMap::iterator it = m_activeCharacters.find(charId); if (it == m_activeCharacters.end()) { warning() << "character ID " << charId << " does not crrespond to an active avatar."; return; } AvatarDeactivated.emit(it->second); delete it->second; // will call back into internalDeactivateCharacter } } // of namespace Eris