feat: implemented tactics markers and mode switching
This commit is contained in:
		
							parent
							
								
									7f2d67d9db
								
							
						
					
					
						commit
						f0185e64bc
					
				| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
[gd_scene load_steps=9 format=3 uid="uid://dpda341t6ipiv"]
 | 
			
		||||
[gd_scene load_steps=11 format=3 uid="uid://dpda341t6ipiv"]
 | 
			
		||||
 | 
			
		||||
[sub_resource type="Curve" id="Curve_7rmf4"]
 | 
			
		||||
min_value = 0.2
 | 
			
		||||
| 
						 | 
				
			
			@ -6,6 +6,32 @@ max_value = 2.0
 | 
			
		|||
_data = [Vector2(0.145299, 0.2), 0.0, 0.482143, 0, 0, Vector2(0.594017, 2), 0.0, 0.0, 0, 0]
 | 
			
		||||
point_count = 2
 | 
			
		||||
 | 
			
		||||
[sub_resource type="MoveStateArgs" id="MoveStateArgs_ibmkn"]
 | 
			
		||||
argument_property = &"g_player_character"
 | 
			
		||||
 | 
			
		||||
[sub_resource type="Action" id="Action_gtisq"]
 | 
			
		||||
effects = {
 | 
			
		||||
"is_near_player": true
 | 
			
		||||
}
 | 
			
		||||
apply_state = SubResource("MoveStateArgs_ibmkn")
 | 
			
		||||
 | 
			
		||||
[sub_resource type="MoveStateArgs" id="MoveStateArgs_vyebd"]
 | 
			
		||||
argument_property = &"target"
 | 
			
		||||
 | 
			
		||||
[sub_resource type="Action" id="Action_cwmvs"]
 | 
			
		||||
effects = {
 | 
			
		||||
"is_near_target": true
 | 
			
		||||
}
 | 
			
		||||
apply_state = SubResource("MoveStateArgs_vyebd")
 | 
			
		||||
 | 
			
		||||
[sub_resource type="Goal" id="Goal_sqtwb"]
 | 
			
		||||
goal_state = {
 | 
			
		||||
"is_near_player": true
 | 
			
		||||
}
 | 
			
		||||
prerequisites = {
 | 
			
		||||
"is_near_player": false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_3g72p"]
 | 
			
		||||
height = 1.59321
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,27 +44,14 @@ albedo_color = Color(0.94902, 0.909804, 0, 1)
 | 
			
		|||
[sub_resource type="BoxMesh" id="BoxMesh_f5yvh"]
 | 
			
		||||
size = Vector3(0.125, 0.14, 0.94)
 | 
			
		||||
 | 
			
		||||
[sub_resource type="MoveStateArgs" id="MoveStateArgs_ibmkn"]
 | 
			
		||||
argument_property = &"g_player_character"
 | 
			
		||||
 | 
			
		||||
[sub_resource type="Action" id="Action_gtisq"]
 | 
			
		||||
effects = {
 | 
			
		||||
"is_near_player": true
 | 
			
		||||
}
 | 
			
		||||
apply_state = SubResource("MoveStateArgs_ibmkn")
 | 
			
		||||
 | 
			
		||||
[sub_resource type="Goal" id="Goal_sqtwb"]
 | 
			
		||||
goal_state = {
 | 
			
		||||
"is_near_player": true
 | 
			
		||||
}
 | 
			
		||||
prerequisites = {
 | 
			
		||||
"is_near_player": false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[node name="PlayerCharacter" type="CharacterActor"]
 | 
			
		||||
rotation_speed_curve = SubResource("Curve_7rmf4")
 | 
			
		||||
collision_layer = 7
 | 
			
		||||
 | 
			
		||||
[node name="Planner" type="Planner" parent="."]
 | 
			
		||||
actions = [SubResource("Action_gtisq"), SubResource("Action_cwmvs")]
 | 
			
		||||
goals = [SubResource("Goal_sqtwb")]
 | 
			
		||||
 | 
			
		||||
[node name="Health" type="Health" parent="."]
 | 
			
		||||
max_health = 5
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +77,3 @@ surface_material_override/0 = SubResource("StandardMaterial3D_scmx3")
 | 
			
		|||
 | 
			
		||||
[node name="WeaponMuzzle" type="WeaponMuzzle" parent="."]
 | 
			
		||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.53551, 0.931313, 0)
 | 
			
		||||
 | 
			
		||||
[node name="Planner" type="Planner" parent="."]
 | 
			
		||||
actions = [SubResource("Action_gtisq")]
 | 
			
		||||
goals = [SubResource("Goal_sqtwb")]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,9 +42,15 @@ fire={
 | 
			
		|||
"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":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
 | 
			
		||||
]
 | 
			
		||||
}
 | 
			
		||||
