From 0ac3ba93adef997910b184bae8c7a4fd133dd69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Mon, 18 Jan 2021 16:42:28 +0100 Subject: [PATCH] uking/ui: Add more inventory functions --- data/uking_functions.csv | 22 +-- lib/sead | 2 +- src/Game/UI/uiPauseMenuDataMgr.cpp | 258 ++++++++++++++++++++++++- src/Game/UI/uiPauseMenuDataMgr.h | 29 ++- src/KingSystem/CMakeLists.txt | 1 + src/KingSystem/Cooking/CMakeLists.txt | 6 + src/KingSystem/Cooking/cookItem.cpp | 7 + src/KingSystem/Cooking/cookItem.h | 24 +++ src/KingSystem/Cooking/cookManager.cpp | 1 + src/KingSystem/Cooking/cookManager.h | 15 ++ 10 files changed, 350 insertions(+), 15 deletions(-) create mode 100644 src/KingSystem/Cooking/CMakeLists.txt create mode 100644 src/KingSystem/Cooking/cookItem.cpp create mode 100644 src/KingSystem/Cooking/cookItem.h create mode 100644 src/KingSystem/Cooking/cookManager.cpp create mode 100644 src/KingSystem/Cooking/cookManager.h diff --git a/data/uking_functions.csv b/data/uking_functions.csv index 09962bda..9bbe82a1 100644 --- a/data/uking_functions.csv +++ b/data/uking_functions.csv @@ -383,7 +383,7 @@ 0x0000007100009ffc,_ZNK4sead15RuntimeTypeInfo6DeriveINS_4HeapEE9isDerivedEPKNS0_9InterfaceE,140, 0x000000710000a088,_ZNK4sead15RuntimeTypeInfo6DeriveINS_15ResourceFactoryEE9isDerivedEPKNS0_9InterfaceE,140, 0x000000710000a114,CookResult::construct,316, -0x000000710000a250,CookItem::ctor,384, +0x000000710000a250,CookItem::ctor,384,_ZN4ksys8CookItemC1Ev 0x000000710000a3d0,sub_710000A3D0,532, 0x000000710000a5e4,act::copyCookResultToCookItem,736, 0x000000710000a8c4,j_GameObject::m0,4, @@ -56335,16 +56335,16 @@ 0x000000710096efb8,PauseMenuDataMgr::__auto4,688,_ZN5uking2ui16PauseMenuDataMgr7itemGetERKN4sead14SafeStringBaseIcEEiPKNS_3act18WeaponModifierInfoE 0x000000710096f268,PauseMenuDataMgr::addToPouch,1876,_ZN5uking2ui16PauseMenuDataMgr10addToPouchERKN4sead14SafeStringBaseIcEENS0_13PouchItemTypeERNS1_5ListsEibPKNS_3act18WeaponModifierInfoEb 0x000000710096f9bc,PauseMenuDataMgr::saveToGameData,1700,_ZNK5uking2ui16PauseMenuDataMgr14saveToGameDataERKN4sead10OffsetListINS0_9PouchItemEEE -0x0000007100970060,PauseMenuDataMgr::pouchGetCookResultMaybe,248, -0x0000007100970158,PauseMenuDataMgr::x_1,268, -0x0000007100970264,PauseMenuDataMgr::__auto5,116, -0x00000071009702d8,sub_71009702D8,268, -0x00000071009703e4,PauseMenuDataMgr::__auto10,216, -0x00000071009704bc,PauseMenuDataMgr::removeOneFromInventory,1352, -0x0000007100970a04,PauseMenuDataMgr::removeFromInventory_,896, +0x0000007100970060,PauseMenuDataMgr::pouchGetCookResultMaybe,248,_ZN5uking2ui16PauseMenuDataMgr11cookItemGetERKN4ksys8CookItemE +0x0000007100970158,PauseMenuDataMgr::x_1,268,_ZN5uking2ui16PauseMenuDataMgr26setCookDataOnLastAddedItemERKN4ksys8CookItemE +0x0000007100970264,PauseMenuDataMgr::__auto5,116,_ZN5uking2ui16PauseMenuDataMgr22autoEquipLastAddedItemEv +0x00000071009702d8,sub_71009702D8,268,_ZN5uking2ui16PauseMenuDataMgr9autoEquipEPNS0_9PouchItemERKN4sead10OffsetListIS2_EE? +0x00000071009703e4,PauseMenuDataMgr::__auto10,216,_ZN5uking2ui16PauseMenuDataMgr10unequipAllENS0_13PouchItemTypeE? +0x00000071009704bc,PauseMenuDataMgr::removeOneFromInventory,1352,_ZN5uking2ui16PauseMenuDataMgr10removeItemERKN4sead14SafeStringBaseIcEE +0x0000007100970a04,PauseMenuDataMgr::removeFromInventory_,896,_ZN5uking2ui16PauseMenuDataMgr22removeWeaponIfEquippedERKN4sead14SafeStringBaseIcEE 0x0000007100970d84,sub_7100970D84,512,_ZN5uking2ui16PauseMenuDataMgr11removeArrowERKN4sead14SafeStringBaseIcEEi -0x0000007100970f84,PauseMenuDataMgr::getItemNum,1204, -0x0000007100971438,PauseMenuDataMgr::setPorchValue1,204,_ZN5uking2ui16PauseMenuDataMgr18setWeaponItemValueEiNS0_13PouchItemTypeE +0x0000007100970f84,PauseMenuDataMgr::getItemNum,1204,_ZNK5uking2ui16PauseMenuDataMgr12getItemCountERKN4sead14SafeStringBaseIcEEb! +0x0000007100971438,PauseMenuDataMgr::setPorchValue1,204,_ZN5uking2ui16PauseMenuDataMgr26setEquippedWeaponItemValueEiNS0_13PouchItemTypeE 0x0000007100971504,PauseMenuDataMgr::fromSaveDataMaybe,716, 0x00000071009717d0,sub_71009717D0,40,_ZNK5uking2ui16PauseMenuDataMgr19getDefaultEquipmentENS0_13EquipmentSlotE 0x00000071009717f8,PauseMenuDataMgr::hasItemMaybe,404,_ZNK5uking2ui16PauseMenuDataMgr7hasItemERKN4sead14SafeStringBaseIcEE @@ -56441,7 +56441,7 @@ 0x000000710097e094,PauseMenuDataMgr::openItemCategoryIfNeeded,204,_ZNK5uking2ui16PauseMenuDataMgr24openItemCategoryIfNeededEv 0x000000710097e160,PauseMenuDataMgr::giveDefaultSetItems,1804, 0x000000710097e86c,PauseMenuDataMgr::doAddToPouch,6284, -0x00000071009800f8,sub_71009800F8,612, +0x00000071009800f8,sub_71009800F8,612,_ZN5uking2ui9PouchItem15sortIngredientsEv! 0x000000710098035c,updateDivineBeastClearFlags,220,_ZN5uking2ui16PauseMenuDataMgr27updateDivineBeastClearFlagsEi 0x0000007100980438,j__ZdlPv_464,4,_ZN5uking2ui9PouchItemD0Ev 0x000000710098043c,sub_710098043C,648, diff --git a/lib/sead b/lib/sead index bad3539a..ed983cc4 160000 --- a/lib/sead +++ b/lib/sead @@ -1 +1 @@ -Subproject commit bad3539a0d834cd72b1d26c05b98456b3163881e +Subproject commit ed983cc42dee614eb4fbc8d71303acec1c7b2c84 diff --git a/src/Game/UI/uiPauseMenuDataMgr.cpp b/src/Game/UI/uiPauseMenuDataMgr.cpp index 311621e9..a7c2bb92 100644 --- a/src/Game/UI/uiPauseMenuDataMgr.cpp +++ b/src/Game/UI/uiPauseMenuDataMgr.cpp @@ -10,6 +10,7 @@ #include "KingSystem/ActorSystem/actActorUtil.h" #include "KingSystem/ActorSystem/actInfoData.h" #include "KingSystem/ActorSystem/actPlayerInfo.h" +#include "KingSystem/Cooking/cookItem.h" #include "KingSystem/GameData/gdtCommonFlagsUtils.h" #include "KingSystem/GameData/gdtSpecialFlags.h" #include "KingSystem/System/PlayReportMgr.h" @@ -945,6 +946,170 @@ void PauseMenuDataMgr::saveToGameData(const sead::OffsetList& list) c } } +void PauseMenuDataMgr::cookItemGet(const ksys::CookItem& cook_item) { + const auto* info = ksys::act::InfoData::instance(); + if (!info->hasTag(cook_item.name.cstr(), ksys::act::tags::CookResult)) + return; + + const auto lock = sead::makeScopedLock(mCritSection); + auto& lists = mItemLists; + + ksys::PlayReportMgr::instance()->reportDebug("PouchGet", cook_item.name); + const auto type = getType(cook_item.name); + addToPouch(cook_item.name, type, lists, 1, false); + setCookDataOnLastAddedItem(cook_item); + saveToGameData(lists.list1); +} + +void PauseMenuDataMgr::setCookDataOnLastAddedItem(const ksys::CookItem& cook_item) { + if (!mLastAddedItem) + return; + + mLastAddedItem->getCookData().setStaminaRecoverY(cook_item.stamina_recover_y); + mLastAddedItem->getCookData().setStaminaRecoverX(cook_item.stamina_recover_x); + mLastAddedItem->getCookData().setCookEffect1(cook_item.cook_effect_1); + const int y = cook_item.cook_effect_0_y; + const int x = cook_item.cook_effect_0_x; + mLastAddedItem->getCookData().setCookEffect0({float(x), float(y)}); + for (s32 i = 0; i < cook_item.ingredients.size(); ++i) + mLastAddedItem->setIngredient(i, cook_item.ingredients[i]); + mLastAddedItem->sortIngredients(); +} + +void PauseMenuDataMgr::autoEquipLastAddedItem() { + if (mLastAddedItem && mLastAddedItem->getType() <= PouchItemType::ArmorLower) { + const auto lock = sead::makeScopedLock(mCritSection); + autoEquip(mLastAddedItem, getItems()); + } +} + +// NON_MATCHING: branching +const sead::SafeString& PauseMenuDataMgr::autoEquip(PouchItem* item, + const sead::OffsetList& list) { + const auto type = item->getType(); + if (type == PouchItemType::KeyItem) + item->mEquipped = true; + + if (type >= PouchItemType::Material) + return sead::SafeString::cEmptyString; + + if (isPouchItemArmor(type)) { + for (auto& other : list) { + if (other.getType() > PouchItemType::ArmorLower) + break; + if (other.getType() == type) + other.mEquipped = false; + } + } else if (isPouchItemWeapon(type)) { + for (auto& other : list) { + if (other.getType() > PouchItemType::Shield) + break; + if (other.getType() == type) + other.mEquipped = false; + } + } + + item->mEquipped = true; + updateInventoryInfo(list); + saveToGameData(list); + return sead::SafeString::cEmptyString; +} + +// NON_MATCHING: harmless reordering +void PauseMenuDataMgr::unequipAll(PouchItemType type) { + const auto lock = sead::makeScopedLock(mCritSection); + + if (type == PouchItemType::Arrow) + return; + + for (auto& item : getItems()) { + if (type == PouchItemType::Invalid) { + if (item.getType() > PouchItemType::ArmorLower) + break; + if (item.isEquipped() && item.getType() != PouchItemType::Arrow) + item.mEquipped = false; + + } else { + if (item.getType() > PouchItemType::ArmorLower) + break; + if (item.isEquipped() && item.getType() == type) { + item.mEquipped = false; + break; + } + } + } +} + +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); + item->~PouchItem(); + new (item) PouchItem; + mItemLists.list2.pushFront(item); + + ksys::PlayReportMgr::instance()->reportDebug("PouchDelete", name); + saveToGameData(list); + updateInventoryInfo(list); + updateListHeads(); +} + +void PauseMenuDataMgr::removeItem(const sead::SafeString& name) { + const auto lock = sead::makeScopedLock(mCritSection); + const auto& items = getItems(); + + PouchItem* item = nullptr; + for (auto& it : items) { + if (name == it.getName() && !it.isEquipped()) { + item = ⁢ + break; + } + } + + if (!item) + return; + + // If the item is stackable, just remove one copy of the item if possible. + auto* info = ksys::act::InfoData::instance(); + if (info->hasTag(item->getName().cstr(), ksys::act::tags::CanStack) && item->getValue() > 1) { + item->mValue -= 1; + ksys::PlayReportMgr::instance()->reportDebug("PouchDelete", name); + updateInventoryInfo(items); + updateListHeads(); + saveToGameData(items); + return; + } + + // Otherwise, delete the PouchItem entirely. + deleteItem_(items, item, name); +} + +void PauseMenuDataMgr::removeWeaponIfEquipped(const sead::SafeString& name) { + const auto lock = sead::makeScopedLock(mCritSection); + if (!ksys::act::PlayerInfo::instance()->getPlayer()) + return; + + const auto& items = getItems(); + + PouchItem* item = nullptr; + for (auto& it : items) { + if (name == it.getName() && it.isEquipped()) { + item = ⁢ + break; + } + } + + if (item) + deleteItem_(items, item, name); +} + void PauseMenuDataMgr::removeArrow(const sead::SafeString& arrow_name, int count) { if (!ksys::act::InfoData::instance()->hasTag(arrow_name.cstr(), ksys::act::tags::Arrow)) return; @@ -969,7 +1134,75 @@ void PauseMenuDataMgr::removeArrow(const sead::SafeString& arrow_name, int count ksys::gdt::setFlag_PorchItem_Value1(num, idx); } -void PauseMenuDataMgr::setWeaponItemValue(s32 value, PouchItemType type) { +// NON_MATCHING: branch merging -- but this is pretty clearly equivalent +int PauseMenuDataMgr::getItemCount(const sead::SafeString& name, bool count_equipped) const { + const auto type = getType(name); + if (isPouchItemInvalid(type)) + return 0; + + const auto& items = getItems(); + sead::SafeString group_name; + getSameGroupActorName(&group_name, name); + + PouchItem* first = nullptr; + switch (type) { + case PouchItemType::Sword: + first = getItemHead(PouchCategory::Sword); + break; + case PouchItemType::Bow: + first = getItemHead(PouchCategory::Bow); + break; + case PouchItemType::Arrow: + for (auto* item = getItemHead(PouchCategory::Bow); item; item = items.next(item)) { + if (item->getType() > PouchItemType::Arrow) + break; + if (item->getType() == PouchItemType::Arrow) { + first = item; + break; + } + } + break; + case PouchItemType::Shield: + first = getItemHead(PouchCategory::Shield); + break; + case PouchItemType::Food: + first = getItemHead(PouchCategory::Food); + break; + case PouchItemType::KeyItem: + first = getItemHead(PouchCategory::KeyItem); + break; + default: + if (type > PouchItemType::Material) + first = getItemHead(PouchCategory::Material); + else + first = getItemHead(PouchCategory::Armor); + break; + } + + if (ksys::act::InfoData::instance()->hasTag(group_name.cstr(), ksys::act::tags::CanStack)) { + for (auto* item = first; item; item = items.next(item)) { + if (group_name == item->getName()) + return item->getValue(); + } + return 0; + } + + s32 count = 0; + if (count_equipped) { + for (auto* item = first; item; item = items.next(item)) { + if (group_name == item->getName()) + count += item->get25(); + } + } else { + for (auto* item = first; item; item = items.next(item)) { + if (group_name == item->getName() && item->get25()) + count += !item->isEquipped(); + } + } + return count; +} + +void PauseMenuDataMgr::setEquippedWeaponItemValue(s32 value, PouchItemType type) { if (isPouchItemNotWeapon(type)) return; @@ -1283,6 +1516,29 @@ bool PauseMenuDataMgr::hasZoraSoulPlus() const { return ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Zora() && aoc::Manager::instance()->hasAoc3(); } +[[gnu::noinline]] void PouchItem::sortIngredients() { + mIngredients.sort( + [](const sead::FixedSafeString<64>* lhs, const sead::FixedSafeString<64>* rhs) { + auto* info = ksys::act::InfoData::instance(); + if (!lhs || !rhs || !info) + return 0; + + if (rhs->isEmpty()) { + if (lhs->isEmpty()) + return 0; + return -1; + } + + const s32 key1 = info->getSortKey(lhs->cstr()); + const s32 key2 = info->getSortKey(rhs->cstr()); + if (key1 < key2) + return -1; + if (key1 > key2) + return 1; + return 0; + }); +} + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) void PauseMenuDataMgr::updateDivineBeastClearFlags(int num_cleared_beasts) { switch (num_cleared_beasts) { diff --git a/src/Game/UI/uiPauseMenuDataMgr.h b/src/Game/UI/uiPauseMenuDataMgr.h index 666c7a70..47821cb5 100644 --- a/src/Game/UI/uiPauseMenuDataMgr.h +++ b/src/Game/UI/uiPauseMenuDataMgr.h @@ -23,6 +23,10 @@ namespace uking::act { struct WeaponModifierInfo; } +namespace ksys { +struct CookItem; +} + namespace uking::ui { constexpr int NumSwordsMax = 20; @@ -149,6 +153,7 @@ public: const sead::SafeString& getIngredient(s32 idx) const { return *mIngredients[idx]; } void setIngredient(s32 idx, const sead::SafeString& value) const { *mIngredients[idx] = value; } + void sortIngredients(); // Only valid if this is a weapon. WeaponData& getWeaponData() { return mData.weapon; } @@ -213,10 +218,27 @@ public: bool isWeaponSectionFull(const sead::SafeString& get_flag) const; void itemGet(const sead::SafeString& name, int value, const act::WeaponModifierInfo* modifier); + void cookItemGet(const ksys::CookItem& cook_item); + + void setCookDataOnLastAddedItem(const ksys::CookItem& cook_item); + + void autoEquipLastAddedItem(); + const sead::SafeString& autoEquip(PouchItem* item, const sead::OffsetList& list); + /// Unequip all inventory items with the specified type. + /// If type is PouchItemType::Invalid, all inventory items will be unequipped. + void unequipAll(PouchItemType type = PouchItemType::Invalid); + + void removeItem(const sead::SafeString& name); + void removeWeaponIfEquipped(const sead::SafeString& name); void removeArrow(const sead::SafeString& arrow_name, int count = 1); - int getItemCount(const sead::SafeString& name, bool x = true) const; - void setWeaponItemValue(s32 value, PouchItemType type); + + int getItemCount(const sead::SafeString& name, bool count_equipped = true) const; + + // TODO: requires CreatePlayerEquipActorMgr + void createPlayerEquipment(); + void setEquippedWeaponItemValue(s32 value, PouchItemType type); const sead::SafeString& getDefaultEquipment(EquipmentSlot idx) const; + bool hasItem(const sead::SafeString& name) const; PouchItem* getMasterSword() const; @@ -296,6 +318,9 @@ private: bool equipped, const act::WeaponModifierInfo* modifier = nullptr, bool is_inventory_load = false); + void deleteItem_(const sead::OffsetList& list, PouchItem* item, + const sead::SafeString& name); + bool hasFreeSpaceForItem(const Lists& lists, const sead::SafeString& name, int n = 1) const; /// @param num_cleared_beasts The number of divine beasts that have been done. diff --git a/src/KingSystem/CMakeLists.txt b/src/KingSystem/CMakeLists.txt index e96be084..01782dba 100644 --- a/src/KingSystem/CMakeLists.txt +++ b/src/KingSystem/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(GameData) add_subdirectory(Resource) add_subdirectory(ActorSystem) +add_subdirectory(Cooking) add_subdirectory(Ecosystem) add_subdirectory(Event) add_subdirectory(Damage) diff --git a/src/KingSystem/Cooking/CMakeLists.txt b/src/KingSystem/Cooking/CMakeLists.txt new file mode 100644 index 00000000..484a3df6 --- /dev/null +++ b/src/KingSystem/Cooking/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources(uking PRIVATE + cookItem.cpp + cookItem.h + cookManager.cpp + cookManager.h +) diff --git a/src/KingSystem/Cooking/cookItem.cpp b/src/KingSystem/Cooking/cookItem.cpp new file mode 100644 index 00000000..192fb7f5 --- /dev/null +++ b/src/KingSystem/Cooking/cookItem.cpp @@ -0,0 +1,7 @@ +#include "KingSystem/Cooking/cookItem.h" + +namespace ksys { + +CookItem::CookItem() = default; + +} // namespace ksys diff --git a/src/KingSystem/Cooking/cookItem.h b/src/KingSystem/Cooking/cookItem.h new file mode 100644 index 00000000..bf168545 --- /dev/null +++ b/src/KingSystem/Cooking/cookItem.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include "KingSystem/Utils/Types.h" + +namespace ksys { + +struct CookItem { + CookItem(); + + sead::FixedSafeString<64> name{""}; + sead::SafeArray, 5> ingredients; + f32 stamina_recover_x{}; + s32 stamina_recover_y{}; + s32 cook_effect_1{}; + s32 cook_effect_0_x = -1; + f32 cook_effect_0_y{}; + bool _224{}; +}; +KSYS_CHECK_SIZE_NX150(CookItem, 0x228); + +} // namespace ksys diff --git a/src/KingSystem/Cooking/cookManager.cpp b/src/KingSystem/Cooking/cookManager.cpp new file mode 100644 index 00000000..d49d1923 --- /dev/null +++ b/src/KingSystem/Cooking/cookManager.cpp @@ -0,0 +1 @@ +#include "KingSystem/Cooking/cookManager.h" diff --git a/src/KingSystem/Cooking/cookManager.h b/src/KingSystem/Cooking/cookManager.h new file mode 100644 index 00000000..f87f9a84 --- /dev/null +++ b/src/KingSystem/Cooking/cookManager.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace ksys { + +// TODO +class CookingMgr { + SEAD_SINGLETON_DISPOSER(CookingMgr) + CookingMgr(); + // TODO: inline + ~CookingMgr(); +}; + +} // namespace ksys