/* 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 "UsagesProperty.h" #include "rules/simulation/BaseWorld.h" #include "rules/entityfilter/Providers.h" #include "common/debug.h" #include "common/AtlasQuery.h" #include "ScriptUtils.h" #include "rules/simulation/PlantedOnProperty.h" #include #include #include #include static const bool debug_flag = false; using Atlas::Message::Element; using Atlas::Message::ListType; using Atlas::Message::MapType; using Atlas::Objects::Root; using Atlas::Objects::Operation::Action; using Atlas::Objects::Operation::Use; using Atlas::Objects::Entity::Anonymous; using Atlas::Objects::Entity::RootEntity; using Atlas::Objects::smart_dynamic_cast; void UsagesProperty::set(const Atlas::Message::Element& val) { Property::set(val); m_usages.clear(); for (auto& usageEntry : m_data) { if (!usageEntry.first.empty()) { try { //TODO: check that the op is a subtype of "action"? if (usageEntry.second.isMap()) { auto map = usageEntry.second.Map(); Usage usage; AtlasQuery::find(map, "handler", [&](const std::string& value) { usage.handler = value; }); if (usage.handler.empty()) { continue; } AtlasQuery::find(map, "description", [&](const std::string& value) { usage.description = value; }); AtlasQuery::find(map, "constraint", [&](const std::string& value) { //TODO: should be a usage constraint provider factory usage.constraint.reset(new EntityFilter::Filter(value, EntityFilter::ProviderFactory())); }); AtlasQuery::find(map, "params", [&](const Atlas::Message::MapType& value) { for (auto& entry : value) { UsageParameter parameter{}; if (!entry.second.isMap()) { throw std::invalid_argument("Parameter must be a map."); } auto& paramMap = entry.second.Map(); auto I = paramMap.find("type"); if (I == paramMap.end() || !I->second.isString()) { throw std::invalid_argument("Parameter must define a string 'type'."); } if (I->second.String() == "entity") { parameter.type = UsageParameter::Type::ENTITY; } else if (I->second.String() == "entity_location") { parameter.type = UsageParameter::Type::ENTITYLOCATION; } else if (I->second.String() == "direction") { parameter.type = UsageParameter::Type::DIRECTION; } else if (I->second.String() == "position") { parameter.type = UsageParameter::Type::POSITION; } else { throw std::invalid_argument(String::compose("Parameter type not recognized: %1.", I->second.String())); } AtlasQuery::find(paramMap, "constraint", [&](const std::string& constraint) { //TODO: should be a usage constraint provider factory parameter.constraint.reset(new EntityFilter::Filter(constraint, EntityFilter::ProviderFactory())); }); AtlasQuery::find(paramMap, "min", [&](const long& min) { parameter.min = min; }); AtlasQuery::find(paramMap, "max", [&](const long& max) { parameter.max = max; }); usage.params.emplace(entry.first, std::move(parameter)); } }); // // // // AtlasQuery::find(map, "targets", [&](const Atlas::Message::ListType& value) { // for (auto& entry : value) { // if (entry.isString()) { // usage.targets.emplace_back(new EntityFilter::Filter(entry.String(), EntityFilter::ProviderFactory())); // } // } // }); // AtlasQuery::find(map, "consumes", [&](const Atlas::Message::ListType& value) { // for (auto& entry : value) { // if (entry.isString()) { // usage.consumed.emplace_back(new EntityFilter::Filter(entry.String(), EntityFilter::ProviderFactory())); // } // } // }); m_usages.emplace(usageEntry.first, std::move(usage)); } } catch (const std::invalid_argument& e) { log(ERROR, String::compose("Could not install usage '%1' : %2", usageEntry.first, e.what())); } } } } void UsagesProperty::install(LocatedEntity* owner, const std::string& name) { owner->installDelegate(Atlas::Objects::Operation::USE_NO, name); } void UsagesProperty::remove(LocatedEntity* owner, const std::string& name) { owner->removeDelegate(Atlas::Objects::Operation::USE_NO, name); } HandlerResult UsagesProperty::operation(LocatedEntity* e, const Operation& op, OpVector& res) { return use_handler(e, op, res); } HandlerResult UsagesProperty::use_handler(LocatedEntity* e, const Operation& op, OpVector& res) { auto actor = BaseWorld::instance().getEntity(op->getFrom()); if (!actor) { e->error(op, "Could not find 'from' entity.", res, e->getId()); return OPERATION_IGNORED; } if (op->isDefaultFrom()) { actor->error(op, "Top op has no 'from' attribute.", res, actor->getId()); return OPERATION_IGNORED; } if (!op->getArgs().empty()) { auto& arg = op->getArgs().front(); auto argOp = smart_dynamic_cast(arg); if (!argOp) { actor->error(op, "First arg wasn't an operation.", res, actor->getId()); return OPERATION_IGNORED; } if (!argOp->hasAttrFlag(Atlas::Objects::PARENT_FLAG)) { actor->error(op, "Use arg op has malformed parent", res, actor->getId()); return OPERATION_IGNORED; } auto op_type = argOp->getParent(); debug_print("Got op type " << op_type << " from arg"); auto obj = Atlas::Objects::Factories::instance()->createObject(op_type); if (!obj.isValid()) { log(ERROR, String::compose("Character::UseOperation Unknown op type " "\"%1\".", op_type)); return OPERATION_IGNORED; } auto rop = smart_dynamic_cast(obj); if (!rop.isValid()) { log(ERROR, String::compose("Character::UseOperation Op type " "\"%1\" but it is not an operation type. ", op_type)); return OPERATION_IGNORED; } rop->setFrom(actor->getId()); rop->setTo(e->getId()); rop->setSeconds(op->getSeconds()); if (argOp->getArgs().empty()) { actor->error(op, "Use arg op has no arguments; one expected.", res, actor->getId()); return OPERATION_IGNORED; } auto arguments = argOp->getArgs().front(); //Check that there's an action registered for this operation auto usagesI = m_usages.find(op_type); if (usagesI != m_usages.end()) { auto& usage = usagesI->second; //Check that the tool is ready auto toolReadyAtProp = e->getPropertyType("ready_at"); if (toolReadyAtProp) { if (toolReadyAtProp->data() > BaseWorld::instance().getTime()) { actor->clientError(op, "Tool is not ready yet.", res, actor->getId()); return OPERATION_IGNORED; } } //Check if the tools is attached, and if so the attachment is ready auto actorReadyAtProp = actor->getPropertyType("_ready_at_attached"); if (actorReadyAtProp) { auto plantedOnProp = e->getPropertyClassFixed(); //First check if the tool is attached to the actor at an attach point if (plantedOnProp && plantedOnProp->data().entity.get() == actor.get() && plantedOnProp->data().attachment) { auto attachPoint = *plantedOnProp->data().attachment; //Lastly check if there's a value for this attach point. auto attachI = actorReadyAtProp->data().find(attachPoint); if (attachI != actorReadyAtProp->data().end()) { if (attachI->second.isFloat() && attachI->second.Float() > BaseWorld::instance().getTime()) { actor->clientError(op, "Actor is not ready yet.", res, actor->getId()); return OPERATION_IGNORED; } } } } //Populate the usage arguments std::map> usage_instance_args; for (auto& param : usage.params) { Atlas::Message::Element element; if (arguments->copyAttr(param.first, element) != 0 || !element.isList()) { actor->clientError(op, String::compose("Could not find required list argument '%1'.", param.first), res, actor->getId()); return OPERATION_IGNORED; } auto& argVector = usage_instance_args[param.first]; for (auto& argElement : element.List()) { switch (param.second.type) { case UsageParameter::Type::ENTITY: { if (!argElement.isMap()) { actor->clientError(op, String::compose("Inner argument in list of arguments for '%1' was not a map.", param.first), res, actor->getId()); return OPERATION_IGNORED; } //The arg is for an RootEntity, expressed as a message. Extract id and pos. auto idI = argElement.Map().find("id"); if (idI == argElement.Map().end() || !idI->second.isString()) { actor->clientError(op, String::compose("Inner argument in list of arguments for '%1' had no id string.", param.first), res, actor->getId()); return OPERATION_IGNORED; } auto involved = BaseWorld::instance().getEntity(idI->second.String()); if (!involved) { actor->error(op, "Involved entity does not exist", res, actor->getId()); return OPERATION_IGNORED; } auto posI = argElement.Map().find("pos"); if (posI != argElement.Map().end() && posI->second.isList()) { argVector.emplace_back(EntityLocation(involved, WFMath::Point<3>(posI->second))); } else { argVector.emplace_back(EntityLocation(involved)); } } break; case UsageParameter::Type::ENTITYLOCATION: argVector.emplace_back(WFMath::Point<3>(argElement)); break; case UsageParameter::Type::POSITION: argVector.emplace_back(WFMath::Point<3>(argElement)); break; case UsageParameter::Type::DIRECTION: argVector.emplace_back(WFMath::Vector<3>(argElement)); break; } } } UsageInstance usageInstance{usage, actor, e, std::move(usage_instance_args), rop}; //Check that the usage is valid before continuing auto validRes = usageInstance.isValid(); if (!validRes.first) { actor->clientError(op, validRes.second, res, actor->getId()); } else { auto lastSeparatorPos = usage.handler.find_last_of('.'); if (lastSeparatorPos != std::string::npos) { auto moduleName = usage.handler.substr(0, lastSeparatorPos); auto functionName = usage.handler.substr(lastSeparatorPos + 1); Py::Module module(PyImport_Import(Py::String(moduleName).ptr())); PyImport_ReloadModule(module.ptr()); auto functionObject = module.getDict()[functionName]; if (!functionObject.isCallable()) { actor->error(op, String::compose("Could not find Python function %1", usage.handler), res, actor->getId()); return OPERATION_IGNORED; } try { auto ret = Py::Callable(functionObject).apply(Py::TupleN(UsageInstance::scriptCreator(std::move(usageInstance)))); return ScriptUtils::processScriptResult(usage.handler, ret, res, e); } catch (const Py::BaseException& py_ex) { log(ERROR, String::compose("Python error calling \"%1\" for entity %2", usage.handler, e->describeEntity())); if (PyErr_Occurred()) { PyErr_Print(); } } } } return OPERATION_BLOCKED; } } //We couldn't find any suitable task. return OPERATION_IGNORED; }