File: Player.cpp

package info (click to toggle)
eris 1.2.1-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 2,360 kB
  • ctags: 1,348
  • sloc: sh: 8,289; cpp: 7,576; perl: 287; ansic: 172; makefile: 143
file content (465 lines) | stat: -rw-r--r-- 12,756 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
#ifdef HAVE_CONFIG_H
	#include "config.h"
#endif

#include <Eris/Player.h>
#include <Eris/Lobby.h>
#include <Eris/World.h>
#include <Eris/Connection.h>
#include <Eris/SignalDispatcher.h>
#include <Eris/ClassDispatcher.h>
#include <Eris/Utils.h>
#include <Eris/Log.h>
#include <Eris/Exceptions.h>

// various atlas headers we need
#include <Atlas/Objects/Encoder.h>
#include <Atlas/Objects/Entity/Player.h>
#include <Atlas/Objects/Entity/GameEntity.h>

#include <Atlas/Objects/Operation/Login.h>
#include <Atlas/Objects/Operation/Create.h>
#include <Atlas/Objects/Operation/Logout.h>
#include <Atlas/Objects/Operation/Look.h>
#include <Atlas/Objects/Operation/Error.h>

#include <sigc++/object_slot.h>

#include <algorithm>
#include <cassert>

typedef Atlas::Message::Element::ListType AtlasListType;
typedef Atlas::Message::Element::MapType AtlasMapType;

using namespace Atlas::Objects;

