12#include "EventService.h"
13#include "SpawnPoint.h"
14#include "TypeService.h"
16#include <Atlas/Objects/Entity.h>
17#include <Atlas/Objects/Operation.h>
18#include <Atlas/Objects/Anonymous.h>
25using Atlas::Objects::Root;
26using Atlas::Message::Element;
27using namespace Atlas::Objects::Operation;
28using Atlas::Objects::Entity::RootEntity;
29using Atlas::Objects::Entity::Anonymous;
30typedef Atlas::Objects::Entity::Account AtlasAccount;
31using Atlas::Objects::smart_dynamic_cast;
46 RouterResult handleOperation(
const RootOperation& op)
override {
48 if (op->getClassNo() == LOGOUT_NO) {
49 debug() <<
"Account received forced logout from server";
50 m_account->internalLogout(
false);
54 if ((op->getClassNo() == SIGHT_NO) && (op->getTo() == m_account->
getId())) {
55 const std::vector<Root>& args = op->getArgs();
56 AtlasAccount acc = smart_dynamic_cast<AtlasAccount>(args.front());
57 m_account->updateFromObject(acc);
60 if (!acc->isDefaultCharacters()) {
67 if (op->getClassNo() == CHANGE_NO) {
68 m_account->
getConnection().getTypeService().handleOperation(op);
72 if (op->getClassNo() == ERROR_NO) {
73 auto message = getErrorMessage(op);
74 if (!message.empty()) {
75 notice() <<
"Got error message from server: " << message;
91 m_status(
Status::DISCONNECTED),
93 m_doingCharacterRefresh(false) {
95 m_con.
Failure.connect(sigc::mem_fun(
this, &Account::netFailure));
99 ActiveCharacterMap::iterator it;
100 for (it = m_activeAvatars.begin(); it != m_activeAvatars.end();) {
102 cur->second->deactivate();
105 while (!m_activeAvatars.empty()) {
116 error() <<
"called login on unconnected Connection";
117 return NOT_CONNECTED;
121 error() <<
"called login, but state is not currently disconnected";
125 return internalLogin(uname, password);
129 const std::string& fullName,
130 const std::string& pwd) {
136 Atlas::Objects::Entity::Player account;
137 account->setPassword(pwd);
138 account->setName(fullName);
139 account->setUsername(uname);
152 c->setArgs1(accountOp);
154 m_con.getResponder().await(c->getSerialno(),
this, &Account::loginResponse);
157 m_timeout = std::make_unique<TimedEvent>(
m_con.getEventService(), std::chrono::seconds(5),
158 [&]() { this->handleLoginTimeout(); });
165 error() <<
"called logout on bad connection ignoring";
166 return NOT_CONNECTED;
172 error() <<
"called logout on non-logged-in Account";
185 m_con.getResponder().await(l->getSerialno(),
this, &Account::logoutResponse);
188 m_timeout = std::make_unique<TimedEvent>(
m_con.getEventService(), std::chrono::seconds(5),
189 [&]() { this->handleLogoutTimeout(); });
196 error() <<
"Not logged into an account : getCharacter returning empty dictionary";
212 if (m_characterIds.empty()) {
224 for (
const auto&
id : m_characterIds) {
228 m_con.getResponder().await(lk->getSerialno(),
this, &Account::sightCharacter);
243 return createCharacterThroughOperation(c);
246Result Account::createCharacterThroughOperation(
const Create& c) {
250 error() <<
"duplicate char creation / take";
251 return DUPLICATE_CHAR_ACTIVE;
253 error() <<
"called createCharacter on unconnected Account, ignoring";
262 m_con.getResponder().await(c->getSerialno(),
this, &Account::avatarCreateResponse);
271 error() <<
"duplicate char creation / take";
272 return DUPLICATE_CHAR_ACTIVE;
274 error() <<
"called takeCharacter on unconnected Account, ignoring";
282 what->setAttr(
"possess_key", key);
284 Atlas::Objects::Operation::Generic possessOp;
285 possessOp->setParent(
"possess");
287 possessOp->setArgs1(what);
291 m_con.getResponder().await(possessOp->getSerialno(),
this, &Account::possessResponse);
301 error() <<
"duplicate char creation / take";
302 return DUPLICATE_CHAR_ACTIVE;
304 error() <<
"called takeCharacter on unconnected Account, ignoring";
312 Atlas::Objects::Operation::Generic possessOp;
313 possessOp->setParent(
"possess");
315 possessOp->setArgs1(what);
319 m_con.getResponder().await(possessOp->getSerialno(),
this, &Account::possessResponse);
329Result Account::internalLogin(
const std::string& uname,
const std::string& pwd) {
335 AtlasAccount account;
336 account->setPassword(pwd);
337 account->setUsername(uname);
340 l->setArgs1(account);
342 m_con.getResponder().await(l->getSerialno(),
this, &Account::loginResponse);
345 m_timeout = std::make_unique<TimedEvent>(
m_con.getEventService(), std::chrono::seconds(5),
346 [&]() { this->handleLoginTimeout(); });
351void Account::logoutResponse(
const RootOperation& op) {
352 if (!op->instanceOf(INFO_NO))
353 warning() <<
"received a logout response that is not an INFO";
355 internalLogout(
true);
358void Account::internalLogout(
bool clean) {
361 error() <<
"got clean logout, but not logging out already";
365 error() <<
"got forced logout, but not currently logged in";
379void Account::loginResponse(
const RootOperation& op) {
380 if (op->instanceOf(ERROR_NO)) {
381 loginError(smart_dynamic_cast<Error>(op));
382 }
else if (op->instanceOf(INFO_NO)) {
383 const std::vector<Root>& args = op->getArgs();
384 loginComplete(smart_dynamic_cast<AtlasAccount>(args.front()));
386 warning() <<
"received malformed login response: " << op->getClassNo();
389void Account::loginComplete(
const AtlasAccount& p) {
391 error() <<
"got loginComplete, but not currently logging in!";
395 error() <<
"no account in response.";
401 warning() <<
"received username does not match existing";
423 auto I = m_activeAvatars.find(avatarId);
425 if (I != m_activeAvatars.end()) {
426 m_activeAvatars.erase(I);
431void Account::updateFromObject(
const AtlasAccount& p) {
432 auto characters = std::set<std::string>(p->getCharacters().begin(), p->getCharacters().end());
433 std::string createdCharacterId;
436 for (
auto& character : characters) {
437 if (m_characterIds.find(character) == m_characterIds.end()) {
438 createdCharacterId = character;
443 m_characterIds = std::move(characters);
444 m_parent = p->getParent();
446 if (p->hasAttr(
"spawns")) {
448 const auto& spawns = p->getAttr(
"spawns");
450 if (spawns.isList()) {
451 auto& spawnsList = spawns.List();
452 for (
const auto& spawnElement : spawnsList) {
453 if (spawnElement.isMap()) {
454 auto& spawnMap = spawnElement.Map();
456 std::string description;
458 std::vector<SpawnProperty> properties;
460 auto I = spawnMap.find(
"name");
461 if (I != spawnMap.end() && I->second.isString()) {
462 name = I->second.String();
466 auto I = spawnMap.find(
"description");
467 if (I != spawnMap.end() && I->second.isString()) {
468 description = I->second.String();
472 auto I = spawnMap.find(
"id");
473 if (I != spawnMap.end() && I->second.isString()) {
474 id = I->second.String();
478 auto I = spawnMap.find(
"properties");
479 if (I != spawnMap.end() && I->second.isList()) {
480 auto propList = I->second.List();
481 for (
auto& entry : propList) {
483 auto& entryMap = entry.Map();
484 std::string propName;
485 std::string propLabel;
486 std::string propDescription;
487 SpawnProperty::Type propType = SpawnProperty::Type::STRING;
488 std::vector<Atlas::Message::Element> propOptions;
491 auto J = entryMap.find(
"name");
492 if (J != entryMap.end() && J->second.isString()) {
493 propName = J->second.String();
497 auto J = entryMap.find(
"label");
498 if (J != entryMap.end() && J->second.isString()) {
499 propLabel = J->second.String();
503 auto J = entryMap.find(
"description");
504 if (J != entryMap.end() && J->second.isString()) {
505 propDescription = J->second.String();
509 auto J = entryMap.find(
"type");
510 if (J != entryMap.end() && J->second.isString()) {
511 auto& type = J->second.String();
512 if (type ==
"string") {
513 propType = SpawnProperty::Type::STRING;
518 auto J = entryMap.find(
"options");
519 if (J != entryMap.end() && J->second.isList()) {
520 propOptions = J->second.List();
523 properties.emplace_back(SpawnProperty{propName, propLabel, propDescription, propType, propOptions});
529 m_spawnPoints.emplace_back(SpawnPoint{id, name, description, properties});
533 error() <<
"Account has attribute \"spawns\" which is not of type List.";
542void Account::loginError(
const Error& err) {
544 warning() <<
"Got invalid error";
548 error() <<
"got loginError while not logging in";
551 std::string msg = getErrorMessage(err);
560void Account::handleLoginTimeout() {
564 LoginFailure.emit(
"timed out waiting for server response");
567void Account::possessResponse(
const RootOperation& op) {
568 if (op->instanceOf(ERROR_NO)) {
569 std::string msg = getErrorMessage(op);
574 }
else if (op->instanceOf(INFO_NO)) {
575 const std::vector<Root>& args = op->getArgs();
577 warning() <<
"no args character possess response";
581 auto ent = smart_dynamic_cast<RootEntity>(args.front());
582 if (!ent.isValid()) {
583 warning() <<
"malformed character possess response";
587 if (!ent->hasAttr(
"entity")) {
588 warning() <<
"malformed character possess response";
592 auto entityMessage = ent->getAttr(
"entity");
594 if (!entityMessage.isMap()) {
595 warning() <<
"malformed character possess response";
598 auto entityObj = smart_dynamic_cast<RootEntity>(
m_con.getFactories().createObject(entityMessage.Map()));
600 if (!entityObj || entityObj->isDefaultId()) {
601 warning() <<
"malformed character possess response";
605 if (m_activeAvatars.find(ent->getId()) != m_activeAvatars.end()) {
606 warning() <<
"got possession response for character already created";
610 auto av = std::make_unique<Avatar>(*
this, ent->getId(), entityObj->getId());
614 m_activeAvatars[av->getId()] = std::move(av);
617 warning() <<
"received incorrect avatar create/take response";
620void Account::avatarCreateResponse(
const RootOperation& op) {
621 if (op->instanceOf(ERROR_NO)) {
622 std::string msg = getErrorMessage(op);
630void Account::internalDeactivateCharacter(
const std::string& avatarId) {
631 auto I = m_activeAvatars.find(avatarId);
632 if (I == m_activeAvatars.end()) {
633 warning() <<
"trying to deactivate non active character";
635 m_activeAvatars.erase(I);
639void Account::sightCharacter(
const RootOperation& op) {
641 error() <<
"got sight of character outside a refresh, ignoring";
645 const std::vector<Root>& args = op->getArgs();
647 error() <<
"got sight of character with no args";
651 RootEntity ge = smart_dynamic_cast<RootEntity>(args.front());
653 error() <<
"got sight of character with malformed args";
659 error() <<
"duplicate sight of character " << ge->getId();
664 _characters.insert(C, CharacterMap::value_type(ge->getId(), ge));
682 debug() <<
"Account " <<
m_username <<
" got netConnected, doing reconnect";
696void Account::netFailure(
const std::string& ) {
700void Account::handleLogoutTimeout() {
701 error() <<
"LOGOUT timed out waiting for response";
709void Account::avatarLogoutResponse(
const RootOperation& op) {
710 if (!op->instanceOf(INFO_NO)) {
711 warning() <<
"received an avatar logout response that is not an INFO";
715 const std::vector<Root>& args(op->getArgs());
717 if (args.empty() || (args.front()->getClassNo() != LOGOUT_NO)) {
718 warning() <<
"argument of avatar logout INFO is not a logout op";
722 RootOperation
logout = smart_dynamic_cast<RootOperation>(args.front());
723 const std::vector<Root>& args2(
logout->getArgs());
725 warning() <<
"argument of avatar logout INFO is logout without args";
729 std::string charId = args2.front()->getId();
730 debug() <<
"got logout for character " << charId;
732 if (m_characterIds.find(charId) == m_characterIds.end()) {
733 warning() <<
"character ID " << charId <<
" is unknown on account " <<
m_accountId;
736 auto it = m_activeAvatars.find(charId);
737 if (it == m_activeAvatars.end()) {
738 warning() <<
"character ID " << charId <<
" does not correspond to an active avatar.";
Encapsulates all the state of an Atlas Account, and methods that operation on that state.
CharacterMap _characters
characters belonging to this player
Result refreshCharacterInfo()
@ LOGGED_IN
Fully logged into a server-side account.
@ CREATED_CHAR
a character was created, we now need to possess it
@ DISCONNECTED
Default state, no server account active.
@ LOGGING_IN
Login sent, waiting for initial INFO response.
@ LOGGING_OUT
Sent a logout op, waiting for the INFO response.
@ CREATING_CHAR
send a character CREATE op, awaiting INFO response
@ TAKING_CHAR
sent a LOOK op for a character, awaiting INFO response
sigc::signal< void, const std::string & > AvatarDeactivated
Result createCharacterThroughEntity(const Atlas::Objects::Entity::RootEntity &character)
enter the game using a new character
Status m_status
what the Player is currently doing
Result takeTransferredCharacter(const std::string &id, const std::string &key)
Transfer all characters to this account and then do all steps in takeCharacter()
std::vector< SpawnPoint > m_spawnPoints
A map of available spawn points. These are points from which a new avatar can be created.
Result logout()
Request logout from the server.
Connection & m_con
underlying connection instance
sigc::signal< void, const std::string & > LoginFailure
Emitted when a server-side error occurs during account creation / login.
sigc::signal< void > GotAllCharacters
emitted when the entire character list had been updated
void destroyAvatar(const std::string &avatarId)
Destroys the avatar with the specified id, if available.
const std::string & getId() const
returns the account ID if logged in
void avatarLogoutRequested(Avatar *avatar)
Called when a logout of the avatar has been requested by the server.
sigc::signal< void > LoginSuccess
Connection & getConnection() const
Access the underlying Connection for this account.
sigc::signal< void, const std::string & > ErrorMessage
sigc::signal< void, bool > LogoutComplete
Emitted when a logout completes.
Result createAccount(const std::string &uname, const std::string &fullName, const std::string &pwd)
Attempt to create a new account on the server and log into it.
Result takeCharacter(const std::string &id)
Enter the game using an existing character.
sigc::signal< void, const Atlas::Objects::Entity::RootEntity & > GotCharacterInfo
emitted when a character has been retrieved from the server
bool netDisconnecting()
help! the plug is being pulled!
void netConnected()
Callback for network re-establishment.
bool isLoggedIn() const
Check if the account is logged in.
Result login(const std::string &uname, const std::string &pwd)
Login to the server using user-supplied account information.
const CharacterMap & getCharacters()
Get the characters owned by this account.
std::string m_username
The player's username ( != account object's ID)
sigc::signal< void, const std::string & > AvatarFailure
std::string m_accountId
the account ID
bool m_doingCharacterRefresh
set if we're refreshing character data
Account(Connection &con)
Create a new Account associated with a Connection object.
sigc::signal< void, Avatar * > AvatarSuccess
const std::string & getId() const
Get the Mind id of this Avatar. All interaction with the entity goes through the Mind.
sigc::signal< void > Connected
sent on successful negotiation of a game server connection
bool isConnected() const
Ascertain whether or not the connection is usable for transport.
Status getStatus() const
get the current status of the connection
@ DISCONNECTING
clean disconnection in progress
sigc::signal< bool > Disconnecting
virtual void send(const Atlas::Objects::Root &obj)
Transmit an Atlas::Objects instance to the server.
sigc::signal< void, const std::string & > Failure
@ ALREADY_LOGGED_IN
Occurs when trying to log in to an Account which is already logged in.
@ NOT_LOGGED_IN
Occurs when performing an operation that requires a valid server login.
std::int64_t getNewSerialno()
operation serial number sequencing
std::map< std::string, Atlas::Objects::Entity::RootEntity > CharacterMap