255 lines
8.1 KiB
C++
255 lines
8.1 KiB
C++
#include "planner.hpp"
|
|
#include "action.hpp"
|
|
#include "character_actor.hpp"
|
|
#include "global_world_state.hpp"
|
|
#include "state.hpp"
|
|
#include "utils/godot_macros.h"
|
|
#include <cstdlib>
|
|
#include <godot_cpp/templates/pair.hpp>
|
|
#include <godot_cpp/variant/utility_functions.hpp>
|
|
#include <godot_cpp/templates/hash_set.hpp>
|
|
#include <godot_cpp/templates/hash_map.hpp>
|
|
#include <godot_cpp/templates/hashfuncs.hpp>
|
|
#include <godot_cpp/variant/vector3.hpp>
|
|
|
|
namespace godot::goap {
|
|
typedef HashMap<PlannerNode, PlannerNode, PlannerNodeHasher> NodeNodeMap;
|
|
typedef HashMap<PlannerNode, float, PlannerNodeHasher> NodeScoreMap;
|
|
typedef HashSet<PlannerNode, PlannerNodeHasher> NodeSet;
|
|
|
|
void Goal::_bind_methods() {
|
|
#define CLASSNAME Goal
|
|
GDPROPERTY(goal_state, Variant::DICTIONARY);
|
|
GDPROPERTY(prerequisites, Variant::DICTIONARY);
|
|
}
|
|
|
|
void Goal::set_goal_state(Dictionary dict) {
|
|
Action::dict_to_property_map(dict, this->goal_state);
|
|
}
|
|
|
|
Dictionary Goal::get_goal_state() const {
|
|
return Action::property_map_to_dict(this->goal_state);
|
|
}
|
|
|
|
void Goal::set_prerequisites(Dictionary dict) {
|
|
Action::dict_to_property_map(dict, this->prerequisites);
|
|
}
|
|
|
|
Dictionary Goal::get_prerequisites() const {
|
|
return Action::property_map_to_dict(this->prerequisites);
|
|
}
|
|
|
|
#undef CLASSNAME // !Goal
|
|
|
|
PlannerNode PlannerNode::goal_node(WorldState const &goal) {
|
|
return PlannerNode{goal};
|
|
}
|
|
|
|
PlannerNode PlannerNode::new_node_along(Ref<Action> action) const {
|
|
PlannerNode new_node{
|
|
action->prerequisites,
|
|
action->effects,
|
|
action,
|
|
};
|
|
for(WorldProperty const &prop : this->state)
|
|
new_node.state.insert(prop.key, prop.value);
|
|
for(WorldProperty const &prop : this->open_requirements)
|
|
new_node.open_requirements.insert(prop.key, prop.value);
|
|
return new_node;
|
|
}
|
|
|
|
void Planner::_bind_methods() {
|
|
#define CLASSNAME Planner
|
|
GDPROPERTY_HINTED(actions, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Action));
|
|
GDPROPERTY_HINTED(goals, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Goal));
|
|
}
|
|
|
|
void Planner::_enter_tree() {
|
|
this->global_world_state = GlobalWorldState::get_singleton();
|
|
this->actor = Object::cast_to<CharacterActor>(this->get_parent());
|
|
}
|
|
|
|
static Vector<Ref<Action>> trace_path(NodeNodeMap &map, PlannerNode &end) {
|
|
Vector<Ref<Action>> edges{};
|
|
PlannerNode node{end};
|
|
while(node.last_edge.is_valid()) {
|
|
edges.push_back(node.last_edge);
|
|
node = map.get(node);
|
|
}
|
|
return edges;
|
|
}
|
|
|
|
Vector<Ref<Action>> Planner::make_plan() {
|
|
// clear cache every planning phase
|
|
this->cached_world_state.clear();
|
|
// select the most desirable goal available
|
|
Ref<Goal> goal = this->select_goal();
|
|
if(!goal.is_valid()) {
|
|
this->plan = {};
|
|
return this->plan;
|
|
}
|
|
// ordered list of all nodes still being considered
|
|
Vector<PlannerNode> open{PlannerNode::goal_node(goal->goal_state)};
|
|
PlannerNode first = open.get(0);
|
|
NodeNodeMap from{}; // mapping states to the previous in the path
|
|
NodeScoreMap dist_traveled{}; // mapping states to the shortest found distance from start
|
|
dist_traveled.insert(first, 0);
|
|
NodeScoreMap best_guess{}; // mapping states to the best guess of the distance to the goal
|
|
best_guess.insert(first, first.open_requirements.size());
|
|
PlannerNode current{}; // state we're checking for neighbours or completion
|
|
while(!open.is_empty()) {
|
|
// current is the top of the ordered list
|
|
current = open.get(0);
|
|
// check if we've reached the goal
|
|
if(current.open_requirements.is_empty()) {
|
|
this->plan = trace_path(from, current);
|
|
return this->plan;
|
|
}
|
|
// current is no longer considered as it cannot be the end
|
|
open.erase(current);
|
|
// find all neighbours of this state
|
|
Vector<PlannerNode> neighbours = this->find_neighbours_of(current);
|
|
for(PlannerNode const& node : neighbours) {
|
|
float const new_dist = dist_traveled.get(current) + 1.f; // unweighed distance traveled to neighbour
|
|
if(!dist_traveled.has(node) || new_dist < dist_traveled.get(node)) {
|
|
// store distances
|
|
dist_traveled[node] = new_dist;
|
|
best_guess[node] = new_dist + node.open_requirements.size();
|
|
from[node] = current;
|
|
int i = open.find(node);
|
|
if(i != -1)
|
|
open.remove_at(i);
|
|
open.ordered_insert(node);
|
|
}
|
|
}
|
|
}
|
|
UtilityFunctions::push_warning("Failed to find a plan satisfying goal");
|
|
this->plan = {};
|
|
return this->plan;
|
|
}
|
|
|
|
Ref<Goal> Planner::select_goal() {
|
|
for(Ref<Goal> const &goal : this->goals) {
|
|
if(this->can_do(goal))
|
|
return goal;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
Variant Planner::get_world_property(StringName prop_key) {
|
|
if(prop_key.begins_with("g_")) {
|
|
return this->global_world_state->get_world_property(prop_key);
|
|
} else if(this->cached_world_state.has(prop_key)) {
|
|
return this->cached_world_state.get(prop_key);
|
|
} else if(this->actor->has_method("get_" + prop_key)) {
|
|
Variant val = this->actor->call("get_" + prop_key);
|
|
this->cached_world_state.insert(prop_key, val);
|
|
return val;
|
|
} else return nullptr;
|
|
}
|
|
|
|
bool Planner::can_do(Ref<Action> action) {
|
|
for(WorldProperty &prop : action->context_prerequisites)
|
|
if(this->get_world_property(prop.key) != prop.value)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool Planner::can_do(Ref<Goal> goal) {
|
|
for(WorldProperty const &prop : goal->prerequisites)
|
|
if(this->get_world_property(prop.key) != prop.value)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
Vector<PlannerNode> Planner::find_neighbours_of(PlannerNode &node) {
|
|
Vector<PlannerNode> neighbours{};
|
|
for(Ref<Action> const &action : this->find_actions_satisfying(node.open_requirements)) {
|
|
PlannerNode new_node = node.new_node_along(action);
|
|
// remove all satisfied requirements
|
|
for(WorldProperty const &delta : action->effects) {
|
|
if(new_node.open_requirements.has(delta.key)
|
|
&& new_node.open_requirements.get(delta.key) == delta.value) {
|
|
new_node.open_requirements.erase(delta.key);
|
|
}
|
|
}
|
|
neighbours.push_back(new_node);
|
|
}
|
|
return neighbours;
|
|
}
|
|
|
|
Vector<Ref<Action>> Planner::find_actions_satisfying(WorldState requirements) {
|
|
Vector<Ref<Action>> found_actions{};
|
|
for(Ref<Action> &act : this->actions) {
|
|
for(WorldProperty &prop : requirements) {
|
|
if(act->effects.has(prop.key)
|
|
&& act->effects.get(prop.key) == prop.value
|
|
&& this->can_do(act)) {
|
|
found_actions.push_back(act);
|
|
}
|
|
}
|
|
}
|
|
return found_actions;
|
|
}
|
|
|
|
bool Planner::is_action_complete() {
|
|
return this->plan.get(0)->get_is_completed(this->actor);
|
|
}
|
|
|
|
State Planner::get_next_state() {
|
|
if(!this->plan.is_empty())
|
|
this->plan.remove_at(0);
|
|
if(this->plan.is_empty())
|
|
this->plan = this->make_plan();
|
|
if(this->plan.is_empty())
|
|
return State::new_invalid();
|
|
return this->plan.get(0)->apply_state->construct(this);
|
|
}
|
|
|
|
void Planner::set_actions(Array value) {
|
|
this->actions.clear();
|
|
this->actions.resize(value.size());
|
|
for(size_t i{0}; i < value.size(); ++i) {
|
|
Ref<Action> act = value[i];
|
|
if(act.is_valid())
|
|
this->actions.set(i, act);
|
|
}
|
|
}
|
|
|
|
Array Planner::get_actions() const {
|
|
Array array{};
|
|
for(Ref<Action> const &act : this->actions) {
|
|
array.push_back(act);
|
|
}
|
|
return array;
|
|
}
|
|
|
|
void Planner::set_goals(Array value) {
|
|
this->goals.clear();
|
|
this->goals.resize(value.size());
|
|
for(size_t i{0}; i < value.size(); ++i) {
|
|
Ref<Goal> goal = value[i];
|
|
if(goal.is_valid())
|
|
this->goals.set(i, goal);
|
|
}
|
|
}
|
|
|
|
Array Planner::get_goals() const {
|
|
Array array{};
|
|
for(Ref<Goal> const &goal : this->goals) {
|
|
array.push_back(goal);
|
|
}
|
|
return array;
|
|
}
|
|
|
|
bool Planner::add_goal(Ref<Goal> goal) {
|
|
bool can_do = this->can_do(goal);
|
|
this->goals.insert(0, goal);
|
|
return can_do;
|
|
}
|
|
|
|
void Planner::remove_goal(Ref<Goal> goal) {
|
|
this->goals.erase(goal);
|
|
}
|
|
}
|