namespace Eris {

Player::Player(Connection *con) :
	_con(con),
	_account(""),
    m_doingCharacterRefresh(false),
	_username(""),
	_lobby(con->getLobby())
{
    _currentAction = "";
    _logoutTimeout = NULL;
    
	assert(_con);

	_con->Connected.connect(SigC::slot(*this, &Player::netConnected));
	_con->Failure.connect(SigC::slot(*this, &Player::netFailure));
	
	Dispatcher *d = _con->getDispatcherByPath("op:error");
	assert(d);
	d->addSubdispatch(new SignalDispatcher<Operation::Error>("player",
		SigC::slot(*this, &Player::recvOpError)
	));

	d = _con->getDispatcherByPath("op");
	d = d->addSubdispatch(ClassDispatcher::newAnonymous(_con));
	d->addSubdispatch(new SignalDispatcher<Operation::Logout>("logout",
		SigC::slot(*this, &Player::recvRemoteLogout)), "logout");
	
	_lobby->LoggedIn.connect(SigC::slot(*this, &Player::loginComplete));
}	

Player::~Player()
{
	if (_con) {
		//_con->removeDispatcherByPath
		
		
	}
}

void Player::login(const std::string &uname,
	const std::string &password)
{
	if (!_con || (_con->getStatus() != BaseConnection::CONNECTED))
		throw InvalidOperation("Invalid connection");
	
	if (!_currentAction.empty())
		throw InvalidOperation("action in progress (" + _currentAction + ")");
	
	internalLogin(uname, password);
	
	// tell the lobby the serial number of the request, so
	// it knows which INFO op to use (assuming other INFOs going about..)
	_lobby->expectInfoRefno(_currentSerial);
	
	// store for re-logins
	_username = uname;
	_pass = password;
}

void Player::createAccount(const std::string &uname, 
	const std::string &name,
	const std::string &pwd)
{
	if (!_con || (_con->getStatus() != BaseConnection::CONNECTED))
		throw InvalidOperation("Invalid connection");
	
	if (!_currentAction.empty())
		throw InvalidOperation("action in progress (" + _currentAction + ")");

	// need option to create an admin object here
	Atlas::Objects::Entity::Player account;
 	account.setId(uname);	// FIXME - I think this should be deprectaed in all of Atlas
 	account.setPassword(pwd);
	account.setName(name);
	account.setAttr("username", uname);
	
 	Atlas::Message::Element::ListType args(1,account.asObject());
	Atlas::Objects::Operation::Create c;
  	
	c.setSerialno(getNewSerialno());
 	c.setArgs(args);
	_con->send(c);
	
	_currentAction = "create-account";
	_currentSerial = c.getSerialno();
	
	// let the lobby know what serial number to expect
	_lobby->expectInfoRefno(_currentSerial);
	
	// store for re-logins
	_username = uname;
	_pass = pwd;
}

void Player::logout()
{
    if (!_con)
	    throw InvalidOperation("connection is invalid");
    
    if (!_con->isConnected()) {
	    Eris::log(LOG_WARNING, "connection not open, ignoring Player::logout");
	    // FIXME - provide feedback here
	    return;
    }
	
    if (!_currentAction.empty()) {
		Eris::log(LOG_WARNING, "got logout with action (%s) already in progress",
	    	_currentAction.c_str());
		return;
    }
    
    Atlas::Objects::Operation::Logout l;
    l.setId(_account);
    l.setSerialno(getNewSerialno());
    l.setFrom(_account);
    
    _con->send(l);
    _currentAction = "logout";
    _currentSerial = l.getSerialno();
	
    _logoutTimeout = new Timeout("logout", this, 5000);
    _logoutTimeout->Expired.connect(SigC::slot(*this, &Player::handleLogoutTimeout));
}

const CharacterMap& Player::getCharacters()
{
    if (_account.empty())
        Eris::log(LOG_ERROR, "Not logged into an account : getCharacter returning empty dictionary");
    
    if (m_doingCharacterRefresh)
        Eris::log(LOG_WARNING, "client retrieving partial / incomplete character dictionary");
    
    return _characters;
}

void Player::refreshCharacterInfo()
{
    if (!_con->isConnected())
        throw InvalidOperation("Not connected to server");
	
    // we need to be logged in too
    if (_account.empty()) {
        Eris::log(LOG_ERROR, "refreshCharacterInfo: Not logged into an account yet");
        return;
    }
    
    if (m_doingCharacterRefresh)
        return; // silently ignore overlapping refreshes
        
    _characters.clear();

    if (m_characterIds.empty()) {
        GotAllCharacters.emit(); // we must emit the done signal
        return;
    }
    
    m_doingCharacterRefresh = true;
    
    Operation::Look lk;
    AtlasMapType args;
    lk.setFrom(_account);
        
    for (StringSet::iterator I=m_characterIds.begin(); I!=m_characterIds.end(); ++I) {
        // modify and send the look
        args["id"] = *I;
        lk.setArgs(AtlasListType(1, args));
        lk.setSerialno(getNewSerialno());
        _con->send(lk);
    }
}

Avatar* Player::createCharacter(const Atlas::Objects::Entity::GameEntity &ent)
{
    if (!_lobby || _lobby->getAccountID().empty())
            throw InvalidOperation("no account exists!");

    if (!_con->isConnected())
            throw InvalidOperation("Not connected to server");

    if (ent.getName().empty())
            throw InvalidOperation("Character unnamed");

    Operation::Create c;    
    Atlas::Message::Element::ListType args(1,ent.asObject());

    c.setArgs(args);
    c.setFrom(_account);
    c.setSerialno(getNewSerialno());

    World* world = new World(this, _con);
    Avatar* avatar = world->createAvatar(c.getSerialno());

    _con->send(c);

    return avatar;
}

void Player::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 Player::createCharacterHandler(long serialno)
{
    if (serialno)
        NewCharacter((new World(this, _con))->createAvatar(serialno));
}

Avatar* Player::takeCharacter(const std::string &id)
{
    StringSet::iterator C = m_characterIds.find(id);
    if (C == m_characterIds.end())
        throw InvalidOperation("Character " + id + " not owned by player");
	
    if (!_con->isConnected())
        throw InvalidOperation("Not connected to server");
	
    Operation::Look l;
    l.setFrom(id);  
    Atlas::Message::Element::MapType what;
    what["id"]=id;
    Atlas::Message::Element::ListType args(1,what);
    l.setArgs(args);
    l.setSerialno(getNewSerialno());
	
    World* world = new World(this, _con);
    Avatar* avatar = world->createAvatar(l.getSerialno(), id);
	
    _con->send(l);
    return avatar;
}

void Player::internalLogin(const std::string &uname, const std::string &pwd)
{
    Atlas::Objects::Entity::Account account;
    account.setId(uname);
    account.setPassword(pwd);
    account.setAttr("username", uname);

    Operation::Login l;
    Atlas::Message::Element::ListType args(1,account.asObject());
    l.setArgs(args);
    l.setSerialno(getNewSerialno());
    
    _con->send(l);
    
    // setup error tracking
    _currentAction = "login";
    _currentSerial = l.getSerialno();
}

void Player::internalLogout(bool clean)
{
    _currentAction = "";
    if (_logoutTimeout)
        delete _logoutTimeout;
	
    _con->disconnect();
    LogoutComplete.emit(clean);
}

const std::string& Player::getAccountID() const
{
    return _account;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// callbacks

void Player::loginComplete(const Atlas::Objects::Entity::Player &p)
{
    // FIXME - user p.GetUsername() to verify this is the correct player object
    
    _currentAction = "";
    _account = p.getId();
    
// extract character IDs, and turn from a list into a set
    m_characterIds.clear();
    const AtlasListType& cs = p.getCharacters();
    for (AtlasListType::const_iterator I=cs.begin(); I != cs.end(); ++I)
        m_characterIds.insert(I->asString());
    
    // setup dispatcher for sight of character ops
    Dispatcher *d = _con->getDispatcherByPath("op:oog:sight:entity");
    assert(d);
    
    // notify an people watching us (as opposed to watching the lobby directly)
    LoginSuccess.emit();
    
    if (d->getSubdispatch("character"))
        // second time around, don't try again
        return;
    
    // there will be several anonymous children, I guess
    d = d->addSubdispatch(ClassDispatcher::newAnonymous(_con));
    d->addSubdispatch(new SignalDispatcher<Atlas::Objects::Entity::GameEntity>("character",
	SigC::slot(*this, &Player::recvSightCharacter)),
	"game_entity"
    );
    
    d = _con->getDispatcherByPath("op:info:op");
    Dispatcher *infoLogout = d->addSubdispatch(ClassDispatcher::newAnonymous(_con));
    infoLogout->addSubdispatch( new SignalDispatcher<Operation::Logout>(
	"player", SigC::slot(*this, &Player::recvLogoutInfo)),
	"logout"
    );
    
    _con->Disconnecting.connect(SigC::slot(*this, &Player::netDisconnecting));
}

void Player::recvOpError(const Atlas::Objects::Operation::Error &err)
{
	// skip errors if we're not doing anything
	if (_currentAction.empty() || (err.getRefno() != _currentSerial))
		return;
	
	std::string serverMsg = getMember(getArg(err, 0), "message").asString();
	// can actually use error[2]->parents here to detrmine the current action
	Eris::log(LOG_WARNING, "Received Atlas error %s", serverMsg.c_str());
	
	std::string pr = getMember(getArg(err, 1), "parents").asList().front().asString();
	if (pr == "login") {
		// prevent re-logins on the account
		_username="";
		LoginFailure.emit(LOGIN_INVALID, serverMsg);
	}
	
	if (_currentAction == "create-account") {
		assert(pr == "create");
		// prevent re-logins on the account
		_username="";
		LoginFailure.emit(LOGIN_INVALID, serverMsg);
	}
	
	// clear these out
	_currentAction = "";
	_currentSerial = 0;
}

void Player::recvSightCharacter(const Atlas::Objects::Entity::GameEntity &ge)
{
    Eris::log(LOG_DEBUG, "got sight of character %s", ge.getName().c_str());
    
    if (!m_doingCharacterRefresh) {
        Eris::log(LOG_ERROR, "got sight of character %s while outside a refresh - ignoring", ge.getId().c_str());
        return;
    }
    
    CharacterMap::iterator C = _characters.find(ge.getId());
    if (C != _characters.end()) {
        Eris::log(LOG_ERROR, "got duplicate of character %s - ignoring", ge.getId().c_str());
        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'redone
    if (_characters.size() == m_characterIds.size())
    {
	GotAllCharacters.emit();
        m_doingCharacterRefresh = false;
    }
}

void Player::recvLogoutInfo(const Atlas::Objects::Operation::Logout &lo)
{
    Eris::log(LOG_DEBUG, "got INFO(logout)");
    internalLogout(true);
}

void Player::recvRemoteLogout(const Operation::Logout &op)
{	
	Eris::log(LOG_DEBUG, "got server-initated LOGOUT");
	internalLogout(false);
}

/* 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 Player::netConnected()
{
	// re-connection
	if (!_username.empty() && _currentAction.empty())
		internalLogin(_username, _pass);
	
	// ditto for IG (world)
}

bool Player::netDisconnecting()
{
	_con->lock();
	logout();
	// FIXME  -  decide what reponse lgout should have, install a handler
	// for it (in logout() itself), and then attach a signal to the handler
	// that calls _con->unlock();
	
	return false;
}

void Player::netFailure(const std::string& /*msg*/)
{
	; // do something useful here?
}

void Player::handleLogoutTimeout()
{
    Eris::log(LOG_DEBUG, "LOGOUT timed out waiting for response");
    
    _currentAction = "";
    delete _logoutTimeout;
    
    LogoutComplete.emit(false);
}

} // of namespace Eris