// 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 "Character.h" #include "Pedestrian.h" #include "MindFactory.h" #include "BaseMind.h" #include "Script.h" #include "World.h" #include "Task.h" #include "StatisticsProperty.h" #include "common/op_switch.h" #include "common/const.h" #include "common/debug.h" #include "common/globals.h" #include "common/log.h" #include "common/inheritance.h" #include "common/serialno.h" #include "common/compose.hpp" #include "common/Add.h" #include "common/Attack.h" #include "common/Burn.h" #include "common/Chop.h" #include "common/Cut.h" #include "common/Eat.h" #include "common/Nourish.h" #include "common/Setup.h" #include "common/Tick.h" #include "common/Unseen.h" #include "common/Update.h" #include #include #include #include using Atlas::Message::Element; using Atlas::Message::ListType; using Atlas::Objects::Root; using Atlas::Objects::Operation::Set; using Atlas::Objects::Operation::Sight; using Atlas::Objects::Operation::Sound; using Atlas::Objects::Operation::Tick; using Atlas::Objects::Operation::Look; using Atlas::Objects::Operation::Move; using Atlas::Objects::Operation::Setup; using Atlas::Objects::Operation::Action; using Atlas::Objects::Operation::Unseen; using Atlas::Objects::Operation::Nourish; using Atlas::Objects::Operation::Appearance; 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; const double Character::energyConsumption = 0.001; const double Character::foodConsumption = 0.1; const double Character::weightConsumption = 0.1; const double Character::energyGain = 0.5; const double Character::energyLoss = 0.1; const double Character::weightGain = 0.5; void Character::metabolise(OpVector & res, double ammount) { // Currently handles energy // We should probably call this whenever the entity performs a movement. Anonymous set_arg; set_arg->setId(getId()); if ((m_status > (1.5 + energyLoss)) && (m_mass < m_maxMass)) { m_status = m_status - energyLoss; set_arg->setAttr("mass", m_mass + weightGain); } double energyUsed = energyConsumption * ammount; if ((m_status <= energyUsed) && (m_mass > weightConsumption)) { set_arg->setAttr("status", m_status - energyUsed + energyGain); set_arg->setAttr("mass", m_mass - weightConsumption); } else { set_arg->setAttr("status", m_status - energyUsed); } if (m_drunkness > 0) { set_arg->setAttr("drunkness", m_drunkness - 0.1); } if (m_stamina < 1. && m_task == 0 && !m_movement.updateNeeded(m_location)) { set_arg->setAttr("stamina", 1.); } Set s; s->setTo(getId()); s->setArgs1(set_arg); res.push_back(s); } void Character::wieldDropped() { Wield wield; wield->setTo(getId()); sendWorld(wield); } Entity * Character::findInContains(Entity * ent, const std::string & id) { EntitySet::const_iterator I = ent->m_contains.begin(); EntitySet::const_iterator Iend = ent->m_contains.end(); for (; I != Iend; ++I) { Entity * child = *I; if (child->getId() == id) { return *I; } if (!child->m_contains.empty()) { Entity * found = findInContains(child, id); if (found != 0) { return found; } } } return 0; } Entity * Character::findInInventory(const std::string & id) { return findInContains(this, id); } Character::Character(const std::string & id, long intId) : Character_parent(id, intId), m_statistics(*this), m_movement(*new Pedestrian(*this)), m_task(0), m_isAlive(true), m_stamina(1.), m_drunkness(0.), m_sex("female"), m_food(0), m_maxMass(100), m_mind(0), m_externalMind(0) { m_mass = 60; m_location.setBBox(BBox(WFMath::Point<3>(-0.25, -0.25, 0), WFMath::Point<3>(0.25, 0.25, 2))); m_properties["stamina"] = new Property(m_stamina, 0); m_properties["drunkness"] = new Property(m_drunkness, a_drunk); m_properties["sex"] = new Property(m_sex, a_sex); m_properties["statistics"] = new StatisticsProperty(m_statistics, 0); m_properties["right_hand_wield"] = new Property(m_rightHandWield, a_rwield); } Character::~Character() { delete &m_movement; if (m_mind != 0) { delete m_mind; } if (m_externalMind != 0) { delete m_externalMind; } } void Character::setTask(Task * task) { if (m_task != 0) { clearTask(); } m_task = task; task->incRef(); updateTask(); } void Character::updateTask() { if (m_task == 0) { log(ERROR, "Character::updateTask called when no task running"); } Anonymous set_arg; m_task->addToEntity(set_arg); set_arg->setId(getId()); Set set; set->setArgs1(set_arg); set->setTo(getId()); sendWorld(set); } void Character::clearTask() { if (m_task == 0) { log(ERROR, "Character.clearTask: No task currently set"); return; } m_task->decRef(); m_task = 0; Anonymous set_arg; set_arg->setAttr("tasks", ListType()); set_arg->setId(getId()); Set set; set->setArgs1(set_arg); set->setTo(getId()); sendWorld(set); } void Character::ImaginaryOperation(const Operation & op, OpVector & res) { Sight s; s->setArgs1(op); res.push_back(s); } void Character::SetupOperation(const Operation & op, OpVector & res) { debug( std::cout << "CHaracter::SetupOperation()" << std::endl << std::flush;); if (!op->getArgs().empty()) { debug( std::cout << __func__ << " Setup op is for subsystem" << std::endl << std::flush;); return; } if (0 == m_externalMind) { // This ensures that newly created player characters don't get // bogged down with an NPC mind. In the short term this // takes away PC programmability. // FIXME Characters restored from the database will still get // AI minds, so we need to handle them somehow differently. // Perhaps the Restore op (different from Setup op) is needed? m_mind = MindFactory::instance()->newMind(getId(), getIntId(), m_name, m_type); Operation s(op.copy()); Anonymous setup_arg; setup_arg->setName("mind"); s->setArgs1(setup_arg); res.push_back(s); Look l; l->setTo(getId()); res.push_back(l); } Tick tick; tick->setTo(getId()); res.push_back(tick); } void Character::TickOperation(const Operation & op, OpVector & res) { debug(std::cout << "================================" << std::endl << std::flush;); const std::vector & args = op->getArgs(); if (!args.empty()) { const Root & arg = args.front(); if (arg->getName() == "move") { // Deal with movement. Element serialno; if (arg->copyAttr("serialno", serialno) == 0 && (serialno.isInt())) { if (serialno.asInt() < m_movement.serialno()) { debug(std::cout << "Old tick" << std::endl << std::flush;); return; } } else { log(ERROR, "Character::TickOperation: No serialno in tick arg"); } Location return_location; if (m_movement.getUpdatedLocation(return_location)) { return; } res.push_back(m_movement.generateMove(return_location)); Anonymous tick_arg; tick_arg->setName("move"); tick_arg->setAttr("serialno", m_movement.serialno()); Tick tickOp; tickOp->setTo(getId()); tickOp->setFutureSeconds(m_movement.getTickAddition(return_location.pos(), return_location.velocity())); tickOp->setArgs1(tick_arg); res.push_back(tickOp); } else if (arg->getName() == "task") { // Deal with task iteration if (m_task == 0) { return; } Element serialno; if (arg->copyAttr("serialno", serialno) == 0 && (serialno.isInt())) { if (serialno.asInt() != m_task->serialno()) { debug(std::cout << "Old tick" << std::endl << std::flush;); return; } } else { log(ERROR, "Character::TickOperation: No serialno in tick arg"); return; } m_task->TickOperation(op, res); assert(m_task != 0); if (m_task->obsolete()) { clearTask(); } else { updateTask(); } } else if (arg->getName() == "mind") { // Do nothing. Passed to mind. } else { log(ERROR, String::compose("Tick to unknown subsystem \"%1\" arrived", arg->getName()).c_str()); } } else { // DIGEST if ((m_food >= foodConsumption) && (m_status < 2)) { // It is important that the metabolise bit is done next, as this // handles the status change m_status = m_status + foodConsumption; m_food = m_food - foodConsumption; Set s; Anonymous food_ent; food_ent->setId(getId()); food_ent->setAttr("food", m_food); s->setTo(getId()); s->setArgs1(food_ent); Sight si; si->setTo(getId()); si->setArgs1(s); res.push_back(si); } // METABOLISE metabolise(res); // TICK Tick tickOp; tickOp->setTo(getId()); tickOp->setFutureSeconds(consts::basic_tick * 30); res.push_back(tickOp); } } void Character::TalkOperation(const Operation & op, OpVector & res) { debug( std::cout << "Character::OPeration(Talk)" << std::endl<setArgs1(op); res.push_back(s); } void Character::EatOperation(const Operation & op, OpVector & res) { // This is identical to Food::Operation(Eat &) // Perhaps animal should inherit from Food? Set s; Anonymous self_ent; self_ent->setId(getId()); self_ent->setAttr("status", -1); s->setTo(getId()); s->setArgs1(self_ent); const std::string & to = op->getFrom(); Nourish n; Anonymous nour_ent; nour_ent->setId(to); nour_ent->setAttr("mass", m_mass); n->setTo(to); n->setArgs1(nour_ent); res.push_back(s); res.push_back(n); } void Character::NourishOperation(const Operation & op, OpVector & res) { if (op->getArgs().empty()) { error(op, "Nourish has no argument", res, getId()); return; } const Root & arg = op->getArgs().front(); Element mass_attr; if (arg->copyAttr("mass", mass_attr) != 0 || !mass_attr.isNum()) { return; } m_food = m_food + mass_attr.asNum(); Anonymous food_ent; food_ent->setId(getId()); food_ent->setAttr("food", m_food); Element alcohol_attr; if (arg->copyAttr("alcohol", alcohol_attr) == 0 && alcohol_attr.isNum()) { m_drunkness += alcohol_attr.asNum() / m_mass; food_ent->setAttr("drunkness", m_drunkness); } Set s; s->setArgs1(food_ent); Sight si; si->setTo(getId()); si->setArgs1(s); res.push_back(si); } void Character::WieldOperation(const Operation & op, OpVector & res) { if (op->getArgs().empty()) { // Wield nothing if (m_rightHandWieldConnection.connected()) { m_rightHandWieldConnection.disconnect(); } Set set; set->setTo(getId()); Anonymous set_arg; set_arg->setId(getId()); set_arg->setAttr("right_hand_wield", ""); set->setArgs1(set_arg); res.push_back(set); return; } const Root & arg = op->getArgs().front(); if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) { error(op, "Wield arg has no ID", res, getId()); return; } const std::string & id = arg->getId(); Entity * item = m_world->getEntity(id); if (item == 0) { error(op, "Wield arg does not exist", res, getId()); return; } EntitySet::const_iterator K = m_contains.find(item); if (K == m_contains.end()) { error(op, "Wield arg is not in inventory", res, getId()); return; } if (m_rightHandWieldConnection.connected()) { m_rightHandWieldConnection.disconnect(); } Set set; set->setTo(getId()); Anonymous set_arg; set_arg->setId(getId()); set_arg->setAttr("right_hand_wield", item->getId()); set->setArgs1(set_arg); res.push_back(set); m_rightHandWieldConnection = item->containered.connect(sigc::mem_fun(this, &Character::wieldDropped)); // m_rightHandWield = item->getId(); debug(std::cout << "Wielding " << item->getId() << std::endl << std::flush;); } void Character::AttackOperation(const Operation & op, OpVector & res) { const std::string & from = op->getFrom(); if (from == getId()) { return; } Entity * attack_ent = m_world->getEntity(op->getFrom()); if (attack_ent == 0) { log(ERROR, "AttackOperation: Attack op from non-existant ID"); return; } Character * attacker = dynamic_cast(attack_ent); if (attacker == 0) { log(ERROR, "AttackOperation: Attack op from non-character entity"); return; } if (attacker->m_task != 0) { log(ERROR, String::compose("AttackOperation: Attack op aborted because attacker %1(%2) busy", attacker->getId(), attacker->getType()).c_str()); return; } if (m_task != 0) { log(ERROR, String::compose("AttackOperation: Attack op aborted because defender %1(%2) busy", getId(), getType()).c_str()); return; } Task * combat = m_world->newTask("combat", *this); if (combat == 0) { log(ERROR, "Character::AttackOperation: Unable to create combat task"); return; } setTask(combat); m_task->initTask(op, res); if (m_task->obsolete()) { clearTask(); return; } combat = m_world->newTask("combat", *attacker); if (combat == 0) { log(ERROR, "Character::AttackOperation: Unable to create combat task"); return; } attacker->setTask(combat); attacker->m_task->initTask(op, res); if (attacker->m_task->obsolete()) { attacker->clearTask(); clearTask(); return; } } void Character::ChopOperation(const Operation & op, OpVector & res) { Set s; Anonymous sarg; sarg->setId(getId()); sarg->setAttr("status", getStatus() - 0.2); s->setArgs1(sarg); s->setTo(getId()); res.push_back(s); } void Character::mindLoginOperation(const Operation & op, OpVector & res) { } void Character::mindLogoutOperation(const Operation & op, OpVector & res) { } void Character::mindActionOperation(const Operation & op, OpVector & res) { // FIXME Make sure it doesn't happen again. Action should probably be // removed from the main switch. log(ERROR, "Explicit Action operation from client"); const std::vector & args = op->getArgs(); if (args.empty()) { log(ERROR, "mindActionOperation: action op has no argument"); return; } const Root & arg = args.front(); Element action; if (arg->copyAttr("action", action) == -1) { log(ERROR, "mindActionOperation: action op arg has no action"); return; } else { std::cout << "Action: " << action.asString() << std::endl << std::flush; } op->setTo(getId()); res.push_back(op); } void Character::mindAddOperation(const Operation & op, OpVector & res) { } void Character::mindAttackOperation(const Operation & op, OpVector & res) { const std::vector & args = op->getArgs(); if (args.empty()) { log(ERROR, "mindAttackOperation: attack op has no argument"); return; } const Root & arg = args.front(); if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) { log(ERROR, "mindAttackOperation: attack op arg has no ID"); return; } const std::string & id = arg->getId(); op->setTo(id); res.push_back(op); } void Character::mindSetupOperation(const Operation & op, OpVector & res) { Anonymous setup_arg; setup_arg->setName("mind"); op->setArgs1(setup_arg); op->setTo(getId()); res.push_back(op); } void Character::mindUseOperation(const Operation & op, OpVector & res) { debug(std::cout << "Got Use op from mind" << std::endl << std::flush;); const std::vector & args = op->getArgs(); if (args.empty()) { if (m_task != 0) { if (!m_task->obsolete()) { m_task->irrelevant(); } assert(m_task->obsolete()); clearTask(); } return; } const std::string & toolId = m_rightHandWield; if (toolId.empty()) { error(op, "Character::mindUseOp No tool wielded.", res, getId()); return; } // FIXME Get a tool id from the op attributes? Entity * tool = m_world->getEntity(toolId); if (tool == 0) { error(op, "Character::mindUseOp Tool does not exist.", res, getId()); return; } Element toolOpAttr; std::set toolOps; std::string op_type; // Determine the operations this tool supports if (!tool->getAttr("operations", toolOpAttr)) { log(NOTICE, "Character::mindUseOp Attempt to use something not a tool"); return; } if (!toolOpAttr.isList()) { log(ERROR, "Character::mindUseOp Tool has non list operations list"); return; } const ListType & toolOpList = toolOpAttr.asList(); if (toolOpList.empty()) { log(ERROR, "Character::mindUseOp Tool operation list is empty"); return; } ListType::const_iterator J = toolOpList.begin(); ListType::const_iterator Jend = toolOpList.end(); assert(J != Jend); if (!(*J).isString()) { log(ERROR, "Character::mindUseOp Tool operation list is malformed"); return; } op_type = (*J).String(); debug(std::cout << "default tool op is " << op_type << std::endl << std::flush;); for (; J != Jend; ++J) { if (!(*J).isString()) { log(ERROR, "Character::mindUseOp Tool has non string in operations list"); } else { toolOps.insert((*J).String()); } } RootEntity entity_arg(0); assert(!entity_arg.isValid()); // Look at Use args. If arg is an entity, this is the target. // If arg is an operation, this is the operation to be used, and the // sub op arg may be an entity specifying target. If op to be used is // specified, this is checked against the ops permitted by this tool. const Root & arg = args.front(); const std::string & argtype = arg->getObjtype(); if (argtype == "op") { if (!arg->hasAttrFlag(Atlas::Objects::PARENTS_FLAG) || (arg->getParents().empty())) { error(op, "Use arg op has malformed parents", res, getId()); return; } op_type = arg->getParents().front(); debug(std::cout << "Got op type " << op_type << " from arg" << std::endl << std::flush;); if (toolOps.find(op_type) == toolOps.end()) { error(op, "Use op is not permitted by tool", res, getId()); return; } // Check against valid ops Operation arg_op = smart_dynamic_cast(arg); if (!arg_op.isValid()) { error(op, "Use op arg is a malformed op", res, getId()); return; } const std::vector & arg_op_args = arg_op->getArgs(); if (!arg_op_args.empty()) { entity_arg = smart_dynamic_cast(arg_op_args.front()); if (!entity_arg.isValid()) { error(op, "Use op target is malformed", res, getId()); return; } } } else if (argtype == "obj") { entity_arg = smart_dynamic_cast(arg); if (!entity_arg.isValid()) { error(op, "Use target is malformed", res, getId()); return; } } else { error(op, "Use arg has unknown objtype", res, getId()); return; } Anonymous target; if (!entity_arg.isValid()) { error(op, "Character::mindUseOperation No target specified", res, getId()); return; } if (!entity_arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) { error(op, "Character::mindUseOperation Target entity has no ID", res, getId()); return; } target->setId(entity_arg->getId()); if (entity_arg->hasAttrFlag(Atlas::Objects::Entity::POS_FLAG)) { target->setPos(entity_arg->getPos()); } if (op_type.empty()) { error(op, "Character::mindUseOperation Unable to determine op type for tool", res, getId()); return; } debug(std::cout << "Using tool " << tool->getType() << " on " << target->getId() << " with " << op_type << " action." << std::endl << std::flush;); Root obj = Atlas::Objects::Factories::instance()->createObject(op_type); if (!obj.isValid()) { log(ERROR, String::compose("Character::mindUseOperation Unknown op type %1 requested by %2 tool", op_type, tool->getType()).c_str()); return; } Operation rop = smart_dynamic_cast(obj); if (!rop.isValid()) { log(ERROR, String::compose("Character::mindUseOperation Op type %1 requested by %2 tool, but it is not an operation type", op_type, tool->getType()).c_str()); // FIXME Think hard about how this error is reported. Would the error // make it back to the client if we made an error response? return; } else if (!target->hasAttrFlag(Atlas::Objects::ID_FLAG)) { debug(std::cout << "No target" << std::endl << std::flush;); } else { rop->setArgs1(target); } rop->setTo(toolId); Entity * target_ent = m_world->getEntity(entity_arg->getId()); if (target_ent == 0) { error(op, "Character::mindUseOperation Target does not exist", res, getId()); return; } Task * task = m_world->activateTask(tool->getType(), op_type, target_ent->getType(), *this); if (task != NULL) { setTask(task); assert(res.empty()); m_task->initTask(rop, res); if (m_task->obsolete()) { clearTask(); } else if (res.empty()) { // If initialising the task did not result in any operation at all // then the task cannot work correctly. In this case all we can // do is flag an error, and get rid of the task. log(ERROR, String::compose("Character::mindUseOperation Op type %1 of tool %2 activated a task, but it did not initialise", op_type, tool->getType()).c_str()); m_task->irrelevant(); clearTask(); } return; } res.push_back(rop); Sight sight; sight->setArgs1(rop); res.push_back(sight); } void Character::mindUpdateOperation(const Operation & op, OpVector & res) { } void Character::mindWieldOperation(const Operation & op, OpVector & res) { debug(std::cout << "Got Wield op from mind" << std::endl << std::flush;); op->setTo(getId()); res.push_back(op); } void Character::mindTickOperation(const Operation & op, OpVector & res) { Anonymous tick_arg; tick_arg->setName("mind"); op->setArgs1(tick_arg); op->setTo(getId()); res.push_back(op); } void Character::mindMoveOperation(const Operation & op, OpVector & res) { debug( std::cout << "Character::mind_move_op" << std::endl << std::flush;); const std::vector & args = op->getArgs(); if (args.empty()) { log(ERROR, "mindMoveOperation: move op has no argument"); return; } const RootEntity arg = smart_dynamic_cast(args.front()); if (!arg.isValid()) { log(ERROR, "mindMoveOperation: Arg is not an entity"); return; } if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) { log(ERROR, "mindMoveOperation: Arg has no ID"); return; } if (getStamina() <= 0.f) { // Character is immobilised. return; } const std::string & other_id = arg->getId(); if (other_id != getId()) { debug( std::cout << "Moving something else. " << other_id << std::endl << std::flush;); Entity * other = m_world->getEntity(other_id); if (other == 0) { Unseen u; Anonymous unseen_arg; unseen_arg->setId(other_id); u->setArgs1(unseen_arg); u->setTo(getId()); res.push_back(u); return; } if ((other->getMass() < 0) || (other->getMass() > m_statistics.get("strength"))) { debug( std::cout << "We can't move this. Just too heavy" << std::endl << std::flush;); return; } op->setTo(other_id); res.push_back(op); 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_velocity; Quaternion new_orientation; try { 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;); } if (arg->hasAttrFlag(Atlas::Objects::Entity::VELOCITY_FLAG)) { fromStdVector(new_velocity, arg->getVelocity()); debug( std::cout << "vel set to " << new_velocity << 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, "Invalid orientation from client. Ignoring"); } } } catch (Atlas::Message::WrongTypeException&) { log(ERROR, "EXCEPTION: mindMoveOperation: Malformed move operation"); return; } catch (...) { log(ERROR, "EXCEPTION: mindMoveOperation: Unknown exception thrown"); return; } if (new_pos.isValid()) { new_pos += (Vector3D(((double)rand())/RAND_MAX, ((double)rand())/RAND_MAX, 0) * (m_drunkness * 10)); } debug( std::cout << ":" << new_loc << ":" << m_location.m_loc->getId() << ":" << std::endl << std::flush;); if (!new_loc.empty() && (new_loc != m_location.m_loc->getId())) { debug(std::cout << "Changing loc" << std::endl << std::flush;); Entity * target_loc = m_world->getEntity(new_loc); if (target_loc == 0) { Unseen u; Anonymous unseen_arg; unseen_arg->setId(new_loc); u->setArgs1(unseen_arg); u->setTo(getId()); res.push_back(u); return; } if (new_pos.isValid()) { Location target(target_loc, new_pos); Vector3D distance = distanceTo(m_location, target); assert(distance.isValid()); // Convert target into our current frame of reference. new_pos = m_location.pos() + distance; } else { log(WARNING, "mindMoveOperation: Argument changes LOC, but no POS specified. Not sure this makes any sense"); } } // Movement within current loc. Work out the speed and stuff and // use movement object to track movement. Location ret_location; int ret = m_movement.getUpdatedLocation(ret_location); if (ret) { ret_location = m_location; } // FIXME THis here? m_movement.reset(); Vector3D direction; if (new_pos.isValid()) { direction = new_pos - ret_location.pos(); } else if (new_velocity.isValid()) { direction = new_velocity; } if (direction.isValid() && (direction.mag() > 0)) { direction.normalize(); debug( std::cout << "Direction: " << direction << std::endl << std::flush;); if (!new_orientation.isValid()) { // This is a character walking, so it should stap upright Vector3D uprightDirection = direction; uprightDirection[cZ] = 0; if (uprightDirection.mag() > 0) { uprightDirection.normalize(); new_orientation = quaternionFromTo(Vector3D(1,0,0), uprightDirection); debug( std::cout << "Orientation: " << new_orientation << std::endl << std::flush;); } } } double vel_mag; if (new_velocity.isValid()) { vel_mag = std::min(new_velocity.mag(), consts::base_velocity); } else { vel_mag = consts::base_velocity; } // 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 (direction.isValid()) { ret_location.m_velocity = direction; ret_location.m_velocity *= vel_mag; debug(std::cout << "Velocity" << ret_location.velocity() << std::endl << std::flush;); } ret_location.m_orientation = new_orientation; debug(std::cout << "Orientation" << ret_location.orientation() << std::endl << std::flush;); Operation moveOp = m_movement.generateMove(ret_location); assert(moveOp.isValid()); res.push_back(moveOp); if (m_movement.hasTarget() && ret_location.velocity().isValid() && ret_location.velocity() != Vector3D(0,0,0)) { Tick tickOp; Anonymous tick_arg; tick_arg->setAttr("serialno", m_movement.serialno()); tick_arg->setName("move"); tickOp->setArgs1(tick_arg); tickOp->setTo(getId()); tickOp->setFutureSeconds(m_movement.getTickAddition(ret_location.pos(), ret_location.velocity())); res.push_back(tickOp); } } void Character::mindSetOperation(const Operation & op, OpVector & res) { log(WARNING, "Set op from mind"); const std::vector & args = op->getArgs(); if (args.empty()) { log(ERROR, "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); } void Character::mindSightOperation(const Operation & op, OpVector & res) { } void Character::mindSoundOperation(const Operation & op, OpVector & res) { } void Character::mindChopOperation(const Operation & op, OpVector & res) { } void Character::mindCombineOperation(const Operation & op, OpVector & res) { std::cout << "mindCombineOperation" << std::endl << std::flush; const std::vector & args = op->getArgs(); if (args.empty()) { log(ERROR, "mindCombineOperation: combine op has no argument"); return; } std::vector::const_iterator I = args.begin(); const Root & arg1 = *I; op->setTo(arg1->getId()); std::vector::const_iterator Iend = args.end(); for (; I != Iend; ++I) { const Root & arg = *I; if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) { error(op, "Character::mindCombineOp No ID.", res, getId()); return; } // FIXME Check item to be combined is in inventory // and then also check stackable and the same type. } res.push_back(op); } void Character::mindCreateOperation(const Operation & op, OpVector & res) { op->setTo(getId()); res.push_back(op); } void Character::mindDeleteOperation(const Operation & op, OpVector & res) { op->setTo(getId()); res.push_back(op); } void Character::mindDivideOperation(const Operation & op, OpVector & res) { const std::vector & args = op->getArgs(); if (args.empty()) { log(ERROR, "mindDivideOperation: op has no argument"); return; } std::vector::const_iterator I = args.begin(); const Root & arg1 = *I; if (!arg1->hasAttrFlag(Atlas::Objects::ID_FLAG)) { error(op, "Character::mindDivideOp arg 1 has no ID.", res, getId()); return; } // FIXME Check entity to be divided is in inventory op->setTo(arg1->getId()); ++I; std::vector::const_iterator Iend = args.end(); for (; I != Iend; ++I) { const Root & arg = *I; if (arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) { error(op, "Character::mindDivideOp arg has ID.", res, getId()); return; } // Check the same type? } res.push_back(op); } void Character::mindBurnOperation(const Operation & op, OpVector & res) { } void Character::mindGetOperation(const Operation & op, OpVector & res) { } void Character::mindImaginaryOperation(const Operation & op, OpVector & res) { op->setTo(getId()); res.push_back(op); } void Character::mindInfoOperation(const Operation & op, OpVector & res) { } void Character::mindNourishOperation(const Operation & op, OpVector & res) { } void Character::mindTalkOperation(const Operation & op, OpVector & res) { debug( std::cout << "Character::mindTalkOperation" << std::endl << std::flush;); op->setTo(getId()); res.push_back(op); } void Character::mindLookOperation(const Operation & op, OpVector & res) { debug(std::cout << "Got look up from mind from [" << op->getFrom() << "] to [" << op->getTo() << "]" << std::endl << std::flush;); m_perceptive = true; const std::vector & args = op->getArgs(); if (args.empty()) { op->setTo(m_world->m_gameWorld.getId()); } else { const Root & arg = args.front(); if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) { log(ERROR, "mindLookOperation: Op has no ID"); return; } op->setTo(arg->getId()); } debug( std::cout <<" now to ["<getTo()<<"]"<setTo(getId()); res.push_back(op); } void Character::mindEatOperation(const Operation & op, OpVector & res) { const std::vector & args = op->getArgs(); if (args.empty()) { log(ERROR, "mindEatOperation: Op has no ARGS"); return; } const Root & arg = args.front(); if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) { log(ERROR, "mindEatOperation: Arg has no ID"); return; } op->setTo(arg->getId()); res.push_back(op); } void Character::mindTouchOperation(const Operation & op, OpVector & res) { // Work out what is being touched. const std::vector & args = op->getArgs(); if (args.empty()) { log(ERROR, "mindTouchOperation: Op has no ARGS"); return; } const Root & arg = args.front(); if (!arg->hasAttrFlag(Atlas::Objects::ID_FLAG)) { log(ERROR, "mindTouchOperation: Op has no ID"); return; } // 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); } void Character::mindAppearanceOperation(const Operation & op, OpVector & res) { } void Character::mindDisappearanceOperation(const Operation & op, OpVector & res) { } void Character::mindErrorOperation(const Operation & op, OpVector & res) { } void Character::mindOtherOperation(const Operation & op, OpVector & res) { op->setTo(getId()); res.push_back(op); } bool Character::w2mActionOperation(const Operation & op) { return false; } bool Character::w2mAttackOperation(const Operation & op) { return false; } bool Character::w2mLoginOperation(const Operation & op) { return false; } bool Character::w2mLogoutOperation(const Operation & op) { return false; } bool Character::w2mChopOperation(const Operation & op) { return false; } bool Character::w2mCreateOperation(const Operation & op) { return false; } bool Character::w2mCutOperation(const Operation & op) { return false; } bool Character::w2mDeleteOperation(const Operation & op) { return false; } bool Character::w2mEatOperation(const Operation & op) { return false; } bool Character::w2mBurnOperation(const Operation & op) { return false; } bool Character::w2mMoveOperation(const Operation & op) { return false; } bool Character::w2mSetOperation(const Operation & op) { return false; } bool Character::w2mLookOperation(const Operation & op) { return false; } bool Character::w2mDivideOperation(const Operation & op) { return false; } bool Character::w2mCombineOperation(const Operation & op) { return false; } bool Character::w2mGetOperation(const Operation & op) { return false; } bool Character::w2mImaginaryOperation(const Operation & op) { return false; } bool Character::w2mInfoOperation(const Operation & op) { return false; } bool Character::w2mTalkOperation(const Operation & op) { return false; } bool Character::w2mNourishOperation(const Operation & op) { return false; } bool Character::w2mAppearanceOperation(const Operation & op) { if (m_drunkness > 1.0) { return false; } return true; } bool Character::w2mDisappearanceOperation(const Operation & op) { if (m_drunkness > 1.0) { return false; } return true; } bool Character::w2mErrorOperation(const Operation & op) { return true; } bool Character::w2mOtherOperation(const Operation & op) { const std::string & ot = op->getParents().front(); log(WARNING, String::compose("Unexpeced %1 op going from world 2 mind", ot).c_str()); return false; } bool Character::w2mSetupOperation(const Operation & op) { if (!op->getArgs().empty()) { if (op->getArgs().front()->getName() == "mind") { return true; } } return false; } bool Character::w2mUseOperation(const Operation & op) { return false; } bool Character::w2mWieldOperation(const Operation & op) { return false; } bool Character::w2mTickOperation(const Operation & op) { if (!op->getArgs().empty()) { if (op->getArgs().front()->getName() == "mind") { return true; } } return false; } bool Character::w2mUpdateOperation(const Operation & op) { return false; } bool Character::w2mSightOperation(const Operation & op) { if (m_drunkness > 1.0) { return false; } return true; } bool Character::w2mSoundOperation(const Operation & op) { if (m_drunkness > 1.0) { return false; } return true; } bool Character::w2mTouchOperation(const Operation & op) { if (m_drunkness > 1.0) { return false; } return true; } void Character::sendMind(const Operation & op, OpVector & res) { debug( std::cout << "Character::sendMind(" << op->getParents().front() << ")" << std::endl << std::flush;); if (0 != m_externalMind) { if (0 != m_mind) { OpVector mindRes; m_mind->operation(op, mindRes); // Discard all the local results } debug(std::cout << "Sending to external mind" << std::endl << std::flush;); m_externalMind->operation(op, res); } else { debug(std::cout << "Using ops from local mind" << std::endl << std::flush;); if (0 != m_mind) { m_mind->operation(op, res); } } } /// \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 Character::mind2body(const Operation & op, OpVector & res) { debug( std::cout << "Character::mind2body(" << std::endl << std::flush;); if (m_drunkness > 1.0) { return; } if (op->hasAttrFlag(Atlas::Objects::Operation::TO_FLAG)) { log(ERROR, String::compose("Operation \"%1\" from mind with TO set", op->getParents().front()).c_str()); } if (op->hasAttrFlag(Atlas::Objects::Operation::FUTURE_SECONDS_FLAG) && op->getClassNo() != OP_TICK) { log(ERROR, String::compose("Operation \"%1\" from mind with FUTURE_SECONDS set", op->getParents().front()).c_str()); } OpNo otype = op->getClassNo(); OP_SWITCH(op, otype, res, mind) } bool Character::world2mind(const Operation & op) { debug( std::cout << "Character::world2mind(" << op->getParents().front() << ")" << std::endl << std::flush;); OpNo otype = op->getClassNo(); POLL_OP_SWITCH(op, otype, w2m) return false; } void Character::operation(const Operation & op, OpVector & res) { debug( std::cout << "Character::operation(" << op->getParents().front() << ")" << std::endl << std::flush;); if (m_script->operation(op->getParents().front(), op, res) != 0) { // Op was handled by a script } else { callOperation(op, res); } // set refno on result? if (!m_isAlive) { return; } if (world2mind(op)) { debug( std::cout << "Character::operation(" << op->getParents().front() << ") passed to mind" << std::endl << std::flush;); OpVector mres; sendMind(op, mres); OpVector::const_iterator Iend = mres.end(); for (OpVector::const_iterator I = mres.begin(); I != Iend; ++I) { //Operation * mr2 = mind2_res.front(); // Need to be very careful about what this actually does externalOperation(*I); } } } void Character::externalOperation(const Operation & op) { debug( std::cout << "Character::externalOperation(" << op->getParents().front() << ")" << std::endl << std::flush;); OpVector mres; mind2body(op, mres); OpVector::const_iterator I = mres.begin(); OpVector::const_iterator Iend = mres.end(); // 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() && I != Iend) { (*I)->setSerialno(op->getSerialno()); } for (; I != Iend; ++I) { sendWorld(*I); } }