tactics_mode={
 | 
			
		||||
"deadzone": 0.5,
 | 
			
		||||
"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":32,"key_label":0,"unicode":32,"echo":false,"script":null)
 | 
			
		||||
]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[layer_names]
 | 
			
		||||
 | 
			
		||||
3d_physics/layer_1="Default"
 | 
			
		||||
3d_physics/layer_2="Vision"
 | 
			
		||||
3d_physics/layer_3="Hitboxes"
 | 
			
		||||
3d_physics/layer_4="Markers"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
[gd_scene load_steps=12 format=3 uid="uid://m36guasmi3c1"]
 | 
			
		||||
[gd_scene load_steps=16 format=3 uid="uid://m36guasmi3c1"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="TunnelsGameState" uid="uid://cl0iikkau5mio" path="res://tunnels_game_state.tres" id="1_aove2"]
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://cqkbxe758jr7p" path="res://player.tscn" id="2_6yx24"]
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +28,23 @@ size = Vector3(20, 0.25, 20)
 | 
			
		|||
[sub_resource type="BoxShape3D" id="BoxShape3D_kacqg"]
 | 
			
		||||
size = Vector3(20, 0.25, 20)
 | 
			
		||||
 | 
			
		||||
[sub_resource type="Goal" id="Goal_jou2o"]
 | 
			
		||||
goal_state = {
 | 
			
		||||
"is_near_target": true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_hp1wp"]
 | 
			
		||||
height = 0.497374
 | 
			
		||||
radius = 1.0
 | 
			
		||||
 | 
			
		||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_navbx"]
 | 
			
		||||
 | 
			
		||||
[sub_resource type="CylinderMesh" id="CylinderMesh_oaw6a"]
 | 
			
		||||
material = SubResource("StandardMaterial3D_navbx")
 | 
			
		||||
top_radius = 1.0
 | 
			
		||||
bottom_radius = 1.0
 | 
			
		||||
height = 0.54
 | 
			
		||||
 | 
			
		||||
[node name="Level3D" type="Level3D"]
 | 
			
		||||
game_mode_prototype = SubResource("TunnelsGameMode_hnap3")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -54,3 +71,15 @@ transform = Transform3D(-0.925514, 0, -0.378713, 0, 1, 0, 0.378713, 0, -0.925514
 | 
			
		|||
 | 
			
		||||
[node name="PlayerCharacter" parent="." instance=ExtResource("4_22npn")]
 | 
			
		||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.12963, 0.125, -3.38872)
 | 
			
		||||
 | 
			
		||||
[node name="GoalMarker" type="GoalMarker" parent="."]
 | 
			
		||||
goal = SubResource("Goal_jou2o")
 | 
			
		||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.0795, 0, -6.23068)
 | 
			
		||||
collision_layer = 8
 | 
			
		||||
collision_mask = 0
 | 
			
		||||
 | 
			
		||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="GoalMarker"]
 | 
			
		||||
shape = SubResource("CylinderShape3D_hp1wp")
 | 
			
		||||
 | 
			
		||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="GoalMarker"]
 | 
			
		||||
