// Cyphesis Online RPG Server and AI Engine
// Copyright (C) 2000-2006 Alistair Riddoch
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// $Id: WorldRouter.cpp,v 1.221 2007-12-02 23:49:07 alriddoch Exp $
#include "WorldRouter.h"
#include "EntityFactory.h"
#include "rulesets/World.h"
#include "common/id.h"
#include "common/log.h"
#include "common/debug.h"
#include "common/const.h"
#include "common/globals.h"
#include "common/random.h"
#include "common/system.h"
#include "common/serialno.h"
#include "common/compose.hpp"
#include "common/inheritance.h"
#include "common/Setup.h"
#include <Atlas/Objects/Operation.h>
#include <Atlas/Objects/Anonymous.h>
#include <sstream>
#include <algorithm>
using Atlas::Message::Element;
using Atlas::Objects::Operation::Setup;
using Atlas::Objects::Operation::Appearance;
using Atlas::Objects::Entity::RootEntity;
using Atlas::Objects::Entity::Anonymous;
static const bool debug_flag = false;
/// \brief Type to hold an operation and the Entity it is from for efficiency
/// when broadcasting.
struct OpQueEntry {
Operation op;
Entity & from;
explicit OpQueEntry(const Operation & o, Entity & f);
OpQueEntry(const OpQueEntry & o);
~OpQueEntry();
const Operation & operator*() const {
return op;
}
Atlas::Objects::Operation::RootOperationData * operator->() const {
return op.get();
}
};
inline OpQueEntry::OpQueEntry(const Operation & o, Entity & f) : op(o), from(f)
{
from.incRef();
}
inline OpQueEntry::OpQueEntry(const OpQueEntry & o) : op(o.op), from(o.from)
{
from.incRef();
}
inline OpQueEntry::~OpQueEntry()
{
from.decRef();
}
/// \brief Update the in-game time.
///
/// Reads the system time, and applies the necessary offsets to calculate
/// the in-game time. This is the stored, and can be accessed using getTime().
void WorldRouter::updateTime(int sec, int usec)
{
double tmp_time = (double)(sec + timeoffset - m_initTime) + (double)usec/1000000;
m_realTime = tmp_time;
}
/// \brief Constructor for the world object.
///
/// The Entity representing the world is implicity constructed.
/// Currently the world entity is included in the perceptives list,
/// but I am not clear why. Need to look into why.
WorldRouter::WorldRouter() : BaseWorld(*new World(consts::rootWorldId,
consts::rootWorldIntId))
{
struct timeval tv;
gettimeofday(&tv, NULL);
m_initTime = tv.tv_sec;
updateTime(tv.tv_sec, tv.tv_usec);
m_gameWorld.incRef();
EntityFactory::init(*this);
m_gameWorld.setType(Inheritance::instance().getType("world"));
m_eobjects[m_gameWorld.getIntId()] = &m_gameWorld;
m_perceptives.insert(&m_gameWorld);
//WorldTime tmp_date("612-1-1 08:57:00");
}
/// \brief Destructor for the world object.
///
/// Destruction of the world object implicity deletes all IG objects in
/// the server, clears the operation queue
WorldRouter::~WorldRouter()
{
{
debug(std::cout << "Flushing world with " << m_eobjects.size()
<< " entities" << std::endl << std::flush;);
}
EntityDict::const_iterator Jend = m_eobjects.end();
for (EntityDict::const_iterator J = m_eobjects.begin(); J != Jend; ++J) {
J->second->decRef();
}
// This should be deleted here rather than in the base class because
// we created it, and BaseWorld should not even know what it is.
m_gameWorld.decRef();
}
/// \brief Add an operation to the ordered op queue.
///
/// Any time adjustment required is made to the operation, and it
/// is added to the apropriate place in the chronologically ordered
/// queue. The From attribute of the operation is set to the id of
/// the entity that is responsible for adding the operation to the
/// queue.
void WorldRouter::addOperationToQueue(const Operation & op, Entity & ent)
{
assert(op.isValid());
assert(op->getFrom() != "cheat");
op->setFrom(ent.getId());
if (!op->hasAttrFlag(Atlas::Objects::Operation::FUTURE_SECONDS_FLAG)) {
op->setSeconds(m_realTime);
m_immediateQueue.push_back(OpQueEntry(op, ent));
return;
}
double t = m_realTime + op->getFutureSeconds();
op->setSeconds(t);
op->setFutureSeconds(0.);
OpQueue::iterator I = m_operationQueue.begin();
OpQueue::iterator Iend = m_operationQueue.end();
for (; I != Iend && (*I).op->getSeconds() <= t; ++I);
m_operationQueue.insert(I, OpQueEntry(op, ent));
}
/// \brief Get the next due operation from the queue.
///
/// If the operation at the end of the queue is now due, return it.
/// This function is now unused, and has become obsolete now there are
/// two queues. If this function is needed again, it will need to be
/// recoded. See idle for sample code that checks for the next due operation.
/// @return a pointer to the operation due for dispatch, or 0 if none
/// is due.
Operation WorldRouter::getOperationFromQueue()
{
OpQueue::const_iterator I = m_operationQueue.begin();
if (I == m_operationQueue.end() || (*I)->getSeconds() > m_realTime) {
return NULL;
}
debug(std::cout << "pulled op off queue" << std::endl << std::flush;);
const Operation & op = (**I);
m_operationQueue.pop_front();
return op;
}
/// \brief Provide an adjusted height for the given entity.
///
/// If the position has a parent which has an associated geometry
/// which define its childrens position, e.g terrain or a floor,
/// calculate what the Z coord, being the height of the entity.
/// This function recurses through the parents until it finds
/// A parent which defines the height.
/// @return the modified Z coord of the position.
float WorldRouter::constrainHeight(LocatedEntity * parent,
const Point3D & pos,
const std::string & mode)
{
assert(parent != 0);
World * wrld = dynamic_cast<World*>(parent);
if (wrld != 0) {
float h;
h = wrld->getHeight(pos.x(), pos.y());
if (mode == "fixed") {
h = pos.z();
} else if (mode == "floating") {
h = 0;
} else if (mode == "swimming") {
h = std::max(h, std::min(0.f, pos.z()));
} else if (mode == "relative") {
h = h + pos.z();
}
debug(std::cout << "Fix height " << pos.z() << " to " << h
<< std::endl << std::flush;);
return h;
} else {
static const Quaternion identity(Quaternion().identity());
assert(parent->m_location.m_loc != 0);
const Point3D & ppos = parent->m_location.pos();
debug(std::cout << "parent " << parent->getId() << " of type "
<< parent->getType() << " pos " << ppos.z()
<< " my pos " << pos.z()
<< std::endl << std::flush;);
float h;
const Quaternion & parent_orientation = parent->m_location.orientation().isValid() ? parent->m_location.orientation() : identity;
h = constrainHeight(parent->m_location.m_loc,
pos.toParentCoords(parent->m_location.pos(),
parent_orientation),
mode) - ppos.z();
debug(std::cout << "Correcting height from " << pos.z() << " to " << h
<< std::endl << std::flush;);
return h;
}
}
/// \brief Add a new entity to the world.
///
/// Adds a new entity to the lists maintained by the WorldRouter.
/// Verify that the entity has a valid location, setting to
/// the default spawn area if necessary. Handle inserting the
/// entity into the loc/contains tree maintained by the Entity
/// class. Send a Setup op to the entity.
Entity * WorldRouter::addEntity(Entity * ent, bool setup)
{
debug(std::cout << "WorldRouter::addEntity(Entity *)" << std::endl
<< std::flush;);
assert(ent->getIntId() != 0);
m_eobjects[ent->getIntId()] = ent;
assert(ent->m_location.isValid());
if (!ent->m_location.isValid()) {
log(ERROR, "Entity added to world with invalid location!");
debug(std::cout << "set loc " << &m_gameWorld << std::endl
<< std::flush;);
ent->m_location.m_loc = &m_gameWorld;
ent->m_location.m_pos = Point3D(uniform(-8,8), uniform(-8,8), 0);
debug(std::cout << "loc set with loc " << ent->m_location.m_loc->getId()
<< std::endl << std::flush;);
}
ent->m_location.update(getTime());
// FIXME
std::string mode;
if (ent->hasAttr("mode")) {
Element mode_attr;
ent->getAttr("mode", mode_attr);
if (mode_attr.isString()) {
mode = mode_attr.String();
if (mode == "relative") {
ent->setAttr("mode", "fixed");
}
} else {
log(ERROR, String::compose("Mode on entity is a %1 in "
"WorldRouter::addEntity",
Element::typeName(mode_attr.getType())));
}
}
ent->m_location.m_pos.z() = constrainHeight(ent->m_location.m_loc,
ent->m_location.pos(), mode);
bool cont_change = ent->m_location.m_loc->m_contains.empty();
ent->m_location.m_loc->m_contains.insert(ent);
ent->m_location.m_loc->incRef();
if (cont_change) {
Entity * loc = dynamic_cast<Entity *>(ent->m_location.m_loc);
assert(loc != 0);
loc->m_update_flags |= a_cont;
loc->updated.emit();
}
debug(std::cout << "Entity loc " << ent->m_location << std::endl
<< std::flush;);
if (setup) {
Setup s;
s->setTo(ent->getId());
s->setFutureSeconds(-0.1);
message(s, m_gameWorld);
Anonymous arg;
Appearance app;
arg->setId(ent->getId());
arg->setStamp(ent->getSeq());
app->setArgs1(arg);
message(app, *ent);
}
return ent;
}
/// \brief Create a new entity and add to the world.
///
/// Construct a new entity using the entity description provided,
/// and pass it to addEntity().
/// @return a pointer to the new entity.
Entity * WorldRouter::addNewEntity(const std::string & typestr,
const RootEntity & attrs)
{
debug(std::cout << "WorldRouter::addNewEntity(\"" << typestr << "\", attrs)"
<< std::endl << std::flush;);
std::string id;
long intId = newId(id);
if (intId < 0) {
log(ERROR, "Unable to get ID for new Entity");
return 0;
}
Entity * ent = EntityFactory::instance()->newEntity(id, intId, typestr, attrs);
if (ent == 0) {
log(ERROR, String::compose("Attempt to create an entity of type \"%1\" "
"but type is unknown or forbidden",
typestr));
return 0;
}
return addEntity(ent);
}
/// \brief Create a new task
///
/// Construct a new task linked to the Character provided.
/// @param name the name of the task type to be instantiated
/// @param owner the character who will own the task
/// @return a pointer to the new task
Task * WorldRouter::newTask(const std::string & name, Character & owner)
{
Task * task = EntityFactory::instance()->newTask(name, owner);
if (task == 0) {
log(ERROR, String::compose("Attempt to create a task of type \"%1\" "
"but type is unknown or forbidden", name));
}
return task;
}
/// \brief Activate a new task
///
/// Construct a task linked to the Character provided, activated by the
/// tool and operation class given.
/// @param tool the type of tool activating the task
/// @param op the type of operation acitivating the task
/// @param target the entity that the task uses as its target
/// @param owner the character who will own the task
/// @return a pointer to the new task
Task * WorldRouter::activateTask(const std::string & tool,
const std::string & op,
const std::string & target,
Character & owner)
{
return EntityFactory::instance()->activateTask(tool, op, target, owner);
}
/// \brief Remove an entity from the world.
///
/// Remove an entity from the various lists in which it is stored.
/// The entity is removed from the LOC/CONTAINS tree, and the
/// reference held by the world is decremented. There may still be
/// a reference held by an operation in the queue from the removed
/// entity.
void WorldRouter::delEntity(Entity * ent)
{
if (ent == &m_gameWorld) {
log(WARNING, "Attempt to delete game world");
return;
}
assert(ent->getIntId() != 0);
m_perceptives.erase(ent);
m_eobjects.erase(ent->getIntId());
ent->destroy();
ent->decRef();
}
/// \brief Pass an operation to the World.
///
/// Pass an operation to addOperationToQueue()
/// so it gets added to the queue for dispatch.
void WorldRouter::message(const Operation & op, Entity & ent)
{
addOperationToQueue(op, ent);
debug(std::cout << "WorldRouter::message {"
<< op->getParents().front() << ":"
<< op->getFrom() << ":" << op->getTo() << "}" << std::endl
<< std::flush;);
}
/// \brief Determine the broadcast list to be used to broadcast an operation.
///
/// Check the type of operation, and work out which list of entities
/// it should be broadcast to. This will be perceptives in case
/// a perception operation, or all entities in any other case.
/// This should probably go, as there is essentially no sane reason
/// for broadcasting a random op to all entities.
/// @return a reference to the list of entities to be used for braodcast.
bool WorldRouter::broadcastPerception(const Operation & op) const
{
int op_class = op->getClassNo();
if (op_class == Atlas::Objects::Operation::SIGHT_NO ||
op_class == Atlas::Objects::Operation::SOUND_NO ||
op_class == Atlas::Objects::Operation::APPEARANCE_NO ||
op_class == Atlas::Objects::Operation::DISAPPEARANCE_NO) {
return true;
}
log(WARNING, String::compose("Broadcasting %1 op from %2",
op->getParents().front(),
op->getFrom()));
return false;
}
/// \brief Deliver an operation to its target.
///
/// Pass the operation to the target entity. The resulting operations
/// have their ref numbers set, and are added to the queue for
/// dispatch.
void WorldRouter::deliverTo(const Operation & op, Entity & ent)
{
OpVector res;
ent.operation(op, res);
OpVector::const_iterator Iend = res.end();
for(OpVector::const_iterator I = res.begin(); I != Iend; ++I) {
if (op->getFrom() == (*I)->getTo()) {
if (!op->isDefaultSerialno() && (*I)->isDefaultRefno()) {
(*I)->setRefno(op->getSerialno());
}
}
message(*I, ent);
}
}
/// \brief Main in-game operation dispatch function.
///
/// Operations are passed here when they are due for dispatch.
/// Determine the target of the operation and deliver it directly,
/// or broadcast if broadcast is required. This function implements
/// sight ranges for perception operations.
/// @param op operation to be dispatched to the world. This is non-const
/// so that broadcast ops can have their TO set correctly for each target.
/// @param from entity the operation to be dispatched was send from. Note
/// that it is possible that this entity has been destroyed, but it
/// should still have a valid location, so can be used for range
/// calculations.
void WorldRouter::operation(const Operation & op, Entity & from)
{
debug(std::cout << "WorldRouter::operation {"
<< op->getParents().front() << ":"
<< op->getFrom() << ":" << op->getTo() << "}"
<< std::endl << std::flush;);
assert(op->getFrom() == from.getId());
assert(!op->getParents().empty());
if (!op->isDefaultTo()) {
const std::string & to = op->getTo();
assert(!to.empty());
Entity * to_entity = 0;
if (to == from.getId()) {
if (from.isDestroyed()) {
// Entity no longer exists
return;
}
to_entity = &from;
} else {
to_entity = getEntity(to);
if (to_entity == 0) {
debug(std::cerr << "WARNING: Op to=\"" << to << "\""
<< " does not exist"
<< std::endl << std::flush;);
return;
}
}
assert(to_entity != 0);
deliverTo(op, *to_entity);
if (op->getClassNo() == Atlas::Objects::Operation::DELETE_NO) {
delEntity(to_entity);
}
} else if (broadcastPerception(op)) {
// Where broadcasts go depends on type of op
float fromSquSize = from.m_location.squareBoxSize();
EntitySet::const_iterator I = m_perceptives.begin();
EntitySet::const_iterator Iend = m_perceptives.end();
for (; I != Iend; ++I) {
// Calculate square distance to target
float dist = squareDistance(from.m_location, (*I)->m_location);
float view_factor = fromSquSize / dist;
if (view_factor < consts::square_sight_factor) {
debug(std::cout << "Op from " << from.getId()
<< " cannot be seen by " << (*I)->getId()
<< std::endl << std::flush;);
continue;
}
op->setTo((*I)->getId());
deliverTo(op, **I);
}
} else {
EntityDict::const_iterator I = m_eobjects.begin();
EntityDict::const_iterator Iend = m_eobjects.end();
for (; I != Iend; ++I) {
op->setTo(I->second->getId());
deliverTo(op, *I->second);
}
}
}
/// Add entity provided to the list of perceptive entities.
/// Look up the entity with the id provided, and add a pointer
/// to the entity to the set of perceptive entities. This method is
/// called when key events occur that indicate that the entity in
/// question can receive broadcast perception operations.
void WorldRouter::addPerceptive(Entity * perceptive)
{
debug(std::cout << "WorldRouter::addPerceptive" << std::endl << std::flush;);
m_perceptives.insert(perceptive);
}
/// Main world loop function.
/// This function is called whenever the communications code is idle.
/// It updates the in-game time, and dispatches operations that are
/// now due for dispatch. The number of operations dispatched is limited
/// to 10 to ensure that client communications are always handled in a timely
/// manner. If the maximum number of operations are dispatched, the return
/// value indicates that this is the case, and the communications code
/// will call this function again as soon as possible rather than sleeping.
/// This ensures that the maximum possible number of operations are dispatched
/// without becoming unresponsive to client communications traffic.
/// @param sec world time seconds component
/// @param usec world time microseconds component
bool WorldRouter::idle(int sec, int usec)
{
updateTime(sec, usec);
unsigned int op_count = 0;
OpQueue::iterator I = m_operationQueue.begin();
OpQueue::iterator Iend = m_operationQueue.end();
while (++op_count < 10 && I != Iend && (*I)->getSeconds() <= m_realTime) {
assert(I != m_operationQueue.end());
OpQueEntry & oqe = *I;
Dispatching.emit(oqe.op);
try {
operation(oqe.op, oqe.from);
}
catch (...) {
log(ERROR, String::compose("Exception caught in world.idle() "
"thrown while processing operation "
"sent to \"%1\" from \"%2\"",
oqe->getTo(), oqe->getFrom()));
}
m_operationQueue.erase(I);
I = m_operationQueue.begin();
}
I = m_immediateQueue.begin();
Iend = m_immediateQueue.end();
while (++op_count < 10 && I != Iend) {
assert(I != m_immediateQueue.end());
OpQueEntry & oqe = *I;
Dispatching.emit(oqe.op);
try {
operation(oqe.op, oqe.from);
}
catch (...) {
log(ERROR, String::compose("Exception caught in world.idle() "
"thrown while processing operation "
"sent to \"%1\" from \"%2\"",
oqe->getTo(), oqe->getFrom()));
}
m_immediateQueue.erase(I);
I = m_immediateQueue.begin();
}
// If we have processed the maximum number for this call, return true
// to tell the server not to sleep when polling clients. This ensures
// that we keep processing ops at a the maximum rate without leaving
// clients unattended.
return (op_count >= 10);
}
/// Find an entity of the given name. This is provided to allow administrators
/// to perform certain admin tasks. It finds and returns the first instance
/// with the name provided in the game world.
/// @param name string specifying name of the instance required.
/// @return a pointer to an entity with the type required, or zero if an
/// instance with this name was not found.
Entity * WorldRouter::findByName(const std::string & name)
{
Element name_attr;
EntityDict::const_iterator Iend = m_eobjects.end();
for (EntityDict::const_iterator I = m_eobjects.begin(); I != Iend; ++I) {
if (I->second->getAttr("name", name_attr)) {
if (name_attr == name) {
return I->second;
}
}
}
return NULL;
}
/// Find an entity of the given type. This is provided to allow administrators
/// to perform certain admin tasks. It finds and returns the first instance
/// of the type provided in the game world.
/// @param type string specifying the class name of the instance required.
/// @return a pointer to an entity of the type required, or zero if no
/// instance was found.
Entity * WorldRouter::findByType(const std::string & type)
{
EntityDict::const_iterator Iend = m_eobjects.end();
for(EntityDict::const_iterator I = m_eobjects.begin(); I != Iend; ++I) {
if (I->second->getType()->name() == type) {
return I->second;
}
}
return NULL;
}
syntax highlighted by Code2HTML, v. 0.9.1