cyphesis/rulesets/Creator.cpp
Erik Ogenvik bb19956c8d Install class default attributes for native types.
Though we strive towards moving all of the specific functionality
into attributes and removing the native subtypes altogether.

Note that we default to all plants being planted by default.
2016-12-15 18:27:42 +01:00

302 lines
11 KiB
C++

// Cyphesis Online RPG Server and AI Engine
// Copyright (C) 2000,2001 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
#include "Creator.h"
#include "BaseMind.h"
#include "common/BaseWorld.h"
#include "common/log.h"
#include "common/debug.h"
#include "common/serialno.h"
#include "common/compose.hpp"
#include "common/Setup.h"
#include "common/Tick.h"
#include "common/Unseen.h"
#include "common/Update.h"
#include "common/custom.h"
#include <Atlas/Objects/Operation.h>
#include <Atlas/Objects/Anonymous.h>
using Atlas::Objects::Root;
using Atlas::Objects::Operation::Delete;
using Atlas::Objects::Operation::Sight;
using Atlas::Objects::Operation::Unseen;
using Atlas::Objects::Entity::Anonymous;
static const bool debug_flag = false;
Creator::Creator(const std::string & id, long intId) :
Character(id, intId)
{
debug( std::cout << "Creator::Creator" << std::endl << std::flush;);
//Creators are always perceptive.
setFlags(entity_perceptive);
}
Creator::~Creator()
{
}
void Creator::operation(const Operation & op, OpVector & res)
{
debug( std::cout << "Creator::operation" << std::endl << std::flush;);
// FIXME: Switch to using callOperation(), some more op handlers would
// need to be implemented. Some might need to be blocked
// to prevent anyone from messing with us, like SetOperation().
auto op_no = op->getClassNo();
switch(op_no) {
case Atlas::Objects::Operation::CREATE_NO:
CreateOperation(op, res);
break;
case Atlas::Objects::Operation::LOOK_NO:
LookOperation(op, res);
break;
case Atlas::Objects::Operation::MOVE_NO:
MoveOperation(op, res);
break;
case Atlas::Objects::Operation::DELETE_NO:
DeleteOperation(op, res);
break;
case Atlas::Objects::Operation::TALK_NO:
TalkOperation(op, res);
break;
default:
if (op_no == Atlas::Objects::Operation::TICK_NO) {
TickOperation(op, res);
} else if (op_no == Atlas::Objects::Operation::RELAY_NO) {
RelayOperation(op, res);
return;
} else if (op_no == Atlas::Objects::Operation::UPDATE_NO) {
//These kind of operations are generated from the world, and
//thus have refno's generated by the simulation. To prevent
//these from colliding with client serialno/refno we need to
//clear the refno before sending to the client.
UpdateOperation(op, res);
op->removeAttr(Atlas::Objects::Operation::REFNO_ATTR);
return;
}
break;
}
//TODO: figure out how to better handle "refno". The issue now is that operations originating from
//the mind are intermingled with those generated from the simulation, resulting in refno collisions.
sendMind(op, res);
}
void Creator::externalOperation(const Operation & op, Link &)
{
// If an admin connection specifies a TO on the op, we treat
// it specially, and make sure it goes direct, otherwise
// we handle it like a normal character.
debug( std::cout << "Creator::externalOperation("
<< op->getParents().front() << ")" << std::endl
<< std::flush;);
if (op->isDefaultTo()) {
debug( std::cout << "Creator handling op normally" << std::endl
<< std::flush;);
filterExternalOperation(op);
} else if (op->getTo() == getId() && op->isDefaultFutureSeconds()) {
debug( std::cout << "Creator handling op " << std::endl << std::flush;);
OpVector lres;
OpVector eres;
callOperation(op, lres);
OpVector::const_iterator Iend = lres.end();
for (OpVector::const_iterator I = lres.begin(); I != Iend; ++I) {
if (!op->isDefaultSerialno()) {
(*I)->setRefno(op->getSerialno());
}
sendMind(*I, eres);
sendWorld(*I);
// Don't delete lres as it has gone into World's queue
// World will deal with it.
}
} else {
LocatedEntity * to = BaseWorld::instance().getEntity(op->getTo());
if (to != 0) {
//If there's a serial number, we expect a response, and we should relay the operation
if (op->isDefaultSerialno()) {
// Make it appear like it came from target itself;
to->sendWorld(op);
//Send a sight of the operation to the mind
//NOTE: Is this really a good idea? There's no serial number set, so the op
//will be handled by the client as a valid op, even though it might not in reality
//always succeed (depending on game rules etc.).
Sight sight;
sight->setArgs1(op);
sight->setTo(getId());
OpVector res;
sendMind(sight, res);
} else {
relayOperationTo(op, *to);
}
} else {
log(ERROR, String::compose("Creator operation from client "
"is to unknown ID \"%1\"",
op->getTo()));
Anonymous unseen_arg;
unseen_arg->setId(op->getTo());
Unseen unseen;
unseen->setArgs1(unseen_arg);
unseen->setTo(getId());
if (!op->isDefaultSerialno()) {
unseen->setRefno(op->getSerialno());
}
OpVector res;
sendMind(unseen, res);
// We are not interested in anything the external mind might return
}
}
}
void Creator::relayOperationTo(const Operation & op, LocatedEntity& to)
{
//Make the op appear to come from the destination entity.
op->setFrom(to.getId());
Relay relay;
relay.serialno = op->getSerialno();
relay.destination = to.getId();
long int serialNo = ++s_serialNumberNext;
Atlas::Objects::Operation::Generic relayOp;
relayOp->setType("relay", Atlas::Objects::Operation::RELAY_NO);
relayOp->setTo(to.getId());
relayOp->setSerialno(serialNo);
relayOp->setArgs1(op);
m_relays.insert(std::make_pair(serialNo, relay));
sendWorld(relayOp);
//Also send a future Relay op to ourselves to make sure that the registered relay in m_relays
//is removed in the case that we don't get any response.
Atlas::Objects::Operation::Generic pruneOp;
pruneOp->setType("relay", Atlas::Objects::Operation::RELAY_NO);
pruneOp->setTo(getId());
pruneOp->setFrom(getId());
pruneOp->setRefno(serialNo);
pruneOp->setFutureSeconds(5);
sendWorld(pruneOp);
}
void Creator::mindLookOperation(const Operation & op, OpVector & res)
{
// This overridden version allows the Creator to search the world for
// entities by type or by name.
//FIXME: This scheme makes for some strange operations, since the sight check will be
//ignored if searching by type or name, but not by id.
//I.e. there's no good way for an admin client to get info on an entity bypassing the sight check if the id the only known data.
//We need to implement some mechanism which allows an admin to properly query all entities using the id of them.
//We might just override the lookup here. Problem with that is however that it would interfere with the normal creator
//entity interaction with the world.
debug(std::cout << "Got look up from prived mind from [" << op->getFrom()
<< "] to [" << op->getTo() << "]" << std::endl << std::flush;);
m_flags |= entity_perceptive;
const std::vector<Root> & args = op->getArgs();
if (args.empty()) {
op->setTo(BaseWorld::instance().getDefaultLocation().getId());
} else {
const Root & arg = args.front();
if (arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
op->setTo(arg->getId());
} else if (arg->hasAttrFlag(Atlas::Objects::NAME_FLAG)) {
// Search by name
LocatedEntity * e = BaseWorld::instance().findByName(arg->getName());
if (e != nullptr) {
//This will override the normal sights checking code, which is ok since we're an administrator
Sight s;
Anonymous sarg;
e->addToEntity(sarg);
s->setArgs1(sarg);
s->setTo(getId());
if (!op->isDefaultSerialno()) {
s->setRefno(op->getSerialno());
}
sendMind(s, res);
} else {
Unseen u;
u->setTo(getId());
u->setArgs1(arg);
if (!op->isDefaultSerialno()) {
u->setRefno(op->getSerialno());
}
sendMind(u, res);
return;
}
} else if (arg->hasAttrFlag(Atlas::Objects::PARENTS_FLAG)) {
// Search by name
if (!arg->getParents().empty()) {
LocatedEntity * e = BaseWorld::instance().findByType(arg->getParents().front());
if (e != nullptr) {
//This will override the normal sights checking code, which is ok since we're an administrator
Sight s;
Anonymous sarg;
e->addToEntity(sarg);
s->setArgs1(sarg);
s->setTo(getId());
if (!op->isDefaultSerialno()) {
s->setRefno(op->getSerialno());
}
sendMind(s, res);
return;
} else {
Unseen u;
u->setTo(getId());
u->setArgs1(arg);
if (!op->isDefaultSerialno()) {
u->setRefno(op->getSerialno());
}
sendMind(u, res);
return;
}
}
}
// FIXME Need to ensure that a broadcast Look insn't sent, and
// an Unseen is sent back, in once place if no match is found.
// Probably most easlier done by checking TO on op by flag.
}
debug( std::cout <<" now to ["<<op->getTo()<<"]"<<std::endl<<std::flush;);
res.push_back(op);
}
void Creator::mindSetOperation(const Operation & op, OpVector & res)
{
const std::vector<Root> & args = op->getArgs();
if (args.empty()) {
log(ERROR, "Creator::mindSetOperation: set op has no argument");
return;
}
const Root & arg = args.front();
if (arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
op->setTo(arg->getId());
} else {
op->setTo(getId());
}
res.push_back(op);
}