diff --git a/modules/wave_survival/health_status.cpp b/modules/wave_survival/health_status.cpp index 785b69f4..3a325d30 100644 --- a/modules/wave_survival/health_status.cpp +++ b/modules/wave_survival/health_status.cpp @@ -37,10 +37,12 @@ int HealthStatus::get_health() const { } void HealthStatus::damage(int amount) { - amount = Math::abs(amount); - this->health -= amount; - emit_signal(sig_health_changed, this->health, -amount); - if (this->health <= 0) { - emit_signal(sig_death); + if (this->health > 0) { + amount = Math::abs(amount); + this->health -= amount; + emit_signal(sig_health_changed, this->health, -amount); + if (this->health <= 0) { + emit_signal(sig_death); + } } } diff --git a/modules/wave_survival/hitbox.cpp b/modules/wave_survival/hitbox.cpp new file mode 100644 index 00000000..8dc826da --- /dev/null +++ b/modules/wave_survival/hitbox.cpp @@ -0,0 +1,24 @@ +#include "hitbox.h" +#include "health_status.h" +#include "macros.h" + +void Hitbox::_bind_methods() { + BIND_HPROPERTY(Variant::OBJECT, health, PROPERTY_HINT_NODE_TYPE, "HealthStatus"); + BIND_PROPERTY(Variant::FLOAT, damage_modifier); +} + +void Hitbox::set_health(HealthStatus *value) { + this->health = value; +} + +HealthStatus *Hitbox::get_health() const { + return this->health; +} + +void Hitbox::set_damage_modifier(float value) { + this->damage_modifier = value; +} + +float Hitbox::get_damage_modifier() const { + return this->damage_modifier; +} diff --git a/modules/wave_survival/hitbox.h b/modules/wave_survival/hitbox.h new file mode 100644 index 00000000..b87bddf3 --- /dev/null +++ b/modules/wave_survival/hitbox.h @@ -0,0 +1,22 @@ +#ifndef HITBOX_H +#define HITBOX_H + +#include "scene/3d/physics/area_3d.h" +class HealthStatus; + +class Hitbox : public Area3D { + GDCLASS(Hitbox, Area3D); + static void _bind_methods(); + +public: + void set_health(HealthStatus *value); + HealthStatus *get_health() const; + void set_damage_modifier(float value); + float get_damage_modifier() const; + +private: + HealthStatus *health{ nullptr }; + float damage_modifier{ 1.f }; +}; + +#endif // !HITBOX_H diff --git a/modules/wave_survival/hitscan_muzzle.cpp b/modules/wave_survival/hitscan_muzzle.cpp index de4acd55..74eae655 100644 --- a/modules/wave_survival/hitscan_muzzle.cpp +++ b/modules/wave_survival/hitscan_muzzle.cpp @@ -1,4 +1,6 @@ #include "hitscan_muzzle.h" +#include "health_status.h" +#include "hitbox.h" #include "macros.h" #include "scene/resources/packed_scene.h" @@ -8,12 +10,35 @@ void HitscanMuzzle::_bind_methods() { BIND_PROPERTY(Variant::INT, ray_count); } +void HitscanMuzzle::instantiate_impact_effect() { + if (get_collider() == nullptr) { + return; + } + Node *effect_as_node{ this->impact_effect->instantiate() }; + if (Node3D * effect{ cast_to(effect_as_node) }) { + get_tree()->get_current_scene()->add_child(effect); + Vector3 const point{ get_collision_point() }; + Vector3 const normal{ get_collision_normal() }; + Vector3 const position{ get_global_position() }; + effect->look_at_from_position(point, point + normal, (point - position).normalized()); + } else if (effect_as_node) { + effect_as_node->queue_free(); + } +} + +void HitscanMuzzle::try_deal_damage() { + if (Hitbox * hitbox{ cast_to(get_collider()) }) { + hitbox->get_health()->damage(this->damage * hitbox->get_damage_modifier()); + } +} + void HitscanMuzzle::ready() { this->home_transform = get_transform(); this->impact_effect = ResourceLoader::load("res://objects/effects/bullet_impact.tscn"); if (!this->impact_effect.is_valid()) { print_error("HitscanMuzzle::ready: impact effect is invalid"); } + set_enabled(false); } void HitscanMuzzle::_notification(int what) { @@ -30,19 +55,13 @@ void HitscanMuzzle::_notification(int what) { } void HitscanMuzzle::shoot() { - set_transform(this->home_transform); - rotate_object_local(Vector3(0.f, 1.f, 0.f), Math::random(-Math::PI, Math::PI)); - rotate_object_local(Vector3(1.f, 0.f, 0.f), Math::random(0.f, this->spread)); - force_raycast_update(); - Node *effect_as_node{ this->impact_effect->instantiate() }; - if (Node3D * effect{ cast_to(effect_as_node) }) { - get_tree()->get_current_scene()->add_child(effect); - Vector3 const point{ get_collision_point() }; - Vector3 const normal{ get_collision_normal() }; - Vector3 const position{ get_global_position() }; - effect->look_at_from_position(point, point + normal, (point - position).normalized()); - } else if (effect_as_node) { - effect_as_node->queue_free(); + for (int i{ this->ray_count }; i > 0; --i) { + set_transform(this->home_transform); + rotate_object_local(Vector3(0.f, 1.f, 0.f), Math::random(-Math::PI, Math::PI)); + rotate_object_local(Vector3(1.f, 0.f, 0.f), Math::random(0.f, this->spread)); + force_raycast_update(); + instantiate_impact_effect(); + try_deal_damage(); } } diff --git a/modules/wave_survival/hitscan_muzzle.h b/modules/wave_survival/hitscan_muzzle.h index 59bcd6d6..357dd3f4 100644 --- a/modules/wave_survival/hitscan_muzzle.h +++ b/modules/wave_survival/hitscan_muzzle.h @@ -6,6 +6,8 @@ class HitscanMuzzle : public RayCast3D { GDCLASS(HitscanMuzzle, RayCast3D); static void _bind_methods(); + void instantiate_impact_effect(); + void try_deal_damage(); void ready(); public: diff --git a/modules/wave_survival/register_types.cpp b/modules/wave_survival/register_types.cpp index 1f2c5f29..0a0f19e1 100644 --- a/modules/wave_survival/register_types.cpp +++ b/modules/wave_survival/register_types.cpp @@ -4,6 +4,7 @@ #include "wave_survival/enemies/enemy_wretched.h" #include "wave_survival/enemy_body.h" #include "wave_survival/health_status.h" +#include "wave_survival/hitbox.h" #include "wave_survival/hitscan_muzzle.h" #include "wave_survival/npc_unit.h" #include "wave_survival/patrol_path.h" @@ -39,6 +40,7 @@ void initialize_wave_survival_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(WretchedChaseState); GDREGISTER_CLASS(WretchedAttackState); GDREGISTER_CLASS(PlayerDetector); + GDREGISTER_CLASS(Hitbox); } void uninitialize_wave_survival_module(ModuleInitializationLevel p_level) { diff --git a/project/objects/enemies/enemy_wretched.tscn b/project/objects/enemies/enemy_wretched.tscn index bd8b1c42..0bad656c 100644 --- a/project/objects/enemies/enemy_wretched.tscn +++ b/project/objects/enemies/enemy_wretched.tscn @@ -1,13 +1,63 @@ -[gd_scene load_steps=3 format=3 uid="uid://dqlqgk1veyos8"] +[gd_scene load_steps=7 format=3 uid="uid://dqlqgk1veyos8"] [ext_resource type="PackedScene" uid="uid://7esw31b76x4" path="res://assets/models/base_character.blend" id="1_qot2n"] +[sub_resource type="GDScript" id="GDScript_qot2n"] +script/source = "extends EnemyWretched + +func _on_health_status_death() -> void: + queue_free(); +" + +[sub_resource type="BoxShape3D" id="BoxShape3D_qot2n"] +size = Vector3(0.37524414, 0.64245605, 0.38867188) + +[sub_resource type="BoxShape3D" id="BoxShape3D_ei3ai"] +size = Vector3(0.24902344, 0.42858887, 0.2619629) + [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_ng1ul"] +[sub_resource type="BoxShape3D" id="BoxShape3D_sivli"] +size = Vector3(0.2770996, 1.5324097, 0.29003906) + [node name="EnemyWretched" type="EnemyWretched"] +script = SubResource("GDScript_qot2n") [node name="base_character" parent="." instance=ExtResource("1_qot2n")] +[node name="SpineAttach" type="BoneAttachment3D" parent="base_character/Character/Skeleton3D" index="0"] +transform = Transform3D(1, -2.2202515e-16, 2.1910396e-16, 2.1910385e-16, 0.9999122, 0.0132439565, -2.2202522e-16, -0.013243958, 0.99991226, -4.125641e-17, 1.1546817, -0.002712473) +bone_name = "spine.002" +bone_idx = 3 + +[node name="Hitbox" type="Hitbox" parent="base_character/Character/Skeleton3D/SpineAttach" node_paths=PackedStringArray("health")] +transform = Transform3D(1, 2.1910389e-16, -2.2202524e-16, -2.2202514e-16, 0.9999124, -0.013243958, 2.1910394e-16, 0.01324396, 0.9999123, -2.1234107e-16, -1.1546164, -0.012580322) +collision_layer = 2 +collision_mask = 0 +monitoring = false +health = NodePath("../../../../../HealthStatus") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="base_character/Character/Skeleton3D/SpineAttach/Hitbox"] +transform = Transform3D(1, -2.2202515e-16, 2.1910396e-16, 2.1910385e-16, 0.9999122, 0.0132439565, -2.2202522e-16, -0.013243958, 0.99991226, 0.0023498535, 1.1967317, -0.0032694251) +shape = SubResource("BoxShape3D_qot2n") + +[node name="HeadAttach" type="BoneAttachment3D" parent="base_character/Character/Skeleton3D" index="1"] +transform = Transform3D(1, -2.2186812e-16, 2.1319257e-16, 2.1319253e-16, 0.99920464, 0.039866537, -2.2186821e-16, -0.03986654, 0.99920475, -1.3069546e-16, 1.5574795, -0.00406873) +bone_name = "Neck" +bone_idx = 5 + +[node name="Hitbox" type="Hitbox" parent="base_character/Character/Skeleton3D/HeadAttach" node_paths=PackedStringArray("health")] +transform = Transform3D(1, 2.1910389e-16, -2.2202524e-16, -2.2202514e-16, 0.9999124, -0.013243958, 2.1910394e-16, 0.01324396, 0.9999123, -2.1234107e-16, -1.1546164, -0.012580322) +collision_layer = 2 +collision_mask = 0 +monitoring = false +health = NodePath("../../../../../HealthStatus") +damage_modifier = 2.0 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="base_character/Character/Skeleton3D/HeadAttach/Hitbox"] +transform = Transform3D(1, -2.2202515e-16, 2.1910393e-16, 2.1910723e-16, 0.9999161, 0.013243863, -2.2202641e-16, -0.013243858, 0.9999161, -7.644832e-17, 1.3035276, -0.01432839) +shape = SubResource("BoxShape3D_ei3ai") + [node name="CollisionShape3D" type="CollisionShape3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) shape = SubResource("CapsuleShape3D_ng1ul") @@ -22,3 +72,22 @@ path_desired_distance = 0.25 [node name="PlayerDetector" type="PlayerDetector" parent="."] unique_name_in_owner = true transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 1.4599279, 0) + +[node name="HealthStatus" type="HealthStatus" parent="."] +unique_name_in_owner = true +health = 2 + +[node name="Hitbox" type="Hitbox" parent="." node_paths=PackedStringArray("health")] +transform = Transform3D(1, 2.1910389e-16, -2.2202524e-16, -2.2202514e-16, 0.9999124, -0.013243958, 2.1910394e-16, 0.01324396, 0.9999123, 6.209e-17, 0.097789526, 0.004007945) +collision_layer = 2 +collision_mask = 0 +monitoring = false +health = NodePath("../HealthStatus") + +[node name="CollisionShape3D2" type="CollisionShape3D" parent="Hitbox"] +transform = Transform3D(1, 4.2740422e-21, 0, 6.2038546e-25, 1.0000037, 0, 0, 0, 1.0000037, -2.2362305e-16, 0.68298185, -0.013339132) +shape = SubResource("BoxShape3D_sivli") + +[connection signal="death" from="HealthStatus" to="." method="_on_health_status_death"] + +[editable path="base_character"] diff --git a/project/objects/weapons/rifle.tscn b/project/objects/weapons/rifle.tscn index d2901e65..b8334d78 100644 --- a/project/objects/weapons/rifle.tscn +++ b/project/objects/weapons/rifle.tscn @@ -22,6 +22,8 @@ bone_idx = 39 unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 0.99999833, -0.0017453281, 0, 0.0017453281, 0.99999833, 1.4540284e-26, 0, 0.027612558) target_position = Vector3(0, 100, 0) +collision_mask = 2 +collide_with_areas = true spread = 0.003 [node name="AnimationPlayer" parent="rifle" index="1"] diff --git a/project/project.godot b/project/project.godot index 168cee50..4575b2d6 100644 --- a/project/project.godot +++ b/project/project.godot @@ -72,5 +72,5 @@ switch_weapon={ 3d_render/layer_1="Default" 3d_render/layer_2="FirstPerson" -3d_physics/layer_1="Movement" -3d_physics/layer_2="Hit" +3d_physics/layer_1="General" +3d_physics/layer_2="Precision"