From f2156e51b2e33fe51e8b61b4bf85fb36a96a2c8a Mon Sep 17 00:00:00 2001 From: octod Date: Sat, 20 Jan 2024 12:09:52 +0100 Subject: [PATCH] feat(wip): initial work on item/inventory/equipment --- .../item_test_ui/items/test_item_000.tres | 6 + .../item_test_ui/items/test_item_001.tres | 6 + .../items/test_item_settings.tres | 6 + project/demos/item_test_ui/main.gd | 8 + project/demos/item_test_ui/main.tscn | 12 + project/new_tag_dictionary.tres | 2 +- .../main_scene/ggs_main_scene.cpp | 21 +- src/editor_plugin/main_scene/ggs_main_scene.h | 6 - .../main_scene/item/ggs_item_main_scene.cpp | 47 +++ .../main_scene/item/ggs_item_main_scene.h | 26 ++ src/register_types.cpp | 44 +- src/system/item/Item.cpp | 118 ------ src/system/item/Item.h | 103 ----- src/system/item/equipment.cpp | 66 +++ src/system/item/equipment.h | 69 ++++ src/system/item/equipment_manager.cpp | 170 ++++++++ src/system/item/equipment_manager.h | 60 +++ .../item/equipment_project_settings.cpp | 96 +++++ src/system/item/equipment_project_settings.h | 34 ++ src/system/item/equipment_slot.cpp | 87 ++++ src/system/item/equipment_slot.h | 52 +++ src/system/item/inventory.cpp | 350 ++++++++++++++++ src/system/item/inventory.h | 146 +++++++ src/system/item/item.cpp | 385 ++++++++++++++++++ src/system/item/item.h | 221 ++++++++++ src/system/item/item_manager.cpp | 214 ++++++++++ src/system/item/item_manager.h | 71 ++++ src/system/item/item_project_settings.cpp | 97 +++++ src/system/item/item_project_settings.h | 36 ++ src/system/item/items_pool.cpp | 48 +++ src/system/item/items_pool.h | 29 ++ 31 files changed, 2396 insertions(+), 240 deletions(-) create mode 100644 project/demos/item_test_ui/items/test_item_000.tres create mode 100644 project/demos/item_test_ui/items/test_item_001.tres create mode 100644 project/demos/item_test_ui/items/test_item_settings.tres create mode 100644 project/demos/item_test_ui/main.gd create mode 100644 project/demos/item_test_ui/main.tscn create mode 100644 src/editor_plugin/main_scene/item/ggs_item_main_scene.cpp create mode 100644 src/editor_plugin/main_scene/item/ggs_item_main_scene.h delete mode 100644 src/system/item/Item.cpp delete mode 100644 src/system/item/Item.h create mode 100644 src/system/item/equipment.cpp create mode 100644 src/system/item/equipment.h create mode 100644 src/system/item/equipment_manager.cpp create mode 100644 src/system/item/equipment_manager.h create mode 100644 src/system/item/equipment_project_settings.cpp create mode 100644 src/system/item/equipment_project_settings.h create mode 100644 src/system/item/equipment_slot.cpp create mode 100644 src/system/item/equipment_slot.h create mode 100644 src/system/item/inventory.cpp create mode 100644 src/system/item/inventory.h create mode 100644 src/system/item/item.cpp create mode 100644 src/system/item/item.h create mode 100644 src/system/item/item_manager.cpp create mode 100644 src/system/item/item_manager.h create mode 100644 src/system/item/item_project_settings.cpp create mode 100644 src/system/item/item_project_settings.h create mode 100644 src/system/item/items_pool.cpp create mode 100644 src/system/item/items_pool.h diff --git a/project/demos/item_test_ui/items/test_item_000.tres b/project/demos/item_test_ui/items/test_item_000.tres new file mode 100644 index 0000000..366be19 --- /dev/null +++ b/project/demos/item_test_ui/items/test_item_000.tres @@ -0,0 +1,6 @@ +[gd_resource type="Item" load_steps=2 format=3 uid="uid://cmuoe6r8lr7g"] + +[ext_resource type="ItemSettings" uid="uid://bcdwpcf5776td" path="res://demos/item_test_ui/items/test_item_settings.tres" id="1_xmfv7"] + +[resource] +item_settings = ExtResource("1_xmfv7") diff --git a/project/demos/item_test_ui/items/test_item_001.tres b/project/demos/item_test_ui/items/test_item_001.tres new file mode 100644 index 0000000..0a05b53 --- /dev/null +++ b/project/demos/item_test_ui/items/test_item_001.tres @@ -0,0 +1,6 @@ +[gd_resource type="Item" load_steps=2 format=3 uid="uid://d27wbmd1lsxtp"] + +[ext_resource type="ItemSettings" uid="uid://bcdwpcf5776td" path="res://demos/item_test_ui/items/test_item_settings.tres" id="1_n6u0y"] + +[resource] +item_settings = ExtResource("1_n6u0y") diff --git a/project/demos/item_test_ui/items/test_item_settings.tres b/project/demos/item_test_ui/items/test_item_settings.tres new file mode 100644 index 0000000..4afcce8 --- /dev/null +++ b/project/demos/item_test_ui/items/test_item_settings.tres @@ -0,0 +1,6 @@ +[gd_resource type="ItemSettings" format=3 uid="uid://bcdwpcf5776td"] + +[resource] +decrease_quantity_on_use = true +is_stackable = true +max_quantity = 10 diff --git a/project/demos/item_test_ui/main.gd b/project/demos/item_test_ui/main.gd new file mode 100644 index 0000000..a7c0480 --- /dev/null +++ b/project/demos/item_test_ui/main.gd @@ -0,0 +1,8 @@ +extends Control + + +func _ready() -> void: + print(ItemManager.get_items()) + print(EquipmentManager.get_slots()) + pass + diff --git a/project/demos/item_test_ui/main.tscn b/project/demos/item_test_ui/main.tscn new file mode 100644 index 0000000..eb3cdff --- /dev/null +++ b/project/demos/item_test_ui/main.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://c7kl1lpd5o5ps"] + +[ext_resource type="Script" path="res://demos/item_test_ui/main.gd" id="1_poj3d"] + +[node name="Main" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_poj3d") diff --git a/project/new_tag_dictionary.tres b/project/new_tag_dictionary.tres index 7aaf7d1..52dc3bd 100644 --- a/project/new_tag_dictionary.tres +++ b/project/new_tag_dictionary.tres @@ -1,4 +1,4 @@ [gd_resource type="TagDictionary" format=3 uid="uid://dh8udsjb5navh"] [resource] -tags = PackedStringArray("enemy.ai.idle", "enemy.ai.roam", "enemy.ai.roam.end", "enemy.ai.roam.start", "enemy.ai.roam.idle", "enemy.ai.combat.chasing", "enemy.ai.combat.low_health", "enemy.ai.combat.panic", "enemy.factions", "enemy.factions.000", "enemy.factions.001", "enemy.factions.002") +tags = PackedStringArray("enemy.ai.idle", "enemy.ai.roam", "enemy.ai.roam.end", "enemy.ai.roam.start", "enemy.ai.roam.idle", "enemy.ai.combat.chasing", "enemy.ai.combat.low_health", "enemy.ai.combat.panic", "enemy.factions", "enemy.factions.enemies", "enemy.factions.neutrals", "enemy.factions.friends") diff --git a/src/editor_plugin/main_scene/ggs_main_scene.cpp b/src/editor_plugin/main_scene/ggs_main_scene.cpp index 5a801d7..cd8b5ff 100644 --- a/src/editor_plugin/main_scene/ggs_main_scene.cpp +++ b/src/editor_plugin/main_scene/ggs_main_scene.cpp @@ -1,6 +1,11 @@ +#include "ggs_main_scene.h" + #include "attribute/ggs_attribute_main_scene.h" #include "tag/ggs_tag_main_scene.h" -#include "ggs_main_scene.h" +#include "item/ggs_item_main_scene.h" + +#include +#include using namespace ggs; using namespace ggs::editor_plugin; @@ -15,10 +20,11 @@ MainScene::~MainScene() void MainScene::_ready() { - ability_panel = memnew(Panel); - attributes_panel = memnew(Panel); - tab_container = memnew(TabContainer); - tag_manager_panel = memnew(Panel); + Panel *ability_panel = memnew(Panel); + Panel *attributes_panel = memnew(Panel); + Panel *item_panel = memnew(Panel); + TabContainer *tab_container = memnew(TabContainer); + Panel *tag_manager_panel = memnew(Panel); tab_container->set_anchors_and_offsets_preset(LayoutPreset::PRESET_FULL_RECT); @@ -26,21 +32,26 @@ void MainScene::_ready() tab_container->add_child(tag_manager_panel); tab_container->add_child(attributes_panel); + tab_container->add_child(item_panel); // commented. Will come with a visual ability designer in the future. // tab_container->add_child(ability_panel); tag_manager_panel->set_name(tag_manager_panel->tr("Tag Manager")); attributes_panel->set_name(attributes_panel->tr("Attributes")); + item_panel->set_name(item_panel->tr("Items & Equipment")); // commented. Will come with a visual ability designer in the future. // ability_panel->set_name(ability_panel->tr("Ability")); AttributeMainScene *attribute_main_scene = memnew(AttributeMainScene); TagMainScene *tag_main_scene = memnew(TagMainScene); + ItemMainScene *item_main_scene = memnew(ItemMainScene); attributes_panel->add_child(attribute_main_scene); tag_manager_panel->add_child(tag_main_scene); + item_panel->add_child(item_main_scene); } void MainScene::_bind_methods() { + /// BANANA SPLITS HERE } diff --git a/src/editor_plugin/main_scene/ggs_main_scene.h b/src/editor_plugin/main_scene/ggs_main_scene.h index 1de21bb..c35e3cf 100644 --- a/src/editor_plugin/main_scene/ggs_main_scene.h +++ b/src/editor_plugin/main_scene/ggs_main_scene.h @@ -1,9 +1,7 @@ #ifndef GGS_MAIN_SCENE_H #define GGS_MAIN_SCENE_H -#include #include -#include using namespace godot; @@ -28,10 +26,6 @@ namespace ggs::editor_plugin protected: static void _bind_methods(); - TabContainer *tab_container; - Panel *ability_panel; - Panel *attributes_panel; - Panel *tag_manager_panel; }; } diff --git a/src/editor_plugin/main_scene/item/ggs_item_main_scene.cpp b/src/editor_plugin/main_scene/item/ggs_item_main_scene.cpp new file mode 100644 index 0000000..a49cdef --- /dev/null +++ b/src/editor_plugin/main_scene/item/ggs_item_main_scene.cpp @@ -0,0 +1,47 @@ +#include "ggs_item_main_scene.h" + +#include +#include +#include +#include + +using namespace ggs; +using namespace ggs::editor_plugin; + +void ItemMainScene::_handle_tab_pressed(int tab_id) +{ +} + +void ItemMainScene::_bind_methods() +{ +} + +void ItemMainScene::_ready() +{ + Button *equipment_button = memnew(Button); + Button *items_button = memnew(Button); + VBoxContainer *tab_buttons = memnew(VBoxContainer); + VBoxContainer *tabs_container = memnew(VBoxContainer); + Panel *tabs_panel = memnew(Panel); + Panel *items_panel = memnew(Panel); + Panel *equipment_panel = memnew(Panel); + + equipment_button->set_text(tr("Equipment")); + items_button->set_text(tr("Items")); + + tab_buttons->set_name("GGS_ITEMS_TabButtons"); + + tab_buttons->add_child(equipment_button); + tab_buttons->add_child(items_button); + + tabs_container->add_child(equipment_panel); + tabs_container->add_child(items_panel); + tabs_container->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + + tabs_panel->add_child(tabs_container); + + add_child(tab_buttons); + add_child(tabs_panel); + + set_anchors_and_offsets_preset(PRESET_FULL_RECT); +} diff --git a/src/editor_plugin/main_scene/item/ggs_item_main_scene.h b/src/editor_plugin/main_scene/item/ggs_item_main_scene.h new file mode 100644 index 0000000..56ad5e1 --- /dev/null +++ b/src/editor_plugin/main_scene/item/ggs_item_main_scene.h @@ -0,0 +1,26 @@ +#ifndef GGS_ITEM_MAIN_SCENE_H +#define GGS_ITEM_MAIN_SCENE_H + +#include + +using namespace godot; + +namespace ggs::editor_plugin +{ + class ItemMainScene : public HBoxContainer + { + GDCLASS(ItemMainScene, HBoxContainer); + /// @brief Handles the tab button being pressed. + /// @param tab_id The tab id of the button that was pressed. + void _handle_tab_pressed(int tab_id); + + protected: + /// @brief Bind methods to Godot. + static void _bind_methods(); + /// @brief Called when the node enters the scene tree for the first time. + public: + void _ready() override; + }; +} + +#endif diff --git a/src/register_types.cpp b/src/register_types.cpp index 55a92fe..42a5d19 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -22,6 +22,14 @@ #include "system/attribute/attribute_project_settings.h" #include "system/attribute/gameplay_effect.h" #include "system/attribute/gameplay_effect_stacking_behavior.h" +#include "system/item/equipment.h" +#include "system/item/equipment_manager.h" +#include "system/item/equipment_project_settings.h" +#include "system/item/item.h" +#include "system/item/item_manager.h" +#include "system/item/item_project_settings.h" +#include "system/item/items_pool.h" +#include "system/item/inventory.h" #include "system/tag/tag_dictionary.h" #include "system/tag/tag_manager.h" #include "system/tag/tag_project_settings.h" @@ -35,6 +43,7 @@ #include "editor_plugin/ggs_editor_plugin.h" #include "editor_plugin/main_scene/ggs_main_scene.h" #include "editor_plugin/main_scene/attribute/ggs_attribute_main_scene.h" +#include "editor_plugin/main_scene/item/ggs_item_main_scene.h" #include "editor_plugin/main_scene/tag/ggs_tag_dictionary_item.h" #include "editor_plugin/main_scene/tag/ggs_tag_main_scene.h" @@ -53,36 +62,55 @@ void initialize_ggs_module(ModuleInitializationLevel p_level) ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_internal_class(); + + /// registering internal classes ClassDB::register_internal_class(); + ClassDB::register_internal_class(); + ClassDB::register_internal_class(); + ClassDB::register_internal_class(); + ClassDB::register_internal_class(); /// registering settings ggs::AttributeProjectSettings::setup(); + ggs::EquipmentProjectSettings::setup(); + ggs::ItemProjectSettings::setup(); ggs::TagProjectSettings::setup(); /// enabling autoloads - Engine::get_singleton()->register_singleton("AttributeManager", memnew(ggs::AttributeManager)); - Engine::get_singleton()->register_singleton("TagManager", memnew(ggs::TagManager)); + Engine::get_singleton()->register_singleton("AttributeManager", ggs::AttributeManager::get_singleton()); + Engine::get_singleton()->register_singleton("EquipmentManager", ggs::EquipmentManager::get_singleton()); + Engine::get_singleton()->register_singleton("ItemManager", ggs::ItemManager::get_singleton()); + Engine::get_singleton()->register_singleton("TagManager", ggs::TagManager::get_singleton()); } else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { - ClassDB::register_internal_class(); ClassDB::register_internal_class(); - ClassDB::register_internal_class(); + ClassDB::register_internal_class(); ClassDB::register_internal_class(); + ClassDB::register_internal_class(); ClassDB::register_internal_class(); ClassDB::register_internal_class(); + ClassDB::register_internal_class(); ClassDB::register_internal_class(); ClassDB::register_internal_class(); ClassDB::register_internal_class(); @@ -96,6 +124,8 @@ void uninitialize_ggs_module(ModuleInitializationLevel p_level) if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { Engine::get_singleton()->unregister_singleton("AttributeManager"); + Engine::get_singleton()->unregister_singleton("EquipmentManager"); + Engine::get_singleton()->unregister_singleton("ItemManager"); Engine::get_singleton()->unregister_singleton("TagManager"); } else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) diff --git a/src/system/item/Item.cpp b/src/system/item/Item.cpp deleted file mode 100644 index 4210649..0000000 --- a/src/system/item/Item.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include - -#include "Item.h" - -using namespace ggs; - -void ItemStack::_bind_methods() -{ - /// methods binding - ClassDB::bind_method(D_METHOD("get_item"), &ItemStack::get_item); - ClassDB::bind_method(D_METHOD("get_max_quantity"), &ItemStack::get_max_quantity); - ClassDB::bind_method(D_METHOD("get_quantity"), &ItemStack::get_quantity); - ClassDB::bind_method(D_METHOD("set_item", "p_item"), &ItemStack::set_item); - ClassDB::bind_method(D_METHOD("set_max_quantity", "p_max_quantity"), &ItemStack::set_max_quantity); - ClassDB::bind_method(D_METHOD("set_quantity", "p_quantity"), &ItemStack::set_quantity); - - /// properties binding - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "Item"), "set_item", "get_item"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "max_quantity"), "set_max_quantity", "get_max_quantity"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "quantity"), "set_quantity", "get_quantity"); -} - -Ref ItemStack::get_item() const -{ - return Ref(item); -} - -int ItemStack::get_max_quantity() const -{ - return 0; -} - -void ItemStack::set_item(Item *p_item) -{ - item = p_item; -} - -void ItemStack::set_max_quantity(int p_max_quantity) -{ -} - -int ItemStack::get_quantity() const -{ - return quantity; -} - -void ItemStack::set_quantity(int p_quantity) -{ - quantity = p_quantity; -} - -void ItemStackSetting::_bind_methods() -{ - /// methods binding - ClassDB::bind_method(D_METHOD("get_stacking_type"), &ItemStackSetting::get_stacking_type); - ClassDB::bind_method(D_METHOD("get_maximum_stack_size"), &ItemStackSetting::get_maximum_stack_size); - ClassDB::bind_method(D_METHOD("set_maximum_stack_size", "p_maximum_stack_size"), &ItemStackSetting::set_maximum_stack_size); - ClassDB::bind_method(D_METHOD("set_stacking_type", "p_stacking_type"), &ItemStackSetting::set_stacking_type); - - /// properties binding - ADD_PROPERTY(PropertyInfo(Variant::INT, "stacking_type", PROPERTY_HINT_ENUM, "Finite,Infinite"), "set_stacking_type", "get_stacking_type"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "maximum_stack_size"), "set_maximum_stack_size", "get_maximum_stack_size"); -} - -int ItemStackSetting::get_maximum_stack_size() const -{ - return maximum_stack_size; -} - -ItemStackSetting::StackingType ItemStackSetting::get_stacking_type() const -{ - return (ItemStackSetting::StackingType)stacking_type; -} - -void Item::_bind_methods() -{ - /// methods binding - ClassDB::bind_method(D_METHOD("get_stack_setting"), &Item::get_stack_setting); - ClassDB::bind_method(D_METHOD("set_stack_setting", "p_stack_setting"), &Item::set_stack_setting); - - /// properties binding - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stack_setting", PROPERTY_HINT_RESOURCE_TYPE, "ItemStackSetting"), "set_stack_setting", "get_stack_setting"); -} - -ItemStackSetting *Item::get_stack_setting() const -{ - return stack_setting; -} - -void ItemStackSetting::set_maximum_stack_size(int p_maximum_stack_size) -{ - maximum_stack_size = p_maximum_stack_size; - - if (Engine::get_singleton()->is_editor_hint()) - { - emit_changed(); - } -} - -void ItemStackSetting::set_stacking_type(ItemStackSetting::StackingType p_stacking_type) -{ - stacking_type = p_stacking_type; - - if (Engine::get_singleton()->is_editor_hint()) - { - emit_changed(); - } -} - -void Item::set_stack_setting(ItemStackSetting *p_stack_setting) -{ - stack_setting = p_stack_setting; - - if (Engine::get_singleton()->is_editor_hint()) - { - emit_changed(); - } -} diff --git a/src/system/item/Item.h b/src/system/item/Item.h deleted file mode 100644 index ccb7e82..0000000 --- a/src/system/item/Item.h +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef GGS_ITEM_H -#define GGS_ITEM_H - -#include -#include - -using namespace godot; - -namespace ggs -{ - class Item; - - class ItemStack : public RefCounted - { - GDCLASS(ItemStack, RefCounted); - - protected: - /// @brief Binds methods to Godot. - static void _bind_methods(); - /// @brief The item of the stack. - Item *item; - /// @brief The maximum quantity of the item. - int max_quantity; - /// @brief The quantity of the item. - int quantity; - - public: - /// @brief Gets the item of the stack. - /// @return The item of the stack. - Ref get_item() const; - /// @brief Gets the maximum quantity of the item. - /// @return The maximum quantity of the item. - int get_max_quantity() const; - /// @brief Gets the quantity of the item. - /// @return The quantity of the item. - int get_quantity() const; - /// @brief Sets the item of the stack. - /// @param p_item The item of the stack. - void set_item(Item *p_item); - /// @brief Sets the maximum quantity of the item. - /// @param p_max_quantity The maximum quantity of the item. - void set_max_quantity(int p_max_quantity); - /// @brief Sets the quantity of the item. - /// @param p_quantity The quantity of the item. - void set_quantity(int p_quantity); - }; - - class ItemStackSetting : public Resource - { - GDCLASS(ItemStackSetting, Resource); - - protected: - /// @brief Binds methods to Godot. - static void _bind_methods(); - /// @brief The maximum stack size. - int maximum_stack_size; - /// @brief The stacking type. - int stacking_type; - - public: - enum StackingType - { - FINITE, - INFINITE - }; - - /// @brief Gets the maximum stack size. - /// @return The maximum stack size. - int get_maximum_stack_size() const; - /// @brief Gets the stacking type. - /// @return The stacking type. - StackingType get_stacking_type() const; - /// @brief Sets the maximum stack size. - /// @param p_maximum_stack_size The maximum stack size. - void set_maximum_stack_size(int p_maximum_stack_size); - /// @brief Sets the stacking type. - /// @param p_stacking_type The stacking type. - void set_stacking_type(StackingType p_stacking_type); - }; - - class Item : public Resource - { - GDCLASS(Item, Resource); - - protected: - /// @brief Binds methods to Godot. - static void _bind_methods(); - /// @brief The stack setting of the item. - ItemStackSetting *stack_setting; - - public: - /// @brief Gets the stack setting of the item. - /// @return The stack setting of the item. - ItemStackSetting *get_stack_setting() const; - /// @brief Sets the stack setting of the item. - /// @param p_stack_setting The stack setting of the item. - void set_stack_setting(ItemStackSetting *p_stack_setting); - }; -} - -VARIANT_ENUM_CAST(ggs::ItemStackSetting::StackingType); - -#endif diff --git a/src/system/item/equipment.cpp b/src/system/item/equipment.cpp new file mode 100644 index 0000000..7adf4f9 --- /dev/null +++ b/src/system/item/equipment.cpp @@ -0,0 +1,66 @@ +#include "equipment.h" +#include "equipment_slot.h" + +using namespace ggs; + +void Equipment::_bind_methods() +{ +} + +bool Equipment::can_equip(Ref p_item, String p_slot) const +{ + return false; +} + +bool Equipment::can_unequip(String p_slot) const +{ + return false; +} + +Item *Equipment::equip(Ref p_item, String p_slot) +{ + return nullptr; +} + +Node *Equipment::get_equipment_owner() const +{ + return nullptr; +} + +PackedStringArray Equipment::get_slots() const +{ + PackedStringArray slots = PackedStringArray(); + + for (int i = 0; i < slots.size(); i++) + { + Variant slot_variant = slots[i]; + EquipmentSlot *slot = cast_to(slot_variant); + + slots.append(slot->get_slot_name()); + } + + return slots; +} + +bool Equipment::is_equipped(Ref p_item) const +{ + return false; +} + +bool Equipment::is_slot_occupied(String p_slot) const +{ + return false; +} + +void Equipment::set_equipment_owner(Node *p_equipment_owner) +{ +} + +void Equipment::set_slots(PackedStringArray p_slots) +{ +} + +bool Equipment::unequip(String p_slot) +{ + return false; +} diff --git a/src/system/item/equipment.h b/src/system/item/equipment.h new file mode 100644 index 0000000..a063bc0 --- /dev/null +++ b/src/system/item/equipment.h @@ -0,0 +1,69 @@ +#ifndef GGS_EQUIPMENT_H +#define GGS_EQUIPMENT_H + +#include +#include + +#include "equipment_slot.h" +#include "item.h" + +using namespace godot; + +namespace ggs +{ + /// @brief The equipment node. + class Equipment : public Node + { + GDCLASS(Equipment, Node) + + protected: + /// @brief Bind methods to Godot. + static void _bind_methods(); + /// @brief The owner of the equipment. + Node *equipment_owner; + /// @brief The equipment slots. + TypedArray slots; + + public: + /// @brief Check if an item can be equipped. + /// @param p_item The item to check. + /// @param p_slot The slot to check. + /// @return Whether the item can be equipped. + bool can_equip(Ref p_item, String p_slot) const; + /// @brief Check if an item can be unequipped. + /// @param p_slot The slot to check. + /// @return Whether the item can be unequipped. + bool can_unequip(String p_slot) const; + /// @brief Equip an item. + /// @param p_item The item to equip. + /// @param p_slot The slot to equip the item to. + /// @return The previously equipped item if any. + Item *equip(Ref p_item, String p_slot); + /// @brief Get the equipment_owner of the equipment. + /// @return The equipment_owner of the equipment. + Node *get_equipment_owner() const; + /// @brief Get the equipment slots. + /// @return The equipment slots. + PackedStringArray get_slots() const; + /// @brief Check if an item is equipped. + /// @param p_item The item to check. + /// @return Whether the item is equipped. + bool is_equipped(Ref p_item) const; + /// @brief Check if a slot is occupied. + /// @param p_slot The slot to check. + /// @return Whether the slot is occupied. + bool is_slot_occupied(String p_slot) const; + /// @brief Set the equipment_owner of the equipment. + /// @param p_equipment_owner The equipment_owner of the equipment. + void set_equipment_owner(Node *p_equipment_owner); + /// @brief Set the equipment slots. + /// @param p_slots The equipment slots. + void set_slots(PackedStringArray p_slots); + /// @brief Unequip an item. + /// @param p_slot The slot to unequip the item from. + /// @return Whether the item was unequipped. + bool unequip(String p_slot); + }; +} + +#endif diff --git a/src/system/item/equipment_manager.cpp b/src/system/item/equipment_manager.cpp new file mode 100644 index 0000000..ff64b3c --- /dev/null +++ b/src/system/item/equipment_manager.cpp @@ -0,0 +1,170 @@ +#include "equipment_manager.h" +#include "equipment_project_settings.h" +#include "system/tag/tag_dictionary.h" + +#include + +using namespace ggs; + +EquipmentManager *EquipmentManager::singleton = nullptr; + +void EquipmentManager::_bind_methods() +{ + /// binds methods + ClassDB::bind_method(D_METHOD("get_slots"), &EquipmentManager::get_slots); + + /// signals bindings + ADD_SIGNAL(MethodInfo("slot_added", PropertyInfo(Variant::STRING, "slot"))); + ADD_SIGNAL(MethodInfo("slot_removed", PropertyInfo(Variant::STRING, "slot"))); + ADD_SIGNAL(MethodInfo("slot_renamed", PropertyInfo(Variant::STRING, "slot"), PropertyInfo(Variant::STRING, "new_slot"))); +} + +void EquipmentManager::add_slot(EquipmentSlot *p_slot) +{ + if (!has_slot_by_instance(p_slot)) + { + slots.append(p_slot); + emit_signal("slot_added", p_slot->get_slot_name()); + } +} + +bool EquipmentManager::has_slot_by_instance(EquipmentSlot *p_slot) const +{ + if (p_slot == nullptr) + { + return false; + } + + return has_slot(p_slot->get_slot_name()); +} + +void EquipmentManager::remove_slot(EquipmentSlot *p_slot) +{ + if (has_slot_by_instance(p_slot)) + { + int index = -1; + + for (int i = 0; i < slots.size(); i++) + { + Variant slot_variant = slots[i]; + Ref slot_ptr = cast_to(slot_variant); + + if (slot_ptr.is_valid()) + { + if (slot_ptr->get_slot_name() == p_slot->get_slot_name()) + { + index = i; + break; + } + } + } + + if (index > -1) + { + slots.remove_at(index); + emit_signal("slot_removed", p_slot->get_slot_name()); + } + } +} + +void EquipmentManager::load_slots() +{ + PackedStringArray slots_paths = EquipmentProjectSettings::get_resource_file_paths(); + ResourceLoader *resource_loader = ResourceLoader::get_singleton(); + + if (resource_loader != nullptr) + { + for (StringName slot_path : slots_paths) + { + Ref resource = resource_loader->load(slot_path); + + if (resource.is_valid()) + { + EquipmentSlot *slots_ptr = cast_to(resource.ptr()); + + if (slots_ptr != nullptr) + { + slots.append(slots_ptr); + } + } + } + } +} + +EquipmentManager::EquipmentManager() +{ + if (singleton == nullptr) + { + singleton = this; + } + else + { + slots = singleton->slots; + } +} + +EquipmentManager::~EquipmentManager() +{ + if (singleton == this) + { + memdelete(singleton); + singleton = nullptr; + } +} + +void EquipmentManager::_ready() +{ + load_slots(); +} + +bool EquipmentManager::can_accept(Item *p_item) const +{ + return false; +} + +bool EquipmentManager::has_slot(StringName p_slot_name) const +{ + for (int i = 0; i < slots.size(); i++) + { + Variant slot_variant = slots[i]; + EquipmentSlot *slot_ptr = cast_to(slot_variant); + + if (slot_ptr != nullptr) + { + if (slot_ptr->get_slot_name() == p_slot_name) + { + return true; + } + } + } + + return false; +} + +EquipmentManager *EquipmentManager::get_singleton() +{ + if (singleton == nullptr) + { + singleton = memnew(EquipmentManager); + } + + return singleton; +} + +PackedStringArray EquipmentManager::get_slots() const +{ + PackedStringArray slot_names = PackedStringArray(); + + for (int i = 0; i < slots.size(); i++) + { + Variant slot_variant = slots[i]; + EquipmentSlot *slot_ptr = cast_to(slot_variant); + + if (slot_ptr != nullptr) + { + slot_names.append(slot_ptr->get_slot_name()); + } + } + + return slot_names; +} \ No newline at end of file diff --git a/src/system/item/equipment_manager.h b/src/system/item/equipment_manager.h new file mode 100644 index 0000000..2710cd1 --- /dev/null +++ b/src/system/item/equipment_manager.h @@ -0,0 +1,60 @@ +#ifndef GGS_EQUIPMENT_MANAGER_H +#define GGS_EQUIPMENT_MANAGER_H + +#include + +#include "equipment_slot.h" + +using namespace godot; + +namespace ggs +{ + class EquipmentManager : public Node + { + GDCLASS(EquipmentManager, Node) + + protected: + /// @brief The singleton instance. + static EquipmentManager *singleton; + /// @brief Binds methods to Godot. + static void _bind_methods(); + /// @brief The slots. + TypedArray slots; + /// @brief Adds a slot. + /// @param p_slot The slot to add. + void add_slot(EquipmentSlot *p_slot); + /// @brief Checks if a slot exists. + /// @param p_slot The slot to check. + /// @return True if the slot exists, false otherwise. + bool has_slot_by_instance(EquipmentSlot *p_slot) const; + /// @brief Removes a slot. + /// @param p_slot + void remove_slot(EquipmentSlot *p_slot); + /// @brief Loads the slots. + void load_slots(); + + public: + /// @brief Default constructor. + EquipmentManager(); + /// @brief Default destructor. + ~EquipmentManager(); + /// @brief The method called when the node enters the scene tree for the first time. + void _ready() override; + /// @brief Check if the item can be accepted by a slot. + /// @param p_item The item to check. + /// @return True if the item can be accepted by a slot, false otherwise. + bool can_accept(Item *p_item) const; + /// @brief Check if the item can be accepted by a slot. + /// @param p_slot_name The slot name. + /// @return True if the slot exists, false otherwise. + bool has_slot(StringName p_slot_name) const; + /// @brief Gets the singleton instance. + /// @return The singleton instance. + static EquipmentManager *get_singleton(); + /// @brief Gets the slots. + /// @return The slots. + PackedStringArray get_slots() const; + }; +} + +#endif diff --git a/src/system/item/equipment_project_settings.cpp b/src/system/item/equipment_project_settings.cpp new file mode 100644 index 0000000..10fb705 --- /dev/null +++ b/src/system/item/equipment_project_settings.cpp @@ -0,0 +1,96 @@ +#include "equipment_project_settings.h" + +#include +#include + +using namespace ggs; + +const char *EquipmentProjectSettings::RESOURCES_PATH_KEY = "application/gameplay_systems/item_system/equipment_slots_paths"; + +void EquipmentProjectSettings::_bind_methods() +{ +} + +void EquipmentProjectSettings::add_resource(const String &p_file_path) +{ + if (Engine::get_singleton()->is_editor_hint()) + { + ProjectSettings *project_settings = ProjectSettings::get_singleton(); + + if (project_settings != nullptr) + { + PackedStringArray settings = project_settings->get_setting(RESOURCES_PATH_KEY, PackedStringArray()); + + if (!settings.has(p_file_path)) + { + settings.append(p_file_path); + } + + project_settings->set_setting(RESOURCES_PATH_KEY, settings); + project_settings->save(); + } + } +} + +void EquipmentProjectSettings::remove_resource(const String &p_file_path) +{ + if (Engine::get_singleton()->is_editor_hint()) + { + ProjectSettings *project_settings = ProjectSettings::get_singleton(); + + if (project_settings != nullptr) + { + PackedStringArray settings = project_settings->get_setting(RESOURCES_PATH_KEY, PackedStringArray()); + + int index = settings.find(p_file_path); + + if (index > -1) + { + settings.remove_at(index); + } + + project_settings->set_setting(RESOURCES_PATH_KEY, settings); + project_settings->save(); + } + } +} + +PackedStringArray EquipmentProjectSettings::get_resource_file_paths() +{ + if (ProjectSettings::get_singleton() != nullptr) + { + return ProjectSettings::get_singleton()->get_setting(RESOURCES_PATH_KEY, PackedStringArray()); + } + + return PackedStringArray(); +} + +void EquipmentProjectSettings::setup() +{ + if (Engine::get_singleton()->is_editor_hint()) + { + ProjectSettings *project_settings = ProjectSettings::get_singleton(); + + if (project_settings != nullptr) + { + PackedStringArray value; + + if (!project_settings->has_setting(RESOURCES_PATH_KEY)) + { + project_settings->set_setting(RESOURCES_PATH_KEY, value); + } + + Dictionary dictionary; + + dictionary["name"] = RESOURCES_PATH_KEY; + dictionary["default_value"] = Variant(value).get_type(); + dictionary["hint"] = PropertyHint::PROPERTY_HINT_ARRAY_TYPE; + dictionary["hint_string"] = "*.tres,*.res,*.csv"; + + project_settings->add_property_info(dictionary); + project_settings->set_initial_value(RESOURCES_PATH_KEY, value); + project_settings->set_restart_if_changed(RESOURCES_PATH_KEY, false); + project_settings->save(); + } + } +} diff --git a/src/system/item/equipment_project_settings.h b/src/system/item/equipment_project_settings.h new file mode 100644 index 0000000..5efe819 --- /dev/null +++ b/src/system/item/equipment_project_settings.h @@ -0,0 +1,34 @@ +#ifndef GGS_EQUIPMENT_SETTINGS_H +#define GGS_EQUIPMENT_SETTINGS_H + +#include + +using namespace godot; + +namespace ggs +{ + class EquipmentProjectSettings : public RefCounted + { + GDCLASS(EquipmentProjectSettings, RefCounted); + + static const char *RESOURCES_PATH_KEY; + + protected: + static void _bind_methods(); + + public: + /// @brief Adds a resource file path to the project settings. + /// @param p_file_path The resource file path to add. + static void add_resource(const String &p_file_path); + /// @brief Removes a resource file path from the project settings. + /// @param p_file_path The resource file path to remove. + static void remove_resource(const String &p_file_path); + /// @brief Gets all resource file paths from the project settings. + /// @return The resource file paths. + static PackedStringArray get_resource_file_paths(); + /// @brief Sets up the project settings. + static void setup(); + }; +} + +#endif diff --git a/src/system/item/equipment_slot.cpp b/src/system/item/equipment_slot.cpp new file mode 100644 index 0000000..f50827a --- /dev/null +++ b/src/system/item/equipment_slot.cpp @@ -0,0 +1,87 @@ +#include "equipment_slot.h" + +#include + +using namespace ggs; + +void EquipmentSlot::_bind_methods() +{ + /// binds methods + ClassDB::bind_method(D_METHOD("get_slot_name"), &EquipmentSlot::get_slot_name); + ClassDB::bind_method(D_METHOD("get_accepts_items_with_tags"), &EquipmentSlot::get_accepts_items_with_tags); + ClassDB::bind_method(D_METHOD("get_denies_items_with_tags"), &EquipmentSlot::get_denies_items_with_tags); + ClassDB::bind_method(D_METHOD("set_slot_name", "slot_name"), &EquipmentSlot::set_slot_name); + ClassDB::bind_method(D_METHOD("set_accepts_items_with_tags", "accepts_items_with_tags"), &EquipmentSlot::set_accepts_items_with_tags); + ClassDB::bind_method(D_METHOD("set_denies_items_with_tags", "denies_items_with_tags"), &EquipmentSlot::set_denies_items_with_tags); + + /// binds properties + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "slot_name"), "set_slot_name", "get_slot_name"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "accepts_items_with_tags"), "set_accepts_items_with_tags", "get_accepts_items_with_tags"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "denies_items_with_tags"), "set_denies_items_with_tags", "get_denies_items_with_tags"); +} + +bool EquipmentSlot::can_accept(Item *p_item) const +{ + for (StringName tag : accepts_items_with_tags) + { + if (!p_item->get_tags().has(tag)) + { + return false; + } + } + + for (StringName tag : denies_items_with_tags) + { + if (p_item->get_tags().has(tag)) + { + return false; + } + } + + return true; +} + +StringName EquipmentSlot::get_slot_name() const +{ + return slot_name; +} + +PackedStringArray EquipmentSlot::get_accepts_items_with_tags() const +{ + return PackedStringArray(accepts_items_with_tags); +} + +PackedStringArray EquipmentSlot::get_denies_items_with_tags() const +{ + return PackedStringArray(denies_items_with_tags); +} + +void EquipmentSlot::set_slot_name(StringName p_slot_name) +{ + slot_name = p_slot_name; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void EquipmentSlot::set_accepts_items_with_tags(PackedStringArray p_accepts_items_with_tags) +{ + accepts_items_with_tags = p_accepts_items_with_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void EquipmentSlot::set_denies_items_with_tags(PackedStringArray p_denies_items_with_tags) +{ + denies_items_with_tags = p_denies_items_with_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} diff --git a/src/system/item/equipment_slot.h b/src/system/item/equipment_slot.h new file mode 100644 index 0000000..d64b8c6 --- /dev/null +++ b/src/system/item/equipment_slot.h @@ -0,0 +1,52 @@ +#ifndef GGS_EQUIPMENT_SLOT_H +#define GGS_EQUIPMENT_SLOT_H + +#include + +#include "item.h" + +using namespace godot; + +namespace ggs +{ + class EquipmentSlot : public Resource + { + GDCLASS(EquipmentSlot, Resource); + + protected: + /// @brief Bind methods to Godot. + static void _bind_methods(); + /// @brief The name of the slot. + StringName slot_name; + /// @brief The items' tags that the slot accepts. + PackedStringArray accepts_items_with_tags; + /// @brief The items' tags that the slot denies. + PackedStringArray denies_items_with_tags; + + public: + /// @brief Check if the slot can accept an item. + /// @param p_item The item to check. + /// @return True if the slot can accept the item, false otherwise. + bool can_accept(Item *p_item) const; + /// @brief Gets the slot name. + /// @return The slot name. + StringName get_slot_name() const; + /// @brief Gets the items' tags that the slot accepts. + /// @return The items' tags that the slot accepts. + PackedStringArray get_accepts_items_with_tags() const; + /// @brief Gets the items' tags that the slot denies. + /// @return The items' tags that the slot denies. + PackedStringArray get_denies_items_with_tags() const; + /// @brief Sets the slot name. + /// @param p_slot_name The slot name. + void set_slot_name(StringName p_slot_name); + /// @brief Sets the items' tags that the slot accepts. + /// @param p_accepts_items_with_tags The items' tags that the slot accepts. + void set_accepts_items_with_tags(PackedStringArray p_accepts_items_with_tags); + /// @brief Sets the items' tags that the slot denies. + /// @param p_denies_items_with_tags The items' tags that the slot denies. + void set_denies_items_with_tags(PackedStringArray p_denies_items_with_tags); + }; +} + +#endif \ No newline at end of file diff --git a/src/system/item/inventory.cpp b/src/system/item/inventory.cpp new file mode 100644 index 0000000..36adffe --- /dev/null +++ b/src/system/item/inventory.cpp @@ -0,0 +1,350 @@ +#include "inventory.h" +#include "equipment.h" + +#include "system/tag/tag_manager.h" + +using namespace ggs; + +void InventoryItem::_bind_methods() +{ +} + +InventoryItem::InventoryItem() +{ + item_ref = Ref(); +} + +InventoryItem::InventoryItem(Item *p_item) +{ + item_ref = Ref(p_item); +} + +InventoryItem::InventoryItem(Item *p_item, int p_quantity) +{ + item_ref = Ref(p_item); + quantity = p_quantity; +} + +InventoryItem::~InventoryItem() +{ +} + +Ref InventoryItem::duplicate() const +{ + if (item_ref.is_valid()) + { + Ref duplicate = memnew(InventoryItem); + + duplicate->set_item(item_ref); + duplicate->set_quantity(quantity); + + return duplicate; + } + + return Ref(); +} + +Ref InventoryItem::get_item() const +{ + return item_ref; +} + +int InventoryItem::get_quantity() const +{ + return quantity; +} + +void InventoryItem::set_item(Ref p_item) +{ + item_ref = p_item; +} + +void InventoryItem::set_quantity(int p_quantity) +{ + quantity = p_quantity; +} + +void Inventory::_bind_methods() +{ + /// binds methods + ClassDB::bind_method(D_METHOD("activate", "item"), &Inventory::activate); + ClassDB::bind_method(D_METHOD("add_item", "item"), &Inventory::add_item); + ClassDB::bind_method(D_METHOD("can_activate", "item"), &Inventory::can_activate); + ClassDB::bind_method(D_METHOD("can_add", "item"), &Inventory::can_add); + ClassDB::bind_method(D_METHOD("can_drop", "item"), &Inventory::can_drop); + ClassDB::bind_method(D_METHOD("can_equip", "item", "slot"), &Inventory::can_equip); + ClassDB::bind_method(D_METHOD("drop_item", "item"), &Inventory::drop_item); + ClassDB::bind_method(D_METHOD("equip_item", "item", "slot"), &Inventory::equip_item); + ClassDB::bind_method(D_METHOD("get_equipment"), &Inventory::get_equipment); + ClassDB::bind_method(D_METHOD("get_inventory_owner"), &Inventory::get_inventory_owner); + ClassDB::bind_method(D_METHOD("has_equipment"), &Inventory::has_equipment); + ClassDB::bind_method(D_METHOD("has_inventory_owner"), &Inventory::has_inventory_owner); + ClassDB::bind_method(D_METHOD("set_equipment", "equipment"), &Inventory::set_equipment); + ClassDB::bind_method(D_METHOD("set_inventory_owner", "owner"), &Inventory::set_inventory_owner); + ClassDB::bind_method(D_METHOD("split_quantity", "item", "quantity"), &Inventory::split_quantity); + + /// binds properties + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "equipment", PROPERTY_HINT_RESOURCE_TYPE, "Equipment"), "set_equipment", "get_equipment"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "inventory_owner", PROPERTY_HINT_RESOURCE_TYPE, "Node"), "set_inventory_owner", "get_inventory_owner"); + + /// binds signals + ADD_SIGNAL(MethodInfo("item_activated", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "Item"))); + ADD_SIGNAL(MethodInfo("item_added", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "Item"))); + ADD_SIGNAL(MethodInfo("item_dropped", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "Item"))); + ADD_SIGNAL(MethodInfo("item_equipped", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "Item"), PropertyInfo(Variant::STRING, "slot"))); + ADD_SIGNAL(MethodInfo("item_splitted", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "Item"), PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "Item"))); + + /// binds enum constants + BIND_ENUM_CONSTANT(ERROR); + BIND_ENUM_CONSTANT(ITEM_NOT_FOUND); + BIND_ENUM_CONSTANT(ITEM_IS_NULL); + BIND_ENUM_CONSTANT(OK); + BIND_ENUM_CONSTANT(QUANTITY_IS_GREATER_THAN_ITEM_QUANTITY); + BIND_ENUM_CONSTANT(QUANTITY_IS_NEGATIVE); + BIND_ENUM_CONSTANT(QUANTITY_IS_ZERO); +} + +bool Inventory::activate(Item *p_item) +{ + if (can_activate(p_item)) + { + TagManager::get_singleton()->add_tags(this, p_item->get_item_tags()->get_tags_added_on_activation()); + TagManager::get_singleton()->remove_tags(this, p_item->get_item_tags()->get_tags_removed_on_activation()); + + if (p_item->has_method("on_activate")) + { + p_item->call("on_activate", this); + } + + return true; + } + + return false; +} + +bool Inventory::add_item(Item *p_item) +{ + if (can_add(p_item)) + { + TagManager::get_singleton()->add_tags(this, p_item->get_item_tags()->get_tags_added_on_add()); + TagManager::get_singleton()->remove_tags(this, p_item->get_item_tags()->get_tags_removed_on_add()); + + if (p_item->has_method("on_add")) + { + p_item->call("on_add", this); + } + + items.append(p_item); + emit_signal("item_added", p_item); + + return true; + } + + return false; +} + +bool Inventory::can_activate(Item *p_item) const +{ + if (p_item == nullptr) + { + return false; + } + + if (p_item->has_method("can_activate")) + { + return p_item->call("can_activate", this); + } + + return TagManager::get_singleton()->has_all_tags(this, p_item->get_item_tags()->get_tags_required_to_activate()); +} + +bool Inventory::can_add(Item *item) const +{ + if (item == nullptr) + { + return false; + } + + if (item->has_method("can_add")) + { + return item->call("can_add", this); + } + + return TagManager::get_singleton()->has_all_tags(this, item->get_item_tags()->get_tags_required_to_add()); +} + +bool Inventory::can_drop(Item *item) const +{ + if (item == nullptr) + { + return false; + } + + if (item->has_method("can_drop")) + { + return item->call("can_drop", this); + } + + return TagManager::get_singleton()->has_all_tags(this, item->get_item_tags()->get_tags_required_to_remove()); +} + +bool Inventory::can_equip(Item *p_item, String p_slot) const +{ + if (!has_equipment()) + { + return false; + } + + if (p_item == nullptr) + { + return false; + } + + if (p_slot.is_empty()) + { + return false; + } + + if (p_item->has_method("can_equip")) + { + return p_item->call("can_equip", this, equipment, p_slot); + } + + return TagManager::get_singleton()->has_all_tags(this, p_item->get_item_tags()->get_tags_required_to_equip()) && equipment->can_equip(p_item, p_slot); +} + +bool Inventory::drop_item(Item *p_item) +{ + if (can_drop(p_item)) + { + TagManager::get_singleton()->add_tags(this, p_item->get_item_tags()->get_tags_added_on_drop()); + TagManager::get_singleton()->remove_tags(this, p_item->get_item_tags()->get_tags_removed_on_drop()); + + if (p_item->has_method("on_drop")) + { + p_item->call("on_drop", this); + } + + int index = items.find(p_item); + + if (index >= 0) + { + items.remove_at(index); + + emit_signal("item_dropped", p_item); + + return true; + } + } + + return false; +} + +bool Inventory::equip_item(Item *p_item, String p_slot) +{ + if (can_equip(p_item, p_slot)) + { + TagManager::get_singleton()->add_tags(this, p_item->get_item_tags()->get_tags_added_on_equip()); + TagManager::get_singleton()->remove_tags(this, p_item->get_item_tags()->get_tags_removed_on_equip()); + + if (p_item->has_method("on_equip")) + { + p_item->call("on_equip", this, equipment, p_slot); + } + + Item *previous_equipped_item = equipment->equip(p_item, p_slot); + + emit_signal("item_equipped", p_item, p_slot); + + if (previous_equipped_item) + { + items.append(previous_equipped_item); + + emit_signal("item_added", previous_equipped_item); + } + + return true; + } + + return false; +} + +Equipment *Inventory::get_equipment() const +{ + return equipment; +} + +Node *Inventory::get_inventory_owner() const +{ + return inventory_owner; +} + +bool Inventory::has_equipment() const +{ + return equipment != nullptr; +} + +bool Inventory::has_inventory_owner() const +{ + return inventory_owner != nullptr; +} + +void Inventory::set_equipment(Equipment *p_equipment) +{ + equipment = p_equipment; +} + +void Inventory::set_inventory_owner(Node *p_owner) +{ + inventory_owner = p_owner; +} + +Inventory::SplitError Inventory::split_quantity(InventoryItem *p_item, int p_quantity) +{ + if (p_quantity == 0) + { + return QUANTITY_IS_ZERO; + } + + if (p_quantity < 0) + { + return QUANTITY_IS_NEGATIVE; + } + + if (p_item == nullptr) + { + return ITEM_IS_NULL; + } + + int quantity = p_item->get_quantity(); + + if (p_quantity > quantity) + { + return QUANTITY_IS_GREATER_THAN_ITEM_QUANTITY; + } + + if (p_quantity == quantity) + { + Ref duplicate = p_item->duplicate(); + + for (int i = 0; i < items.size(); i++) + { + Variant item_variant = items[i]; + InventoryItem *item = cast_to(item_variant); + + if (item != nullptr && item == p_item) + { + item->set_quantity(item->get_quantity() - p_quantity); + duplicate->set_quantity(p_quantity); + + emit_signal("item_splitted", item, duplicate); + + return OK; + } + } + + return ITEM_NOT_FOUND; + } + + return ERROR; +} diff --git a/src/system/item/inventory.h b/src/system/item/inventory.h new file mode 100644 index 0000000..2815d0c --- /dev/null +++ b/src/system/item/inventory.h @@ -0,0 +1,146 @@ +#ifndef GGS_INVENTORY_H +#define GGS_INVENTORY_H + +#include +#include + +#include "item.h" + +using namespace godot; + +namespace ggs +{ + class Equipment; + + class InventoryItem : public RefCounted + { + GDCLASS(InventoryItem, RefCounted); + + protected: + /// @brief Binds methods to Godot. + static void _bind_methods(); + /// @brief The item reference. + Ref item_ref; + /// @brief The quantity of the item. + int quantity; + + public: + /// @brief Default constructor. + InventoryItem(); + /// @brief Constructor with item. + /// @param p_item The item. + InventoryItem(Item *p_item); + /// @brief Constructor with item and quantity. + /// @param p_item The item. + /// @param p_quantity The quantity of the item. + InventoryItem(Item *p_item, int p_quantity); + /// @brief Destructor. + ~InventoryItem(); + /// @brief Returns a duplicate of the item. + /// @return The duplicate of the item. + Ref duplicate() const; + /// @brief Get the item reference. + /// @return The item reference. + Ref get_item() const; + /// @brief Get the quantity of the item. + /// @return The quantity of the item. + int get_quantity() const; + /// @brief Set the item reference. + /// @param p_item The item reference. + void set_item(Ref p_item); + /// @brief Set the quantity of the item. + /// @param p_quantity The quantity of the item. + void set_quantity(int p_quantity); + }; + + /// @brief The inventory node. + class Inventory : public Node + { + GDCLASS(Inventory, Node) + + friend class Equipment; + + protected: + /// @brief Bind methods to Godot. + static void _bind_methods(); + /// @brief The related equipment to this inventory. + Equipment *equipment; + /// @brief The owner of the inventory. + Node *inventory_owner; + /// @brief The items in the inventory. + TypedArray items; + + public: + enum SplitError + { + ERROR, + ITEM_NOT_FOUND, + ITEM_IS_NULL, + OK, + QUANTITY_IS_GREATER_THAN_ITEM_QUANTITY, + QUANTITY_IS_NEGATIVE, + QUANTITY_IS_ZERO, + }; + + /// @brief Activates the item if possible. Also calls "on_activate" from gdscript's item if defined. + /// @param p_item The item to activate. + /// @return True if the item has been activated, false otherwise. + bool activate(Item *p_item); + /// @brief Adds an item to this inventory. Also calls "on_add" from gdscript's item if defined. + /// @param p_item The item to add. + /// @return True if the item has been added, false otherwise. + bool add_item(Item *p_item); + /// @brief Check if the item can be activated. Also calls "can_activate" from gdscript's item if defined. If it's defined, it will be "override" the inventory's method. + /// @param p_item The item to check. + /// @return True if the item can be activated, false otherwise. + bool can_activate(Item *p_item) const; + /// @brief Check if the item can be added. Also calls "can_add" from gdscript's item if defined. If it's defined, it will be "override" the inventory's method. + /// @param p_item The item to check. + /// @return True if the item can be added, false otherwise. + bool can_add(Item *p_item) const; + /// @brief Check if the item can be dropped. Also calls "can_drop" from gdscript's item if defined. If it's defined, it will be "override" the inventory's method. + /// @param p_item The item to check. + /// @return True if the item can be dropped, false otherwise. + bool can_drop(Item *p_item) const; + /// @brief Check if the item can be equipped. + /// @param p_item The item to check. + /// @param p_slot The slot to check. + /// @return True if the item can be equipped, false otherwise. + bool can_equip(Item *p_item, String p_slot) const; + /// @brief Drops an item from the inventory. Also calls "on_drop" from gdscript's item if defined. + /// @param p_item The item to drop. + /// @return True if the item has been dropped, false otherwise. + bool drop_item(Item *p_item); + /// @brief Equips an item to the related equipment node. + /// @param p_item The item to equip. + /// @param p_slot The slot to equip the item. + /// @return True if the item has been equipped, false otherwise. + bool equip_item(Item *p_item, String p_slot); + /// @brief Gets the related equipment node. + /// @return The related equipment node. + Equipment *get_equipment() const; + /// @brief Get the owner of the inventory. + /// @return The owner of the inventory. + Node *get_inventory_owner() const; + /// @brief Checks if the inventory has an equipment. + /// @return True if the inventory has an equipment, false otherwise. + bool has_equipment() const; + /// @brief Checks if the Inventory has an inventory owner. + /// @return True if the inventory owner is set, false otherwise. + bool has_inventory_owner() const; + /// @brief Sets the related equipment node. + /// @param p_equipment The related equipment node. + void set_equipment(Equipment *p_equipment); + /// @brief Set the owner of the inventory. + /// @param owner The owner of the inventory. + void set_inventory_owner(Node *p_owner); + /// @brief Splits the quantity of an item in two separated items. + /// @param p_item The item to split. + /// @param p_quantity_a The resulting quantity. + SplitError split_quantity(InventoryItem *p_item, int p_quantity); + }; +} + +VARIANT_ENUM_CAST(ggs::Inventory::SplitError); + +#endif diff --git a/src/system/item/item.cpp b/src/system/item/item.cpp new file mode 100644 index 0000000..57b8b3c --- /dev/null +++ b/src/system/item/item.cpp @@ -0,0 +1,385 @@ +#include + +#include "item.h" + +using namespace ggs; + +void ItemSettings::_bind_methods() +{ + /// methods binding + ClassDB::bind_method(D_METHOD("get_decrease_quantity_on_activate"), &ItemSettings::get_decrease_quantity_on_activate); + ClassDB::bind_method(D_METHOD("get_is_stackable"), &ItemSettings::get_is_stackable); + ClassDB::bind_method(D_METHOD("get_max_quantity"), &ItemSettings::get_max_quantity); + ClassDB::bind_method(D_METHOD("set_decrease_quantity_on_activate", "p_decrease_quantity_on_activate"), &ItemSettings::set_decrease_quantity_on_activate); + ClassDB::bind_method(D_METHOD("set_is_stackable", "p_is_stackable"), &ItemSettings::set_is_stackable); + ClassDB::bind_method(D_METHOD("set_max_quantity", "p_max_quantity"), &ItemSettings::set_max_quantity); + + /// properties binding + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "decrease_quantity_on_activate"), "set_decrease_quantity_on_activate", "get_decrease_quantity_on_activate"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_stackable"), "set_is_stackable", "get_is_stackable"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_quantity"), "set_max_quantity", "get_max_quantity"); +} + +bool ItemSettings::get_decrease_quantity_on_activate() const +{ + return decrease_quantity_on_activate; +} + +bool ItemSettings::get_is_stackable() const +{ + return is_stackable; +} + +int ItemSettings::get_max_quantity() const +{ + return max_quantity; +} + +void ItemSettings::set_decrease_quantity_on_activate(bool p_decrease_quantity_on_use) +{ + decrease_quantity_on_activate = p_decrease_quantity_on_use; +} + +void ItemSettings::set_max_quantity(int p_max_quantity) +{ + max_quantity = p_max_quantity; +} + +void ItemSettings::set_is_stackable(bool p_is_stackable) +{ + is_stackable = p_is_stackable; +} + +void ItemTags::_bind_methods() +{ + /// methods binding + ClassDB::bind_method(D_METHOD("get_tags_added_on_activation"), &ItemTags::get_tags_added_on_activation); + ClassDB::bind_method(D_METHOD("get_tags_added_on_add"), &ItemTags::get_tags_added_on_add); + ClassDB::bind_method(D_METHOD("get_tags_added_on_drop"), &ItemTags::get_tags_added_on_drop); + ClassDB::bind_method(D_METHOD("get_tags_added_on_equip"), &ItemTags::get_tags_added_on_equip); + ClassDB::bind_method(D_METHOD("get_tags_added_on_unequip"), &ItemTags::get_tags_added_on_unequip); + ClassDB::bind_method(D_METHOD("get_tags_removed_on_activation"), &ItemTags::get_tags_removed_on_activation); + ClassDB::bind_method(D_METHOD("get_tags_removed_on_add"), &ItemTags::get_tags_removed_on_add); + ClassDB::bind_method(D_METHOD("get_tags_removed_on_drop"), &ItemTags::get_tags_removed_on_drop); + ClassDB::bind_method(D_METHOD("get_tags_removed_on_equip"), &ItemTags::get_tags_removed_on_equip); + ClassDB::bind_method(D_METHOD("get_tags_removed_on_unequip"), &ItemTags::get_tags_removed_on_unequip); + ClassDB::bind_method(D_METHOD("get_tags_required_to_activate"), &ItemTags::get_tags_required_to_activate); + ClassDB::bind_method(D_METHOD("get_tags_required_to_add"), &ItemTags::get_tags_required_to_add); + ClassDB::bind_method(D_METHOD("get_tags_required_to_equip"), &ItemTags::get_tags_required_to_equip); + ClassDB::bind_method(D_METHOD("get_tags_required_to_remove"), &ItemTags::get_tags_required_to_remove); + ClassDB::bind_method(D_METHOD("get_tags_required_to_unequip"), &ItemTags::get_tags_required_to_unequip); + ClassDB::bind_method(D_METHOD("set_tags_added_on_activation", "p_tags"), &ItemTags::set_tags_added_on_activation); + ClassDB::bind_method(D_METHOD("set_tags_added_on_add", "p_tags"), &ItemTags::set_tags_added_on_add); + ClassDB::bind_method(D_METHOD("set_tags_added_on_drop", "p_tags"), &ItemTags::set_tags_added_on_drop); + ClassDB::bind_method(D_METHOD("set_tags_added_on_equip", "p_tags"), &ItemTags::set_tags_added_on_equip); + ClassDB::bind_method(D_METHOD("set_tags_added_on_unequip", "p_tags"), &ItemTags::set_tags_added_on_unequip); + ClassDB::bind_method(D_METHOD("set_tags_removed_on_activation", "p_tags"), &ItemTags::set_tags_removed_on_activation); + ClassDB::bind_method(D_METHOD("set_tags_removed_on_add", "p_tags"), &ItemTags::set_tags_removed_on_add); + ClassDB::bind_method(D_METHOD("set_tags_removed_on_drop", "p_tags"), &ItemTags::set_tags_removed_on_drop); + ClassDB::bind_method(D_METHOD("set_tags_removed_on_equip", "p_tags"), &ItemTags::set_tags_removed_on_equip); + ClassDB::bind_method(D_METHOD("set_tags_removed_on_unequip", "p_tags"), &ItemTags::set_tags_removed_on_unequip); + ClassDB::bind_method(D_METHOD("set_tags_required_to_activate", "p_tags"), &ItemTags::set_tags_required_to_activate); + ClassDB::bind_method(D_METHOD("set_tags_required_to_add", "p_tags"), &ItemTags::set_tags_required_to_add); + ClassDB::bind_method(D_METHOD("set_tags_required_to_equip", "p_tags"), &ItemTags::set_tags_required_to_equip); + ClassDB::bind_method(D_METHOD("set_tags_required_to_remove", "p_tags"), &ItemTags::set_tags_required_to_remove); + ClassDB::bind_method(D_METHOD("set_tags_required_to_unequip", "p_tags"), &ItemTags::set_tags_required_to_unequip); + + /// properties binding + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_added_on_activation"), "set_tags_added_on_activation", "get_tags_added_on_activation"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_added_on_add"), "set_tags_added_on_add", "get_tags_added_on_add"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_added_on_drop"), "set_tags_added_on_drop", "get_tags_added_on_drop"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_added_on_equip"), "set_tags_added_on_equip", "get_tags_added_on_equip"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_added_on_unequip"), "set_tags_added_on_unequip", "get_tags_added_on_unequip"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_removed_on_activation"), "set_tags_removed_on_activation", "get_tags_removed_on_activation"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_removed_on_add"), "set_tags_removed_on_add", "get_tags_removed_on_add"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_removed_on_drop"), "set_tags_removed_on_drop", "get_tags_removed_on_drop"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_removed_on_equip"), "set_tags_removed_on_equip", "get_tags_removed_on_equip"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_removed_on_unequip"), "set_tags_removed_on_unequip", "get_tags_removed_on_unequip"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_required_to_activate"), "set_tags_required_to_activate", "get_tags_required_to_activate"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_required_to_add"), "set_tags_required_to_add", "get_tags_required_to_add"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_required_to_equip"), "set_tags_required_to_equip", "get_tags_required_to_equip"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_required_to_remove"), "set_tags_required_to_remove", "get_tags_required_to_remove"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags_required_to_unequip"), "set_tags_required_to_unequip", "get_tags_required_to_unequip"); +} + +PackedStringArray ItemTags::get_tags_added_on_activation() const +{ + return tags_added_on_activation; +} + +PackedStringArray ItemTags::get_tags_added_on_add() const +{ + return tags_added_on_add; +} + +PackedStringArray ItemTags::get_tags_added_on_equip() const +{ + return tags_added_on_equip; +} + +PackedStringArray ItemTags::get_tags_added_on_drop() const +{ + return tags_added_on_drop; +} + +PackedStringArray ItemTags::get_tags_added_on_unequip() const +{ + return tags_added_on_unequip; +} + +PackedStringArray ItemTags::get_tags_required_to_activate() const +{ + return tags_required_to_activate; +} + +PackedStringArray ItemTags::get_tags_required_to_add() const +{ + return tags_required_to_add; +} + +PackedStringArray ItemTags::get_tags_required_to_equip() const +{ + return tags_required_to_equip; +} + +PackedStringArray ItemTags::get_tags_required_to_remove() const +{ + return tags_required_to_remove; +} + +PackedStringArray ItemTags::get_tags_required_to_unequip() const +{ + return tags_required_to_unequip; +} + +PackedStringArray ItemTags::get_tags_removed_on_activation() const +{ + return tags_removed_on_activation; +} + +PackedStringArray ItemTags::get_tags_removed_on_add() const +{ + return tags_removed_on_add; +} + +PackedStringArray ItemTags::get_tags_removed_on_equip() const +{ + return tags_removed_on_equip; +} + +PackedStringArray ItemTags::get_tags_removed_on_drop() const +{ + return tags_removed_on_drop; +} + +PackedStringArray ItemTags::get_tags_removed_on_unequip() const +{ + return tags_removed_on_unequip; +} + +void ItemTags::set_tags_added_on_activation(const PackedStringArray &p_tags) +{ + tags_added_on_activation = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_added_on_add(const PackedStringArray &p_tags) +{ + tags_added_on_add = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_added_on_equip(const PackedStringArray &p_tags) +{ + tags_added_on_equip = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_added_on_drop(const PackedStringArray &p_tags) +{ + tags_added_on_drop = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_added_on_unequip(const PackedStringArray &p_tags) +{ + tags_added_on_unequip = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_required_to_activate(const PackedStringArray &p_tags) +{ + tags_required_to_activate = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_required_to_add(const PackedStringArray &p_tags) +{ + tags_required_to_add = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_required_to_equip(const PackedStringArray &p_tags) +{ + tags_required_to_equip = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_required_to_remove(const PackedStringArray &p_tags) +{ + tags_required_to_remove = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_required_to_unequip(const PackedStringArray &p_tags) +{ + tags_required_to_unequip = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_removed_on_activation(const PackedStringArray &p_tags) +{ + tags_removed_on_activation = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_removed_on_add(const PackedStringArray &p_tags) +{ + tags_removed_on_add = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_removed_on_equip(const PackedStringArray &p_tags) +{ + tags_removed_on_equip = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_removed_on_drop(const PackedStringArray &p_tags) +{ + tags_removed_on_drop = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemTags::set_tags_removed_on_unequip(const PackedStringArray &p_tags) +{ + tags_removed_on_unequip = p_tags; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void Item::_bind_methods() +{ + /// methods binding + ClassDB::bind_method(D_METHOD("get_item_name"), &Item::get_item_name); + ClassDB::bind_method(D_METHOD("get_item_settings"), &Item::get_item_settings); + ClassDB::bind_method(D_METHOD("get_tags"), &Item::get_tags); + ClassDB::bind_method(D_METHOD("set_item_name", "p_item_name"), &Item::set_item_name); + ClassDB::bind_method(D_METHOD("set_item_settings", "p_item_settings"), &Item::set_item_settings); + ClassDB::bind_method(D_METHOD("set_tags", "p_tags"), &Item::set_tags); + + /// properties binding + + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "item_name"), "set_item_name", "get_item_name"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "tags"), "set_tags", "get_tags"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "item_settings", PROPERTY_HINT_RESOURCE_TYPE, "ItemSettings"), "set_item_settings", "get_item_settings"); +} + +StringName Item::get_item_name() const +{ + return item_name; +} + +ItemSettings *Item::get_item_settings() const +{ + return item_settings; +} + +ItemTags *Item::get_item_tags() const +{ + return item_tags; +} + +PackedStringArray Item::get_tags() const +{ + return tags; +} + +void Item::set_item_name(StringName p_item_name) +{ + item_name = p_item_name; +} + +void Item::set_item_settings(ItemSettings *p_item_settings) +{ + item_settings = p_item_settings; +} + +void Item::set_item_tags(ItemTags *p_item_tags) +{ + item_tags = p_item_tags; +} + +void Item::set_tags(const PackedStringArray &p_tags) +{ + tags = p_tags; +} \ No newline at end of file diff --git a/src/system/item/item.h b/src/system/item/item.h new file mode 100644 index 0000000..fe9d2fc --- /dev/null +++ b/src/system/item/item.h @@ -0,0 +1,221 @@ +#ifndef GGS_ITEM_H +#define GGS_ITEM_H + +#include +#include + +using namespace godot; + +namespace ggs +{ + /// @brief The settings of an item. + class ItemSettings : public Resource + { + GDCLASS(ItemSettings, Resource); + + protected: + /// @brief Binds methods to Godot. + static void _bind_methods(); + /// @brief The item decreases in quantity when used. + bool decrease_quantity_on_activate; + /// @brief Whether the item is stackable. + bool is_stackable; + /// @brief The maximum quantity of the item. + int max_quantity; + + public: + /// @brief Gets whether the item decreases in quantity when used. + /// @return True if the item decreases in quantity when used, false otherwise. + bool get_decrease_quantity_on_activate() const; + /// @brief Gets whether the item is stackable. + /// @return True if the item is stackable, false otherwise. + bool get_is_stackable() const; + /// @brief Gets the maximum quantity of the item. + /// @return The maximum quantity of the item. + int get_max_quantity() const; + /// @brief Sets whether the item decreases in quantity when used. + /// @param p_decrease_quantity_on_use True if the item decreases in quantity when used, false otherwise. + void set_decrease_quantity_on_activate(bool p_decrease_quantity_on_activate); + /// @brief Sets the maximum quantity of the item. + /// @param p_max_quantity The maximum quantity of the item. + void set_max_quantity(int p_max_quantity); + /// @brief Sets whether the item is stackable. + /// @param p_is_stackable True if the item is stackable, false otherwise. + void set_is_stackable(bool p_is_stackable); + }; + + /// @brief An item resource. + class ItemTags : public Resource + { + GDCLASS(ItemTags, Resource); + + protected: + /// @brief Binds methods to Godot. + static void _bind_methods(); + /// @brief The tags which are added when the item is activated. + PackedStringArray tags_added_on_activation; + /// @brief The tags which are added when the item is added. + PackedStringArray tags_added_on_add; + /// @brief The tags which are added when the item is removed. + PackedStringArray tags_added_on_drop; + /// @brief The tags which are added when the item is equipped. + PackedStringArray tags_added_on_equip; + /// @brief The tags which are added when the item is unequipped. + PackedStringArray tags_added_on_unequip; + /// @brief The tags which are required to activate the item. + PackedStringArray tags_required_to_activate; + /// @brief The tags which are required to add the item. + PackedStringArray tags_required_to_add; + /// @brief The tags which are required to equip the item. + PackedStringArray tags_required_to_equip; + /// @brief The tags which are required to remove the item. + PackedStringArray tags_required_to_remove; + /// @brief The tags which are required to unequip the item. + PackedStringArray tags_required_to_unequip; + /// @brief The tags which are removed when the item is activated. + PackedStringArray tags_removed_on_activation; + /// @brief The tags which are removed when the item is added. + PackedStringArray tags_removed_on_add; + /// @brief The tags which are removed when the item is equipped. + PackedStringArray tags_removed_on_equip; + /// @brief The tags which are removed when the item is removed. + PackedStringArray tags_removed_on_drop; + /// @brief The tags which are removed when the item is unequipped. + PackedStringArray tags_removed_on_unequip; + + public: + /// @brief Gets the tags which are added when the item is activated. + /// @return the tags which are added when the item is activated. + PackedStringArray get_tags_added_on_activation() const; + /// @brief Gets the tags which are added when the item is added. + /// @return The tags which are added when the item is added. + PackedStringArray get_tags_added_on_add() const; + /// @brief Gets the tags which are added when the item is removed. + /// @return The tags which are added when the item is removed. + PackedStringArray get_tags_added_on_drop() const; + /// @brief Gets the tags which are added when the item is equipped. + /// @return The tags which are added when the item is equipped. + PackedStringArray get_tags_added_on_equip() const; + /// @brief Gets the tags which are added when the item is unequipped. + /// @return The tags which are added when the item is unequipped. + PackedStringArray get_tags_added_on_unequip() const; + /// @brief Gets the tags which are required to activate the item. + /// @return The tags which are required to activate the item. + PackedStringArray get_tags_required_to_activate() const; + /// @brief Gets the tags which are required to add the item. + /// @return The tags which are required to add the item. + PackedStringArray get_tags_required_to_add() const; + /// @brief Gets the tags which are required to equip the item. + /// @return The tags which are required to equip the item. + PackedStringArray get_tags_required_to_equip() const; + /// @brief Gets the tags which are required to remove the item. + /// @return The tags which are required to remove the item. + PackedStringArray get_tags_required_to_remove() const; + /// @brief Gets the tags which are required to unequip the item. + /// @return The tags which are required to unequip the item. + PackedStringArray get_tags_required_to_unequip() const; + /// @brief Gets the tags which are removed when the item is activated. + /// @return The tags which are removed when the item is activated. + PackedStringArray get_tags_removed_on_activation() const; + /// @brief Gets the tags which are removed when the item is added. + /// @return The tags which are removed when the item is added. + PackedStringArray get_tags_removed_on_add() const; + /// @brief Gets the tags which are removed when the item is removed. + /// @return The tags which are removed when the item is removed. + PackedStringArray get_tags_removed_on_drop() const; + /// @brief Gets the tags which are removed when the item is equipped. + /// @return The tags which are removed when the item is equipped. + PackedStringArray get_tags_removed_on_equip() const; + /// @brief Gets the tags which are removed when the item is unequipped. + /// @return The tags which are removed when the item is unequipped. + PackedStringArray get_tags_removed_on_unequip() const; + /// @brief Sets the tags which are added when the item is activated. + /// @param p_tags The tags which are added when the item is activated. + void set_tags_added_on_activation(const PackedStringArray &p_tags); + /// @brief Sets the tags which are added when the item is added. + /// @param p_tags The tags which are added when the item is added. + void set_tags_added_on_add(const PackedStringArray &p_tags); + /// @brief Sets the tags which are added when the item is removed. + /// @param p_tags The tags which are added when the item is removed. + void set_tags_added_on_drop(const PackedStringArray &p_tags); + /// @brief Sets the tags which are added when the item is equipped. + /// @param p_tags The tags which are added when the item is equipped. + void set_tags_added_on_equip(const PackedStringArray &p_tags); + /// @brief Sets the tags which are added when the item is unequipped. + /// @param p_tags The tags which are added when the item is unequipped. + void set_tags_added_on_unequip(const PackedStringArray &p_tags); + /// @brief Sets the tags which are required to activate the item. + /// @param p_tags The tags which are required to activate the item. + void set_tags_required_to_activate(const PackedStringArray &p_tags); + /// @brief Sets the tags which are required to add the item. + /// @param p_tags The tags which are required to add the item. + void set_tags_required_to_add(const PackedStringArray &p_tags); + /// @brief Sets the tags which are required to equip the item. + /// @param p_tags The tags which are required to equip the item. + void set_tags_required_to_equip(const PackedStringArray &p_tags); + /// @brief Sets the tags which are required to remove the item. + /// @param p_tags The tags which are required to remove the item. + void set_tags_required_to_remove(const PackedStringArray &p_tags); + /// @brief Sets the tags which are required to unequip the item. + /// @param p_tags The tags which are required to unequip the item. + void set_tags_required_to_unequip(const PackedStringArray &p_tags); + /// @brief Sets the tags which are removed when the item is activated. + /// @param p_tags The tags which are removed when the item is activated. + void set_tags_removed_on_activation(const PackedStringArray &p_tags); + /// @brief Sets the tags which are removed when the item is added. + /// @param p_tags The tags which are removed when the item is added. + void set_tags_removed_on_add(const PackedStringArray &p_tags); + /// @brief Sets the tags which are removed when the item is removed. + /// @param p_tags The tags which are removed when the item is removed. + void set_tags_removed_on_drop(const PackedStringArray &p_tags); + /// @brief Sets the tags which are removed when the item is equipped. + /// @param p_tags The tags which are removed when the item is equipped. + void set_tags_removed_on_equip(const PackedStringArray &p_tags); + /// @brief Sets the tags which are removed when the item is unequipped. + /// @param p_tags The tags which are removed when the item is unequipped. + void set_tags_removed_on_unequip(const PackedStringArray &p_tags); + }; + + class Item : public Resource + { + GDCLASS(Item, Resource); + + protected: + static void _bind_methods(); + + /// @brief The name of the item. + StringName item_name; + /// @brief The settings of the item. + ItemSettings *item_settings; + /// @brief The item's tags. + PackedStringArray tags; + /// @brief The tags of the item. + ItemTags *item_tags; + + public: + /// @brief The name of the item. + StringName get_item_name() const; + /// @brief Gets the settings of the item. + /// @return The settings of the item. + ItemSettings *get_item_settings() const; + /// @brief Gets the tags of the item. + /// @return The tags of the item. + ItemTags *get_item_tags() const; + /// @brief Gets the tags of the item. + /// @return The tags of the item. + PackedStringArray get_tags() const; + /// @brief Sets the name of the item. + void set_item_name(StringName p_item_name); + /// @brief Sets the settings of the item. + /// @param p_item_settings The settings of the item. + void set_item_settings(ItemSettings *p_item_settings); + /// @brief Sets the tags of the item. + /// @param p_item_tags The tags of the item. + void set_item_tags(ItemTags *p_item_tags); + /// @brief Sets the tags of the item. + /// @param p_tags The tags of the item. + void set_tags(const PackedStringArray &p_tags); + }; +} + +#endif diff --git a/src/system/item/item_manager.cpp b/src/system/item/item_manager.cpp new file mode 100644 index 0000000..14ecf79 --- /dev/null +++ b/src/system/item/item_manager.cpp @@ -0,0 +1,214 @@ +#include "item_manager.h" +#include "item_project_settings.h" + +#include + +using namespace ggs; + +ItemManager *ItemManager::singleton = nullptr; + +void ItemManager::load_items_pools() +{ + /// let's retrieve the settings' value + PackedStringArray resource_file_paths = ItemProjectSettings::get_resource_file_paths(); + /// let's get the resource loader instance + ResourceLoader *resource_loader = ResourceLoader::get_singleton(); + + if (resource_loader != nullptr) + { + for (int i = 0; i < resource_file_paths.size(); i++) + { + String resource_file_path = resource_file_paths[i]; + Ref resource = resource_loader->load(resource_file_path); + + if (resource.is_valid()) + { + add_items_pool(resource.ptr()); + } + } + } +} + +void ItemManager::_bind_methods() +{ + /// binds methods + ClassDB::bind_method(D_METHOD("get_items"), &ItemManager::get_items); + ClassDB::bind_method(D_METHOD("get_items_from_pool", "item_pool_name"), &ItemManager::get_items_from_pool); + ClassDB::bind_method(D_METHOD("get_items_pools"), &ItemManager::get_items_pools); + ClassDB::bind_method(D_METHOD("has_items_pool", "item_pool_name"), &ItemManager::has_items_pool); + + /// binds signals + ADD_SIGNAL(MethodInfo("items_pool_added", PropertyInfo(Variant::OBJECT, "items_pool", PROPERTY_HINT_RESOURCE_TYPE, "ItemsPool"))); + ADD_SIGNAL(MethodInfo("items_pool_removed", PropertyInfo(Variant::OBJECT, "items_pool", PROPERTY_HINT_RESOURCE_TYPE, "ItemsPool"))); + ADD_SIGNAL(MethodInfo("items_pool_updated", PropertyInfo(Variant::OBJECT, "items_pool", PROPERTY_HINT_RESOURCE_TYPE, "ItemsPool"))); +} + +void ItemManager::add_items_pool(ItemsPool *p_items_pool) +{ + if (p_items_pool == nullptr) + { + return; + } + + if (has_items_pool_resource(p_items_pool)) + { + return; + } + + items_pools.append(p_items_pool); + emit_signal("items_pool_added", p_items_pool); + + ItemProjectSettings::add_resource(p_items_pool->get_path()); +} + +bool ItemManager::has_items_pool_resource(ItemsPool *p_items_pool) +{ + if (p_items_pool == nullptr) + { + return false; + } + + for (int i = 0; i < items_pools.size(); i++) + { + Variant variant = items_pools[i]; + ItemsPool *items_pool = cast_to(variant); + + if (items_pool->get_path() == p_items_pool->get_path() || items_pool->get_pool_name() == p_items_pool->get_pool_name()) + { + return true; + } + } + + return false; +} + +void ItemManager::remove_items_pool(ItemsPool *p_items_pool) +{ + if (!has_items_pool_resource(p_items_pool)) + { + return; + } + + int index = items_pools.find(p_items_pool); + + if (index != -1) + { + ItemProjectSettings::remove_resource(p_items_pool->get_path()); + + items_pools.remove_at(index); + emit_signal("items_pool_removed", p_items_pool); + } +} + +void ItemManager::update_items_pool(ItemsPool *p_items_pool) +{ + if (!has_items_pool_resource(p_items_pool)) + { + add_items_pool(p_items_pool); + return; + } + + int index = items_pools.find(p_items_pool); + + if (index != -1) + { + items_pools[index] = p_items_pool; + emit_signal("items_pool_updated", p_items_pool); + } +} + +ItemManager::ItemManager() +{ + if (singleton == nullptr) + { + singleton = this; + } + else + { + items_pools = singleton->items_pools; + } +} + +void ItemManager::_ready() +{ + load_items_pools(); +} + +ItemManager *ItemManager::get_singleton() +{ + if (singleton == nullptr) + { + singleton = memnew(ItemManager); + } + + return singleton; +} + +Ref ItemManager::get_items_pool(StringName p_item_pool_name) const +{ + for (int i = 0; i < items_pools.size(); i++) + { + Variant variant = items_pools[i]; + ItemsPool *items_pool = cast_to(variant); + + if (items_pool->get_pool_name() == p_item_pool_name) + { + return items_pool->duplicate(); + } + } + + return Ref(); +} + +bool ItemManager::has_items_pool(StringName p_item_pool_name) const +{ + for (int i = 0; i < items_pools.size(); i++) + { + Variant variant = items_pools[i]; + ItemsPool *items_pool = cast_to(variant); + + if (items_pool->get_pool_name() == p_item_pool_name) + { + return true; + } + } + + return false; +} + +TypedArray ItemManager::get_items() const +{ + TypedArray items = TypedArray(); + + for (int i = 0; i < items_pools.size(); i++) + { + Variant variant = items_pools[i]; + ItemsPool *items_pool = cast_to(variant); + + if (items_pool == nullptr) + { + continue; + } + + items.append_array(items_pool->get_items()); + } + + return TypedArray(); +} + +TypedArray ItemManager::get_items_from_pool(StringName p_item_pool_name) const +{ + Ref pool = get_items_pool(p_item_pool_name); + + if (pool != nullptr) + { + return pool->get_items(); + } + + return TypedArray(); +} + +TypedArray ItemManager::get_items_pools() const +{ + return TypedArray(items_pools); +} diff --git a/src/system/item/item_manager.h b/src/system/item/item_manager.h new file mode 100644 index 0000000..565f7e3 --- /dev/null +++ b/src/system/item/item_manager.h @@ -0,0 +1,71 @@ +#ifndef GGS_ITEM_MANAGER_H +#define GGS_ITEM_MANAGER_H + +#include + +#include "item.h" +#include "items_pool.h" + +using namespace godot; + +namespace ggs +{ + class ItemManager : public Node + { + GDCLASS(ItemManager, Node) + + private: + /// @brief Loads the items pools. + void load_items_pools(); + + protected: + /// @brief The singleton instance. + static ItemManager *singleton; + /// @brief Binds methods to Godot. + static void _bind_methods(); + /// @brief The items pools. + TypedArray items_pools; + /// @brief Adds an items pool to the items pools. + /// @param p_items_pool The items pool to add. + void add_items_pool(ItemsPool *p_items_pool); + /// @brief Checks if the items pools contains the specified items pool. + /// @param p_items_pool The items pool to check. + /// @return True if the items pools contains the specified items pool, false otherwise. + bool has_items_pool_resource(ItemsPool *p_items_pool); + /// @brief Removes an items pool from the items pools. + /// @param p_items_pool The items pool to remove. + void remove_items_pool(ItemsPool *p_items_pool); + /// @brief Updates an items pool in the items pools. + /// @param p_items_pool The items pool to update. + void update_items_pool(ItemsPool *p_items_pool); + + public: + /// @brief Default constructor. + ItemManager(); + /// @brief Called when the node enters the scene tree for the first time. + void _ready() override; + /// @brief Gets the singleton instance. + /// @return The singleton instance. + static ItemManager *get_singleton(); + /// @brief Gets the specified items pool. + /// @param p_item_pool_name The name of the items pool to get. + /// @return The specified items pool. + Ref get_items_pool(StringName p_item_pool_name) const; + /// @brief Checks if the specified items pool exists. + /// @param p_item_pool_name The name of the items pool to check. + /// @return True if the specified items pool exists, false otherwise. + bool has_items_pool(StringName p_item_pool_name) const; + /// @brief Gets all the items. + /// @return The items. + TypedArray get_items() const; + /// @brief Gets all the items from the specified items pool. + /// @param p_item_pool_name The name of the items pool to get the items from. + /// @return The items from the specified items pool. + TypedArray get_items_from_pool(StringName p_item_pool_name) const; + /// @brief Gets all the items pools. + /// @return The items pools. + TypedArray get_items_pools() const; + }; +} + +#endif diff --git a/src/system/item/item_project_settings.cpp b/src/system/item/item_project_settings.cpp new file mode 100644 index 0000000..6c946eb --- /dev/null +++ b/src/system/item/item_project_settings.cpp @@ -0,0 +1,97 @@ +#include "item_project_settings.h" + +#include +#include +#include + +using namespace ggs; + +const char *ItemProjectSettings::RESOURCES_PATH_KEY = "application/gameplay_systems/item_system/item_pools_paths"; + +void ItemProjectSettings::_bind_methods() +{ +} + +void ItemProjectSettings::add_resource(const String &p_file_path) +{ + if (Engine::get_singleton()->is_editor_hint()) + { + ProjectSettings *project_settings = ProjectSettings::get_singleton(); + + if (project_settings != nullptr) + { + PackedStringArray settings = project_settings->get_setting(RESOURCES_PATH_KEY, PackedStringArray()); + + if (!settings.has(p_file_path)) + { + settings.append(p_file_path); + } + + project_settings->set_setting(RESOURCES_PATH_KEY, settings); + project_settings->save(); + } + } +} + +void ItemProjectSettings::remove_resource(const String &p_file_path) +{ + if (Engine::get_singleton()->is_editor_hint()) + { + ProjectSettings *project_settings = ProjectSettings::get_singleton(); + + if (project_settings != nullptr) + { + PackedStringArray settings = project_settings->get_setting(RESOURCES_PATH_KEY, PackedStringArray()); + + int index = settings.find(p_file_path); + + if (index > -1) + { + settings.remove_at(index); + } + + project_settings->set_setting(RESOURCES_PATH_KEY, settings); + project_settings->save(); + } + } +} + +PackedStringArray ItemProjectSettings::get_resource_file_paths() +{ + if (ProjectSettings::get_singleton() != nullptr) + { + return ProjectSettings::get_singleton()->get_setting(RESOURCES_PATH_KEY, PackedStringArray()); + } + + return PackedStringArray(); +} + +void ItemProjectSettings::setup() +{ + if (Engine::get_singleton()->is_editor_hint()) + { + ProjectSettings *project_settings = ProjectSettings::get_singleton(); + + if (project_settings != nullptr) + { + PackedStringArray value; + + if (!project_settings->has_setting(RESOURCES_PATH_KEY)) + { + project_settings->set_setting(RESOURCES_PATH_KEY, value); + } + + Dictionary dictionary; + + dictionary["name"] = RESOURCES_PATH_KEY; + dictionary["default_value"] = Variant(value).get_type(); + dictionary["hint"] = PropertyHint::PROPERTY_HINT_ARRAY_TYPE; + dictionary["hint_string"] = "*.tres,*.res,*.csv"; + + project_settings->add_property_info(dictionary); + project_settings->set_initial_value(RESOURCES_PATH_KEY, value); + project_settings->set_restart_if_changed(RESOURCES_PATH_KEY, false); + project_settings->save(); + } + } +} diff --git a/src/system/item/item_project_settings.h b/src/system/item/item_project_settings.h new file mode 100644 index 0000000..93f66ce --- /dev/null +++ b/src/system/item/item_project_settings.h @@ -0,0 +1,36 @@ +#ifndef GGS_ITEMS_SETTINGS_H +#define GGS_ITEMS_SETTINGS_H + +#include + +#include "items_pool.h" + +using namespace godot; + +namespace ggs +{ + class ItemProjectSettings : public RefCounted + { + GDCLASS(ItemProjectSettings, RefCounted) + + static const char *RESOURCES_PATH_KEY; + + protected: + static void _bind_methods(); + + public: + /// @brief Adds a resource file path to the project settings. + /// @param p_file_path The resource file path to add. + static void add_resource(const String &p_file_path); + /// @brief Removes a resource file path from the project settings. + /// @param p_file_path The resource file path to remove. + static void remove_resource(const String &p_file_path); + /// @brief Gets all resource file paths from the project settings. + /// @return The resource file paths. + static PackedStringArray get_resource_file_paths(); + /// @brief Sets up the project settings. + static void setup(); + }; +} + +#endif diff --git a/src/system/item/items_pool.cpp b/src/system/item/items_pool.cpp new file mode 100644 index 0000000..e1e537b --- /dev/null +++ b/src/system/item/items_pool.cpp @@ -0,0 +1,48 @@ +#include "items_pool.h" + +#include + +using namespace ggs; + +void ItemsPool::_bind_methods() +{ + /// binds methods + ClassDB::bind_method(D_METHOD("get_items"), &ItemsPool::get_items); + ClassDB::bind_method(D_METHOD("get_pool_name"), &ItemsPool::get_pool_name); + ClassDB::bind_method(D_METHOD("set_items", "p_items"), &ItemsPool::set_items); + ClassDB::bind_method(D_METHOD("set_pool_name", "p_pool_name"), &ItemsPool::set_pool_name); + + /// binds properties + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items"), "set_items", "get_items"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "pool_name"), "set_pool_name", "get_pool_name"); +} + +TypedArray ItemsPool::get_items() const +{ + return items; +} + +StringName ItemsPool::get_pool_name() const +{ + return pool_name; +} + +void ItemsPool::set_items(const TypedArray &p_items) +{ + items = p_items; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} + +void ItemsPool::set_pool_name(const StringName &p_pool_name) +{ + pool_name = p_pool_name; + + if (Engine::get_singleton()->is_editor_hint()) + { + emit_changed(); + } +} diff --git a/src/system/item/items_pool.h b/src/system/item/items_pool.h new file mode 100644 index 0000000..47c0796 --- /dev/null +++ b/src/system/item/items_pool.h @@ -0,0 +1,29 @@ +#ifndef GGS_ITEMS_POOL_H +#define GGS_ITEMS_POOL_H + +#include + +#include "item.h" + +using namespace godot; + +namespace ggs +{ + class ItemsPool : public Resource + { + GDCLASS(ItemsPool, Resource) + + protected: + static void _bind_methods(); + TypedArray items; + StringName pool_name; + + public: + TypedArray get_items() const; + StringName get_pool_name() const; + void set_items(const TypedArray &p_items); + void set_pool_name(const StringName &p_pool_name); + }; +} + +#endif \ No newline at end of file