From a024aab20328101c444de8a51772e5d6354c2d95 Mon Sep 17 00:00:00 2001 From: Sara Date: Thu, 15 Aug 2024 21:42:11 +0200 Subject: [PATCH] feat: implemented item and inventory outline --- godot/GameObjects/enemy_unit.tscn | 2 +- godot/GameObjects/player_unit.tscn | 4 ++ src/enemy_world_state.cpp | 9 ++-- src/enemy_world_state.hpp | 4 +- src/entity_health.cpp | 18 +++++-- src/entity_health.hpp | 4 ++ src/inventory.cpp | 85 ++++++++++++++++++++++++++++++ src/inventory.hpp | 47 +++++++++++++++++ src/item.cpp | 35 ++++++++++++ src/item.hpp | 70 ++++++++++++++++++++++++ src/item_db.cpp | 58 ++++++++++++++++++++ src/item_db.hpp | 34 ++++++++++++ src/nav_room.cpp | 6 +-- src/nav_room.hpp | 4 +- src/register_types.cpp | 46 +++++++++------- src/rts_items.cpp | 46 ++++++++++++++++ src/rts_items.hpp | 46 ++++++++++++++++ src/stats.cpp | 15 ++++++ src/stats.hpp | 17 ++++++ src/unit.cpp | 19 +++---- src/unit.hpp | 2 + 21 files changed, 527 insertions(+), 44 deletions(-) create mode 100644 src/inventory.cpp create mode 100644 src/inventory.hpp create mode 100644 src/item.cpp create mode 100644 src/item.hpp create mode 100644 src/item_db.cpp create mode 100644 src/item_db.hpp create mode 100644 src/rts_items.cpp create mode 100644 src/rts_items.hpp create mode 100644 src/stats.cpp create mode 100644 src/stats.hpp diff --git a/godot/GameObjects/enemy_unit.tscn b/godot/GameObjects/enemy_unit.tscn index b1408d4..549fce6 100644 --- a/godot/GameObjects/enemy_unit.tscn +++ b/godot/GameObjects/enemy_unit.tscn @@ -28,7 +28,7 @@ collision_layer = 6 collision_mask = 0 [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 [node name="Planner" type="Planner" parent="."] diff --git a/godot/GameObjects/player_unit.tscn b/godot/GameObjects/player_unit.tscn index 0ba9c3d..3700d51 100644 --- a/godot/GameObjects/player_unit.tscn +++ b/godot/GameObjects/player_unit.tscn @@ -33,6 +33,10 @@ unique_name_in_owner = true [node name="EntityHealth" type="EntityHealth" parent="."] unique_name_in_owner = true +[node name="Inventory" type="Inventory" parent="."] +weapon = 2 +unique_name_in_owner = true + [node name="NavigationAgent3D" type="NavigationAgent3D" parent="."] unique_name_in_owner = true path_desired_distance = 0.2 diff --git a/src/enemy_world_state.cpp b/src/enemy_world_state.cpp index af7a385..79e9898 100644 --- a/src/enemy_world_state.cpp +++ b/src/enemy_world_state.cpp @@ -8,10 +8,7 @@ void EnemyWorldState::_bind_methods() { #define CLASSNAME EnemyWorldState - GDPROPERTY_HINTED(editor_available_goals, - gd::Variant::ARRAY, - gd::PROPERTY_HINT_ARRAY_TYPE, - GDRESOURCETYPE("Goal")); + GDPROPERTY_HINTED(available_goals_inspector, gd::Variant::ARRAY, gd::PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE("Goal")); } void EnemyWorldState::_ready() { GDGAMEONLY(); @@ -144,13 +141,13 @@ void EnemyWorldState::try_set_target(Unit *unit) { 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(); for(int i{0}; i < array.size(); ++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{}; for(gd::Ref const &goal : this->available_goals) a.push_back(goal); diff --git a/src/enemy_world_state.hpp b/src/enemy_world_state.hpp index 59c6719..710e9c7 100644 --- a/src/enemy_world_state.hpp +++ b/src/enemy_world_state.hpp @@ -36,8 +36,8 @@ private: float calculate_priority(Unit *target); gd::Ref get_goal_for_target(Unit *unit); void try_set_target(Unit *target); - void set_editor_available_goals(gd::Array array); - gd::Array get_editor_available_goals() const; + 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> available_goals{}; diff --git a/src/entity_health.cpp b/src/entity_health.cpp index a38ff2a..25c9f0a 100644 --- a/src/entity_health.cpp +++ b/src/entity_health.cpp @@ -73,21 +73,33 @@ float EntityHealth::get_wounds_damage_factor() 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 { - 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 { 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) { this->injury_max = max_health; } diff --git a/src/entity_health.hpp b/src/entity_health.hpp index a75d918..a8871fe 100644 --- a/src/entity_health.hpp +++ b/src/entity_health.hpp @@ -1,6 +1,7 @@ #ifndef RTS_ENTITY_HEALTH_HPP #define RTS_ENTITY_HEALTH_HPP +#include "stats.hpp" #include namespace gd = godot; @@ -25,6 +26,8 @@ public: bool can_be_healed() const; bool can_be_damaged() const; bool is_dead() const; + void set_stats(Stats const *stats); + Stats const *get_stats() const; void set_injury_max(int max_health); int get_injury_max() const; int get_injury_current() const; @@ -32,6 +35,7 @@ public: int get_wounds_max() const; int get_wounds_current() const; private: + Stats const *stats; // long term health int injury_max{10}; int injury_current{0}; diff --git a/src/inventory.cpp b/src/inventory.cpp new file mode 100644 index 0000000..d58fece --- /dev/null +++ b/src/inventory.cpp @@ -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(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 const &item : this->inventory) + dict[item.key->get_class()] = item.value; + return dict; +} diff --git a/src/inventory.hpp b/src/inventory.hpp new file mode 100644 index 0000000..f8ccd64 --- /dev/null +++ b/src/inventory.hpp @@ -0,0 +1,47 @@ +#ifndef INVENTORY_HPP +#define INVENTORY_HPP + +#include "item.hpp" +#include "stats.hpp" +#include "utils/godot_macros.hpp" +#include +#include +#include +#include + +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 inventory{}; + + Unit *parent_unit{nullptr}; +}; + +#endif // !INVENTORY_HPP diff --git a/src/item.cpp b/src/item.cpp new file mode 100644 index 0000000..112ce97 --- /dev/null +++ b/src/item.cpp @@ -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; +} diff --git a/src/item.hpp b/src/item.hpp new file mode 100644 index 0000000..752cad6 --- /dev/null +++ b/src/item.hpp @@ -0,0 +1,70 @@ +#ifndef ITEM_HPP +#define ITEM_HPP + +#include "stats.hpp" +#include "goap/action.hpp" +#include "utils/godot_macros.hpp" +#include +#include + +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 ""; + } + 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 ""; + } + 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 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 diff --git a/src/item_db.cpp b/src/item_db.cpp new file mode 100644 index 0000000..ef14a57 --- /dev/null +++ b/src/item_db.cpp @@ -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{}; diff --git a/src/item_db.hpp b/src/item_db.hpp new file mode 100644 index 0000000..8d8b1f4 --- /dev/null +++ b/src/item_db.hpp @@ -0,0 +1,34 @@ +#ifndef ITEM_DB_HPP +#define ITEM_DB_HPP + +#include "item.hpp" +#include + +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 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 + _FORCE_INLINE_ static ItemID register_item() { + return ItemDB::register_item(new TItem(), TItem::get_static_class()); + } +private: + static StaticData data; +}; + +#endif // !ITEM_DB_HPP diff --git a/src/nav_room.cpp b/src/nav_room.cpp index 811729a..4a94f17 100644 --- a/src/nav_room.cpp +++ b/src/nav_room.cpp @@ -6,7 +6,7 @@ void NavRoom::_bind_methods() { #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) { @@ -44,14 +44,14 @@ gd::Vector const &NavRoom::get_markers() const { return this->markers; } -gd::Array NavRoom::get_editor_neighbours() const { +gd::Array NavRoom::get_neighbours_inspector() const { gd::Array a{}; for(NavRoom *room : this->neighbours) a.push_back(room); return a; } -void NavRoom::set_editor_neighbours(gd::Array array) { +void NavRoom::set_neighbours_inspector(gd::Array array) { this->neighbours.clear(); while(!array.is_empty()) if(NavRoom *room{gd::Object::cast_to(array.pop_front())}) this->neighbours.push_back(room); diff --git a/src/nav_room.hpp b/src/nav_room.hpp index 24f55e8..eb5ef19 100644 --- a/src/nav_room.hpp +++ b/src/nav_room.hpp @@ -19,8 +19,8 @@ public: gd::Vector const &get_neighbours() const; gd::Vector const &get_markers() const; private: - void set_editor_neighbours(gd::Array array); - gd::Array get_editor_neighbours() const; + 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}; diff --git a/src/register_types.cpp b/src/register_types.cpp index dbc9350..e0f5a2f 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -1,11 +1,13 @@ #include "register_types.h" #include "enemy_world_state.hpp" #include "entity_health.hpp" -#include "goap/state.hpp" +#include "inventory.hpp" +#include "item_db.hpp" #include "nav_marker.hpp" #include "nav_room.hpp" #include "rts_actions.hpp" #include "rts_game_mode.hpp" +#include "rts_items.hpp" #include "rts_player.hpp" #include "rts_states.hpp" #include "unit.hpp" @@ -15,6 +17,7 @@ #include "goap/actor_world_state.hpp" #include "goap/goal.hpp" #include "goap/planner.hpp" +#include "goap/state.hpp" #include "utils/register_types.hpp" #include #include @@ -31,7 +34,7 @@ void initialize_gdextension_types(gd::ModuleInitializationLevel p_level) utils::godot_cpp_utils_register_types(); // 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(); goap::ActionDB::register_action(); goap::ActionDB::register_action(); @@ -40,24 +43,31 @@ void initialize_gdextension_types(gd::ModuleInitializationLevel p_level) goap::ActionDB::register_action(); goap::ActionDB::register_action(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); + // same for items, + // make sure ItemDB::get_enum_hint is fully populated out before _bind_methods. + ItemDB::register_item(); + ItemDB::register_item(); + ItemDB::register_item(); + ItemDB::register_item(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); + GDREGISTER_CLASS(goap::ActorWorldState); + GDREGISTER_CLASS(goap::Goal); + GDREGISTER_CLASS(goap::Planner); + GDREGISTER_CLASS(goap::State); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); - gd::ClassDB::register_class(); + GDREGISTER_CLASS(MoveTo); + GDREGISTER_CLASS(Animate); + GDREGISTER_CLASS(Activate); + GDREGISTER_CLASS(UnitWorldState); + GDREGISTER_CLASS(EnemyWorldState); + GDREGISTER_CLASS(GoalMarker); + GDREGISTER_CLASS(Unit); + GDREGISTER_CLASS(RTSGameMode); + GDREGISTER_CLASS(RTSPlayer); + GDREGISTER_CLASS(EntityHealth); + GDREGISTER_CLASS(NavMarker); + GDREGISTER_CLASS(NavRoom); + GDREGISTER_CLASS(Inventory); } extern "C" diff --git a/src/rts_items.cpp b/src/rts_items.cpp new file mode 100644 index 0000000..c95e85e --- /dev/null +++ b/src/rts_items.cpp @@ -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(used_on) != nullptr; +} + +void Medkit::use_on(Unit *used_by, gd::Object *used_on) const { + gd::Object::cast_to(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(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(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); +} diff --git a/src/rts_items.hpp b/src/rts_items.hpp new file mode 100644 index 0000000..e8dc2df --- /dev/null +++ b/src/rts_items.hpp @@ -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 diff --git a/src/stats.cpp b/src/stats.cpp new file mode 100644 index 0000000..76f1156 --- /dev/null +++ b/src/stats.cpp @@ -0,0 +1,15 @@ +#include "stats.hpp" +#include + +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); +} diff --git a/src/stats.hpp b/src/stats.hpp new file mode 100644 index 0000000..89697cf --- /dev/null +++ b/src/stats.hpp @@ -0,0 +1,17 @@ +#ifndef STATS_HPP +#define STATS_HPP + +#include "utils/godot_macros.hpp" +#include + +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 diff --git a/src/unit.cpp b/src/unit.cpp index bdb23dd..3a083fe 100644 --- a/src/unit.cpp +++ b/src/unit.cpp @@ -29,6 +29,7 @@ void Unit::_enter_tree() { GDGAMEONLY(); this->anim_player = this->get_node("%AnimationPlayer"); this->health = this->get_node("%EntityHealth"); this->health->connect("death", callable_mp(this, &Unit::on_death)); + this->inventory = this->get_node("%Inventory"); } void Unit::_physics_process(double) { GDGAMEONLY(); @@ -78,15 +79,15 @@ void Unit::begin_goal(gd::Ref goal) { void Unit::use_weapon() { gd::Node3D *target{this->world_state->get_target_node()}; - if(target == nullptr) - return; - if(!this->world_state->get_can_see_target()) - return; - this->aim_at(target); - EntityHealth *health{target->get_node("EntityHealth")}; - if(health == nullptr) - return; - health->damaged_by(1, this); + Item const *weapon{this->inventory->get_weapon()}; + if(weapon == nullptr) { + // try and do a melee attack instead when no weapon is equipped + Unit *target_unit{gd::Object::cast_to(target)}; + if(target_unit != nullptr && this->world_state->get_can_see_target() && this->world_state->get_is_in_melee_range()) + target_unit->get_entity_health()->damaged_by(1, this); + } else { + this->inventory->get_weapon()->try_use_on(this, target); + } } void Unit::aim_at(gd::Node3D *target) { diff --git a/src/unit.hpp b/src/unit.hpp index 6f46824..cc154d0 100644 --- a/src/unit.hpp +++ b/src/unit.hpp @@ -2,6 +2,7 @@ #define RTS_UNIT_HPP #include "goal_marker.hpp" +#include "inventory.hpp" #include "unit_world_state.hpp" #include "goap/goal.hpp" #include "goap/planner.hpp" @@ -69,6 +70,7 @@ protected: goap::Planner *planner{nullptr}; EntityHealth *health{nullptr}; UnitWorldState *world_state{nullptr}; + Inventory *inventory{nullptr}; #ifdef DEBUG_ENABLED public: gd::String DEBUG_print_debug_info();