feat: implemented item and inventory outline

This commit is contained in:
Sara 2024-08-15 21:42:11 +02:00
parent f771b383f3
commit a024aab203
21 changed files with 527 additions and 44 deletions

View file

@ -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="."]

View file

@ -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

View file

@ -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);

View file

@ -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{};

View file

@ -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;
} }

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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

View file

@ -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);

View file

@ -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};

View file

@ -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
View 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
View 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
View 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
View 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

View file

@ -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) {

View file

@ -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();