feat: implemented melee attacks for Tank enemy
This commit is contained in:
parent
1021920e1f
commit
a78bbfd3b5
|
@ -1,6 +1,9 @@
|
||||||
[gd_resource type="Goal" format=3 uid="uid://b4i4e34046n44"]
|
[gd_resource type="Goal" format=3 uid="uid://b4i4e34046n44"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
desired_state_dict = {
|
requirements_dict = {
|
||||||
"goal1": true
|
"is_target_enemy": true
|
||||||
|
}
|
||||||
|
desired_state_dict = {
|
||||||
|
"is_target_dead": true
|
||||||
}
|
}
|
BIN
godot/Animation/bean_characters.res
Normal file
BIN
godot/Animation/bean_characters.res
Normal file
Binary file not shown.
|
@ -1,145 +1,10 @@
|
||||||
[gd_scene load_steps=9 format=3 uid="uid://ba17jrcaduowj"]
|
[gd_scene load_steps=8 format=3 uid="uid://ba17jrcaduowj"]
|
||||||
|
|
||||||
[sub_resource type="Animation" id="Animation_va30j"]
|
[ext_resource type="Goal" uid="uid://b4i4e34046n44" path="res://AI/defeat_enemy_unit.tres" id="1_b1qo1"]
|
||||||
resource_name = "RESET"
|
[ext_resource type="AnimationLibrary" uid="uid://crkh5gahl2ci6" path="res://Animation/bean_characters.res" id="2_lrpu6"]
|
||||||
length = 0.001
|
|
||||||
loop_mode = 1
|
|
||||||
tracks/0/type = "value"
|
|
||||||
tracks/0/imported = false
|
|
||||||
tracks/0/enabled = true
|
|
||||||
tracks/0/path = NodePath("MeshInstance3D2:position")
|
|
||||||
tracks/0/interp = 1
|
|
||||||
tracks/0/loop_wrap = true
|
|
||||||
tracks/0/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(-0.363891, 1.24027, 0.471381)]
|
|
||||||
}
|
|
||||||
tracks/1/type = "value"
|
|
||||||
tracks/1/imported = false
|
|
||||||
tracks/1/enabled = true
|
|
||||||
tracks/1/path = NodePath("MeshInstance3D:position")
|
|
||||||
tracks/1/interp = 1
|
|
||||||
tracks/1/loop_wrap = true
|
|
||||||
tracks/1/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(0, 1, 0)]
|
|
||||||
}
|
|
||||||
tracks/2/type = "value"
|
|
||||||
tracks/2/imported = false
|
|
||||||
tracks/2/enabled = true
|
|
||||||
tracks/2/path = NodePath("MeshInstance3D:rotation")
|
|
||||||
tracks/2/interp = 1
|
|
||||||
tracks/2/loop_wrap = true
|
|
||||||
tracks/2/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(0, 0, 0)]
|
|
||||||
}
|
|
||||||
tracks/3/type = "value"
|
|
||||||
tracks/3/imported = false
|
|
||||||
tracks/3/enabled = true
|
|
||||||
tracks/3/path = NodePath("MeshInstance3D2:rotation")
|
|
||||||
tracks/3/interp = 1
|
|
||||||
tracks/3/loop_wrap = true
|
|
||||||
tracks/3/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(0, 0, 0)]
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="Animation" id="Animation_opwhc"]
|
[sub_resource type="SphereShape3D" id="SphereShape3D_5pqvg"]
|
||||||
resource_name = "fire"
|
radius = 7.38627
|
||||||
step = 0.01
|
|
||||||
tracks/0/type = "value"
|
|
||||||
tracks/0/imported = false
|
|
||||||
tracks/0/enabled = true
|
|
||||||
tracks/0/path = NodePath("MeshInstance3D2:position")
|
|
||||||
tracks/0/interp = 1
|
|
||||||
tracks/0/loop_wrap = true
|
|
||||||
tracks/0/keys = {
|
|
||||||
"times": PackedFloat32Array(0, 0.04, 0.14),
|
|
||||||
"transitions": PackedFloat32Array(1, 1, 1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(-0.363891, 1.24027, 0.471381), Vector3(-0.363891, 1.24027, 0.199307), Vector3(-0.363891, 1.24027, 0.471381)]
|
|
||||||
}
|
|
||||||
tracks/1/type = "method"
|
|
||||||
tracks/1/imported = false
|
|
||||||
tracks/1/enabled = true
|
|
||||||
tracks/1/path = NodePath(".")
|
|
||||||
tracks/1/interp = 1
|
|
||||||
tracks/1/loop_wrap = true
|
|
||||||
tracks/1/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"values": [{
|
|
||||||
"args": [],
|
|
||||||
"method": &"fire_at_target"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="Animation" id="Animation_yj5ao"]
|
|
||||||
resource_name = "death"
|
|
||||||
tracks/0/type = "value"
|
|
||||||
tracks/0/imported = false
|
|
||||||
tracks/0/enabled = true
|
|
||||||
tracks/0/path = NodePath("MeshInstance3D:position")
|
|
||||||
tracks/0/interp = 1
|
|
||||||
tracks/0/loop_wrap = true
|
|
||||||
tracks/0/keys = {
|
|
||||||
"times": PackedFloat32Array(0, 0.1, 1),
|
|
||||||
"transitions": PackedFloat32Array(1, 1, 1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(0, 1, 0), Vector3(0, 0.495427, 0.341233), Vector3(0, -0.527121, 0.341233)]
|
|
||||||
}
|
|
||||||
tracks/1/type = "value"
|
|
||||||
tracks/1/imported = false
|
|
||||||
tracks/1/enabled = true
|
|
||||||
tracks/1/path = NodePath("MeshInstance3D:rotation")
|
|
||||||
tracks/1/interp = 1
|
|
||||||
tracks/1/loop_wrap = true
|
|
||||||
tracks/1/keys = {
|
|
||||||
"times": PackedFloat32Array(0, 0.1),
|
|
||||||
"transitions": PackedFloat32Array(1, 1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(0, 0, 0), Vector3(1.59169, 0, 0)]
|
|
||||||
}
|
|
||||||
tracks/2/type = "value"
|
|
||||||
tracks/2/imported = false
|
|
||||||
tracks/2/enabled = true
|
|
||||||
tracks/2/path = NodePath("MeshInstance3D2:position")
|
|
||||||
tracks/2/interp = 1
|
|
||||||
tracks/2/loop_wrap = true
|
|
||||||
tracks/2/keys = {
|
|
||||||
"times": PackedFloat32Array(0, 0.1, 1),
|
|
||||||
"transitions": PackedFloat32Array(1, 1, 1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(-0.363891, 1.24027, 0.471381), Vector3(-0.363891, 0.0191291, 0.571603), Vector3(-0.363891, -1.00342, 0.571603)]
|
|
||||||
}
|
|
||||||
tracks/3/type = "value"
|
|
||||||
tracks/3/imported = false
|
|
||||||
tracks/3/enabled = true
|
|
||||||
tracks/3/path = NodePath("MeshInstance3D2:rotation")
|
|
||||||
tracks/3/interp = 1
|
|
||||||
tracks/3/loop_wrap = true
|
|
||||||
tracks/3/keys = {
|
|
||||||
"times": PackedFloat32Array(0, 0.1),
|
|
||||||
"transitions": PackedFloat32Array(1, 1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(0, 0, 0), Vector3(1.59169, 0, 0)]
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_3jmw4"]
|
|
||||||
_data = {
|
|
||||||
"RESET": SubResource("Animation_va30j"),
|
|
||||||
"death": SubResource("Animation_yj5ao"),
|
|
||||||
"fire_weapon": SubResource("Animation_opwhc")
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_drlm2"]
|
[sub_resource type="SphereShape3D" id="SphereShape3D_drlm2"]
|
||||||
radius = 1.0
|
radius = 1.0
|
||||||
|
@ -158,17 +23,36 @@ configure_team = 3
|
||||||
collision_layer = 6
|
collision_layer = 6
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
|
|
||||||
[node name="ActorWorldState" type="UnitWorldState" parent="."]
|
[node name="ActorWorldState" type="EnemyWorldState" parent="."]
|
||||||
|
editor_available_goals = [ExtResource("1_b1qo1")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
[node name="Planner" type="Planner" parent="."]
|
[node name="Planner" type="Planner" parent="."]
|
||||||
|
actions_inspector = [3, 2, 4]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
[node name="EntityHealth" type="EntityHealth" parent="."]
|
[node name="EntityHealth" type="EntityHealth" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
|
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
path_height_offset = 0.5
|
||||||
|
|
||||||
|
[node name="AwarenessArea" type="Area3D" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.1216, 3.55442)
|
||||||
|
collision_layer = 0
|
||||||
|
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)
|
||||||
|
shape = SubResource("SphereShape3D_5pqvg")
|
||||||
|
|
||||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
libraries = {
|
libraries = {
|
||||||
"": SubResource("AnimationLibrary_3jmw4")
|
"": ExtResource("2_lrpu6")
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="Eyes" type="Node3D" parent="."]
|
[node name="Eyes" type="Node3D" parent="."]
|
||||||
|
@ -179,7 +63,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.4512, 0)
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.31501, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.31501, 0)
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
transform = Transform3D(1, 0, 0, 0, 0.999222, -0.0394342, 0, 0.0394342, 0.999222, 0, 1.01253, 0.443459)
|
||||||
shape = SubResource("SphereShape3D_drlm2")
|
shape = SubResource("SphereShape3D_drlm2")
|
||||||
|
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||||
|
|
|
@ -1,57 +1,9 @@
|
||||||
[gd_scene load_steps=8 format=3 uid="uid://pme230qx1377"]
|
[gd_scene load_steps=6 format=3 uid="uid://pme230qx1377"]
|
||||||
|
|
||||||
[sub_resource type="Animation" id="Animation_va30j"]
|
[ext_resource type="AnimationLibrary" uid="uid://crkh5gahl2ci6" path="res://Animation/bean_characters.res" id="1_70627"]
|
||||||
resource_name = "RESET"
|
|
||||||
length = 0.001
|
|
||||||
loop_mode = 1
|
|
||||||
tracks/0/type = "value"
|
|
||||||
tracks/0/imported = false
|
|
||||||
tracks/0/enabled = true
|
|
||||||
tracks/0/path = NodePath("MeshInstance3D2:position")
|
|
||||||
tracks/0/interp = 1
|
|
||||||
tracks/0/loop_wrap = true
|
|
||||||
tracks/0/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(-0.363891, 1.24027, 0.471381)]
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="Animation" id="Animation_opwhc"]
|
[sub_resource type="SphereShape3D" id="SphereShape3D_drlm2"]
|
||||||
resource_name = "fire"
|
radius = 1.0
|
||||||
step = 0.01
|
|
||||||
tracks/0/type = "value"
|
|
||||||
tracks/0/imported = false
|
|
||||||
tracks/0/enabled = true
|
|
||||||
tracks/0/path = NodePath("MeshInstance3D2:position")
|
|
||||||
tracks/0/interp = 1
|
|
||||||
tracks/0/loop_wrap = true
|
|
||||||
tracks/0/keys = {
|
|
||||||
"times": PackedFloat32Array(0, 0.04, 0.14),
|
|
||||||
"transitions": PackedFloat32Array(1, 1, 1),
|
|
||||||
"update": 0,
|
|
||||||
"values": [Vector3(-0.363891, 1.24027, 0.471381), Vector3(-0.363891, 1.24027, 0.199307), Vector3(-0.363891, 1.24027, 0.471381)]
|
|
||||||
}
|
|
||||||
tracks/1/type = "method"
|
|
||||||
tracks/1/imported = false
|
|
||||||
tracks/1/enabled = true
|
|
||||||
tracks/1/path = NodePath(".")
|
|
||||||
tracks/1/interp = 1
|
|
||||||
tracks/1/loop_wrap = true
|
|
||||||
tracks/1/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"values": [{
|
|
||||||
"args": [],
|
|
||||||
"method": &"fire_at_target"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_3jmw4"]
|
|
||||||
_data = {
|
|
||||||
"RESET": SubResource("Animation_va30j"),
|
|
||||||
"fire_weapon": SubResource("Animation_opwhc")
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_n4q15"]
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_n4q15"]
|
||||||
albedo_color = Color(0.407843, 1, 0.447059, 1)
|
albedo_color = Color(0.407843, 1, 0.447059, 1)
|
||||||
|
@ -59,9 +11,6 @@ albedo_color = Color(0.407843, 1, 0.447059, 1)
|
||||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_5r0b3"]
|
[sub_resource type="CapsuleMesh" id="CapsuleMesh_5r0b3"]
|
||||||
material = SubResource("StandardMaterial3D_n4q15")
|
material = SubResource("StandardMaterial3D_n4q15")
|
||||||
|
|
||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_drlm2"]
|
|
||||||
radius = 1.0
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_p8wvo"]
|
[sub_resource type="BoxMesh" id="BoxMesh_p8wvo"]
|
||||||
size = Vector3(0.2, 0.2, 0.5)
|
size = Vector3(0.2, 0.2, 0.5)
|
||||||
|
|
||||||
|
@ -71,19 +20,24 @@ collision_layer = 6
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
|
|
||||||
[node name="ActorWorldState" type="UnitWorldState" parent="."]
|
[node name="ActorWorldState" type="UnitWorldState" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
[node name="Planner" type="Planner" parent="."]
|
[node name="Planner" type="Planner" parent="."]
|
||||||
actions_inspector = [0, 1, 2]
|
actions_inspector = [0, 1, 2]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
[node name="EntityHealth" type="EntityHealth" parent="."]
|
[node name="EntityHealth" type="EntityHealth" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
|
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
path_height_offset = 0.5
|
path_height_offset = 0.5
|
||||||
debug_enabled = true
|
debug_enabled = true
|
||||||
|
|
||||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
libraries = {
|
libraries = {
|
||||||
"": SubResource("AnimationLibrary_3jmw4")
|
"": ExtResource("1_70627")
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="Eyes" type="Node3D" parent="."]
|
[node name="Eyes" type="Node3D" parent="."]
|
||||||
|
@ -93,14 +47,14 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.49429, 0)
|
||||||
[node name="VisionTarget" type="Node3D" parent="."]
|
[node name="VisionTarget" type="Node3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.39151, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.39151, 0)
|
||||||
|
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
|
||||||
mesh = SubResource("CapsuleMesh_5r0b3")
|
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||||
shape = SubResource("SphereShape3D_drlm2")
|
shape = SubResource("SphereShape3D_drlm2")
|
||||||
|
|
||||||
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||||
|
mesh = SubResource("CapsuleMesh_5r0b3")
|
||||||
|
|
||||||
[node name="MeshInstance3D2" type="MeshInstance3D" parent="."]
|
[node name="MeshInstance3D2" type="MeshInstance3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.363891, 1.24027, 0.471381)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.363891, 1.24027, 0.471381)
|
||||||
mesh = SubResource("BoxMesh_p8wvo")
|
mesh = SubResource("BoxMesh_p8wvo")
|
||||||
|
|
105
src/enemy_world_state.cpp
Normal file
105
src/enemy_world_state.cpp
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#include "enemy_world_state.hpp"
|
||||||
|
#include "entity_health.hpp"
|
||||||
|
#include "goap/goal.hpp"
|
||||||
|
#include "godot_cpp/variant/utility_functions.hpp"
|
||||||
|
#include "unit.hpp"
|
||||||
|
#include "utils/godot_macros.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
void EnemyWorldState::_bind_methods() {
|
||||||
|
#define CLASSNAME EnemyWorldState
|
||||||
|
GDPROPERTY_HINTED(editor_available_goals,
|
||||||
|
gd::Variant::ARRAY,
|
||||||
|
gd::PROPERTY_HINT_ARRAY_TYPE,
|
||||||
|
GDRESOURCETYPE("Goal"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyWorldState::_ready() {
|
||||||
|
this->awareness_area = this->get_node<gd::Area3D>("%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));
|
||||||
|
this->health = this->get_node<EntityHealth>("%EntityHealth");
|
||||||
|
if(this->health == nullptr)
|
||||||
|
this->health->connect("damage", callable_mp(this, &EnemyWorldState::on_damaged));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyWorldState::on_awareness_entered(gd::Node3D *node) {
|
||||||
|
gd::UtilityFunctions::print("1) object entered awareness");
|
||||||
|
Unit *unit{gd::Object::cast_to<Unit>(node)};
|
||||||
|
if(unit == nullptr) return;
|
||||||
|
if(unit == this->parent_unit) return;
|
||||||
|
gd::UtilityFunctions::print("2) object was Unit");
|
||||||
|
this->known_enemies.push_back(unit);
|
||||||
|
this->try_set_target(this->select_target_from_known());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyWorldState::on_awareness_exited(gd::Node3D *node) {
|
||||||
|
Unit *unit{gd::Object::cast_to<Unit>(node)};
|
||||||
|
this->known_enemies.erase(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyWorldState::on_damaged(int remaining, int delta, Unit *source) {
|
||||||
|
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)};
|
||||||
|
if(priority >= highest_priority)
|
||||||
|
this->try_set_target(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit *EnemyWorldState::select_target_from_known_with_priority(float *out_priority) {
|
||||||
|
Unit *out{nullptr};
|
||||||
|
*out_priority = -INFINITY;
|
||||||
|
for(Unit *unit : this->known_enemies) {
|
||||||
|
float const priority{this->calculate_priority(unit)};
|
||||||
|
if(priority > *out_priority) {
|
||||||
|
out = unit;
|
||||||
|
*out_priority = priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit *EnemyWorldState::select_target_from_known() {
|
||||||
|
float dummy{};
|
||||||
|
return this->select_target_from_known_with_priority(&dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnemyWorldState::calculate_priority(Unit *target) {
|
||||||
|
return target->get_global_position().distance_squared_to(this->parent_unit->get_global_position());
|
||||||
|
}
|
||||||
|
|
||||||
|
gd::Ref<goap::Goal> EnemyWorldState::get_goal_for_target(Unit *unit) {
|
||||||
|
gd::Node3D *store{this->target_node};
|
||||||
|
this->target_node = unit;
|
||||||
|
for(gd::Ref<goap::Goal> const &goal : this->available_goals) {
|
||||||
|
if(goal->check_requirements_met(this)) {
|
||||||
|
this->target_node = store;
|
||||||
|
return goal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->target_node = store;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyWorldState::try_set_target(Unit *unit) {
|
||||||
|
gd::UtilityFunctions::print("3) selecting goal");
|
||||||
|
gd::Ref<goap::Goal> goal{this->get_goal_for_target(unit)};
|
||||||
|
if(!goal.is_valid()) return;
|
||||||
|
gd::UtilityFunctions::print("4) selected goal ", goal->get_path());
|
||||||
|
this->parent_unit->set_target_goal(unit, goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyWorldState::set_editor_available_goals(gd::Array array) {
|
||||||
|
this->available_goals.clear();
|
||||||
|
while(!array.is_empty()) {
|
||||||
|
gd::Ref<goap::Goal> goal{array.pop_back()};
|
||||||
|
this->available_goals.push_back(goal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gd::Array EnemyWorldState::get_editor_available_goals() const {
|
||||||
|
gd::Array a{};
|
||||||
|
for(gd::Ref<goap::Goal> const &goal : this->available_goals)
|
||||||
|
a.push_back(goal);
|
||||||
|
return a;
|
||||||
|
}
|
44
src/enemy_world_state.hpp
Normal file
44
src/enemy_world_state.hpp
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#ifndef ENEMY_WORLD_STATE_HPP
|
||||||
|
#define ENEMY_WORLD_STATE_HPP
|
||||||
|
|
||||||
|
#include "unit_world_state.hpp"
|
||||||
|
#include "goap/goal.hpp"
|
||||||
|
#include <godot_cpp/classes/area3d.hpp>
|
||||||
|
#include <godot_cpp/templates/vector.hpp>
|
||||||
|
#include <godot_cpp/variant/array.hpp>
|
||||||
|
|
||||||
|
namespace gd = godot;
|
||||||
|
|
||||||
|
class EntityHealth;
|
||||||
|
|
||||||
|
class EnemyWorldState : public UnitWorldState {
|
||||||
|
GDCLASS(EnemyWorldState, UnitWorldState);
|
||||||
|
static void _bind_methods();
|
||||||
|
public:
|
||||||
|
virtual void _ready() override;
|
||||||
|
|
||||||
|
void on_awareness_entered(gd::Node3D *node);
|
||||||
|
void on_awareness_exited(gd::Node3D *node);
|
||||||
|
void on_damaged(int remaining, int delta, Unit *source);
|
||||||
|
/*! Select the target with the highest priority and return it.
|
||||||
|
* Assigns the priority of found target to out_priority.
|
||||||
|
* \param out_priority Assigned to highest found priority, cannot be nullptr.
|
||||||
|
*/
|
||||||
|
Unit *select_target_from_known_with_priority(float *out_priority);
|
||||||
|
//! Shorthand for select_target_from_known_with_priority(nullptr)
|
||||||
|
Unit *select_target_from_known();
|
||||||
|
private:
|
||||||
|
float calculate_priority(Unit *target);
|
||||||
|
gd::Ref<goap::Goal> get_goal_for_target(Unit *unit);
|
||||||
|
void try_set_target(Unit *target);
|
||||||
|
void set_editor_available_goals(gd::Array array);
|
||||||
|
gd::Array get_editor_available_goals() const;
|
||||||
|
private:
|
||||||
|
gd::Vector<gd::Ref<goap::Goal>> available_goals{};
|
||||||
|
gd::Vector<Unit *> known_enemies{};
|
||||||
|
|
||||||
|
gd::Area3D *awareness_area{nullptr};
|
||||||
|
EntityHealth *health{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // !ENEMY_WORLD_STATE_HPP
|
|
@ -4,9 +4,9 @@
|
||||||
|
|
||||||
void EntityHealth::_bind_methods() {
|
void EntityHealth::_bind_methods() {
|
||||||
#define CLASSNAME EntityHealth
|
#define CLASSNAME EntityHealth
|
||||||
GDPROPERTY(max_health, gd::Variant::INT);
|
GDPROPERTY(injury_max, gd::Variant::INT);
|
||||||
|
GDPROPERTY(wounds_max, gd::Variant::INT);
|
||||||
|
|
||||||
gd::PropertyInfo const source_arg{};
|
|
||||||
GDSIGNAL("damage",
|
GDSIGNAL("damage",
|
||||||
gd::PropertyInfo(gd::Variant::INT, "remaining"),
|
gd::PropertyInfo(gd::Variant::INT, "remaining"),
|
||||||
gd::PropertyInfo(gd::Variant::INT, "change"),
|
gd::PropertyInfo(gd::Variant::INT, "change"),
|
||||||
|
@ -22,39 +22,48 @@ void EntityHealth::_bind_methods() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityHealth::_enter_tree() {
|
void EntityHealth::_enter_tree() {
|
||||||
this->current_health = this->max_health;
|
this->injury_current = this->injury_max;
|
||||||
this->emit_signal("damage", this->current_health, 0, nullptr);
|
this->wounds_current = this->wounds_max;
|
||||||
|
this->emit_signal("damage", this->injury_current, 0, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityHealth::damaged_by(int amount, Unit *source) {
|
void EntityHealth::damaged_by(int amount, Unit *source) {
|
||||||
amount = gd::Math::abs(amount);
|
amount = gd::Math::abs(amount);
|
||||||
this->current_health -= amount;
|
this->injury_current -= amount;
|
||||||
this->emit_signal("damage", this->current_health, amount, source);
|
this->emit_signal("damage", this->injury_current, amount, source);
|
||||||
this->emit_signal("health_changed", this->current_health, -amount);
|
this->emit_signal("health_changed", this->injury_current, -amount);
|
||||||
if(this->current_health <= 0) {
|
if(this->injury_current <= 0) {
|
||||||
this->emit_signal("death", source);
|
this->emit_signal("death", source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityHealth::healed_by(int amount, Unit *source) {
|
void EntityHealth::healed_by(int amount, Unit *source) {
|
||||||
amount = gd::Math::abs(amount);
|
amount = gd::Math::abs(amount);
|
||||||
this->current_health = gd::Math::min(this->max_health, this->current_health + amount);
|
this->injury_current = gd::Math::min(this->injury_max, this->injury_current + amount);
|
||||||
this->emit_signal("heal", this->current_health, amount, source);
|
this->emit_signal("heal", this->injury_current, amount, source);
|
||||||
this->emit_signal("health_changed", this->current_health, amount);
|
this->emit_signal("health_changed", this->injury_current, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityHealth::set_max_health(int max_health) {
|
void EntityHealth::set_injury_max(int max_health) {
|
||||||
this->max_health = max_health;
|
this->injury_max = max_health;
|
||||||
}
|
}
|
||||||
|
|
||||||
int EntityHealth::get_max_health() const {
|
int EntityHealth::get_injury_max() const {
|
||||||
return this->max_health;
|
return this->injury_max;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityHealth::set_current_health(int current_health) {
|
int EntityHealth::get_injury_current() const {
|
||||||
this->current_health = current_health;
|
return this->injury_current;
|
||||||
}
|
}
|
||||||
|
|
||||||
int EntityHealth::get_current_health() const {
|
void EntityHealth::set_wounds_max(int max_health) {
|
||||||
return this->current_health;
|
this->wounds_max = max_health;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EntityHealth::get_wounds_max() const {
|
||||||
|
return this->wounds_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EntityHealth::get_wounds_current() const {
|
||||||
|
return this->wounds_current;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,19 @@ public:
|
||||||
void damage(int amount);
|
void damage(int amount);
|
||||||
void healed_by(int amount, Unit *source);
|
void healed_by(int amount, Unit *source);
|
||||||
void heal(int amount);
|
void heal(int amount);
|
||||||
void set_max_health(int max_health);
|
void set_injury_max(int max_health);
|
||||||
int get_max_health() const;
|
int get_injury_max() const;
|
||||||
void set_current_health(int current_health);
|
int get_injury_current() const;
|
||||||
int get_current_health() const;
|
void set_wounds_max(int max_health);
|
||||||
|
int get_wounds_max() const;
|
||||||
|
int get_wounds_current() const;
|
||||||
private:
|
private:
|
||||||
int max_health{100};
|
// long term health
|
||||||
int current_health{0};
|
int injury_max{10};
|
||||||
|
int injury_current{0};
|
||||||
|
// short term health
|
||||||
|
int wounds_max{10};
|
||||||
|
int wounds_current{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // !RTS_ENTITY_HEALTH_HPP
|
#endif // !RTS_ENTITY_HEALTH_HPP
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "register_types.h"
|
#include "register_types.h"
|
||||||
|
#include "enemy_world_state.hpp"
|
||||||
#include "entity_health.hpp"
|
#include "entity_health.hpp"
|
||||||
#include "goap/state.hpp"
|
#include "goap/state.hpp"
|
||||||
#include "rts_actions.hpp"
|
#include "rts_actions.hpp"
|
||||||
|
@ -32,6 +33,8 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
|
||||||
goap::ActionDB::register_action<MoveToTarget>();
|
goap::ActionDB::register_action<MoveToTarget>();
|
||||||
goap::ActionDB::register_action<FireAtTarget>();
|
goap::ActionDB::register_action<FireAtTarget>();
|
||||||
goap::ActionDB::register_action<FindTarget>();
|
goap::ActionDB::register_action<FindTarget>();
|
||||||
|
goap::ActionDB::register_action<GetInMeleeRange>();
|
||||||
|
goap::ActionDB::register_action<MeleeAttack>();
|
||||||
|
|
||||||
ClassDB::register_class<goap::ActorWorldState>();
|
ClassDB::register_class<goap::ActorWorldState>();
|
||||||
ClassDB::register_class<goap::Goal>();
|
ClassDB::register_class<goap::Goal>();
|
||||||
|
@ -43,6 +46,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
|
||||||
ClassDB::register_class<Activate>();
|
ClassDB::register_class<Activate>();
|
||||||
|
|
||||||
ClassDB::register_class<UnitWorldState>();
|
ClassDB::register_class<UnitWorldState>();
|
||||||
|
ClassDB::register_class<EnemyWorldState>();
|
||||||
ClassDB::register_class<GoalMarker>();
|
ClassDB::register_class<GoalMarker>();
|
||||||
ClassDB::register_class<Unit>();
|
ClassDB::register_class<Unit>();
|
||||||
ClassDB::register_class<RTSGameMode>();
|
ClassDB::register_class<RTSGameMode>();
|
||||||
|
|
|
@ -7,16 +7,16 @@
|
||||||
|
|
||||||
MoveToTarget::MoveToTarget()
|
MoveToTarget::MoveToTarget()
|
||||||
: Action() {
|
: Action() {
|
||||||
effects.insert("is_at_target", true);
|
this->effects.insert("is_at_target", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const {
|
goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const {
|
||||||
gd::Node3D *target = gd::Object::cast_to<gd::Node3D>(context->get_world_property("target_node"));
|
gd::Node3D *target{gd::Object::cast_to<gd::Node3D>(context->get_world_property("target_node"))};
|
||||||
if(target == nullptr) {
|
if(target == nullptr) {
|
||||||
gd::UtilityFunctions::push_warning("Failed to get target node of action ", get_static_class());
|
gd::UtilityFunctions::push_warning("Failed to get target node of action ", get_static_class());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
} else {
|
} else {
|
||||||
MoveTo *state = this->create_state<MoveTo>();
|
MoveTo *state{this->create_state<MoveTo>()};
|
||||||
state->target_node = target;
|
state->target_node = target;
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -24,24 +24,50 @@ goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const
|
||||||
|
|
||||||
FireAtTarget::FireAtTarget()
|
FireAtTarget::FireAtTarget()
|
||||||
: Action() {
|
: Action() {
|
||||||
effects.insert("is_target_dead", true);
|
this->effects.insert("is_target_dead", true);
|
||||||
required.insert("can_see_target", true);
|
this->required.insert("can_see_target", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
goap::State *FireAtTarget::get_apply_state(goap::ActorWorldState *context) const {
|
goap::State *FireAtTarget::get_apply_state(goap::ActorWorldState *context) const {
|
||||||
Animate *state = this->create_state<Animate>();
|
Animate *state{this->create_state<Animate>()};
|
||||||
state->animation = "fire_weapon";
|
state->animation = "fire_weapon";
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
FindTarget::FindTarget()
|
FindTarget::FindTarget()
|
||||||
: Action() {
|
: Action() {
|
||||||
effects.insert("can_see_target", true);
|
this->effects.insert("can_see_target", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
goap::State *FindTarget::get_apply_state(goap::ActorWorldState *context) const {
|
goap::State *FindTarget::get_apply_state(goap::ActorWorldState *context) const {
|
||||||
gd::Node3D *target = gd::Object::cast_to<gd::Node3D>(context->get_world_property("target_node"));
|
gd::Node3D *target{gd::Object::cast_to<gd::Node3D>(context->get_world_property("target_node"))};
|
||||||
MoveTo *state = this->create_state<MoveTo>();
|
MoveTo *state{this->create_state<MoveTo>()};
|
||||||
state->target_node = target;
|
state->target_node = target;
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetInMeleeRange::GetInMeleeRange()
|
||||||
|
: Action() {
|
||||||
|
this->effects.insert("is_in_melee_range", true);
|
||||||
|
this->required.insert("can_see_target", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
goap::State *GetInMeleeRange::get_apply_state(goap::ActorWorldState *context) const {
|
||||||
|
gd::Node3D *target{gd::Object::cast_to<gd::Node3D>(context->get_world_property("target_node"))};
|
||||||
|
MoveTo *state{this->create_state<MoveTo>()};
|
||||||
|
state->target_node = target;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
goap::State *MeleeAttack::get_apply_state(goap::ActorWorldState *context) const {
|
||||||
|
Animate *state{this->create_state<Animate>()};
|
||||||
|
state->animation = "melee_attack";
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
|
@ -25,4 +25,18 @@ public:
|
||||||
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
|
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class GetInMeleeRange : public goap::Action {
|
||||||
|
GOAP_ACTION(GetInMeleeRange);
|
||||||
|
public:
|
||||||
|
GetInMeleeRange();
|
||||||
|
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MeleeAttack : public goap::Action {
|
||||||
|
GOAP_ACTION(MeleeAttack);
|
||||||
|
public:
|
||||||
|
MeleeAttack();
|
||||||
|
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
|
||||||
|
};
|
||||||
|
|
||||||
#endif // !RTS_ACTIONS_HPP
|
#endif // !RTS_ACTIONS_HPP
|
||||||
|
|
19
src/tank_unit.cpp
Normal file
19
src/tank_unit.cpp
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#include "tank_unit.hpp"
|
||||||
|
#include "entity_health.hpp"
|
||||||
|
|
||||||
|
void TankUnit::_bind_methods() {}
|
||||||
|
|
||||||
|
void TankUnit::use_weapon() {
|
||||||
|
gd::Node3D *target{this->world_state->get_target_node()};
|
||||||
|
if(target == nullptr)
|
||||||
|
return;
|
||||||
|
if(!this->world_state->get_can_see_target())
|
||||||
|
return;
|
||||||
|
if(1.f < target->get_global_position().distance_squared_to(this->get_global_position()))
|
||||||
|
return;
|
||||||
|
this->aim_at(target);
|
||||||
|
EntityHealth *health = target->get_node<EntityHealth>("EntityHealth");
|
||||||
|
if(health == nullptr)
|
||||||
|
return;
|
||||||
|
health->damaged_by(2, this);
|
||||||
|
}
|
13
src/tank_unit.hpp
Normal file
13
src/tank_unit.hpp
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef TANK_UNIT_HPP
|
||||||
|
#define TANK_UNIT_HPP
|
||||||
|
|
||||||
|
#include "unit.hpp"
|
||||||
|
|
||||||
|
class TankUnit : public Unit {
|
||||||
|
GDCLASS(TankUnit, Unit);
|
||||||
|
static void _bind_methods();
|
||||||
|
public:
|
||||||
|
virtual void use_weapon() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // !TANK_UNIT_HPP
|
33
src/unit.cpp
33
src/unit.cpp
|
@ -9,18 +9,18 @@
|
||||||
void Unit::_bind_methods() {
|
void Unit::_bind_methods() {
|
||||||
#define CLASSNAME Unit
|
#define CLASSNAME Unit
|
||||||
GDSIGNAL("goal_finished");
|
GDSIGNAL("goal_finished");
|
||||||
|
GDSIGNAL("plan_failed");
|
||||||
GDPROPERTY_HINTED(configure_team, gd::Variant::INT, gd::PROPERTY_HINT_ENUM, UnitTeam::get_property_hint());
|
GDPROPERTY_HINTED(configure_team, gd::Variant::INT, gd::PROPERTY_HINT_ENUM, UnitTeam::get_property_hint());
|
||||||
GDFUNCTION(fire_at_target);
|
GDFUNCTION(use_weapon);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::_enter_tree() { GDGAMEONLY();
|
void Unit::_enter_tree() { GDGAMEONLY();
|
||||||
this->agent = this->get_node<gd::NavigationAgent3D>("NavigationAgent3D");
|
this->agent = this->get_node<gd::NavigationAgent3D>("%NavigationAgent3D");
|
||||||
this->planner = this->get_node<goap::Planner>("Planner");
|
this->planner = this->get_node<goap::Planner>("%Planner");
|
||||||
this->world_state = this->get_node<UnitWorldState>("ActorWorldState");
|
this->world_state = this->get_node<UnitWorldState>("%ActorWorldState");
|
||||||
this->eyes = this->get_node<gd::Node3D>("%Eyes");
|
|
||||||
this->world_state->connect("attention_changed", callable_mp(this, &Unit::stop_plan));
|
this->world_state->connect("attention_changed", callable_mp(this, &Unit::stop_plan));
|
||||||
this->anim_player = this->get_node<gd::AnimationPlayer>("AnimationPlayer");
|
this->anim_player = this->get_node<gd::AnimationPlayer>("%AnimationPlayer");
|
||||||
EntityHealth *health{this->get_node<EntityHealth>("EntityHealth")};
|
EntityHealth *health{this->get_node<EntityHealth>("%EntityHealth")};
|
||||||
health->connect("death", callable_mp(this, &Unit::on_death));
|
health->connect("death", callable_mp(this, &Unit::on_death));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,11 +30,13 @@ void Unit::stop_plan() {
|
||||||
if(this->state && !this->state->is_queued_for_deletion())
|
if(this->state && !this->state->is_queued_for_deletion())
|
||||||
this->destroy_state();
|
this->destroy_state();
|
||||||
this->state = nullptr;
|
this->state = nullptr;
|
||||||
|
this->emit_signal("plan_failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::begin_marker_temporary(GoalMarker *marker) {
|
void Unit::begin_marker_temporary(GoalMarker *marker) {
|
||||||
this->destroy_state();
|
this->destroy_state();
|
||||||
this->world_state->set_target_node(marker);
|
this->world_state->set_target_node(marker);
|
||||||
|
this->destroy_state();
|
||||||
this->set_goal_and_plan(marker->get_goal());
|
this->set_goal_and_plan(marker->get_goal());
|
||||||
// destroy temporary marker if goal is already achieved or failed
|
// destroy temporary marker if goal is already achieved or failed
|
||||||
// connect observers if a plan was formed
|
// connect observers if a plan was formed
|
||||||
|
@ -58,15 +60,15 @@ void Unit::begin_goal(gd::Ref<goap::Goal> goal) {
|
||||||
this->next_action();
|
this->next_action();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::fire_at_target() {
|
void Unit::use_weapon() {
|
||||||
gd::Node3D *target{this->world_state->get_target_node()};
|
gd::Node3D *target{this->world_state->get_target_node()};
|
||||||
if(!target)
|
if(target == nullptr)
|
||||||
return;
|
return;
|
||||||
this->aim_at(target);
|
this->aim_at(target);
|
||||||
EntityHealth *health = target->get_node<EntityHealth>("EntityHealth");
|
EntityHealth *health{target->get_node<EntityHealth>("EntityHealth")};
|
||||||
if(!health)
|
if(health == nullptr)
|
||||||
return;
|
return;
|
||||||
health->damaged_by(10, this);
|
health->damaged_by(1, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::aim_at(gd::Node3D *target) {
|
void Unit::aim_at(gd::Node3D *target) {
|
||||||
|
@ -86,10 +88,6 @@ UnitWorldState *Unit::get_world_state() const {
|
||||||
return this->world_state;
|
return this->world_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
gd::Transform3D Unit::get_eye_transform() const {
|
|
||||||
return this->eyes->get_global_transform();
|
|
||||||
}
|
|
||||||
|
|
||||||
UnitTeam Unit::get_team() const {
|
UnitTeam Unit::get_team() const {
|
||||||
return this->team;
|
return this->team;
|
||||||
}
|
}
|
||||||
|
@ -105,6 +103,9 @@ int Unit::get_configure_team() const {
|
||||||
void Unit::set_goal_and_plan(gd::Ref<goap::Goal> goal) {
|
void Unit::set_goal_and_plan(gd::Ref<goap::Goal> goal) {
|
||||||
this->current_goal = goal;
|
this->current_goal = goal;
|
||||||
this->current_plan = this->planner->plan_for_goal(goal);
|
this->current_plan = this->planner->plan_for_goal(goal);
|
||||||
|
if(this->current_plan.is_empty()) {
|
||||||
|
this->current_goal.unref();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::destroy_state() {
|
void Unit::destroy_state() {
|
||||||
|
|
11
src/unit.hpp
11
src/unit.hpp
|
@ -32,12 +32,11 @@ public:
|
||||||
void begin_goal(gd::Ref<goap::Goal> goal);
|
void begin_goal(gd::Ref<goap::Goal> goal);
|
||||||
void set_target_goal(gd::Node3D *target, gd::Ref<goap::Goal> goal);
|
void set_target_goal(gd::Node3D *target, gd::Ref<goap::Goal> goal);
|
||||||
|
|
||||||
void fire_at_target();
|
virtual void use_weapon();
|
||||||
void aim_at(gd::Node3D *node);
|
void aim_at(gd::Node3D *node);
|
||||||
void on_death(Unit *damage_source);
|
void on_death(Unit *damage_source);
|
||||||
|
|
||||||
UnitWorldState *get_world_state() const;
|
UnitWorldState *get_world_state() const;
|
||||||
gd::Transform3D get_eye_transform() const;
|
|
||||||
|
|
||||||
UnitTeam get_team() const;
|
UnitTeam get_team() const;
|
||||||
void set_configure_team(int value);
|
void set_configure_team(int value);
|
||||||
|
@ -49,15 +48,13 @@ private:
|
||||||
void next_action();
|
void next_action();
|
||||||
void replan_goal();
|
void replan_goal();
|
||||||
private:
|
private:
|
||||||
|
gd::Callable on_state_finished{callable_mp(this, &Unit::state_finished)};
|
||||||
|
gd::Callable on_plan_failed{callable_mp(this, &Unit::replan_goal)};
|
||||||
|
protected:
|
||||||
goap::Plan current_plan{};
|
goap::Plan current_plan{};
|
||||||
gd::Ref<goap::Goal> current_goal{};
|
gd::Ref<goap::Goal> current_goal{};
|
||||||
goap::State *state{nullptr};
|
goap::State *state{nullptr};
|
||||||
|
|
||||||
gd::Node3D *eyes{nullptr};
|
|
||||||
|
|
||||||
gd::Callable on_state_finished{callable_mp(this, &Unit::state_finished)};
|
|
||||||
gd::Callable on_plan_failed{callable_mp(this, &Unit::replan_goal)};
|
|
||||||
|
|
||||||
UnitTeam team{UnitTeam::Neutral};
|
UnitTeam team{UnitTeam::Neutral};
|
||||||
|
|
||||||
gd::NavigationAgent3D *agent{nullptr};
|
gd::NavigationAgent3D *agent{nullptr};
|
||||||
|
|
|
@ -15,17 +15,20 @@ void UnitWorldState::_bind_methods() {
|
||||||
GDFUNCTION(get_is_target_dead);
|
GDFUNCTION(get_is_target_dead);
|
||||||
GDFUNCTION(get_is_at_target);
|
GDFUNCTION(get_is_at_target);
|
||||||
GDFUNCTION(get_target_node);
|
GDFUNCTION(get_target_node);
|
||||||
|
GDFUNCTION(get_is_target_enemy);
|
||||||
|
GDFUNCTION(get_is_in_melee_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnitWorldState::_enter_tree() { GDGAMEONLY();
|
void UnitWorldState::_enter_tree() { GDGAMEONLY();
|
||||||
this->parent_unit = gd::Object::cast_to<Unit>(this->get_parent());
|
this->parent_unit = gd::Object::cast_to<Unit>(this->get_parent());
|
||||||
this->agent = this->get_node<gd::NavigationAgent3D>("../NavigationAgent3D");
|
this->agent = this->get_node<gd::NavigationAgent3D>("%NavigationAgent3D");
|
||||||
|
this->eye_location = this->get_node<gd::Node3D>("%Eyes");
|
||||||
if(this->parent_unit == nullptr)
|
if(this->parent_unit == nullptr)
|
||||||
gd::UtilityFunctions::push_warning("UnitWorldState needs to be a child node of a Unit");
|
gd::UtilityFunctions::push_warning("UnitWorldState needs to be a child node of a Unit");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UnitWorldState::get_can_see_target() const {
|
bool UnitWorldState::get_can_see_target() const {
|
||||||
gd::Transform3D const eyes{this->parent_unit->get_eye_transform()};
|
gd::Transform3D const eyes{this->eye_location->get_global_transform()};
|
||||||
// check if the target has a separate vision target, or is it's own vision target
|
// check if the target has a separate vision target, or is it's own vision target
|
||||||
gd::Node3D *vision_target{this->target_node->get_node<gd::Node3D>("VisionTarget")};
|
gd::Node3D *vision_target{this->target_node->get_node<gd::Node3D>("VisionTarget")};
|
||||||
gd::Vector3 target_position{
|
gd::Vector3 target_position{
|
||||||
|
@ -56,13 +59,28 @@ bool UnitWorldState::get_is_at_target() const {
|
||||||
|
|
||||||
bool UnitWorldState::get_is_target_dead() const {
|
bool UnitWorldState::get_is_target_dead() const {
|
||||||
if(EntityHealth *health{this->target_node->get_node<EntityHealth>("EntityHealth")})
|
if(EntityHealth *health{this->target_node->get_node<EntityHealth>("EntityHealth")})
|
||||||
return health->get_current_health() <= 0.f;
|
return health->get_injury_current() <= 0.f;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UnitWorldState::get_is_target_unit() const {
|
||||||
|
return gd::ClassDB::is_parent_class(this->target_node->get_class(), "Unit");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnitWorldState::get_is_target_enemy() const {
|
||||||
|
Unit *unit{gd::Object::cast_to<Unit>(this->target_node)};
|
||||||
|
return unit != nullptr
|
||||||
|
&& unit->get_team() != UnitTeam::Neutral
|
||||||
|
&& unit->get_team() != this->parent_unit->get_team();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnitWorldState::get_is_in_melee_range() const {
|
||||||
|
return this->target_node != nullptr
|
||||||
|
&& this->target_node->get_global_position()
|
||||||
|
.distance_squared_to(this->parent_unit->get_global_position()) <= 4.f;
|
||||||
|
}
|
||||||
|
|
||||||
void UnitWorldState::set_target_node(gd::Node3D *node) {
|
void UnitWorldState::set_target_node(gd::Node3D *node) {
|
||||||
if(node == this->target_node)
|
|
||||||
return;
|
|
||||||
if(this->target_node != nullptr)
|
if(this->target_node != nullptr)
|
||||||
this->target_node->disconnect("tree_exited", this->target_node_exited_tree.bind(this->target_node));
|
this->target_node->disconnect("tree_exited", this->target_node_exited_tree.bind(this->target_node));
|
||||||
this->target_node = node;
|
this->target_node = node;
|
||||||
|
|
|
@ -15,6 +15,9 @@ public:
|
||||||
bool get_can_see_target() const;
|
bool get_can_see_target() const;
|
||||||
bool get_is_at_target() const;
|
bool get_is_at_target() const;
|
||||||
bool get_is_target_dead() const;
|
bool get_is_target_dead() const;
|
||||||
|
bool get_is_target_unit() const;
|
||||||
|
bool get_is_target_enemy() const;
|
||||||
|
bool get_is_in_melee_range() const;
|
||||||
|
|
||||||
void set_target_node(gd::Node3D *node);
|
void set_target_node(gd::Node3D *node);
|
||||||
gd::Node3D *get_target_node() const;
|
gd::Node3D *get_target_node() const;
|
||||||
|
@ -22,10 +25,11 @@ private:
|
||||||
void target_destroyed(gd::Node3D *target);
|
void target_destroyed(gd::Node3D *target);
|
||||||
private:
|
private:
|
||||||
gd::Callable const target_node_exited_tree{callable_mp(this, &UnitWorldState::target_destroyed)};
|
gd::Callable const target_node_exited_tree{callable_mp(this, &UnitWorldState::target_destroyed)};
|
||||||
|
protected:
|
||||||
Unit *parent_unit{nullptr};
|
Unit *parent_unit{nullptr};
|
||||||
gd::NavigationAgent3D *agent{nullptr};
|
gd::NavigationAgent3D *agent{nullptr};
|
||||||
gd::Node3D *target_node{nullptr};
|
gd::Node3D *target_node{nullptr};
|
||||||
|
gd::Node3D *eye_location{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // !UNIT_WORLD_STATE_HPP
|
#endif // !UNIT_WORLD_STATE_HPP
|
||||||
|
|
Loading…
Reference in a new issue