#include "StarBehaviorDatabase.hpp" #include "StarAssets.hpp" #include "StarRoot.hpp" #include "StarJsonExtra.hpp" namespace Star { EnumMap const NodeParameterTypeNames { {NodeParameterType::Json, "json"}, {NodeParameterType::Entity, "entity"}, {NodeParameterType::Position, "position"}, {NodeParameterType::Vec2, "vec2"}, {NodeParameterType::Number, "number"}, {NodeParameterType::Bool, "bool"}, {NodeParameterType::List, "list"}, {NodeParameterType::Table, "table"}, {NodeParameterType::String, "string"} }; NodeParameterValue nodeParameterValueFromJson(Json const& json) { if (auto key = json.optString("key")) return *key; else return json.get("value"); } Json jsonFromNodeParameter(NodeParameter const& parameter) { JsonObject json { {"type", NodeParameterTypeNames.getRight(parameter.first)} }; if (auto key = parameter.second.maybe()) json.set("key", *key); else json.set("value", parameter.second.get()); return json; } NodeParameter jsonToNodeParameter(Json const& json) { NodeParameterType type = NodeParameterTypeNames.getLeft(json.getString("type")); if (auto key = json.optString("key")) return {type, *key}; else return {type, json.opt("value").value(Json())}; } Json nodeOutputToJson(NodeOutput const& output) { return JsonObject { {"type", NodeParameterTypeNames.getRight(output.first)}, {"key", jsonFromMaybe(output.second.first, [](String const& s) { return Json(s); })}, {"ephemeral", output.second.second} }; } NodeOutput jsonToNodeOutput(Json const& json) { return { NodeParameterTypeNames.getLeft(json.getString("type")), {jsonToMaybe(json.get("key"), [](Json const& j) { return j.toString(); }), json.optBool("ephemeral").value(false)} }; } EnumMap const BehaviorNodeTypeNames { {BehaviorNodeType::Action, "Action"}, {BehaviorNodeType::Decorator, "Decorator"}, {BehaviorNodeType::Composite, "Composite"}, {BehaviorNodeType::Module, "Module"} }; EnumMap const CompositeTypeNames { {CompositeType::Sequence, "Sequence"}, {CompositeType::Selector, "Selector"}, {CompositeType::Parallel, "Parallel"}, {CompositeType::Dynamic, "Dynamic"}, {CompositeType::Randomize, "Randomize"} }; void applyTreeParameters(StringMap& nodeParameters, StringMap const& treeParameters) { for (auto& p : nodeParameters) { NodeParameter& parameter = p.second; parameter.second = replaceBehaviorTag(parameter.second, treeParameters); } } NodeParameterValue replaceBehaviorTag(NodeParameterValue const& parameter, StringMap const& treeParameters) { Maybe strVal = parameter.maybe(); if (!strVal && parameter.get().isType(Json::Type::String)) strVal = parameter.get().toString(); if (strVal) { String key = *strVal; if (key.beginsWith('<') && key.endsWith('>')) { String treeKey = key.substr(1, key.size() - 2); if (auto replace = treeParameters.maybe(treeKey)) { return *replace; } else { throw StarException(strf("No parameter specified for tag '{}'", key)); } } } return parameter; } Maybe replaceOutputBehaviorTag(Maybe const& output, StringMap const& treeParameters) { if (auto out = output) { if (out->beginsWith('<') && out->endsWith('>')) { if (auto replace = treeParameters.maybe(out->substr(1, out->size() - 2))) { if (auto key = replace->maybe()) return *key; else if (replace->get().isType(Json::Type::String)) return replace->get().toString(); else return {}; } else { throw StarException(strf("No parameter specified for tag '{}'", *out)); } } } return output; } // TODO: This is temporary until BehaviorState can handle valueType:value pairs void parseNodeParameters(JsonObject& parameters) { for (auto& p : parameters) p.second = p.second.opt("key").orMaybe(p.second.opt("value")).value(Json()); } ActionNode::ActionNode(String name, StringMap parameters, StringMap output) : name(std::move(name)), parameters(std::move(parameters)), output(std::move(output)) { } DecoratorNode::DecoratorNode(String const& name, StringMap parameters, BehaviorNodeConstPtr child) : name(name), parameters(parameters), child(child) { } SequenceNode::SequenceNode(List children) : children(children) { } SelectorNode::SelectorNode(List children) : children(children) { } ParallelNode::ParallelNode(StringMap parameters, List children) : children(children) { int s = parameters.get("success").second.get().optInt().value(-1); succeed = s == -1 ? children.size() : s; int f = parameters.get("fail").second.get().optInt().value(-1); fail = f == -1 ? children.size() : f; } DynamicNode::DynamicNode(List children) : children(children) { } RandomizeNode::RandomizeNode(List children) : children(children) { } BehaviorTree::BehaviorTree(String const& name, StringSet scripts, JsonObject const& parameters) : name(name), scripts(scripts), parameters(parameters) { } BehaviorDatabase::BehaviorDatabase() { auto assets = Root::singleton().assets(); auto& nodeFiles = assets->scanExtension("nodes"); assets->queueJsons(nodeFiles); for (String const& file : nodeFiles) { try { Json nodes = assets->json(file); for (auto& node : nodes.toObject()) { StringMap parameters; for (auto p : node.second.getObject("properties", {})) parameters.set(p.first, jsonToNodeParameter(p.second)); m_nodeParameters.set(node.first, parameters); StringMap output; for (auto p : node.second.getObject("output", {})) output.set(p.first, jsonToNodeOutput(p.second)); m_nodeOutput.set(node.first, output); } } catch (StarException const& e) { throw StarException(strf("Could not load nodes file \'{}\'", file), e); } } auto& behaviorFiles = assets->scanExtension("behavior"); assets->queueJsons(behaviorFiles); for (auto const& file : behaviorFiles) { try { auto config = assets->json(file); auto name = config.getString("name"); if (m_configs.contains(name)) throw StarException(strf("Duplicate behavior tree \'{}\'", name)); m_configs[name] = config; } catch (StarException const& e) { throw StarException(strf("Could not load behavior file \'{}\'", file), e); } } for (auto& pair : m_configs) { if (!m_behaviors.contains(pair.first)) loadTree(pair.first); } } BehaviorTreeConstPtr BehaviorDatabase::behaviorTree(String const& name) const { if (!m_behaviors.contains(name)) throw StarException(strf("No such behavior tree \'{}\'", name)); return m_behaviors.get(name); } BehaviorTreeConstPtr BehaviorDatabase::buildTree(Json const& config, StringMap const& overrides) const { StringSet scripts = jsonToStringSet(config.get("scripts", JsonArray())); auto tree = BehaviorTree(config.getString("name"), scripts, config.getObject("parameters", {})); StringMap parameters; for (auto p : config.getObject("parameters", {})) parameters.set(p.first, p.second); for (auto p : overrides) parameters.set(p.first, p.second); BehaviorNodeConstPtr root = behaviorNode(config.get("root"), parameters, tree); tree.root = root; return std::make_shared(std::move(tree)); } Json BehaviorDatabase::behaviorConfig(String const& name) const { if (!m_configs.contains(name)) throw StarException(strf("No such behavior tree \'{}\'", name)); return m_configs.get(name); } void BehaviorDatabase::loadTree(String const& name) { m_behaviors.set(name, buildTree(m_configs.get(name))); } CompositeNode BehaviorDatabase::compositeNode(Json const& config, StringMap parameters, StringMap const& treeParameters, BehaviorTree& tree) const { List children = config.getArray("children", {}).transformed([this,treeParameters,&tree](Json const& child) { return behaviorNode(child, treeParameters, tree); }); CompositeType type = CompositeTypeNames.getLeft(config.getString("name")); if (type == CompositeType::Sequence) return SequenceNode(children); else if (type == CompositeType::Selector) return SelectorNode(children); else if (type == CompositeType::Parallel) return ParallelNode(parameters, children); else if (type == CompositeType::Dynamic) return DynamicNode(children); else if (type == CompositeType::Randomize) return RandomizeNode(children); // above statement needs to be exhaustive throw StarException(strf("Composite node type '{}' could not be created from JSON", CompositeTypeNames.getRight(type))); } BehaviorNodeConstPtr BehaviorDatabase::behaviorNode(Json const& json, StringMap const& treeParameters, BehaviorTree& tree) const { BehaviorNodeType type = BehaviorNodeTypeNames.getLeft(json.getString("type")); auto name = json.getString("name"); auto parameterConfig = json.getObject("parameters", {}); if (type == BehaviorNodeType::Module) { // merge in module parameters to a copy of the treeParameters to propagate // tree parameters into the sub-tree, but allow modules to override auto moduleParameters = treeParameters; for (auto p : parameterConfig) moduleParameters.set(p.first, replaceBehaviorTag(nodeParameterValueFromJson(p.second), treeParameters)); BehaviorTree module = *buildTree(m_configs.get(name), moduleParameters); tree.scripts.addAll(module.scripts); tree.functions.addAll(module.functions); return module.root; } StringMap parameters = m_nodeParameters.get(name); for (auto& p : parameters) p.second.second = parameterConfig.maybe(p.first).apply(nodeParameterValueFromJson).value(p.second.second); applyTreeParameters(parameters, treeParameters); if (type == BehaviorNodeType::Action) { tree.functions.add(name); Json outputConfig = json.getObject("output", {}); StringMap output = m_nodeOutput.get(name); for (auto& p : output) p.second.second.first = replaceOutputBehaviorTag(outputConfig.optString(p.first).orMaybe(p.second.second.first), treeParameters); return make_shared(ActionNode(name, parameters, output)); } else if (type == BehaviorNodeType::Decorator) { tree.functions.add(name); BehaviorNodeConstPtr child = behaviorNode(json.get("child"), treeParameters, tree); return make_shared(DecoratorNode(name, parameters, child)); } else if (type == BehaviorNodeType::Composite) { return make_shared(compositeNode(json, parameters, treeParameters, tree)); } // above statement must be exhaustive throw StarException(strf("Behavior node type '{}' could not be created from JSON", BehaviorNodeTypeNames.getRight(type))); } }