diff --git a/data/uking_functions.csv b/data/uking_functions.csv index 9bbe82a1..5dc371f9 100644 --- a/data/uking_functions.csv +++ b/data/uking_functions.csv @@ -56349,19 +56349,19 @@ 0x00000071009717d0,sub_71009717D0,40,_ZNK5uking2ui16PauseMenuDataMgr19getDefaultEquipmentENS0_13EquipmentSlotE 0x00000071009717f8,PauseMenuDataMgr::hasItemMaybe,404,_ZNK5uking2ui16PauseMenuDataMgr7hasItemERKN4sead14SafeStringBaseIcEE 0x000000710097198c,sub_710097198C,372,_ZNK5uking2ui16PauseMenuDataMgr14getMasterSwordEv -0x0000007100971b00,PauseMenuDataMgr::__auto7,620, -0x0000007100971d6c,sub_7100971D6C,1152, -0x00000071009721ec,sub_71009721EC,392, +0x0000007100971b00,PauseMenuDataMgr::__auto7,620,_ZN5uking2ui16PauseMenuDataMgr18removeGrabbedItemsEv +0x0000007100971d6c,sub_7100971D6C,1152,_ZN5uking2ui16PauseMenuDataMgr14addGrabbedItemEPN4ksys3act12BaseProcLinkE! +0x00000071009721ec,sub_71009721EC,392,_ZNK5uking2ui16PauseMenuDataMgr20getEquippedArrowTypeEPN4sead22BufferedSafeStringBaseIcEEPi 0x0000007100972374,PauseMenuDataMgr::getPorchNum,356,_ZNK5uking2ui16PauseMenuDataMgr13getArrowCountERKN4sead14SafeStringBaseIcEE 0x00000071009724d8,PauseMenuDataMgr::getPorchNumFromSaveOrPouch,304,_ZNK5uking2ui16PauseMenuDataMgr17getRealArrowCountERKN4sead14SafeStringBaseIcEE 0x0000007100972608,sub_7100972608,212,_ZN5uking2ui16PauseMenuDataMgr16breakMasterSwordEv 0x00000071009726dc,PauseMenuDataMgr::restoreMasterSwordLife,228,_ZN5uking2ui16PauseMenuDataMgr18restoreMasterSwordEb -0x00000071009727c0,PauseMenuDataMgr::checkWeaponFreeSlotMaybe,976, +0x00000071009727c0,PauseMenuDataMgr::checkWeaponFreeSlotMaybe,976,_ZNK5uking2ui16PauseMenuDataMgr20checkAddOrRemoveItemERKN4sead14SafeStringBaseIcEEib 0x0000007100972b90,PauseMenuDataMgr::increasePouchNum,4200, 0x0000007100973bf8,sub_7100973BF8,644, -0x0000007100973e7c,sub_7100973E7C,272, -0x0000007100973f8c,sub_7100973F8C,264, -0x0000007100974094,sub_7100974094,788, +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, @@ -73352,7 +73352,7 @@ 0x0000007100d2e010,ActorInfoData::getHorseUnitRiddenAnimalType,20, 0x0000007100d2e024,ActorInfoData::getMonsterShopBuyMamo,20, 0x0000007100d2e038,sub_7100D2E038,20, -0x0000007100d2e04c,act::getMonsterShopSellMamo,20, +0x0000007100d2e04c,act::getMonsterShopSellMamo,20,_ZN4ksys3act22getMonsterShopSellMamoERKN2al9ByamlIterE 0x0000007100d2e060,act::getPictureBookLiveSpot1,20, 0x0000007100d2e074,act::getPictureBookLiveSpot2,20, 0x0000007100d2e088,act::getPictureBookSpecialDrop,20, diff --git a/src/Game/UI/uiPauseMenuDataMgr.cpp b/src/Game/UI/uiPauseMenuDataMgr.cpp index a7c2bb92..823eca50 100644 --- a/src/Game/UI/uiPauseMenuDataMgr.cpp +++ b/src/Game/UI/uiPauseMenuDataMgr.cpp @@ -7,7 +7,10 @@ #include "Game/DLC/aocManager.h" #include "Game/UI/uiUtils.h" #include "KingSystem/ActorSystem/Profiles/actPlayerBase.h" +#include "KingSystem/ActorSystem/actActorConstDataAccess.h" #include "KingSystem/ActorSystem/actActorUtil.h" +#include "KingSystem/ActorSystem/actBaseProcLink.h" +#include "KingSystem/ActorSystem/actInfoCommon.h" #include "KingSystem/ActorSystem/actInfoData.h" #include "KingSystem/ActorSystem/actPlayerInfo.h" #include "KingSystem/Cooking/cookItem.h" @@ -138,7 +141,7 @@ PauseMenuDataMgr::PauseMenuDataMgr() { mArray1[i] = nullptr; mArray2[i] = PouchItemType::Invalid; } - for (auto& x : mArray3) + for (auto& x : mGrabbedItems) x = {}; _447e0 = {}; _447e8 = {}; @@ -180,11 +183,8 @@ void PauseMenuDataMgr::init(sead::Heap* heap) {} void PauseMenuDataMgr::initForNewSave() { const auto lock = sead::makeScopedLock(mCritSection); - for (auto* item = getItems().popFront(); item; item = getItems().popFront()) { - item->~PouchItem(); - new (item) PouchItem; - mItemLists.list2.pushFront(item); - } + for (auto* item = getItems().popFront(); item; item = getItems().popFront()) + destroyAndRecycleItem(item); mListHeads.fill(nullptr); for (s32 i = 0; i < NumPouch50; ++i) { @@ -202,7 +202,7 @@ void PauseMenuDataMgr::initForNewSave() { _444f8 = -1; resetItem(); mIsPouchForQuest = false; - for (auto& x : mArray3) + for (auto& x : mGrabbedItems) x = {}; _44504 = {}; _44508 = {}; @@ -233,7 +233,7 @@ void PauseMenuDataMgr::initForNewSave() { void PauseMenuDataMgr::loadFromGameData() { doLoadFromGameData(); - for (auto& x : mArray3) + for (auto& x : mGrabbedItems) x = {}; mLastAddedItem = {}; mItem_444f0 = {}; @@ -255,11 +255,8 @@ void PauseMenuDataMgr::doLoadFromGameData() { const auto lock = sead::makeScopedLock(mCritSection); auto& lists = mItemLists; - for (auto* item = lists.list1.popFront(); item; item = lists.list1.popFront()) { - item->~PouchItem(); - new (item) PouchItem; - mItemLists.list2.pushFront(item); - } + for (auto* item = lists.list1.popFront(); item; item = lists.list1.popFront()) + destroyAndRecycleItem(item); mListHeads.fill(nullptr); mRitoSoulItem = nullptr; @@ -885,7 +882,7 @@ void PauseMenuDataMgr::saveToGameData(const sead::OffsetList& list) c sead::FixedSafeString<64> name{item->getName()}; s32 value = item->getValue(); - for (const auto& entry : mArray3) { + for (const auto& entry : mGrabbedItems) { if (entry.item == item) { value += ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::CanStack); @@ -1051,9 +1048,7 @@ PauseMenuDataMgr::deleteItem_(const sead::OffsetList& list, PouchItem // Reset the PouchItem so that it is ready to be reused. getItems().erase(item); - item->~PouchItem(); - new (item) PouchItem; - mItemLists.list2.pushFront(item); + destroyAndRecycleItem(item); ksys::PlayReportMgr::instance()->reportDebug("PouchDelete", name); saveToGameData(list); @@ -1261,6 +1256,91 @@ PouchItem* PauseMenuDataMgr::getMasterSword() const { return nullptr; } +void PauseMenuDataMgr::removeGrabbedItems() { + const auto lock = sead::makeScopedLock(mCritSection); + 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); + } + entry = {}; + } + + const auto& items = getItems(); + mLastAddedItem = nullptr; + updateInventoryInfo(items); + updateListHeads(); + saveToGameData(items); +} + +// NON_MATCHING: mostly branching (which leads to other differences), but visibly equivalent +bool PauseMenuDataMgr::addGrabbedItem(ksys::act::BaseProcLink* link) { + if (!link || !link->hasProc()) + return false; + + ksys::act::ActorConstDataAccess accessor; + ksys::act::acquireActor(link, &accessor); + const auto name = accessor.getName(); + auto& cs = mCritSection; + const auto& items = getItems(); + bool found = false; + + for (s32 i = 0; i < NumGrabbableItems; ++i) { + auto& entry = mGrabbedItems[i]; + if (found) { + mGrabbedItems[i - 1].item = entry.item; + mGrabbedItems[i - 1]._8 = entry._8; + mGrabbedItems[i - 1]._9 = entry._9; + continue; + } + + if (!entry.item || name != entry.item->getName()) + continue; + + 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); + updateInventoryInfo(items); + updateListHeads(); + saveToGameData(items); + mLastAddedItem = nullptr; + } + + found = true; + entry = {}; + } + mGrabbedItems[4] = {}; + return found; +} + +bool PauseMenuDataMgr::getEquippedArrowType(sead::BufferedSafeString* name, int* count) const { + const auto lock = sead::makeScopedLock(mCritSection); + for (const auto* item = getItemHead(PouchCategory::Bow); item; item = nextItem(item)) { + if (item->getType() > PouchItemType::Arrow) + break; + if (item->getType() == PouchItemType::Arrow && item->get25() && item->isEquipped()) { + if (name) + name->copy(item->getName()); + if (count) + *count = item->getValue(); + return true; + } + } + return false; +} + int PauseMenuDataMgr::getArrowCount(const sead::SafeString& name) const { const auto lock = sead::makeScopedLock(mCritSection); for (auto item = getItemHead(PouchCategory::Bow); item; item = nextItem(item)) { @@ -1327,6 +1407,91 @@ void PauseMenuDataMgr::restoreMasterSword(bool only_if_broken) { } } +static s32 checkItemRemoval(const sead::OffsetList& items, const sead::SafeString& name, + int num_to_remove, bool include_equipped_items, bool stackable) { + s32 total = 0; + for (const auto& item : items) { + if (name == item.getName() && (stackable || include_equipped_items || !item.isEquipped())) { + total += stackable ? item.getValue() : 1; + if (total >= num_to_remove) + return true; + } + } + return false; +} + +bool PauseMenuDataMgr::checkAddOrRemoveItem(const sead::SafeString& name, int count, + bool include_equipped_items) const { + const auto lock = sead::makeScopedLock(mCritSection); + + static_cast(getType(name)); + if (count < 0) { + const auto* info = ksys::act::InfoData::instance(); + const int num_to_remove = -count; + const auto& items = getItems(); + if (!info->hasTag(name.cstr(), ksys::act::tags::CanStack)) + return checkItemRemoval(items, name, num_to_remove, include_equipped_items, false); + return checkItemRemoval(items, name, num_to_remove, true, true); + } + + return !cannotGetItem(name, count); +} + +int PauseMenuDataMgr::getFreeSlotCount() const { + const auto lock = sead::makeScopedLock(mCritSection); + + const s32 num_items = getItems().size(); + const s32 num_weapons = countItems(PouchItemType::Sword, true); + const s32 num_food = countItems(PouchItemType::Food); + + return NumArmorsMax + NumMaterialsMax + NumKeyItemsMax - num_items + num_weapons + num_food; +} + +int PauseMenuDataMgr::calculateEnemyMaterialMamo() const { + const auto lock = sead::makeScopedLock(mCritSection); + int value = 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, ksys::act::tags::EnemyMaterial)) { + value += ksys::act::getMonsterShopSellMamo(iter) * item.getValue(); + } + } + return value; +} + +void PauseMenuDataMgr::removeAllEnemyMaterials() { + const auto lock = sead::makeScopedLock(mCritSection); + const auto& items = getItems(); + + // Materials cannot be removed from the linked list immediately as we need to traverse it + // at the same time. Instead, only remove a material *after* we have moved to the next item. + PouchItem* material_to_remove = nullptr; + for (auto& item : items) { + if (material_to_remove != nullptr) { + getItems().erase(material_to_remove); + destroyAndRecycleItem(material_to_remove); + } + + auto* info = ksys::act::InfoData::instance(); + material_to_remove = + 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); + } + + saveToGameData(items); + updateInventoryInfo(items); + updateListHeads(); +} + using SortPredicate = int (*)(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); diff --git a/src/Game/UI/uiPauseMenuDataMgr.h b/src/Game/UI/uiPauseMenuDataMgr.h index 47821cb5..41c42216 100644 --- a/src/Game/UI/uiPauseMenuDataMgr.h +++ b/src/Game/UI/uiPauseMenuDataMgr.h @@ -16,6 +16,7 @@ class ByamlIter; } namespace ksys::act { +class BaseProcLink; class InfoData; } @@ -48,6 +49,8 @@ constexpr int ItemStackSizeMax = 999; // TODO: figure out what this is constexpr int NumPouch50 = 50; +constexpr int NumGrabbableItems = 5; + enum class PouchItemType { Sword = 0, Bow = 1, @@ -242,6 +245,10 @@ public: bool hasItem(const sead::SafeString& name) const; PouchItem* getMasterSword() const; + void removeGrabbedItems(); + bool addGrabbedItem(ksys::act::BaseProcLink* link); + + bool getEquippedArrowType(sead::BufferedSafeString* name, int* count) const; int getArrowCount(const sead::SafeString& name) const; /// Get the number of arrows in the real inventory (ignoring any temporary inventory data). /// This was added in 1.3.1 to patch the Trial of the Sword arrow restock glitch. @@ -250,6 +257,12 @@ public: void breakMasterSword(); void restoreMasterSword(bool only_if_broken); + bool checkAddOrRemoveItem(const sead::SafeString& name, int count, bool include_equipped_items) const; + int getFreeSlotCount() const; + + int calculateEnemyMaterialMamo() const; + void removeAllEnemyMaterials(); + bool isHeroSoulEnabled(const sead::SafeString& name) const; bool hasRitoSoulPlus() const; bool hasGoronSoulPlus() const; @@ -263,12 +276,12 @@ public: private: // TODO: rename - struct ItemInfo { + struct GrabbedItemInfo { PouchItem* item{}; - u8 _8{}; - u8 _9{}; + bool _8{}; + bool _9{}; }; - KSYS_CHECK_SIZE_NX150(ItemInfo, 0x10); + KSYS_CHECK_SIZE_NX150(GrabbedItemInfo, 0x10); struct Lists { Lists() { @@ -300,6 +313,12 @@ private: PouchItem* nextItem(const PouchItem* item) const { return getItems().next(item); } bool isList2Empty() const { return mItemLists.list2.isEmpty(); } + void destroyAndRecycleItem(PouchItem* item) { + item->~PouchItem(); + new (item) PouchItem; + mItemLists.list2.pushFront(item); + } + void resetItem(); void setItemModifier(PouchItem& item, const act::WeaponModifierInfo* modifier); @@ -335,7 +354,7 @@ private: s32 _44490 = -1; s32 _44494 = -1; s32 _44498{}; - sead::SafeArray mArray3; + sead::SafeArray mGrabbedItems; PouchItem* mItem_444f0{}; s32 _444f8 = -1; s32 _444fc{}; diff --git a/src/KingSystem/ActorSystem/actInfoCommon.cpp b/src/KingSystem/ActorSystem/actInfoCommon.cpp index e05903d9..8fd8fd54 100644 --- a/src/KingSystem/ActorSystem/actInfoCommon.cpp +++ b/src/KingSystem/ActorSystem/actInfoCommon.cpp @@ -115,4 +115,8 @@ bool getWeaponCommonPoweredSharpAddSurfMaster(const al::ByamlIter& iter) { return InfoData::getBoolByKey(iter, "weaponCommonPoweredSharpAddSurfMaster"); } +int getMonsterShopSellMamo(const al::ByamlIter& iter) { + return InfoData::getIntByKey(iter, "monsterShopSellMamo"); +} + } // namespace ksys::act diff --git a/src/KingSystem/ActorSystem/actInfoCommon.h b/src/KingSystem/ActorSystem/actInfoCommon.h index 0707b40c..c7bd7eb6 100644 --- a/src/KingSystem/ActorSystem/actInfoCommon.h +++ b/src/KingSystem/ActorSystem/actInfoCommon.h @@ -42,4 +42,6 @@ float getWeaponCommonPoweredSharpAddRapidFireMin(const al::ByamlIter& iter); float getWeaponCommonPoweredSharpAddRapidFireMax(const al::ByamlIter& iter); bool getWeaponCommonPoweredSharpAddSurfMaster(const al::ByamlIter& iter); +int getMonsterShopSellMamo(const al::ByamlIter& iter); + } // namespace ksys::act