Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/game/StarBehaviorDatabase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/game/StarBehaviorDatabase.cpp')
-rw-r--r--source/game/StarBehaviorDatabase.cpp298
1 files changed, 298 insertions, 0 deletions
diff --git a/source/game/StarBehaviorDatabase.cpp b/source/game/StarBehaviorDatabase.cpp
new file mode 100644
index 0000000..4306cd7
--- /dev/null
+++ b/source/game/StarBehaviorDatabase.cpp
@@ -0,0 +1,298 @@
+#include "StarBehaviorDatabase.hpp"
+#include "StarAssets.hpp"
+#include "StarRoot.hpp"
+#include "StarJsonExtra.hpp"
+
+namespace Star {
+
+EnumMap<NodeParameterType> 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<String>())
+ json.set("key", *key);
+ else
+ json.set("value", parameter.second.get<Json>());
+ 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<String>(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<String>(json.get("key"), [](Json const& j) { return j.toString(); }), json.optBool("ephemeral").value(false)}
+ };
+}
+
+EnumMap<BehaviorNodeType> const BehaviorNodeTypeNames {
+ {BehaviorNodeType::Action, "Action"},
+ {BehaviorNodeType::Decorator, "Decorator"},
+ {BehaviorNodeType::Composite, "Composite"},
+ {BehaviorNodeType::Module, "Module"}
+};
+
+EnumMap<CompositeType> const CompositeTypeNames {
+ {CompositeType::Sequence, "Sequence"},
+ {CompositeType::Selector, "Selector"},
+ {CompositeType::Parallel, "Parallel"},
+ {CompositeType::Dynamic, "Dynamic"},
+ {CompositeType::Randomize, "Randomize"}
+};
+
+void applyTreeParameters(StringMap<NodeParameter>& nodeParameters, StringMap<NodeParameterValue> const& treeParameters) {
+ for (auto& p : nodeParameters) {
+ NodeParameter& parameter = p.second;
+ parameter.second = replaceBehaviorTag(parameter.second, treeParameters);
+ }
+}
+
+NodeParameterValue replaceBehaviorTag(NodeParameterValue const& parameter, StringMap<NodeParameterValue> const& treeParameters) {
+ Maybe<String> strVal = parameter.maybe<String>();
+ if (!strVal && parameter.get<Json>().isType(Json::Type::String))
+ strVal = parameter.get<Json>().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 '%s'", key));
+ }
+ }
+ }
+ return parameter;
+}
+
+Maybe<String> replaceOutputBehaviorTag(Maybe<String> const& output, StringMap<NodeParameterValue> 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<String>())
+ return *key;
+ else if (replace->get<Json>().isType(Json::Type::String))
+ return replace->get<Json>().toString();
+ else
+ return {};
+ } else {
+ throw StarException(strf("No parameter specified for tag '%s'", *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<NodeParameter> parameters, StringMap<NodeOutput> output)
+ : name(move(name)), parameters(move(parameters)), output(move(output)) { }
+
+DecoratorNode::DecoratorNode(String const& name, StringMap<NodeParameter> parameters, BehaviorNodeConstPtr child)
+ : name(name), parameters(parameters), child(child) { }
+
+SequenceNode::SequenceNode(List<BehaviorNodeConstPtr> children) : children(children) { }
+
+SelectorNode::SelectorNode(List<BehaviorNodeConstPtr> children) : children(children) { }
+
+ParallelNode::ParallelNode(StringMap<NodeParameter> parameters, List<BehaviorNodeConstPtr> children) : children(children) {
+ int s = parameters.get("success").second.get<Json>().optInt().value(-1);
+ succeed = s == -1 ? children.size() : s;
+
+ int f = parameters.get("fail").second.get<Json>().optInt().value(-1);
+ fail = f == -1 ? children.size() : f;
+}
+
+DynamicNode::DynamicNode(List<BehaviorNodeConstPtr> children) : children(children) { }
+
+RandomizeNode::RandomizeNode(List<BehaviorNodeConstPtr> 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();
+
+ StringList nodeFiles = assets->scanExtension("nodes");
+ assets->queueJsons(nodeFiles);
+ for (String const& file : nodeFiles) {
+ try {
+ Json nodes = assets->json(file);
+ for (auto node : nodes.toObject()) {
+ StringMap<NodeParameter> parameters;
+ for (auto p : node.second.getObject("properties", {}))
+ parameters.set(p.first, jsonToNodeParameter(p.second));
+
+ m_nodeParameters.set(node.first, parameters);
+
+ StringMap<NodeOutput> 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 \'%s\'", 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 \'%s\'", name));
+
+ m_configs[name] = config;
+ } catch (StarException const& e) {
+ throw StarException(strf("Could not load behavior file \'%s\'", 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 \'%s\'", name));
+
+ return m_behaviors.get(name);
+}
+
+BehaviorTreeConstPtr BehaviorDatabase::buildTree(Json const& config, StringMap<NodeParameterValue> const& overrides) const {
+ StringSet scripts = jsonToStringSet(config.get("scripts", JsonArray()));
+ auto tree = BehaviorTree(config.getString("name"), scripts, config.getObject("parameters", {}));
+
+ StringMap<NodeParameterValue> 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 make_shared<BehaviorTree>(move(tree));
+}
+
+Json BehaviorDatabase::behaviorConfig(String const& name) const {
+ if (!m_configs.contains(name))
+ throw StarException(strf("No such behavior tree \'%s\'", 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<NodeParameter> parameters, StringMap<NodeParameterValue> const& treeParameters, BehaviorTree& tree) const {
+ List<BehaviorNodeConstPtr> 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 '%s' could not be created from JSON", CompositeTypeNames.getRight(type)));
+}
+
+BehaviorNodeConstPtr BehaviorDatabase::behaviorNode(Json const& json, StringMap<NodeParameterValue> 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<NodeParameter> 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<NodeOutput> 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<BehaviorNode>(ActionNode(name, parameters, output));
+ } else if (type == BehaviorNodeType::Decorator) {
+ tree.functions.add(name);
+ BehaviorNodeConstPtr child = behaviorNode(json.get("child"), treeParameters, tree);
+ return make_shared<BehaviorNode>(DecoratorNode(name, parameters, child));
+ } else if (type == BehaviorNodeType::Composite) {
+ return make_shared<BehaviorNode>(compositeNode(json, parameters, treeParameters, tree));
+ }
+
+ // above statement must be exhaustive
+ throw StarException(strf("Behavior node type '%s' could not be created from JSON", BehaviorNodeTypeNames.getRight(type)));
+}
+
+}