fix: plan being considered empty if the last state is still active

This commit is contained in:
Sara 2025-03-10 23:34:02 +01:00
parent d7dadac070
commit 9b27fbaabf
37 changed files with 354 additions and 220 deletions

View file

@ -18,4 +18,3 @@ void CharacterUnit::use_weapon() {
this->inventory->get_weapon()->try_use_on(this, target);
}
}

View file

@ -1,15 +1,17 @@
#ifndef CHARACTER_UNIT_HPP
#define CHARACTER_UNIT_HPP
#include "unit.hpp"
#include "inventory.hpp"
#include "unit.hpp"
class CharacterUnit : public Unit {
GDCLASS(CharacterUnit, Unit);
static void _bind_methods();
public:
virtual void _ready() override;
virtual void use_weapon() override;
private:
Inventory *inventory{nullptr};
};

View file

@ -7,10 +7,12 @@
class CharacterWorldState : public UnitWorldState {
GDCLASS(CharacterWorldState, UnitWorldState);
static void _bind_methods();
public:
virtual void _enter_tree() override;
virtual gd::String get_weapon_animation() const override;
virtual bool get_is_in_range() const override;
private:
Inventory *inventory{nullptr};
};

View file

@ -1,10 +1,10 @@
#include "enemy_world_state.hpp"
#include "entity_health.hpp"
#include "unit.hpp"
#include "goap/goal.hpp"
#include "unit.hpp"
#include "utils/godot_macros.hpp"
#include <godot_cpp/variant/utility_functions.hpp>
#include <cmath>
#include <godot_cpp/variant/utility_functions.hpp>
void EnemyWorldState::_bind_methods() {
#define CLASSNAME EnemyWorldState
@ -20,33 +20,45 @@ void EnemyWorldState::_ready() {
}
void EnemyWorldState::_process(double) {
if(!this->health->is_conscious())
return;
if(this->select_and_set_target_on_process) {
gd::UtilityFunctions::print("!!! ", this->get_path(), " requested, select_and_set_target");
this->select_and_set_target_on_process = false;
this->select_and_set_target();
} else if(!this->parent_unit->has_plan()) {
gd::UtilityFunctions::print("!!! ", this->get_path(), " no plan, select_and_set_target");
this->select_and_set_target();
}
}
void EnemyWorldState::on_awareness_entered(gd::Node3D *node) {
if(Unit *unit{gd::Object::cast_to<Unit>(node)}) {
if(Unit * unit{gd::Object::cast_to<Unit>(node)}) {
this->add_aware_unit(unit);
}
}
void EnemyWorldState::on_awareness_exited(gd::Node3D *node) {
if(Unit *unit{gd::Object::cast_to<Unit>(node)})
if(Unit * unit{gd::Object::cast_to<Unit>(node)})
this->remove_aware_unit(unit);
}
void EnemyWorldState::on_damaged(EntityHealth *, int, Unit *source) {
if(source != nullptr && !this->known_enemies.has(source))
this->add_aware_unit(source);
else if(!this->parent_unit->has_plan())
if(!this->parent_unit->has_plan())
this->select_and_set_target();
}
Unit *EnemyWorldState::select_target_from_known_with_priority(float *out_priority) {
Unit *out{nullptr};
*out_priority = -INFINITY;
Unit *out{gd::Object::cast_to<Unit>(target_node)};
// prioritze current target if it is still valid
if(out != nullptr && this->known_enemies.has(out)) {
*out_priority = INFINITY;
return out;
} else {
*out_priority = -INFINITY;
}
for(Unit *unit : this->known_enemies) {
if(!this->get_can_see_node(unit))
continue;
@ -101,6 +113,7 @@ void EnemyWorldState::on_aware_unit_death(Unit *, Unit *dead_entity) {
}
void EnemyWorldState::select_and_set_target() {
gd::UtilityFunctions::print("!!! ", this->get_path(), " select_and_set_target");
this->try_set_target(this->select_target_from_known());
}
@ -130,14 +143,11 @@ gd::Ref<goap::Goal> EnemyWorldState::get_goal_for_target(Unit *unit) {
}
void EnemyWorldState::try_set_target(Unit *unit) {
if(unit == nullptr) {
this->target_node = nullptr;
this->parent_unit->stop_plan();
if(unit == nullptr)
return;
}
gd::Ref<goap::Goal> goal{this->get_goal_for_target(unit)};
this->last_goal = goal;
if(goal.is_valid())
if(goal.is_valid() && goal != this->parent_unit->get_current_goal())
this->parent_unit->set_target_goal(unit, goal);
}

View file

@ -1,8 +1,8 @@
#ifndef ENEMY_WORLD_STATE_HPP
#define ENEMY_WORLD_STATE_HPP
#include "unit_world_state.hpp"
#include "goap/goal.hpp"
#include "unit_world_state.hpp"
#include <godot_cpp/classes/area3d.hpp>
#include <godot_cpp/templates/vector.hpp>
#include <godot_cpp/variant/array.hpp>
@ -14,6 +14,7 @@ class EntityHealth;
class EnemyWorldState : public UnitWorldState {
GDCLASS(EnemyWorldState, UnitWorldState);
static void _bind_methods();
public:
virtual void _ready() override;
virtual void _process(double) override;
@ -23,10 +24,11 @@ public:
/*! Select the target with the highest priority and return it.
* Assigns the priority of found target to out_priority.
* \param out_priority Assigned to highest found priority, cannot be nullptr.
*/
*/
Unit *select_target_from_known_with_priority(float *out_priority);
//! Shorthand for select_target_from_known_with_priority(nullptr)
Unit *select_target_from_known();
private:
void queue_select_and_set_target();
void add_aware_unit(Unit *unit);
@ -38,6 +40,7 @@ private:
void try_set_target(Unit *target);
void set_available_goals_inspector(gd::Array array);
gd::Array get_available_goals_inspector() const;
private:
gd::Callable aware_unit_death{callable_mp(this, &EnemyWorldState::on_aware_unit_death)};
gd::Vector<gd::Ref<goap::Goal>> available_goals{};

View file

@ -10,22 +10,22 @@ void EntityHealth::_bind_methods() {
GDPROPERTY(wounds_max, gd::Variant::INT);
GDSIGNAL("damage",
gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
gd::PropertyInfo(gd::Variant::INT, "change"),
gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
gd::PropertyInfo(gd::Variant::INT, "change"),
gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
GDSIGNAL("heal",
gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
gd::PropertyInfo(gd::Variant::INT, "change"),
gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
gd::PropertyInfo(gd::Variant::INT, "change"),
gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
GDSIGNAL("health_changed",
gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
gd::PropertyInfo(gd::Variant::INT, "change"));
gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
gd::PropertyInfo(gd::Variant::INT, "change"));
GDSIGNAL("death", gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
GDSIGNAL("unconscious", gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
GDSIGNAL("revived",
gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "Unit"),
gd::PropertyInfo(gd::Variant::INT, "healed"),
gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "Unit"),
gd::PropertyInfo(gd::Variant::INT, "healed"),
gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
}
void EntityHealth::_enter_tree() {
@ -71,24 +71,20 @@ void EntityHealth::receive_injury(int incoming_amount, Unit *source) {
float EntityHealth::get_wounds_damage_factor() const {
return this->injury_max == 0
? 1.f
: float(this->injury_current) / float(this->injury_max);
? 1.f
: float(this->injury_current) / float(this->injury_max);
}
bool EntityHealth::is_conscious() const {
return !this->is_dead()
&& (this->wounds_current > 0 || this->wounds_max <= 0);
return !this->is_dead() && (this->wounds_current > 0 || this->wounds_max <= 0);
}
bool EntityHealth::can_be_revived() const {
return this->wounds_current <= 0
&& this->injury_current > 0;
return this->wounds_current <= 0 && this->injury_current > 0;
}
bool EntityHealth::can_be_healed() const {
return this->injury_current > 0
&& this->wounds_current > 0
&& this->wounds_current < this->wounds_max;
return this->injury_current > 0 && this->wounds_current > 0 && this->wounds_current < this->wounds_max;
}
bool EntityHealth::is_dead() const {

View file

@ -11,15 +11,18 @@ class Unit;
class EntityHealth : public gd::Node {
GDCLASS(EntityHealth, gd::Node);
static void _bind_methods();
public:
virtual void _enter_tree() override;
void damaged_by(int amount, Unit *source);
void healed_by(int amount, Unit *source);
void heal_and_revive(int heal_amount, Unit *source);
private:
void receive_wounds(int incoming_amount, Unit *source);
void receive_injury(int incoming_amount, Unit *source);
float get_wounds_damage_factor() const;
public:
bool is_conscious() const;
bool can_be_revived() const;
@ -34,6 +37,7 @@ public:
void set_wounds_max(int max_health);
int get_wounds_max() const;
int get_wounds_current() const;
private:
Stats const *stats;
// long term health

View file

@ -10,10 +10,12 @@ namespace gd = godot;
class GoalMarker : public gd::StaticBody3D {
GDCLASS(GoalMarker, gd::StaticBody3D);
static void _bind_methods();
public:
void destroy_on_forgotten();
void set_goal(gd::Ref<goap::Goal> goal);
gd::Ref<goap::Goal> get_goal() const;
private:
gd::Ref<goap::Goal> goal{};
};

View file

@ -2,6 +2,7 @@
#include "../goap/action.hpp"
#include "../goap/actor_world_state.hpp"
#include "../utils/godot_macros.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
namespace goap {
void State::_bind_methods() {
@ -21,11 +22,13 @@ void State::_process(double) {
}
void State::interrupt_state() {
gd::UtilityFunctions::print("!!! ", this->get_path(), " interrupt_state");
this->_end_state();
this->queue_free();
}
void State::end_state() {
gd::UtilityFunctions::print("!!! ", this->get_path(), " end_state");
this->interrupt_state();
this->emit_signal(this->is_action_done() ? "state_finished" : "state_failed");
this->emit_signal("end_state");

View file

@ -19,10 +19,14 @@ void Inventory::_ready() {
Stats Inventory::get_stats() const {
Stats stats{};
if(this->weapon != nullptr) this->weapon->apply_stats(stats);
if(this->utility != nullptr) this->utility->apply_stats(stats);
if(this->armour != nullptr) this->armour->apply_stats(stats);
if(this->helmet != nullptr) this->helmet->apply_stats(stats);
if(this->weapon != nullptr)
this->weapon->apply_stats(stats);
if(this->utility != nullptr)
this->utility->apply_stats(stats);
if(this->armour != nullptr)
this->armour->apply_stats(stats);
if(this->helmet != nullptr)
this->helmet->apply_stats(stats);
return stats;
}

View file

@ -6,8 +6,8 @@
#include "utils/godot_macros.hpp"
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/templates/pair.hpp>
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/templates/pair.hpp>
namespace gd = godot;
class Unit;
@ -15,6 +15,7 @@ class Unit;
class Inventory : public gd::Node {
GDCLASS(Inventory, gd::Node);
static void _bind_methods();
public:
virtual void _ready() override;
Stats get_stats() const;
@ -38,6 +39,7 @@ public:
int get_utility_id() const;
void set_inventory_inspector(gd::Dictionary dict);
gd::Dictionary get_inventory_inspector() const;
private:
Item const *weapon{nullptr};
Item const *utility{nullptr};

View file

@ -1,8 +1,8 @@
#ifndef ITEM_HPP
#define ITEM_HPP
#include "stats.hpp"
#include "goap/action.hpp"
#include "stats.hpp"
#include "utils/godot_macros.hpp"
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
@ -10,22 +10,22 @@
namespace gd = godot;
GDENUM(ItemCapability,
Heal,
Revive,
WeaponEquip,
WeaponRanged,
WeaponMelee,
ArmourEquip,
HeadEquip,
UtilityEquip,
Consume
);
Heal,
Revive,
WeaponEquip,
WeaponRanged,
WeaponMelee,
ArmourEquip,
HeadEquip,
UtilityEquip,
Consume);
typedef int ItemID;
class Unit;
class Item {
friend class ItemDB;
friend class ItemDB;
public:
static gd::StringName get_static_class() {
gd::UtilityFunctions::push_error("Calling Item::get_static_class() on an Item class that does not implement it. Use ITEM_CLASS(*) macro.");
@ -45,6 +45,7 @@ public:
}
Item() = default;
virtual ~Item();
public:
virtual bool can_use_on(Unit *used_by, gd::Object *used_on) const;
bool try_use_on(Unit *used_by, gd::Object *used_on) const;
@ -52,22 +53,27 @@ public:
bool has_capability(ItemCapability const &capability) const;
gd::StringName const &get_animation() const;
ItemID get_id() const;
protected:
virtual void use_on(Unit *used_by, gd::Object *used_on) const;
protected:
gd::StringName animation{"RESET"};
gd::HashSet<uint32_t> capabilities{};
private:
ItemID id{-1};
};
#define ITEM_CLASS(Class_, DisplayName_, Description_)\
friend class ItemDB;\
public: \
_FORCE_INLINE_ static godot::StringName get_static_class() { return #Class_; }\
_FORCE_INLINE_ virtual godot::StringName get_class() const override { return #Class_; }\
_FORCE_INLINE_ virtual godot::String get_display_name() const override { return DisplayName_; }\
_FORCE_INLINE_ virtual godot::String get_description() const override { return Description_; }\
#define ITEM_CLASS(Class_, DisplayName_, Description_) \
friend class ItemDB; \
\
public: \
_FORCE_INLINE_ static godot::StringName get_static_class() { return #Class_; } \
_FORCE_INLINE_ virtual godot::StringName get_class() const override { return #Class_; } \
_FORCE_INLINE_ virtual godot::String get_display_name() const override { return DisplayName_; } \
_FORCE_INLINE_ virtual godot::String get_description() const override { return Description_; } \
\
private:
#endif // !ITEM_HPP

View file

@ -22,7 +22,7 @@ gd::String &ItemDB::StaticData::get_array_hint() {
}
ItemID ItemDB::register_item(Item *item, gd::String item_name) {
item->id = ItemDB::data.items.size()+1;
item->id = ItemDB::data.items.size() + 1;
ItemDB::data.get_hint() += ",";
ItemDB::data.get_hint() += item_name;
ItemDB::data.get_array_hint() = gd::vformat("%s/%s:%s", gd::Variant::INT, gd::PROPERTY_HINT_ENUM, ItemDB::data.get_hint());

View file

@ -14,9 +14,10 @@ class ItemDB {
gd::String &get_array_hint();
gd::String *array_hint{};
gd::String *hint{};
gd::Vector<Item*> items{};
gd::Vector<Item *> items{};
};
static ItemID register_item(Item *item, gd::String item_name);
public:
static Item const *get_item(ItemID id);
static gd::String const &get_enum_hint();
@ -27,6 +28,7 @@ public:
_FORCE_INLINE_ static ItemID register_item() {
return ItemDB::register_item(new TItem(), TItem::get_static_class());
}
private:
static StaticData data;
};

View file

@ -7,19 +7,20 @@
namespace gd = godot;
GDENUM(MarkerType,
Generic,
Cover,
HalfCover
);
Generic,
Cover,
HalfCover);
class NavMarker : public gd::Marker3D {
GDCLASS(NavMarker, gd::Marker3D);
static void _bind_methods();
public:
virtual void _process(double) override;
void set_marker_type(int type);
int get_marker_type() const;
private:
MarkerType type{MarkerType::Generic};
};

View file

@ -1,8 +1,8 @@
#include "nav_room.hpp"
#include "nav_marker.hpp"
#include "utils/godot_macros.hpp"
#include <godot_cpp/classes/global_constants.hpp>
#include <cmath>
#include <godot_cpp/classes/global_constants.hpp>
void NavRoom::_bind_methods() {
#define CLASSNAME NavRoom
@ -32,15 +32,15 @@ void NavRoom::_exit_tree() {
}
void NavRoom::on_child_entered(gd::Node *child) {
if(NavMarker *marker{gd::Object::cast_to<NavMarker>(child)})
if(NavMarker * marker{gd::Object::cast_to<NavMarker>(child)})
this->markers.push_back(marker);
}
gd::Vector<NavRoom*> const &NavRoom::get_neighbours() const {
gd::Vector<NavRoom *> const &NavRoom::get_neighbours() const {
return this->neighbours;
}
gd::Vector<NavMarker*> const &NavRoom::get_markers() const {
gd::Vector<NavMarker *> const &NavRoom::get_markers() const {
return this->markers;
}
@ -53,8 +53,9 @@ gd::Array NavRoom::get_neighbours_inspector() const {
void NavRoom::set_neighbours_inspector(gd::Array array) {
this->neighbours.clear();
while(!array.is_empty()) if(NavRoom *room{gd::Object::cast_to<NavRoom>(array.pop_front())})
this->neighbours.push_back(room);
while(!array.is_empty())
if(NavRoom * room{gd::Object::cast_to<NavRoom>(array.pop_front())})
this->neighbours.push_back(room);
}
gd::Vector<NavRoom*> NavRoom::rooms{};
gd::Vector<NavRoom *> NavRoom::rooms{};

View file

@ -11,23 +11,26 @@ class NavMarker;
class NavRoom : public gd::Node3D {
GDCLASS(NavRoom, gd::Node3D);
static void _bind_methods();
public:
static NavRoom *get_closest_room(gd::Vector3 const &closest_to);
virtual void _enter_tree() override;
virtual void _exit_tree() override;
virtual void on_child_entered(gd::Node *child);
gd::Vector<NavRoom*> const &get_neighbours() const;
gd::Vector<NavMarker*> const &get_markers() const;
gd::Vector<NavRoom *> const &get_neighbours() const;
gd::Vector<NavMarker *> const &get_markers() const;
private:
void set_neighbours_inspector(gd::Array array);
gd::Array get_neighbours_inspector() const;
private:
float radius{1.f};
gd::Vector3 centre{0.f, 0.f, 0.f};
gd::Vector<NavMarker*> markers{};
gd::Vector<NavRoom*> neighbours{};
gd::Vector<NavMarker *> markers{};
gd::Vector<NavRoom *> neighbours{};
static gd::Vector<NavRoom*> rooms;
static gd::Vector<NavRoom *> rooms;
};
#endif // !NAV_ROOM_HPP

View file

@ -3,6 +3,12 @@
#include "character_world_state.hpp"
#include "enemy_world_state.hpp"
#include "entity_health.hpp"
#include "goap/action.hpp"
#include "goap/action_db.hpp"
#include "goap/actor_world_state.hpp"
#include "goap/goal.hpp"
#include "goap/planner.hpp"
#include "goap/state.hpp"
#include "inventory.hpp"
#include "item_db.hpp"
#include "nav_marker.hpp"
@ -16,12 +22,6 @@
#include "unit.hpp"
#include "unit_world_state.hpp"
#include "utility_lock.hpp"
#include "goap/action.hpp"
#include "goap/action_db.hpp"
#include "goap/actor_world_state.hpp"
#include "goap/goal.hpp"
#include "goap/planner.hpp"
#include "goap/state.hpp"
#include "utils/register_types.hpp"
#include <gdextension_interface.h>
#include <godot_cpp/core/class_db.hpp>
@ -30,9 +30,8 @@
namespace gd = godot;
void initialize_gdextension_types(gd::ModuleInitializationLevel p_level)
{
if (p_level != gd::MODULE_INITIALIZATION_LEVEL_SCENE) {
void initialize_gdextension_types(gd::ModuleInitializationLevel p_level) {
if(p_level != gd::MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
utils::godot_cpp_utils_register_types();
@ -78,15 +77,13 @@ void initialize_gdextension_types(gd::ModuleInitializationLevel p_level)
GDREGISTER_RUNTIME_CLASS(RTSPlayer);
}
extern "C"
{
// Initialization
GDExtensionBool GDE_EXPORT metro_rts_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization)
{
gd::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
init_obj.register_initializer(initialize_gdextension_types);
init_obj.set_minimum_library_initialization_level(gd::MODULE_INITIALIZATION_LEVEL_SCENE);
extern "C" {
// Initialization
GDExtensionBool GDE_EXPORT metro_rts_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
gd::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
init_obj.register_initializer(initialize_gdextension_types);
init_obj.set_minimum_library_initialization_level(gd::MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj.init();
}
return init_obj.init();
}
}

View file

@ -1,17 +1,21 @@
#include "rts_actions.hpp"
#include "nav_marker.hpp"
#include "nav_room.hpp"
#include "rts_states.hpp"
#include "utility_lock.hpp"
#include "goap/actor_world_state.hpp"
#include "goap/state.hpp"
#include "godot_cpp/classes/object.hpp"
#include "nav_marker.hpp"
#include "nav_room.hpp"
#include "rts_actions.hpp"
#include "rts_states.hpp"
#include "unit.hpp"
#include "unit_world_state.hpp"
#include "utility_lock.hpp"
#include <cmath>
#include <godot_cpp/core/memory.hpp>
#include <godot_cpp/variant/basis.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <cmath>
MoveToTarget::MoveToTarget()
: Action() {
MoveToTarget::MoveToTarget() :
Action() {
this->required.insert("can_reach_target", true);
this->effects.insert("is_at_target", true);
}
@ -26,8 +30,8 @@ goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const
return state;
}
UseWeapon::UseWeapon()
: Action() {
UseWeapon::UseWeapon() :
Action() {
this->required.insert("can_see_target", true);
this->required.insert("is_in_range", true);
this->effects.insert("is_target_unconscious", true);
@ -39,8 +43,8 @@ goap::State *UseWeapon::get_apply_state(goap::ActorWorldState *context) const {
return state;
}
FindTarget::FindTarget()
: Action() {
FindTarget::FindTarget() :
Action() {
this->require_state_complete = false;
this->effects.insert("can_see_target", true);
}
@ -52,10 +56,10 @@ goap::State *FindTarget::get_apply_state(goap::ActorWorldState *context) const {
return state;
}
GetInRange::GetInRange()
: Action() {
GetInRange::GetInRange() :
Action() {
this->require_state_complete = false;
this->required.insert("can_see_target", true);
this->effects.insert("can_see_target", true);
this->effects.insert("is_in_range", true);
}
@ -66,9 +70,8 @@ goap::State *GetInRange::get_apply_state(goap::ActorWorldState *context) const {
return state;
}
TankSelfHeal::TankSelfHeal()
: Action() {
this->required.insert("can_see_target", false);
TankSelfHeal::TankSelfHeal() :
Action() {
this->effects.insert("is_health_safe", true);
}
@ -78,8 +81,8 @@ goap::State *TankSelfHeal::get_apply_state(goap::ActorWorldState *) const {
return state;
}
TakeCover::TakeCover()
: Action () {
TakeCover::TakeCover() :
Action() {
this->effects.insert("can_see_target", false);
}
@ -100,6 +103,7 @@ bool TakeCover::procedural_is_possible(goap::ActorWorldState *context) const {
}
goap::State *TakeCover::get_apply_state(goap::ActorWorldState *context) const {
UnitWorldState *unit_state{gd::Object::cast_to<UnitWorldState>(context)};
// positions of the context and the target to hide from
gd::Vector3 const context_position{context->get_world_property("parent_global_position")};
gd::Vector3 const target_position{context->get_world_property("target_global_position")};
@ -110,6 +114,10 @@ goap::State *TakeCover::get_apply_state(goap::ActorWorldState *context) const {
NavMarker *best_marker{nullptr}; // marker with the best score found so far
float best_score{0.f}; // best score found so far, starts at zero because negative values should not be considered
for(NavMarker *marker : room->get_markers()) {
bool can_reach{unit_state->can_reach_point(marker->get_global_position())};
if(!can_reach) {
continue; // skip if unreachable
}
float const score{this->score_cover_marker(marker, target_position, context_position)};
if(score > best_score) {
best_score = score;
@ -128,11 +136,10 @@ goap::State *TakeCover::get_apply_state(goap::ActorWorldState *context) const {
bool TakeCover::is_marker_cover_from(NavMarker *marker, gd::Vector3 const &target_position, gd::Vector3 const &context_position) {
gd::Vector3 const marker_position{marker->get_global_position()};
float const distance_to_target{target_position.distance_to(context_position)};
return marker->get_marker_type() == MarkerType::Cover
&& marker->get_global_basis().get_column(2).dot(marker_position - target_position) < -2.f;
return marker->get_marker_type() == MarkerType::Cover && marker->get_global_basis().get_column(2).dot(marker_position - target_position) < -2.f;
}
float TakeCover::score_cover_marker(class NavMarker* marker, const gd::Vector3& target_position, const gd::Vector3& context_position) const {
float TakeCover::score_cover_marker(class NavMarker *marker, const gd::Vector3 &target_position, const gd::Vector3 &context_position) const {
if(!TakeCover::is_marker_cover_from(marker, target_position, context_position))
return 0.f;
gd::Vector3 const marker_position{marker->get_global_position()}; // position of the marker being considered
@ -173,7 +180,7 @@ goap::State *ActivateLock::get_apply_state(goap::ActorWorldState *context) const
}
Activate *activate = this->create_state<Activate>();
activate->lock = lock;
if(Inventory *inventory{unit_context->get_node<Inventory>("%Inventory")})
if(Inventory * inventory{unit_context->get_node<Inventory>("%Inventory")})
activate->using_item = inventory->get_utility();
return activate;
}

View file

@ -6,6 +6,7 @@
class MoveToTarget : public goap::Action {
GOAP_ACTION(MoveToTarget);
public:
MoveToTarget();
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
@ -13,6 +14,7 @@ public:
class UseWeapon : public goap::Action {
GOAP_ACTION(UseWeapon);
public:
UseWeapon();
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
@ -20,6 +22,7 @@ public:
class FindTarget : public goap::Action {
GOAP_ACTION(FindTarget);
public:
FindTarget();
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
@ -27,6 +30,7 @@ public:
class GetInRange : public goap::Action {
GOAP_ACTION(GetInRange);
public:
GetInRange();
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
@ -34,6 +38,7 @@ public:
class TankSelfHeal : public goap::Action {
GOAP_ACTION(TankSelfHeal);
public:
TankSelfHeal();
virtual goap::State *get_apply_state(goap::ActorWorldState *) const override;
@ -41,10 +46,12 @@ public:
class TakeCover : public goap::Action {
GOAP_ACTION(TakeCover);
public:
TakeCover();
virtual bool procedural_is_possible(goap::ActorWorldState *context) const override;
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
private:
static bool is_marker_cover_from(class NavMarker *marker, gd::Vector3 const &target_position, gd::Vector3 const &context_position);
float score_cover_marker(class NavMarker *marker, gd::Vector3 const &target_position, gd::Vector3 const &context_position) const;
@ -52,6 +59,7 @@ private:
class ActivateLock : public goap::Action {
GOAP_ACTION(ActivateLock);
public:
ActivateLock();
virtual bool procedural_is_possible(goap::ActorWorldState *context) const override;

View file

@ -6,6 +6,7 @@
class RTSGameMode : public utils::GameMode {
GDCLASS(RTSGameMode, utils::GameMode);
static void _bind_methods();
public:
virtual void _ready() override;
};

View file

@ -1,9 +1,9 @@
#include "rts_items.hpp"
#include "unit.hpp"
#include "entity_health.hpp"
#include "unit.hpp"
Medkit::Medkit()
: Item() {
Medkit::Medkit() :
Item() {
this->capabilities.insert(ItemCapability::Consume);
this->capabilities.insert(ItemCapability::Heal);
this->capabilities.insert(ItemCapability::Revive);
@ -17,38 +17,38 @@ void Medkit::use_on(Unit *used_by, gd::Object *used_on) const {
gd::Object::cast_to<Unit>(used_on)->get_entity_health()->healed_by(3, used_by);
}
Handgun::Handgun()
: Item() {
Handgun::Handgun() :
Item() {
this->animation = "fire_weapon";
this->capabilities.insert(ItemCapability::WeaponEquip);
this->capabilities.insert(ItemCapability::WeaponRanged);
}
bool Handgun::can_use_on(Unit *used_by, gd::Object *used_on) const {
return gd::Object::cast_to<Unit>(used_on) != nullptr
&& used_by->get_world_state()->get_can_see_target();
return gd::Object::cast_to<Unit>(used_on) != nullptr && used_by->get_world_state()->get_can_see_target();
}
void Handgun::apply_stats(Stats &stats) const {
stats.weapon_range = 10.f;
}
void Handgun::use_on(Unit* used_by, gd::Object* used_on) const {
void Handgun::use_on(Unit *used_by, gd::Object *used_on) const {
Unit *target{gd::Object::cast_to<Unit>(used_on)};
used_by->aim_at(target);
EntityHealth *health{target->get_entity_health()};
health->damaged_by(4, used_by);
gd::Vector3 const owner_position{used_by->get_global_position()};
gd::Vector3 const target_position{target->get_global_position()};
used_by->look_at(owner_position-(target_position-owner_position));
if(owner_position != target_position)
used_by->look_at(owner_position - (target_position - owner_position));
}
Lasercutter::Lasercutter()
: Item() {
Lasercutter::Lasercutter() :
Item() {
this->capabilities.insert(ItemCapability::UtilityEquip);
}
Welder::Welder()
: Item() {
Welder::Welder() :
Item() {
this->capabilities.insert(ItemCapability::UtilityEquip);
}

View file

@ -5,32 +5,40 @@
class Medkit : public Item {
ITEM_CLASS(Medkit, "Medkit",
"Standard emergency home medical kit. Use to manage wounds and stabilize a person.");
"Standard emergency home medical kit. Use to manage wounds and stabilize a person.");
private:
Medkit();
public:
virtual bool can_use_on(Unit *used_by, gd::Object *used_on) const override;
protected:
virtual void use_on(Unit *used_by, gd::Object *used_on) const override;
};
class Handgun : public Item {
ITEM_CLASS(Handgun, "9mm handgun.",
"A standard issue police firearm.");
"A standard issue police firearm.");
private:
Handgun();
public:
virtual bool can_use_on(Unit *used_by, gd::Object *used_on) const override;
virtual void apply_stats(Stats &stats) const override;
protected:
virtual void use_on(Unit *used_by, gd::Object *used_on) const override;
};
class Lasercutter : public Item {
ITEM_CLASS(Lasercutter, "Lasercutter",
"A laser cutter, use to clear metal obstacles.");
"A laser cutter, use to clear metal obstacles.");
private:
Lasercutter();
public:
// virtual bool can_use_on(Unit *used_by, gd::Object *object) const override;
protected:
@ -39,9 +47,11 @@ protected:
class Welder : public Item {
ITEM_CLASS(Welder, "Welder",
"A welder with tanks intended to be carried on a person's back. Use to repair broken metal parts.");
"A welder with tanks intended to be carried on a person's back. Use to repair broken metal parts.");
private:
Welder();
public:
// virtual bool can_use_on(Unit *used_by, gd::Object *object) const override;
protected:

View file

@ -6,7 +6,6 @@
#include <godot_cpp/classes/curve.hpp>
#include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
#include <godot_cpp/classes/viewport.hpp>
#include <godot_cpp/classes/world3d.hpp>
@ -127,7 +126,6 @@ bool RTSPlayer::order_activate_object(gd::Node3D *node) {
return true;
}
return unit != nullptr;
}
void RTSPlayer::clear_selected_unit() {
@ -236,4 +234,3 @@ void RTSPlayer::DEBUG_enable_debug(gd::Ref<gd::InputEvent>, float value) {
}
}
#endif

View file

@ -17,6 +17,7 @@ class RTSPlayer : public gd::Node3D,
public utils::IPlayer {
GDCLASS(RTSPlayer, godot::Node3D);
static void _bind_methods();
public:
virtual void _ready() override;
virtual void _process(double delta_time) override;
@ -24,6 +25,7 @@ public:
virtual void setup_player_input(utils::PlayerInput *input) override;
virtual Node *to_node() override;
virtual void spawn_at_position(gd::Transform3D const &at) override;
private:
//! process any changes to mouse position
void process_mouse();
@ -61,10 +63,12 @@ private:
gd::Ref<gd::PackedScene> get_ground_marker_scene() const;
void set_zoom_curve(gd::Ref<gd::Curve> curve);
gd::Ref<gd::Curve> get_zoom_curve() const;
private:
gd::Callable const on_selected_unit_destroyed{callable_mp(this, &RTSPlayer::clear_selected_unit)};
private:
Unit* selected_unit{nullptr};
Unit *selected_unit{nullptr};
bool mmb_down{false};
bool lmb_down{false};

View file

@ -2,9 +2,9 @@
#include "unit.hpp"
#include "utility_lock.hpp"
#include "utils/util_functions.hpp"
#include <cmath>
#include <godot_cpp/core/math.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <cmath>
void MoveTo::_bind_methods() {}
@ -114,18 +114,34 @@ void Activate::_end_state() {
void Animate::_bind_methods() {}
void Animate::_ready() {
gd::UtilityFunctions::print("!!! ", this->get_path(), ": attempting to play animation ", this->animation);
this->anim = this->get_parent()->get_node<gd::AnimationPlayer>("%AnimationPlayer");
if(!this->anim->has_animation(this->animation))
if(!this->anim->has_animation(this->animation)) {
this->end_state();
else this->anim->play(this->animation);
gd::UtilityFunctions::print("!!! ", this->get_path(), ": failed to play animation ", this->animation);
} else {
this->anim->play(this->animation);
this->anim->advance(0);
this->anim->connect("animation_finished", callable_mp(this, &self_type::_on_animation_finished));
gd::UtilityFunctions::print("!!! ", this->get_path(), ": succeeded playing animation ", this->animation);
}
}
void Animate::_process(double) {
bool const animation_finished{!this->anim->is_playing() || this->anim->get_current_animation() != this->animation};
if(this->is_action_done_interrupt() || animation_finished)
if(this->is_action_done_interrupt() || this->is_finished) {
gd::UtilityFunctions::print("!!! animation " , this->animation,
" is done in ", this->get_path(),
" animation finished: ", this->is_finished,
" interrupted: ", this->is_action_done_interrupt());
this->end_state();
}
}
void Animate::_end_state() {
this->anim->stop();
gd::UtilityFunctions::print("!!! ", this->get_path(), " animation ", this->animation, " stopped");
}
void Animate::_on_animation_finished(gd::String new_anim) {
this->is_finished = true;
}

View file

@ -1,8 +1,8 @@
#ifndef RTS_STATES_HPP
#define RTS_STATES_HPP
#include "unit.hpp"
#include "goap/state.hpp"
#include "unit.hpp"
#include <godot_cpp/classes/animation_player.hpp>
#include <godot_cpp/classes/navigation_agent3d.hpp>
@ -13,6 +13,7 @@ class UtilityLock;
class MoveTo : public goap::State {
GDCLASS(MoveTo, goap::State);
static void _bind_methods();
public:
virtual void _ready() override;
virtual void _end_state() override;
@ -22,8 +23,10 @@ public:
float target_delta_position() const;
float distance_to_target() const;
double get_repath_interval() const;
public:
gd::Node3D *target_node{nullptr};
private:
gd::Callable nav_velocity_computed{callable_mp(this, &MoveTo::on_velocity_computed)};
Unit *parent_unit{nullptr};
@ -35,12 +38,14 @@ private:
class Activate : public goap::State {
GDCLASS(Activate, goap::State);
static void _bind_methods();
public:
virtual void _ready() override;
virtual void _process(double) override;
virtual void _end_state() override;
UtilityLock *lock{nullptr};
Item const *using_item{nullptr};
private:
gd::String animation{};
gd::AnimationPlayer *anim{nullptr};
@ -50,12 +55,16 @@ private:
class Animate : public goap::State {
GDCLASS(Animate, goap::State);
static void _bind_methods();
public:
virtual void _ready() override;
virtual void _process(double delta_time) override;
virtual void _end_state() override;
void _on_animation_finished(gd::String new_anim);
gd::StringName animation{};
private:
bool is_finished{false};
gd::AnimationPlayer *anim{nullptr};
};

View file

@ -6,6 +6,7 @@
class TankUnit : public Unit {
GDCLASS(TankUnit, Unit);
static void _bind_methods();
public:
virtual void use_weapon() override;
};

View file

@ -45,6 +45,7 @@ void Unit::_physics_process(double) {
}
void Unit::stop_plan() {
gd::UtilityFunctions::print("!!! ", this->get_path(), " stop_plan");
this->current_goal.unref();
this->current_plan.clear();
this->destroy_state();
@ -71,6 +72,7 @@ void Unit::begin_marker_temporary(GoalMarker *marker) {
}
void Unit::set_target_goal(gd::Node3D *target, gd::Ref<goap::Goal> goal) {
gd::UtilityFunctions::print("!!! ", this->get_path(), " set_target_goal");
this->destroy_state();
this->world_state->set_target_node(target);
this->begin_goal(goal);
@ -96,6 +98,7 @@ void Unit::aim_at(gd::Node3D *target) {
}
void Unit::on_unconscious(Unit *damage_source) {
gd::UtilityFunctions::print("!!! ", this->get_path(), " on_unconscious");
this->destroy_state();
this->emit_signal("plan_failed");
this->anim_player->stop();
@ -103,6 +106,7 @@ void Unit::on_unconscious(Unit *damage_source) {
}
void Unit::on_death(Unit *damage_source) {
gd::UtilityFunctions::print("!!! ", this->get_path(), " on_death");
this->destroy_state();
this->emit_signal("plan_failed");
if(this->anim_player->get_current_animation() != gd::StringName("death")) {
@ -113,13 +117,16 @@ void Unit::on_death(Unit *damage_source) {
void Unit::on_velocity_computed(gd::Vector3 vel) {
gd::Vector3 const pos{this->get_global_position()};
if(vel.x == 0 && vel.z == 0)
if(vel.x == 0 && vel.z == 0) {
this->set_velocity({0.f, 0.f, 0.f});
return;
}
this->set_velocity(vel.normalized() * this->movement_speed);
this->look_at(pos - gd::Vector3{vel.x, 0.f, vel.z});
}
void Unit::destroy_state() {
gd::UtilityFunctions::print("!!! ", this->get_path(), " destroy_state");
if(this->state == nullptr)
return;
if(!this->state->is_queued_for_deletion())
@ -143,8 +150,14 @@ void Unit::next_action() {
if(this->current_plan.is_empty())
return;
goap::Action const *action{this->current_plan.get(0)};
if(action == nullptr || !action->is_possible(this->world_state)) {
if(action == nullptr) {
gd::UtilityFunctions::push_error("Plan interrupted by invalid action in queue");
this->emit_signal("plan_interrupted");
return;
}
if(!action->is_possible(this->world_state)) {
this->emit_signal("plan_interrupted");
gd::UtilityFunctions::push_error("Plan interrupted because action ", action->get_class(), " is impossible");
return;
}
// get next action and apply state
@ -193,10 +206,14 @@ int Unit::get_configure_team() const {
}
bool Unit::has_plan() const {
return !this->current_plan.is_empty();
return !this->current_plan.is_empty() || this->state != nullptr;
}
EntityHealth *Unit::get_entity_health() {
gd::Ref<goap::Goal> Unit::get_current_goal() const {
return this->current_goal;
}
EntityHealth *Unit::get_entity_health() const {
return this->health;
}
@ -211,7 +228,8 @@ float Unit::get_movement_speed() const {
#ifdef DEBUG_ENABLED
gd::String Unit::DEBUG_print_debug_info() {
gd::String debug_info{};
debug_info += gd::String("health: ") + gd::vformat("%d", this->get_entity_health()->get_wounds_current());
debug_info += gd::String("wounds: ") + gd::vformat("%d", this->get_entity_health()->get_wounds_current());
debug_info += gd::String("\ninjury: ") + gd::vformat("%d", this->get_entity_health()->get_injury_current());
debug_info += "\ngoal: ";
if(!this->current_goal.is_valid()) {
debug_info += "No goal assigned";

View file

@ -2,9 +2,9 @@
#define RTS_UNIT_HPP
#include "goal_marker.hpp"
#include "unit_world_state.hpp"
#include "goap/goal.hpp"
#include "goap/planner.hpp"
#include "unit_world_state.hpp"
#include "utils/godot_macros.hpp"
#include <godot_cpp/classes/animation_player.hpp>
#include <godot_cpp/classes/character_body3d.hpp>
@ -15,15 +15,15 @@
namespace gd = godot;
GDENUM(UnitTeam,
Neutral,
Player,
Ally,
Enemy
);
Neutral,
Player,
Ally,
Enemy);
class Unit : public gd::CharacterBody3D {
GDCLASS(Unit, gd::CharacterBody3D);
static void _bind_methods();
public:
virtual void _enter_tree() override;
virtual void _physics_process(double) override;
@ -36,6 +36,7 @@ public:
void aim_at(gd::Node3D *node);
void on_unconscious(Unit *damage_source);
void on_death(Unit *damage_source);
private:
void on_velocity_computed(gd::Vector3 vel);
void destroy_state();
@ -43,18 +44,22 @@ private:
void next_action();
void replan_goal();
void set_goal_and_plan(gd::Ref<goap::Goal> goal);
public: // getter-setters
UnitWorldState *get_world_state() const;
UnitTeam get_team() const;
void set_configure_team(int value);
int get_configure_team() const;
bool has_plan() const;
EntityHealth *get_entity_health();
gd::Ref<goap::Goal> get_current_goal() const;
EntityHealth *get_entity_health() const;
void set_movement_speed(float speed);
float get_movement_speed() const;
private:
gd::Callable on_state_finished{callable_mp(this, &Unit::state_finished)};
gd::Callable on_plan_failed{callable_mp(this, &Unit::replan_goal)};
protected:
goap::Plan current_plan{};
gd::Ref<goap::Goal> current_goal{};

View file

@ -1,12 +1,14 @@
#include "unit_world_state.hpp"
#include "entity_health.hpp"
#include "godot_cpp/classes/navigation_server3d.hpp"
#include "godot_cpp/variant/packed_vector3_array.hpp"
#include "unit.hpp"
#include "utility_lock.hpp"
#include "utils/godot_macros.hpp"
#include "utils/util_functions.hpp"
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
#include <godot_cpp/classes/world3d.hpp>
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
#include <godot_cpp/variant/callable_method_pointer.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
@ -22,6 +24,7 @@ void UnitWorldState::_bind_methods() {
GDFUNCTION(get_is_target_enemy);
GDFUNCTION(get_is_target_activated);
GDFUNCTION(get_is_in_range);
GDFUNCTION(get_can_reach_target);
GDFUNCTION(get_is_health_safe);
GDFUNCTION(get_parent_global_position);
GDFUNCTION(get_target_global_position);
@ -61,19 +64,19 @@ bool UnitWorldState::get_can_see_node(gd::Node3D *node) const {
// construct list for ignored objects including target node and parent unit
gd::TypedArray<gd::RID> ignore_list{this->parent_unit->get_rid()};
// ignore target if it is a collision object that might obstruct it's own collision test
if(gd::CollisionObject3D *as_collision_object{gd::Object::cast_to<gd::CollisionObject3D>(node)})
if(gd::CollisionObject3D * as_collision_object{gd::Object::cast_to<gd::CollisionObject3D>(node)})
ignore_list.push_back(as_collision_object->get_rid());
// construct raycast query from unit's eye node to vision target. Ignoring parent unit and target if applicable
gd::Ref<gd::PhysicsRayQueryParameters3D> const query{gd::PhysicsRayQueryParameters3D::create(
eyes.origin,
target_position,
0x1 | 0x4, ignore_list
)};
eyes.origin,
target_position,
0x1 | 0x4, ignore_list)};
return this->parent_unit->get_world_3d()->get_direct_space_state()->intersect_ray(query).is_empty();
}
bool UnitWorldState::get_is_at_target() const {
if(this->target_node == nullptr) return true;
if(this->target_node == nullptr)
return true;
gd::Vector3 const target = this->target_node->get_global_position();
gd::Vector3 const current = this->parent_unit->get_global_position();
float const min_dist = this->agent->get_target_desired_distance();
@ -85,7 +88,7 @@ bool UnitWorldState::get_has_target() const {
}
bool UnitWorldState::get_is_target_unconscious() const {
if(EntityHealth *health{this->target_node->get_node<EntityHealth>("%EntityHealth")})
if(EntityHealth * health{this->target_node->get_node<EntityHealth>("%EntityHealth")})
return !health->is_conscious();
return false;
}
@ -100,9 +103,7 @@ bool UnitWorldState::get_is_target_enemy() const {
}
bool UnitWorldState::get_is_unit_enemy(Unit *unit) const {
return unit != nullptr
&& unit->get_team() != UnitTeam::Neutral
&& unit->get_team() != this->parent_unit->get_team();
return unit != nullptr && unit->get_team() != UnitTeam::Neutral && unit->get_team() != this->parent_unit->get_team();
}
bool UnitWorldState::get_is_target_activated() const {
@ -111,8 +112,21 @@ bool UnitWorldState::get_is_target_activated() const {
}
bool UnitWorldState::get_is_in_range() const {
return this->target_node != nullptr
&& this->target_node->get_global_position().distance_squared_to(this->parent_unit->get_global_position()) <= 2.f * 2.f;
return this->target_node != nullptr && this->target_node->get_global_position().distance_squared_to(this->parent_unit->get_global_position()) <= 2.f * 2.f;
}
bool UnitWorldState::can_reach_point(gd::Vector3 point) const {
gd::NavigationServer3D *server{gd::NavigationServer3D::get_singleton()};
godot::PackedVector3Array path{server->map_get_path(this->agent->get_navigation_map(),
this->parent_unit->get_global_position(), point, false, 0xF)};
gd::Vector3 const endpoint{path[path.size() - 1]};
bool const can_reach{!path.is_empty() && gd::Vector2{endpoint.x, endpoint.z}.is_equal_approx({point.x, point.z})};
gd::UtilityFunctions::print("target ", point, " reachable from ", this->get_parent_global_position(), ": ", can_reach, ", endpoint ", path[path.size()-1]);
return can_reach;
}
bool UnitWorldState::get_can_reach_target() const {
return this->can_reach_point(this->get_target_global_position());
}
bool UnitWorldState::get_is_health_safe() const {
@ -142,7 +156,7 @@ gd::Node3D *UnitWorldState::get_target_node() const {
}
void UnitWorldState::cache_property(gd::String property, gd::Variant value, double auto_invalidate_time) {
this->cache[property] = {.value=value, .auto_invalidate_time=auto_invalidate_time};
this->cache[property] = {.value = value, .auto_invalidate_time = auto_invalidate_time};
}
void UnitWorldState::target_destroyed(gd::Node3D *target) {

View file

@ -2,8 +2,8 @@
#define UNIT_WORLD_STATE_HPP
#include "entity_health.hpp"
#include "inventory.hpp"
#include "goap/actor_world_state.hpp"
#include "inventory.hpp"
#include <godot_cpp/classes/navigation_agent3d.hpp>
#include <godot_cpp/classes/node3d.hpp>
@ -19,6 +19,7 @@ struct CachedWorldProperty {
class UnitWorldState : public goap::ActorWorldState {
GDCLASS(UnitWorldState, goap::ActorWorldState);
static void _bind_methods();
public:
virtual void _enter_tree() override;
virtual gd::Variant get_world_property(gd::String property) override;
@ -34,16 +35,21 @@ public:
bool get_is_unit_enemy(Unit *unit) const;
bool get_is_target_activated() const;
virtual bool get_is_in_range() const;
bool can_reach_point(gd::Vector3 point) const;
bool get_can_reach_target() const;
bool get_is_health_safe() const;
gd::Vector3 get_parent_global_position() const;
gd::Vector3 get_target_global_position() const;
void set_target_node(gd::Node3D *node);
gd::Node3D *get_target_node() const;
private:
void cache_property(gd::String property, gd::Variant value, double auto_invalidate_time);
void target_destroyed(gd::Node3D *target);
private:
gd::Callable const target_node_exited_tree{callable_mp(this, &UnitWorldState::target_destroyed)};
protected:
Unit *parent_unit{nullptr};
EntityHealth *health{nullptr};

View file

@ -23,7 +23,8 @@ bool UtilityLock::begin_activate(Item const *with, Unit *by) {
this->_begin_activate(with, by);
this->emit_signal("begin_activate", with->get_id(), by);
return true;
} else return false;
} else
return false;
}
bool UtilityLock::finish_activate(Item const *with, Unit *by) {
@ -33,7 +34,8 @@ bool UtilityLock::finish_activate(Item const *with, Unit *by) {
this->_finish_activate(with, by);
this->emit_signal("finish_activate", with->get_id(), by);
return true;
} else return false;
} else
return false;
}
void UtilityLock::interrupt_activate() {

View file

@ -3,14 +3,15 @@
#include "goal_marker.hpp"
#include "item.hpp"
#include <godot_cpp/variant/utility_functions.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
namespace gd = godot;
class UtilityLock : public GoalMarker {
GDCLASS(UtilityLock, GoalMarker)
static void _bind_methods();
public:
enum ActivationState {
Inactive,
@ -31,6 +32,7 @@ public:
gd::Array get_allowed_items() const;
void set_animation_name(gd::String animation);
gd::String get_animation_name() const;
private:
gd::HashSet<Item const *> allowed_items{};
Unit *user{nullptr};