fix: plan being considered empty if the last state is still active
This commit is contained in:
parent
d7dadac070
commit
9b27fbaabf
37 changed files with 354 additions and 220 deletions
|
|
@ -18,4 +18,3 @@ void CharacterUnit::use_weapon() {
|
|||
this->inventory->get_weapon()->try_use_on(this, target);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
44
src/item.hpp
44
src/item.hpp
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
class RTSGameMode : public utils::GameMode {
|
||||
GDCLASS(RTSGameMode, utils::GameMode);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void _ready() override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
class TankUnit : public Unit {
|
||||
GDCLASS(TankUnit, Unit);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void use_weapon() override;
|
||||
};
|
||||
|
|
|
|||
28
src/unit.cpp
28
src/unit.cpp
|
|
@ -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";
|
||||
|
|
|
|||
19
src/unit.hpp
19
src/unit.hpp
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue