diff --git a/data/uking_functions.csv b/data/uking_functions.csv index 5dc371f9..31879ead 100644 --- a/data/uking_functions.csv +++ b/data/uking_functions.csv @@ -56362,30 +56362,30 @@ 0x0000007100973e7c,sub_7100973E7C,272,_ZNK5uking2ui16PauseMenuDataMgr16getFreeSlotCountEv 0x0000007100973f8c,sub_7100973F8C,264,_ZNK5uking2ui16PauseMenuDataMgr26calculateEnemyMaterialMamoEv 0x0000007100974094,sub_7100974094,788,_ZN5uking2ui16PauseMenuDataMgr23removeAllEnemyMaterialsEv -0x00000071009743a8,PauseMenuDataMgr::countItemsWithCategory,836, -0x00000071009746ec,PauseMenuDataMgr::countItemsWithTag,412, -0x0000007100974888,sub_7100974888,828, -0x0000007100974bc4,PauseMenuDataMgr::countItemsWithPouchCategory,228, -0x0000007100974ca8,sub_7100974CA8,68, -0x0000007100974cec,sub_7100974CEC,1556, -0x0000007100975300,PauseMenuDataMgr::__auto13,852, -0x0000007100975654,sub_7100975654,872, -0x00000071009759bc,sub_71009759BC,408, -0x0000007100975b54,sub_7100975B54,1876, +0x00000071009743a8,PauseMenuDataMgr::countItemsWithCategory,836,_ZNK5uking2ui16PauseMenuDataMgr21countItemsWithProfileERKN4sead14SafeStringBaseIcEEb +0x00000071009746ec,PauseMenuDataMgr::countItemsWithTag,412,_ZNK5uking2ui16PauseMenuDataMgr17countItemsWithTagEjb +0x0000007100974888,sub_7100974888,828,_ZNK5uking2ui16PauseMenuDataMgr16countCookResultsERKN4sead14SafeStringBaseIcEEib +0x0000007100974bc4,PauseMenuDataMgr::countItemsWithPouchCategory,228,_ZNK5uking2ui16PauseMenuDataMgr22countItemsWithCategoryENS0_13PouchCategoryE +0x0000007100974ca8,sub_7100974CA8,68,_ZNK5uking2ui16PauseMenuDataMgr18getCategoryForTypeENS0_13PouchItemTypeE +0x0000007100974cec,sub_7100974CEC,1556,_ZN5uking2ui16PauseMenuDataMgr16removeCookResultERKN4sead14SafeStringBaseIcEEib? +0x0000007100975300,PauseMenuDataMgr::__auto13,852,_ZN5uking2ui16PauseMenuDataMgr15switchEquipmentERKN4sead14SafeStringBaseIcEEPiPNS_3act18WeaponModifierInfoE +0x0000007100975654,sub_7100975654,872,_ZN5uking2ui16PauseMenuDataMgr17initPouchForQuestEv +0x00000071009759bc,sub_71009759BC,408,_ZN5uking2ui16PauseMenuDataMgr20restorePouchForQuestEv +0x0000007100975b54,sub_7100975B54,1876,_ZN5uking2ui10getItemUseERKN4sead14SafeStringBaseIcEE 0x00000071009762a8,sub_71009762A8,504, 0x00000071009764a0,sub_71009764A0,568, 0x00000071009766d8,PauseMenuDataMgr::trashItem,1516, 0x0000007100976cc4,PauseMenuDataMgr::useMaybe,1124, 0x0000007100977128,sub_7100977128,868, -0x000000710097748c,sub_710097748C,628, +0x000000710097748c,sub_710097748C,628,_ZN5uking2ui16PauseMenuDataMgr9sortItemsENS0_13PouchCategoryEb 0x0000007100977700,sub_7100977700,248,_ZN5uking2ui22pouchItemSortPredicateEPKNS0_9PouchItemES3_ 0x00000071009777f8,sub_71009777F8,272, 0x0000007100977908,sub_7100977908,288, 0x0000007100977a28,sub_7100977A28,272, -0x0000007100977b38,sub_7100977B38,336,_ZN5uking2ui9sortArmorEPKNS0_9PouchItemES3_PN4ksys3act8InfoDataE +0x0000007100977b38,sub_7100977B38,336,_ZN5uking2ui12compareArmorEPKNS0_9PouchItemES3_PN4ksys3act8InfoDataE 0x0000007100977c88,sub_7100977C88,308, 0x0000007100977dbc,sub_7100977DBC,540, -0x0000007100977fd8,sub_7100977FD8,116,_ZN5uking2ui11sortKeyItemEPKNS0_9PouchItemES3_PN4ksys3act8InfoDataE +0x0000007100977fd8,sub_7100977FD8,116,_ZN5uking2ui14compareKeyItemEPKNS0_9PouchItemES3_PN4ksys3act8InfoDataE 0x000000710097804c,sub_710097804C,676, 0x00000071009782f0,sub_71009782F0,664, 0x0000007100978588,sub_7100978588,480,_ZN5uking2ui16getCookItemOrderEPKNS0_9PouchItemEPN4ksys3act8InfoDataE @@ -56395,15 +56395,15 @@ 0x0000007100978c38,sub_7100978C38,488, 0x0000007100978e20,sub_7100978E20,548, 0x0000007100979044,PauseMenuDataMgr::__auto8,480, -0x0000007100979224,PauseMenuDataMgr::__auto11,208, -0x00000071009792f4,PauseMenuDataMgr::__auto12,212, -0x00000071009793c8,sub_71009793C8,588, -0x0000007100979614,PauseMenuDataMgr::pouchGetFromShop,496, +0x0000007100979224,PauseMenuDataMgr::__auto11,208,_ZNK5uking2ui16PauseMenuDataMgr19getEquippedItemNameENS0_13PouchItemTypeE? +0x00000071009792f4,PauseMenuDataMgr::__auto12,212,_ZNK5uking2ui16PauseMenuDataMgr15getEquippedItemENS0_13PouchItemTypeE +0x00000071009793c8,sub_71009793C8,588,_ZNK5uking2ui16PauseMenuDataMgr12getItemValueERKN4sead14SafeStringBaseIcEE +0x0000007100979614,PauseMenuDataMgr::pouchGetFromShop,496,_ZN5uking2ui16PauseMenuDataMgr11getFromShopERKN4sead14SafeStringBaseIcEEiPKNS_3act18WeaponModifierInfoE 0x0000007100979804,sub_7100979804,316, 0x0000007100979940,sub_7100979940,120, 0x00000071009799b8,PauseMenuDataMgr::pouchUseFromRecipe,2688, -0x000000710097a438,PauseMenuDataMgr::__auto0,196, -0x000000710097a4fc,sub_710097A4FC,204, +0x000000710097a438,PauseMenuDataMgr::__auto0,196,_ZNK5uking2ui16PauseMenuDataMgr13countArmorDyeEv +0x000000710097a4fc,sub_710097A4FC,204,_ZNK5uking2ui16PauseMenuDataMgr21countAlreadyDyedArmorEv 0x000000710097a5c8,sub_710097A5C8,516, 0x000000710097a7cc,sub_710097A7CC,376, 0x000000710097a944,sub_710097A944,184, diff --git a/expected/_ZN5uking2ui16PauseMenuDataMgr16removeCookResultERKN4sead14SafeStringBaseIcEEib.bin b/expected/_ZN5uking2ui16PauseMenuDataMgr16removeCookResultERKN4sead14SafeStringBaseIcEEib.bin new file mode 100644 index 00000000..137bd626 Binary files /dev/null and b/expected/_ZN5uking2ui16PauseMenuDataMgr16removeCookResultERKN4sead14SafeStringBaseIcEEib.bin differ diff --git a/src/Game/UI/uiPauseMenuDataMgr.cpp b/src/Game/UI/uiPauseMenuDataMgr.cpp index 823eca50..8ab38975 100644 --- a/src/Game/UI/uiPauseMenuDataMgr.cpp +++ b/src/Game/UI/uiPauseMenuDataMgr.cpp @@ -197,10 +197,7 @@ void PauseMenuDataMgr::initForNewSave() { ksys::gdt::setFlag_DungeonClearSealNum(0); ksys::gdt::setFlag_FairyCountCheck(false); _444fc = {}; - mLastAddedItem = {}; - mItem_444f0 = {}; - _444f8 = -1; - resetItem(); + resetItemAndPointers(); mIsPouchForQuest = false; for (auto& x : mGrabbedItems) x = {}; @@ -235,10 +232,7 @@ void PauseMenuDataMgr::loadFromGameData() { for (auto& x : mGrabbedItems) x = {}; - mLastAddedItem = {}; - mItem_444f0 = {}; - _444f8 = -1; - resetItem(); + resetItemAndPointers(); _444fc = 0; mIsPouchForQuest = false; _447e0 = {}; @@ -735,7 +729,7 @@ void PauseMenuDataMgr::updateAfterAddingItem(bool only_sort) { if (getItems().isEmpty()) return; - _44800 = PouchCategory::Invalid; + mCategoryToSort = PouchCategory::Invalid; auto& items = getItems(); items.sort(pouchItemSortPredicateForArrow); if (!only_sort) { @@ -1040,16 +1034,7 @@ void PauseMenuDataMgr::unequipAll(PouchItemType type) { KSYS_ALWAYS_INLINE inline void PauseMenuDataMgr::deleteItem_(const sead::OffsetList& list, PouchItem* item, const sead::SafeString& name) { - if (mItem_444f0 == item) - mItem_444f0 = nullptr; - - if (mLastAddedItem == item) - mLastAddedItem = nullptr; - - // Reset the PouchItem so that it is ready to be reused. - getItems().erase(item); - destroyAndRecycleItem(item); - + destroyAndRecycleItem(mItemLists, item); ksys::PlayReportMgr::instance()->reportDebug("PouchDelete", name); saveToGameData(list); updateInventoryInfo(list); @@ -1261,14 +1246,8 @@ void PauseMenuDataMgr::removeGrabbedItems() { for (s32 i = 0, n = mGrabbedItems.size(); i < n; ++i) { auto& entry = mGrabbedItems[i]; auto* item = entry.item; - if (item && item->getValue() == 0 && !entry._9) { - if (mItem_444f0 == item) - mItem_444f0 = nullptr; - if (mLastAddedItem == item) - mLastAddedItem = nullptr; - getItems().erase(item); - destroyAndRecycleItem(item); - } + if (item && item->getValue() == 0 && !entry._9) + destroyAndRecycleItem(mItemLists, item); entry = {}; } @@ -1306,12 +1285,7 @@ bool PauseMenuDataMgr::addGrabbedItem(ksys::act::BaseProcLink* link) { if (entry.item->getValue() == 0 && !entry._9) { const auto lock = sead::makeScopedLock(cs); auto* item = entry.item; - if (mItem_444f0 == item) - mItem_444f0 = nullptr; - if (mLastAddedItem == item) - mLastAddedItem = nullptr; - getItems().erase(item); - destroyAndRecycleItem(item); + destroyAndRecycleItem(mItemLists, item); updateInventoryInfo(items); updateListHeads(); saveToGameData(items); @@ -1478,24 +1452,362 @@ void PauseMenuDataMgr::removeAllEnemyMaterials() { info->hasTag(item.getName().cstr(), ksys::act::tags::EnemyMaterial) ? &item : nullptr; } - if (material_to_remove) { - if (mItem_444f0 == material_to_remove) - mItem_444f0 = nullptr; - if (mLastAddedItem == material_to_remove) - mLastAddedItem = nullptr; - getItems().erase(material_to_remove); - destroyAndRecycleItem(material_to_remove); - } + if (material_to_remove) + destroyAndRecycleItem(mItemLists, material_to_remove); saveToGameData(items); updateInventoryInfo(items); updateListHeads(); } +int PauseMenuDataMgr::countItemsWithProfile(const sead::SafeString& profile, + bool count_stacked_items) const { + const auto lock = sead::makeScopedLock(mCritSection); + s32 count = 0; + for (const auto& item : getItems()) { + const char* item_profile; + al::ByamlIter iter; + if (ksys::act::InfoData::instance()->getActorIter(&iter, item.getName().cstr()) && + ksys::act::InfoData::instance()->getActorProfile(&item_profile, iter) && + profile == item_profile) { + if (count_stacked_items && + ksys::act::InfoData::instance()->hasTag(iter, ksys::act::tags::CanStack)) { + count += item.getValue(); + } else { + count += 1; + } + } + } + return count; +} + +int PauseMenuDataMgr::countItemsWithTag(u32 tag, bool count_stacked_items) const { + const auto lock = sead::makeScopedLock(mCritSection); + s32 count = 0; + for (const auto& item : getItems()) { + al::ByamlIter iter; + if (ksys::act::InfoData::instance()->getActorIter(&iter, item.getName().cstr()) && + ksys::act::InfoData::instance()->hasTag(iter, tag)) { + if (count_stacked_items && + ksys::act::InfoData::instance()->hasTag(iter, ksys::act::tags::CanStack)) { + count += item.getValue(); + } else { + count += 1; + } + } + } + return count; +} + +int PauseMenuDataMgr::countCookResults(const sead::SafeString& name, s32 effect_type, + bool check_effect_type) const { + const auto lock = sead::makeScopedLock(mCritSection); + + auto* info = ksys::act::InfoData::instance(); + if (!info) + return 0; + + const bool check_name = !name.isEmpty(); + if (check_name && !info->hasTag(name.cstr(), ksys::act::tags::CookResult)) + return 0; + + s32 count = 0; + for (auto* item = getItemHead(PouchCategory::Food); item; item = nextItem(item)) { + if (item->getType() != PouchItemType::Food) + break; + if (!item->get25()) + continue; + if (!info->hasTag(item->getName().cstr(), ksys::act::tags::CookResult)) + continue; + if (check_effect_type && item->getCookData().mCookEffect0.x != effect_type) + continue; + if (check_name && item->getName() != name) + continue; + ++count; + } + return count; +} + +int PauseMenuDataMgr::countItemsWithCategory(PouchCategory category) const { + const auto lock = sead::makeScopedLock(mCritSection); + if (u32(category) > 6) + return 0; + s32 count = 0; + for (auto* item = getItemHead(category); item; item = nextItem(item)) { + const auto type = item->getType(); + if (getCategoryForType(type) != category) + break; + count += item->get25(); + } + return count; +} + +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +PouchCategory PauseMenuDataMgr::getCategoryForType(PouchItemType type) const { + if (type == PouchItemType::Sword) + return PouchCategory::Sword; + + if (isPouchItemBowOrArrow(type)) + return PouchCategory::Bow; + + switch (type) { + case PouchItemType::Shield: + return PouchCategory::Shield; + case PouchItemType::ArmorHead: + case PouchItemType::ArmorUpper: + case PouchItemType::ArmorLower: + return PouchCategory::Armor; + case PouchItemType::Material: + return PouchCategory::Material; + case PouchItemType::Food: + return PouchCategory::Food; + case PouchItemType::KeyItem: + return PouchCategory::KeyItem; + default: + return PouchCategory::Invalid; + } +} + +// NON_MATCHING: two harmless reorderings +void PauseMenuDataMgr::removeCookResult(const sead::SafeString& name, s32 effect_type, + bool check_effect) { + auto* info = ksys::act::InfoData::instance(); + if (!info) + return; + + const bool check_name = !name.isEmpty(); + if (check_name && !info->hasTag(name.cstr(), ksys::act::tags::CookResult)) + return; + + const auto lock = sead::makeScopedLock(mCritSection); + const auto& items = getItems(); + + auto min_x = std::numeric_limits::max(); + auto min_y = std::numeric_limits::infinity(); + auto min_fx = std::numeric_limits::infinity(); + PouchItem* to_remove = nullptr; + + for (auto* item = getItemHead(PouchCategory::Food); item; item = items.next(item)) { + if (item->getType() != PouchItemType::Food) + break; + if (!item->get25()) + continue; + if (!info->hasTag(item->getName().cstr(), ksys::act::tags::CookResult)) + continue; + if (item->getCookData().mCookEffect0.x != effect_type && check_effect) + continue; + if (check_name && item->getName() != name) + continue; + + const auto y = f32(item->getCookData().mStaminaRecoverY) * 30.0f; + if (y < min_y) { + min_x = item->getCookData().mStaminaRecoverX; + min_y = y; + to_remove = item; + min_fx = item->getCookData().mCookEffect0.y; + } else if (y == min_y) { + const auto x = item->getCookData().mStaminaRecoverX; + if (x < min_x) { + min_x = x; + to_remove = item; + min_fx = item->getCookData().mCookEffect0.y; + } else if (check_effect && x == min_x && item->getCookData().mCookEffect0.y < min_fx) { + min_fx = item->getCookData().mCookEffect0.y; + to_remove = item; + } + } + } + + if (!to_remove) + return; + + destroyAndRecycleItem(mItemLists, to_remove); + ksys::PlayReportMgr::instance()->reportDebug("PouchDeleteFromFlow", name); + saveToGameData(items); + updateInventoryInfo(items); + updateListHeads(); +} + +bool PauseMenuDataMgr::switchEquipment(const sead::SafeString& name, int* value, + act::WeaponModifierInfo* modifier) { + const auto lock = sead::makeScopedLock(mCritSection); + const auto& items = getItems(); + + sead::SafeString group_name; + getSameGroupActorName(&group_name, name); + + PouchItem* target = nullptr; + + const auto type = getType(group_name); + if (type <= PouchItemType::Shield) { + for (auto& item : items) { + if (item.getType() > PouchItemType::Shield) + break; + if (item.getName() == group_name && (!target || item.getValue() < target->getValue())) + target = &item; + } + } else { + if (type >= PouchItemType::Material) + return false; + for (auto& item : items) { + if (item.getType() >= PouchItemType::Material) + break; + if (item.getName() == group_name) { + target = &item; + break; + } + } + } + + if (!target) + return false; + + if (value) + *value = target->getValue(); + + if (type == PouchItemType::Arrow) { + if (target->getValue() > 0) { + autoEquip(target, items); + return true; + } + return false; + } + + autoEquip(target, items); + + const bool is_weapon = type <= PouchItemType::Shield; + if (modifier && is_weapon) + modifier->fromItem(*target); + + return true; +} + +void PauseMenuDataMgr::initPouchForQuest() { + mIsPouchForQuest = true; + resetItemAndPointers(); + + { + const auto lock = sead::makeScopedLock(mCritSection); + const auto& items = getItems(); + PouchItem* to_remove = nullptr; + + for (auto& item : items) { + if (to_remove != nullptr) { + getItems().erase(to_remove); + destroyAndRecycleItem(to_remove); + } + + to_remove = item.getType() == PouchItemType::KeyItem ? nullptr : &item; + } + + if (to_remove) { + getItems().erase(to_remove); + destroyAndRecycleItem(to_remove); + } + + updateInventoryInfo(items); + updateListHeads(); + } + + ksys::gdt::setFlag_FairyCountCheck(false); + createPlayerEquipment(); +} + +void PauseMenuDataMgr::restorePouchForQuest() { + mIsPouchForQuest = false; + doLoadFromGameData(); + for (auto& entry : mGrabbedItems) + entry = {}; + resetItemAndPointers(); + createPlayerEquipment(); +} + +ItemUse getItemUse(const sead::SafeString& name) { + const char* profile_c; + if (!ksys::act::InfoData::instance()->getActorProfile(&profile_c, name.cstr())) + return ItemUse::Invalid; + + const sead::SafeString profile = profile_c; + + if (profile == sValues.WeaponSmallSword) + return ItemUse::WeaponSmallSword; + + if (profile == sValues.WeaponLargeSword) + return ItemUse::WeaponLargeSword; + + if (profile == sValues.WeaponSpear) + return ItemUse::WeaponSpear; + + if (profile == sValues.WeaponBow) + return ItemUse::WeaponBow; + + if (profile == sValues.WeaponShield) + return ItemUse::WeaponShield; + + if (profile == sValues.ArmorHead) + return ItemUse::ArmorHead; + + if (profile == sValues.ArmorUpper) + return ItemUse::ArmorUpper; + + if (profile == sValues.ArmorLower) + return ItemUse::ArmorLower; + + if (profile != sValues.Item && profile != sValues.PlayerItem) + return ItemUse::Item; + + al::ByamlIter iter; + if (!ksys::act::InfoData::instance()->getActorIter(&iter, name.cstr())) + return ItemUse::Item; + + if (ksys::act::InfoData::instance()->hasTag(iter, ksys::act::tags::CureItem)) + return ItemUse::CureItem; + + if (ksys::act::InfoData::instance()->hasTag(iter, ksys::act::tags::Important)) + return ItemUse::ImportantItem; + + return ItemUse::Item; +} + +void PauseMenuDataMgr::sortItems(PouchCategory category, bool do_not_save) { + const auto lock = sead::makeScopedLock(mCritSection); + auto& items = getItems(); + + if (items.isEmpty()) + return; + + mCategoryToSort = category; + items.mergeSort(pouchItemSortPredicate); + + switch (category) { + case PouchCategory::Sword: + ksys::gdt::setFlag_SortTypeWeaponPouch(!ksys::gdt::getFlag_SortTypeWeaponPouch()); + break; + case PouchCategory::Bow: + ksys::gdt::setFlag_SortTypeBowPouch(!ksys::gdt::getFlag_SortTypeBowPouch()); + break; + case PouchCategory::Shield: + ksys::gdt::setFlag_SortTypeShieldPouch(!ksys::gdt::getFlag_SortTypeShieldPouch()); + break; + case PouchCategory::Armor: + ksys::gdt::setFlag_SortTypeArmorPouch(!ksys::gdt::getFlag_SortTypeArmorPouch()); + break; + default: + break; + } + + if (do_not_save) + return; + + updateInventoryInfo(items); + updateListHeads(); + saveToGameData(items); +} + using SortPredicate = int (*)(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); -static PouchCategory getTypeForCategory(PouchItemType type) { +static PouchCategory getCategoryForTypeWithLookupTable(PouchItemType type) { static constexpr sead::SafeArray sMap{{ PouchCategory::Sword, // Weapon PouchCategory::Bow, // Bow @@ -1512,8 +1824,8 @@ static PouchCategory getTypeForCategory(PouchItemType type) { } static auto getSortPredicateTable() { - sead::SafeArray table{ - {sortWeapon, sortBow, sortShield, sortArmor, sortMaterial, sortFood, sortKeyItem}}; + sead::SafeArray table{{compareWeapon, compareBow, compareShield, compareArmor, + compareMaterial, compareFood, compareKeyItem}}; return table; } @@ -1525,8 +1837,8 @@ int pouchItemSortPredicate(const PouchItem* lhs, const PouchItem* rhs) { if (!info_data || !lhs->get25() || !rhs->get25()) return 0; - const auto cat1 = getTypeForCategory(lhs->getType()); - const auto cat2 = getTypeForCategory(rhs->getType()); + const auto cat1 = getCategoryForTypeWithLookupTable(lhs->getType()); + const auto cat2 = getCategoryForTypeWithLookupTable(rhs->getType()); if (cat1 != cat2) return 0; @@ -1551,7 +1863,7 @@ static s32 compareSortKeys(const PouchItem* lhs, const PouchItem* rhs, ksys::act return 0; } -int sortArmor(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) { +int compareArmor(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) { if (ksys::gdt::getFlag_SortTypeArmorPouch()) { if (auto cmp = compareSortKeys(lhs, rhs, data)) return cmp; @@ -1578,7 +1890,7 @@ int sortArmor(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* d return 0; } -int sortKeyItem(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) { +int compareKeyItem(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) { if (auto cmp = compareSortKeys(lhs, rhs, data)) return cmp; return 0; @@ -1620,8 +1932,8 @@ int pouchItemSortPredicateForArrow(const PouchItem* lhs, const PouchItem* rhs) { if (!info_data || !lhs->get25() || !rhs->get25()) return 0; - const auto cat1 = getTypeForCategory(lhs->getType()); - const auto cat2 = getTypeForCategory(rhs->getType()); + const auto cat1 = getCategoryForTypeWithLookupTable(lhs->getType()); + const auto cat2 = getCategoryForTypeWithLookupTable(rhs->getType()); if (cat1 != cat2) return 0; @@ -1636,6 +1948,111 @@ int pouchItemSortPredicateForArrow(const PouchItem* lhs, const PouchItem* rhs) { return (*fn)(lhs, rhs, info_data); } +// NON_MATCHING: branching, but this is so trivial it isn't worth spending time on matching this +const sead::SafeString* PauseMenuDataMgr::getEquippedItemName(PouchItemType type) const { + const auto lock = sead::makeScopedLock(mCritSection); + const auto& items = getItems(); + + if (!isPouchItemEquipment(type) || items.isEmpty()) + return nullptr; + + auto* first = type <= PouchItemType::Shield ? items.nth(0) : getItemHead(PouchCategory::Armor); + for (auto* item = first; item; item = items.next(item)) { + if (item->isEquipped() && item->getType() == type) + return &item->getName(); + } + return nullptr; +} + +const PouchItem* PauseMenuDataMgr::getEquippedItem(PouchItemType type) const { + const auto lock = sead::makeScopedLock(mCritSection); + const auto& items = getItems(); + + if (!isPouchItemEquipment(type) || items.isEmpty()) + return nullptr; + + auto* first = type <= PouchItemType::Shield ? items.nth(0) : getItemHead(PouchCategory::Armor); + for (auto* item = first; item; item = items.next(item)) { + if (item->isEquipped() && item->getType() == type) + return item; + } + return nullptr; +} + +int PauseMenuDataMgr::getItemValue(const sead::SafeString& name) const { + const auto type = getType(name); + if (isPouchItemInvalid(type)) + return 0; + + sead::SafeString group_name; + getSameGroupActorName(&group_name, name); + + if (ksys::act::InfoData::instance()->hasTag(group_name.cstr(), ksys::act::tags::CanStack)) { + for (const auto& item : mItemLists.buffer) { + if (item.get25() && item.getType() == type && !group_name.comparen(item.getName(), 64)) + return item.getValue(); + } + return 0; + } + + s32 count = 0; + for (const auto& item : mItemLists.buffer) { + if (item.get25() && item.getType() == type && !group_name.comparen(item.getName(), 64)) + count++; + } + return count; +} + +bool PauseMenuDataMgr::getFromShop(const sead::SafeString& name, int value, + const act::WeaponModifierInfo* modifier) { + const auto lock = sead::makeScopedLock(mCritSection); + auto& lists = mItemLists; + const auto type = getType(name); + + al::ByamlIter iter; + namespace act = ksys::act; + act::InfoData::instance()->getActorIter(&iter, name.cstr()); + + if (iter.isValid() && act::InfoData::instance()->hasTag(iter, act::tags::CanStack)) { + addToPouch(name, type, lists, value, false, modifier); + } else if (iter.isValid() && act::InfoData::instance()->hasTag(iter, act::tags::ArmorDye)) { + addToPouch(name, type, lists, 0, false, modifier); + } else if (type > PouchItemType::Shield || type == PouchItemType::Arrow) { + addToPouch(name, type, lists, 1, false, modifier); + } else { + int life = getItemGeneralLife(name.cstr()); + if (modifier) + life += modifier->flags.isOn(uking::act::WeaponModifier::AddLife) ? modifier->value : 0; + addToPouch(name, type, lists, life, false, modifier); + } + + ksys::PlayReportMgr::instance()->reportDebug("PouchGetFromShop", name); + saveToGameData(lists.list1); + return true; +} + +int PauseMenuDataMgr::countArmorDye() const { + int count = 0; + using namespace ksys::act; + for (const auto& item : getItems()) { + if (item.get25() && InfoData::instance()->hasTag(item.getName().cstr(), tags::ArmorDye)) + ++count; + } + return count; +} + +int PauseMenuDataMgr::countAlreadyDyedArmor() const { + int count = 0; + using namespace ksys::act; + for (const auto& item : getItems()) { + if (item.get25() && InfoData::instance()->hasTag(item.getName().cstr(), tags::ArmorDye) && + item.getValue() > 0) { + ++count; + } + } + return count; +} + bool PauseMenuDataMgr::isHeroSoulEnabled(const sead::SafeString& name) const { if (name == sValues.Obj_HeroSoul_Zora || name == sValues.Obj_DLC_HeroSoul_Zora) { if (mZoraSoulItem) diff --git a/src/Game/UI/uiPauseMenuDataMgr.h b/src/Game/UI/uiPauseMenuDataMgr.h index 41c42216..8e75857f 100644 --- a/src/Game/UI/uiPauseMenuDataMgr.h +++ b/src/Game/UI/uiPauseMenuDataMgr.h @@ -18,7 +18,7 @@ class ByamlIter; namespace ksys::act { class BaseProcLink; class InfoData; -} +} // namespace ksys::act namespace uking::act { struct WeaponModifierInfo; @@ -72,6 +72,10 @@ constexpr bool isPouchItemWeapon(PouchItemType type) { type == PouchItemType::Arrow || type == PouchItemType::Shield; } +constexpr bool isPouchItemBowOrArrow(PouchItemType type) { + return type == PouchItemType::Bow || type == PouchItemType::Arrow; +} + constexpr bool isPouchItemNotWeapon(PouchItemType type) { return !isPouchItemWeapon(type); } @@ -80,6 +84,10 @@ constexpr bool isPouchItemArmor(PouchItemType type) { return PouchItemType::ArmorHead <= type && type <= PouchItemType::ArmorLower; } +constexpr bool isPouchItemEquipment(PouchItemType type) { + return isPouchItemWeapon(type) || isPouchItemArmor(type); +} + constexpr bool isPouchItemInvalid(PouchItemType type) { return u32(type) > u32(PouchItemType::KeyItem); } @@ -107,6 +115,21 @@ enum class EquipmentSlot { ArmorLower = 6, }; +enum class ItemUse { + WeaponSmallSword = 0, + WeaponLargeSword = 1, + WeaponSpear = 2, + WeaponBow = 3, + WeaponShield = 4, + ArmorHead = 5, + ArmorUpper = 6, + ArmorLower = 7, + Item = 8, + ImportantItem = 9, + CureItem = 10, + Invalid = -1, +}; + struct CookTagInfo { u32 is_tag; sead::SafeString name; @@ -257,12 +280,41 @@ public: void breakMasterSword(); void restoreMasterSword(bool only_if_broken); - bool checkAddOrRemoveItem(const sead::SafeString& name, int count, bool include_equipped_items) const; + bool checkAddOrRemoveItem(const sead::SafeString& name, int count, + bool include_equipped_items) const; int getFreeSlotCount() const; int calculateEnemyMaterialMamo() const; void removeAllEnemyMaterials(); + int countItemsWithProfile(const sead::SafeString& profile, bool count_stacked_items) const; + int countItemsWithTag(u32 tag, bool count_stacked_items) const; + int countCookResults(const sead::SafeString& name = {}, s32 effect_type = 0x11, + bool check_effect_type = false) const; + int countItemsWithCategory(PouchCategory category) const; + PouchCategory getCategoryForType(PouchItemType type) const; + + void removeCookResult(const sead::SafeString& name = {}, s32 effect_type = 0x11, + bool check_effect = false); + + bool switchEquipment(const sead::SafeString& name, int* value = nullptr, + act::WeaponModifierInfo* modifier = nullptr); + + void initPouchForQuest(); + void restorePouchForQuest(); + + void sortItems(PouchCategory category, bool do_not_save = false); + + const sead::SafeString* getEquippedItemName(PouchItemType type) const; + const PouchItem* getEquippedItem(PouchItemType type) const; + int getItemValue(const sead::SafeString& name) const; + + bool getFromShop(const sead::SafeString& name, int value, + const act::WeaponModifierInfo* modifier = nullptr); + + int countArmorDye() const; + int countAlreadyDyedArmor() const; + bool isHeroSoulEnabled(const sead::SafeString& name) const; bool hasRitoSoulPlus() const; bool hasGoronSoulPlus() const; @@ -272,7 +324,7 @@ public: bool isOverCategoryLimit(PouchItemType type) const; void openItemCategoryIfNeeded() const; - auto get44800() const { return _44800; } + auto get44800() const { return mCategoryToSort; } private: // TODO: rename @@ -293,6 +345,13 @@ private: } } + void destroyAndRecycleItem(PouchItem* item) { + list1.erase(item); + item->~PouchItem(); + new (item) PouchItem; + list2.pushFront(item); + } + sead::OffsetList list1; sead::OffsetList list2; sead::SafeArray buffer; @@ -319,7 +378,24 @@ private: mItemLists.list2.pushFront(item); } + void destroyAndRecycleItem(Lists& lists, PouchItem* item) { + if (mItem_444f0 == item) + mItem_444f0 = nullptr; + if (mLastAddedItem == item) + mLastAddedItem = nullptr; + + lists.destroyAndRecycleItem(item); + } + void resetItem(); + + void resetItemAndPointers() { + mLastAddedItem = nullptr; + mItem_444f0 = nullptr; + _444f8 = -1; + resetItem(); + } + void setItemModifier(PouchItem& item, const act::WeaponModifierInfo* modifier); void doLoadFromGameData(); @@ -378,18 +454,20 @@ private: u64 _447e8; u64 _447f0; u64 _447f8; - PouchCategory _44800 = PouchCategory::Invalid; + PouchCategory mCategoryToSort = PouchCategory::Invalid; }; KSYS_CHECK_SIZE_NX150(PauseMenuDataMgr, 0x44808); -int sortWeapon(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); -int sortBow(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); -int sortShield(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); -int sortArmor(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); -int sortMaterial(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); -int sortFood(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); -int sortKeyItem(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); +int compareWeapon(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); +int compareBow(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); +int compareShield(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); +int compareArmor(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); +int compareMaterial(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); +int compareFood(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); +int compareKeyItem(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); int getCookItemOrder(const PouchItem* item, ksys::act::InfoData* data); +ItemUse getItemUse(const sead::SafeString& name); + } // namespace uking::ui diff --git a/src/Game/UI/uiUtils.h b/src/Game/UI/uiUtils.h index 4330c4eb..e9ad6457 100644 --- a/src/Game/UI/uiUtils.h +++ b/src/Game/UI/uiUtils.h @@ -7,4 +7,7 @@ namespace uking::ui { int getWeaponInventoryLife(const sead::SafeString& name); bool isMasterSwordActorName(const sead::SafeString& name); +// TODO: move this to another translation unit (TBD) +int getItemGeneralLife(const char* name); + } // namespace uking::ui