Compare commits

..

No commits in common. "main" and "goap-planner" have entirely different histories.

42 changed files with 157 additions and 720 deletions

2
.gitmodules vendored
View file

@ -1,7 +1,7 @@
[submodule "godot-cpp"]
path = godot-cpp
url = https://github.com/godotengine/godot-cpp.git
branch = 4.2
branch = 4.1
[submodule "src/utils"]
path = src/utils

@ -1 +1 @@
Subproject commit 51c752c46b44769d3b6c661526c364a18ea64781
Subproject commit 4b63d795e4279838d988399f008eec47eb2dcc7f

Binary file not shown.

View file

@ -1,41 +0,0 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bw3gqiinifef4"
path="res://.godot/imported/whiteblock-station-level.glb-0ec8b9e904d919ae6bf68126120ff1a4.scn"
[deps]
source_file="res://Environments/Non-Modular/whiteblock-station-level.glb"
dest_files=["res://.godot/imported/whiteblock-station-level.glb-0ec8b9e904d919ae6bf68126120ff1a4.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=2
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
import_script/path=""
_subresources={
"materials": {
"Shadow": {
"use_external/enabled": true,
"use_external/path": "res://Environments/Special Materials/shadow.tres"
}
}
}
gltf/naming_version=1
gltf/embedded_image_handling=1

BIN
godot/Environments/Roots/roots_a.glb (Stored with Git LFS)

Binary file not shown.

View file

@ -1,34 +0,0 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://d3apnkhay2yq3"
path="res://.godot/imported/roots_a.glb-3fef5f980b3fb4c8f2d3e000d80eb81e.scn"
[deps]
source_file="res://Environments/Roots/roots_a.glb"
dest_files=["res://.godot/imported/roots_a.glb-3fef5f980b3fb4c8f2d3e000d80eb81e.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
import_script/path=""
_subresources={}
gltf/naming_version=1
gltf/embedded_image_handling=1

View file

@ -1,5 +0,0 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://ccujbp2eghtha"]
[resource]
shading_mode = 0
albedo_color = Color(0, 0, 0, 1)

View file

@ -8,6 +8,7 @@ point_count = 2
[resource]
projectile_scene = ExtResource("1_h12ld")
range = 25.0
range = 15.0
rounds_per_second = 10.0
allow_automatic = true
projectile_speed = SubResource("Curve_tdh3d")

View file

@ -4,7 +4,3 @@
[node name="GameRoot" type="GameRoot3D"]
first_boot_level = ExtResource("1_4g2mr")
[node name="GlobalWorldState" type="GlobalWorldState" parent="."]
process_priority = -1
process_physics_priority = -1

View file

@ -1,36 +1,21 @@
[gd_scene load_steps=4 format=3 uid="uid://cqkbxe758jr7p"]
[gd_scene load_steps=3 format=3 uid="uid://cqkbxe758jr7p"]
[sub_resource type="Curve" id="Curve_bxjan"]
_data = [Vector2(0, 0), 0.0, 3.33407, 0, 0, Vector2(0.430894, 0.692308), 0.730621, 0.730621, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 3
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_i3qba"]
no_depth_test = true
shading_mode = 0
disable_ambient_light = true
disable_fog = true
albedo_color = Color(1, 0.596078, 0.0705882, 1)
billboard_mode = 1
grow_amount = -1.0
[sub_resource type="PlaneMesh" id="PlaneMesh_rug6u"]
material = SubResource("StandardMaterial3D_i3qba")
size = Vector2(0.3, 0.3)
orientation = 2
[sub_resource type="SphereMesh" id="SphereMesh_jkn5p"]
radius = 0.2
height = 0.4
[node name="TunnelsPlayer" type="TunnelsPlayer"]
camera_rotation_ramp = SubResource("Curve_bxjan")
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(-1, -6.99417e-08, 5.24488e-08, 1.59825e-08, 0.443572, 0.896239, -8.59493e-08, 0.896239, -0.443572, -2.38419e-07, 9.56087, -0.526886)
cull_mask = 1048571
fov = 100.0
near = 0.1
far = 1000.0
[node name="Reticle" type="Node3D" parent="."]
[node name="MeshInstance3D" type="MeshInstance3D" parent="Reticle"]
layers = 8
gi_mode = 0
mesh = SubResource("PlaneMesh_rug6u")
mesh = SubResource("SphereMesh_jkn5p")

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=11 format=3 uid="uid://dpda341t6ipiv"]
[gd_scene load_steps=9 format=3 uid="uid://dpda341t6ipiv"]
[sub_resource type="Curve" id="Curve_7rmf4"]
min_value = 0.2
@ -6,8 +6,20 @@ 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="CapsuleShape3D" id="CapsuleShape3D_3g72p"]
height = 1.59321
[sub_resource type="CapsuleMesh" id="CapsuleMesh_rwcvu"]
height = 1.605
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_scmx3"]
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"
argument_property = &"player_character"
[sub_resource type="Action" id="Action_gtisq"]
effects = {
@ -15,15 +27,6 @@ effects = {
}
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
@ -32,51 +35,36 @@ prerequisites = {
"is_near_player": false
}
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_3g72p"]
[sub_resource type="CapsuleMesh" id="CapsuleMesh_rwcvu"]
radial_segments = 12
rings = 1
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_scmx3"]
albedo_color = Color(0.94902, 0.909804, 0, 1)
[sub_resource type="BoxMesh" id="BoxMesh_f5yvh"]
size = Vector3(0.125, 0.14, 0.94)
[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
[node name="ProjectilePool" type="ProjectilePool" parent="."]
[node name="WeaponMuzzle" type="WeaponMuzzle" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.53551, 1.36699, 0.121549)
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
avoidance_enabled = true
radius = 1.0
[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, 0.802835, 0)
shape = SubResource("CapsuleShape3D_3g72p")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.99491, 0)
layers = 2
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8121, 0)
mesh = SubResource("CapsuleMesh_rwcvu")
surface_material_override/0 = SubResource("StandardMaterial3D_scmx3")
[node name="MeshInstance3D2" type="MeshInstance3D" parent="MeshInstance3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.509142, 0.361236, 0.380722)
layers = 2
[node name="MeshInstance3D2" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.509142, 0.986876, 0.380722)
mesh = SubResource("BoxMesh_f5yvh")
skeleton = NodePath("../..")
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")]

View file

@ -42,19 +42,9 @@ 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_render/layer_1="Default"
3d_render/layer_2="Entities"
3d_render/layer_3="TacticsModeOnly"
3d_render/layer_4="NoShadows"
3d_physics/layer_1="Default"
3d_physics/layer_2="Vision"
3d_physics/layer_3="Hitboxes"
3d_physics/layer_4="Markers"

Binary file not shown.

View file

@ -1,26 +0,0 @@
[remap]
importer="2d_array_texture"
type="CompressedTexture2DArray"
uid="uid://batsfdiilq57g"
path="res://.godot/imported/test_level.exr-33d7ba8e62b2edfcc804854db2b1cf50.ctexarray"
metadata={
"vram_texture": false
}
[deps]
source_file="res://test_level.exr"
dest_files=["res://.godot/imported/test_level.exr-33d7ba8e62b2edfcc804854db2b1cf50.ctexarray"]
[params]
compress/mode=3
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/channel_pack=1
mipmaps/generate=false
mipmaps/limit=-1
slices/horizontal=1
slices/vertical=1

File diff suppressed because one or more lines are too long

BIN
models/long_light_a.blend (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
models/roots_a.blend (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
models/tunnel_wall_segment_a.blend (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
models/whiteblock-level.blend (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

View file

@ -1,5 +1,4 @@
#include "character_actor.hpp"
#include "goal_marker.hpp"
#include "planner.hpp"
#include "projectile_pool.hpp"
#include "state.hpp"
@ -16,41 +15,34 @@ namespace godot {
void CharacterActor::_bind_methods() {
#define CLASSNAME CharacterActor
GDPROPERTY_HINTED(rotation_speed_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
GDPROPERTY_HINTED(target, Variant::OBJECT, PROPERTY_HINT_NODE_TYPE, "Node");
GDPROPERTY(acceleration, Variant::FLOAT);
GDPROPERTY(walk_speed, Variant::FLOAT);
GDPROPERTY(sprint_speed, Variant::FLOAT);
GDPROPERTY(rotation_speed, Variant::FLOAT);
GDFUNCTION_ARGS(set_velocity_target, "value");
GDFUNCTION(get_is_near_player);
GDFUNCTION(get_player_character);
}
void CharacterActor::_enter_tree() { GDGAMEONLY();
this->nav_agent = this->get_node<NavigationAgent3D>("NavigationAgent3D");
this->nav_agent->connect("velocity_computed", callable_mp(this, &CharacterActor::set_velocity_target));
this->nav_agent->connect("velocity_computed", Callable(this, "set_velocity_target"));
this->target_rotation = this->get_global_transform().get_basis().get_quaternion();
this->health = this->get_node<Health>("Health");
this->primary_weapon_pool = this->get_node<ProjectilePool>("ProjectilePool");
this->planner = this->get_node<goap::Planner>("Planner");
Ref<TunnelsGameMode> game_mode = GameRoot::get_singleton()->get_game_mode();
game_mode->register_player_character(this);
}
void CharacterActor::_process(double delta_time) { GDGAMEONLY();
this->process_rotation(delta_time);
if(!this->mode_manual) {
this->process_behaviour(delta_time);
this->process_navigation(delta_time);
if(!this->velocity_target.is_zero_approx())
this->aim_direction(this->velocity_target.normalized());
}
if(this->firing) {
this->try_fire_weapon();
}
this->process_rotation(delta_time);
}
void CharacterActor::_physics_process(double delta_time) { GDGAMEONLY();
// accelerate towards velocity target
Vector3 const new_velocity = this->get_velocity().move_toward(this->velocity_target, delta_time * CharacterActor::acceleration);
Vector3 const new_velocity = this->get_velocity().move_toward(this->velocity_target, delta_time * CharacterActor::ACCELERATION);
Vector3 const gravity{Vector3{0.f, this->get_velocity().y - 9.8f, 0.f}};
// apply either gravity or walking velocity depending on results
this->set_velocity(this->is_on_floor() ? new_velocity : this->get_velocity() + gravity);
@ -59,7 +51,7 @@ void CharacterActor::_physics_process(double delta_time) { GDGAMEONLY();
}
void CharacterActor::move(Vector3 world_vector) {
this->velocity_target = world_vector * CharacterActor::walk_speed;
this->velocity_target = world_vector * CharacterActor::WALK_SPEED;
}
void CharacterActor::aim(Vector3 at) {
@ -90,10 +82,6 @@ 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;
}
@ -141,40 +129,12 @@ Vector3 CharacterActor::get_velocity_target() const {
}
bool CharacterActor::get_is_near_player() const {
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;
return this->get_player_character()->get_global_position().distance_to(this->get_global_position()) < 5.f;
}
bool CharacterActor::get_is_near_target() const {
GoalMarker *target_marker = Object::cast_to<GoalMarker>(this->target);
Node3D *target_node3d = Object::cast_to<Node3D>(this->target);
return target_marker
? target_marker->is_point_on(this->get_global_position())
: (target_node3d && target_node3d->get_global_position().distance_to(this->get_global_position()) < 5.f);
}
Vector3 CharacterActor::get_move_target() const {
GoalMarker *as_marker = Object::cast_to<GoalMarker>(this->current_state.move_to);
if(as_marker)
return as_marker->nearest_point_on(this->get_global_position());
Node3D *as_node3d = Object::cast_to<Node3D>(this->current_state.move_to);
if(as_node3d)
return as_node3d->get_global_position();
return this->get_global_position();
}
void CharacterActor::set_target(Node *target) {
this->target = target;
}
Node *CharacterActor::get_target() const {
return this->target;
}
goap::Planner *CharacterActor::get_planner() const {
return this->planner;
CharacterActor *CharacterActor::get_player_character() const {
Ref<TunnelsGameMode> game_mode = GameRoot::get_singleton()->get_game_mode();
return game_mode->get_player_instance()->get_character();
}
void CharacterActor::set_state(goap::State state) {
@ -190,7 +150,7 @@ void CharacterActor::set_state(goap::State state) {
default:
break;
case goap::State::STATE_MOVE_TO:
this->move_to(this->get_move_target());
this->move_to(state.move_to->get_global_position());
break;
}
}
@ -202,8 +162,8 @@ void CharacterActor::process_behaviour(double delta_time) {
default:
break;
case goap::State::STATE_MOVE_TO:
if(this->nav_agent->get_target_position().distance_to(this->get_move_target()) > 2.f)
this->nav_agent->set_target_position(this->get_move_target());
if(this->nav_agent->get_target_position().distance_to(this->current_state.move_to->get_global_position()) > 2.f)
this->nav_agent->set_target_position(this->current_state.move_to->get_global_position());
break;
case goap::State::STATE_ACTIVATE:
break;
@ -219,7 +179,7 @@ void CharacterActor::process_navigation(double delta_time) {
Vector3 const target_position = this->nav_agent->get_next_path_position();
Vector3 const direction = (target_position - this->get_global_position()).normalized();
if(this->nav_agent->get_avoidance_enabled())
this->nav_agent->set_velocity(direction * CharacterActor::walk_speed);
this->nav_agent->set_velocity(direction * CharacterActor::WALK_SPEED);
else
this->move(direction);
}
@ -236,7 +196,7 @@ void CharacterActor::process_rotation(double delta_time) {
// calculate the angle that still needs to be traveled
float const angle = current_quaternion.angle_to(target_quaternion);
// calculate the angle amount that can be moved this frame
float const angle_step{float(this->rotation_speed_curve->sample(angle) * CharacterActor::rotation_speed * delta_time)};
float const angle_step{float(this->rotation_speed_curve->sample(angle) * CharacterActor::ROTATION_SPEED * delta_time)};
// update this object's global transform with the new rotation
basis.set_quaternion(angle < angle_step ? target_quaternion // to avoid overshooting, check if the max step is smaller than the angle distance
: current_quaternion.slerp(target_quaternion, angle_step / angle)); // convert the angle step to a lerp t value between current and target rotations
@ -255,35 +215,8 @@ void CharacterActor::try_fire_weapon() {
node->set_global_transform(this->weapon_muzzle->get_global_transform());
}
void CharacterActor::set_acceleration(float acceleration) {
this->acceleration = acceleration;
}
float CharacterActor::get_acceleration() const {
return this->acceleration;
}
void CharacterActor::set_walk_speed(float walk_speed) {
this->walk_speed = walk_speed;
}
float CharacterActor::get_walk_speed() const {
return this->walk_speed;
}
void CharacterActor::set_sprint_speed(float sprint_speed) {
this->sprint_speed = sprint_speed;
}
float CharacterActor::get_sprint_speed() const {
return this->sprint_speed;
}
void CharacterActor::set_rotation_speed(float rotation_speed) {
this->rotation_speed = rotation_speed;
}
float CharacterActor::get_rotation_speed() const {
return this->rotation_speed;
}
float const CharacterActor::ACCELERATION{20.f};
float const CharacterActor::WALK_SPEED{3.f};
float const CharacterActor::SPRINT_SPEED{5.f};
float const CharacterActor::ROTATION_SPEED{10.f};
}

View file

@ -12,17 +12,10 @@ namespace godot {
class NavigationAgent3D;
class TunnelsPlayer;
class AnimationPlayer;
namespace goap {
class Planner;
};
enum class Team {
CHARACTER_TEAM_WORLD = 0u,
CHARACTER_TEAM_PLAYER = 1u,
CHARACTER_TEAM_ENEMY = 2u,
};
class CharacterActor : public CharacterBody3D,
public IHealthEntity {
GDCLASS(CharacterActor, CharacterBody3D);
@ -43,42 +36,21 @@ 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;
// planner getters
bool get_is_near_player() const;
bool get_is_near_target() const;
Vector3 get_move_target() const;
// getter - setters
void set_target(Node *target);
Node *get_target() const;
goap::Planner *get_planner() const;
CharacterActor *get_player_character() const;
void set_state(goap::State state);
void set_acceleration(float acceleration);
float get_acceleration() const;
void set_walk_speed(float walk_speed);
float get_walk_speed() const;
void set_sprint_speed(float sprint_speed);
float get_sprint_speed() const;
void set_rotation_speed(float rotation_speed);
float get_rotation_speed() const;
protected:
void process_behaviour(double delta_time);
void process_navigation(double delta_time);
@ -113,10 +85,10 @@ private:
Ref<CharacterData> data;
float fire_interval{0.f}; // derived from 1 / the current weapon's rps
float acceleration{20.f};
float walk_speed{3.f};
float sprint_speed{5.f};
float rotation_speed{10.f};
static float const ACCELERATION;
static float const WALK_SPEED;
static float const SPRINT_SPEED;
static float const ROTATION_SPEED;
};
}

View file

@ -1,13 +1,10 @@
#include "global_world_state.hpp"
#include "character_actor.hpp"
#include "utils/game_root.hpp"
#include "utils/godot_macros.h"
#include <godot_cpp/variant/utility_functions.hpp>
namespace godot::goap {
void GlobalWorldState::_bind_methods() {
#define CLASSNAME GlobalWorldState
GDFUNCTION(get_player_character);
}
bool GlobalWorldState::has_singleton() {
@ -18,27 +15,26 @@ GlobalWorldState *GlobalWorldState::get_singleton() {
return GlobalWorldState::singleton_instance;
}
void GlobalWorldState::_enter_tree() { GDGAMEONLY();
if(GlobalWorldState::singleton_instance == nullptr) {
void GlobalWorldState::_enter_tree() {
if(GlobalWorldState::singleton_instance == nullptr)
GlobalWorldState::singleton_instance = this;
}
void GlobalWorldState::_ready() {
this->game_mode = GameRoot::get_singleton()->get_game_mode();
}
void GlobalWorldState::_exit_tree() { GDGAMEONLY();
void GlobalWorldState::_exit_tree() {
if(GlobalWorldState::singleton_instance == this)
GlobalWorldState::singleton_instance = nullptr;
}
void GlobalWorldState::_process(double delta_time) { GDGAMEONLY();
void GlobalWorldState::_process(double delta_time) {
global_state_cache.clear(); // invalidate cache
}
Vector3 GlobalWorldState::get_player_position() const {
return this->get_player_character()->get_global_position();
}
CharacterActor *GlobalWorldState::get_player_character() const {
return Ref<TunnelsGameMode>(GameRoot::get_singleton()->get_game_mode())->get_player_instance()->get_character();
Vector3 GlobalWorldState::get_player_position() {
return this->game_mode->get_player_instance()->get_character()->get_global_position();
}
Variant GlobalWorldState::get_world_property(StringName prop_key) {
@ -49,18 +45,14 @@ Variant GlobalWorldState::get_world_property(StringName prop_key) {
else if(global_state_cache.has(prop_key))
return global_state_cache.get(prop_key);
// fetch by function name
StringName const fn = "get_" + prop_key.erase(0, 2);
StringName const fn = "get_" + prop_key.right(prop_key.length() - 2);
if(this->has_method(fn)) {
Variant result = this->call(fn);
// cache and return
this->global_state_cache.insert(prop_key, result);
return result;
} else {
#ifdef DEBUG_ENABLED_ENABLED
abort();
#endif
return nullptr;
}
return nullptr;
}
GlobalWorldState *GlobalWorldState::singleton_instance{nullptr};

View file

@ -14,15 +14,16 @@ public:
static GlobalWorldState *get_singleton();
virtual void _enter_tree() override;
virtual void _ready() override;
virtual void _exit_tree() override;
virtual void _process(double delta_time) override;
Vector3 get_player_position() const;
CharacterActor *get_player_character() const;
Vector3 get_player_position();
Variant get_world_property(StringName prop_key);
private:
WorldState global_state_cache{};
Ref<TunnelsGameMode> game_mode{};
static GlobalWorldState *singleton_instance;
};
}

View file

@ -1,63 +0,0 @@
#include "goal_marker.hpp"
#include "godot_cpp/core/math.hpp"
#include "planner.hpp"
#include "utils/godot_macros.h"
#include <godot_cpp/classes/global_constants.hpp>
namespace godot {
void GoalMarker::_bind_methods() {
#define CLASSNAME GoalMarker
GDPROPERTY_HINTED(goal, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Goal");
}
bool GoalMarker::is_point_on(Vector3 point) {
return point.distance_squared_to(this->get_global_position()) <= .5f * .5f;
}
Vector3 GoalMarker::nearest_point_on(Vector3 nearest_to) {
return this->get_global_position();
}
void GoalMarker::set_goal(Ref<goap::Goal> goal) {
this->goal = goal;
}
Ref<goap::Goal> GoalMarker::get_goal() const {
return this->goal;
}
void GoalMarker::set_radius(float radius) {
this->radius = radius;
}
float GoalMarker::get_radius() const {
return this->radius;
}
#undef CLASSNAME
void LineGoalMarker::_bind_methods() {
#define CLASSNAME LineGoalMarker
GDPROPERTY(extent, Variant::FLOAT);
}
bool LineGoalMarker::is_point_on(Vector3 point) {
return this->nearest_point_on(point).distance_to(point) < this->radius * this->radius;
}
Vector3 LineGoalMarker::nearest_point_on(Vector3 point) {
Basis const &basis{this->get_global_basis()};
Vector3 const left_unit{basis.get_column(0).normalized()};
Vector3 const right_vec{this->get_global_position() + left_unit * -extent};
float const length{extent * 2.f};
return right_vec + left_unit * Math::clamp(left_unit.dot(point - right_vec), 0.f, length);
}
void LineGoalMarker::set_extent(float extent) {
this->extent = extent;
}
float LineGoalMarker::get_extent() const {
return this->extent;
}
}

View file

@ -1,43 +0,0 @@
#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:
virtual bool is_point_on(Vector3 point);
virtual Vector3 nearest_point_on(Vector3 near_to);
void set_goal(Ref<goap::Goal> goal);
Ref<goap::Goal> get_goal() const;
void set_radius(float radius);
float get_radius() const;
protected:
Ref<goap::Goal> goal{nullptr};
float radius{0.2f};
};
class LineGoalMarker : public GoalMarker {
GDCLASS(LineGoalMarker, GoalMarker);
static void _bind_methods();
public:
virtual bool is_point_on(Vector3 point) override;
virtual Vector3 nearest_point_on(Vector3 near_to) override;
void set_extent(float left);
float get_extent() const;
private:
float extent{0.5f};
};
}
#endif // !GOAL_MARKER_HPP

View file

@ -19,14 +19,15 @@ void PelletProjectile::_physics_process(double delta_time) { GDGAMEONLY();
float const speed = this->data->get_projectile_speed()->sample(distance_traveled / this->data->get_range());
this->distance_traveled += speed;
Vector3 const next_position{this->get_global_position() + this->get_global_transform().basis.get_column(2) * speed};
if(this->check_hit(next_position) || this->distance_traveled > this->data->get_range())
if(this->check_hit(next_position) || this->distance_traveled > this->data->get_range()) {
this->return_to_pool();
else
} else {
this->set_global_position(next_position);
}
}
bool PelletProjectile::check_hit(Vector3 next_position) {
Ref<PhysicsRayQueryParameters3D> query{PhysicsRayQueryParameters3D::create(this->get_global_position(), next_position, 0b101)};
Ref<PhysicsRayQueryParameters3D> query{PhysicsRayQueryParameters3D::create(this->get_global_position(), next_position, 0x4)};
Dictionary hit = this->get_world_3d()->get_direct_space_state()->intersect_ray(query);
if(hit.is_empty())
return false;
@ -35,7 +36,7 @@ bool PelletProjectile::check_hit(Vector3 next_position) {
return false;
IHealthEntity *health_entity = dynamic_cast<IHealthEntity*>(collider);
if(health_entity == nullptr)
return true;
return false;
health_entity->get_health()->damage(this->data->get_damage());
return true;
}

View file

@ -1,6 +1,7 @@
#ifndef PELLET_PROJECTILE_HPP
#define PELLET_PROJECTILE_HPP
#include "weapon_data.hpp"
#include "projectile.hpp"
#include <godot_cpp/classes/node3d.hpp>

View file

@ -13,8 +13,8 @@
#include <godot_cpp/variant/vector3.hpp>
namespace godot::goap {
typedef HashMap<PlannerNode, PlannerNode, PlannerNodeHasher> NodeNodeMap;
typedef HashMap<PlannerNode, float, PlannerNodeHasher> NodeScoreMap;
typedef HashMap<PlannerNode, PlannerNode, PlannerNodeHasher> FromMap;
typedef HashMap<PlannerNode, float, PlannerNodeHasher> ScoreMap;
typedef HashSet<PlannerNode, PlannerNodeHasher> NodeSet;
void Goal::_bind_methods() {
@ -64,12 +64,12 @@ void Planner::_bind_methods() {
GDPROPERTY_HINTED(goals, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Goal));
}
void Planner::_enter_tree() {
void Planner::_ready() {
this->global_world_state = GlobalWorldState::get_singleton();
this->actor = Object::cast_to<CharacterActor>(this->get_parent());
}
static Vector<Ref<Action>> trace_path(NodeNodeMap &map, PlannerNode &end) {
static Vector<Ref<Action>> trace_path(FromMap &map, PlannerNode &end) {
Vector<Ref<Action>> edges{};
PlannerNode node{end};
while(node.last_edge.is_valid()) {
@ -82,29 +82,24 @@ static Vector<Ref<Action>> trace_path(NodeNodeMap &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()) {
this->plan = {};
return this->plan;
}
if(!goal.is_valid())
return {};
// ordered list of all nodes still being considered
Vector<PlannerNode> open{PlannerNode::goal_node(goal->goal_state)};
PlannerNode first = open.get(0);
NodeNodeMap from{}; // mapping states to the previous in the path
NodeScoreMap dist_traveled{}; // mapping states to the shortest found distance from start
FromMap from{}; // mapping states to the previous in the path
ScoreMap dist_traveled{}; // mapping states to the shortest found distance from start
dist_traveled.insert(first, 0);
NodeScoreMap best_guess{}; // mapping states to the best guess of the distance to the goal
ScoreMap best_guess{}; // mapping states to the best guess of the distance to the goal
best_guess.insert(first, first.open_requirements.size());
PlannerNode current{}; // state we're checking for neighbours or completion
while(!open.is_empty()) {
// 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()) {
this->plan = trace_path(from, current);
return this->plan;
}
if(current.open_requirements.is_empty())
return trace_path(from, current);
// current is no longer considered as it cannot be the end
open.erase(current);
// find all neighbours of this state
@ -123,15 +118,19 @@ Vector<Ref<Action>> Planner::make_plan() {
}
}
}
UtilityFunctions::push_warning("Failed to find a plan satisfying goal");
this->plan = {};
return this->plan;
return {};
}
Ref<Goal> Planner::select_goal() {
for(Ref<Goal> const &goal : this->goals) {
if(this->can_do(goal))
return goal;
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;
}
return {};
}
@ -149,16 +148,10 @@ 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;
}
@ -203,7 +196,7 @@ State Planner::get_next_state() {
this->plan = this->make_plan();
if(this->plan.is_empty())
return State::new_invalid();
return this->plan.get(0)->apply_state->construct(this);
return this->plan.get(0)->apply_state->construct(this->actor);
}
void Planner::set_actions(Array value) {
@ -241,14 +234,4 @@ 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);
}
}

View file

@ -2,7 +2,6 @@
#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>
@ -45,7 +44,7 @@ class Planner : public Node {
GDCLASS(Planner, Node);
static void _bind_methods();
public:
virtual void _enter_tree() override;
virtual void _ready() override;
Vector<Ref<Action>> make_plan();
Ref<Goal> select_goal();
@ -53,7 +52,6 @@ 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);
@ -64,8 +62,6 @@ 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

View file

@ -4,7 +4,6 @@
#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"
@ -65,8 +64,6 @@ 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>();
ClassDB::register_class<LineGoalMarker>();
}
extern "C"

View file

@ -1,6 +1,5 @@
#include "state.hpp"
#include "character_actor.hpp"
#include "planner.hpp"
#include "utils/godot_macros.h"
namespace godot::goap {
@ -52,7 +51,7 @@ void StateArgs::_bind_methods() {
GDPROPERTY(argument_property, Variant::STRING_NAME);
}
State StateArgs::construct(Planner *context) const {
State StateArgs::construct(Node *context) const {
return { .type = State::STATE_TYPE_MAX };
}
@ -61,21 +60,21 @@ StringName StateArgs::get_argument_property() const { return this->argument_prop
void MoveStateArgs::_bind_methods() {}
State MoveStateArgs::construct(Planner *context) const {
Node3D *node = Object::cast_to<Node3D>(context->get_world_property(this->argument_property));
State MoveStateArgs::construct(Node *context) const {
Node3D *node = Object::cast_to<Node3D>(context->call("get_" + this->argument_property));
return State::new_move_to(node);
}
void AnimateStateArgs::_bind_methods() {}
State AnimateStateArgs::construct(Planner *context) const {
return State::new_animate(context->get_world_property(this->argument_property));
State AnimateStateArgs::construct(Node *context) const {
return State::new_animate(context->call("get_" + this->argument_property));
}
void ActivateStateArgs::_bind_methods() {}
State ActivateStateArgs::construct(Planner *context) const {
Node *node = Object::cast_to<Node>(context->get_world_property(this->argument_property));
State ActivateStateArgs::construct(Node *context) const {
Node *node = Object::cast_to<Node>(context->call("get_" + this->argument_property));
return State::new_activate(node);
}
}

View file

@ -9,8 +9,6 @@
namespace godot { class CharacterActor; }
namespace godot::goap {
class Planner;
struct State {
~State();
static State new_move_to(Node3D *location);
@ -38,7 +36,7 @@ class StateArgs : public Resource {
GDCLASS(StateArgs, Resource);
static void _bind_methods();
public:
virtual State construct(Planner *context) const;
virtual State construct(Node *context) const;
void set_argument_property(StringName name);
StringName get_argument_property() const;
StringName argument_property;
@ -47,21 +45,21 @@ public:
class MoveStateArgs : public StateArgs {
GDCLASS(MoveStateArgs, StateArgs);
static void _bind_methods();
virtual State construct(Planner *context) const override;
virtual State construct(Node *context) const override;
};
class AnimateStateArgs : public StateArgs {
GDCLASS(AnimateStateArgs, StateArgs);
static void _bind_methods();
public:
virtual State construct(Planner *context) const override;
virtual State construct(Node *context) const override;
};
class ActivateStateArgs : public StateArgs {
GDCLASS(ActivateStateArgs, StateArgs);
static void _bind_methods();
public:
virtual State construct(Planner *context) const override;
virtual State construct(Node *context) const override;
};
};

View file

@ -1,12 +1,8 @@
#include "tunnels_game_mode.hpp"
#include "character_actor.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
#include "utils/game_root.hpp"
#include "utils/godot_macros.h"
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/core/object.hpp>
#include <godot_cpp/variant/callable.hpp>
#include <godot_cpp/variant/callable_method_pointer.hpp>
#include <godot_cpp/variant/variant.hpp>
namespace godot {
void TunnelsGameMode::_bind_methods() {
@ -25,27 +21,4 @@ void TunnelsGameMode::on_player_spawned(Node *player) {
TunnelsPlayer *TunnelsGameMode::get_player_instance() const {
return this->player;
}
void TunnelsGameMode::register_player_character(CharacterActor *actor) {
if(!this->player_characters.has(actor)) {
this->player_characters.push_back(actor);
actor->connect("tree_exited", callable_mp(this, &TunnelsGameMode::on_character_destroyed).bind(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;
}
}

View file

@ -1,9 +1,9 @@
#ifndef TUNNELS_GAME_MODE_HPP
#define TUNNELS_GAME_MODE_HPP
#include "tunnels_game_state.hpp"
#include "tunnels_player.hpp"
#include "utils/game_mode.hpp"
#include <godot_cpp/templates/vector.hpp>
namespace godot {
class TunnelsGameMode : public GameMode {
@ -15,14 +15,8 @@ public:
void on_player_spawned(Node *player);
TunnelsPlayer *get_player_instance() const;
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};
Vector<CharacterActor*> player_characters{};
};
}

View file

@ -1,42 +1,34 @@
#include "tunnels_player.hpp"
#include "character_actor.hpp"
#include "character_data.hpp"
#include "goal_marker.hpp"
#include "godot_cpp/variant/callable_method_pointer.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
#include "planner.hpp"
#include "tunnels_game_mode.hpp"
#include "godot_cpp/variant/plane.hpp"
#include "godot_cpp/variant/projection.hpp"
#include "character_actor.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>
namespace godot {
void TunnelsPlayer::_bind_methods() {
#define CLASSNAME TunnelsPlayer
GDFUNCTION_ARGS(horizontal_move_input, "event", "value");
GDFUNCTION_ARGS(vertical_move_input, "event", "value");
GDFUNCTION_ARGS(fire_pressed, "event", "value");
GDPROPERTY_HINTED(camera_rotation_ramp, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
}
void TunnelsPlayer::_enter_tree() { GDGAMEONLY();
void TunnelsPlayer::_ready() { GDGAMEONLY();
this->camera = this->get_viewport()->get_camera_3d();
this->initialize_character();
this->camera_rotation_ramp->bake();
this->reticle = this->get_node<Node3D>("Reticle");
}
void TunnelsPlayer::_ready() {
this->camera = this->get_viewport()->get_camera_3d();
}
void TunnelsPlayer::_exit_tree() { GDGAMEONLY();
GameRoot::get_singleton()->remove_player(this->get_player_id());
}
@ -60,11 +52,10 @@ 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;
}
}
@ -82,16 +73,15 @@ void TunnelsPlayer::process_camera_rotation(double delta_time) {
Vector3 rotation = this->get_global_rotation();
// the influence of the mouse's y position on the rotation speed
float const y_multiplier = std::max(TunnelsPlayer::ROTATION_Y_MIN_INFLUENCE, this->mouse_location.y);
float const margin = TunnelsPlayer::ROTATION_MARGIN * (this->state == State::Tactics ? TunnelsPlayer::ROTATION_MARGIN_TACTICS_MUL : 1.f);
// rotate the camera when the mouse is close to the edge of the screen
if(this->mouse_location.x < margin) {
if(this->mouse_location.x < TunnelsPlayer::ROTATION_MARGIN) {
// normalized measurement of how far into the rotation margin the mouse is
float const normalized{1.f - (this->mouse_location.x / margin)};
float const normalized{1.f - (this->mouse_location.x / TunnelsPlayer::ROTATION_MARGIN)};
// rotate based on delta time and use a curve to make the rotation zone feel more natural
rotation.y += delta_time * double(TunnelsPlayer::ROTATION_SPEED * camera_rotation_ramp->sample(normalized) * y_multiplier);
}
if(this->mouse_location.x > 1.f - margin) {
float const normalized{((this->mouse_location.x - (1.f - margin)) / margin)};
if(this->mouse_location.x > 1.f - TunnelsPlayer::ROTATION_MARGIN) {
float const normalized{((this->mouse_location.x - (1.f - TunnelsPlayer::ROTATION_MARGIN)) / TunnelsPlayer::ROTATION_MARGIN)};
rotation.y -= delta_time * double(TunnelsPlayer::ROTATION_SPEED * camera_rotation_ramp->sample(normalized) * y_multiplier);
}
@ -105,21 +95,15 @@ void TunnelsPlayer::process_camera_rotation(double delta_time) {
}
void TunnelsPlayer::setup_player_input(PlayerInput *input) {
input->listen_to(PlayerInput::Listener("move_left", "move_right", callable_mp(this, &TunnelsPlayer::horizontal_move_input)));
input->listen_to(PlayerInput::Listener("move_forward", "move_backward", callable_mp(this, &TunnelsPlayer::vertical_move_input)));
input->listen_to(PlayerInput::Listener("fire", callable_mp(this, &TunnelsPlayer::fire_pressed)));
input->listen_to(PlayerInput::Listener("tactics_mode", callable_mp(this, &TunnelsPlayer::mode_switch_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"));
}
Node *TunnelsPlayer::to_node() {
return Object::cast_to<Node>(this);
}
void TunnelsPlayer::spawn_at_position(Transform3D const &at) {
this->character->set_global_transform(at);
this->set_global_basis(at.get_basis());
}
void TunnelsPlayer::horizontal_move_input(Ref<InputEvent> event, float value) {
this->move_input.x = value;
}
@ -128,70 +112,8 @@ 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() {
@ -201,7 +123,6 @@ void TunnelsPlayer::initialize_character() {
return;
if(player_scene->get_state()->get_node_type(0) != StringName("CharacterActor"))
return;
UtilityFunctions::print("initialize_character pos: ", this->get_global_position());
// instantiate and store the player character
this->character = Object::cast_to<CharacterActor>(player_scene->instantiate());
this->get_parent()->add_child(this->character);
@ -211,7 +132,6 @@ 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 {
@ -255,6 +175,4 @@ 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::ROTATION_MARGIN_TACTICS_MUL{0.6f};
float const TunnelsPlayer::TACTICS_MOVEMENT_SPEED{10.f};
}

View file

@ -1,7 +1,6 @@
#ifndef TUNNELS_PLAYER_HPP
#define TUNNELS_PLAYER_HPP
#include "godot_cpp/variant/transform3d.hpp"
#include "utils/player.hpp"
#include "utils/player_input.hpp"
#include <godot_cpp/classes/camera3d.hpp>
@ -23,7 +22,6 @@ class TunnelsPlayer : public Node3D, public IPlayer {
};
public:
virtual void _enter_tree() override;
virtual void _ready() override;
virtual void _exit_tree() override;
virtual void _process(double delta_time) override;
@ -33,14 +31,11 @@ public:
virtual void setup_player_input(PlayerInput *input) override;
virtual Node *to_node() override;
virtual void spawn_at_position(Transform3D const &at) override;
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;
@ -64,9 +59,7 @@ private:
static float const ROTATION_SPEED;
static float const ROTATION_Y_MIN_INFLUENCE;
static float const ROTATION_MARGIN_TACTICS_MUL;
static float const ROTATION_MARGIN;
static float const TACTICS_MOVEMENT_SPEED;
};
}

@ -1 +1 @@
Subproject commit d81ad91a885a74338c02edf1d52a2fa5aa8746b6
Subproject commit f0bddcf074f040e6a02f70a26c58507580669327