diff --git a/godot/player_character.tscn b/godot/player_character.tscn index e43c534..924af26 100644 --- a/godot/player_character.tscn +++ b/godot/player_character.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=12 format=3 uid="uid://dpda341t6ipiv"] +[gd_scene load_steps=9 format=3 uid="uid://dpda341t6ipiv"] [sub_resource type="Curve" id="Curve_7rmf4"] min_value = 0.2 @@ -18,38 +18,21 @@ albedo_color = Color(0.94902, 0.909804, 0, 1) [sub_resource type="BoxMesh" id="BoxMesh_f5yvh"] size = Vector3(0.125, 0.14, 0.94) -[sub_resource type="MoveStateArgs" id="MoveStateArgs_752r2"] -argument_property = &"target_position" +[sub_resource type="MoveStateArgs" id="MoveStateArgs_ibmkn"] +argument_property = &"player_character" -[sub_resource type="Action" id="Action_ksl64"] +[sub_resource type="Action" id="Action_gtisq"] effects = { -"prereq": true +"is_near_player": true } -apply_state = SubResource("MoveStateArgs_752r2") - -[sub_resource type="Action" id="Action_mdru6"] -effects = { -"not_goal": 1 -} - -[sub_resource type="AnimateStateArgs" id="AnimateStateArgs_tlart"] -argument_property = &"fire_weapon" - -[sub_resource type="Action" id="Action_7v1i5"] -prerequisites = { -"prereq": true -} -effects = { -"goal": true -} -apply_state = SubResource("AnimateStateArgs_tlart") +apply_state = SubResource("MoveStateArgs_ibmkn") [sub_resource type="Goal" id="Goal_sqtwb"] goal_state = { -"target_dead": true +"is_near_player": true } prerequisites = { -"has_target": true +"is_near_player": false } [node name="PlayerCharacter" type="CharacterActor"] @@ -83,5 +66,5 @@ surface_material_override/0 = SubResource("StandardMaterial3D_scmx3") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.53551, 0.931313, 0) [node name="Planner" type="Planner" parent="."] -actions = [SubResource("Action_ksl64"), SubResource("Action_mdru6"), SubResource("Action_7v1i5")] +actions = [SubResource("Action_gtisq")] goals = [SubResource("Goal_sqtwb")] diff --git a/src/character_actor.cpp b/src/character_actor.cpp index 8da7733..525b294 100644 --- a/src/character_actor.cpp +++ b/src/character_actor.cpp @@ -1,5 +1,9 @@ #include "character_actor.hpp" +#include "planner.hpp" #include "projectile_pool.hpp" +#include "state.hpp" +#include "tunnels_game_mode.hpp" +#include "utils/game_root.hpp" #include "utils/godot_macros.h" #include #include @@ -12,6 +16,8 @@ void CharacterActor::_bind_methods() { #define CLASSNAME CharacterActor GDPROPERTY_HINTED(rotation_speed_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve"); GDFUNCTION_ARGS(set_velocity_target, "value"); + GDFUNCTION(get_is_near_player); + GDFUNCTION(get_player_character); } void CharacterActor::_enter_tree() { GDGAMEONLY(); @@ -20,12 +26,14 @@ void CharacterActor::_enter_tree() { GDGAMEONLY(); this->target_rotation = this->get_global_transform().get_basis().get_quaternion(); this->health = this->get_node("Health"); this->primary_weapon_pool = this->get_node("ProjectilePool"); + this->planner = this->get_node("Planner"); } void CharacterActor::_process(double delta_time) { GDGAMEONLY(); this->process_rotation(delta_time); if(!this->mode_manual) { - this->process_ai(delta_time); + this->process_behaviour(delta_time); + this->process_navigation(delta_time); } if(this->firing) { this->try_fire_weapon(); @@ -83,6 +91,7 @@ void CharacterActor::set_manual_mode(bool value) { ProcessMode const mode = value ? ProcessMode::PROCESS_MODE_DISABLED : ProcessMode::PROCESS_MODE_PAUSABLE; //this->nav_agent->set_process_mode(mode); this->nav_agent->set_avoidance_priority(value ? 1.f : 0.9f); + this->set_state(goap::State::new_invalid()); } void CharacterActor::set_rotation_speed_curve(Ref curve) { @@ -119,10 +128,54 @@ Vector3 CharacterActor::get_velocity_target() const { return this->velocity_target; } -void CharacterActor::process_ai(double delta_time) { - float const distance = this->nav_agent->get_target_position().distance_squared_to(this->get_global_position()); - float const target_distance_sqr = std::pow(this->nav_agent->get_target_desired_distance(), 2.f); - if(!this->nav_agent->is_navigation_finished() && distance >= target_distance_sqr) { +bool CharacterActor::get_is_near_player() const { + return this->get_player_character()->get_global_position().distance_to(this->get_global_position()) < 5.f; +} + +CharacterActor *CharacterActor::get_player_character() const { + Ref game_mode = GameRoot::get_singleton()->get_game_mode(); + return game_mode->get_player_instance()->get_character(); +} + +void CharacterActor::set_state(goap::State state) { + switch(this->current_state.type) { + default: + break; + case goap::State::STATE_MOVE_TO: + this->nav_agent->set_target_position(this->get_global_position()); + break; + } + this->current_state = state; + switch(state.type) { + default: + break; + case goap::State::STATE_MOVE_TO: + this->move_to(state.move_to->get_global_position()); + break; + } +} + +void CharacterActor::process_behaviour(double delta_time) { + if(this->current_state.is_complete(this) || this->planner->is_action_complete()) + this->set_state(this->planner->get_next_state()); + switch(this->current_state.type) { + default: + break; + case goap::State::STATE_MOVE_TO: + if(this->nav_agent->get_target_position().distance_to(this->current_state.move_to->get_global_position()) > 2.f) + this->nav_agent->set_target_position(this->current_state.move_to->get_global_position()); + break; + case goap::State::STATE_ACTIVATE: + break; + case goap::State::STATE_ANIMATE: + break; + } +} + +void CharacterActor::process_navigation(double delta_time) { + float const distance_sqr = this->nav_agent->get_target_position().distance_squared_to(this->get_global_position()); + float const distance_target_sqr = std::pow(this->nav_agent->get_target_desired_distance(), 2.f); + if(!this->nav_agent->is_navigation_finished() && distance_sqr >= distance_target_sqr) { Vector3 const target_position = this->nav_agent->get_next_path_position(); Vector3 const direction = (target_position - this->get_global_position()).normalized(); if(this->nav_agent->get_avoidance_enabled()) diff --git a/src/character_actor.hpp b/src/character_actor.hpp index 9ee2f77..1d83bce 100644 --- a/src/character_actor.hpp +++ b/src/character_actor.hpp @@ -3,14 +3,18 @@ #include "character_data.hpp" #include "health.hpp" -#include "state.hpp" #include "projectile_pool.hpp" +#include "state.hpp" #include #include namespace godot { class NavigationAgent3D; class TunnelsPlayer; +class AnimationPlayer; +namespace goap { + class Planner; +}; class CharacterActor : public CharacterBody3D, public IHealthEntity { @@ -38,18 +42,18 @@ public: void set_rotation_speed_curve(Ref curve); Ref get_rotation_speed_curve() const; - virtual Health *get_health() override; virtual Health const *get_health() const override; - void set_character_data(Ref data); - void set_weapon_muzzle(Node3D *node); - void set_velocity_target(Vector3 value); Vector3 get_velocity_target() const; + bool get_is_near_player() const; + CharacterActor *get_player_character() const; + void set_state(goap::State state); protected: - void process_ai(double delta_time); + void process_behaviour(double delta_time); + void process_navigation(double delta_time); void process_rotation(double delta_time); void try_fire_weapon(); private: @@ -65,19 +69,21 @@ private: float fire_timer{0.f}; // the origin point for projectiles Node3D *weapon_muzzle{nullptr}; - // whatever the AI is currently targetting + // something that the AI wants to target Node *target{nullptr}; // the current state of the actor - goap::State current_state{}; // the current state + goap::State current_state{goap::State::new_invalid()}; + AnimationPlayer *anim_player{nullptr}; Health *health{nullptr}; ProjectilePool *primary_weapon_pool{nullptr}; NavigationAgent3D *nav_agent{nullptr}; + goap::Planner *planner{nullptr}; Ref rotation_speed_curve{}; // character data assigned when spawned Ref data; - float fire_interval{0.f}; // derived from the current weapon's rpm + float fire_interval{0.f}; // derived from 1 / the current weapon's rps static float const ACCELERATION; static float const WALK_SPEED; diff --git a/src/planner.cpp b/src/planner.cpp index 3e6808c..cdd1235 100644 --- a/src/planner.cpp +++ b/src/planner.cpp @@ -2,6 +2,7 @@ #include "action.hpp" #include "character_actor.hpp" #include "global_world_state.hpp" +#include "state.hpp" #include "utils/godot_macros.h" #include #include @@ -61,7 +62,6 @@ 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)); - GDFUNCTION_ARGS(gdscript_make_plan, "goal"); } void Planner::_ready() { @@ -79,21 +79,12 @@ static Vector> trace_path(FromMap &map, PlannerNode &end) { return edges; } -Array Planner::gdscript_make_plan(Ref goal) { - Vector> plan = this->make_plan(goal); - Array out{}; - int i{0}; - UtilityFunctions::print("plan len: ", plan.size()); - for(Ref const &action : plan) { - out.push_back(action); - UtilityFunctions::print("plan[", i++, "]: ", this->actions.find(action)); - } - return out; -} - -Vector> Planner::make_plan(Ref goal) { +Vector> Planner::make_plan() { // clear cache every planning phase this->cached_world_state.clear(); + Ref goal = this->select_goal(); + if(!goal.is_valid()) + return {}; // ordered list of all nodes still being considered Vector open{PlannerNode::goal_node(goal->goal_state)}; PlannerNode first = open.get(0); @@ -130,6 +121,20 @@ Vector> Planner::make_plan(Ref goal) { return {}; } +Ref Planner::select_goal() { + for(Ref const &goal : this->goals) { + bool can_try{true}; + for(WorldProperty const &prop : goal->prerequisites) { + if(prop.value != this->get_world_property(prop.key)) { + can_try = false; + break; + } + } + if(can_try) 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); @@ -172,13 +177,28 @@ Vector> Planner::find_actions_satisfying(WorldState requirements) { for(WorldProperty &prop : requirements) { if(act->effects.has(prop.key) && act->effects.get(prop.key) == prop.value - && this->can_do(act)) + && 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->actor); +} + void Planner::set_actions(Array value) { this->actions.clear(); this->actions.resize(value.size()); diff --git a/src/planner.hpp b/src/planner.hpp index 25add97..9bf9e73 100644 --- a/src/planner.hpp +++ b/src/planner.hpp @@ -46,8 +46,8 @@ class Planner : public Node { public: virtual void _ready() override; - Array gdscript_make_plan(Ref goal); - Vector> make_plan(Ref goal); + Vector> make_plan(); + Ref select_goal(); Variant get_world_property(StringName prop_key); @@ -55,9 +55,11 @@ public: Vector find_neighbours_of(PlannerNode &node); Vector> find_actions_satisfying(WorldState requirements); + bool is_action_complete(); + State get_next_state(); + void set_actions(Array actions); Array get_actions() const; - void set_goals(Array goals); Array get_goals() const; private: @@ -67,6 +69,7 @@ private: // configured settings Vector> actions{}; // available actions Vector> goals{}; // available goals + Vector> plan{}; }; struct PlannerNodeHasher { diff --git a/src/state.cpp b/src/state.cpp index 165c3e7..bdb1900 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -29,6 +29,10 @@ State State::new_activate(Node *node) { }; } +State State::new_invalid() { + return { .type = State::Type::STATE_TYPE_MAX }; +} + bool State::is_complete(CharacterActor *context) const { switch(this->type) { default: diff --git a/src/state.hpp b/src/state.hpp index 8a7e528..1e9986c 100644 --- a/src/state.hpp +++ b/src/state.hpp @@ -14,6 +14,7 @@ struct State { static State new_move_to(Node3D *location); static State new_animate(StringName animation); static State new_activate(Node *node); + static State new_invalid(); bool is_complete(CharacterActor *context) const;