Commit f162a202 authored by Stephen M. Webb's avatar Stephen M. Webb

Imported Upstream version 1.3.14

parents
Original author and current maintainer is James Turner
(james@worldforge.org)
Numerous contributions by Ron Steinke including the Perl bindings, WFmath
conversion and bug reports by the dozen.
Eris would not have been possible without Uclient by Karsten Olaf-Laux, the
first large-scale client to use Atlas.
Al Riddoch has contributed so many build / packaging fixes, bug reports and
clarified the Atlas protocol for me on countless occasions. He is truly
the master of autoconf and rpmbuild.
Contributors :
Alistair Riddoch
Karsten Olaf-Laux
Anders Petersen
Ron Steinke
Michael Koch
Simon Goodall
Erik Hjortsberg
This file describes the significant changes in Eris 1.4 from 1.2
The major goals of the 1.4 release are Atlas-C++ 0.6 support, renaming various
classes so their names better match the equivalent Atlas objects, re-designing
poorly-performing areas of the code, and addressing some long standing bugs.
At present, only unstable development releases of the 1.4 series, labelled
1.3.x, are available. Their API is still subject to change, and the code
contains bugs. Feedback on any issues, or suggestions for API improvements
would be much appreciated.
The following classes have been renamed, and their behaviour evolved:
- Player has now become Account, and has slightly more control over Account
related tasks. Crucially, the Lobby is now strictly optional, and need only
be created if a client wishes to support out-of-game chat.
- World has been renamed to View. This is designed to reflect the fact it is
merely a subset of the total server world that is currently visible to the
client. Also, some View functions were moved to Avatar which is now the
primary in-game object.
Related to these changes, the ground-work has been done to support multiple
Connections, Avatars and Views. The possible configurations for this have not
yet been fully explored, but obvious cases are multiple Avatars sharing a
single View and Connection, and a single Avatar bound to multiple Views (i.e
aggregating a distributed world). As a result, all of the static Instance()
methods are gone.
Motion prediction support has been added: by called View::update() once per
frame, Eris will calculate a predicted position for each Entity with a
velocity. The prediction is currently simplistic, but can be improved based on
feedback. Note that motion-predicted entities do NOT emit the 'Moved' signal
every time their predicted position changes, since this would be wasteful.
Instead they emit a 'Moving' signal when they start and end motion-prediction;
clients should maintain their own list of moving entities and synchronise the
predicted position with their display system once per frame.
The Entity class has been re-factored, with more consistent support for
sub-classing. Also, actions, sounds and imaginary operations are now handled
directly by Eris. This enables to client to respond more expressively to server
events, for example triggering single-shot animations in response to actions.
The meta-server code has been overhauled and now works reliably. It should not
hang or block regardless of what remote servers do. Similarly, disconnecting
and reconnecting to a server should be much improved. Bug reports about
remaining problems in these areas, especially on different platforms, would be
appreciated.
Internally, the Dispatcher system has been replaced with fixed-function Router
classes. This greatly simplifies the code, and together with Atlas-C++ 0.6
should alleviate the pervasive and redundant data-copying which made previous
versions perform poorly.
Al Riddoch has added support for SigC++ 2.0 (SigC++ 1.2 is still supported), to
enable compatibility with other projects which uses the newer version, notably
Gtkmm 2.4
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
#ifndef ERIS_PLAYER_H
#define ERIS_PLAYER_H
#include <Eris/Types.h>
#include <Atlas/Objects/ObjectsFwd.h>
#include <sigc++/signal.h>
#include <vector>
#include <map>
#include <memory>
namespace Eris
{
class Connection;
class Avatar;
class AccountRouter;
class Timeout;
/** Type used to return available characters */
typedef std::map<std::string, Atlas::Objects::Entity::RootEntity> CharacterMap;
typedef std::map<std::string, Avatar*> ActiveCharacterMap;
/// Encapsulates all the state of an Atlas Account, and methods that operation on that state
/** An Account object represents the encapsulation of a server account, and it's binding to a character in the
game world. Future versions of Eris will support multiple Account objects per Connection, allowing various
configurations of interface, proxies and so forth.
<br>
Account is also the mechanism by which Lobby and Avatars objects are made available to the client,
in response to login / create operations */
class Account : virtual public sigc::trackable
{
public:
/// Create a new Account associated with a Connection object
/**
Create a new Account object : currently only one is assumed, but multiple
Accounts might be supported in the future
@param con A valid (but not necessarily connected) Connection instance
*/
Account(Connection *con);
~Account();
/// Login to the server using user-supplied account information
/** This is the basic way of logging into an existing account. Server-side
failures during the login process, such as the account being unknown
or an incorrect password being supplied, will result in the 'LoginFailure' signal being
emitted with some vaugely helpful error message, and an error code. The LoginSuccess
signal will be emitted upon sucessful completion of the login process.
@param uname The username of the account
@param pwd The correct password for the account
*/
Result login(const std::string &uname, const std::string &pwd);
/// Attempt to create a new account on the server and log into it.
/* Create a new account on the server, if possible.
Server-side failures, such as an account already existing with the specified
username, will cause the 'LoginFailure' signal to be emitted with an error message
and a code. As for 'login', LoginSuccess wil be emitted if everything goes as plan.
@param uname The desired username of the account (eg 'ajr')
@param fullName The real name of the user (e.g 'Al Riddoch')
@param pwd The plaintext password for the new account
*/
Result createAccount(const std::string &uname,
const std::string &fullName,
const std::string &pwd);
/// Request logout from the server.
/** Initiate a clean disconnection from the server. The LogoutComplete
signal will be emitted when the process completes. Calling this on an Account
which is not logged in will produce an error. */
Result logout();
/// Check if the account is logged in.
/** Many operations will produce errors if the account is not logged in. */
bool isLoggedIn() const;
/// Returns a container of character types that the client is allowed to create.
const std::vector< std::string > & getCharacterTypes(void) const;
/// Get the characters owned by this account.
/**
Note you should call
refreshCharacterInfo, and wait for the GotAllCharacters signal, prior to the
initial call : otherwise, it will return an empty or incomplete list.
*/
const CharacterMap& getCharacters();
/**
Update the charcter list (based on changes to play). The intention here is
that clients will call this method for some kind of'choose character' interface
or menu, and wait for the GotAllCharacters signal before displaying the list.
Alternatively, you can display the UI immediatley, and add character entries
based on the GotCharacterInfo signal, which will be emitted once for each character.
*/
Result refreshCharacterInfo();
/// Enter the game using an existing character
/**
@param id The id of the game entity to activate; this must be owned by the account.
@result The Avatar that represents the character. Note ownership of this passes to
the caller.
*/
Result takeCharacter(const std::string &id);
/// enter the game using a new character
Result createCharacter(const Atlas::Objects::Entity::RootEntity &character);
/// pop up the game's character creation dialog, if present
//void createCharacter();
/// returns true if the game has defined a character creation dialog
bool canCreateCharacter() {return false;}
const ActiveCharacterMap& getActiveCharacters() const
{ return m_activeCharacters; }
/**
Request de-activation of a character. The 'AvatarDeactivated' signal will
be emitted when the deactivation completes.
*/
Result deactivateCharacter(Avatar* av);
/// returns the account ID if logged in
const std::string& getId() const
{
return m_accountId;
}
/** Return the username of this account. */
const std::string& getUsername() const
{ return m_username; }
/// Access the underlying Connection for this account
Connection* getConnection() const
{
return m_con;
}
// signals
/// emitted when a character has been retrived from the server
sigc::signal<void, const Atlas::Objects::Entity::RootEntity&> GotCharacterInfo;
/// emitted when the entire character list had been updated
sigc::signal<void> GotAllCharacters;
/// Emitted when a server-side error occurs during account creation / login.
/**
The argument is an error message from the server - hopefully this will
become something more useful such as an enum code, in the future.
*/
sigc::signal<void, const std::string &> LoginFailure;
/** Emitted when login or character creation is successful. */
sigc::signal<void> LoginSuccess;
/// Emitted when a logout completes
/** Depending on whether the logout completed with a positive server
acknowledgement or just timedout, the argument will be either true
(success, clean logout) or false (failure, timeout or other problem)
*/
sigc::signal<void, bool> LogoutComplete;
/**
Emitted when creating a character or taking an existing one
succeeds.
*/
sigc::signal<void, Avatar*> AvatarSuccess;
/**
Emitted when creating or taking a character fails for some reason.
String argument is the error messgae from the server.
*/
sigc::signal<void, const std::string &> AvatarFailure;
/**
Emitted when an active avatar is deactivated. Clients <em>must not</em>
refer to the Avatar or View objects after this signal is emitted (it is
safe to access them in a slot connected to this signal)
*/
sigc::signal<void, Avatar*> AvatarDeactivated;
protected:
friend class AccountRouter;
friend class Avatar; // so avatar can call deactivateCharacter
void sightCharacter(const Atlas::Objects::Operation::RootOperation& op);
void loginComplete(const Atlas::Objects::Entity::Account &p);
void loginError(const Atlas::Objects::Operation::Error& err);
Result internalLogin(const std::string &unm, const std::string &pwd);
void internalLogout(bool clean);
/// Callback for network re-establishment
void netConnected();
/// help! the plug is being pulled!
bool netDisconnecting();
void netFailure(const std::string& msg);
void loginResponse(const Atlas::Objects::Operation::RootOperation& op);
void logoutResponse(const Atlas::Objects::Operation::RootOperation& op);
void avatarResponse(const Atlas::Objects::Operation::RootOperation& op);
void avatarLogoutResponse(const Atlas::Objects::Operation::RootOperation& op);
void handleLogoutTimeout();
// void recvRemoteLogout(const Atlas::Objects::Operation::Logout &lo);
void handleLoginTimeout();
typedef enum
{
DISCONNECTED = 0, ///< Default state, no server account active
LOGGING_IN, ///< Login sent, waiting for initial INFO response
LOGGED_IN, ///< Fully logged into a server-side account
LOGGING_OUT, ///< Sent a logout op, waiting for the INFO response
TAKING_CHAR, ///< sent a LOOK op for a character, awaiting INFO response
CREATING_CHAR ///< send a character CREATE op, awaiting INFO response
} Status;
void internalDeactivateCharacter(Avatar* av);
void updateFromObject(const Atlas::Objects::Entity::Account &p);
Connection* m_con; ///< underlying connection instance
Status m_status; ///< what the Player is currently doing
AccountRouter* m_router;
std::string m_accountId; ///< the account ID
std::string m_username; ///< The player's username ( != account object's ID)
std::string m_pass;
std::vector< std::string > m_characterTypes;
CharacterMap _characters; ///< characters belonging to this player
StringSet m_characterIds;
bool m_doingCharacterRefresh; ///< set if we're refreshing character data
ActiveCharacterMap m_activeCharacters;
std::auto_ptr<Timeout> m_timeout;
};
} // of namespace Eris
#endif
#include <Eris/Alarm.h>
#include <Eris/DeleteLater.h>
namespace Eris
{
Alarm::Alarm(unsigned long msec, const sigc::slot<void>& done)
{
m_due = WFMath::TimeStamp::now() + msec;
AlarmExpired.connect(done);
}
void Alarm::expired()
{
AlarmExpired();
deleteLater(this);
}
} // of namespace Eris
#ifndef ERIS_ALARM_H
#define ERIS_ALARM_H
#include <Eris/TimedEventService.h>
#include <sigc++/trackable.h>
#include <sigc++/slot.h>
#include <sigc++/signal.h>
namespace Eris
{
class Alarm : public TimedEvent, public sigc::trackable
{
public:
Alarm(unsigned long, const sigc::slot<void>& done);
virtual void expired();
virtual const WFMath::TimeStamp& due() const
{
return m_due;
}
private:
sigc::signal<void> AlarmExpired;
WFMath::TimeStamp m_due;
};
} // namespace Eris
#endif // of ERIS_ALARM_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <Eris/Avatar.h>
#include <Eris/Entity.h>
#include <Eris/Connection.h>
#include <Eris/Log.h>
#include <Eris/View.h>
#include <Eris/IGRouter.h>
#include <Eris/Account.h>
#include <Eris/Exceptions.h>
#include <Eris/TypeService.h>
#include <Eris/Operations.h>
#include <Eris/Response.h>
#include <Eris/DeleteLater.h>
#include <wfmath/atlasconv.h>
#include <sigc++/slot.h>
#include <Atlas/Objects/Operation.h>
#include <Atlas/Objects/Entity.h>
#include <Atlas/Objects/Anonymous.h>
using namespace Atlas::Objects::Operation;
using Atlas::Objects::Root;
using Atlas::Objects::Entity::Anonymous;
using WFMath::TimeStamp;
using namespace Atlas::Message;
using Atlas::Objects::smart_dynamic_cast;
namespace Eris
{
Avatar::Avatar(Account* pl, const std::string& entId) :
m_account(pl),
m_entityId(entId),
m_entity(NULL),
m_stampAtLastOp(TimeStamp::now()),
m_lastOpTime(0.0)
{
m_view = new View(this);
m_entityAppearanceCon = m_view->Appearance.connect(sigc::mem_fun(this, &Avatar::onEntityAppear));
m_router = new IGRouter(this);
m_view->getEntityFromServer("");
m_view->getEntity(m_entityId);
}
Avatar::~Avatar()
{
m_account->internalDeactivateCharacter(this);
delete m_router;
delete m_view;
}
void Avatar::deactivate()
{
Logout l;
Anonymous arg;
arg->setId(m_entityId);
l->setArgs1(arg);
l->setSerialno(getNewSerialno());
getConnection()->getResponder()->await(l->getSerialno(), this, &Avatar::logoutResponse);
getConnection()->send(l);
}
#pragma mark -
void Avatar::drop(Entity* e, const WFMath::Point<3>& pos, const std::string& loc)
{
if(e->getLocation() != m_entity)
{
error() << "Can't drop an Entity which is not held by the character";
return;
}
Move moveOp;
moveOp->setFrom(m_entityId);
Anonymous what;
what->setLoc(loc);
Atlas::Message::Element apos(pos.toAtlas());
what->setPosAsList(apos.asList());
what->setId(e->getId());
moveOp->setArgs1(what);
getConnection()->send(moveOp);
}
void Avatar::drop(Entity* e, const WFMath::Vector<3>& offset)
{
drop(e, m_entity->getPosition() + offset, m_entity->getLocation()->getId());
}
void Avatar::take(Entity* e)
{
Move moveOp;
moveOp->setFrom(m_entityId);
Anonymous what;
what->setLoc(m_entityId);
std::vector<double> p(3, 0.0);
what->setPos(p); // cyphesis is rejecting move ops with no pos set
what->setId(e->getId());
moveOp->setArgs1(what);
getConnection()->send(moveOp);
}
void Avatar::touch(Entity* e)
{
Touch touchOp;
touchOp->setFrom(m_entityId);
Anonymous what;
what->setId(e->getId());
touchOp->setArgs1(what);
getConnection()->send(touchOp);
}
void Avatar::say(const std::string& msg)
{
Talk t;
Anonymous what;
what->setAttr("say", msg);
t->setArgs1(what);
t->setFrom(m_entityId);
getConnection()->send(t);
}
void Avatar::emote(const std::string &em)
{
Imaginary im;
Anonymous emote;
emote->setId("emote");
emote->setAttr("description", em);
im->setArgs1(emote);
im->setFrom(m_entityId);
im->setSerialno(getNewSerialno());
getConnection()->send(im);
}
void Avatar::moveToPoint(const WFMath::Point<3>& pos)
{
Anonymous what;
what->setLoc(m_entity->getLocation()->getId());
what->setId(m_entityId);
what->setAttr("pos", pos.toAtlas());
Move moveOp;
moveOp->setFrom(m_entityId);
moveOp->setArgs1(what);
getConnection()->send(moveOp);
}
void Avatar::moveInDirection(const WFMath::Vector<3>& vel)
{
const double MIN_VELOCITY = 1e-3;
Anonymous arg;
//arg->setAttr("location", m_entity->getLocation()->getId());
arg->setId(m_entityId);
arg->setAttr("velocity", vel.toAtlas());
WFMath::CoordType sqr_mag = vel.sqrMag();
if(sqr_mag > MIN_VELOCITY) { // don't set orientation for zero velocity
WFMath::Quaternion q;
WFMath::CoordType z_squared = vel.z() * vel.z();
WFMath::CoordType plane_sqr_mag = sqr_mag - z_squared;
if(plane_sqr_mag < WFMATH_EPSILON * z_squared) {
// it's on the z axis
q.rotation(1, vel[2] > 0 ? -WFMath::Pi/2 : WFMath::Pi/2);
} else {
// rotate in the plane first
q.rotation(2, atan2(vel[1], vel[0]));
// then get the angle away from the plane
q = WFMath::Quaternion(1, -asin(vel[2] / sqrt(plane_sqr_mag))) * q;
}
arg->setAttr("orientation", q.toAtlas());
}
Move moveOp;
moveOp->setFrom(m_entityId);
moveOp->setArgs1(arg);
getConnection()->send(moveOp);
}
void Avatar::moveInDirection(const WFMath::Vector<3>& vel, const WFMath::Quaternion& orient)
{
Anonymous arg;
// arg->setAttr("location", m_entity->getLocation()->getId());
arg->setAttr("velocity", vel.toAtlas());
arg->setAttr("orientation", orient.toAtlas());
arg->setId(m_entityId);
Move moveOp;
moveOp->setFrom(m_entityId);
moveOp->setArgs1(arg);
getConnection()->send(moveOp);
}
void Avatar::place(Entity* e, Entity* container, const WFMath::Point<3>& pos)
{
Anonymous what;
what->setLoc(container->getId());
what->setAttr("pos", pos.toAtlas());
// what->setVelocityAsList( .... zero ... );
what->setId(e->getId());
Move moveOp;
moveOp->setFrom(m_entityId);
moveOp->setArgs1(what);
getConnection()->send(moveOp);
}
void Avatar::wield(Entity * entity)
{
if(entity->getLocation() != m_entity)
{
error() << "Can't wield an Entity which is not located in the avatar.";
return;
}
Anonymous arguments;
arguments->setId(entity->getId());
Wield wield;
wield->setFrom(m_entityId);
wield->setArgs1(arguments);
getConnection()->send(wield);
}
void Avatar::useOn(Entity * entity, const WFMath::Point< 3 > & position, const std::string& opType)
{
Anonymous arguments;
arguments->setId(entity->getId());
arguments->setObjtype("obj");
if (position.isValid()) arguments->setAttr("pos", position.toAtlas());
Use use;
use->setFrom(m_entityId);
if (opType.empty())
{