#include "Game/UI/uiPauseMenuDataMgr.h" #include #include #include #include "Game/Actor/actWeapon.h" #include "Game/DLC/aocManager.h" #include "Game/UI/uiUtils.h" #include "KingSystem/ActorSystem/Profiles/actPlayerBase.h" #include "KingSystem/ActorSystem/actActorUtil.h" #include "KingSystem/ActorSystem/actInfoData.h" #include "KingSystem/ActorSystem/actPlayerInfo.h" #include "KingSystem/GameData/gdtCommonFlagsUtils.h" #include "KingSystem/System/PlayReportMgr.h" #include "KingSystem/Utils/Byaml/Byaml.h" #include "KingSystem/Utils/InitTimeInfo.h" namespace uking::ui { SEAD_SINGLETON_DISPOSER_IMPL(PauseMenuDataMgr) sead::Vector2f sDummyCookEffect0{-1, 0}; namespace { sead::SafeArray sCookItemOrder_{{ {1, "CookFruit", ksys::act::tags::CookFruit}, {1, "CookMushroom", ksys::act::tags::CookMushroom}, {1, "CookPlant", ksys::act::tags::CookPlant}, {1, "CookMeat", ksys::act::tags::CookMeat}, {1, "CookSpice", ksys::act::tags::CookSpice}, {1, "CookFish", ksys::act::tags::CookFish}, {0, "Animal_Insect_F", 0}, {1, "CookInsect", ksys::act::tags::CookInsect}, {1, "CookOre", ksys::act::tags::CookOre}, {1, "CookEnemy", ksys::act::tags::CookEnemy}, {0, "Obj_FireWoodBundle", 0}, }}; struct PouchStaticData { ksys::util::InitTimeInfoEx info; u32 last_added_weapon_add_type{}; u32 last_added_weapon_add_value{}; f32 _20 = std::numeric_limits::infinity(); f32 _24 = std::numeric_limits::infinity(); f32 _28 = std::numeric_limits::infinity(); u32 _2c{}; u32 _30{}; sead::SafeArray _34{{ 0.0, 1.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.5, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.5, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.5, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.5, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.5, }}; u32 _14c{}; u32 _150{}; u32 _154{}; u32 _158{}; sead::SafeString WeaponSmallSword = "WeaponSmallSword"; sead::SafeString WeaponLargeSword = "WeaponLargeSword"; sead::SafeString WeaponSpear = "WeaponSpear"; sead::SafeString WeaponBow = "WeaponBow"; sead::SafeString WeaponShield = "WeaponShield"; sead::SafeString ArmorHead = "ArmorHead"; sead::SafeString ArmorUpper = "ArmorUpper"; sead::SafeString ArmorLower = "ArmorLower"; sead::SafeString Item = "Item"; sead::SafeString PlayerItem = "PlayerItem"; sead::SafeString HorseReins = "HorseReins"; sead::SafeString None = "None"; sead::SafeString Obj_KorokNuts = "Obj_KorokNuts"; sead::SafeString Obj_DungeonClearSeal = "Obj_DungeonClearSeal"; sead::SafeString Animal_Insect_F = "Animal_Insect_F"; sead::SafeString Obj_HeroSoul_Zora = "Obj_HeroSoul_Zora"; sead::SafeString Obj_HeroSoul_Rito = "Obj_HeroSoul_Rito"; sead::SafeString Obj_HeroSoul_Goron = "Obj_HeroSoul_Goron"; sead::SafeString Obj_HeroSoul_Gerudo = "Obj_HeroSoul_Gerudo"; sead::SafeString Obj_DLC_HeroSoul_Zora = "Obj_DLC_HeroSoul_Zora"; sead::SafeString Obj_DLC_HeroSoul_Rito = "Obj_DLC_HeroSoul_Rito"; sead::SafeString Obj_DLC_HeroSoul_Goron = "Obj_DLC_HeroSoul_Goron"; sead::SafeString Obj_DLC_HeroSoul_Gerudo = "Obj_DLC_HeroSoul_Gerudo"; sead::SafeString Obj_WarpDLC = "Obj_WarpDLC"; sead::SafeString Armor_116_Upper = "Armor_116_Upper"; sead::SafeString Armor_148_Upper = "Armor_148_Upper"; sead::SafeString Armor_149_Upper = "Armor_149_Upper"; sead::SafeString Armor_150_Upper = "Armor_150_Upper"; sead::SafeString Armor_151_Upper = "Armor_151_Upper"; sead::Buffer cook_item_order{sCookItemOrder_.size(), sCookItemOrder_.getBufferPtr()}; sead::SafeArray default_equipment_names{{ "Weapon_Default_Right", "Weapon_Default_Bow", "Weapon_Default_Arrow", "Weapon_Default_Left", "Armor_Default_Head", "Armor_Default_Upper", "Armor_Default_Lower", }}; sead::SafeArray divine_helms{{ "Armor_168_Head", "Armor_169_Head", "Armor_181_Head", "Armor_182_Head", "Armor_183_Head", "Armor_184_Head", "Armor_186_Head", "Armor_187_Head", "Armor_188_Head", "Armor_189_Head", "Armor_190_Head", "Armor_191_Head", "Armor_192_Head", "Armor_193_Head", "Armor_194_Head", "Armor_195_Head", "Armor_196_Head", "Armor_197_Head", "Armor_198_Head", "Armor_199_Head", }}; }; PouchStaticData sValues; void getSameGroupActorName(sead::SafeString* group, const sead::SafeString& item, al::ByamlIter* iter = nullptr) { if (iter) { ksys::act::getSameGroupActorName(group, item, iter); } else { ksys::act::getSameGroupActorName(group, item); } } } // namespace int pouchItemSortPredicate(const PouchItem* lhs, const PouchItem* rhs); int pouchItemSortPredicateForArrow(const PouchItem* lhs, const PouchItem* rhs); PauseMenuDataMgr::PauseMenuDataMgr() { mListHeads.fill(nullptr); for (s32 i = 0; i < NumPouch50; ++i) { mArray1[i] = nullptr; mArray2[i] = PouchItemType::Invalid; } for (auto& x : mArray3) x = {}; _447e0 = {}; _447e8 = {}; _447f0 = {}; _447f8 = {}; } PauseMenuDataMgr::~PauseMenuDataMgr() = default; PouchItem::PouchItem() { mData.cook.mCookEffect0 = sDummyCookEffect0; for (s32 i = 0; i < NumIngredientsMax; ++i) mIngredients.emplaceBack(); } void PauseMenuDataMgr::resetItem() { mNewlyAddedItem.mType = PouchItemType::Invalid; mNewlyAddedItem._1c = -1; mNewlyAddedItem.mValue = 0; mNewlyAddedItem.mEquipped = false; mNewlyAddedItem._25 = 0; mNewlyAddedItem.mName.clear(); mNewlyAddedItem.mData.cook = {}; mNewlyAddedItem.mData.cook.mCookEffect0 = sDummyCookEffect0; } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) void PauseMenuDataMgr::setItemModifier(PouchItem& item, const act::WeaponModifierInfo* modifier) { if (modifier && !modifier->flags.isZero()) { item.setWeaponAddType(modifier->flags.getDirect()); item.setWeaponAddValue(static_cast(modifier->value)); } else { item.setWeaponAddType(0); } } 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); } mListHeads.fill(nullptr); for (s32 i = 0; i < NumPouch50; ++i) { mArray1[i] = nullptr; mArray2[i] = PouchItemType::Invalid; } _44498 = {}; ksys::gdt::setFlag_KorokNutsNum(0); ksys::gdt::setFlag_DungeonClearSealNum(0); ksys::gdt::setFlag_FairyCountCheck(false); _444fc = {}; mItem_44488 = {}; mItem_444f0 = {}; _444f8 = -1; resetItem(); mIsPouchForQuest = false; for (auto& x : mArray3) x = {}; _44504 = {}; _44508 = {}; _4450c = {}; _44510 = {}; _44514 = {}; mRitoSoulItem = {}; mGoronSoulItem = {}; mZoraSoulItem = {}; mGerudoSoulItem = {}; _44538 = false; _447e0 = {}; _447e8 = {}; _447f0 = {}; _447f8 = {}; auto* player = ksys::act::PlayerInfo::instance()->getPlayer(); if (player) { player->switchEquipment(getDefaultEquipment(EquipmentSlot::WeaponRight), 1); player->switchEquipment(getDefaultEquipment(EquipmentSlot::WeaponLeft), 1); player->switchEquipment(getDefaultEquipment(EquipmentSlot::WeaponBow), 1); player->switchEquipment(getDefaultEquipment(EquipmentSlot::ArmorHead), 30); player->switchEquipment(getDefaultEquipment(EquipmentSlot::ArmorUpper), 30); player->switchEquipment(getDefaultEquipment(EquipmentSlot::ArmorLower), 30); } } void PauseMenuDataMgr::loadFromGameData() { doLoadFromGameData(); for (auto& x : mArray3) x = {}; mItem_44488 = {}; mItem_444f0 = {}; _444f8 = -1; resetItem(); _444fc = 0; mIsPouchForQuest = false; _447e0 = {}; _447e8 = {}; _447f0 = {}; _447f8 = {}; const auto lock = sead::makeScopedLock(mCritSection); updateInventoryInfo(getItems()); } void PauseMenuDataMgr::doLoadFromGameData() { namespace gdt = ksys::gdt; const auto lock = sead::makeScopedLock(mCritSection); auto& items = getItems(); for (auto* item = items.popFront(); item; item = items.popFront()) { item->~PouchItem(); new (item) PouchItem; mItemLists.list2.pushFront(item); } mListHeads.fill(nullptr); mRitoSoulItem = nullptr; mGoronSoulItem = nullptr; mZoraSoulItem = nullptr; mGerudoSoulItem = nullptr; for (s32 i = 0; i < NumPouch50; ++i) { mArray1[i] = nullptr; mArray2[i] = PouchItemType::Invalid; } s32 num_food = 0; s32 num_swords = 0; s32 num_shields = 0; s32 num_bows = 0; mItem_44488 = nullptr; bool found_travel_medallion = false; for (u32 idx = 0; idx < u32(NumPouchItemsMax); ++idx) { const char* item_name; gdt::getFlag_PorchItem(&item_name, idx); if (found_travel_medallion || sValues.Obj_WarpDLC == item_name) found_travel_medallion = true; if (sead::SafeString(item_name).isEmpty()) break; const bool equipped = gdt::getFlag_PorchItem_EquipFlag(idx); const s32 value = gdt::getFlag_PorchItem_Value1(idx); const auto type = getType(item_name); switch (type) { case PouchItemType::Sword: { act::WeaponModifierInfo info{}; info.loadPorchSwordFlag(num_swords); addToPouch(item_name, type, items, value, equipped, &info, true); ++num_swords; break; } case PouchItemType::Bow: { act::WeaponModifierInfo info{}; info.loadPorchBowFlag(num_bows); addToPouch(item_name, type, items, value, equipped, &info, true); ++num_bows; break; } case PouchItemType::Shield: { act::WeaponModifierInfo info{}; info.loadPorchShieldFlag(num_shields); addToPouch(item_name, type, items, value, equipped, &info, true); ++num_shields; break; } default: addToPouch(item_name, type, items, value, equipped, nullptr, true); break; } if (!mItem_44488) continue; if (type == PouchItemType::Food) { sead::Vector2f v{0, 0}; gdt::getFlag_StaminaRecover(&v, num_food); mItem_44488->getCookData().setStaminaRecoverX(v.x); mItem_44488->getCookData().setStaminaRecoverY(v.y); gdt::getFlag_CookEffect0(&v, num_food); mItem_44488->getCookData().setCookEffect0(v); gdt::getFlag_CookEffect1(&v, num_food); mItem_44488->getCookData().setCookEffect1(v.x); gdt::getFlag_CookMaterialName0(&item_name, num_food); mItem_44488->setIngredient(0, item_name); gdt::getFlag_CookMaterialName1(&item_name, num_food); mItem_44488->setIngredient(1, item_name); gdt::getFlag_CookMaterialName2(&item_name, num_food); mItem_44488->setIngredient(2, item_name); gdt::getFlag_CookMaterialName3(&item_name, num_food); mItem_44488->setIngredient(3, item_name); gdt::getFlag_CookMaterialName4(&item_name, num_food); mItem_44488->setIngredient(4, item_name); ++num_food; } else if (type == PouchItemType::Sword && isMasterSwordActorName(mItem_44488->getName()) && gdt::getFlag_MasterSwordRecoverTime() <= std::numeric_limits::epsilon() && mItem_44488->getValue() <= 0) { const s32 new_value = getWeaponInventoryLife(mItem_44488->getName()); mItem_44488->setValue(new_value); gdt::setFlag_PorchItem_Value1(new_value, idx); } } // Add the Travel Medallion (Obj_WarpDLC) to the inventory if it is missing for some reason if (aoc::Manager::instance()->hasAoc2() && !(found_travel_medallion | !gdt::getFlag_IsGet_Obj_WarpDLC())) { addToPouch(sValues.Obj_WarpDLC.cstr(), PouchItemType::KeyItem, items, 1, false, nullptr, true); } // Add missing champion powers and fix some divine beast related flags // in case something went wrong with the "divine beast cleared" cutscenes bool was_missing_hero_soul = false; s32 num_cleared_beasts = 0; if (gdt::getFlag_Clear_RemainsWind()) { ++num_cleared_beasts; if (!mRitoSoulItem) { was_missing_hero_soul = true; addToPouch(sValues.Obj_HeroSoul_Rito.cstr(), PouchItemType::KeyItem, items, 1, true, nullptr, true); gdt::setFlag_IsPlayed_Demo119_0(true); } } if (gdt::getFlag_Clear_RemainsFire()) { ++num_cleared_beasts; if (!mGoronSoulItem) { was_missing_hero_soul = true; addToPouch(sValues.Obj_HeroSoul_Goron.cstr(), PouchItemType::KeyItem, items, 1, true, nullptr, true); gdt::setFlag_IsPlayed_Demo116_0(true); } } if (gdt::getFlag_Clear_RemainsWater()) { ++num_cleared_beasts; if (!mZoraSoulItem) { was_missing_hero_soul = true; addToPouch(sValues.Obj_HeroSoul_Zora.cstr(), PouchItemType::KeyItem, items, 1, true, nullptr, true); gdt::setFlag_IsPlayed_Demo122_0(true); } } if (gdt::getFlag_Clear_RemainsElectric()) { ++num_cleared_beasts; if (!mGerudoSoulItem) { was_missing_hero_soul = true; addToPouch(sValues.Obj_HeroSoul_Gerudo.cstr(), PouchItemType::KeyItem, items, 1, true, nullptr, true); gdt::setFlag_IsPlayed_Demo125_0(true); } } if (was_missing_hero_soul) updateDivineBeastClearFlags(num_cleared_beasts); _44490 = -1; _44494 = -1; } bool PauseMenuDataMgr::cannotGetItem(const sead::SafeString& name, int n) const { namespace act = ksys::act; al::ByamlIter iter; if (!act::InfoData::instance()->getActorIter(&iter, name.cstr())) return true; if (!act::InfoData::instance()->hasTag(iter, act::tags::CanGetPouch)) return true; if (act::InfoData::instance()->getSortKey(name.cstr()) == 99999) return true; const auto lock = sead::makeScopedLock(mCritSection); const auto type = getType(name); if (act::InfoData::instance()->hasTag(iter, act::tags::CanStack)) return !hasFreeSpaceForItem(mItemLists, name, n); if (type <= PouchItemType::Shield) { switch (type) { case PouchItemType::Sword: { const auto limit = ksys::gdt::getFlag_WeaponPorchStockNum(); return isList2Empty() || limit < countItems(PouchItemType::Sword) + n; } case PouchItemType::Shield: { const auto limit = ksys::gdt::getFlag_ShieldPorchStockNum(); return isList2Empty() || limit < countItems(PouchItemType::Shield) + n; } case PouchItemType::Bow: { const auto limit = ksys::gdt::getFlag_BowPorchStockNum(); return isList2Empty() || limit < countItems(PouchItemType::Bow) + n; } default: { const auto limit = ksys::gdt::getFlag_WeaponPorchStockNum(); return isList2Empty() || limit < countItems(PouchItemType::Sword, true) + n; } } } if (type == PouchItemType::Food) return isList2Empty() || countItems(PouchItemType::Food, true) + n > NumFoodMax; if (type == PouchItemType::Material) return isList2Empty() || countItems(PouchItemType::Material) + n > NumMaterialsMax; if (type == PouchItemType::KeyItem) return isList2Empty() || countItems(PouchItemType::KeyItem) + n > NumKeyItemsMax; return isList2Empty() || countItems(PouchItemType::ArmorHead) + n > NumArmorsMax; } PouchItemType PauseMenuDataMgr::getType(const sead::SafeString& item, al::ByamlIter* iter) { sead::SafeString group; getSameGroupActorName(&group, item, iter); al::ByamlIter actor_iter; if (iter && iter->isValid() && group == item) { actor_iter = *iter; } else { if (!ksys::act::InfoData::instance()->getActorIter(&actor_iter, group.cstr())) return PouchItemType::Invalid; } if (ksys::act::InfoData::instance()->hasTag(actor_iter, ksys::act::tags::Arrow)) return PouchItemType::Arrow; const char* profile_c; if (!ksys::act::InfoData::instance()->getActorProfile(&profile_c, actor_iter)) return PouchItemType::Invalid; const sead::SafeString profile = profile_c; if (profile == sValues.WeaponSmallSword) return PouchItemType::Sword; if (profile == sValues.WeaponLargeSword) return PouchItemType::Sword; if (profile == sValues.WeaponSpear) return PouchItemType::Sword; if (profile == sValues.WeaponBow) return PouchItemType::Bow; if (profile == sValues.WeaponShield) return PouchItemType::Shield; if (profile == sValues.ArmorHead) return PouchItemType::ArmorHead; if (profile == sValues.ArmorUpper) return PouchItemType::ArmorUpper; if (profile == sValues.ArmorLower) return PouchItemType::ArmorLower; if (profile == sValues.HorseReins) return PouchItemType::KeyItem; if (ksys::act::InfoData::instance()->hasTag(actor_iter, ksys::act::tags::CookResult)) return PouchItemType::Food; if (ksys::act::InfoData::instance()->hasTag(actor_iter, ksys::act::tags::RoastItem)) return PouchItemType::Food; if (ksys::act::InfoData::instance()->hasTag(actor_iter, ksys::act::tags::Important)) return PouchItemType::KeyItem; return PouchItemType::Material; } bool PauseMenuDataMgr::hasFreeSpaceForItem(const PauseMenuDataMgr::Lists& lists, const sead::SafeString& name, int n) const { sead::SafeString group_name; getSameGroupActorName(&group_name, name); const int count = getItemCount(group_name); const bool is_arrow = ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::Arrow); if (count == 0 && is_arrow) { for (const auto& item : lists.list1) { if (item.getType() == PouchItemType::Arrow && group_name == item.getName()) return true; } return countItems(PouchItemType::Arrow) < NumArrowItemsMax; } if (count == 0) { const auto type = getType(name); if (type == PouchItemType::Food) return !lists.list2.isEmpty() && countItems(PouchItemType::Food, true) < NumFoodMax; if (type == PouchItemType::Material) return !lists.list2.isEmpty() && countItems(PouchItemType::Material) < NumMaterialsMax; if (type == PouchItemType::KeyItem) return !lists.list2.isEmpty() && countItems(PouchItemType::KeyItem) < NumKeyItemsMax; return false; } return count + n <= ItemStackSizeMax; } int PauseMenuDataMgr::countItems(PouchItemType type, bool count_any_weapon) const { if (isPouchItemInvalid(type) || getItems().isEmpty()) return 0; const auto& list = getItems(); if (count_any_weapon) { PouchItem** head = nullptr; if (type <= PouchItemType::Shield) { int count = 0; for (auto* item = list.nth(0); item && item->getType() <= PouchItemType::Shield; item = list.next(item)) { count += item->get25(); } return count; } else if (type <= PouchItemType::ArmorLower) { int count = 0; for (auto* item = getItemHead(PouchCategory::Armor); item && item->getType() <= PouchItemType::ArmorLower; item = list.next(item)) { count += item->get25(); } return count; } else if (type == PouchItemType::Material) { head = getItemHeadp(PouchCategory::Material); if (!head) return 0; } else if (type == PouchItemType::Food) { head = getItemHeadp(PouchCategory::Food); if (!head) return 0; } else if (type == PouchItemType::KeyItem) { head = getItemHeadp(PouchCategory::KeyItem); if (!head) return 0; } else { return 0; } if (!head) return 0; int count = 0; for (auto* item = *head; item && item->getType() == type; item = list.next(item)) { count += item->get25(); } return count; } PouchItem* first = nullptr; if (type == PouchItemType::Sword) { first = getItemHead(PouchCategory::Sword); } else if (type == PouchItemType::Bow) { first = getItemHead(PouchCategory::Bow); } else if (type == PouchItemType::Arrow) { for (auto* item = getItemHead(PouchCategory::Bow); item && item->getType() <= PouchItemType::Arrow; item = list.next(item)) { if (item->getType() == PouchItemType::Arrow) { first = item; break; } } } else if (type == PouchItemType::Shield) { first = getItemHead(PouchCategory::Shield); } else if (type <= PouchItemType::ArmorLower) { int count = 0; for (auto* item = getItemHead(PouchCategory::Armor); item && item->getType() <= PouchItemType::ArmorLower; item = list.next(item)) { count += item->get25(); } return count; } else if (type == PouchItemType::Material) { first = getItemHead(PouchCategory::Material); } else if (type == PouchItemType::Food) { first = getItemHead(PouchCategory::Food); } else if (type == PouchItemType::KeyItem) { first = getItemHead(PouchCategory::KeyItem); } else { return 0; } int count = 0; for (auto* item = first; item && item->getType() == type; item = list.next(item)) { count += item->get25(); } return count; } bool PauseMenuDataMgr::isWeaponSectionFull(const sead::SafeString& weapon_type) const { const auto lock = sead::makeScopedLock(mCritSection); const auto check = [this](auto get_flag, PouchCategory category, PouchItemType type) { const s32 num_slots = get_flag(false); if (mItemLists.list2.isEmpty()) return true; s32 num = 0; if (!getItems().isEmpty()) { for (auto item = getItemHead(category); item; item = nextItem(item)) { if (item->getType() != type) break; num += item->_25; } } return num_slots <= num; }; if (weapon_type == sValues.WeaponSmallSword || weapon_type == sValues.WeaponLargeSword || weapon_type == sValues.WeaponSpear) { return check(ksys::gdt::getFlag_WeaponPorchStockNum, PouchCategory::Sword, PouchItemType::Sword); } if (weapon_type == sValues.WeaponBow) { return check(ksys::gdt::getFlag_BowPorchStockNum, PouchCategory::Bow, PouchItemType::Bow); } if (weapon_type == sValues.WeaponShield) { return check(ksys::gdt::getFlag_ShieldPorchStockNum, PouchCategory::Shield, PouchItemType::Shield); } return false; } void PauseMenuDataMgr::itemGet(const sead::SafeString& name, int value, const act::WeaponModifierInfo* modifier) { if (name.include("Default") || name.include("Extra")) return; const auto type = getType(name); mNewlyAddedItem.mType = type; mNewlyAddedItem.mValue = value; mNewlyAddedItem._25 = 1; mNewlyAddedItem.mName = name; mNewlyAddedItem.mEquipped = false; if (modifier) { setItemModifier(mNewlyAddedItem, modifier); const auto add_type = modifier->flags.getDirect(); const auto add_value = mNewlyAddedItem.getWeaponAddValue(); sValues.last_added_weapon_add_type = add_type; sValues.last_added_weapon_add_value = add_value; } const auto lock = sead::makeScopedLock(mCritSection); auto& items = getItems(); ksys::PlayReportMgr::instance()->reportDebug("PouchGet", name); addToPouch(name, type, items, value, false, modifier); saveToGameData(items); } void PauseMenuDataMgr::updateAfterAddingItem(bool only_sort) { const auto lock = sead::makeScopedLock(mCritSection); if (getItems().isEmpty()) return; _44800 = PouchCategory::Invalid; auto& items = getItems(); items.sort(pouchItemSortPredicateForArrow); if (!only_sort) { updateInventoryInfo(items); updateListHeads(); saveToGameData(items); } } void PauseMenuDataMgr::updateListHeads() { mListHeads.fill(nullptr); for (s32 i = 0; i < NumPouch50; ++i) { auto& ptr = mArray1[i]; switch (mArray2[i]) { case PouchItemType::Sword: if (!mListHeads[s32(PouchCategory::Sword)]) mListHeads[s32(PouchCategory::Sword)] = &ptr; break; case PouchItemType::Bow: case PouchItemType::Arrow: if (!mListHeads[s32(PouchCategory::Bow)]) mListHeads[s32(PouchCategory::Bow)] = &ptr; break; case PouchItemType::Shield: if (!mListHeads[s32(PouchCategory::Shield)]) mListHeads[s32(PouchCategory::Shield)] = &ptr; break; case PouchItemType::ArmorHead: case PouchItemType::ArmorUpper: case PouchItemType::ArmorLower: if (!mListHeads[s32(PouchCategory::Armor)]) mListHeads[s32(PouchCategory::Armor)] = &ptr; break; case PouchItemType::Material: if (!mListHeads[s32(PouchCategory::Material)]) mListHeads[s32(PouchCategory::Material)] = &ptr; break; case PouchItemType::Food: if (!mListHeads[s32(PouchCategory::Food)]) mListHeads[s32(PouchCategory::Food)] = &ptr; break; case PouchItemType::KeyItem: if (!mListHeads[s32(PouchCategory::KeyItem)]) mListHeads[s32(PouchCategory::KeyItem)] = &ptr; break; case PouchItemType::Invalid: break; } } } void PauseMenuDataMgr::saveToGameData(const sead::OffsetList& list) const { if (mIsPouchForQuest) return; auto* item = list.size() > 0 ? list.nth(0) : nullptr; s32 num_food = 0; s32 idx = 0; s32 num_swords = 0; s32 num_shields = 0; s32 num_bows = 0; for (idx = 0; idx < NumPouchItemsMax; ++idx) { if (!item) { ksys::gdt::setFlag_PorchItem({}, idx); ksys::gdt::setFlag_PorchItem_EquipFlag(false, idx); ksys::gdt::setFlag_PorchItem_Value1(0, idx); continue; } while (item && !item->get25()) { #ifdef MATCHING_HACK_NX_CLANG asm(""); // Prevent list.mOffset from being loaded too early #endif item = list.next(item); } if (!item) { ksys::gdt::setFlag_PorchItem({}, idx); ksys::gdt::setFlag_PorchItem_EquipFlag(false, idx); ksys::gdt::setFlag_PorchItem_Value1(0, idx); return; } sead::FixedSafeString<64> name{item->getName()}; s32 value = item->getValue(); for (const auto& entry : mArray3) { if (entry.item == item) { value += ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::CanStack); } } const auto type = item->getType(); ksys::gdt::setFlag_PorchItem(name, idx); ksys::gdt::setFlag_PorchItem_EquipFlag(item->isEquipped(), idx); ksys::gdt::setFlag_PorchItem_Value1(value, idx); switch (type) { case PouchItemType::Sword: if (num_swords < NumSwordsMax) { act::WeaponModifierInfo(*item).savePorchSwordFlag(num_swords); ++num_swords; } break; case PouchItemType::Bow: if (num_bows < NumBowsMax) { act::WeaponModifierInfo(*item).savePorchBowFlag(num_bows); ++num_bows; } break; case PouchItemType::Arrow: break; case PouchItemType::Shield: if (num_shields < NumShieldsMax) { act::WeaponModifierInfo(*item).savePorchShieldFlag(num_shields); ++num_shields; } break; case PouchItemType::ArmorHead: case PouchItemType::ArmorUpper: case PouchItemType::ArmorLower: case PouchItemType::Material: break; case PouchItemType::Food: if (num_food >= NumFoodMax) break; ksys::gdt::setFlag_StaminaRecover( {static_cast(item->getCookData().mStaminaRecoverX), static_cast(item->getCookData().mStaminaRecoverY) * 30.0f / 30.0f}, num_food); ksys::gdt::setFlag_CookEffect0(item->getCookData().mCookEffect0, num_food); ksys::gdt::setFlag_CookEffect1({f32(item->getCookData().mCookEffect1), 0.0}, num_food); ksys::gdt::setFlag_CookMaterialName0(item->getIngredient(0), num_food); ksys::gdt::setFlag_CookMaterialName1(item->getIngredient(1), num_food); ksys::gdt::setFlag_CookMaterialName2(item->getIngredient(2), num_food); ksys::gdt::setFlag_CookMaterialName3(item->getIngredient(3), num_food); ksys::gdt::setFlag_CookMaterialName4(item->getIngredient(4), num_food); ++num_food; break; default: break; } item = list.next(item); } } void PauseMenuDataMgr::removeArrow(const sead::SafeString& arrow_name, int count) { if (!ksys::act::InfoData::instance()->hasTag(arrow_name.cstr(), ksys::act::tags::Arrow)) return; const auto lock = sead::makeScopedLock(mCritSection); s32 idx = 0; for (auto& item : getItems()) { if (arrow_name == item.getName()) { if (count < item.getCount()) { item.mValue -= count; break; } count -= item.getCount(); item.mValue = 0; } ++idx; } const auto num = getItemCount(arrow_name); if (!mIsPouchForQuest && idx >= 0) ksys::gdt::setFlag_PorchItem_Value1(num, idx); } void PauseMenuDataMgr::setWeaponItemValue(s32 value, PouchItemType type) { if (isPouchItemNotWeapon(type)) return; const auto lock = sead::makeScopedLock(mCritSection); s32 idx = 0; for (auto& item : getItems()) { if (!item.isEquipped() || item.getType() != type) { ++idx; continue; } item.setValue(value); if (idx >= 0 && !mIsPouchForQuest) ksys::gdt::setFlag_PorchItem_Value1(value, idx); return; } } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) const sead::SafeString& PauseMenuDataMgr::getDefaultEquipment(EquipmentSlot idx) const { if (idx != EquipmentSlot::WeaponArrow && u32(idx) < u32(sValues.default_equipment_names.size())) { return sValues.default_equipment_names(u32(idx)); } return sead::SafeString::cEmptyString; } bool PauseMenuDataMgr::hasItem(const sead::SafeString& name) const { const auto lock = sead::makeScopedLock(mCritSection); bool ret = false; for (auto& item : getItems()) { if (name != item.getName()) continue; if (ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::CanStack)) ret = item.getValue() > 0; else ret = true; break; } return ret; } PouchItem* PauseMenuDataMgr::getMasterSword() const { const auto lock = sead::makeScopedLock(mCritSection); for (auto* item = getItemHead(PouchCategory::Sword); item; item = nextItem(item)) { if (item->getType() != PouchItemType::Sword) return nullptr; if (item->_25 && item->getName() == "Weapon_Sword_070") return item; } return nullptr; } int PauseMenuDataMgr::getArrowCount(const sead::SafeString& name) const { const auto lock = sead::makeScopedLock(mCritSection); for (auto item = getItemHead(PouchCategory::Bow); item; item = nextItem(item)) { if (item->getType() > PouchItemType::Arrow) break; if (item->getType() == PouchItemType::Arrow && item->_25 && item->getName() == name) return item->getCount(); } return 0; } int PauseMenuDataMgr::getRealArrowCount(const sead::SafeString& name) const { if (!mIsPouchForQuest) return getArrowCount(name); s32 count; s32 status = 2; for (u32 i = 0; i < u32(NumPouchItemsMax); ++i) { const char* item_name; ksys::gdt::getFlag_PorchItem(&item_name, i); if (sead::SafeString(item_name).isEmpty()) break; if (sead::SafeString(item_name) == name) { count = ksys::gdt::getFlag_PorchItem_Value1(i); status = 1; break; } } return status == 2 ? 0 : count; } void PauseMenuDataMgr::breakMasterSword() { const auto lock = sead::makeScopedLock(mCritSection); s32 idx = 0; for (auto& item : getItems()) { if (item.getType() == PouchItemType::Sword && isMasterSwordActorName(item.getName())) { item.mValue = 0; item.mEquipped = false; if (!mIsPouchForQuest && idx >= 0) { ksys::gdt::setFlag_PorchItem_Value1(0, idx); ksys::gdt::setFlag_PorchItem_EquipFlag(false, idx); } break; } ++idx; } } void PauseMenuDataMgr::restoreMasterSword(bool only_if_broken) { const auto lock = sead::makeScopedLock(mCritSection); s32 idx = 0; for (auto& item : getItems()) { if (item.getType() == PouchItemType::Sword && isMasterSwordActorName(item.getName())) { if (only_if_broken && item.getValue() > 0) break; item.mValue = getWeaponInventoryLife(item.getName()); if (!mIsPouchForQuest && idx >= 0) ksys::gdt::setFlag_PorchItem_Value1(item.mValue, idx); break; } ++idx; } } using SortPredicate = int (*)(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data); static PouchCategory getTypeForCategory(PouchItemType type) { static constexpr sead::SafeArray sMap{{ PouchCategory::Sword, // Weapon PouchCategory::Bow, // Bow PouchCategory::Bow, // Arrow PouchCategory::Shield, // Shield PouchCategory::Armor, // ArmorHead PouchCategory::Armor, // ArmorUpper PouchCategory::Armor, // ArmorLower PouchCategory::Material, // Material PouchCategory::Food, // Food PouchCategory::KeyItem, // KeyItem }}; return sMap[s32(type)]; } static auto getSortPredicateTable() { sead::SafeArray table{ {sortWeapon, sortBow, sortShield, sortArmor, sortMaterial, sortFood, sortKeyItem}}; return table; } int pouchItemSortPredicate(const PouchItem* lhs, const PouchItem* rhs) { if (!lhs || !rhs) return 0; auto* info_data = ksys::act::InfoData::instance(); if (!info_data || !lhs->get25() || !rhs->get25()) return 0; const auto cat1 = getTypeForCategory(lhs->getType()); const auto cat2 = getTypeForCategory(rhs->getType()); if (cat1 != cat2) return 0; const auto cat3 = PauseMenuDataMgr::instance()->get44800(); if (cat3 != PouchCategory::Invalid && cat1 != cat3) return 0; auto predicate_table = getSortPredicateTable(); const auto* fn = &predicate_table[0]; if (u32(cat1) < u32(predicate_table.size())) fn = &predicate_table(u32(cat1)); return (*fn)(lhs, rhs, info_data); } static s32 compareSortKeys(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) { const auto a = data->getSortKey(lhs->getName().cstr()); const auto b = data->getSortKey(rhs->getName().cstr()); if (a < b) return -1; if (a > b) return 1; return 0; } int sortArmor(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) { if (ksys::gdt::getFlag_SortTypeArmorPouch()) { if (auto cmp = compareSortKeys(lhs, rhs, data)) return cmp; if (lhs->getType() < rhs->getType()) return -1; if (lhs->getType() > rhs->getType()) return 1; } else { if (lhs->getType() < rhs->getType()) return -1; if (lhs->getType() > rhs->getType()) return 1; if (auto cmp = compareSortKeys(lhs, rhs, data)) return cmp; } if (lhs->getCount() < rhs->getCount()) return -1; if (lhs->getCount() > rhs->getCount()) return 1; return 0; } int sortKeyItem(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) { if (auto cmp = compareSortKeys(lhs, rhs, data)) return cmp; return 0; } int getCookItemOrder(const PouchItem* item, ksys::act::InfoData* data) { const auto& order = sValues.cook_item_order; al::ByamlIter iter; if (data->getActorIter(&iter, item->getName().cstr())) { for (auto it = order.begin(), end = order.end(); it != end; ++it) { if (it->is_tag == 0 && item->getName() == it->name.cstr()) return it.getIndex(); } for (auto it = order.begin(), end = order.end(); it != end; ++it) { if (it->is_tag == 1 && data->hasTag(iter, it->hash)) return it.getIndex(); } } return order.size(); } int pouchItemSortPredicateForArrow(const PouchItem* lhs, const PouchItem* rhs) { if (!lhs || !rhs) return 0; static constexpr sead::SafeArray sMap{{0, 1, 2, 3, 4, 4, 4, 7, 8, 9}}; const auto x1 = sMap[s32(lhs->getType())]; const auto x2 = sMap[s32(rhs->getType())]; if (x1 < x2) return -1; if (x1 > x2) return 1; if (lhs->getType() != PouchItemType::Arrow) return 0; auto* info_data = ksys::act::InfoData::instance(); if (!info_data || !lhs->get25() || !rhs->get25()) return 0; const auto cat1 = getTypeForCategory(lhs->getType()); const auto cat2 = getTypeForCategory(rhs->getType()); if (cat1 != cat2) return 0; const auto cat3 = PauseMenuDataMgr::instance()->get44800(); if (cat3 != PouchCategory::Invalid && cat1 != cat3) return 0; const auto predicate_table = getSortPredicateTable(); const auto* fn = &predicate_table[0]; if (u32(cat1) < u32(predicate_table.size())) fn = &predicate_table(u32(cat1)); return (*fn)(lhs, rhs, info_data); } bool PauseMenuDataMgr::isHeroSoulEnabled(const sead::SafeString& name) const { if (name == sValues.Obj_HeroSoul_Zora || name == sValues.Obj_DLC_HeroSoul_Zora) { if (mZoraSoulItem) return mZoraSoulItem->isEquipped(); } if (name == sValues.Obj_HeroSoul_Rito || name == sValues.Obj_DLC_HeroSoul_Rito) { if (mRitoSoulItem) return mRitoSoulItem->isEquipped(); } if (name == sValues.Obj_HeroSoul_Goron || name == sValues.Obj_DLC_HeroSoul_Goron) { if (mGoronSoulItem) return mGoronSoulItem->isEquipped(); } if (name == sValues.Obj_HeroSoul_Gerudo || name == sValues.Obj_DLC_HeroSoul_Gerudo) { if (mGerudoSoulItem) return mGerudoSoulItem->isEquipped(); } return false; } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) bool PauseMenuDataMgr::hasRitoSoulPlus() const { return ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Rito() && aoc::Manager::instance()->hasAoc3(); } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) bool PauseMenuDataMgr::hasGoronSoulPlus() const { return ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Goron() && aoc::Manager::instance()->hasAoc3(); } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) bool PauseMenuDataMgr::hasGerudoSoulPlus() const { return ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Gerudo() && aoc::Manager::instance()->hasAoc3(); } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) bool PauseMenuDataMgr::hasZoraSoulPlus() const { return ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Zora() && aoc::Manager::instance()->hasAoc3(); } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) void PauseMenuDataMgr::updateDivineBeastClearFlags(int num_cleared_beasts) { switch (num_cleared_beasts) { case 1: ksys::gdt::setFlag_Find_4Relic_1stClear(true); break; case 2: ksys::gdt::setFlag_Find_4Relic_2ndClear(true); ksys::gdt::setFlag_BattleArena_Level1(false, true); ksys::gdt::setFlag_BattleArena_Level2(true, true); break; case 3: ksys::gdt::setFlag_Find_4Relic_3rdClear(true); ksys::gdt::setFlag_BattleArena_Level1(false, true); ksys::gdt::setFlag_BattleArena_Level2(false, true); ksys::gdt::setFlag_BattleArena_Level3(true, true); break; case 4: ksys::gdt::setFlag_Find_4Relic_4thClear(true); ksys::gdt::setFlag_BattleArena_Level1(false, true); ksys::gdt::setFlag_BattleArena_Level2(false, true); ksys::gdt::setFlag_BattleArena_Level3(false, true); ksys::gdt::setFlag_BattleArena_Level4(true, true); break; default: break; } } bool PauseMenuDataMgr::isOverCategoryLimit(PouchItemType type) const { const auto count = countItems(type); switch (type) { case PouchItemType::Sword: return ksys::gdt::getFlag_WeaponPorchStockNum() <= count || count >= NumSwordsMax; case PouchItemType::Bow: return ksys::gdt::getFlag_BowPorchStockNum() <= count || count >= NumBowsMax; case PouchItemType::Arrow: return count >= NumArrowItemsMax; case PouchItemType::Shield: return ksys::gdt::getFlag_ShieldPorchStockNum() <= count || count >= NumShieldsMax; case PouchItemType::ArmorHead: case PouchItemType::ArmorUpper: case PouchItemType::ArmorLower: return count >= NumArmorsMax; case PouchItemType::Material: return count >= NumMaterialsMax; case PouchItemType::Food: return count >= NumFoodMax; case PouchItemType::KeyItem: return count >= NumKeyItemsMax; case PouchItemType::Invalid: break; } return true; } void PauseMenuDataMgr::openItemCategoryIfNeeded() const { for (s32 i = 0; i < NumPouch50; ++i) { const auto type = mArray2[i]; if (isPouchItemArmor(type)) { ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Armor)); } else { switch (type) { case PouchItemType::Sword: ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Sword)); break; case PouchItemType::Bow: ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Bow)); break; case PouchItemType::Shield: ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Shield)); break; case PouchItemType::Material: ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Material)); break; case PouchItemType::Food: ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Food)); break; case PouchItemType::KeyItem: ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::KeyItem)); break; default: break; } } } } } // namespace uking::ui