From 176bf5f0c49311d48e128d2b1718eff21dc7a010 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 1 May 2026 13:11:43 -0600 Subject: [PATCH] Editor inventory tab --- src/dusk/ui/editor.cpp | 277 +++++++++++++++++++++++++++++++++++++-- src/dusk/ui/pane.cpp | 68 ++++------ src/dusk/ui/pane.hpp | 10 +- src/dusk/ui/settings.cpp | 16 +-- 4 files changed, 310 insertions(+), 61 deletions(-) diff --git a/src/dusk/ui/editor.cpp b/src/dusk/ui/editor.cpp index c46ded84e8..8cb1547432 100644 --- a/src/dusk/ui/editor.cpp +++ b/src/dusk/ui/editor.cpp @@ -14,12 +14,13 @@ #include "string_button.hpp" #include +#include #include #include -#include -#include #include #include +#include +#include namespace dusk::ui { namespace { @@ -56,6 +57,27 @@ dSv_horse_place_c* get_horse_place() { return &dComIfGs_getSaveData()->getPlayer().getHorsePlace(); } +dSv_player_item_c* get_player_item() { + if (!has_save_data()) { + return nullptr; + } + return &dComIfGs_getSaveData()->getPlayer().getItem(); +} + +dSv_player_item_record_c* get_player_item_record() { + if (!has_save_data()) { + return nullptr; + } + return &dComIfGs_getSaveData()->getPlayer().getItemRecord(); +} + +dSv_player_item_max_c* get_player_item_max() { + if (!has_save_data()) { + return nullptr; + } + return &dComIfGs_getSaveData()->getPlayer().getItemMax(); +} + template Rml::String fixed_string(const char (&value)[Size]) { size_t length = 0; @@ -436,7 +458,11 @@ std::map itemMap = { }; Rml::String get_item_name(u8 id) { - return itemMap.find(id)->second.m_name; + const auto it = itemMap.find(id); + if (it == itemMap.end()) { + return fmt::format("Item {}", id); + } + return it->second.m_name; } Rml::String item_label_for_slot(u8 slot) { @@ -447,6 +473,116 @@ Rml::String item_label_for_slot(u8 slot) { return fmt::format("Slot {0} ({1})", slot, get_item_name(id)); } +struct DefaultInventoryEntry { + u8 slot; + u8 item; +}; + +constexpr std::array defaultInventory = { + DefaultInventoryEntry{SLOT_0, dItemNo_BOOMERANG_e}, + DefaultInventoryEntry{SLOT_1, dItemNo_KANTERA_e}, + DefaultInventoryEntry{SLOT_2, dItemNo_SPINNER_e}, + DefaultInventoryEntry{SLOT_3, dItemNo_HVY_BOOTS_e}, + DefaultInventoryEntry{SLOT_4, dItemNo_BOW_e}, + DefaultInventoryEntry{SLOT_5, dItemNo_HAWK_EYE_e}, + DefaultInventoryEntry{SLOT_6, dItemNo_IRONBALL_e}, + DefaultInventoryEntry{SLOT_8, dItemNo_COPY_ROD_e}, + DefaultInventoryEntry{SLOT_9, dItemNo_HOOKSHOT_e}, + DefaultInventoryEntry{SLOT_10, dItemNo_W_HOOKSHOT_e}, + DefaultInventoryEntry{SLOT_11, dItemNo_EMPTY_BOTTLE_e}, + DefaultInventoryEntry{SLOT_12, dItemNo_EMPTY_BOTTLE_e}, + DefaultInventoryEntry{SLOT_13, dItemNo_EMPTY_BOTTLE_e}, + DefaultInventoryEntry{SLOT_14, dItemNo_EMPTY_BOTTLE_e}, + DefaultInventoryEntry{SLOT_15, dItemNo_NORMAL_BOMB_e}, + DefaultInventoryEntry{SLOT_16, dItemNo_WATER_BOMB_e}, + DefaultInventoryEntry{SLOT_17, dItemNo_POKE_BOMB_e}, + DefaultInventoryEntry{SLOT_18, dItemNo_DUNGEON_EXIT_e}, + DefaultInventoryEntry{SLOT_20, dItemNo_FISHING_ROD_1_e}, + DefaultInventoryEntry{SLOT_21, dItemNo_HORSE_FLUTE_e}, + DefaultInventoryEntry{SLOT_22, dItemNo_ANCIENT_DOCUMENT_e}, + DefaultInventoryEntry{SLOT_23, dItemNo_PACHINKO_e}, +}; + +u8 get_slot_default(int slot) { + for (const auto& entry : defaultInventory) { + if (entry.slot == slot) { + return entry.item; + } + } + return dItemNo_NONE_e; +} + +void set_item_first_bit(u8 itemNo, bool owned) { + if (owned) { + dComIfGs_onItemFirstBit(itemNo); + } else { + dComIfGs_offItemFirstBit(itemNo); + } +} + +void toggle_item_first_bit(u8 itemNo) { + set_item_first_bit(itemNo, !dComIfGs_isItemFirstBit(itemNo)); +} + +bool can_edit_item_first_bit(int itemId, const itemInfo& item) { + return itemId < 254 && item.m_name != "Reserved"; +} + +void set_all_item_first_bits(bool owned) { + for (const auto& [itemId, item] : itemMap) { + if (!can_edit_item_first_bit(itemId, item)) { + continue; + } + set_item_first_bit(static_cast(itemId), owned); + } +} + +void populate_item_slot_picker(Pane& pane, int slot) { + pane.clear(); + pane.add_section("Actions"); + pane.add_button(fmt::format("Default ({})", get_item_name(get_slot_default(slot))), + [slot] { dComIfGs_setItem(slot, get_slot_default(slot)); }); + + pane.add_section("Items"); + pane.add_button( + { + .text = "None", + .isSelected = [slot] { return get_player_item()->mItems[slot] == dItemNo_NONE_e; }, + }, + [slot] { dComIfGs_setItem(slot, dItemNo_NONE_e); }); + for (const auto& [itemId, item] : itemMap) { + if (item.m_type != ITEMTYPE_EQUIP_e) { + continue; + } + pane.add_button( + { + .text = item.m_name, + .isSelected = [slot, itemId] { return get_player_item()->mItems[slot] == itemId; }, + }, + [slot, itemId] { dComIfGs_setItem(slot, static_cast(itemId)); }); + } +} + +void populate_item_flag_picker(Pane& pane) { + pane.clear(); + pane.add_section("Actions"); + pane.add_button("Select All", [] { set_all_item_first_bits(true); }); + pane.add_button("Clear None", [] { set_all_item_first_bits(false); }); + + pane.add_section("Items"); + for (const auto& [itemId, item] : itemMap) { + if (!can_edit_item_first_bit(itemId, item)) { + continue; + } + pane.add_button( + { + .text = item.m_name, + .isSelected = [itemId] { return dComIfGs_isItemFirstBit(static_cast(itemId)); }, + }, + [itemId] { toggle_item_first_bit(static_cast(itemId)); }); + } +} + constexpr std::array walletSizeNames = { "Normal", "Big", @@ -476,8 +612,8 @@ void set_clock_time(int hour, int minute) { EditorWindow::EditorWindow() { add_tab("Player Status", [this](Rml::Element* content) { - auto& leftPane = add_child(content, Pane::Direction::Vertical); - auto& rightPane = add_child(content, Pane::Direction::Vertical); + auto& leftPane = add_child(content, Pane::Type::Controlled); + auto& rightPane = add_child(content, Pane::Type::Uncontrolled); leftPane.add_section("Player"); leftPane @@ -754,8 +890,8 @@ EditorWindow::EditorWindow() { }); add_tab("Location", [this](Rml::Element* content) { - auto& leftPane = add_child(content, Pane::Direction::Vertical); - auto& rightPane = add_child(content, Pane::Direction::Vertical); + auto& leftPane = add_child(content, Pane::Type::Controlled); + auto& rightPane = add_child(content, Pane::Type::Uncontrolled); leftPane.add_section("Save Location"); leftPane @@ -864,9 +1000,132 @@ EditorWindow::EditorWindow() { }); add_tab("Inventory", [this](Rml::Element* content) { - // TODO - }); + auto& leftPane = add_child(content, Pane::Type::Controlled); + auto& rightPane = add_child(content, Pane::Type::Uncontrolled); + leftPane.add_section("Item Wheel"); + leftPane + .add_button("Default All", + [&rightPane] { + for (int slot = 0; slot < 24; ++slot) { + dComIfGs_setItem(slot, get_slot_default(slot)); + } + rightPane.clear(); + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + leftPane + .add_button("Clear All", + [&rightPane] { + for (int slot = 0; slot < 24; ++slot) { + dComIfGs_setItem(slot, dItemNo_NONE_e); + } + rightPane.clear(); + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + for (int slot = 0; slot < 24; ++slot) { + leftPane + .add_select_button({ + .key = fmt::format("Slot {0:02d}", slot), + .getValue = [slot] { return get_item_name(get_player_item()->mItems[slot]); }, + }) + .on_focus([&rightPane, slot]( + Rml::Event&) { populate_item_slot_picker(rightPane, slot); }); + } + + leftPane.add_section("Amounts"); + leftPane + .add_child(NumberButton::Props{ + .key = "Arrows Amount", + .getValue = [] { return get_player_item_record()->mArrowNum; }, + .setValue = + [](int value) { get_player_item_record()->mArrowNum = static_cast(value); }, + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + leftPane + .add_child(NumberButton::Props{ + .key = "Slingshot Amount", + .getValue = [] { return get_player_item_record()->mPachinkoNum; }, + .setValue = + [](int value) { + get_player_item_record()->mPachinkoNum = static_cast(value); + }, + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + for (int bag = 0; bag < 3; ++bag) { + leftPane + .add_child(NumberButton::Props{ + .key = fmt::format("Bomb Bag {} Amount", bag + 1), + .getValue = [bag] { return get_player_item_record()->mBombNum[bag]; }, + .setValue = + [bag](int value) { + get_player_item_record()->mBombNum[bag] = static_cast(value); + }, + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + } + for (int bottle = 0; bottle < 4; ++bottle) { + leftPane + .add_child(NumberButton::Props{ + .key = fmt::format("Bottle {} Amount", bottle + 1), + .getValue = [bottle] { return get_player_item_record()->mBottleNum[bottle]; }, + .setValue = + [bottle](int value) { + get_player_item_record()->mBottleNum[bottle] = static_cast(value); + }, + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + } + + leftPane.add_section("Capacities"); + leftPane + .add_child(NumberButton::Props{ + .key = "Arrows Max", + .getValue = [] { return get_player_item_max()->mItemMax[0]; }, + .setValue = + [](int value) { get_player_item_max()->mItemMax[0] = static_cast(value); }, + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + leftPane + .add_child(NumberButton::Props{ + .key = "Normal Bombs Max", + .getValue = [] { return get_player_item_max()->mItemMax[1]; }, + .setValue = + [](int value) { get_player_item_max()->mItemMax[1] = static_cast(value); }, + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + leftPane + .add_child(NumberButton::Props{ + .key = "Water Bombs Max", + .getValue = [] { return get_player_item_max()->mItemMax[2]; }, + .setValue = + [](int value) { get_player_item_max()->mItemMax[2] = static_cast(value); }, + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + leftPane + .add_child(NumberButton::Props{ + .key = "Bomblings Max", + .getValue = [] { return get_player_item_max()->mItemMax[3]; }, + .setValue = + [](int value) { get_player_item_max()->mItemMax[3] = static_cast(value); }, + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + + leftPane.add_section("Flags"); + leftPane + .add_select_button({ + .key = "Obtained Items", + .getValue = [] { return "Edit"; }, + }) + .on_focus([&rightPane](Rml::Event&) { populate_item_flag_picker(rightPane); }); + }); add_tab("Collection", [this](Rml::Element* content) { // TODO }); diff --git a/src/dusk/ui/pane.cpp b/src/dusk/ui/pane.cpp index de81f4dc2e..722d022ff6 100644 --- a/src/dusk/ui/pane.cpp +++ b/src/dusk/ui/pane.cpp @@ -13,15 +13,12 @@ Rml::Element* createRoot(Rml::Element* parent) { } // namespace -Pane::Pane(Rml::Element* parent, Direction direction) - : FluentComponent(createRoot(parent)), mDirection(direction) { +Pane::Pane(Rml::Element* parent, Type type) : FluentComponent(createRoot(parent)), mType(type) { listen(Rml::EventId::Keydown, [this](Rml::Event& event) { const auto cmd = map_nav_event(event); // If navigating to the next pane, select the focused item - if ((mDirection == Direction::Vertical && cmd == NavCommand::Right) || - (mDirection == Direction::Horizontal && cmd == NavCommand::Down)) - { + if (mType == Type::Controlled && cmd == NavCommand::Right) { auto* target = event.GetTargetElement(); int focusedChild = -1; for (size_t i = 0; i < mChildren.size(); ++i) { @@ -38,13 +35,9 @@ Pane::Pane(Rml::Element* parent, Direction direction) } int direction = 0; - if ((mDirection == Direction::Vertical && cmd == NavCommand::Down) || - (mDirection == Direction::Horizontal && cmd == NavCommand::Right)) - { + if (cmd == NavCommand::Down) { direction = 1; - } else if ((mDirection == Direction::Vertical && cmd == NavCommand::Up) || - (mDirection == Direction::Horizontal && cmd == NavCommand::Left)) - { + } else if (cmd == NavCommand::Up) { direction = -1; } else { return; @@ -70,29 +63,31 @@ Pane::Pane(Rml::Element* parent, Direction direction) } }); - // Listen for selection change events - listen(Rml::EventId::Change, [this](Rml::Event& event) { - const auto it = std::find_if(event.GetParameters().begin(), event.GetParameters().end(), - [](const auto& param) { return param.first == "selected"; }); - if (it != event.GetParameters().end()) { - const auto selected = it->second.Get(); - int childIndex = -1; - for (int i = 0; i < mChildren.size(); ++i) { - if (event.GetTargetElement() == mChildren[i]->root()) { - childIndex = i; + if (type == Type::Controlled) { + // Listen for selection change events + listen(Rml::EventId::Change, [this](Rml::Event& event) { + const auto it = std::find_if(event.GetParameters().begin(), event.GetParameters().end(), + [](const auto& param) { return param.first == "selected"; }); + if (it != event.GetParameters().end()) { + const auto selected = it->second.Get(); + int childIndex = -1; + for (int i = 0; i < mChildren.size(); ++i) { + if (event.GetTargetElement() == mChildren[i]->root()) { + childIndex = i; + } } - } - if (childIndex != -1) { - if (selected) { - set_selected_item(childIndex); - } else if (childIndex == mSelectedItem) { + if (childIndex != -1) { + if (selected) { + set_selected_item(childIndex); + } else if (childIndex == mSelectedItem) { + set_selected_item(-1); + } + } else { set_selected_item(-1); } - } else { - set_selected_item(-1); } - } - }); + }); + } } void Pane::update() { @@ -116,17 +111,12 @@ void Pane::set_selected_item(int index) { } bool Pane::focus() { - // Update selected child - for (int i = 0; i < mChildren.size(); ++i) { - if (mChildren[i]->selected()) { - mSelectedItem = i; + // Focus the first selected child + for (const auto& child : mChildren) { + if (child->selected() && child->focus()) { + return true; } } - // If there's a selected child, focus that - if (mSelectedItem >= 0 && mSelectedItem < mChildren.size() && mChildren[mSelectedItem]->focus()) - { - return true; - } for (const auto& child : mChildren) { if (child->focus()) { return true; diff --git a/src/dusk/ui/pane.hpp b/src/dusk/ui/pane.hpp index 0f6d99a051..ffa9d31ff0 100644 --- a/src/dusk/ui/pane.hpp +++ b/src/dusk/ui/pane.hpp @@ -8,12 +8,12 @@ namespace dusk::ui { class Pane : public FluentComponent { public: - enum class Direction { - Vertical, - Horizontal, + enum class Type { + Controlled, + Uncontrolled, }; - explicit Pane(Rml::Element* parent, Direction direction); + explicit Pane(Rml::Element* parent, Type type); bool focus() override; void update() override; @@ -44,7 +44,7 @@ public: void clear(); private: - Direction mDirection; + Type mType; bool finalized = false; int mSelectedItem = -1; }; diff --git a/src/dusk/ui/settings.cpp b/src/dusk/ui/settings.cpp index c0452c3a2f..a842202ca1 100644 --- a/src/dusk/ui/settings.cpp +++ b/src/dusk/ui/settings.cpp @@ -231,8 +231,8 @@ private: SettingsWindow::SettingsWindow() { add_tab("Audio", [this](Rml::Element* content) { - auto& leftPane = add_child(content, Pane::Direction::Vertical); - auto& rightPane = add_child(content, Pane::Direction::Vertical); + auto& leftPane = add_child(content, Pane::Type::Controlled); + auto& rightPane = add_child(content, Pane::Type::Uncontrolled); leftPane.add_section("Volume"); leftPane.add_child(ConfigIntSelect::Props{ @@ -270,8 +270,8 @@ SettingsWindow::SettingsWindow() { }); add_tab("Cheats", [this](Rml::Element* content) { - auto& leftPane = add_child(content, Pane::Direction::Vertical); - auto& rightPane = add_child(content, Pane::Direction::Vertical); + auto& leftPane = add_child(content, Pane::Type::Controlled); + auto& rightPane = add_child(content, Pane::Type::Uncontrolled); auto addCheat = [&](const Rml::String& key, ConfigVar& value, const Rml::String& helpText) { @@ -315,8 +315,8 @@ SettingsWindow::SettingsWindow() { }); add_tab("Gameplay", [this](Rml::Element* content) { - auto& leftPane = add_child(content, Pane::Direction::Vertical); - auto& rightPane = add_child(content, Pane::Direction::Vertical); + auto& leftPane = add_child(content, Pane::Type::Controlled); + auto& rightPane = add_child(content, Pane::Type::Uncontrolled); auto addOption = [&](const Rml::String& key, ConfigVar& value, const Rml::String& helpText) { @@ -422,8 +422,8 @@ SettingsWindow::SettingsWindow() { }); add_tab("Graphics", [this](Rml::Element* content) { - auto& leftPane = add_child(content, Pane::Direction::Vertical); - auto& rightPane = add_child(content, Pane::Direction::Vertical); + auto& leftPane = add_child(content, Pane::Type::Controlled); + auto& rightPane = add_child(content, Pane::Type::Uncontrolled); leftPane.add_section("Display");