cyphesis/rules/simulation/MindsProperty.cpp
Erik Ogenvik e85f0831b2 Restructure rule classes.
Classes under "rulesets" have been moved to "rules", and split up into
futher subdirectories matching their library. As a result we can now
better separate the python bindings, so that things that belongs to the
simulation are separeted from things that belongs to the ai.
2018-10-31 21:38:35 +01:00

969 lines
34 KiB
C++

/*
Copyright (C) 2018 Erik Ogenvik
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "MindsProperty.h"
#include "rules/LocatedEntity.h"
#include "TransientProperty.h"
#include "UsagesProperty.h"
#include "rules/simulation/BaseWorld.h"
#include "common/Router.h"
#include "common/debug.h"
#include "common/op_switch.h"
#include "common/custom.h"
#include "common/operations/Actuate.h"
#include "common/operations/Attack.h"
#include "common/operations/Eat.h"
#include "common/operations/Setup.h"
#include "common/operations/Tick.h"
#include "common/operations/Unseen.h"
#include "common/operations/Update.h"
#include "common/operations/Think.h"
#include "common/operations/Thought.h"
#include <Atlas/Objects/RootEntity.h>
#include <Atlas/Objects/Anonymous.h>
#include <Atlas/Objects/Operation.h>
#include <wfmath/atlasconv.h>
#include <iostream>
using Atlas::Message::Element;
using Atlas::Message::ListType;
using Atlas::Message::MapType;
using Atlas::Objects::Root;
using Atlas::Objects::Operation::Info;
using Atlas::Objects::Operation::Set;
using Atlas::Objects::Operation::Sight;
using Atlas::Objects::Operation::Sound;
using Atlas::Objects::Operation::Tick;
using Atlas::Objects::Operation::Move;
using Atlas::Objects::Operation::Action;
using Atlas::Objects::Operation::Unseen;
using Atlas::Objects::Operation::Update;
using Atlas::Objects::Operation::Wield;
using Atlas::Objects::Entity::Anonymous;
using Atlas::Objects::Entity::RootEntity;
using Atlas::Objects::smart_dynamic_cast;
static const bool debug_flag = false;
MindsProperty::MindsProperty()
: PropertyBase(per_ephem)
{
}
MindsProperty::~MindsProperty() = default;
void MindsProperty::set(const Atlas::Message::Element& val)
{
//Don't allow setting.
}
int MindsProperty::get(Atlas::Message::Element& val) const
{
Atlas::Message::ListType list;
for (auto& mind : m_data) {
list.push_back(mind->getId());
}
val = list;
return 0;
}
void MindsProperty::add(const std::string& val, Atlas::Message::MapType& map) const
{
Atlas::Message::ListType list;
for (auto& mind : m_data) {
list.push_back(mind->getId());
}
map[val] = list;
}
void MindsProperty::add(const std::string& val, const Atlas::Objects::Entity::RootEntity& ent) const
{
Atlas::Message::ListType list;
for (auto& mind : m_data) {
list.push_back(mind->getId());
}
ent->setAttr(val, list);
}
MindsProperty* MindsProperty::copy() const
{
//Don't copy any values
return new MindsProperty();
}
void MindsProperty::install(LocatedEntity* entity, const std::string& name)
{
entity->addListener(this);
}
void MindsProperty::remove(LocatedEntity* entity, const std::string& name)
{
entity->removeListener(this);
}
HandlerResult MindsProperty::operation(LocatedEntity* ent, const Operation& op, OpVector& res)
{
if (op->getClassNo() == Atlas::Objects::Operation::THOUGHT_NO) {
return ThoughtOperation(ent, op, res);
} else if (op->getClassNo() == Atlas::Objects::Operation::RELAY_NO) {
return RelayOperation(ent, op, res);
} else {
if (world2mind(op)) {
debug_print("MindsProperty::operation(" << op->getParent() << ") passed to mind");
OpVector mres;
sendToMinds(op, mres);
for (auto& resOp: mres) {
//Wrap any returning ops in thoughts and send them to our entity
Atlas::Objects::Operation::Thought thought;
thought->setArgs1(resOp);
thought->setTo(ent->getId());
ent->sendWorld(thought);
}
}
}
return OPERATION_HANDLED;
}
void MindsProperty::sendToMinds(const Operation& op, OpVector& res) const
{
for (auto& mind : m_data) {
mind->operation(op, res);
}
}
HandlerResult MindsProperty::RelayOperation(LocatedEntity* ent, const Operation& op, OpVector& res)
{
if (op->isDefaultTo()) {
ent->error(op, "A relay op must have a 'to'.", res, ent->getId());
return OPERATION_BLOCKED;
}
if (op->isDefaultFrom()) {
ent->error(op, "A relay op must have a 'from'.", res, ent->getId());
return OPERATION_BLOCKED;
}
if (op->isDefaultId()) {
ent->error(op, "A relay op must have an 'id'.", res, ent->getId());
return OPERATION_BLOCKED;
}
//Get the mind to which the relay is directed.
auto mindId = op->getId();
for (auto& mind : m_data) {
if (mind->getId() == mindId) {
OpVector mres;
mind->operation(op, res);
for (auto& resOp: mres) {
//Wrap any returning ops in thoughts and send them to our entity
Atlas::Objects::Operation::Thought thought;
thought->setArgs1(resOp);
thought->setTo(ent->getId());
res.push_back(std::move(resOp));
}
return OPERATION_BLOCKED;
}
}
return OPERATION_BLOCKED;
}
HandlerResult MindsProperty::ThoughtOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
for (auto& arg : op->getArgs()) {
auto innerOp = smart_dynamic_cast<Operation>(arg);
if (innerOp) {
OpVector mres;
mind2body(ent, innerOp, mres);
// If the original op had a serial no, we assume the first consequence
// of that is effectively the same operation.
// FIXME Can this be guaranteed by the mind2body phase?
if (!op->isDefaultSerialno()) {
if (!mres.empty() && mres.front()->isDefaultSerialno()) {
mres.front()->setSerialno(op->getSerialno());
}
}
for (auto& resOp : mres) {
resOp->setFrom(ent->getId());
res.push_back(std::move(resOp));
}
}
}
return OPERATION_BLOCKED;
}
/// \brief Filter an Actuate operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindActuateOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
debug(std::cout << "Got Actuate op from mind" << std::endl << std::flush;);
//Distance filtering etc. happens in ActuateOperation
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter a Setup operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindSetupOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
Anonymous setup_arg;
setup_arg->setName("mind");
op->setArgs1(setup_arg);
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter a Use operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindUseOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
debug(std::cout << "Got Use op from mind" << std::endl << std::flush;);
//Make sure that the first contained arg is another Use operation,
// which is then sent to the actual tool.
auto& args = op->getArgs();
if (args.empty()) {
log(ERROR, "mindUseOperation: use op has no arguments. " + ent->describeEntity());
return;
}
//TODO: should we perhaps check that this only can be Action ops?
auto innerOp = smart_dynamic_cast<Atlas::Objects::Operation::RootOperation>(args.front());
if (!innerOp) {
log(ERROR, "mindUseOperation: Second arg is not an operation. " + ent->describeEntity());
return;
}
auto& innerArgs = innerOp->getArgs();
if (innerArgs.empty()) {
log(ERROR, "mindUseOperation: inner use op has no arguments. " + ent->describeEntity());
return;
}
auto toolEnt = smart_dynamic_cast<Atlas::Objects::Entity::RootEntity>(innerArgs.front());
if (!toolEnt) {
log(ERROR, "mindUseOperation: First inner arg is not an entity. " + ent->describeEntity());
return;
}
if (!toolEnt->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
log(ERROR, "mindMoveOperation: First inner arg has no ID. " + ent->describeEntity());
return;
}
Atlas::Objects::Operation::Use useOp;
useOp->setTo(toolEnt->getId());
useOp->setFrom(ent->getId());
useOp->setArgs1(innerOp);
res.push_back(useOp);
}
/// \brief Filter a Update operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindUpdateOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
}
/// \brief Filter a Wield operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindWieldOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
debug(std::cout << "Got Wield op from mind" << std::endl << std::flush;);
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter a Tick operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindTickOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
}
/// \brief Filter a Move operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindMoveOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
debug(std::cout << "MindsProperty::mind_move_op" << std::endl << std::flush;);
const std::vector<Root>& args = op->getArgs();
if (args.empty()) {
log(ERROR, "mindMoveOperation: move op has no argument. " + ent->describeEntity());
return;
}
const RootEntity arg = smart_dynamic_cast<RootEntity>(args.front());
if (!arg.isValid()) {
log(ERROR, "mindMoveOperation: Arg is not an entity. " + ent->describeEntity());
return;
}
if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
log(ERROR, "mindMoveOperation: Arg has no ID. " + ent->describeEntity());
return;
}
// Element stamina_attr;
// if (getAttrType(STAMINA, stamina_attr, Element::TYPE_FLOAT) == 0) {
// if (stamina_attr.Float() <= 0.f) {
// // Character is immobilised.
// return;
// }
// }
const std::string& other_id = arg->getId();
if (other_id != ent->getId()) {
debug(std::cout << "Moving something else. " << other_id << std::endl << std::flush;);
auto other = BaseWorld::instance().getEntity(other_id);
if (!other) {
Unseen u;
Anonymous unseen_arg;
unseen_arg->setId(other_id);
u->setArgs1(unseen_arg);
u->setTo(ent->getId());
res.push_back(u);
return;
}
//Check that we actually can reach the other entity.
if (ent->canReach({other, {}})) {
//Now also check that we can reach wherever we're trying to move the entity.
auto targetLoc = other->m_location.m_parent;
//Only allow some things to be set when moving another entity.
RootEntity newArgs1;
//We've already checked that the id exists
newArgs1->setId(arg->getId());
if (!arg->isDefaultLoc()) {
newArgs1->setLoc(arg->getLoc());
targetLoc = BaseWorld::instance().getEntity(arg->getLoc());
}
if (!targetLoc) {
ent->clientError(op, "Target parent entity doesn't exist.", res, op->getFrom());
return;
}
WFMath::Point<3> targetPos;
if (!arg->isDefaultPos()) {
newArgs1->setPos(arg->getPos());
targetPos.fromAtlas(arg->getPosAsList());
}
//Check that we can reach the edge of the entity if it's placed in its new location.
if (!ent->canReach({targetLoc, targetPos}, other->m_location.radius())) {
ent->clientError(op, "Target is too far away.", res, op->getFrom());
return;
}
if (arg->hasAttr("orientation")) {
newArgs1->setAttr("orientation", arg->getAttr("orientation"));
}
//Replace first arg with our sanitized arg.
op->setArgs1(newArgs1);
op->setFrom(ent->getId());
op->setTo(other_id);
res.push_back(op);
} else {
ent->clientError(op, "Entity is too far away.", res, op->getFrom());
}
//TODO: add checks for the things that we can reach, and that we can move.
//Probably involve the domain in this.
// Element mass;
// if (other->getAttr(MASS, mass) != 0 || !mass.isFloat()) {
// // FIXME Check against strength
// // || mass.Float() > m_statistics.get("strength"));
// debug(std::cout << "We can't move this. Just too heavy" << std::endl << std::flush
// ;);
// //TODO: send op back to the mind informing it that it was too heavy to move.
// return;
// }
return;
}
std::string new_loc;
if (arg->hasAttrFlag(Atlas::Objects::Entity::LOC_FLAG)) {
new_loc = arg->getLoc();
} else {
debug(std::cout << "Parent not set" << std::endl << std::flush;);
}
Point3D new_pos;
Vector3D new_propel;
Quaternion new_orientation;
try {
//If there's a position specified, that takes precedence (i.e. move as quickly straight to the position).
//Note that we still look for a propel attribute, since that can be used to determine the speed of movement.
if (arg->hasAttrFlag(Atlas::Objects::Entity::POS_FLAG)) {
fromStdVector(new_pos, arg->getPos());
debug(std::cout << "pos set to " << new_pos << std::endl << std::flush;);
}
//First look for the "propel" attribute; if that's not there look for the legacy "velocity" attribute.
//Note that we differ between "propel", which is how an entity propels itself forward, and "velocity"
//which is the resulting velocity of the entity, taking all other entities as well as gravity into consideration.
Element attr_propel;
if (arg->copyAttr("propel", attr_propel) == 0) {
try {
new_propel.fromAtlas(attr_propel);
} catch (...) {
//just ignore malformed data
}
} else {
if (arg->hasAttrFlag(Atlas::Objects::Entity::VELOCITY_FLAG)) {
fromStdVector(new_propel, arg->getVelocity());
debug(std::cout << "propel set to " << new_propel << std::endl << std::flush;);
}
}
Element orientation_attr;
if (arg->copyAttr("orientation", orientation_attr) == 0) {
new_orientation.fromAtlas(orientation_attr);
debug(std::cout << "ori set to " << new_orientation << std::endl << std::flush;);
if (!new_orientation.isValid()) {
log(ERROR, "Ignoring invalid orientation from client " + ent->describeEntity() + ".");
}
}
} catch (Atlas::Message::WrongTypeException&) {
log(ERROR, "EXCEPTION: mindMoveOperation: Malformed move operation. " + ent->describeEntity());
return;
} catch (...) {
log(ERROR, "EXCEPTION: mindMoveOperation: Unknown exception thrown. " + ent->describeEntity());
return;
}
debug(std::cout << ":" << new_loc << ":" << ent->m_location.m_parent->getId() << ":" << std::endl << std::flush;);
if (!new_loc.empty() && (new_loc != ent->m_location.m_parent->getId())) {
debug(std::cout << "Changing loc" << std::endl << std::flush;);
auto target_loc = BaseWorld::instance().getEntity(new_loc);
if (!target_loc) {
//TODO: what use case is this? Moving the entity to a null location?
Unseen u;
Anonymous unseen_arg;
unseen_arg->setId(new_loc);
u->setArgs1(unseen_arg);
u->setTo(ent->getId());
res.push_back(u);
return;
}
if (new_pos.isValid()) {
Location target(target_loc, new_pos);
Vector3D distance = distanceTo(ent->m_location, target);
assert(distance.isValid());
// Convert target into our current frame of reference.
new_pos = ent->m_location.pos() + distance;
} else {
log(WARNING, "mindMoveOperation: Argument changes LOC, but no POS specified. Not sure this makes any sense. " + ent->describeEntity());
}
}
// Movement within current loc. Work out the speed and stuff and
// use movement object to track movement.
Location ret_location = ent->m_location;
//If there's a position set, we'll use that to determine the propel value. However, we'll also check if there also was a propel value set,
//since the magnitude of that indicates the speed to use (otherwise we'll use full speed).
if (new_pos.isValid()) {
if (new_propel.isValid()) {
auto mag = new_propel.mag();
new_propel = new_pos - ent->m_location.pos();
new_propel.normalize();
new_propel *= mag;
} else {
new_propel = new_pos - ent->m_location.pos();
new_propel.normalize();
}
}
// Set up argument for operation
Anonymous move_arg;
move_arg->setId(ent->getId());
// Need to add the arguments to this op before we return it
// direction is already a unit vector
// if (new_pos.isValid()) {
// m_movement.setTarget(new_pos);
// debug(std::cout << "Target" << new_pos << std::endl << std::flush;);
// }
if (new_propel.isValid()) {
auto mag = new_propel.mag();
if (mag == 0) {
move_arg->setAttr("propel", new_propel.toAtlas());
} else {
//We don't allow the mind to set any speed greater than a normalized value.
if (mag > 1.0) {
new_propel.normalize();
}
move_arg->setAttr("propel", new_propel.toAtlas());
}
}
if (new_orientation.isValid()) {
move_arg->setAttr("orientation", new_orientation.toAtlas());
}
// Create move operation
Move moveOp;
moveOp->setTo(ent->getId());
moveOp->setSeconds(BaseWorld::instance().getTime());
moveOp->setArgs1(move_arg);
res.push_back(moveOp);
}
/// \brief Filter a Set operation coming from the mind
///
/// Currently any Set op is permitted. In the future this will be locked
/// down to only allow mutable things to be changed. For example, for
/// inventory items with no name can have their name set from the client.
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindSetOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
log(WARNING, "Set op from mind");
const std::vector<Root>& args = op->getArgs();
if (args.empty()) {
log(ERROR, "mindSetOperation: set op has no argument. " + ent->describeEntity());
return;
}
const Root& arg = args.front();
if (arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
op->setTo(arg->getId());
} else {
op->setTo(ent->getId());
}
res.push_back(op);
}
/// \brief Filter a Combine operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindCombineOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
debug(std::cout << "mindCombineOperation" << std::endl << std::flush;);
const std::vector<Root>& args = op->getArgs();
if (args.empty()) {
log(ERROR, "mindCombineOperation: combine op has no argument. " + ent->describeEntity());
return;
}
auto I = args.begin();
const Root& arg1 = *I;
op->setTo(arg1->getId());
auto Iend = args.end();
for (; I != Iend; ++I) {
const Root& arg = *I;
if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
ent->error(op, "MindsProperty::mindCombineOp No ID.", res, ent->getId());
return;
}
// FIXME Check item to be combined is in inventory
// and then also check stackable and the same type.
}
res.push_back(op);
}
/// \brief Filter a Create operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindCreateOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter a Delete operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindDeleteOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter a Divide operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindDivideOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
const std::vector<Root>& args = op->getArgs();
if (args.empty()) {
log(ERROR, "mindDivideOperation: op has no argument. " + ent->describeEntity());
return;
}
auto I = args.begin();
const Root& arg1 = *I;
if (!arg1->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
ent->error(op, "MindsProperty::mindDivideOp arg 1 has no ID.", res, ent->getId());
return;
}
// FIXME Check entity to be divided is in inventory
op->setTo(arg1->getId());
++I;
auto Iend = args.end();
for (; I != Iend; ++I) {
const Root& arg = *I;
if (arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
ent->error(op, "MindsProperty::mindDivideOp arg has ID.", res, ent->getId());
return;
}
// Check the same type?
}
res.push_back(op);
}
/// \brief Filter a Imaginary operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindImaginaryOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter a Talk operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindTalkOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
debug(std::cout << "MindsProperty::mindTalkOperation" << std::endl << std::flush;);
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter a Look operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindLookOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
debug(std::cout << "Got look up from mind from [" << op->getFrom() << "] to [" << op->getTo() << "]" << std::endl << std::flush;);
const std::vector<Root>& args = op->getArgs();
if (args.empty()) {
//If nothing is specified, send to parent, if available.
if (ent->m_location.m_parent) {
op->setTo(ent->m_location.m_parent->getId());
} else {
return;
}
} else {
const Root& arg = args.front();
if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
log(ERROR, ent->describeEntity() + " mindLookOperation: Op has no ID");
return;
}
op->setTo(arg->getId());
}
debug(std::cout << " now to [" << op->getTo() << "]" << std::endl << std::flush;);
res.push_back(op);
}
/// \brief Filter a Eat operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindEatOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
const std::vector<Root>& args = op->getArgs();
if (args.empty()) {
log(ERROR, ent->describeEntity() + " mindEatOperation: Op has no ARGS");
return;
}
const Root& arg = args.front();
if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
log(ERROR, ent->describeEntity() + " mindEatOperation: Arg has no ID");
return;
}
auto target = BaseWorld::instance().getEntity(arg->getId());
if (!target) {
log(NOTICE, ent->describeEntity() + " mindEatOperation: Target does not exist");
return;
}
if (!ent->canReach({target, {}})) {
ent->clientError(op, "Target is too far away.", res, op->getFrom());
return;
}
op->setTo(arg->getId());
res.push_back(op);
}
/// \brief Filter a GoalInfo operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindGoalInfoOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter a Touch operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindTouchOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
// Work out what is being touched.
const std::vector<Root>& args = op->getArgs();
if (args.empty()) {
log(ERROR, "mindTouchOperation: Op has no ARGS");
return;
}
auto arg = smart_dynamic_cast<Atlas::Objects::Entity::Anonymous>(args.front());
if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
log(ERROR, ent->describeEntity() + " mindTouchOperation: Op has no ID");
return;
}
WFMath::Point<3> pos;
if (arg->hasAttrFlag(Atlas::Objects::Entity::POS_FLAG)) {
pos.fromAtlas(arg->getPosAsList());
}
auto other = BaseWorld::instance().getEntity(arg->getId());
//Check that we actually can reach the other entity.
if (ent->canReach({std::move(other), pos})) {
// Pass the modified touch operation on to target.
op->setTo(arg->getId());
res.push_back(op);
// Send sight of touch
Sight s;
s->setArgs1(op);
res.push_back(s);
} else {
ent->clientError(op, "Entity is too far away.", res, op->getFrom());
}
}
/// \brief Filter any other operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindOtherOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
log(WARNING, String::compose("Passing '%1' op from mind through to world. %2", op->getParent(), ent->describeEntity()));
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter a Thought operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindThoughtOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter a Think operation coming from the mind
///
/// @param op The operation to be filtered.
/// @param res The filtered result is returned here.
void MindsProperty::mindThinkOperation(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
op->setTo(ent->getId());
res.push_back(op);
}
/// \brief Filter operations from the mind destined for the body.
///
/// Operations from the character's mind which is either an NPC mind,
/// or a remote client are passed in here for pre-processing and filtering
/// before they are valid to be processed as internal ops. The operation
/// may be modified and re-used so operations passed to this function have
/// their ownership passed in, and caller should not modify the operation,
/// make assumptions that it has not been modified after calling mind2body.
/// @param op The operation to be processed.
/// @param res The result of the operation is returned here.
void MindsProperty::mind2body(LocatedEntity* ent, const Operation& op, OpVector& res) const
{
debug_print("MindsProperty::mind2body(" << op->getParent() << ") " << ent->describeEntity());
if (!op->isDefaultTo()) {
log(ERROR, String::compose("Operation \"%1\" from mind with TO set. %2", op->getParent(), ent->describeEntity()));
return;
}
if (!op->isDefaultFutureSeconds() && op->getClassNo() != Atlas::Objects::Operation::TICK_NO) {
log(ERROR, String::compose("Operation \"%1\" from mind with "
"FUTURE_SECONDS set. %2", op->getParent(), ent->describeEntity()));
}
auto op_no = op->getClassNo();
switch (op_no) {
case Atlas::Objects::Operation::COMBINE_NO:
mindCombineOperation(ent, op, res);
break;
case Atlas::Objects::Operation::CREATE_NO:
mindCreateOperation(ent, op, res);
break;
case Atlas::Objects::Operation::DELETE_NO:
mindDeleteOperation(ent, op, res);
break;
case Atlas::Objects::Operation::DIVIDE_NO:
mindDivideOperation(ent, op, res);
break;
case Atlas::Objects::Operation::IMAGINARY_NO:
mindImaginaryOperation(ent, op, res);
break;
case Atlas::Objects::Operation::LOOK_NO:
mindLookOperation(ent, op, res);
break;
case Atlas::Objects::Operation::MOVE_NO:
mindMoveOperation(ent, op, res);
break;
case Atlas::Objects::Operation::SET_NO:
mindSetOperation(ent, op, res);
break;
case Atlas::Objects::Operation::TALK_NO:
mindTalkOperation(ent, op, res);
break;
case Atlas::Objects::Operation::TOUCH_NO:
mindTouchOperation(ent, op, res);
break;
case Atlas::Objects::Operation::USE_NO:
mindUseOperation(ent, op, res);
break;
case Atlas::Objects::Operation::WIELD_NO:
mindWieldOperation(ent, op, res);
break;
default:
if (op_no == Atlas::Objects::Operation::ACTUATE_NO) {
mindActuateOperation(ent, op, res);
} else if (op_no == Atlas::Objects::Operation::EAT_NO) {
mindEatOperation(ent, op, res);
} else if (op_no == Atlas::Objects::Operation::SETUP_NO) {
mindSetupOperation(ent, op, res);
} else if (op_no == Atlas::Objects::Operation::TICK_NO) {
mindTickOperation(ent, op, res);
} else if (op_no == Atlas::Objects::Operation::UPDATE_NO) {
mindUpdateOperation(ent, op, res);
} else if (op_no == Atlas::Objects::Operation::THOUGHT_NO) {
mindThoughtOperation(ent, op, res);
} else if (op_no == Atlas::Objects::Operation::GOAL_INFO_NO) {
mindGoalInfoOperation(ent, op, res);
} else if (op_no == Atlas::Objects::Operation::THINK_NO) {
mindThinkOperation(ent, op, res);
} else {
mindOtherOperation(ent, op, res);
}
break;
}
}
/// \brief Filter operations from the world to the mind
///
/// Operations from the world are checked here to see if they are suitable
/// to send to the mind. Some operations should not go to the mind as they
/// leak information. Others are just not necessary as they provide no
/// useful information.
bool MindsProperty::world2mind(const Operation& op) const
{
auto otype = op->getClassNo();
if (otype != Atlas::Objects::Operation::RELAY_NO) {
return true;
}
return false;
}
void MindsProperty::addMind(Router* mind)
{
m_data.push_back(mind);
}
void MindsProperty::removeMind(Router* mind, LocatedEntity* entity)
{
auto I = std::find(m_data.begin(), m_data.end(), mind);
if (I != m_data.end()) {
m_data.erase(I);
}
//If there are no more minds controlling we should either remove a transient entity, or stop a moving one.
if (m_data.empty()) {
//If the entity is marked as "transient" we should remove it from the world once it's not controlled anymore.
if (entity->getProperty(TransientProperty::property_name)) {
log(NOTICE, "Removing entity marked as transient when mind disconnected. " + entity->describeEntity());
Atlas::Objects::Operation::Delete delOp;
delOp->setTo(entity->getId());
Atlas::Objects::Entity::Anonymous anon;
anon->setId(entity->getId());
delOp->setArgs1(anon);
entity->sendWorld(delOp);
} else {
// Send a move op stopping the current movement
Atlas::Objects::Entity::Anonymous move_arg;
move_arg->setId(entity->getId());
move_arg->setAttr("propel", Vector3D::ZERO().toAtlas());
::addToEntity(Vector3D::ZERO(), move_arg->modifyVelocity());
Atlas::Objects::Operation::Move move;
move->setFrom(entity->getId());
move->setTo(entity->getId());
move->setArgs1(move_arg);
entity->sendWorld(move);
}
}
}
const std::vector<Router*>& MindsProperty::getMinds() const
{
return m_data;
}