feat: implemented item and inventory outline
This commit is contained in:
parent
f771b383f3
commit
a024aab203
|
@ -28,7 +28,7 @@ collision_layer = 6
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
|
|
||||||
[node name="ActorWorldState" type="EnemyWorldState" parent="."]
|
[node name="ActorWorldState" type="EnemyWorldState" parent="."]
|
||||||
editor_available_goals = [ExtResource("1_jwvis"), ExtResource("1_b1qo1")]
|
available_goals_inspector = [ExtResource("1_jwvis"), ExtResource("1_b1qo1")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
||||||
[node name="Planner" type="Planner" parent="."]
|
[node name="Planner" type="Planner" parent="."]
|
||||||
|
|
|
@ -33,6 +33,10 @@ unique_name_in_owner = true
|
||||||
[node name="EntityHealth" type="EntityHealth" parent="."]
|
[node name="EntityHealth" type="EntityHealth" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[node name="Inventory" type="Inventory" parent="."]
|
||||||
|
weapon = 2
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
|
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
path_desired_distance = 0.2
|
path_desired_distance = 0.2
|
||||||
|
|
|
@ -8,10 +8,7 @@
|
||||||
|
|
||||||
void EnemyWorldState::_bind_methods() {
|
void EnemyWorldState::_bind_methods() {
|
||||||
#define CLASSNAME EnemyWorldState
|
#define CLASSNAME EnemyWorldState
|
||||||
GDPROPERTY_HINTED(editor_available_goals,
|
GDPROPERTY_HINTED(available_goals_inspector, gd::Variant::ARRAY, gd::PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE("Goal"));
|
||||||
gd::Variant::ARRAY,
|
|
||||||
gd::PROPERTY_HINT_ARRAY_TYPE,
|
|
||||||
GDRESOURCETYPE("Goal"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnemyWorldState::_ready() { GDGAMEONLY();
|
void EnemyWorldState::_ready() { GDGAMEONLY();
|
||||||
|
@ -144,13 +141,13 @@ void EnemyWorldState::try_set_target(Unit *unit) {
|
||||||
this->parent_unit->set_target_goal(unit, goal);
|
this->parent_unit->set_target_goal(unit, goal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnemyWorldState::set_editor_available_goals(gd::Array array) {
|
void EnemyWorldState::set_available_goals_inspector(gd::Array array) {
|
||||||
this->available_goals.clear();
|
this->available_goals.clear();
|
||||||
for(int i{0}; i < array.size(); ++i)
|
for(int i{0}; i < array.size(); ++i)
|
||||||
this->available_goals.push_back(array[i]);
|
this->available_goals.push_back(array[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
gd::Array EnemyWorldState::get_editor_available_goals() const {
|
gd::Array EnemyWorldState::get_available_goals_inspector() const {
|
||||||
gd::Array a{};
|
gd::Array a{};
|
||||||
for(gd::Ref<goap::Goal> const &goal : this->available_goals)
|
for(gd::Ref<goap::Goal> const &goal : this->available_goals)
|
||||||
a.push_back(goal);
|
a.push_back(goal);
|
||||||
|
|
|
@ -36,8 +36,8 @@ private:
|
||||||
float calculate_priority(Unit *target);
|
float calculate_priority(Unit *target);
|
||||||
gd::Ref<goap::Goal> get_goal_for_target(Unit *unit);
|
gd::Ref<goap::Goal> get_goal_for_target(Unit *unit);
|
||||||
void try_set_target(Unit *target);
|
void try_set_target(Unit *target);
|
||||||
void set_editor_available_goals(gd::Array array);
|
void set_available_goals_inspector(gd::Array array);
|
||||||
gd::Array get_editor_available_goals() const;
|
gd::Array get_available_goals_inspector() const;
|
||||||
private:
|
private:
|
||||||
gd::Callable aware_unit_death{callable_mp(this, &EnemyWorldState::on_aware_unit_death)};
|
gd::Callable aware_unit_death{callable_mp(this, &EnemyWorldState::on_aware_unit_death)};
|
||||||
gd::Vector<gd::Ref<goap::Goal>> available_goals{};
|
gd::Vector<gd::Ref<goap::Goal>> available_goals{};
|
||||||
|
|
|
@ -73,21 +73,33 @@ float EntityHealth::get_wounds_damage_factor() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityHealth::is_conscious() const {
|
bool EntityHealth::is_conscious() const {
|
||||||
return !this->is_dead() && this->wounds_current > 0;
|
return !this->is_dead()
|
||||||
|
&& this->wounds_current > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityHealth::can_be_revived() const {
|
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 {
|
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 {
|
bool EntityHealth::is_dead() const {
|
||||||
return this->injury_current <= 0;
|
return this->injury_current <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityHealth::set_stats(Stats const *stats) {
|
||||||
|
this->stats = stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stats const *EntityHealth::get_stats() const {
|
||||||
|
return this->stats;
|
||||||
|
}
|
||||||
|
|
||||||
void EntityHealth::set_injury_max(int max_health) {
|
void EntityHealth::set_injury_max(int max_health) {
|
||||||
this->injury_max = max_health;
|
this->injury_max = max_health;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef RTS_ENTITY_HEALTH_HPP
|
#ifndef RTS_ENTITY_HEALTH_HPP
|
||||||
#define RTS_ENTITY_HEALTH_HPP
|
#define RTS_ENTITY_HEALTH_HPP
|
||||||
|
|
||||||
|
#include "stats.hpp"
|
||||||
#include <godot_cpp/classes/node.hpp>
|
#include <godot_cpp/classes/node.hpp>
|
||||||
|
|
||||||
namespace gd = godot;
|
namespace gd = godot;
|
||||||
|
@ -25,6 +26,8 @@ public:
|
||||||
bool can_be_healed() const;
|
bool can_be_healed() const;
|
||||||
bool can_be_damaged() const;
|
bool can_be_damaged() const;
|
||||||
bool is_dead() const;
|
bool is_dead() const;
|
||||||
|
void set_stats(Stats const *stats);
|
||||||
|
Stats const *get_stats() const;
|
||||||
void set_injury_max(int max_health);
|
void set_injury_max(int max_health);
|
||||||
int get_injury_max() const;
|
int get_injury_max() const;
|
||||||
int get_injury_current() const;
|
int get_injury_current() const;
|
||||||
|
@ -32,6 +35,7 @@ public:
|
||||||
int get_wounds_max() const;
|
int get_wounds_max() const;
|
||||||
int get_wounds_current() const;
|
int get_wounds_current() const;
|
||||||
private:
|
private:
|
||||||
|
Stats const *stats;
|
||||||
// long term health
|
// long term health
|
||||||
int injury_max{10};
|
int injury_max{10};
|
||||||
int injury_current{0};
|
int injury_current{0};
|
||||||
|
|
85
src/inventory.cpp
Normal file
85
src/inventory.cpp
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
#include "inventory.hpp"
|
||||||
|
#include "item_db.hpp"
|
||||||
|
#include "unit.hpp"
|
||||||
|
#include "utils/godot_macros.hpp"
|
||||||
|
|
||||||
|
void Inventory::_bind_methods() {
|
||||||
|
#define CLASSNAME Inventory
|
||||||
|
GDPROPERTY_HINTED(weapon_id, gd::Variant::INT, gd::PROPERTY_HINT_ENUM, ItemDB::get_enum_hint());
|
||||||
|
GDPROPERTY_HINTED(armour_id, gd::Variant::INT, gd::PROPERTY_HINT_ENUM, ItemDB::get_enum_hint());
|
||||||
|
GDPROPERTY(inventory_inspector, gd::Variant::DICTIONARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inventory::_ready() {
|
||||||
|
this->parent_unit = gd::Object::cast_to<Unit>(this->get_parent());
|
||||||
|
}
|
||||||
|
|
||||||
|
Stats Inventory::get_stats() const {
|
||||||
|
return this->weapon->get_stats() & this->armour->get_stats() & this->helmet->get_stats() & this->utility->get_stats();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inventory::add_item(const Item *item, unsigned num) {
|
||||||
|
if(!this->inventory.has(item))
|
||||||
|
this->inventory.insert(item, num);
|
||||||
|
else if(num > 0)
|
||||||
|
this->inventory[item] += num;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned Inventory::get_item_amount(Item const *item) {
|
||||||
|
return this->inventory.has(item) ? this->inventory[item] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Inventory::has_item(Item const *item) {
|
||||||
|
return this->inventory.has(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inventory::set_weapon(Item const *item) {
|
||||||
|
this->weapon = item != nullptr && item->has_capability(ItemCapability::WeaponEquip) ? item : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item const *Inventory::get_weapon() const {
|
||||||
|
return this->weapon;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inventory::set_armour(Item const *item) {
|
||||||
|
this->armour = item != nullptr && item->has_capability(ItemCapability::ArmourEquip) ? item : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item const *Inventory::get_armour() const {
|
||||||
|
return this->armour;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inventory::set_weapon_id(int item_id) {
|
||||||
|
this->set_weapon(ItemDB::get_item(item_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
int Inventory::get_weapon_id() const {
|
||||||
|
return this->weapon ? this->weapon->get_id() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inventory::set_armour_id(int item_id) {
|
||||||
|
this->set_armour(ItemDB::get_item(item_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
int Inventory::get_armour_id() const {
|
||||||
|
return this->armour ? this->armour->get_id() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inventory::set_inventory_inspector(gd::Dictionary dict) {
|
||||||
|
this->inventory.clear();
|
||||||
|
gd::Array a{dict.keys()};
|
||||||
|
for(int i{0}; i < a.size(); ++i) {
|
||||||
|
gd::StringName const item_name{a[i]};
|
||||||
|
unsigned const amount{dict[item_name]};
|
||||||
|
Item const *item{ItemDB::get_item_by_name(item_name)};
|
||||||
|
if(item != nullptr && amount > 0)
|
||||||
|
this->inventory[item] = amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gd::Dictionary Inventory::get_inventory_inspector() const {
|
||||||
|
gd::Dictionary dict{};
|
||||||
|
for(gd::KeyValue<Item const *, unsigned> const &item : this->inventory)
|
||||||
|
dict[item.key->get_class()] = item.value;
|
||||||
|
return dict;
|
||||||
|
}
|
47
src/inventory.hpp
Normal file
47
src/inventory.hpp
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#ifndef INVENTORY_HPP
|
||||||
|
#define INVENTORY_HPP
|
||||||
|
|
||||||
|
#include "item.hpp"
|
||||||
|
#include "stats.hpp"
|
||||||
|
#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>
|
||||||
|
|
||||||
|
namespace gd = godot;
|
||||||
|
class Unit;
|
||||||
|
|
||||||
|
class Inventory : public gd::Node {
|
||||||
|
GDCLASS(Inventory, gd::Node);
|
||||||
|
static void _bind_methods();
|
||||||
|
public:
|
||||||
|
virtual void _ready() override;
|
||||||
|
Stats get_stats() const;
|
||||||
|
Item const *get_capability_for(ItemCapability const &capability, gd::Object *target) const;
|
||||||
|
void add_item(Item const *item, unsigned num);
|
||||||
|
unsigned get_item_amount(Item const *item);
|
||||||
|
bool has_item(Item const *item);
|
||||||
|
|
||||||
|
void set_weapon(Item const *weapon);
|
||||||
|
Item const *get_weapon() const;
|
||||||
|
void set_armour(Item const *weapon);
|
||||||
|
Item const *get_armour() const;
|
||||||
|
|
||||||
|
void set_weapon_id(int item_id);
|
||||||
|
int get_weapon_id() const;
|
||||||
|
void set_armour_id(int item_id);
|
||||||
|
int get_armour_id() const;
|
||||||
|
void set_inventory_inspector(gd::Dictionary dict);
|
||||||
|
gd::Dictionary get_inventory_inspector() const;
|
||||||
|
private:
|
||||||
|
Item const *weapon{nullptr};
|
||||||
|
Item const *armour{nullptr};
|
||||||
|
Item const *helmet{nullptr};
|
||||||
|
Item const *utility{nullptr};
|
||||||
|
gd::HashMap<Item const *, unsigned> inventory{};
|
||||||
|
|
||||||
|
Unit *parent_unit{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // !INVENTORY_HPP
|
35
src/item.cpp
Normal file
35
src/item.cpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#include "item.hpp"
|
||||||
|
#include "unit.hpp"
|
||||||
|
#include "utils/godot_macros.hpp"
|
||||||
|
|
||||||
|
Item::~Item() {}
|
||||||
|
|
||||||
|
bool Item::can_use_on(Unit *used_by, gd::Object *used_on) const {
|
||||||
|
return used_on != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Item::try_use_on(Unit *used_by, gd::Object *used_on) const {
|
||||||
|
if(this->can_use_on(used_by, used_on)) {
|
||||||
|
this->use_on(used_by, used_on);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Item::use_on(Unit *used_by, gd::Object *used_on) const {
|
||||||
|
if(used_on != nullptr)
|
||||||
|
gd::UtilityFunctions::push_warning(used_by->get_path(), " tried to use default Item::use_on implementation on object of type ", used_on->get_class());
|
||||||
|
}
|
||||||
|
|
||||||
|
Stats Item::get_stats() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Item::has_capability(ItemCapability const &capability) const {
|
||||||
|
return this->capabilities.has(capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemID Item::get_id() const {
|
||||||
|
return this->id;
|
||||||
|
}
|
70
src/item.hpp
Normal file
70
src/item.hpp
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
#ifndef ITEM_HPP
|
||||||
|
#define ITEM_HPP
|
||||||
|
|
||||||
|
#include "stats.hpp"
|
||||||
|
#include "goap/action.hpp"
|
||||||
|
#include "utils/godot_macros.hpp"
|
||||||
|
#include <godot_cpp/templates/hash_set.hpp>
|
||||||
|
#include <godot_cpp/variant/utility_functions.hpp>
|
||||||
|
|
||||||
|
namespace gd = godot;
|
||||||
|
|
||||||
|
GDENUM(ItemCapability,
|
||||||
|
Heal,
|
||||||
|
Revive,
|
||||||
|
WeaponEquip,
|
||||||
|
WeaponRanged,
|
||||||
|
WeaponMelee,
|
||||||
|
ArmourEquip,
|
||||||
|
HeadEquip,
|
||||||
|
UtilityEquip,
|
||||||
|
Consume
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef int ItemID;
|
||||||
|
class Unit;
|
||||||
|
|
||||||
|
class Item {
|
||||||
|
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.");
|
||||||
|
return "Item";
|
||||||
|
}
|
||||||
|
virtual gd::StringName get_class() const {
|
||||||
|
gd::UtilityFunctions::push_error("Calling Item::get_class() on an Item class that does not implement it. Use ITEM_CLASS(*) macro.");
|
||||||
|
return "Item";
|
||||||
|
}
|
||||||
|
virtual gd::String get_display_name() const {
|
||||||
|
gd::UtilityFunctions::push_error("Calling Item::get_display_name() on an Item class that does not implement it. Use ITEM_CLASS(*) macro.");
|
||||||
|
return "<UNNAMED ITEM>";
|
||||||
|
}
|
||||||
|
virtual gd::String get_description() const {
|
||||||
|
gd::UtilityFunctions::push_error("Calling Item::get_description() on an Item class that does not implement it. Use ITEM_CLASS(*) macro.");
|
||||||
|
return "<UNDESCRIBED>";
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
virtual Stats get_stats() const;
|
||||||
|
bool has_capability(ItemCapability const &capability) const;
|
||||||
|
ItemID get_id() const;
|
||||||
|
protected:
|
||||||
|
virtual void use_on(Unit *used_by, gd::Object *used_on) const;
|
||||||
|
protected:
|
||||||
|
gd::HashSet<uint32_t> capabilities{};
|
||||||
|
private:
|
||||||
|
ItemID id{-1};
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ITEM_CLASS(Class_, DisplayName_, Description_)\
|
||||||
|
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
|
58
src/item_db.cpp
Normal file
58
src/item_db.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#include "item_db.hpp"
|
||||||
|
|
||||||
|
ItemDB::StaticData::~StaticData() {
|
||||||
|
if(this->hint != nullptr)
|
||||||
|
delete this->hint;
|
||||||
|
if(this->array_hint != nullptr)
|
||||||
|
delete this->array_hint;
|
||||||
|
for(Item *action : this->items)
|
||||||
|
delete action;
|
||||||
|
}
|
||||||
|
|
||||||
|
gd::String &ItemDB::StaticData::get_hint() {
|
||||||
|
if(this->hint == nullptr)
|
||||||
|
this->hint = new gd::String("None");
|
||||||
|
return *this->hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
gd::String &ItemDB::StaticData::get_array_hint() {
|
||||||
|
if(this->array_hint == nullptr)
|
||||||
|
this->array_hint = new gd::String("None");
|
||||||
|
return *this->array_hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemID ItemDB::register_item(Item *item, gd::String item_name) {
|
||||||
|
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());
|
||||||
|
ItemDB::data.items.push_back(item);
|
||||||
|
gd::UtilityFunctions::print("registered item type ", item_name);
|
||||||
|
return item->get_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
Item const *ItemDB::get_item(ItemID index) {
|
||||||
|
index -= 1;
|
||||||
|
if(ItemDB::data.items.size() <= index || index < 0) {
|
||||||
|
gd::UtilityFunctions::push_warning("Attempted to get pointer to non-existent item type by ID ", index);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return ItemDB::data.items[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
gd::String const &ItemDB::get_enum_hint() {
|
||||||
|
return ItemDB::data.get_hint();
|
||||||
|
}
|
||||||
|
|
||||||
|
gd::String const &ItemDB::get_array_hint() {
|
||||||
|
return ItemDB::data.get_array_hint();
|
||||||
|
}
|
||||||
|
|
||||||
|
Item const *ItemDB::get_item_by_name(gd::StringName item_name) {
|
||||||
|
for(Item const *item : ItemDB::data.items)
|
||||||
|
if(item->get_class() == item_name)
|
||||||
|
return item;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDB::StaticData ItemDB::data{};
|
34
src/item_db.hpp
Normal file
34
src/item_db.hpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef ITEM_DB_HPP
|
||||||
|
#define ITEM_DB_HPP
|
||||||
|
|
||||||
|
#include "item.hpp"
|
||||||
|
#include <godot_cpp/templates/vector.hpp>
|
||||||
|
|
||||||
|
namespace gd = godot;
|
||||||
|
|
||||||
|
class ItemDB {
|
||||||
|
struct StaticData {
|
||||||
|
StaticData() = default;
|
||||||
|
~StaticData();
|
||||||
|
gd::String &get_hint();
|
||||||
|
gd::String &get_array_hint();
|
||||||
|
gd::String *array_hint{};
|
||||||
|
gd::String *hint{};
|
||||||
|
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();
|
||||||
|
static gd::String const &get_array_hint();
|
||||||
|
static Item const *get_item_by_name(gd::StringName item);
|
||||||
|
|
||||||
|
template <class TItem>
|
||||||
|
_FORCE_INLINE_ static ItemID register_item() {
|
||||||
|
return ItemDB::register_item(new TItem(), TItem::get_static_class());
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static StaticData data;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // !ITEM_DB_HPP
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
void NavRoom::_bind_methods() {
|
void NavRoom::_bind_methods() {
|
||||||
#define CLASSNAME NavRoom
|
#define CLASSNAME NavRoom
|
||||||
GDPROPERTY_HINTED(editor_neighbours, gd::Variant::ARRAY, gd::PROPERTY_HINT_ARRAY_TYPE, GDNODETYPE("NavRoom"));
|
GDPROPERTY_HINTED(neighbours_inspector, gd::Variant::ARRAY, gd::PROPERTY_HINT_ARRAY_TYPE, GDNODETYPE("NavRoom"));
|
||||||
}
|
}
|
||||||
|
|
||||||
NavRoom *NavRoom::get_closest_room(gd::Vector3 const &closest_to) {
|
NavRoom *NavRoom::get_closest_room(gd::Vector3 const &closest_to) {
|
||||||
|
@ -44,14 +44,14 @@ gd::Vector<NavMarker*> const &NavRoom::get_markers() const {
|
||||||
return this->markers;
|
return this->markers;
|
||||||
}
|
}
|
||||||
|
|
||||||
gd::Array NavRoom::get_editor_neighbours() const {
|
gd::Array NavRoom::get_neighbours_inspector() const {
|
||||||
gd::Array a{};
|
gd::Array a{};
|
||||||
for(NavRoom *room : this->neighbours)
|
for(NavRoom *room : this->neighbours)
|
||||||
a.push_back(room);
|
a.push_back(room);
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavRoom::set_editor_neighbours(gd::Array array) {
|
void NavRoom::set_neighbours_inspector(gd::Array array) {
|
||||||
this->neighbours.clear();
|
this->neighbours.clear();
|
||||||
while(!array.is_empty()) if(NavRoom *room{gd::Object::cast_to<NavRoom>(array.pop_front())})
|
while(!array.is_empty()) if(NavRoom *room{gd::Object::cast_to<NavRoom>(array.pop_front())})
|
||||||
this->neighbours.push_back(room);
|
this->neighbours.push_back(room);
|
||||||
|
|
|
@ -19,8 +19,8 @@ public:
|
||||||
gd::Vector<NavRoom*> const &get_neighbours() const;
|
gd::Vector<NavRoom*> const &get_neighbours() const;
|
||||||
gd::Vector<NavMarker*> const &get_markers() const;
|
gd::Vector<NavMarker*> const &get_markers() const;
|
||||||
private:
|
private:
|
||||||
void set_editor_neighbours(gd::Array array);
|
void set_neighbours_inspector(gd::Array array);
|
||||||
gd::Array get_editor_neighbours() const;
|
gd::Array get_neighbours_inspector() const;
|
||||||
private:
|
private:
|
||||||
float radius{1.f};
|
float radius{1.f};
|
||||||
gd::Vector3 centre{0.f, 0.f, 0.f};
|
gd::Vector3 centre{0.f, 0.f, 0.f};
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
#include "register_types.h"
|
#include "register_types.h"
|
||||||
#include "enemy_world_state.hpp"
|
#include "enemy_world_state.hpp"
|
||||||
#include "entity_health.hpp"
|
#include "entity_health.hpp"
|
||||||
#include "goap/state.hpp"
|
#include "inventory.hpp"
|
||||||
|
#include "item_db.hpp"
|
||||||
#include "nav_marker.hpp"
|
#include "nav_marker.hpp"
|
||||||
#include "nav_room.hpp"
|
#include "nav_room.hpp"
|
||||||
#include "rts_actions.hpp"
|
#include "rts_actions.hpp"
|
||||||
#include "rts_game_mode.hpp"
|
#include "rts_game_mode.hpp"
|
||||||
|
#include "rts_items.hpp"
|
||||||
#include "rts_player.hpp"
|
#include "rts_player.hpp"
|
||||||
#include "rts_states.hpp"
|
#include "rts_states.hpp"
|
||||||
#include "unit.hpp"
|
#include "unit.hpp"
|
||||||
|
@ -15,6 +17,7 @@
|
||||||
#include "goap/actor_world_state.hpp"
|
#include "goap/actor_world_state.hpp"
|
||||||
#include "goap/goal.hpp"
|
#include "goap/goal.hpp"
|
||||||
#include "goap/planner.hpp"
|
#include "goap/planner.hpp"
|
||||||
|
#include "goap/state.hpp"
|
||||||
#include "utils/register_types.hpp"
|
#include "utils/register_types.hpp"
|
||||||
#include <gdextension_interface.h>
|
#include <gdextension_interface.h>
|
||||||
#include <godot_cpp/core/class_db.hpp>
|
#include <godot_cpp/core/class_db.hpp>
|
||||||
|
@ -31,7 +34,7 @@ void initialize_gdextension_types(gd::ModuleInitializationLevel p_level)
|
||||||
utils::godot_cpp_utils_register_types();
|
utils::godot_cpp_utils_register_types();
|
||||||
|
|
||||||
// always register actions before classes,
|
// always register actions before classes,
|
||||||
// so that ActionDB::get_enum_hint is complete before _bind_methods
|
// so that ActionDB::get_enum_hint is complete before _bind_methods.
|
||||||
goap::ActionDB::register_action<MoveToTarget>();
|
goap::ActionDB::register_action<MoveToTarget>();
|
||||||
goap::ActionDB::register_action<FireAtTarget>();
|
goap::ActionDB::register_action<FireAtTarget>();
|
||||||
goap::ActionDB::register_action<FindTarget>();
|
goap::ActionDB::register_action<FindTarget>();
|
||||||
|
@ -40,24 +43,31 @@ void initialize_gdextension_types(gd::ModuleInitializationLevel p_level)
|
||||||
goap::ActionDB::register_action<TankSelfHeal>();
|
goap::ActionDB::register_action<TankSelfHeal>();
|
||||||
goap::ActionDB::register_action<TakeCover>();
|
goap::ActionDB::register_action<TakeCover>();
|
||||||
|
|
||||||
gd::ClassDB::register_class<goap::ActorWorldState>();
|
// same for items,
|
||||||
gd::ClassDB::register_class<goap::Goal>();
|
// make sure ItemDB::get_enum_hint is fully populated out before _bind_methods.
|
||||||
gd::ClassDB::register_class<goap::Planner>();
|
ItemDB::register_item<Medkit>();
|
||||||
gd::ClassDB::register_class<goap::State>();
|
ItemDB::register_item<Handgun>();
|
||||||
|
ItemDB::register_item<Lasercutter>();
|
||||||
|
ItemDB::register_item<Welder>();
|
||||||
|
|
||||||
gd::ClassDB::register_class<MoveTo>();
|
GDREGISTER_CLASS(goap::ActorWorldState);
|
||||||
gd::ClassDB::register_class<Animate>();
|
GDREGISTER_CLASS(goap::Goal);
|
||||||
gd::ClassDB::register_class<Activate>();
|
GDREGISTER_CLASS(goap::Planner);
|
||||||
|
GDREGISTER_CLASS(goap::State);
|
||||||
|
|
||||||
gd::ClassDB::register_class<UnitWorldState>();
|
GDREGISTER_CLASS(MoveTo);
|
||||||
gd::ClassDB::register_class<EnemyWorldState>();
|
GDREGISTER_CLASS(Animate);
|
||||||
gd::ClassDB::register_class<GoalMarker>();
|
GDREGISTER_CLASS(Activate);
|
||||||
gd::ClassDB::register_class<Unit>();
|
GDREGISTER_CLASS(UnitWorldState);
|
||||||
gd::ClassDB::register_class<RTSGameMode>();
|
GDREGISTER_CLASS(EnemyWorldState);
|
||||||
gd::ClassDB::register_class<RTSPlayer>();
|
GDREGISTER_CLASS(GoalMarker);
|
||||||
gd::ClassDB::register_class<EntityHealth>();
|
GDREGISTER_CLASS(Unit);
|
||||||
gd::ClassDB::register_class<NavMarker>();
|
GDREGISTER_CLASS(RTSGameMode);
|
||||||
gd::ClassDB::register_class<NavRoom>();
|
GDREGISTER_CLASS(RTSPlayer);
|
||||||
|
GDREGISTER_CLASS(EntityHealth);
|
||||||
|
GDREGISTER_CLASS(NavMarker);
|
||||||
|
GDREGISTER_CLASS(NavRoom);
|
||||||
|
GDREGISTER_CLASS(Inventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
|
|
46
src/rts_items.cpp
Normal file
46
src/rts_items.cpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#include "rts_items.hpp"
|
||||||
|
#include "unit.hpp"
|
||||||
|
#include "entity_health.hpp"
|
||||||
|
|
||||||
|
Medkit::Medkit()
|
||||||
|
: Item() {
|
||||||
|
this->capabilities.insert(ItemCapability::Consume);
|
||||||
|
this->capabilities.insert(ItemCapability::Heal);
|
||||||
|
this->capabilities.insert(ItemCapability::Revive);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Medkit::can_use_on(Unit *used_by, gd::Object *used_on) const {
|
||||||
|
return gd::Object::cast_to<Unit>(used_on) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Lasercutter::Lasercutter()
|
||||||
|
: Item() {
|
||||||
|
this->capabilities.insert(ItemCapability::UtilityEquip);
|
||||||
|
}
|
||||||
|
|
||||||
|
Welder::Welder()
|
||||||
|
: Item() {
|
||||||
|
this->capabilities.insert(ItemCapability::UtilityEquip);
|
||||||
|
}
|
46
src/rts_items.hpp
Normal file
46
src/rts_items.hpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#ifndef RTS_ITEMS_HPP
|
||||||
|
#define RTS_ITEMS_HPP
|
||||||
|
|
||||||
|
#include "item.hpp"
|
||||||
|
|
||||||
|
class Medkit : public Item {
|
||||||
|
ITEM_CLASS(Medkit, "Medkit",
|
||||||
|
"Standard emergency home medical kit. Use to manage wounds and stabilize a person.");
|
||||||
|
public:
|
||||||
|
Medkit();
|
||||||
|
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.");
|
||||||
|
public:
|
||||||
|
Handgun();
|
||||||
|
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 Lasercutter : public Item {
|
||||||
|
ITEM_CLASS(Lasercutter, "Lasercutter",
|
||||||
|
"A laser cutter, use to clear metal obstacles.");
|
||||||
|
public:
|
||||||
|
Lasercutter();
|
||||||
|
// virtual bool can_use_on(Unit *used_by, gd::Object *object) const override;
|
||||||
|
protected:
|
||||||
|
// virtual void use_on(Unit *used_by, gd::Object *object) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
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.");
|
||||||
|
public:
|
||||||
|
Welder();
|
||||||
|
// virtual bool can_use_on(Unit *used_by, gd::Object *object) const override;
|
||||||
|
protected:
|
||||||
|
// virtual void use_on(Unit *used_by, gd::Object *object) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // !RTS_ITEMS_HPP
|
15
src/stats.cpp
Normal file
15
src/stats.cpp
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#include "stats.hpp"
|
||||||
|
#include <godot_cpp/core/math.hpp>
|
||||||
|
|
||||||
|
namespace gd = godot;
|
||||||
|
|
||||||
|
Stats operator &(Stats const &lhs, Stats const &rhs) {
|
||||||
|
Stats rval{};
|
||||||
|
rval.damage_absorb = gd::Math::min(lhs.damage_absorb + rhs.damage_absorb, 1.f);
|
||||||
|
rval.hazmat_level = gd::Math::max(lhs.hazmat_level, rhs.hazmat_level);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stats &operator <<(Stats &lhs, Stats const &rhs) {
|
||||||
|
return (lhs = lhs & rhs);
|
||||||
|
}
|
17
src/stats.hpp
Normal file
17
src/stats.hpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef STATS_HPP
|
||||||
|
#define STATS_HPP
|
||||||
|
|
||||||
|
#include "utils/godot_macros.hpp"
|
||||||
|
#include <godot_cpp/templates/hash_set.hpp>
|
||||||
|
|
||||||
|
namespace gd = godot;
|
||||||
|
|
||||||
|
struct Stats {
|
||||||
|
float damage_absorb{0.f};
|
||||||
|
int hazmat_level{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
Stats operator &(Stats const &lhs, Stats const &rhs);
|
||||||
|
Stats &operator <<(Stats &lhs, Stats const &rhs);
|
||||||
|
|
||||||
|
#endif // !STATS_HPP
|
19
src/unit.cpp
19
src/unit.cpp
|
@ -29,6 +29,7 @@ void Unit::_enter_tree() { GDGAMEONLY();
|
||||||
this->anim_player = this->get_node<gd::AnimationPlayer>("%AnimationPlayer");
|
this->anim_player = this->get_node<gd::AnimationPlayer>("%AnimationPlayer");
|
||||||
this->health = this->get_node<EntityHealth>("%EntityHealth");
|
this->health = this->get_node<EntityHealth>("%EntityHealth");
|
||||||
this->health->connect("death", callable_mp(this, &Unit::on_death));
|
this->health->connect("death", callable_mp(this, &Unit::on_death));
|
||||||
|
this->inventory = this->get_node<Inventory>("%Inventory");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::_physics_process(double) { GDGAMEONLY();
|
void Unit::_physics_process(double) { GDGAMEONLY();
|
||||||
|
@ -78,15 +79,15 @@ void Unit::begin_goal(gd::Ref<goap::Goal> goal) {
|
||||||
|
|
||||||
void Unit::use_weapon() {
|
void Unit::use_weapon() {
|
||||||
gd::Node3D *target{this->world_state->get_target_node()};
|
gd::Node3D *target{this->world_state->get_target_node()};
|
||||||
if(target == nullptr)
|
Item const *weapon{this->inventory->get_weapon()};
|
||||||
return;
|
if(weapon == nullptr) {
|
||||||
if(!this->world_state->get_can_see_target())
|
// try and do a melee attack instead when no weapon is equipped
|
||||||
return;
|
Unit *target_unit{gd::Object::cast_to<Unit>(target)};
|
||||||
this->aim_at(target);
|
if(target_unit != nullptr && this->world_state->get_can_see_target() && this->world_state->get_is_in_melee_range())
|
||||||
EntityHealth *health{target->get_node<EntityHealth>("EntityHealth")};
|
target_unit->get_entity_health()->damaged_by(1, this);
|
||||||
if(health == nullptr)
|
} else {
|
||||||
return;
|
this->inventory->get_weapon()->try_use_on(this, target);
|
||||||
health->damaged_by(1, this);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::aim_at(gd::Node3D *target) {
|
void Unit::aim_at(gd::Node3D *target) {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define RTS_UNIT_HPP
|
#define RTS_UNIT_HPP
|
||||||
|
|
||||||
#include "goal_marker.hpp"
|
#include "goal_marker.hpp"
|
||||||
|
#include "inventory.hpp"
|
||||||
#include "unit_world_state.hpp"
|
#include "unit_world_state.hpp"
|
||||||
#include "goap/goal.hpp"
|
#include "goap/goal.hpp"
|
||||||
#include "goap/planner.hpp"
|
#include "goap/planner.hpp"
|
||||||
|
@ -69,6 +70,7 @@ protected:
|
||||||
goap::Planner *planner{nullptr};
|
goap::Planner *planner{nullptr};
|
||||||
EntityHealth *health{nullptr};
|
EntityHealth *health{nullptr};
|
||||||
UnitWorldState *world_state{nullptr};
|
UnitWorldState *world_state{nullptr};
|
||||||
|
Inventory *inventory{nullptr};
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
public:
|
public:
|
||||||
gd::String DEBUG_print_debug_info();
|
gd::String DEBUG_print_debug_info();
|
||||||
|
|
Loading…
Reference in a new issue