#include "planner.hpp" #include "action.hpp" #include "character_actor.hpp" #include "global_world_state.hpp" #include "state.hpp" #include "utils/godot_macros.h" #include #include #include #include #include #include #include namespace godot::goap { typedef HashMap NodeNodeMap; typedef HashMap NodeScoreMap; typedef HashSet 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) 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(this->get_parent()); } static Vector> trace_path(NodeNodeMap &map, PlannerNode &end) { Vector> edges{}; PlannerNode node{end}; while(node.last_edge.is_valid()) { edges.push_back(node.last_edge); node = map.get(node); } return edges; } Vector> Planner::make_plan() { // clear cache every planning phase this->cached_world_state.clear(); // select the most desirable goal available Ref goal = this->select_goal(); if(!goal.is_valid()) { this->plan = {}; return this->plan; } // ordered list of all nodes still being considered Vector 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 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 Planner::select_goal() { for(Ref 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) { 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) { for(WorldProperty const &prop : goal->prerequisites) if(this->get_world_property(prop.key) != prop.value) return false; return true; } Vector Planner::find_neighbours_of(PlannerNode &node) { Vector neighbours{}; for(Ref 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> Planner::find_actions_satisfying(WorldState requirements) { Vector> found_actions{}; for(Ref &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 act = value[i]; if(act.is_valid()) this->actions.set(i, act); } } Array Planner::get_actions() const { Array array{}; for(Ref 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 = value[i]; if(goal.is_valid()) this->goals.set(i, goal); } } Array Planner::get_goals() const { Array array{}; for(Ref const &goal : this->goals) { array.push_back(goal); } return array; } bool Planner::add_goal(Ref goal) { bool can_do = this->can_do(goal); this->goals.insert(0, goal); return can_do; } void Planner::remove_goal(Ref goal) { this->goals.erase(goal); } }