diff --git a/godot/Animation/bean_characters.res b/godot/Animation/bean_characters.res index 1224374..0dfb739 100644 Binary files a/godot/Animation/bean_characters.res and b/godot/Animation/bean_characters.res differ diff --git a/godot/GameObjects/player_unit.tscn b/godot/GameObjects/player_unit.tscn index 7e4d1b7..a115082 100644 --- a/godot/GameObjects/player_unit.tscn +++ b/godot/GameObjects/player_unit.tscn @@ -27,7 +27,7 @@ collision_mask = 0 unique_name_in_owner = true [node name="Planner" type="Planner" parent="."] -actions_inspector = [0, 1, 2, 3] +actions_inspector = [0, 1, 2, 3, 6] unique_name_in_owner = true [node name="EntityHealth" type="EntityHealth" parent="."] diff --git a/godot/Levels/test_level.tscn b/godot/Levels/test_level.tscn index 8e39291..0eba452 100644 --- a/godot/Levels/test_level.tscn +++ b/godot/Levels/test_level.tscn @@ -206,39 +206,40 @@ size = Vector3(39.7404, 10.5501, 8.32035) [node name="NavigationObstacle3D" type="NavigationObstacle3D" parent="WorldEnvironment/NavigationRegion3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.64773, 0) +visible = false vertices = PackedVector3Array(-50, 0, 50, -50, 0, -50, 50, 0, -50, 50, 0, 50) affect_navigation_mesh = true carve_navigation_mesh = true -[node name="UtilityLock" type="UtilityLock" parent="WorldEnvironment"] +[node name="UtilityLock" type="UtilityLock" parent="WorldEnvironment/NavigationRegion3D"] allowed_items = [3] +animation_name = "activate_crouched" goal = SubResource("Goal_yju55") transform = Transform3D(0.0142588, 0, 0.999898, 0, 1, 0, -0.999898, 0, 0.0142588, -4.68514, 1.8999e-07, 2.96901) collision_layer = 4 collision_mask = 0 script = SubResource("GDScript_2bv87") -[node name="CollisionShape3D" type="CollisionShape3D" parent="WorldEnvironment/UtilityLock"] +[node name="CollisionShape3D" type="CollisionShape3D" parent="WorldEnvironment/NavigationRegion3D/UtilityLock"] shape = SubResource("SphereShape3D_pptgx") -[node name="StaticBody3D" type="StaticBody3D" parent="WorldEnvironment/UtilityLock"] +[node name="StaticBody3D" type="StaticBody3D" parent="WorldEnvironment/NavigationRegion3D/UtilityLock"] -[node name="CollisionShape3D" type="CollisionShape3D" parent="WorldEnvironment/UtilityLock/StaticBody3D"] +[node name="CollisionShape3D" type="CollisionShape3D" parent="WorldEnvironment/NavigationRegion3D/UtilityLock/StaticBody3D"] transform = Transform3D(1, 0, 3.1664e-08, 0, 1, 0, -3.1664e-08, 0, 1, 0.0699434, 0.984067, -2.11005) shape = SubResource("BoxShape3D_3h2p0") -[node name="NavigationObstacle3D" type="NavigationObstacle3D" parent="WorldEnvironment/UtilityLock/StaticBody3D"] +[node name="NavigationObstacle3D" type="NavigationObstacle3D" parent="WorldEnvironment/NavigationRegion3D/UtilityLock/StaticBody3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0515832, 0.0826392, -2.12334) -height = 2.0 +height = 4.0 vertices = PackedVector3Array(-1, 0, 3, -1, 0, -3, 1, 0, -3, 1, 0, 3) -carve_navigation_mesh = true -[node name="MeshInstance3D" type="MeshInstance3D" parent="WorldEnvironment/UtilityLock"] +[node name="MeshInstance3D" type="MeshInstance3D" parent="WorldEnvironment/NavigationRegion3D/UtilityLock"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.136569, 0) mesh = SubResource("CylinderMesh_r0j0v") surface_material_override/0 = SubResource("StandardMaterial3D_emsq8") -[node name="MeshInstance3D2" type="MeshInstance3D" parent="WorldEnvironment/UtilityLock"] +[node name="MeshInstance3D2" type="MeshInstance3D" parent="WorldEnvironment/NavigationRegion3D/UtilityLock"] transform = Transform3D(0.984294, -0.176535, 0, 0.176535, 0.984294, 0, 0, 0, 1, 0, 0.857907, -1.99146) mesh = SubResource("BoxMesh_bl5l6") @@ -394,3 +395,5 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.0413, -7.63685e-07, 2.867 [node name="Tank4" parent="." instance=ExtResource("4_0o33v")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.4329, -7.63685e-07, 6.82909) + +[connection signal="finish_activate" from="WorldEnvironment/NavigationRegion3D/UtilityLock" to="WorldEnvironment/NavigationRegion3D/UtilityLock" method="_on_finish_activate"] diff --git a/godot/project.godot b/godot/project.godot index fe35f0a..41b9b71 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -87,6 +87,16 @@ DEBUG_toggle_debug={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":96,"key_label":0,"unicode":96,"location":0,"echo":false,"script":null) ] } +zoom_in={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":8,"position":Vector2(179, 12),"global_position":Vector2(188, 58),"factor":1.0,"button_index":4,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} +zoom_out={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":16,"position":Vector2(227, 19),"global_position":Vector2(236, 65),"factor":1.0,"button_index":5,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} [layer_names] diff --git a/src/register_types.cpp b/src/register_types.cpp index c1c18fc..b5dd65f 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -45,6 +45,7 @@ void initialize_gdextension_types(gd::ModuleInitializationLevel p_level) goap::ActionDB::register_action(); goap::ActionDB::register_action(); goap::ActionDB::register_action(); + goap::ActionDB::register_action(); // same for items, // make sure ItemDB::get_enum_hint is fully populated out before _bind_methods. diff --git a/src/rts_actions.cpp b/src/rts_actions.cpp index 4a90b51..660bb24 100644 --- a/src/rts_actions.cpp +++ b/src/rts_actions.cpp @@ -2,6 +2,7 @@ #include "nav_marker.hpp" #include "nav_room.hpp" #include "rts_states.hpp" +#include "utility_lock.hpp" #include "goap/actor_world_state.hpp" #include "goap/state.hpp" #include @@ -19,11 +20,10 @@ goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const if(target == nullptr) { gd::UtilityFunctions::push_warning("Failed to get target node of ", context->get_path()); return nullptr; - } else { - MoveTo *state{this->create_state()}; - state->target_node = target; - return state; } + MoveTo *state{this->create_state()}; + state->target_node = target; + return state; } UseWeapon::UseWeapon() @@ -140,6 +140,40 @@ float TakeCover::score_cover_marker(class NavMarker* marker, const gd::Vector3& float const context_distance{marker_position.distance_to(context_position)}; // score is a weighted comparison between distances to the target and context // marker distance from context grows faster than marker distance from target - return (target_distance) - (context_distance); + return target_distance - context_distance; } +ActivateLock::ActivateLock() { + this->required.insert("is_at_target", true); + this->effects.insert("is_target_activated", true); +} + +bool ActivateLock::procedural_is_possible(goap::ActorWorldState *context) const { + Unit *context_unit{gd::Object::cast_to(context->get_parent())}; + if(context_unit == nullptr) + return false; + UnitWorldState *unit_context{context_unit->get_world_state()}; + if(unit_context == nullptr) + return false; + UtilityLock *lock{gd::Object::cast_to(unit_context->get_target_node())}; + if(lock == nullptr) + return false; + return lock->can_use(context_unit->get_node("%Inventory")->get_utility(), context_unit); +} + +goap::State *ActivateLock::get_apply_state(goap::ActorWorldState *context) const { + UnitWorldState *unit_context{gd::Object::cast_to(context)}; + if(unit_context == nullptr) { + gd::UtilityFunctions::push_error("ActivateLock: Context '", context->get_path(), "' is not a UnitWorldState"); + return nullptr; + } + UtilityLock *lock{gd::Object::cast_to(unit_context->get_target_node())}; + if(lock == nullptr) { + gd::UtilityFunctions::push_error("ActivateLock: Target for '", context->get_path(), "' is not a UtilityLock"); + } + Activate *activate = this->create_state(); + activate->lock = lock; + if(Inventory *inventory{unit_context->get_node("%Inventory")}) + activate->using_item = inventory->get_utility(); + return activate; +} diff --git a/src/rts_actions.hpp b/src/rts_actions.hpp index 8c9af95..23b8640 100644 --- a/src/rts_actions.hpp +++ b/src/rts_actions.hpp @@ -50,4 +50,12 @@ private: float score_cover_marker(class NavMarker *marker, gd::Vector3 const &target_position, gd::Vector3 const &context_position) const; }; +class ActivateLock : public goap::Action { + GOAP_ACTION(ActivateLock); +public: + ActivateLock(); + virtual bool procedural_is_possible(goap::ActorWorldState *context) const override; + virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; +}; + #endif // !RTS_ACTIONS_HPP diff --git a/src/rts_player.cpp b/src/rts_player.cpp index a123b57..5ec4d61 100644 --- a/src/rts_player.cpp +++ b/src/rts_player.cpp @@ -18,6 +18,9 @@ void RTSPlayer::_bind_methods() { void RTSPlayer::_ready() { this->camera = this->get_node("Camera3D"); + this->local_zoom_direction = this->camera->get_position().normalized(); + this->current_zoom_level = this->max_zoom_level = this->camera->get_position().length(); + this->set_zoom_level(this->current_zoom_level); } void RTSPlayer::_process(double delta_time) { @@ -41,6 +44,7 @@ void RTSPlayer::setup_player_input(utils::PlayerInput *input) { input->listen_to("rotate_left", "rotate_right", callable_mp(this, &RTSPlayer::on_rotate_horizontal)); input->listen_to("_mouse_left", "_mouse_right", callable_mp(this, &RTSPlayer::on_mouse_horizontal)); input->listen_to("_mouse_down", "_mouse_up", callable_mp(this, &RTSPlayer::on_mouse_vertical)); + input->listen_to("zoom_in", "zoom_out", callable_mp(this, &RTSPlayer::on_zoom)); #ifdef DEBUG_ENABLED input->listen_to("DEBUG_toggle_debug", callable_mp(this, &RTSPlayer::DEBUG_enable_debug)); #endif @@ -139,6 +143,14 @@ void RTSPlayer::select_unit(Unit *unit) { unit->connect("tree_exited", this->on_selected_unit_destroyed); } +void RTSPlayer::set_zoom_level(float level) { + this->current_zoom_level = gd::Math::clamp(level, this->min_zoom_level, this->max_zoom_level); + this->camera->set_position(this->local_zoom_direction * this->current_zoom_level); + gd::Vector3 const forward_vector{(this->camera->get_global_position() - this->get_global_position()).normalized()}; + gd::Vector3 const left_vector{gd::Vector3{0.f, 1.f, 0.f}.cross(forward_vector)}; + this->camera->set_global_basis({left_vector, forward_vector.cross(left_vector), forward_vector}); +} + void RTSPlayer::on_mouse_horizontal(gd::Ref, float value) { if(this->mmb_down) this->camera_mouse_motion.x = value; @@ -182,6 +194,9 @@ void RTSPlayer::on_lclick(gd::Ref, float value) { void RTSPlayer::on_mclick(gd::Ref, float value) { this->mmb_down = value != 0.f; } +void RTSPlayer::on_zoom(gd::Ref, float value) { + this->set_zoom_level(this->current_zoom_level + value * this->camera_zoom_speed); +} gd::Vector3 RTSPlayer::get_forward_direction() const { gd::Vector3 forward = this->get_global_basis().get_column(2); diff --git a/src/rts_player.hpp b/src/rts_player.hpp index 44ed5c4..7ba9823 100644 --- a/src/rts_player.hpp +++ b/src/rts_player.hpp @@ -40,6 +40,7 @@ private: void spawn_movement_marker(gd::Vector3 at); void clear_selected_unit(); void select_unit(Unit *unit); + void set_zoom_level(float level); // input functions void on_mouse_horizontal(gd::Ref, float value); @@ -50,12 +51,15 @@ private: void on_lclick(gd::Ref, float value); void on_rclick(gd::Ref, float value); void on_mclick(gd::Ref, float value); + void on_zoom(gd::Ref, float value); // setters & getters gd::Vector3 get_forward_direction() const; gd::Vector3 get_left_direction() const; void set_ground_marker_scene(gd::Ref scene); gd::Ref get_ground_marker_scene() const; +private: + gd::Callable const on_selected_unit_destroyed{callable_mp(this, &RTSPlayer::clear_selected_unit)}; private: Unit* selected_unit{nullptr}; @@ -75,13 +79,17 @@ private: gd::Vector3 cursor_camera_normal{0.f, 0.f, 0.f}; gd::Camera3D *camera{nullptr}; gd::Ref ground_marker_scene{nullptr}; - gd::Callable const on_selected_unit_destroyed{callable_mp(this, &RTSPlayer::clear_selected_unit)}; + gd::Vector3 local_zoom_direction{0.f, 0.f, 0.f}; + float current_zoom_level{1.f}; + float max_zoom_level{1.f}; + float const min_zoom_level{3.f}; float const camera_keys_speed{10.f}; float const camera_keys_rotation_speed{1.f}; float const camera_mouse_speed{0.01f}; float const camera_mouse_rotation_speed{-0.003f}; double const time_to_held{0.1}; + float const camera_zoom_speed{1.f}; #ifdef DEBUG_ENABLED private: bool DEBUG_debug_enabled{false}; diff --git a/src/rts_states.cpp b/src/rts_states.cpp index a955bd1..139b61b 100644 --- a/src/rts_states.cpp +++ b/src/rts_states.cpp @@ -82,10 +82,14 @@ void Activate::_ready() { return; } // start activating the lock - this->lock->begin_activate(this->using_item, this->parent_unit); + if(!this->lock->begin_activate(this->using_item, this->parent_unit)) { + this->end_state(); + return; + } + this->animation = this->lock->get_animation_name(); // play activate animation this->anim = this->parent_unit->get_node("%AnimationPlayer"); - if(!this->anim->has_animation(this->animation)) { + if(this->anim == nullptr || !this->anim->has_animation(this->animation)) { this->end_state(); return; } diff --git a/src/rts_states.hpp b/src/rts_states.hpp index fd193d3..88d50cc 100644 --- a/src/rts_states.hpp +++ b/src/rts_states.hpp @@ -40,9 +40,9 @@ public: virtual void _process(double) override; virtual void _end_state() override; UtilityLock *lock{nullptr}; - gd::String animation{}; -private: Item const *using_item{nullptr}; +private: + gd::String animation{}; gd::AnimationPlayer *anim{nullptr}; Unit *parent_unit{nullptr}; }; diff --git a/src/unit_world_state.hpp b/src/unit_world_state.hpp index c9f82dc..937ccf2 100644 --- a/src/unit_world_state.hpp +++ b/src/unit_world_state.hpp @@ -37,7 +37,6 @@ public: bool get_is_health_safe() const; gd::Vector3 get_parent_global_position() const; gd::Vector3 get_target_global_position() const; - void set_target_node(gd::Node3D *node); gd::Node3D *get_target_node() const; private: diff --git a/src/utility_lock.cpp b/src/utility_lock.cpp index 82012e5..b971794 100644 --- a/src/utility_lock.cpp +++ b/src/utility_lock.cpp @@ -1,10 +1,12 @@ #include "utility_lock.hpp" #include "item_db.hpp" +#include "unit.hpp" #include "utils/godot_macros.hpp" void UtilityLock::_bind_methods() { #define CLASSNAME UtilityLock GDPROPERTY_HINTED(allowed_items, gd::Variant::ARRAY, gd::PROPERTY_HINT_ARRAY_TYPE, ItemDB::get_array_hint()); + GDPROPERTY(animation_name, gd::Variant::STRING); GDSIGNAL("begin_activate", gd::PropertyInfo(gd::Variant::INT, "with_item"), gd::PropertyInfo(gd::Variant::OBJECT, "by_unit", gd::PROPERTY_HINT_NODE_TYPE, "Unit")); GDSIGNAL("finish_activate", gd::PropertyInfo(gd::Variant::INT, "with_item"), gd::PropertyInfo(gd::Variant::OBJECT, "by_unit", gd::PROPERTY_HINT_NODE_TYPE, "Unit")); GDSIGNAL("interrupt_activate"); @@ -60,3 +62,11 @@ gd::Array UtilityLock::get_allowed_items() const { array.push_back(item == nullptr ? 0 : item->get_id()); return array; } + +void UtilityLock::set_animation_name(gd::String animation) { + this->animation_name = animation; +} + +gd::String UtilityLock::get_animation_name() const { + return this->animation_name; +} diff --git a/src/utility_lock.hpp b/src/utility_lock.hpp index 1811cb8..4d62acf 100644 --- a/src/utility_lock.hpp +++ b/src/utility_lock.hpp @@ -29,9 +29,12 @@ public: void set_allowed_items(gd::Array array); gd::Array get_allowed_items() const; + void set_animation_name(gd::String animation); + gd::String get_animation_name() const; private: gd::HashSet allowed_items{}; Unit *user{nullptr}; + gd::String animation_name{}; ActivationState activation_state{ActivationState::Inactive}; };