diff --git a/godot/AI/maintain_health.tres b/godot/AI/maintain_health.tres index 6126e85..d8a7000 100644 --- a/godot/AI/maintain_health.tres +++ b/godot/AI/maintain_health.tres @@ -5,5 +5,6 @@ requirements_dict = { "is_health_safe": false } desired_state_dict = { +"can_see_target": false, "is_health_safe": true } diff --git a/godot/GameObjects/enemy_unit.tscn b/godot/GameObjects/enemy_unit.tscn index f408f0d..2547fc1 100644 --- a/godot/GameObjects/enemy_unit.tscn +++ b/godot/GameObjects/enemy_unit.tscn @@ -5,7 +5,7 @@ [ext_resource type="AnimationLibrary" uid="uid://crkh5gahl2ci6" path="res://Animation/bean_characters.res" id="2_lrpu6"] [sub_resource type="SphereShape3D" id="SphereShape3D_5pqvg"] -radius = 7.38627 +radius = 8.42531 [sub_resource type="SphereShape3D" id="SphereShape3D_drlm2"] radius = 1.0 @@ -15,6 +15,8 @@ albedo_color = Color(1, 0.24, 0.24, 1) [sub_resource type="CapsuleMesh" id="CapsuleMesh_5r0b3"] material = SubResource("StandardMaterial3D_ss47r") +radial_segments = 8 +rings = 3 [sub_resource type="BoxMesh" id="BoxMesh_p8wvo"] size = Vector3(0.2, 0.2, 0.5) @@ -29,7 +31,7 @@ editor_available_goals = [ExtResource("2_k42dl"), ExtResource("1_b1qo1")] unique_name_in_owner = true [node name="Planner" type="Planner" parent="."] -actions_inspector = [3, 2, 4, 5] +actions_inspector = [3, 2, 4, 5, 6] unique_name_in_owner = true [node name="EntityHealth" type="EntityHealth" parent="."] @@ -37,6 +39,8 @@ unique_name_in_owner = true [node name="NavigationAgent3D" type="NavigationAgent3D" parent="."] unique_name_in_owner = true +path_desired_distance = 0.5 +target_desired_distance = 0.2 path_height_offset = 0.5 [node name="AwarenessArea" type="Area3D" parent="."] @@ -47,7 +51,7 @@ collision_mask = 2 input_ray_pickable = false [node name="CollisionShape3D" type="CollisionShape3D" parent="AwarenessArea"] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2.36036) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.238092) shape = SubResource("SphereShape3D_5pqvg") [node name="AnimationPlayer" type="AnimationPlayer" parent="."] diff --git a/godot/GameObjects/nav_marker.tscn b/godot/GameObjects/nav_marker.tscn new file mode 100644 index 0000000..725248e --- /dev/null +++ b/godot/GameObjects/nav_marker.tscn @@ -0,0 +1,5 @@ +[gd_scene format=3 uid="uid://c82s8vpaethtv"] + +[node name="NavMarker" type="NavMarker"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.68886, 0.383154, -2.15328) +gizmo_extents = 1.0 diff --git a/godot/GameObjects/player_camera.tscn b/godot/GameObjects/player_camera.tscn index bf4836e..e740965 100644 --- a/godot/GameObjects/player_camera.tscn +++ b/godot/GameObjects/player_camera.tscn @@ -9,7 +9,7 @@ height = 0.1 ground_marker_scene = ExtResource("1_t46fa") [node name="Camera3D" type="Camera3D" parent="."] -transform = Transform3D(-1, 7.57104e-08, -4.37114e-08, 0, 0.5, 0.866025, 8.74228e-08, 0.866025, -0.5, 0, 8.41077, -4.42292) +transform = Transform3D(-1, 7.57104e-08, -4.37114e-08, 0, 0.5, 0.866025, 8.74228e-08, 0.866025, -0.5, 0, 10.1792, -3.46957) [node name="MeshInstance3D" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0494189, 0) diff --git a/godot/GameObjects/player_unit.tscn b/godot/GameObjects/player_unit.tscn index 3db90ae..f08225f 100644 --- a/godot/GameObjects/player_unit.tscn +++ b/godot/GameObjects/player_unit.tscn @@ -10,6 +10,8 @@ albedo_color = Color(0.407843, 1, 0.447059, 1) [sub_resource type="CapsuleMesh" id="CapsuleMesh_5r0b3"] material = SubResource("StandardMaterial3D_n4q15") +radial_segments = 8 +rings = 3 [sub_resource type="BoxMesh" id="BoxMesh_p8wvo"] size = Vector3(0.2, 0.2, 0.5) @@ -31,6 +33,8 @@ unique_name_in_owner = true [node name="NavigationAgent3D" type="NavigationAgent3D" parent="."] unique_name_in_owner = true +path_desired_distance = 0.2 +target_desired_distance = 0.5 path_height_offset = 0.5 debug_enabled = true diff --git a/godot/Levels/test_level.tscn b/godot/Levels/test_level.tscn index d5eaf48..69a3499 100644 --- a/godot/Levels/test_level.tscn +++ b/godot/Levels/test_level.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=10 format=3 uid="uid://c62s1jmtgajjk"] +[gd_scene load_steps=11 format=3 uid="uid://c62s1jmtgajjk"] [ext_resource type="PackedScene" uid="uid://dsalxxq3xs842" path="res://rts_game_mode.tscn" id="1_4nchg"] [ext_resource type="Environment" uid="uid://cnfk8yrvklysq" path="res://Environments/default_environment.tres" id="2_jq6bw"] [ext_resource type="PackedScene" uid="uid://pme230qx1377" path="res://GameObjects/player_unit.tscn" id="3_wl7wm"] [ext_resource type="PackedScene" uid="uid://ba17jrcaduowj" path="res://GameObjects/enemy_unit.tscn" id="4_0o33v"] +[ext_resource type="PackedScene" uid="uid://c82s8vpaethtv" path="res://GameObjects/nav_marker.tscn" id="5_ta2oq"] [sub_resource type="NavigationMesh" id="NavigationMesh_8a2j6"] vertices = PackedVector3Array(0, 0.4, -2, 1.75, 0.4, -3.25, 1.75, 0.4, -9.5, -0.75, 0.4, -0.25, 0, 0.4, -2, 1.75, 0.4, -9.5, -9.5, 0.4, -9.5, -9.5, 0.4, -0.25, 4.5, 0.4, -1.25, 4.5, 0.4, -0.5, 9.5, 0.4, -0.5, 3.25, 0.4, -2.5, 4.5, 0.4, -1.25, 9.5, 0.4, -0.5, 1.75, 0.4, -9.5, 1.75, 0.4, -3.25, 3.25, 0.4, -2.5, 9.5, 0.4, -0.5, 9.5, 0.4, -9.5, 1.75, 3.4, 1, 2.25, 3.4, 1, 3.5, 3.4, -0.5, 2.75, 3.4, -1.5, 1.5, 3.4, -2, 0.25, 3.4, -0.5, 3, 0.4, -0.5, 2.5, 0.4, -1.25, 1.5, 0.4, -1.5, 0.75, 0.4, -0.5, 1.75, 0.4, 0.5, 9.5, 0.4, -0.5, 4.5, 0.4, -0.5, 3.5, 0.4, 1.25, 9.5, 0.4, -0.5, 3.5, 0.4, 1.25, 2, 0.4, 2.25, 2, 0.4, 9.5, 9.5, 0.4, 9.5, 2, 0.4, 9.5, 2, 0.4, 2.25, 0, 0.4, 1, 0, 0.4, 1, -0.75, 0.4, -0.25, -9.5, 0.4, -0.25, -9.5, 0.4, 9.5, 2, 0.4, 9.5) @@ -64,3 +65,37 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.7165, 1.8999e-07, 3.29106) [node name="Unit2" parent="." instance=ExtResource("4_0o33v")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.57207, -7.63685e-07, -6.40453) + +[node name="NavRoom" type="NavRoom" parent="."] + +[node name="NavMarker" parent="NavRoom" instance=ExtResource("5_ta2oq")] +marker_type = 1 +transform = Transform3D(0.80479, 0, -0.593559, 0, 1, 0, 0.593559, 0, 0.80479, 3.65141, 0.4, -2.09859) + +[node name="NavMarker2" parent="NavRoom" instance=ExtResource("5_ta2oq")] +marker_type = 1 +transform = Transform3D(0.794658, 0, -0.607057, 0, 1, 0, 0.607057, 0, 0.794658, 3.07483, 0.4, -2.58758) + +[node name="NavMarker3" parent="NavRoom" instance=ExtResource("5_ta2oq")] +marker_type = 1 +transform = Transform3D(-0.784766, 0, 0.619792, 0, 1, 0, -0.619792, 0, -0.784766, 0.723487, 0.4, 1.45218) + +[node name="NavMarker4" parent="NavRoom" instance=ExtResource("5_ta2oq")] +marker_type = 1 +transform = Transform3D(-0.628248, 0, -0.778013, 0, 1, 0, 0.778013, 0, -0.628248, 3.51616, 0.4, 1.22172) + +[node name="NavMarker8" parent="NavRoom" instance=ExtResource("5_ta2oq")] +marker_type = 1 +transform = Transform3D(-0.635135, 0, -0.772401, 0, 1, 0, 0.772401, 0, -0.635135, 3.91311, 0.4, 0.527064) + +[node name="NavMarker5" parent="NavRoom" instance=ExtResource("5_ta2oq")] +marker_type = 1 +transform = Transform3D(-0.762273, 0, 0.647256, 0, 1, 0, -0.647256, 0, -0.762273, 0.0761999, 0.4, 1.04762) + +[node name="NavMarker6" parent="NavRoom" instance=ExtResource("5_ta2oq")] +marker_type = 1 +transform = Transform3D(0.652363, 0, 0.757906, 0, 1, 0, -0.757906, 0, 0.652363, -0.132873, 0.4, -1.68996) + +[node name="NavMarker7" parent="NavRoom" instance=ExtResource("5_ta2oq")] +marker_type = 1 +transform = Transform3D(0.668508, 0, 0.743705, 0, 1, 0, -0.743705, 0, 0.668508, 0.277942, 0.4, -2.19853) diff --git a/src/enemy_world_state.cpp b/src/enemy_world_state.cpp index bd2d296..711b805 100644 --- a/src/enemy_world_state.cpp +++ b/src/enemy_world_state.cpp @@ -1,8 +1,7 @@ #include "enemy_world_state.hpp" #include "entity_health.hpp" -#include "goap/goal.hpp" -#include "godot_cpp/variant/utility_functions.hpp" #include "unit.hpp" +#include "goap/goal.hpp" #include "utils/godot_macros.hpp" #include @@ -14,7 +13,7 @@ void EnemyWorldState::_bind_methods() { GDRESOURCETYPE("Goal")); } -void EnemyWorldState::_ready() { +void EnemyWorldState::_ready() { GDGAMEONLY(); this->awareness_area = this->get_node("%AwarenessArea"); this->awareness_area->connect("body_entered", callable_mp(this, &EnemyWorldState::on_awareness_entered)); this->awareness_area->connect("body_exited", callable_mp(this, &EnemyWorldState::on_awareness_exited)); @@ -36,6 +35,8 @@ void EnemyWorldState::on_awareness_exited(gd::Node3D *node) { } void EnemyWorldState::on_damaged(int remaining, int delta, Unit *source) { + if(this->parent_unit->has_plan()) + return; float highest_priority{0.f}; Unit *highest_priority_unit{this->select_target_from_known_with_priority(&highest_priority)}; float const priority{this->calculate_priority(source)}; diff --git a/src/goap/planner.cpp b/src/goap/planner.cpp index 4fbd37e..4fe7f34 100644 --- a/src/goap/planner.cpp +++ b/src/goap/planner.cpp @@ -1,10 +1,10 @@ #include "planner.hpp" #include "action_db.hpp" -#include "godot_cpp/classes/engine.hpp" -#include "godot_cpp/classes/global_constants.hpp" #include "godot_cpp/templates/hashfuncs.hpp" #include "utils/godot_macros.hpp" #include +#include +#include #include namespace goap { @@ -16,18 +16,20 @@ WorldStateNode::WorldStateNode(WorldStateNode const &last_state, Action const *l , open_requirements{last_state.open_requirements} , last_action{last_action} , context{last_state.context} { - for(WorldProperty const &req : last_action->get_required()) + for(WorldProperty const &req : last_action->get_required()) { this->open_requirements[req.key] = req.value; + this->state[req.key] = this->context->get_world_property(req.key); + } for(WorldProperty const &effect : last_action->get_effects()) - if(this->open_requirements.has(effect.key)) - this->state[effect.key] = effect.value; + this->state[effect.key] = effect.value; } int WorldStateNode::requirements_unmet() const { int requirements = this->open_requirements.size(); for(WorldProperty const &req : this->open_requirements) { - if(this->state.has(req.key) && this->state[req.key] == req.value) { - --requirements; + if(this->state.has(req.key)) { + if(this->state[req.key] == req.value) + --requirements; } else if(this->context->check_property_match(req)) { --requirements; } @@ -63,19 +65,19 @@ bool operator!=(goap::WorldStateNode const &lhs, goap::WorldStateNode const &rhs } bool operator<(goap::WorldStateNode const &lhs, goap::WorldStateNode const &rhs) { - return lhs.state.size() < rhs.state.size(); + return lhs.requirements_unmet() < rhs.requirements_unmet(); } bool operator<=(goap::WorldStateNode const &lhs, goap::WorldStateNode const &rhs) { - return lhs.state.size() <= rhs.state.size(); + return lhs.requirements_unmet() <= rhs.requirements_unmet(); } bool operator>(goap::WorldStateNode const &lhs, goap::WorldStateNode const &rhs) { - return lhs.state.size() > rhs.state.size(); + return lhs.requirements_unmet() > rhs.requirements_unmet(); } bool operator>=(goap::WorldStateNode const &lhs, goap::WorldStateNode const &rhs) { - return lhs.state.size() >= rhs.state.size(); + return lhs.requirements_unmet() >= rhs.requirements_unmet(); } void Planner::_bind_methods() { diff --git a/src/nav_marker.cpp b/src/nav_marker.cpp new file mode 100644 index 0000000..4e255b3 --- /dev/null +++ b/src/nav_marker.cpp @@ -0,0 +1,32 @@ +#include "nav_marker.hpp" +#include "utils/godot_macros.hpp" +#include +#include +#include +#include +#include +#include + +void NavMarker::_bind_methods() { +#define CLASSNAME NavMarker + GDPROPERTY_HINTED(marker_type, gd::Variant::INT, gd::PROPERTY_HINT_ENUM, MarkerType::get_property_hint()); +} + +void NavMarker::_process(double delta_time) { +#ifdef DEBUG_ENABLED + GDEDITORONLY() + if(gd::EditorInterface::get_singleton()->get_selection()->get_selected_nodes().has(this)) { + gd::RID const map_id{this->get_world_3d()->get_navigation_map()}; + gd::Vector3 const new_point{gd::NavigationServer3D::get_singleton()->map_get_closest_point(map_id, this->get_global_position())}; + this->set_global_position(new_point); + } +#endif // DEBUG +} + +void NavMarker::set_marker_type(int type) { + this->type = type; +} + +int NavMarker::get_marker_type() const { + return this->type; +} diff --git a/src/nav_marker.hpp b/src/nav_marker.hpp new file mode 100644 index 0000000..95ef037 --- /dev/null +++ b/src/nav_marker.hpp @@ -0,0 +1,26 @@ +#ifndef NAV_MARKER_HPP +#define NAV_MARKER_HPP + +#include "utils/godot_macros.hpp" +#include + +namespace gd = godot; + +GDENUM(MarkerType, + Generic, + Cover +); + +class NavMarker : public gd::Marker3D { + GDCLASS(NavMarker, gd::Marker3D); + static void _bind_methods(); +public: + virtual void _process(double delta_time) override; + + void set_marker_type(int type); + int get_marker_type() const; +private: + MarkerType type{MarkerType::Generic}; +}; + +#endif // !NAV_MARKER_HPP diff --git a/src/nav_room.cpp b/src/nav_room.cpp new file mode 100644 index 0000000..37598c1 --- /dev/null +++ b/src/nav_room.cpp @@ -0,0 +1,60 @@ +#include "nav_room.hpp" +#include "godot_cpp/classes/global_constants.hpp" +#include "nav_marker.hpp" +#include "utils/godot_macros.hpp" +#include + +void NavRoom::_bind_methods() { +#define CLASSNAME NavRoom + GDPROPERTY_HINTED(editor_neighbours, gd::Variant::ARRAY, gd::PROPERTY_HINT_ARRAY_TYPE, GDNODETYPE("NavRoom")); +} + +NavRoom *NavRoom::get_closest_room(gd::Vector3 const &closest_to) { + NavRoom *candidate{nullptr}; + float shortest_distance{INFINITY}; + for(NavRoom *room : NavRoom::rooms) { + float const distance{room->get_global_position().distance_squared_to(closest_to)}; + if(distance < shortest_distance) { + shortest_distance = distance; + candidate = room; + } + } + return candidate; +} + +void NavRoom::_enter_tree() { GDGAMEONLY() + NavRoom::rooms.push_back(this); + this->connect("child_entered_tree", callable_mp(this, &NavRoom::on_child_entered)); +} + +void NavRoom::_exit_tree() { GDGAMEONLY() + NavRoom::rooms.erase(this); +} + +void NavRoom::on_child_entered(gd::Node *child) { + if(NavMarker *marker{gd::Object::cast_to(child)}) + this->markers.push_back(marker); +} + +gd::Vector const &NavRoom::get_neighbours() const { + return this->neighbours; +} + +gd::Vector const &NavRoom::get_markers() const { + return this->markers; +} + +gd::Array NavRoom::get_editor_neighbours() const { + gd::Array a{}; + for(NavRoom *room : this->neighbours) + a.push_back(a); + return a; +} + +void NavRoom::set_editor_neighbours(gd::Array array) { + this->neighbours.clear(); + while(!array.is_empty()) if(NavRoom *room{gd::Object::cast_to(array.pop_front())}) + this->neighbours.push_back(room); +} + +gd::Vector NavRoom::rooms{}; diff --git a/src/nav_room.hpp b/src/nav_room.hpp new file mode 100644 index 0000000..24f55e8 --- /dev/null +++ b/src/nav_room.hpp @@ -0,0 +1,33 @@ +#ifndef NAV_ROOM_HPP +#define NAV_ROOM_HPP + +#include +#include + +namespace gd = godot; + +class NavMarker; + +class NavRoom : public gd::Node3D { + GDCLASS(NavRoom, gd::Node3D); + static void _bind_methods(); +public: + static NavRoom *get_closest_room(gd::Vector3 const &closest_to); + virtual void _enter_tree() override; + virtual void _exit_tree() override; + virtual void on_child_entered(gd::Node *child); + gd::Vector const &get_neighbours() const; + gd::Vector const &get_markers() const; +private: + void set_editor_neighbours(gd::Array array); + gd::Array get_editor_neighbours() const; +private: + float radius{1.f}; + gd::Vector3 centre{0.f, 0.f, 0.f}; + gd::Vector markers{}; + gd::Vector neighbours{}; + + static gd::Vector rooms; +}; + +#endif // !NAV_ROOM_HPP diff --git a/src/register_types.cpp b/src/register_types.cpp index 6a57c8c..6060985 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -2,6 +2,8 @@ #include "enemy_world_state.hpp" #include "entity_health.hpp" #include "goap/state.hpp" +#include "nav_marker.hpp" +#include "nav_room.hpp" #include "rts_actions.hpp" #include "rts_game_mode.hpp" #include "rts_player.hpp" @@ -36,6 +38,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) goap::ActionDB::register_action(); goap::ActionDB::register_action(); goap::ActionDB::register_action(); + goap::ActionDB::register_action(); ClassDB::register_class(); ClassDB::register_class(); @@ -53,6 +56,8 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); } extern "C" diff --git a/src/rts_actions.cpp b/src/rts_actions.cpp index 1a3a17b..b3de8a7 100644 --- a/src/rts_actions.cpp +++ b/src/rts_actions.cpp @@ -1,9 +1,13 @@ #include "rts_actions.hpp" +#include "nav_marker.hpp" +#include "nav_room.hpp" #include "rts_states.hpp" #include "goap/actor_world_state.hpp" #include "goap/state.hpp" #include +#include #include +#include MoveToTarget::MoveToTarget() : Action() { @@ -24,8 +28,8 @@ goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const FireAtTarget::FireAtTarget() : Action() { - this->effects.insert("is_target_dead", true); this->required.insert("can_see_target", true); + this->effects.insert("is_target_dead", true); } goap::State *FireAtTarget::get_apply_state(goap::ActorWorldState *context) const { @@ -50,8 +54,8 @@ goap::State *FindTarget::get_apply_state(goap::ActorWorldState *context) const { GetInMeleeRange::GetInMeleeRange() : Action() { this->require_state_complete = false; - this->effects.insert("is_in_melee_range", true); this->required.insert("can_see_target", true); + this->effects.insert("is_in_melee_range", true); } goap::State *GetInMeleeRange::get_apply_state(goap::ActorWorldState *context) const { @@ -63,9 +67,9 @@ goap::State *GetInMeleeRange::get_apply_state(goap::ActorWorldState *context) co MeleeAttack::MeleeAttack() : Action() { - this->effects.insert("is_target_dead", true); this->required.insert("can_see_target", true); this->required.insert("is_in_melee_range", true); + this->effects.insert("is_target_dead", true); } goap::State *MeleeAttack::get_apply_state(goap::ActorWorldState *context) const { @@ -76,6 +80,7 @@ goap::State *MeleeAttack::get_apply_state(goap::ActorWorldState *context) const TankSelfHeal::TankSelfHeal() : Action() { + this->required.insert("can_see_target", false); this->effects.insert("is_health_safe", true); } @@ -84,3 +89,48 @@ goap::State *TankSelfHeal::get_apply_state(goap::ActorWorldState *context) const state->animation = "self_heal"; return state; } + +TakeCover::TakeCover() +: Action () { + this->require_state_complete = false; + this->effects.insert("can_see_target", false); +} + +bool TakeCover::procedural_is_possible(goap::ActorWorldState *context) const { + gd::Vector3 const global_position{context->get_world_property("parent_global_position")}; + gd::Vector3 const target_position{context->get_world_property("target_global_position")}; + NavRoom *room{NavRoom::get_closest_room(global_position)}; + if(room == nullptr) + return false; + for(NavMarker *marker : room->get_markers()) + if(TakeCover::is_marker_cover_from(marker, target_position)) + return true; + return false; +} + +goap::State *TakeCover::get_apply_state(goap::ActorWorldState *context) const { + MoveTo *state{this->create_state()}; + gd::Vector3 const global_position{context->get_world_property("parent_global_position")}; + gd::Vector3 const target_position{context->get_world_property("target_global_position")}; + NavRoom *room{NavRoom::get_closest_room(global_position)}; + if(room == nullptr) + return nullptr; + float best_score{-INFINITY}; + for(NavMarker *marker : room->get_markers()) { + gd::Vector3 const marker_position{marker->get_global_position()}; + float const score{100.f - marker_position.distance_to(global_position)}; + if(score > best_score && TakeCover::is_marker_cover_from(marker, target_position)) { + best_score = score; + state->target_node = marker; + gd::UtilityFunctions::print("!!! best cover so far: ", marker->get_path()); + } + } + return state; +} + +bool TakeCover::is_marker_cover_from(NavMarker *marker, gd::Vector3 const &target) { + return marker->get_marker_type() == MarkerType::Cover + && marker->get_global_basis() + .get_column(2) + .dot((marker->get_global_position() - target).normalized()) < -0.7f; +} diff --git a/src/rts_actions.hpp b/src/rts_actions.hpp index 3e77448..122ebc1 100644 --- a/src/rts_actions.hpp +++ b/src/rts_actions.hpp @@ -40,10 +40,20 @@ public: }; class TankSelfHeal : public goap::Action { - GOAP_ACTION(SelfHeal); + GOAP_ACTION(TankSelfHeal); public: TankSelfHeal(); virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; }; +class TakeCover : public goap::Action { + GOAP_ACTION(TakeCover); +public: + TakeCover(); + virtual bool procedural_is_possible(goap::ActorWorldState *context) const override; + virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; +private: + static bool is_marker_cover_from(class NavMarker *marker, gd::Vector3 const &target); +}; + #endif // !RTS_ACTIONS_HPP diff --git a/src/rts_states.cpp b/src/rts_states.cpp index 5ca4ceb..25f39aa 100644 --- a/src/rts_states.cpp +++ b/src/rts_states.cpp @@ -7,10 +7,16 @@ void MoveTo::_bind_methods() {} void MoveTo::_ready() { - this->agent = this->get_node("../NavigationAgent3D"); - this->agent->set_target_position(this->target_node->get_global_position()); this->parent_node3d = Object::cast_to(this->get_parent()); + this->agent = this->get_node("../NavigationAgent3D"); + if(this->target_node == nullptr) { + this->state_ended(); + gd::UtilityFunctions::push_warning("failed to start MoveTo state due to missing target"); + return; + } + this->agent->set_target_position(this->target_node->get_global_position()); this->calculate_path(); + gd::UtilityFunctions::print(this->parent_node3d->get_path(), " MoveTo ", this->target_node->get_path()); } void MoveTo::_end_state() { @@ -65,6 +71,8 @@ void Animate::_bind_methods() {} void Animate::_ready() { this->anim = this->get_node("../AnimationPlayer"); this->anim->play(this->animation); + if(!this->anim->has_animation(this->animation)) + this->state_ended(); } void Animate::_process(double delta_time) { diff --git a/src/unit_world_state.cpp b/src/unit_world_state.cpp index 8f1fea1..095c690 100644 --- a/src/unit_world_state.cpp +++ b/src/unit_world_state.cpp @@ -50,7 +50,9 @@ bool UnitWorldState::get_can_see_target() const { target_position, 0x1 | 0x4, ignore_list) }; - return this->parent_unit->get_world_3d()->get_direct_space_state()->intersect_ray(query).is_empty(); + bool const can_see{this->parent_unit->get_world_3d()->get_direct_space_state()->intersect_ray(query).is_empty()}; + gd::UtilityFunctions::print(this->parent_unit->get_path(), can_see ? " can see " : " can't see ", this->target_node->get_path()); + return can_see; } bool UnitWorldState::get_is_at_target() const {