1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4
5 #include <Eris/Account.h>
6 #include <Eris/Connection.h>
7 #include <Eris/LogStream.h>
8 #include <Eris/Exceptions.h>
9 #include <Eris/Avatar.h>
10 #include <Eris/Router.h>
11 #include <Eris/Response.h>
12 #include <Eris/DeleteLater.h>
13 #include <Eris/Timeout.h>
14 #include "SpawnPoint.h"
15 #include "CharacterType.h"
16
17 #include <Atlas/Objects/Entity.h>
18 #include <Atlas/Objects/Operation.h>
19 #include <Atlas/Objects/Anonymous.h>
20
21 #include <algorithm>
22 #include <cassert>
23 #include <iostream>
24
25 using Atlas::Objects::Root;
26 using Atlas::Message::Element;
27 using namespace Atlas::Objects::Operation;
28 using Atlas::Objects::Entity::RootEntity;
29 using Atlas::Objects::Entity::Anonymous;
30 typedef Atlas::Objects::Entity::Account AtlasAccount;
31 using Atlas::Objects::smart_dynamic_cast;
32
33 namespace Eris {
34
35 class AccountRouter : public Router
36 {
37 public:
AccountRouter(Account * pl)38 AccountRouter(Account* pl) :
39 m_account(pl)
40 {
41 m_account->getConnection()->setDefaultRouter(this);
42 }
43
~AccountRouter()44 virtual ~AccountRouter()
45 {
46 m_account->getConnection()->clearDefaultRouter();
47 }
48
handleOperation(const RootOperation & op)49 virtual RouterResult handleOperation(const RootOperation& op)
50 {
51 // logout
52 if (op->getClassNo() == LOGOUT_NO) {
53 debug() << "Account received forced logout from server";
54 m_account->internalLogout(false);
55 return HANDLED;
56 }
57
58 if ((op->getClassNo() == SIGHT_NO) && (op->getTo() == m_account->getId()))
59 {
60 const std::vector<Root>& args = op->getArgs();
61 AtlasAccount acc = smart_dynamic_cast<AtlasAccount>(args.front());
62 m_account->updateFromObject(acc);
63
64 // refresh character data if it changed
65 if (!acc->isDefaultCharacters()) m_account->refreshCharacterInfo();
66
67 return HANDLED;
68 }
69
70 return IGNORED;
71 }
72
73 private:
74 Account* m_account;
75 };
76
77 #pragma mark -
78
Account(Connection * con)79 Account::Account(Connection *con) :
80 m_con(con),
81 m_status(DISCONNECTED),
82 m_doingCharacterRefresh(false)
83 {
84 if (!m_con) throw InvalidOperation("invalid Connection passed to Account");
85
86 m_router = new AccountRouter(this);
87
88 m_con->Connected.connect(sigc::mem_fun(this, &Account::netConnected));
89 m_con->Failure.connect(sigc::mem_fun(this, &Account::netFailure));
90 }
91
~Account()92 Account::~Account()
93 {
94 ActiveCharacterMap::iterator it;
95 for (it = m_activeCharacters.begin(); it != m_activeCharacters.end(); )
96 {
97 ActiveCharacterMap::iterator cur = it++;
98 deactivateCharacter(cur->second); // send logout op
99 // cur gets invalidated by innerDeactivateCharacter
100 delete cur->second;
101 }
102
103 if (isLoggedIn()) logout();
104 delete m_router;
105 }
106
login(const std::string & uname,const std::string & password)107 Result Account::login(const std::string &uname, const std::string &password)
108 {
109 if (!m_con->isConnected()) {
110 error() << "called login on unconnected Connection";
111 return NOT_CONNECTED;
112 }
113
114 if (m_status != DISCONNECTED) {
115 error() << "called login, but state is not currently disconnected";
116 return ALREADY_LOGGED_IN;
117 }
118
119 return internalLogin(uname, password);
120 }
121
createAccount(const std::string & uname,const std::string & fullName,const std::string & pwd)122 Result Account::createAccount(const std::string &uname,
123 const std::string &fullName,
124 const std::string &pwd)
125 {
126 // store for re-logins
127 m_username = uname;
128 m_pass = pwd;
129
130
131 AtlasAccount account;
132 account->setPassword(pwd);
133 account->setName(fullName);
134 account->setUsername(uname);
135
136 return createAccount(account);
137 }
138
createAccount(Atlas::Objects::Entity::Account accountOp)139 Result Account::createAccount(Atlas::Objects::Entity::Account accountOp)
140 {
141 if (!m_con->isConnected()) return NOT_CONNECTED;
142 if (m_status != DISCONNECTED) return ALREADY_LOGGED_IN;
143
144 m_status = LOGGING_IN;
145
146 Create c;
147 c->setSerialno(getNewSerialno());
148 c->setArgs1(accountOp);
149
150 m_con->getResponder()->await(c->getSerialno(), this, &Account::loginResponse);
151 m_con->send(c);
152
153 m_timeout.reset(new Timeout(5000));
154 m_timeout->Expired.connect(sigc::mem_fun(this, &Account::handleLoginTimeout));
155
156 return NO_ERR;
157 }
158
logout()159 Result Account::logout()
160 {
161 if (!m_con->isConnected()) {
162 error() << "called logout on bad connection ignoring";
163 return NOT_CONNECTED;
164 }
165
166 if (m_status == LOGGING_OUT) return NO_ERR;
167
168 if (m_status != LOGGED_IN) {
169 error() << "called logout on non-logged-in Account";
170 return NOT_LOGGED_IN;
171 }
172
173 m_status = LOGGING_OUT;
174
175 Logout l;
176 Anonymous arg;
177 arg->setId(m_accountId);
178 l->setArgs1(arg);
179 l->setSerialno(getNewSerialno());
180
181 m_con->getResponder()->await(l->getSerialno(), this, &Account::logoutResponse);
182 m_con->send(l);
183
184 m_timeout.reset(new Timeout(5000));
185 m_timeout->Expired.connect(sigc::mem_fun(this, &Account::handleLogoutTimeout));
186
187 return NO_ERR;
188 }
189
getCharacterTypes(void) const190 const std::vector< std::string > & Account::getCharacterTypes(void) const
191 {
192 return m_characterTypes;
193 }
194
getCharacters()195 const CharacterMap& Account::getCharacters()
196 {
197 if (m_status != LOGGED_IN)
198 error() << "Not logged into an account : getCharacter returning empty dictionary";
199
200 return _characters;
201 }
202
refreshCharacterInfo()203 Result Account::refreshCharacterInfo()
204 {
205 if (!m_con->isConnected()) return NOT_CONNECTED;
206 if (m_status != LOGGED_IN) return NOT_LOGGED_IN;
207
208 // silently ignore overlapping refreshes
209 if (m_doingCharacterRefresh) return NO_ERR;
210
211 _characters.clear();
212
213 if (m_characterIds.empty())
214 {
215 GotAllCharacters.emit(); // we must emit the done signal
216 return NO_ERR;
217 }
218
219 // okay, now we know we have at least one character to lookup, set the flag
220 m_doingCharacterRefresh = true;
221
222 Look lk;
223 Anonymous obj;
224 lk->setFrom(m_accountId);
225
226 for (StringSet::iterator I=m_characterIds.begin(); I!=m_characterIds.end(); ++I)
227 {
228 obj->setId(*I);
229 lk->setArgs1(obj);
230 lk->setSerialno(getNewSerialno());
231 m_con->getResponder()->await(lk->getSerialno(), this, &Account::sightCharacter);
232 m_con->send(lk);
233 }
234
235 return NO_ERR;
236 }
237
createCharacter(const Atlas::Objects::Entity::RootEntity & ent)238 Result Account::createCharacter(const Atlas::Objects::Entity::RootEntity &ent)
239 {
240 if (!m_con->isConnected()) return NOT_CONNECTED;
241 if (m_status != LOGGED_IN) {
242 if ((m_status == CREATING_CHAR) || (m_status == TAKING_CHAR)) {
243 error() << "duplicate char creation / take";
244 return DUPLICATE_CHAR_ACTIVE;
245 } else {
246 error() << "called createCharacter on unconnected Account, ignoring";
247 return NOT_LOGGED_IN;
248 }
249 }
250
251 Create c;
252 c->setArgs1(ent);
253 c->setFrom(m_accountId);
254 c->setSerialno(getNewSerialno());
255 m_con->send(c);
256
257 m_con->getResponder()->await(c->getSerialno(), this, &Account::avatarResponse);
258 m_status = CREATING_CHAR;
259 return NO_ERR;
260 }
261
262 /*
263 void Account::createCharacter()
264 {
265 if (!_lobby || _lobby->getAccountID().empty())
266 throw InvalidOperation("no account exists!");
267
268 if (!_con->isConnected())
269 throw InvalidOperation("Not connected to server");
270
271 throw InvalidOperation("No UserInterface handler defined");
272
273 // FIXME look up the dialog, create the instance,
274 // hook in a slot to feed the serialno of any Create op
275 // the dialog passes back to createCharacterHandler()
276 }
277
278 void Account::createCharacterHandler(long serialno)
279 {
280 if (serialno)
281 NewCharacter((new World(this, _con))->createAvatar(serialno));
282 }
283 */
284
takeTransferredCharacter(const std::string & id,const std::string & key)285 Result Account::takeTransferredCharacter(const std::string &id, const std::string &key)
286 {
287 if (!m_con->isConnected()) return NOT_CONNECTED;
288 if (m_status != LOGGED_IN) {
289 if ((m_status == CREATING_CHAR) || (m_status == TAKING_CHAR)) {
290 error() << "duplicate char creation / take";
291 return DUPLICATE_CHAR_ACTIVE;
292 } else {
293 error() << "called createCharacter on unconnected Account, ignoring";
294 return NOT_LOGGED_IN;
295 }
296 }
297
298 Anonymous what;
299 what->setId(id);
300 what->setAttr("possess_key", key);
301
302 Look l;
303 l->setFrom(getId());
304 l->setArgs1(what);
305 l->setSerialno(getNewSerialno());
306 m_con->send(l);
307
308 m_con->getResponder()->await(l->getSerialno(), this, &Account::avatarResponse);
309 m_status = TAKING_CHAR;
310 return NO_ERR;
311 }
312
takeCharacter(const std::string & id)313 Result Account::takeCharacter(const std::string &id)
314 {
315 if (m_characterIds.count(id) == 0) {
316 error() << "Character '" << id << "' not owned by Account " << m_username;
317 return BAD_CHARACTER_ID;
318 }
319
320 if (!m_con->isConnected()) return NOT_CONNECTED;
321 if (m_status != LOGGED_IN) {
322 if ((m_status == CREATING_CHAR) || (m_status == TAKING_CHAR)) {
323 error() << "duplicate char creation / take";
324 return DUPLICATE_CHAR_ACTIVE;
325 } else {
326 error() << "called createCharacter on unconnected Account, ignoring";
327 return NOT_LOGGED_IN;
328 }
329 }
330
331 Anonymous what;
332 what->setId(id);
333
334 Look l;
335 l->setFrom(id); // should this be m_accountId?
336 l->setArgs1(what);
337 l->setSerialno(getNewSerialno());
338 m_con->send(l);
339
340 m_con->getResponder()->await(l->getSerialno(), this, &Account::avatarResponse);
341 m_status = TAKING_CHAR;
342 return NO_ERR;
343 }
344
deactivateCharacter(Avatar * av)345 Result Account::deactivateCharacter(Avatar* av)
346 {
347 av->deactivate();
348 return NO_ERR;
349 }
350
isLoggedIn() const351 bool Account::isLoggedIn() const
352 {
353 return ((m_status == LOGGED_IN) ||
354 (m_status == TAKING_CHAR) || (m_status == CREATING_CHAR));
355 }
356
357 #pragma mark -
358
internalLogin(const std::string & uname,const std::string & pwd)359 Result Account::internalLogin(const std::string &uname, const std::string &pwd)
360 {
361 assert(m_status == DISCONNECTED);
362
363 m_status = LOGGING_IN;
364 m_username = uname; // store for posterity
365
366 AtlasAccount account;
367 account->setPassword(pwd);
368 account->setUsername(uname);
369
370 Login l;
371 l->setArgs1(account);
372 l->setSerialno(getNewSerialno());
373 m_con->getResponder()->await(l->getSerialno(), this, &Account::loginResponse);
374 m_con->send(l);
375
376 m_timeout.reset(new Timeout(5000));
377 m_timeout->Expired.connect(sigc::mem_fun(this, &Account::handleLoginTimeout));
378
379 return NO_ERR;
380 }
381
logoutResponse(const RootOperation & op)382 void Account::logoutResponse(const RootOperation& op)
383 {
384 if (!op->instanceOf(INFO_NO))
385 warning() << "received a logout response that is not an INFO";
386
387 internalLogout(true);
388 }
389
internalLogout(bool clean)390 void Account::internalLogout(bool clean)
391 {
392 if (clean) {
393 if (m_status != LOGGING_OUT)
394 error() << "got clean logout, but not logging out already";
395 } else {
396 if ((m_status != LOGGED_IN) && (m_status != TAKING_CHAR) && (m_status != CREATING_CHAR))
397 error() << "got forced logout, but not currently logged in";
398 }
399
400 m_con->unregisterRouterForTo(m_router, m_accountId);
401 m_status = DISCONNECTED;
402 m_timeout.reset();
403
404 if (m_con->getStatus() == BaseConnection::DISCONNECTING) {
405 m_con->unlock();
406 } else {
407 LogoutComplete.emit(clean);
408 }
409 }
410
loginResponse(const RootOperation & op)411 void Account::loginResponse(const RootOperation& op)
412 {
413 if (op->instanceOf(ERROR_NO)) {
414 loginError(smart_dynamic_cast<Error>(op));
415 } else if (op->instanceOf(INFO_NO)) {
416 const std::vector<Root>& args = op->getArgs();
417 loginComplete(smart_dynamic_cast<AtlasAccount>(args.front()));
418 } else
419 warning() << "received malformed login response: " << op->getClassNo();
420 }
421
loginComplete(const AtlasAccount & p)422 void Account::loginComplete(const AtlasAccount &p)
423 {
424 if (m_status != LOGGING_IN) {
425 error() << "got loginComplete, but not currently logging in!";
426 }
427
428 if (!p.isValid()) {
429 error() << "no account in response.";
430 return;
431 }
432
433 //The user name being different should not be a fatal thing.
434 if (p->getUsername() != m_username) {
435 warning() << "received username does not match existing";
436 m_username = p->getUsername();
437 }
438
439 m_status = LOGGED_IN;
440 m_accountId = p->getId();
441
442 m_con->registerRouterForTo(m_router, m_accountId);
443 updateFromObject(p);
444
445 // notify an people watching us
446 LoginSuccess.emit();
447
448 m_con->Disconnecting.connect(sigc::mem_fun(this, &Account::netDisconnecting));
449 m_timeout.reset();
450 }
451
avatarLogoutRequested(Avatar * avatar)452 void Account::avatarLogoutRequested(Avatar* avatar)
453 {
454 AvatarDeactivated.emit(avatar);
455 delete avatar;
456 }
457
updateFromObject(const AtlasAccount & p)458 void Account::updateFromObject(const AtlasAccount &p)
459 {
460 m_characterIds = StringSet(p->getCharacters().begin(), p->getCharacters().end());
461 m_parents = p->getParents();
462
463 if(p->hasAttr("character_types") == true)
464 {
465 const Atlas::Message::Element& CharacterTypes(p->getAttr("character_types"));
466
467 if(CharacterTypes.isList() == true)
468 {
469 const Atlas::Message::ListType & CharacterTypesList(CharacterTypes.asList());
470 Atlas::Message::ListType::const_iterator iCharacterType(CharacterTypesList.begin());
471 Atlas::Message::ListType::const_iterator iEnd(CharacterTypesList.end());
472
473 m_characterTypes.reserve(CharacterTypesList.size());
474 while(iCharacterType != iEnd)
475 {
476 if(iCharacterType->isString() == true)
477 {
478 m_characterTypes.push_back(iCharacterType->asString());
479 }
480 else
481 {
482 error() << "An element of the \"character_types\" list is not a String.";
483 }
484 ++iCharacterType;
485 }
486 }
487 else
488 {
489 error() << "Account has attribute \"character_types\" which is not of type List.";
490 }
491 }
492
493 if (p->hasAttr("spawns")) {
494 const Atlas::Message::Element& spawns(p->getAttr("spawns"));
495
496 if (spawns.isList()) {
497 m_spawnPoints.clear();
498 const Atlas::Message::ListType & spawnsList(spawns.asList());
499 for (Atlas::Message::ListType::const_iterator I = spawnsList.begin(); I != spawnsList.end(); ++I) {
500 if (I->isMap()) {
501 const Atlas::Message::MapType& spawnMap = I->asMap();
502 Atlas::Message::MapType::const_iterator spawnNameI = spawnMap.find("name");
503 if (spawnNameI != spawnMap.end()) {
504 const Atlas::Message::Element& name(spawnNameI->second);
505 if (name.isString()) {
506 CharacterTypeStore characterTypes;
507 Atlas::Message::MapType::const_iterator characterTypesI = spawnMap.find("character_types");
508
509 if (characterTypesI != spawnMap.end()) {
510 const Atlas::Message::Element& characterTypesElement(characterTypesI->second);
511 if (characterTypesElement.isList()) {
512 const Atlas::Message::ListType & characterTypesList(characterTypesElement.asList());
513 for (Atlas::Message::ListType::const_iterator J = characterTypesList.begin(); J != characterTypesList.end(); ++J)
514 {
515 if (J->isString()) {
516 characterTypes.push_back(CharacterType(J->asString(), ""));
517 } else {
518 error() << "Character type is not of type string.";
519 }
520 }
521 } else {
522 error() << "Character type element is not of type list.";
523 }
524 }
525 SpawnPoint spawnPoint(name.asString(), characterTypes, "");
526 m_spawnPoints.insert(SpawnPointMap::value_type(spawnPoint.getName(), spawnPoint));
527 } else {
528 error() << "Spawn name is not a string.";
529 }
530 } else {
531 error() << "Spawn point has no name defined.";
532 }
533 }
534 }
535 } else {
536 error() << "Account has attribute \"spawns\" which is not of type List.";
537 }
538 }
539 }
540
getErrorMessage(const RootOperation & err)541 std::string getErrorMessage(const RootOperation & err)
542 {
543 std::string msg;
544 const std::vector<Root>& args = err->getArgs();
545 if (args.empty()) {
546 error() << "got Error error op from server without args";
547 msg = "Unknown error.";
548 } else {
549 const Root & arg = args.front();
550 Atlas::Message::Element message;
551 if (arg->copyAttr("message", message) != 0) {
552 error() << "got Error error op from server without message";
553 msg = "Unknown error.";
554 } else {
555 if (!message.isString()) {
556 error() << "got Error error op from server with bad message";
557 msg = "Unknown error.";
558 } else {
559 msg = message.String();
560 }
561 }
562 }
563 return msg;
564 }
565
loginError(const Error & err)566 void Account::loginError(const Error& err)
567 {
568 assert(err.isValid());
569 if (m_status != LOGGING_IN) {
570 error() << "got loginError while not logging in";
571 }
572
573 std::string msg = getErrorMessage(err);
574
575 // update state before emitting signal
576 m_status = DISCONNECTED;
577 m_timeout.reset();
578
579 LoginFailure.emit(msg);
580 }
581
handleLoginTimeout()582 void Account::handleLoginTimeout()
583 {
584 m_status = DISCONNECTED;
585 deleteLater(m_timeout.release());
586
587 LoginFailure.emit("timed out waiting for server response");
588 }
589
avatarResponse(const RootOperation & op)590 void Account::avatarResponse(const RootOperation& op)
591 {
592 if (op->instanceOf(ERROR_NO)) {
593 std::string msg = getErrorMessage(op);
594
595 // creating or taking a character failed for some reason
596 AvatarFailure(msg);
597 m_status = Account::LOGGED_IN;
598 } else if (op->instanceOf(INFO_NO)) {
599 const std::vector<Root>& args = op->getArgs();
600 if (args.empty()) {
601 warning() << "no args character create/take response";
602 return;
603 }
604
605 RootEntity ent = smart_dynamic_cast<RootEntity>(args.front());
606 if (!ent.isValid()) {
607 warning() << "malformed character create/take response";
608 return;
609 }
610
611 Avatar* av = new Avatar(*this, ent->getId());
612 AvatarSuccess.emit(av);
613 m_status = Account::LOGGED_IN;
614
615 assert(m_activeCharacters.count(av->getId()) == 0);
616 m_activeCharacters[av->getId()] = av;
617
618 // expect another op with the same refno
619 m_con->getResponder()->ignore(op->getRefno());
620 } else
621 warning() << "received incorrect avatar create/take response";
622 }
623
internalDeactivateCharacter(Avatar * av)624 void Account::internalDeactivateCharacter(Avatar* av)
625 {
626 assert(m_activeCharacters.count(av->getId()) == 1);
627 m_activeCharacters.erase(av->getId());
628 }
629
sightCharacter(const RootOperation & op)630 void Account::sightCharacter(const RootOperation& op)
631 {
632 if (!m_doingCharacterRefresh) {
633 error() << "got sight of character outside a refresh, ignoring";
634 return;
635 }
636
637 const std::vector<Root>& args = op->getArgs();
638 if (args.empty()) {
639 error() << "got sight of character with no args";
640 return;
641 }
642
643 RootEntity ge = smart_dynamic_cast<RootEntity>(args.front());
644 if (!ge.isValid()) {
645 error() << "got sight of character with malformed args";
646 return;
647 }
648
649 CharacterMap::iterator C = _characters.find(ge->getId());
650 if (C != _characters.end()) {
651 error() << "duplicate sight of character " << ge->getId();
652 return;
653 }
654
655 // okay, we can now add it to our map
656 _characters.insert(C, CharacterMap::value_type(ge->getId(), ge));
657 GotCharacterInfo.emit(ge);
658
659 // check if we're done
660 if (_characters.size() == m_characterIds.size()) {
661 m_doingCharacterRefresh = false;
662 GotAllCharacters.emit();
663 }
664 }
665
666 /* this will only ever get encountered after the connection is initally up;
667 thus we use it to trigger a reconnection. Note that some actions by
668 Lobby and World are also required to get everything back into the correct
669 state */
670
netConnected()671 void Account::netConnected()
672 {
673 // re-connection
674 if (!m_username.empty() && !m_pass.empty() && (m_status == DISCONNECTED)) {
675 debug() << "Account " << m_username << " got netConnected, doing reconnect";
676 internalLogin(m_username, m_pass);
677 }
678 }
679
netDisconnecting()680 bool Account::netDisconnecting()
681 {
682 if (m_status == LOGGED_IN) {
683 m_con->lock();
684 logout();
685 return false;
686 } else
687 return true;
688 }
689
netFailure(const std::string &)690 void Account::netFailure(const std::string& /*msg*/)
691 {
692
693 }
694
handleLogoutTimeout()695 void Account::handleLogoutTimeout()
696 {
697 error() << "LOGOUT timed out waiting for response";
698
699 m_status = DISCONNECTED;
700 deleteLater(m_timeout.release());
701
702 LogoutComplete.emit(false);
703 }
704
avatarLogoutResponse(const RootOperation & op)705 void Account::avatarLogoutResponse(const RootOperation& op)
706 {
707 if (!op->instanceOf(INFO_NO)) {
708 warning() << "received an avatar logout response that is not an INFO";
709 return;
710 }
711
712 const std::vector<Root>& args(op->getArgs());
713
714 if (args.empty() || (args.front()->getClassNo() != LOGOUT_NO)) {
715 warning() << "argument of avatar logout INFO is not a logout op";
716 return;
717 }
718
719 RootOperation logout = smart_dynamic_cast<RootOperation>(args.front());
720 const std::vector<Root>& args2(logout->getArgs());
721 if (args2.empty()) {
722 warning() << "argument of avatar logout INFO is logout without args";
723 return;
724 }
725
726 std::string charId = args2.front()->getId();
727 debug() << "got logout for character " << charId;
728
729 if (!m_characterIds.count(charId)) {
730 warning() << "character ID " << charId << " is unknown on account " << m_accountId;
731 }
732
733 ActiveCharacterMap::iterator it = m_activeCharacters.find(charId);
734 if (it == m_activeCharacters.end()) {
735 warning() << "character ID " << charId << " does not correspond to an active avatar.";
736 return;
737 }
738
739 AvatarDeactivated.emit(it->second);
740 delete it->second; // will call back into internalDeactivateCharacter
741 }
742
743 } // of namespace Eris
744