botw/src/Game/UI/uiPauseMenuDataMgr.cpp

2823 lines
93 KiB
C++

#include "Game/UI/uiPauseMenuDataMgr.h"
#include <algorithm>
#include <container/seadBuffer.h>
#include <limits>
#include <math/seadMathCalcCommon.h>
#include <prim/seadScopedLock.h>
#include "Game/Actor/actWeapon.h"
#include "Game/Cooking/cookManager.h"
#include "Game/DLC/aocManager.h"
#include "Game/UI/uiUtils.h"
#include "Game/gameItemUtils.h"
#include "Game/gameScene.h"
#include "KingSystem/ActorSystem/Profiles/actPlayerBase.h"
#include "KingSystem/ActorSystem/actActorConstDataAccess.h"
#include "KingSystem/ActorSystem/actActorHeapUtil.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/GameData/gdtCommonFlagsUtils.h"
#include "KingSystem/GameData/gdtSpecialFlags.h"
#include "KingSystem/System/PlayReportMgr.h"
#include "KingSystem/Utils/Byaml/Byaml.h"
#include "KingSystem/Utils/HeapUtil.h"
#include "KingSystem/Utils/InitTimeInfo.h"
namespace uking::ui {
SEAD_SINGLETON_DISPOSER_IMPL(PauseMenuDataMgr)
sead::Vector2f sEmptyCookEffect{f32(CookEffectId::None), 0};
namespace {
sead::SafeArray<CookTagInfo, 11> 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<f32>::infinity();
f32 _24 = std::numeric_limits<f32>::infinity();
f32 _28 = std::numeric_limits<f32>::infinity();
u32 _2c{};
u32 _30{};
sead::SafeArray<f32, 70> _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<CookTagInfo> cook_item_order{sCookItemOrder_.size(),
sCookItemOrder_.getBufferPtr()};
sead::SafeArray<sead::SafeString, 7> 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<sead::SafeString, 5 * 4> 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);
}
}
int getWeaponModifierSortKey(sead::TypedBitFlag<act::WeaponModifier> flags) {
if (flags.isOn(act::WeaponModifier::AddAtk))
return flags.isOn(act::WeaponModifier::IsYellow) ? 0 : 1;
if (flags.isOn(act::WeaponModifier::AddLife))
return flags.isOn(act::WeaponModifier::IsYellow) ? 4 : 5;
if (flags.isOn(act::WeaponModifier::AddThrow))
return 6;
if (flags.isOn(act::WeaponModifier::AddCrit))
return 7;
if (flags.isOn(act::WeaponModifier::AddGuard))
return flags.isOn(act::WeaponModifier::IsYellow) ? 2 : 3;
if (flags.isOn(act::WeaponModifier::AddSurfMaster))
return 8;
if (flags.isOn(act::WeaponModifier::AddSpreadFire))
return 10;
if (flags.isOn(act::WeaponModifier::AddZoomRapid))
return 11;
if (flags.isOn(act::WeaponModifier::AddRapidFire))
return 9;
return 12;
}
int getFoodSortKey(int* effect_level, const PouchItem* item) {
const CookEffectId type = item->getCookData().getEffect();
const int level = int(item->getCookData().getEffectLevel());
*effect_level = level;
switch (type) {
case CookEffectId::LifeRecover:
return 0;
case CookEffectId::LifeMaxUp:
return 1;
case CookEffectId::GutsRecover:
return 2;
case CookEffectId::ExGutsMaxUp:
return 3;
case CookEffectId::MovingSpeed:
return 4;
case CookEffectId::Fireproof:
return 5;
case CookEffectId::ResistCold:
return 6;
case CookEffectId::ResistHot:
return 7;
case CookEffectId::ResistElectric:
return 8;
case CookEffectId::AttackUp:
return 9;
case CookEffectId::DefenseUp:
return 10;
case CookEffectId::Quietness:
return 11;
default:
*effect_level = 0;
if (ksys::act::InfoData::instance()->hasTag(item->getName().cstr(),
ksys::act::tags::CookResult)) {
return 0;
}
return 12;
}
}
int getWeaponPowerSortValue(const PouchItem* item, ksys::act::InfoData* data) {
#ifdef MATCHING_HACK_NX_CLANG
__builtin_assume(data); // Force LLVM to keep data (perhaps N had an "assume not null" macro?)
#endif
WeaponStats stats;
getWeaponStats(*item, &stats);
int value = stats.power;
if (item->getType() == PouchItemType::Bow && stats.bow_add_value > 1)
value *= stats.bow_add_value;
return value;
}
} // 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 < NumTabMax; ++i) {
mTabs[i] = nullptr;
mTabsType[i] = PouchItemType::Invalid;
}
for (auto& x : mGrabbedItems)
x = {};
resetEquippedItemArray();
}
PauseMenuDataMgr::~PauseMenuDataMgr() = default;
PouchItem::PouchItem() {
mData.cook.mEffect = sEmptyCookEffect;
for (s32 i = 0; i < NumIngredientsMax; ++i)
mIngredients.emplaceBack();
}
void PauseMenuDataMgr::resetItem() {
mNewlyAddedItem.mType = PouchItemType::Invalid;
mNewlyAddedItem.mItemUse = ItemUse::Invalid;
mNewlyAddedItem.mValue = 0;
mNewlyAddedItem.mEquipped = false;
mNewlyAddedItem.mInInventory = false;
mNewlyAddedItem.mName.clear();
mNewlyAddedItem.mData.cook = {};
mNewlyAddedItem.mData.cook.mEffect = sEmptyCookEffect;
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
void PauseMenuDataMgr::setItemModifier(PouchItem& item, const act::WeaponModifierInfo* modifier) {
if (modifier && !modifier->flags.isZero()) {
item.setWeaponModifier(modifier->flags.getDirect());
item.setWeaponModifierValue(static_cast<u32>(modifier->value));
} else {
item.setWeaponModifier(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())
destroyAndRecycleItem(item);
mListHeads.fill(nullptr);
for (s32 i = 0; i < NumTabMax; ++i) {
mTabs[i] = nullptr;
mTabsType[i] = PouchItemType::Invalid;
}
mNumTabs = 0;
ksys::gdt::setFlag_KorokNutsNum(0);
ksys::gdt::setFlag_DungeonClearSealNum(0);
ksys::gdt::setFlag_FairyCountCheck(false);
_444fc = {};
resetItemAndPointers();
mIsPouchForQuest = false;
for (auto& x : mGrabbedItems)
x = {};
_44504 = {};
_44508 = {};
_4450c = {};
_44510 = {};
_44514 = {};
mRitoSoulItem = {};
mGoronSoulItem = {};
mZoraSoulItem = {};
mGerudoSoulItem = {};
mCanSeeHealthBar = false;
mEquippedWeapons.fill({});
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 : mGrabbedItems)
x = {};
resetItemAndPointers();
_444fc = 0;
mIsPouchForQuest = false;
resetEquippedItemArray();
const auto lock = sead::makeScopedLock(mCritSection);
updateInventoryInfo(getItems());
}
void PauseMenuDataMgr::doLoadFromGameData() {
namespace gdt = ksys::gdt;
const auto lock = sead::makeScopedLock(mCritSection);
auto& lists = mItemLists;
for (auto* item = lists.list1.popFront(); item; item = lists.list1.popFront())
destroyAndRecycleItem(item);
mListHeads.fill(nullptr);
mRitoSoulItem = nullptr;
mGoronSoulItem = nullptr;
mZoraSoulItem = nullptr;
mGerudoSoulItem = nullptr;
for (s32 i = 0; i < NumTabMax; ++i) {
mTabs[i] = nullptr;
mTabsType[i] = PouchItemType::Invalid;
}
s32 num_food = 0;
s32 num_swords = 0;
s32 num_shields = 0;
s32 num_bows = 0;
mLastAddedItem = 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, lists, value, equipped, &info, true);
++num_swords;
break;
}
case PouchItemType::Bow: {
act::WeaponModifierInfo info{};
info.loadPorchBowFlag(num_bows);
addToPouch(item_name, type, lists, value, equipped, &info, true);
++num_bows;
break;
}
case PouchItemType::Shield: {
act::WeaponModifierInfo info{};
info.loadPorchShieldFlag(num_shields);
addToPouch(item_name, type, lists, value, equipped, &info, true);
++num_shields;
break;
}
default:
addToPouch(item_name, type, lists, value, equipped, nullptr, true);
break;
}
if (!mLastAddedItem)
continue;
if (type == PouchItemType::Food) {
sead::Vector2f v{0, 0};
gdt::getFlag_StaminaRecover(&v, num_food);
mLastAddedItem->getCookData().setHealthRecover(v.x);
mLastAddedItem->getCookData().setEffectDuration(v.y);
gdt::getFlag_CookEffect0(&v, num_food);
mLastAddedItem->getCookData().setEffect(v);
gdt::getFlag_CookEffect1(&v, num_food);
mLastAddedItem->getCookData().setSellPrice(v.x);
gdt::getFlag_CookMaterialName0(&item_name, num_food);
mLastAddedItem->setIngredient(0, item_name);
gdt::getFlag_CookMaterialName1(&item_name, num_food);
mLastAddedItem->setIngredient(1, item_name);
gdt::getFlag_CookMaterialName2(&item_name, num_food);
mLastAddedItem->setIngredient(2, item_name);
gdt::getFlag_CookMaterialName3(&item_name, num_food);
mLastAddedItem->setIngredient(3, item_name);
gdt::getFlag_CookMaterialName4(&item_name, num_food);
mLastAddedItem->setIngredient(4, item_name);
++num_food;
} else if (type == PouchItemType::Sword &&
isMasterSwordActorName(mLastAddedItem->getName()) &&
gdt::getFlag_MasterSwordRecoverTime() <= sead::Mathf::epsilon() &&
mLastAddedItem->getValue() <= 0) {
const s32 new_value = getWeaponInventoryLife(mLastAddedItem->getName());
mLastAddedItem->mValue = 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, lists, 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, lists, 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, lists, 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, lists, 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, lists, 1, true,
nullptr, true);
gdt::setFlag_IsPlayed_Demo125_0(true);
}
}
if (was_missing_hero_soul)
updateDivineBeastClearFlags(num_cleared_beasts);
mLastAddedItemTab = -1;
mLastAddedItemSlot = -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->isInInventory();
}
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->isInInventory();
}
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->isInInventory();
}
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->isInInventory();
}
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->isInInventory();
}
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->mInInventory;
}
}
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.mInInventory = true;
mNewlyAddedItem.mName = name;
mNewlyAddedItem.mEquipped = false;
if (modifier) {
setItemModifier(mNewlyAddedItem, modifier);
const auto add_type = modifier->flags.getDirect();
const auto add_value = mNewlyAddedItem.getWeaponModifierValue();
sValues.last_added_weapon_add_type = add_type;
sValues.last_added_weapon_add_value = add_value;
}
const auto lock = sead::makeScopedLock(mCritSection);
auto& lists = mItemLists;
ksys::PlayReportMgr::instance()->reportDebug("PouchGet", name);
addToPouch(name, type, lists, value, false, modifier);
saveToGameData(lists.list1);
}
void PauseMenuDataMgr::updateAfterAddingItem(bool only_sort) {
const auto lock = sead::makeScopedLock(mCritSection);
if (getItems().isEmpty())
return;
mCategoryToSort = PouchCategory::Invalid;
auto& items = getItems();
items.sort(pouchItemSortPredicateForArrow);
if (!only_sort) {
updateInventoryInfo(items);
updateListHeads();
saveToGameData(items);
}
}
void PauseMenuDataMgr::updateListHeads() {
for (s32 i = 0; i < mListHeads.size(); ++i)
mListHeads[i] = nullptr;
const auto set_if_null = [&](PouchCategory cat, s32 i) {
if (!mListHeads[s32(cat)])
mListHeads[s32(cat)] = &mTabs[i];
};
for (s32 i = 0; i < NumTabMax; ++i) {
if (mTabsType[i] == PouchItemType::Invalid)
continue;
switch (mTabsType[i]) {
case PouchItemType::Sword:
set_if_null(PouchCategory::Sword, i);
break;
case PouchItemType::Bow:
case PouchItemType::Arrow:
set_if_null(PouchCategory::Bow, i);
break;
case PouchItemType::Shield:
set_if_null(PouchCategory::Shield, i);
break;
case PouchItemType::ArmorHead:
case PouchItemType::ArmorUpper:
case PouchItemType::ArmorLower:
set_if_null(PouchCategory::Armor, i);
break;
case PouchItemType::Material:
set_if_null(PouchCategory::Material, i);
break;
case PouchItemType::Food:
set_if_null(PouchCategory::Food, i);
break;
case PouchItemType::KeyItem:
set_if_null(PouchCategory::KeyItem, i);
break;
case PouchItemType::Invalid:
break;
}
}
}
void PauseMenuDataMgr::addToPouch(const sead::SafeString& name, PouchItemType type, Lists& lists,
int value, bool equipped, const act::WeaponModifierInfo* modifier,
bool is_inventory_load) {
if (ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::CanGetCollectSet))
return;
if (type == PouchItemType::KeyItem) {
// If this is a key item and duplicates are not allowed, we need to check
// whether the item is already in the inventory. If it is, do not add it again.
if (!ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::CanStack)) {
for (auto* item = getItemHead(PouchCategory::KeyItem);
item && item->getType() == PouchItemType::KeyItem; item = lists.list1.next(item)) {
if (item->isInInventory() && item->getName() == name)
return;
}
}
} else if (type == PouchItemType::Sword && isMasterSwordActorName(name)) {
// Duplicates are not allowed for the Master Sword.
// Adding a second Master Sword should just refresh the item value or equipped status.
for (auto* item = getItemHead(PouchCategory::Sword);
item && item->getType() == PouchItemType::Sword; item = lists.list1.next(item)) {
if (!item->isInInventory() || item->getName() != name)
continue;
if (ksys::gdt::getFlag_MasterSwordRecoverTime() <= sead::Mathf::epsilon()) {
if (item->getValue() <= 0)
item->mValue = value;
} else {
item->mValue = 0;
item->mEquipped = false;
}
mLastAddedItem = item->mValue > 0 ? item : nullptr;
resetItem();
return;
}
}
if (type != PouchItemType::Invalid) {
doAddToPouch(type, name, lists, value, equipped, modifier, is_inventory_load);
updateAfterAddingItem(true);
updateInventoryInfo(lists.list1);
updateListHeads();
if (name == sValues.Obj_KorokNuts)
ksys::gdt::setFlag_KorokNutsNum(getItemCount(name));
else if (name == sValues.Obj_DungeonClearSeal)
ksys::gdt::setFlag_DungeonClearSealNum(getItemCount(name));
}
sead::SafeString same_group_actor_name;
ksys::act::getSameGroupActorName(&same_group_actor_name, name);
ksys::gdt::setIsGetItem(same_group_actor_name, true);
ksys::gdt::setIsGetItem2(same_group_actor_name, true);
}
void PauseMenuDataMgr::saveToGameData(const sead::OffsetList<PouchItem>& 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->isInInventory()) {
#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 : mGrabbedItems) {
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<f32>(item->getCookData().mHealthRecover),
static_cast<f32>(item->getCookData().mEffectDuration) * 30.0f / 30.0f},
num_food);
ksys::gdt::setFlag_CookEffect0(item->getCookData().mEffect, num_food);
ksys::gdt::setFlag_CookEffect1({f32(item->getCookData().mSellPrice), 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::cookItemGet(const uking::CookItem& cook_item) {
const auto* info = ksys::act::InfoData::instance();
if (!info->hasTag(cook_item.actor_name.cstr(), ksys::act::tags::CookResult))
return;
const auto lock = sead::makeScopedLock(mCritSection);
auto& lists = mItemLists;
ksys::PlayReportMgr::instance()->reportDebug("PouchGet", cook_item.actor_name);
const auto type = getType(cook_item.actor_name);
addToPouch(cook_item.actor_name, type, lists, 1, false);
setCookDataOnLastAddedItem(cook_item);
saveToGameData(lists.list1);
}
void PauseMenuDataMgr::setCookDataOnLastAddedItem(const uking::CookItem& cook_item) {
if (!mLastAddedItem)
return;
mLastAddedItem->getCookData().setEffectDuration(cook_item.effect_time);
mLastAddedItem->getCookData().setHealthRecover(static_cast<int>(cook_item.life_recover));
mLastAddedItem->getCookData().setSellPrice(cook_item.sell_price);
const int level = int(cook_item.vitality_boost);
const CookEffectId effect_id = cook_item.effect_id;
mLastAddedItem->getCookData().setEffect({float(effect_id), float(level)});
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());
}
}
const sead::SafeString& PauseMenuDataMgr::autoEquip(PouchItem* item,
const sead::OffsetList<PouchItem>& list) {
const auto type = item->getType();
if (type == PouchItemType::KeyItem)
item->mEquipped = true;
if (type >= PouchItemType::Material)
return sead::SafeString::cEmptyString;
switch (type) {
case PouchItemType::Sword:
case PouchItemType::Bow:
case PouchItemType::Shield:
case PouchItemType::Arrow:
for (auto& other : list) {
if (other.getType() > PouchItemType::Shield)
break;
if (other.getType() == type)
other.mEquipped = false;
}
break;
case PouchItemType::ArmorHead:
case PouchItemType::ArmorUpper:
case PouchItemType::ArmorLower:
for (auto& other : list) {
if (other.getType() > PouchItemType::ArmorLower)
break;
if (other.getType() == type)
other.mEquipped = false;
}
break;
default:
break;
}
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<PouchItem>& list, PouchItem* item,
const sead::SafeString& name) {
destroyAndRecycleItem(mItemLists, 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 = &it;
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 = &it;
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;
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);
}
// 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->isInInventory();
}
} else {
for (auto* item = first; item; item = items.next(item)) {
if (group_name == item->getName() && item->isInInventory())
count += !item->isEquipped();
}
}
return count;
}
void PauseMenuDataMgr::setEquippedWeaponItemValue(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.mValue = 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->mInInventory && item->getName() == "Weapon_Sword_070")
return item;
}
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)
destroyAndRecycleItem(mItemLists, item);
entry = {};
}
const auto& items = getItems();
mLastAddedItem = nullptr;
updateInventoryInfo(items);
updateListHeads();
saveToGameData(items);
}
bool PauseMenuDataMgr::removeGrabbedItem(ksys::act::BaseProcLink* link) {
bool found = false;
if (link && link->hasProc()) {
ksys::act::ActorConstDataAccess accessor;
ksys::act::acquireActor(link, &accessor);
const auto name = accessor.getName();
auto& cs = mCritSection;
const auto& items = getItems();
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;
destroyAndRecycleItem(mItemLists, 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->isInInventory() &&
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)) {
if (item->getType() > PouchItemType::Arrow)
break;
if (item->getType() == PouchItemType::Arrow && item->mInInventory &&
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;
}
}
static s32 checkItemRemoval(const sead::OffsetList<PouchItem>& 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<void>(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)
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->isInInventory())
continue;
if (!info->hasTag(item->getName().cstr(), ksys::act::tags::CookResult))
continue;
if (check_effect_type && item->getCookData().getEffectId() != 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->isInInventory();
}
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_hp = std::numeric_limits<s32>::max();
auto min_stam = std::numeric_limits<f32>::infinity();
auto min_level = std::numeric_limits<f32>::infinity();
PouchItem* to_remove = nullptr;
for (auto* item = getItemHead(PouchCategory::Food); item; item = items.next(item)) {
if (item->getType() != PouchItemType::Food)
break;
if (!item->isInInventory())
continue;
if (!info->hasTag(item->getName().cstr(), ksys::act::tags::CookResult))
continue;
if (item->getCookData().getEffectId() != effect_type && check_effect)
continue;
if (check_name && item->getName() != name)
continue;
const auto stam = f32(item->getCookData().mEffectDuration) * 30.0f;
if (stam < min_stam) {
min_hp = item->getCookData().mHealthRecover;
min_stam = stam;
to_remove = item;
min_level = item->getCookData().getEffectLevel();
} else if (stam == min_stam) {
const auto hp = item->getCookData().mHealthRecover;
if (hp < min_hp) {
min_hp = hp;
to_remove = item;
min_level = item->getCookData().getEffectLevel();
} else if (check_effect && hp == min_hp &&
item->getCookData().getEffectLevel() < min_level) {
min_level = item->getCookData().getEffectLevel();
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 getCategoryForTypeWithLookupTable(PouchItemType type) {
static constexpr sead::SafeArray<PouchCategory, NumPouchItemTypes> 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<SortPredicate, 7> table{{compareWeapon, compareBow, compareShield, compareArmor,
compareMaterial, compareFood, compareKeyItem}};
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->isInInventory() || !rhs->isInInventory())
return 0;
const auto cat1 = getCategoryForTypeWithLookupTable(lhs->getType());
const auto cat2 = getCategoryForTypeWithLookupTable(rhs->getType());
if (cat1 != cat2)
return 0;
const auto cat3 = PauseMenuDataMgr::instance()->getCategoryToSort();
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;
}
static s32 compareItemValues(const PouchItem* lhs, const PouchItem* rhs) {
const int val1 = lhs->getValue();
const int val2 = rhs->getValue();
// Higher is better
if (val1 > val2)
return -1;
if (val1 < val2)
return 1;
return 0;
}
static int doCompareWeapon(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
const auto use1 = lhs->getItemUse();
const auto use2 = rhs->getItemUse();
if (use1 < use2)
return -1;
if (use1 > use2)
return 1;
const auto power1 = getWeaponPowerSortValue(lhs, data);
const auto power2 = getWeaponPowerSortValue(rhs, data);
if (power1 > power2)
return -1;
if (power1 < power2)
return 1;
const auto mod1 = getWeaponModifierSortKey(lhs->getWeaponModifier());
const auto mod2 = getWeaponModifierSortKey(rhs->getWeaponModifier());
if (mod1 < mod2)
return -1;
if (mod1 > mod2)
return 1;
return compareItemValues(lhs, rhs);
}
static int compareWeaponType0(const PouchItem* lhs, const PouchItem* rhs,
ksys::act::InfoData* data) {
if (auto cmp = doCompareWeapon(lhs, rhs, data))
return cmp;
return compareSortKeys(lhs, rhs, data);
}
static int compareWeaponType1(const PouchItem* lhs, const PouchItem* rhs,
ksys::act::InfoData* data) {
if (auto cmp = compareSortKeys(lhs, rhs, data))
return cmp;
return doCompareWeapon(lhs, rhs, data);
}
static int getShieldGuardPower(const PouchItem* item, ksys::act::InfoData* data) {
int power = ksys::act::getWeaponCommonGuardPower(data, item->getName().cstr());
if (item->getWeaponModifier().isOn(act::WeaponModifier::AddGuard))
power += item->getWeaponModifierValue();
return power;
}
static int doCompareShield(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
const int gp1 = getShieldGuardPower(lhs, data);
const int gp2 = getShieldGuardPower(rhs, data);
// Higher is better
if (gp1 > gp2)
return -1;
if (gp1 < gp2)
return 1;
const int mod1 = getWeaponModifierSortKey(lhs->getWeaponModifier());
const int mod2 = getWeaponModifierSortKey(rhs->getWeaponModifier());
// Lower is better
if (mod1 < mod2)
return -1;
if (mod1 > mod2)
return 1;
return compareItemValues(lhs, rhs);
}
int compareWeapon(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
if (ksys::gdt::getFlag_SortTypeWeaponPouch())
return compareWeaponType1(lhs, rhs, data);
return compareWeaponType0(lhs, rhs, data);
}
int compareBow(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
if (lhs->getType() == PouchItemType::Arrow)
return compareSortKeys(lhs, rhs, data);
if (ksys::gdt::getFlag_SortTypeBowPouch())
return compareWeaponType1(lhs, rhs, data);
return compareWeaponType0(lhs, rhs, data);
}
int compareShield(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
if (ksys::gdt::getFlag_SortTypeShieldPouch()) {
if (auto cmp = compareSortKeys(lhs, rhs, data))
return cmp;
return doCompareShield(lhs, rhs, data);
} else {
if (auto cmp = doCompareShield(lhs, rhs, data))
return cmp;
return compareSortKeys(lhs, rhs, 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;
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 compareMaterial(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
const int order1 = getCookItemOrder(lhs, data);
const int order2 = getCookItemOrder(rhs, data);
// Lower is better
if (order1 < order2)
return -1;
if (order1 > order2)
return 1;
const int hp1 = getItemHitPointRecover(lhs->getName().cstr());
const int hp2 = getItemHitPointRecover(rhs->getName().cstr());
// Higher is better
if (hp1 > hp2)
return -1;
if (hp1 < hp2)
return 1;
return compareSortKeys(lhs, rhs, data);
}
int compareFood(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
int e1, e2;
const int k1 = getFoodSortKey(&e1, lhs);
const int k2 = getFoodSortKey(&e2, rhs);
// Lower key is better
if (k1 < k2)
return -1;
if (k1 > k2)
return 1;
if (auto cmp = compareSortKeys(lhs, rhs, data))
return cmp;
// Higher is better
if (e1 > e2)
return -1;
if (e1 < e2)
return 1;
const int st1 = lhs->getCookData().mHealthRecover;
const int st2 = rhs->getCookData().mHealthRecover;
// Higher is better
if (st1 > st2)
return -1;
if (st1 < st2)
return 1;
const auto sv1 = lhs->getCookData().getEffectDurationFrames();
const auto sv2 = rhs->getCookData().getEffectDurationFrames();
// Higher is better
if (sv1 > sv2)
return -1;
if (sv1 < sv2)
return 1;
return 0;
}
int compareKeyItem(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<int, NumPouchItemTypes> 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->isInInventory() || !rhs->isInInventory())
return 0;
const auto cat1 = getCategoryForTypeWithLookupTable(lhs->getType());
const auto cat2 = getCategoryForTypeWithLookupTable(rhs->getType());
if (cat1 != cat2)
return 0;
const auto cat3 = PauseMenuDataMgr::instance()->getCategoryToSort();
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);
}
// 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.isInInventory() && 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.isInInventory() && 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.isInInventory() &&
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.isInInventory() &&
InfoData::instance()->hasTag(item.getName().cstr(), tags::ArmorDye) &&
item.getValue() > 0) {
++count;
}
}
return count;
}
int PauseMenuDataMgr::getNextGrabbedItemIndex() const {
for (int i = 0; i < mGrabbedItems.size(); ++i) {
if (mGrabbedItems[i].item == nullptr)
return i;
}
return mGrabbedItems.size();
}
bool PauseMenuDataMgr::canGrabAnotherItem() const {
return std::any_of(mGrabbedItems.begin(), mGrabbedItems.end(),
[](const auto& entry) { return entry.item == nullptr; });
}
bool PauseMenuDataMgr::isNothingBeingGrabbed() const {
return std::all_of(mGrabbedItems.begin(), mGrabbedItems.end(),
[](const auto& entry) { return entry.item == nullptr; });
}
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();
}
int PauseMenuDataMgr::countItemsWithCategoryByType(PouchCategory category) const {
switch (category) {
case PouchCategory::Sword:
return countItems(PouchItemType::Sword);
case PouchCategory::Bow:
return countItems(PouchItemType::Bow) + countItems(PouchItemType::Arrow);
case PouchCategory::Shield:
return countItems(PouchItemType::Shield);
case PouchCategory::Armor:
return countItems(PouchItemType::ArmorLower);
case PouchCategory::Material:
return countItems(PouchItemType::Material);
case PouchCategory::Food:
return countItems(PouchItemType::Food);
case PouchCategory::KeyItem:
return countItems(PouchItemType::KeyItem);
case PouchCategory::Invalid:
break;
}
return 0;
}
const PouchItem* PauseMenuDataMgr::getItemByIndex(PouchItemType type, int index) const {
const auto& list = getItems();
PouchItem* item = nullptr;
switch (type) {
case PouchItemType::Sword:
item = getItemHead(PouchCategory::Sword);
break;
case PouchItemType::Bow:
item = getItemHead(PouchCategory::Bow);
break;
case PouchItemType::Arrow:
for (auto* item_ = getItemHead(PouchCategory::Bow);
item_ && item_->getType() <= PouchItemType::Arrow; item_ = list.next(item_)) {
if (item_->getType() == PouchItemType::Arrow) {
item = item_;
break;
}
}
break;
case PouchItemType::Shield:
item = getItemHead(PouchCategory::Shield);
break;
case PouchItemType::ArmorHead:
case PouchItemType::ArmorUpper:
case PouchItemType::ArmorLower:
item = getItemHead(PouchCategory::Armor);
break;
case PouchItemType::Material:
item = getItemHead(PouchCategory::Material);
break;
case PouchItemType::Food:
item = getItemHead(PouchCategory::Food);
break;
case PouchItemType::KeyItem:
item = getItemHead(PouchCategory::KeyItem);
break;
case PouchItemType::Invalid:
break;
}
if (!item)
return nullptr;
for (int i = 0; i < index; ++i) {
if (!item)
return nullptr;
item = list.next(item);
}
if (item) {
if (isPouchItemArmor(type))
return isPouchItemArmor(item->getType()) ? item : nullptr;
if (item->getType() == type)
return item;
}
return nullptr;
}
const PouchItem* PauseMenuDataMgr::getItemByIndex(PouchCategory category, int index) const {
switch (category) {
case PouchCategory::Sword:
return getItemByIndex(PouchItemType::Sword, index);
case PouchCategory::Bow: {
const auto num_bows = ksys::gdt::getFlag_BowPorchStockNum();
if (index < num_bows)
return getItemByIndex(PouchItemType::Bow, index);
return getItemByIndex(PouchItemType::Arrow, index - num_bows);
}
case PouchCategory::Shield:
return getItemByIndex(PouchItemType::Shield, index);
case PouchCategory::Armor:
return getItemByIndex(PouchItemType::ArmorLower, index);
case PouchCategory::Material:
return getItemByIndex(PouchItemType::Material, index);
case PouchCategory::Food:
return getItemByIndex(PouchItemType::Food, index);
case PouchCategory::KeyItem:
return getItemByIndex(PouchItemType::KeyItem, index);
case PouchCategory::Invalid:
break;
}
return nullptr;
}
bool PauseMenuDataMgr::hasItemDye() const {
int counts[1 + NumDyeColors]{};
for (const auto& item : getItems()) {
auto* info = ksys::act::InfoData::instance();
const int color = ksys::act::getItemStainColor(info, item.getName().cstr());
if (color >= FirstDyeColorIndex && color <= LastDyeColorIndex && item.isInInventory()) {
counts[color] += item.getValue();
if (counts[color] >= NumRequiredDyeItemsPerColor)
return true;
}
}
return false;
}
bool PauseMenuDataMgr::hasItemDye(int color) const {
int count = 0;
for (const auto& item : getItems()) {
auto* info = ksys::act::InfoData::instance();
if (ksys::act::getItemStainColor(info, item.getName().cstr()) == color &&
item.isInInventory()) {
count += item.getValue();
if (count >= NumRequiredDyeItemsPerColor)
return true;
}
}
return false;
}
const PouchItem* PauseMenuDataMgr::getLastAddedItem() const {
if (!mNewlyAddedItem.getName().isEmpty())
return &mNewlyAddedItem;
if (mLastAddedItem)
return mLastAddedItem;
return &mNewlyAddedItem;
}
void PauseMenuDataMgr::updateEquippedItemArray() {
mEquippedWeapons.fill({});
const auto lock = sead::makeScopedLock(mCritSection);
for (auto& item : getItems()) {
if (item.getType() > PouchItemType::Shield)
break;
if (item.isEquipped())
mEquippedWeapons[u32(item.getType())] = &item;
}
}
void PauseMenuDataMgr::resetEquippedItemArray() {
mEquippedWeapons.fill({});
}
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;
}
// NON_MATCHING: branching (really weird issue...)
int PauseMenuDataMgr::countArmors(const sead::SafeString& lowest_rank_armor_name) const {
if (!isPouchItemArmor(getType(lowest_rank_armor_name)))
return 0;
if (!getItemHead(PouchCategory::Armor))
return 0;
auto* info = ksys::act::InfoData::instance();
if (!info)
return 0;
const auto lock = sead::makeScopedLock(mCritSection);
int count = 0;
sead::FixedSafeString<64> armor_name{lowest_rank_armor_name};
while (!armor_name.isEmpty()) {
for (auto* item = *mListHeads[u32(PouchCategory::Armor)]; item; item = nextItem(item)) {
if (item->getType() > PouchItemType::ArmorLower)
break;
if (item->isInInventory() && armor_name == item->getName())
++count;
}
armor_name = ksys::act::getArmorNextRankName(info, armor_name.cstr());
}
return count;
}
void PauseMenuDataMgr::addNonDefaultItem(const sead::SafeString& name, int value,
const act::WeaponModifierInfo* modifier) {
if (name.include("Default") || name.include("Extra"))
return;
const auto type = getType(name);
if (type != PouchItemType::Arrow && value >= 0 && isPouchItemWeapon(type))
value *= act::WeaponModifierInfo::getLifeMultiplier();
const auto lock = sead::makeScopedLock(mCritSection);
addToPouch(name, type, mItemLists, value, false, modifier);
}
void PauseMenuDataMgr::openItemCategoryIfNeeded() const {
for (s32 i = 0; i < NumTabMax; ++i) {
const auto type = mTabsType[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;
}
}
}
}
void PauseMenuDataMgr::initInventoryForOpenWorldDemo() {
if (!GameScene::isOpenWorldDemo())
return;
initForNewSave();
auto* info = ksys::act::InfoData::instance();
sead::FixedSafeString<64> unused{""};
addNonDefaultItem("Weapon_Sword_001",
info ? ksys::act::getGeneralLife(info, "Weapon_Sword_001") : 30);
autoEquipLastAddedItem();
addNonDefaultItem("Weapon_Shield_001",
info ? ksys::act::getGeneralLife(info, "Weapon_Shield_001") : 30);
autoEquipLastAddedItem();
addNonDefaultItem("Weapon_Bow_001",
info ? ksys::act::getGeneralLife(info, "Weapon_Bow_001") : 30);
autoEquipLastAddedItem();
addNonDefaultItem("Armor_001_Head", 0);
autoEquipLastAddedItem();
addNonDefaultItem("Armor_116_Upper", 0);
autoEquipLastAddedItem();
addNonDefaultItem("Armor_001_Lower", 0);
autoEquipLastAddedItem();
for (int i = 0; i < 10; ++i) {
addNonDefaultItem("Obj_ArrowBundle_A_01", 1);
autoEquipLastAddedItem();
}
if (!ksys::util::getDebugHeap()) {
addItemForDebug("Obj_BombArrow_A_01", 100);
addItemForDebug("Obj_AncientArrow_A_01", 100);
addItemForDebug("Obj_FireArrow_A_01", 100);
addItemForDebug("Obj_ElectricArrow_A_01", 100);
addItemForDebug("Obj_IceArrow_A_01", 100);
addItemForDebug("PlayerStole2", 1);
addItemForDebug("Obj_DRStone_Get", 1);
addItemForDebug("GameRomHorseSaddle_01", 1);
addItemForDebug("GameRomHorseSaddle_02", 1);
addItemForDebug("GameRomHorseSaddle_03", 1);
addItemForDebug("GameRomHorseSaddle_04", 1);
addItemForDebug("GameRomHorseSaddle_05", 1);
addItemForDebug("GameRomHorseReins_01", 1);
addItemForDebug("GameRomHorseReins_02", 1);
addItemForDebug("GameRomHorseReins_03", 1);
addItemForDebug("GameRomHorseReins_04", 1);
addItemForDebug("GameRomHorseReins_05", 1);
addItemForDebug("Weapon_Lsword_056", 1);
addItemForDebug("Weapon_Spear_001", 1);
addItemForDebug("Weapon_Lsword_032", 1);
addItemForDebug("Weapon_Shield_002", 1);
addItemForDebug("Weapon_Bow_002", 1);
addItemForDebug("Weapon_Bow_027", 1);
addItemForDebug("Weapon_Sword_070", 1);
addItemForDebug("Weapon_Sword_043", 1);
addItemForDebug("Weapon_Sword_006", 1);
}
const auto lock = sead::makeScopedLock(mCritSection);
saveToGameData(getItems());
}
[[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) {
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;
}
}
void PauseMenuDataMgr::equipWeapon(PouchItem* weapon) {
if (!weapon) {
return;
}
auto lock = sead::makeScopedLock(mCritSection);
auto* item = getItems().nth(0);
while (item && item->isWeapon()) {
if (item->mType == weapon->mType) {
item->mEquipped = false;
}
item = getItems().next(item);
}
weapon->mEquipped = true;
saveToGameData(getItems());
}
void PauseMenuDataMgr::unequip(PouchItem* item) {
if (!item) {
return;
}
auto lock = sead::makeScopedLock(mCritSection);
item->mEquipped = false;
saveToGameData(getItems());
}
// FIXME: types
bool PauseMenuDataMgr::useItemFromRecipeAndSave(void* unk, int multiplier, PouchItem* item) {
auto lock = sead::makeScopedLock(mCritSection);
useItemFromRecipe(&mItemLists, unk, multiplier, item);
saveToGameData(getItems());
return true;
}
void PauseMenuDataMgr::grabbedItemStuff(PouchItem* item) {
auto lock = sead::makeScopedLock(mCritSection);
for (auto& cur : getItems()) {
if (&cur == item && item->mType == PouchItemType::Material) {
if (item->mValue > 1) {
item->mValue -= 1;
} else {
item->mEquipped = false;
item->mValue = 0;
}
for (auto& info : mGrabbedItems) {
if (info.item) {
if (info.item == item) {
info._9 = true;
}
} else {
info.item = item;
info._8 = true;
info._9 = false;
spawnDroppedInventoryItem(
item->getName().cstr(),
ksys::act::ActorHeapUtil::instance()->getBaseProcHeap(), -1,
SleepAfterInit::No, nullptr, SpawnViaCarryBox::Yes, 0.8, -0.8);
break;
}
}
}
updateInventoryInfo(getItems());
saveToGameData(getItems());
}
}
} // namespace uking::ui