mesh = SubResource("CylinderMesh_oaw6a")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,8 +16,8 @@ void CharacterActor::_bind_methods() {
 | 
			
		|||
#define CLASSNAME CharacterActor
 | 
			
		||||
    GDPROPERTY_HINTED(rotation_speed_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
 | 
			
		||||
    GDFUNCTION_ARGS(set_velocity_target, "value");
 | 
			
		||||
    GDPROPERTY_HINTED(target, Variant::OBJECT, PROPERTY_HINT_NODE_TYPE, "Node");
 | 
			
		||||
    GDFUNCTION(get_is_near_player);
 | 
			
		||||
    GDFUNCTION(get_player_character);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CharacterActor::_enter_tree() { GDGAMEONLY();
 | 
			
		||||
| 
						 | 
				
			
			@ -84,6 +84,10 @@ void CharacterActor::shoot_at(Vector3 at) {
 | 
			
		|||
    this->set_firing(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CharacterActor::force_update_action() {
 | 
			
		||||
    this->set_state(this->planner->get_next_state());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CharacterActor::set_firing(bool firing) {
 | 
			
		||||
    this->firing = firing;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -131,12 +135,27 @@ Vector3 CharacterActor::get_velocity_target() const {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
bool CharacterActor::get_is_near_player() const {
 | 
			
		||||
    return this->get_player_character()->get_global_position().distance_to(this->get_global_position()) < 5.f;
 | 
			
		||||
    return Ref<TunnelsGameMode>(GameRoot::get_singleton()->get_game_mode())
 | 
			
		||||
        ->get_player_instance()
 | 
			
		||||
        ->get_character()
 | 
			
		||||
        ->get_global_position().distance_to(this->get_global_position()) < 5.f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CharacterActor *CharacterActor::get_player_character() const {
 | 
			
		||||
    Ref<TunnelsGameMode> game_mode = GameRoot::get_singleton()->get_game_mode();
 | 
			
		||||
    return game_mode->get_player_instance()->get_character();
 | 
			
		||||
bool CharacterActor::get_is_near_target() const {
 | 
			
		||||
    Node3D *target_node3d = Object::cast_to<Node3D>(this->target);
 | 
			
		||||
    return target_node3d ? target_node3d->get_global_position().distance_to(this->get_global_position()) < 5.f : false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
goap::Planner *CharacterActor::get_planner() const {
 | 
			
		||||
    return this->planner;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CharacterActor::set_target(Node *target) {
 | 
			
		||||
    this->target = target;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Node *CharacterActor::get_target() const {
 | 
			
		||||
    return this->target;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CharacterActor::set_state(goap::State state) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@ namespace godot {
 | 
			
		|||
class NavigationAgent3D;
 | 
			
		||||
class TunnelsPlayer;
 | 
			
		||||
class AnimationPlayer;
 | 
			
		||||
 | 
			
		||||
namespace goap {
 | 
			
		||||
    class Planner;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -36,20 +37,27 @@ public:
 | 
			
		|||
    // fire weapon at a target position
 | 
			
		||||
    // calls aim(at) and set_firing(true)
 | 
			
		||||
    void shoot_at(Vector3 at);
 | 
			
		||||
    // refresh the current action, ending the previous one
 | 
			
		||||
    void force_update_action();
 | 
			
		||||
    // getter-setters
 | 
			
		||||
    void set_firing(bool firing);
 | 
			
		||||
    void set_manual_mode(bool value);
 | 
			
		||||
 | 
			
		||||
    void set_rotation_speed_curve(Ref<Curve> curve);
 | 
			
		||||
    Ref<Curve> get_rotation_speed_curve() const;
 | 
			
		||||
 | 
			
		||||
    virtual Health *get_health() override;
 | 
			
		||||
    virtual Health const *get_health() const override;
 | 
			
		||||
 | 
			
		||||
    void set_character_data(Ref<CharacterData> data);
 | 
			
		||||
    void set_weapon_muzzle(Node3D *node);
 | 
			
		||||
    void set_velocity_target(Vector3 value);
 | 
			
		||||
    Vector3 get_velocity_target() const;
 | 
			
		||||
    bool get_is_near_player() const;
 | 
			
		||||
    CharacterActor *get_player_character() const;
 | 
			
		||||
    bool get_is_near_target() const;
 | 
			
		||||
    goap::Planner *get_planner() const;
 | 
			
		||||
    void set_target(Node *target);
 | 
			
		||||
    Node *get_target() const;
 | 
			
		||||
    void set_state(goap::State state);
 | 
			
		||||
protected:
 | 
			
		||||
    void process_behaviour(double delta_time);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,6 @@ CharacterActor *GlobalWorldState::get_player_character() const {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
Variant GlobalWorldState::get_world_property(StringName prop_key) {
 | 
			
		||||
    UtilityFunctions::print("fetching: ", prop_key);
 | 
			
		||||
    // check if prop key corresponds to a global key
 | 
			
		||||
    if(!prop_key.begins_with("g_"))
 | 
			
		||||
        return nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -56,9 +55,12 @@ Variant GlobalWorldState::get_world_property(StringName prop_key) {
 | 
			
		|||
        // cache and return
 | 
			
		||||
        this->global_state_cache.insert(prop_key, result);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
    } else {
 | 
			
		||||
#ifdef DEBUG_ENABLED_ENABLED
 | 
			
		||||
        abort();
 | 
			
		||||
#endif
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GlobalWorldState *GlobalWorldState::singleton_instance{nullptr};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								src/goal_marker.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/goal_marker.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
#include "goal_marker.hpp"
 | 
			
		||||
#include "godot_cpp/classes/global_constants.hpp"
 | 
			
		||||
#include "planner.hpp"
 | 
			
		||||
#include "utils/godot_macros.h"
 | 
			
		||||
 | 
			
		||||
namespace godot {
 | 
			
		||||
void GoalMarker::_bind_methods() {
 | 
			
		||||
#define CLASSNAME GoalMarker
 | 
			
		||||
    GDPROPERTY_HINTED(goal, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Goal");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Ref<goap::Goal> GoalMarker::get_goal() const {
 | 
			
		||||
    return this->goal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GoalMarker::set_goal(Ref<goap::Goal> goal) {
 | 
			
		||||
    this->goal = goal;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/goal_marker.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/goal_marker.hpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
#ifndef GOAL_MARKER_HPP
 | 
			
		||||
#define GOAL_MARKER_HPP
 | 
			
		||||
 | 
			
		||||
#include <godot_cpp/classes/area3d.hpp>
 | 
			
		||||
 | 
			
		||||
namespace godot {
 | 
			
		||||
class CharacterActor;
 | 
			
		||||
namespace goap {
 | 
			
		||||
    class Planner;
 | 
			
		||||
    class Goal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class GoalMarker : public Area3D {
 | 
			
		||||
    GDCLASS(GoalMarker, Area3D);
 | 
			
		||||
    static void _bind_methods();
 | 
			
		||||
public:
 | 
			
		||||
    Ref<goap::Goal> get_goal() const;
 | 
			
		||||
    void set_goal(Ref<goap::Goal> goal);
 | 
			
		||||
private:
 | 
			
		||||
    Ref<goap::Goal> goal{nullptr};
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif // !GOAL_MARKER_HPP
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +66,6 @@ void Planner::_bind_methods() {
 | 
			
		|||
 | 
			
		||||
void Planner::_enter_tree() {
 | 
			
		||||
    this->global_world_state = GlobalWorldState::get_singleton();
 | 
			
		||||
    UtilityFunctions::print("global world state cached: ", this->global_world_state);
 | 
			
		||||
    this->actor = Object::cast_to<CharacterActor>(this->get_parent());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -83,9 +82,12 @@ static Vector<Ref<Action>> trace_path(FromMap &map, PlannerNode &end) {
 | 
			
		|||
Vector<Ref<Action>> Planner::make_plan() {
 | 
			
		||||
    // clear cache every planning phase
 | 
			
		||||
    this->cached_world_state.clear();
 | 
			
		||||
    // select the most desirable goal available
 | 
			
		||||
    Ref<Goal> goal = this->select_goal();
 | 
			
		||||
    if(!goal.is_valid())
 | 
			
		||||
        return {};
 | 
			
		||||
    if(!goal.is_valid()) {
 | 
			
		||||
        this->plan = {};
 | 
			
		||||
        return this->plan;
 | 
			
		||||
    }
 | 
			
		||||
    // ordered list of all nodes still being considered
 | 
			
		||||
    Vector<PlannerNode> open{PlannerNode::goal_node(goal->goal_state)};
 | 
			
		||||
    PlannerNode first = open.get(0);
 | 
			
		||||
| 
						 | 
				
			
			@ -99,8 +101,10 @@ Vector<Ref<Action>> Planner::make_plan() {
 | 
			
		|||
        // current is the top of the ordered list
 | 
			
		||||
        current = open.get(0);
 | 
			
		||||
        // check if we've reached the goal
 | 
			
		||||
        if(current.open_requirements.is_empty())
 | 
			
		||||
            return trace_path(from, current);
 | 
			
		||||
        if(current.open_requirements.is_empty()) {
 | 
			
		||||
            this->plan = trace_path(from, current);
 | 
			
		||||
            return this->plan;
 | 
			
		||||
        }
 | 
			
		||||
        // current is no longer considered as it cannot be the end
 | 
			
		||||
        open.erase(current);
 | 
			
		||||
        // find all neighbours of this state
 | 
			
		||||
| 
						 | 
				
			
			@ -119,19 +123,15 @@ Vector<Ref<Action>> Planner::make_plan() {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return {};
 | 
			
		||||
    UtilityFunctions::push_warning("Failed to find a path satisfying goal");
 | 
			
		||||
    this->plan = {};
 | 
			
		||||
    return this->plan;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Ref<Goal> Planner::select_goal() {
 | 
			
		||||
    for(Ref<Goal> const &goal : this->goals) {
 | 
			
		||||
        bool can_try{true};
 | 
			
		||||
        for(WorldProperty const &prop : goal->prerequisites) {
 | 
			
		||||
            if(prop.value != this->get_world_property(prop.key)) {
 | 
			
		||||
                can_try = false;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if(can_try) return goal;
 | 
			
		||||
        if(this->can_do(goal))
 | 
			
		||||
            return goal;
 | 
			
		||||
    }
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -149,10 +149,16 @@ Variant Planner::get_world_property(StringName prop_key) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
bool Planner::can_do(Ref<Action> action) {
 | 
			
		||||
    for(WorldProperty &prop : action->context_prerequisites) {
 | 
			
		||||
    for(WorldProperty &prop : action->context_prerequisites)
 | 
			
		||||
        if(this->get_world_property(prop.key) != prop.value)
 | 
			
		||||
            return false;
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Planner::can_do(Ref<Goal> goal) {
 | 
			
		||||
    for(WorldProperty const &prop : goal->prerequisites)
 | 
			
		||||
        if(this->get_world_property(prop.key) != prop.value)
 | 
			
		||||
            return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -235,4 +241,14 @@ Array Planner::get_goals() const {
 | 
			
		|||
    }
 | 
			
		||||
    return array;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Planner::add_goal(Ref<Goal> goal) {
 | 
			
		||||
    bool can_do = this->can_do(goal);
 | 
			
		||||
    this->goals.insert(0, goal);
 | 
			
		||||
    return can_do;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Planner::remove_goal(Ref<Goal> goal) {
 | 
			
		||||
    this->goals.erase(goal);
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
#define GOAP_PLANNER_HPP
 | 
			
		||||
 | 
			
		||||
#include "action.hpp"
 | 
			
		||||
#include "goal_marker.hpp"
 | 
			
		||||
#include "godot_cpp/variant/variant.hpp"
 | 
			
		||||
#include <godot_cpp/classes/node.hpp>
 | 
			
		||||
#include <godot_cpp/classes/resource.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +53,7 @@ public:
 | 
			
		|||
    Variant get_world_property(StringName prop_key);
 | 
			
		||||
 | 
			
		||||
    bool can_do(Ref<Action> action);
 | 
			
		||||
    bool can_do(Ref<Goal> goal);
 | 
			
		||||
    Vector<PlannerNode> find_neighbours_of(PlannerNode &node);
 | 
			
		||||
    Vector<Ref<Action>> find_actions_satisfying(WorldState requirements);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -62,6 +64,8 @@ public:
 | 
			
		|||
    Array get_actions() const;
 | 
			
		||||
    void set_goals(Array goals);
 | 
			
		||||
    Array get_goals() const;
 | 
			
		||||
    bool add_goal(Ref<Goal> goal);
 | 
			
		||||
    void remove_goal(Ref<Goal> goal);
 | 
			
		||||
private:
 | 
			
		||||
    CharacterActor *actor{nullptr}; // the parent actor of this planner
 | 
			
		||||
    WorldState cached_world_state{}; // the cached worldstate, cleared for every make_plan call
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
#include "character_data.hpp"
 | 
			
		||||
#include "enemy.hpp"
 | 
			
		||||
#include "global_world_state.hpp"
 | 
			
		||||
#include "goal_marker.hpp"
 | 
			
		||||
#include "health.hpp"
 | 
			
		||||
#include "pellet_projectile.hpp"
 | 
			
		||||
#include "planner.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +65,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
 | 
			
		|||
    ClassDB::register_class<goap::ActivateStateArgs>();
 | 
			
		||||
    ClassDB::register_class<goap::Goal>();
 | 
			
		||||
    ClassDB::register_class<goap::Planner>();
 | 
			
		||||
    ClassDB::register_class<GoalMarker>();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,7 +33,19 @@ void TunnelsGameMode::register_player_character(CharacterActor *actor) {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TunnelsGameMode::set_manual_character(CharacterActor *actor) {
 | 
			
		||||
    if(!this->player_characters.has(actor))
 | 
			
		||||
        this->register_player_character(actor);
 | 
			
		||||
    this->manual_character = actor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TunnelsGameMode::on_character_destroyed(CharacterActor *actor) {
 | 
			
		||||
    this->player_characters.erase(actor);
 | 
			
		||||
    if(this->manual_character == actor)
 | 
			
		||||
        this->manual_character = nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Vector<CharacterActor*> const &TunnelsGameMode::get_player_characters() const {
 | 
			
		||||
    return this->player_characters;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ public:
 | 
			
		|||
    void register_player_character(CharacterActor *actor);
 | 
			
		||||
    void set_manual_character(CharacterActor *actor);
 | 
			
		||||
    void on_character_destroyed(CharacterActor *actor);
 | 
			
		||||
    Vector<CharacterActor*> const &get_player_characters() const;
 | 
			
		||||
private:
 | 
			
		||||
    TunnelsPlayer *player{nullptr};
 | 
			
		||||
    CharacterActor *manual_character{nullptr};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,15 +1,22 @@
 | 
			
		|||
#include "tunnels_player.hpp"
 | 
			
		||||
#include "character_actor.hpp"
 | 
			
		||||
#include "character_data.hpp"
 | 
			
		||||
#include "goal_marker.hpp"
 | 
			
		||||
#include "godot_cpp/variant/utility_functions.hpp"
 | 
			
		||||
#include "planner.hpp"
 | 
			
		||||
#include "tunnels_game_mode.hpp"
 | 
			
		||||
#include "tunnels_game_state.hpp"
 | 
			
		||||
#include "utils/game_root.hpp"
 | 
			
		||||
#include "utils/godot_macros.h"
 | 
			
		||||
#include "utils/player_input.hpp"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <godot_cpp/classes/input_event.hpp>
 | 
			
		||||
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
 | 
			
		||||
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
 | 
			
		||||
#include <godot_cpp/classes/resource_loader.hpp>
 | 
			
		||||
#include <godot_cpp/classes/scene_state.hpp>
 | 
			
		||||
#include <godot_cpp/classes/viewport.hpp>
 | 
			
		||||
#include <godot_cpp/classes/world3d.hpp>
 | 
			
		||||
#include <godot_cpp/variant/plane.hpp>
 | 
			
		||||
#include <godot_cpp/variant/projection.hpp>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +26,7 @@ void TunnelsPlayer::_bind_methods() {
 | 
			
		|||
    GDFUNCTION_ARGS(horizontal_move_input, "event", "value");
 | 
			
		||||
    GDFUNCTION_ARGS(vertical_move_input, "event", "value");
 | 
			
		||||
    GDFUNCTION_ARGS(fire_pressed, "event", "value");
 | 
			
		||||
    GDFUNCTION_ARGS(mode_switch_input, "event", "value");
 | 
			
		||||
    GDPROPERTY_HINTED(camera_rotation_ramp, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -55,10 +63,11 @@ void TunnelsPlayer::_process(double delta_time) { GDGAMEONLY();
 | 
			
		|||
        this->set_global_position(this->character->get_global_position());
 | 
			
		||||
        break;
 | 
			
		||||
    case State::Tactics:
 | 
			
		||||
        // move camera along with the input
 | 
			
		||||
        this->set_global_position(this->get_global_position() + this->get_world_move_input().normalized() *
 | 
			
		||||
                                  delta_time * TunnelsPlayer::TACTICS_MOVEMENT_SPEED);
 | 
			
		||||
        break;
 | 
			
		||||
    case State::Overview:
 | 
			
		||||
        // move camera along with the input
 | 
			
		||||
        this->set_global_position(this->get_global_position() + this->get_world_move_input().normalized());
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -101,6 +110,7 @@ void TunnelsPlayer::setup_player_input(PlayerInput *input) {
 | 
			
		|||
    input->listen_to(PlayerInput::Listener("move_left", "move_right", this, "horizontal_move_input"));
 | 
			
		||||
    input->listen_to(PlayerInput::Listener("move_forward", "move_backward", this, "vertical_move_input"));
 | 
			
		||||
    input->listen_to(PlayerInput::Listener("fire", this, "fire_pressed"));
 | 
			
		||||
    input->listen_to(PlayerInput::Listener("tactics_mode", this, "mode_switch_input"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Node *TunnelsPlayer::to_node() {
 | 
			
		||||
| 
						 | 
				
			
			@ -115,8 +125,70 @@ void TunnelsPlayer::vertical_move_input(Ref<InputEvent> event, float value) {
 | 
			
		|||
    this->move_input.y = value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TunnelsPlayer::mode_switch_input(Ref<InputEvent> event, float value) {
 | 
			
		||||
    if(value != 0.f)
 | 
			
		||||
        this->state = this->state == State::Tactics ? State::ManualControl : State::Tactics;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TunnelsPlayer::fire_pressed(Ref<InputEvent> event, float value) {
 | 
			
		||||
    switch(this->state) {
 | 
			
		||||
    case State::ManualControl:
 | 
			
		||||
        this->character->set_firing(value != 0);
 | 
			
		||||
        break;
 | 
			
		||||
    case State::Tactics:
 | 
			
		||||
        if(value == 1.f)
 | 
			
		||||
            this->try_select_marker();
 | 
			
		||||
        break;
 | 
			
		||||
    case State::Overview:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TunnelsPlayer::try_select_marker() {
 | 
			
		||||
    UtilityFunctions::print("TunnelsPlayer::try_select_marker()");
 | 
			
		||||
    Transform3D const &camera_trans{this->camera->get_global_transform()};
 | 
			
		||||
    // prepare raycast query
 | 
			
		||||
    Ref<PhysicsRayQueryParameters3D> params{PhysicsRayQueryParameters3D::create(camera_trans.origin, camera_trans.origin + this->mouse_world_ray_normal * 1000.f)};
 | 
			
		||||
    params->set_collision_mask(1u << 3u);
 | 
			
		||||
    params->set_collide_with_areas(true);
 | 
			
		||||
    // fetch current physics state and cast ray
 | 
			
		||||
    PhysicsDirectSpaceState3D *state = this->get_world_3d()->get_direct_space_state();
 | 
			
		||||
    Dictionary dict{state->intersect_ray(params)};
 | 
			
		||||
    // fail if nothing was hit
 | 
			
		||||
    if(dict.is_empty())
 | 
			
		||||
        return;
 | 
			
		||||
    // attempt to cast hit node to a marker
 | 
			
		||||
    GoalMarker *marker{Object::cast_to<GoalMarker>(dict["collider"])};
 | 
			
		||||
    // fail if hit object is not a marker
 | 
			
		||||
    if(marker == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
    UtilityFunctions::print("Hit: ", marker->get_path());
 | 
			
		||||
    CharacterActor *target_character{nullptr};
 | 
			
		||||
    for(CharacterActor *loop_character : Ref<TunnelsGameMode>(GameRoot::get_singleton()->get_game_mode())->get_player_characters()) {
 | 
			
		||||
        if(loop_character != this->character) {
 | 
			
		||||
            target_character = loop_character;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // no non-player ally was found
 | 
			
		||||
    if(target_character == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
    // cache planner component
 | 
			
		||||
    goap::Planner *planner{target_character->get_planner()};
 | 
			
		||||
    // cache previous target in case planning fails
 | 
			
		||||
    Node *previous_target{target_character->get_target()};
 | 
			
		||||
    // attempt to find a plan to marker's goal
 | 
			
		||||
    target_character->set_target(marker);
 | 
			
		||||
    if(planner->can_do(marker->get_goal())) {
 | 
			
		||||
        planner->add_goal(marker->get_goal());
 | 
			
		||||
        planner->make_plan();
 | 
			
		||||
        target_character->force_update_action();
 | 
			
		||||
        UtilityFunctions::print("Made plan for character ", target_character->get_path());
 | 
			
		||||
    } else {
 | 
			
		||||
        // reset character to the state it was in before attempts to change goal
 | 
			
		||||
        UtilityFunctions::push_warning("Failed to make plan for ", marker->get_goal()->get_path());
 | 
			
		||||
        target_character->set_target(previous_target);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TunnelsPlayer::initialize_character() {
 | 
			
		||||
| 
						 | 
				
			
			@ -135,6 +207,7 @@ void TunnelsPlayer::initialize_character() {
 | 
			
		|||
    this->character->set_character_data(game_state->get_characters()[0]);
 | 
			
		||||
    // disable navmesh navigation and start using player input
 | 
			
		||||
    this->character->set_manual_mode(true);
 | 
			
		||||
    Ref<TunnelsGameMode>(GameRoot::get_singleton()->get_game_mode())->set_manual_character(this->character);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Vector3 TunnelsPlayer::get_world_move_input() const {
 | 
			
		||||
| 
						 | 
				
			
			@ -178,4 +251,5 @@ CharacterActor *TunnelsPlayer::get_character() const {
 | 
			
		|||
float const TunnelsPlayer::ROTATION_SPEED{0.5f};
 | 
			
		||||
float const TunnelsPlayer::ROTATION_Y_MIN_INFLUENCE{7.f};
 | 
			
		||||
float const TunnelsPlayer::ROTATION_MARGIN{0.4f};
 | 
			
		||||
float const TunnelsPlayer::TACTICS_MOVEMENT_SPEED{20.f};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,8 +35,10 @@ public:
 | 
			
		|||
 | 
			
		||||
    void horizontal_move_input(Ref<InputEvent> event, float value);
 | 
			
		||||
    void vertical_move_input(Ref<InputEvent> event, float value);
 | 
			
		||||
    void mode_switch_input(Ref<InputEvent> event, float value);
 | 
			
		||||
    void fire_pressed(Ref<InputEvent> event, float value);
 | 
			
		||||
 | 
			
		||||
    void try_select_marker();
 | 
			
		||||
    void initialize_character();
 | 
			
		||||
 | 
			
		||||
    Vector3 get_world_move_input() const;
 | 
			
		||||
| 
						 | 
				
			
			@ -61,6 +63,7 @@ private:
 | 
			
		|||
    static float const ROTATION_SPEED;
 | 
			
		||||
    static float const ROTATION_Y_MIN_INFLUENCE;
 | 
			
		||||
    static float const ROTATION_MARGIN;
 | 
			
		||||
    static float const TACTICS_MOVEMENT_SPEED;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue