feat: implemented last bits required to create a 'follow player' goal
This commit is contained in:
parent
540d91ddcf
commit
ea730d61b4
|
@ -1,4 +1,4 @@
|
||||||
[gd_scene load_steps=12 format=3 uid="uid://dpda341t6ipiv"]
|
[gd_scene load_steps=9 format=3 uid="uid://dpda341t6ipiv"]
|
||||||
|
|
||||||
[sub_resource type="Curve" id="Curve_7rmf4"]
|
[sub_resource type="Curve" id="Curve_7rmf4"]
|
||||||
min_value = 0.2
|
min_value = 0.2
|
||||||
|
@ -18,38 +18,21 @@ albedo_color = Color(0.94902, 0.909804, 0, 1)
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_f5yvh"]
|
[sub_resource type="BoxMesh" id="BoxMesh_f5yvh"]
|
||||||
size = Vector3(0.125, 0.14, 0.94)
|
size = Vector3(0.125, 0.14, 0.94)
|
||||||
|
|
||||||
[sub_resource type="MoveStateArgs" id="MoveStateArgs_752r2"]
|
[sub_resource type="MoveStateArgs" id="MoveStateArgs_ibmkn"]
|
||||||
argument_property = &"target_position"
|
argument_property = &"player_character"
|
||||||
|
|
||||||
[sub_resource type="Action" id="Action_ksl64"]
|
[sub_resource type="Action" id="Action_gtisq"]
|
||||||
effects = {
|
effects = {
|
||||||
"prereq": true
|
"is_near_player": true
|
||||||
}
|
}
|
||||||
apply_state = SubResource("MoveStateArgs_752r2")
|
apply_state = SubResource("MoveStateArgs_ibmkn")
|
||||||
|
|
||||||
[sub_resource type="Action" id="Action_mdru6"]
|
|
||||||
effects = {
|
|
||||||
"not_goal": 1
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="AnimateStateArgs" id="AnimateStateArgs_tlart"]
|
|
||||||
argument_property = &"fire_weapon"
|
|
||||||
|
|
||||||
[sub_resource type="Action" id="Action_7v1i5"]
|
|
||||||
prerequisites = {
|
|
||||||
"prereq": true
|
|
||||||
}
|
|
||||||
effects = {
|
|
||||||
"goal": true
|
|
||||||
}
|
|
||||||
apply_state = SubResource("AnimateStateArgs_tlart")
|
|
||||||
|
|
||||||
[sub_resource type="Goal" id="Goal_sqtwb"]
|
[sub_resource type="Goal" id="Goal_sqtwb"]
|
||||||
goal_state = {
|
goal_state = {
|
||||||
"target_dead": true
|
"is_near_player": true
|
||||||
}
|
}
|
||||||
prerequisites = {
|
prerequisites = {
|
||||||
"has_target": true
|
"is_near_player": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="PlayerCharacter" type="CharacterActor"]
|
[node name="PlayerCharacter" type="CharacterActor"]
|
||||||
|
@ -83,5 +66,5 @@ surface_material_override/0 = SubResource("StandardMaterial3D_scmx3")
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.53551, 0.931313, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.53551, 0.931313, 0)
|
||||||
|
|
||||||
[node name="Planner" type="Planner" parent="."]
|
[node name="Planner" type="Planner" parent="."]
|
||||||
actions = [SubResource("Action_ksl64"), SubResource("Action_mdru6"), SubResource("Action_7v1i5")]
|
actions = [SubResource("Action_gtisq")]
|
||||||
goals = [SubResource("Goal_sqtwb")]
|
goals = [SubResource("Goal_sqtwb")]
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
#include "character_actor.hpp"
|
#include "character_actor.hpp"
|
||||||
|
#include "planner.hpp"
|
||||||
#include "projectile_pool.hpp"
|
#include "projectile_pool.hpp"
|
||||||
|
#include "state.hpp"
|
||||||
|
#include "tunnels_game_mode.hpp"
|
||||||
|
#include "utils/game_root.hpp"
|
||||||
#include "utils/godot_macros.h"
|
#include "utils/godot_macros.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
||||||
|
@ -12,6 +16,8 @@ void CharacterActor::_bind_methods() {
|
||||||
#define CLASSNAME CharacterActor
|
#define CLASSNAME CharacterActor
|
||||||
GDPROPERTY_HINTED(rotation_speed_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
GDPROPERTY_HINTED(rotation_speed_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||||
GDFUNCTION_ARGS(set_velocity_target, "value");
|
GDFUNCTION_ARGS(set_velocity_target, "value");
|
||||||
|
GDFUNCTION(get_is_near_player);
|
||||||
|
GDFUNCTION(get_player_character);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterActor::_enter_tree() { GDGAMEONLY();
|
void CharacterActor::_enter_tree() { GDGAMEONLY();
|
||||||
|
@ -20,12 +26,14 @@ void CharacterActor::_enter_tree() { GDGAMEONLY();
|
||||||
this->target_rotation = this->get_global_transform().get_basis().get_quaternion();
|
this->target_rotation = this->get_global_transform().get_basis().get_quaternion();
|
||||||
this->health = this->get_node<Health>("Health");
|
this->health = this->get_node<Health>("Health");
|
||||||
this->primary_weapon_pool = this->get_node<ProjectilePool>("ProjectilePool");
|
this->primary_weapon_pool = this->get_node<ProjectilePool>("ProjectilePool");
|
||||||
|
this->planner = this->get_node<goap::Planner>("Planner");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterActor::_process(double delta_time) { GDGAMEONLY();
|
void CharacterActor::_process(double delta_time) { GDGAMEONLY();
|
||||||
this->process_rotation(delta_time);
|
this->process_rotation(delta_time);
|
||||||
if(!this->mode_manual) {
|
if(!this->mode_manual) {
|
||||||
this->process_ai(delta_time);
|
this->process_behaviour(delta_time);
|
||||||
|
this->process_navigation(delta_time);
|
||||||
}
|
}
|
||||||
if(this->firing) {
|
if(this->firing) {
|
||||||
this->try_fire_weapon();
|
this->try_fire_weapon();
|
||||||
|
@ -83,6 +91,7 @@ void CharacterActor::set_manual_mode(bool value) {
|
||||||
ProcessMode const mode = value ? ProcessMode::PROCESS_MODE_DISABLED : ProcessMode::PROCESS_MODE_PAUSABLE;
|
ProcessMode const mode = value ? ProcessMode::PROCESS_MODE_DISABLED : ProcessMode::PROCESS_MODE_PAUSABLE;
|
||||||
//this->nav_agent->set_process_mode(mode);
|
//this->nav_agent->set_process_mode(mode);
|
||||||
this->nav_agent->set_avoidance_priority(value ? 1.f : 0.9f);
|
this->nav_agent->set_avoidance_priority(value ? 1.f : 0.9f);
|
||||||
|
this->set_state(goap::State::new_invalid());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterActor::set_rotation_speed_curve(Ref<Curve> curve) {
|
void CharacterActor::set_rotation_speed_curve(Ref<Curve> curve) {
|
||||||
|
@ -119,10 +128,54 @@ Vector3 CharacterActor::get_velocity_target() const {
|
||||||
return this->velocity_target;
|
return this->velocity_target;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterActor::process_ai(double delta_time) {
|
bool CharacterActor::get_is_near_player() const {
|
||||||
float const distance = this->nav_agent->get_target_position().distance_squared_to(this->get_global_position());
|
return this->get_player_character()->get_global_position().distance_to(this->get_global_position()) < 5.f;
|
||||||
float const target_distance_sqr = std::pow(this->nav_agent->get_target_desired_distance(), 2.f);
|
}
|
||||||
if(!this->nav_agent->is_navigation_finished() && distance >= target_distance_sqr) {
|
|
||||||
|
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) {
|
||||||
|
switch(this->current_state.type) {
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case goap::State::STATE_MOVE_TO:
|
||||||
|
this->nav_agent->set_target_position(this->get_global_position());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this->current_state = state;
|
||||||
|
switch(state.type) {
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case goap::State::STATE_MOVE_TO:
|
||||||
|
this->move_to(state.move_to->get_global_position());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterActor::process_behaviour(double delta_time) {
|
||||||
|
if(this->current_state.is_complete(this) || this->planner->is_action_complete())
|
||||||
|
this->set_state(this->planner->get_next_state());
|
||||||
|
switch(this->current_state.type) {
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case goap::State::STATE_MOVE_TO:
|
||||||
|
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;
|
||||||
|
case goap::State::STATE_ANIMATE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterActor::process_navigation(double delta_time) {
|
||||||
|
float const distance_sqr = this->nav_agent->get_target_position().distance_squared_to(this->get_global_position());
|
||||||
|
float const distance_target_sqr = std::pow(this->nav_agent->get_target_desired_distance(), 2.f);
|
||||||
|
if(!this->nav_agent->is_navigation_finished() && distance_sqr >= distance_target_sqr) {
|
||||||
Vector3 const target_position = this->nav_agent->get_next_path_position();
|
Vector3 const target_position = this->nav_agent->get_next_path_position();
|
||||||
Vector3 const direction = (target_position - this->get_global_position()).normalized();
|
Vector3 const direction = (target_position - this->get_global_position()).normalized();
|
||||||
if(this->nav_agent->get_avoidance_enabled())
|
if(this->nav_agent->get_avoidance_enabled())
|
||||||
|
|
|
@ -3,14 +3,18 @@
|
||||||
|
|
||||||
#include "character_data.hpp"
|
#include "character_data.hpp"
|
||||||
#include "health.hpp"
|
#include "health.hpp"
|
||||||
#include "state.hpp"
|
|
||||||
#include "projectile_pool.hpp"
|
#include "projectile_pool.hpp"
|
||||||
|
#include "state.hpp"
|
||||||
#include <godot_cpp/classes/character_body3d.hpp>
|
#include <godot_cpp/classes/character_body3d.hpp>
|
||||||
#include <godot_cpp/classes/curve.hpp>
|
#include <godot_cpp/classes/curve.hpp>
|
||||||
|
|
||||||
namespace godot {
|
namespace godot {
|
||||||
class NavigationAgent3D;
|
class NavigationAgent3D;
|
||||||
class TunnelsPlayer;
|
class TunnelsPlayer;
|
||||||
|
class AnimationPlayer;
|
||||||
|
namespace goap {
|
||||||
|
class Planner;
|
||||||
|
};
|
||||||
|
|
||||||
class CharacterActor : public CharacterBody3D,
|
class CharacterActor : public CharacterBody3D,
|
||||||
public IHealthEntity {
|
public IHealthEntity {
|
||||||
|
@ -38,18 +42,18 @@ public:
|
||||||
|
|
||||||
void set_rotation_speed_curve(Ref<Curve> curve);
|
void set_rotation_speed_curve(Ref<Curve> curve);
|
||||||
Ref<Curve> get_rotation_speed_curve() const;
|
Ref<Curve> get_rotation_speed_curve() const;
|
||||||
|
|
||||||
virtual Health *get_health() override;
|
virtual Health *get_health() override;
|
||||||
virtual Health const *get_health() const override;
|
virtual Health const *get_health() const override;
|
||||||
|
|
||||||
void set_character_data(Ref<CharacterData> data);
|
void set_character_data(Ref<CharacterData> data);
|
||||||
|
|
||||||
void set_weapon_muzzle(Node3D *node);
|
void set_weapon_muzzle(Node3D *node);
|
||||||
|
|
||||||
void set_velocity_target(Vector3 value);
|
void set_velocity_target(Vector3 value);
|
||||||
Vector3 get_velocity_target() const;
|
Vector3 get_velocity_target() const;
|
||||||
|
bool get_is_near_player() const;
|
||||||
|
CharacterActor *get_player_character() const;
|
||||||
|
void set_state(goap::State state);
|
||||||
protected:
|
protected:
|
||||||
void process_ai(double delta_time);
|
void process_behaviour(double delta_time);
|
||||||
|
void process_navigation(double delta_time);
|
||||||
void process_rotation(double delta_time);
|
void process_rotation(double delta_time);
|
||||||
void try_fire_weapon();
|
void try_fire_weapon();
|
||||||
private:
|
private:
|
||||||
|
@ -65,19 +69,21 @@ private:
|
||||||
float fire_timer{0.f};
|
float fire_timer{0.f};
|
||||||
// the origin point for projectiles
|
// the origin point for projectiles
|
||||||
Node3D *weapon_muzzle{nullptr};
|
Node3D *weapon_muzzle{nullptr};
|
||||||
// whatever the AI is currently targetting
|
// something that the AI wants to target
|
||||||
Node *target{nullptr};
|
Node *target{nullptr};
|
||||||
// the current state of the actor
|
// the current state of the actor
|
||||||
goap::State current_state{}; // the current state
|
goap::State current_state{goap::State::new_invalid()};
|
||||||
|
AnimationPlayer *anim_player{nullptr};
|
||||||
|
|
||||||
Health *health{nullptr};
|
Health *health{nullptr};
|
||||||
ProjectilePool *primary_weapon_pool{nullptr};
|
ProjectilePool *primary_weapon_pool{nullptr};
|
||||||
NavigationAgent3D *nav_agent{nullptr};
|
NavigationAgent3D *nav_agent{nullptr};
|
||||||
|
goap::Planner *planner{nullptr};
|
||||||
|
|
||||||
Ref<Curve> rotation_speed_curve{};
|
Ref<Curve> rotation_speed_curve{};
|
||||||
// character data assigned when spawned
|
// character data assigned when spawned
|
||||||
Ref<CharacterData> data;
|
Ref<CharacterData> data;
|
||||||
float fire_interval{0.f}; // derived from the current weapon's rpm
|
float fire_interval{0.f}; // derived from 1 / the current weapon's rps
|
||||||
|
|
||||||
static float const ACCELERATION;
|
static float const ACCELERATION;
|
||||||
static float const WALK_SPEED;
|
static float const WALK_SPEED;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "action.hpp"
|
#include "action.hpp"
|
||||||
#include "character_actor.hpp"
|
#include "character_actor.hpp"
|
||||||
#include "global_world_state.hpp"
|
#include "global_world_state.hpp"
|
||||||
|
#include "state.hpp"
|
||||||
#include "utils/godot_macros.h"
|
#include "utils/godot_macros.h"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <godot_cpp/templates/pair.hpp>
|
#include <godot_cpp/templates/pair.hpp>
|
||||||
|
@ -61,7 +62,6 @@ void Planner::_bind_methods() {
|
||||||
#define CLASSNAME Planner
|
#define CLASSNAME Planner
|
||||||
GDPROPERTY_HINTED(actions, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Action));
|
GDPROPERTY_HINTED(actions, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Action));
|
||||||
GDPROPERTY_HINTED(goals, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Goal));
|
GDPROPERTY_HINTED(goals, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Goal));
|
||||||
GDFUNCTION_ARGS(gdscript_make_plan, "goal");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Planner::_ready() {
|
void Planner::_ready() {
|
||||||
|
@ -79,21 +79,12 @@ static Vector<Ref<Action>> trace_path(FromMap &map, PlannerNode &end) {
|
||||||
return edges;
|
return edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
Array Planner::gdscript_make_plan(Ref<Goal> goal) {
|
Vector<Ref<Action>> Planner::make_plan() {
|
||||||
Vector<Ref<Action>> plan = this->make_plan(goal);
|
|
||||||
Array out{};
|
|
||||||
int i{0};
|
|
||||||
UtilityFunctions::print("plan len: ", plan.size());
|
|
||||||
for(Ref<Action> const &action : plan) {
|
|
||||||
out.push_back(action);
|
|
||||||
UtilityFunctions::print("plan[", i++, "]: ", this->actions.find(action));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector<Ref<Action>> Planner::make_plan(Ref<Goal> goal) {
|
|
||||||
// clear cache every planning phase
|
// clear cache every planning phase
|
||||||
this->cached_world_state.clear();
|
this->cached_world_state.clear();
|
||||||
|
Ref<Goal> goal = this->select_goal();
|
||||||
|
if(!goal.is_valid())
|
||||||
|
return {};
|
||||||
// ordered list of all nodes still being considered
|
// ordered list of all nodes still being considered
|
||||||
Vector<PlannerNode> open{PlannerNode::goal_node(goal->goal_state)};
|
Vector<PlannerNode> open{PlannerNode::goal_node(goal->goal_state)};
|
||||||
PlannerNode first = open.get(0);
|
PlannerNode first = open.get(0);
|
||||||
|
@ -130,6 +121,20 @@ Vector<Ref<Action>> Planner::make_plan(Ref<Goal> goal) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
Variant Planner::get_world_property(StringName prop_key) {
|
Variant Planner::get_world_property(StringName prop_key) {
|
||||||
if(prop_key.begins_with("g_")) {
|
if(prop_key.begins_with("g_")) {
|
||||||
return this->global_world_state->get_world_property(prop_key);
|
return this->global_world_state->get_world_property(prop_key);
|
||||||
|
@ -172,13 +177,28 @@ Vector<Ref<Action>> Planner::find_actions_satisfying(WorldState requirements) {
|
||||||
for(WorldProperty &prop : requirements) {
|
for(WorldProperty &prop : requirements) {
|
||||||
if(act->effects.has(prop.key)
|
if(act->effects.has(prop.key)
|
||||||
&& act->effects.get(prop.key) == prop.value
|
&& act->effects.get(prop.key) == prop.value
|
||||||
&& this->can_do(act))
|
&& this->can_do(act)) {
|
||||||
found_actions.push_back(act);
|
found_actions.push_back(act);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return found_actions;
|
return found_actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Planner::is_action_complete() {
|
||||||
|
return this->plan.get(0)->get_is_completed(this->actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
State Planner::get_next_state() {
|
||||||
|
if(!this->plan.is_empty())
|
||||||
|
this->plan.remove_at(0);
|
||||||
|
if(this->plan.is_empty())
|
||||||
|
this->plan = this->make_plan();
|
||||||
|
if(this->plan.is_empty())
|
||||||
|
return State::new_invalid();
|
||||||
|
return this->plan.get(0)->apply_state->construct(this->actor);
|
||||||
|
}
|
||||||
|
|
||||||
void Planner::set_actions(Array value) {
|
void Planner::set_actions(Array value) {
|
||||||
this->actions.clear();
|
this->actions.clear();
|
||||||
this->actions.resize(value.size());
|
this->actions.resize(value.size());
|
||||||
|
|
|
@ -46,8 +46,8 @@ class Planner : public Node {
|
||||||
public:
|
public:
|
||||||
virtual void _ready() override;
|
virtual void _ready() override;
|
||||||
|
|
||||||
Array gdscript_make_plan(Ref<Goal> goal);
|
Vector<Ref<Action>> make_plan();
|
||||||
Vector<Ref<Action>> make_plan(Ref<Goal> goal);
|
Ref<Goal> select_goal();
|
||||||
|
|
||||||
Variant get_world_property(StringName prop_key);
|
Variant get_world_property(StringName prop_key);
|
||||||
|
|
||||||
|
@ -55,9 +55,11 @@ public:
|
||||||
Vector<PlannerNode> find_neighbours_of(PlannerNode &node);
|
Vector<PlannerNode> find_neighbours_of(PlannerNode &node);
|
||||||
Vector<Ref<Action>> find_actions_satisfying(WorldState requirements);
|
Vector<Ref<Action>> find_actions_satisfying(WorldState requirements);
|
||||||
|
|
||||||
|
bool is_action_complete();
|
||||||
|
State get_next_state();
|
||||||
|
|
||||||
void set_actions(Array actions);
|
void set_actions(Array actions);
|
||||||
Array get_actions() const;
|
Array get_actions() const;
|
||||||
|
|
||||||
void set_goals(Array goals);
|
void set_goals(Array goals);
|
||||||
Array get_goals() const;
|
Array get_goals() const;
|
||||||
private:
|
private:
|
||||||
|
@ -67,6 +69,7 @@ private:
|
||||||
// configured settings
|
// configured settings
|
||||||
Vector<Ref<Action>> actions{}; // available actions
|
Vector<Ref<Action>> actions{}; // available actions
|
||||||
Vector<Ref<Goal>> goals{}; // available goals
|
Vector<Ref<Goal>> goals{}; // available goals
|
||||||
|
Vector<Ref<Action>> plan{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PlannerNodeHasher {
|
struct PlannerNodeHasher {
|
||||||
|
|
|
@ -29,6 +29,10 @@ State State::new_activate(Node *node) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
State State::new_invalid() {
|
||||||
|
return { .type = State::Type::STATE_TYPE_MAX };
|
||||||
|
}
|
||||||
|
|
||||||
bool State::is_complete(CharacterActor *context) const {
|
bool State::is_complete(CharacterActor *context) const {
|
||||||
switch(this->type) {
|
switch(this->type) {
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -14,6 +14,7 @@ struct State {
|
||||||
static State new_move_to(Node3D *location);
|
static State new_move_to(Node3D *location);
|
||||||
static State new_animate(StringName animation);
|
static State new_animate(StringName animation);
|
||||||
static State new_activate(Node *node);
|
static State new_activate(Node *node);
|
||||||
|
static State new_invalid();
|
||||||
|
|
||||||
bool is_complete(CharacterActor *context) const;
|
bool is_complete(CharacterActor *context) const;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue