Files
botw/src/Game/UI/uiPauseMenuDataMgr.cpp
T
Léo Lam d8da4fc5f2 Actor: PlayerInfo/PlayerBase are game, GameConfig ksys
Every decomp user of PlayerInfo is game code, and PlayerBase sits right
above Player. GameConfig.h models the engine base that
links at GameConfig's slot in KingSystem/System.

(AI assisted using Claude Fable 5.1 and Claude Opus 5.5)
2026-10-06 03:19:38 +02:00

3088 lines
103 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/Player/actPlayerBase.h"
#include "Game/Actor/Player/actPlayerInfo.h"
#include "Game/Actor/actPlayerCreateMgr.h"
#include "Game/Actor/actPlayerCreateUtils.h"
#include "Game/Actor/actWeapon.h"
#include "Game/Cooking/cookManager.h"
#include "Game/DLC/aocManager.h"
#include "Game/Scene/gameScene.h"
#include "Game/UI/uiUtils.h"
#include "Game/gameItemUtils.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/GameData/gdtCommonFlagsUtils.h"
#include "KingSystem/GameData/gdtManager.h"
#include "KingSystem/GameData/gdtSpecialFlagNames.h"
#include "KingSystem/GameData/gdtSpecialFlags.h"
#include "KingSystem/GameData/gdtTriggerParam.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)
// NOLINTNEXTLINE(misc-use-internal-linkage)
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{};
// NOLINTBEGIN
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";
// NOLINTEND
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
// NOLINTNEXTLINE(misc-use-internal-linkage)
int pouchItemSortPredicate(const PouchItem* lhs, const PouchItem* rhs);
// NOLINTNEXTLINE(misc-use-internal-linkage)
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) {
item.setWeaponModifier(0);
return;
}
u32 flags = modifier->flags.getDirect();
if (flags) {
item.setWeaponModifier(flags);
item.setWeaponModifierValue(modifier->value);
return;
}
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 = uking::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, s32(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(s32(idx));
const s32 value = gdt::getFlag_PorchItem_Value1(s32(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(s32(v.x));
mLastAddedItem->getCookData().setEffectDuration(s32(v.y));
gdt::getFlag_CookEffect0(&v, num_food);
mLastAddedItem->getCookData().setEffect(v);
gdt::getFlag_CookEffect1(&v, num_food);
mLastAddedItem->getCookData().setSellPrice(s32(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, s32(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;
}
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;
}
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::doAddToPouch(PouchItemType type, const sead::SafeString& name, Lists& lists,
int value, bool equipped,
const act::WeaponModifierInfo* modifier,
bool is_inventory_load) {
bool can_stack =
ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::CanStack);
sead::FixedSafeString<0x80> item_name_to_add = sead::SafeString::cEmptyString;
ksys::act::getSameGroupActorName(&item_name_to_add, name);
if (!is_inventory_load) {
if (ksys::act::InfoData::instance()->hasTag(item_name_to_add.cstr(),
ksys::act::tags::Arrow)) {
value *= ksys::act::getArrowArrowNum(ksys::act::InfoData::instance(), name.cstr());
}
if (can_stack) {
for (auto& item : lists.list1) {
if (item_name_to_add != item.getName() || item.mValue < 0) {
continue;
}
item.mValue = std::clamp(item.mValue + value, 0, ItemStackSizeMax);
if (name == sValues.Obj_DungeonClearSeal) {
int current = item.mValue;
int max = 0;
auto* gdt_manager = ksys::gdt::Manager::instance();
if (gdt_manager) {
sead::SafeString flag_name = ksys::gdt::flagname::DungeonClearSealNum();
gdt_manager->getParam().get1().getBuffer1()->getMaxValueForS32(&max,
flag_name);
}
if (max < current) {
item.mValue = max;
}
}
mLastAddedItem = &item;
resetItem();
_444fc = 0;
return;
}
}
}
// Add the item as a new slot
if (cannotGetItem(name, can_stack ? value : 1)) {
return;
}
if (item_name_to_add == sValues.Obj_WarpDLC) {
if (!aoc::Manager::instance()->hasAoc2()) {
return;
}
}
if (!aoc::Manager::instance()->hasAoc3()) {
if (item_name_to_add == sValues.Obj_DLC_HeroSoul_Zora) {
item_name_to_add = sValues.Obj_HeroSoul_Zora;
} else if (item_name_to_add == sValues.Obj_DLC_HeroSoul_Rito) {
item_name_to_add = sValues.Obj_HeroSoul_Rito;
} else if (item_name_to_add == sValues.Obj_DLC_HeroSoul_Goron) {
item_name_to_add = sValues.Obj_HeroSoul_Goron;
} else if (item_name_to_add == sValues.Obj_DLC_HeroSoul_Gerudo) {
item_name_to_add = sValues.Obj_HeroSoul_Gerudo;
}
} else {
if (item_name_to_add == sValues.Obj_HeroSoul_Zora &&
ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Zora()) {
item_name_to_add = sValues.Obj_DLC_HeroSoul_Zora;
} else if (item_name_to_add == sValues.Obj_HeroSoul_Rito &&
ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Rito()) {
item_name_to_add = sValues.Obj_DLC_HeroSoul_Rito;
} else if (item_name_to_add == sValues.Obj_HeroSoul_Goron &&
ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Goron()) {
item_name_to_add = sValues.Obj_DLC_HeroSoul_Goron;
} else if (item_name_to_add == sValues.Obj_HeroSoul_Gerudo &&
ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Gerudo()) {
item_name_to_add = sValues.Obj_DLC_HeroSoul_Gerudo;
}
}
auto* added_item = lists.pushNewItem();
if (!added_item) {
return;
}
added_item->mType = type;
added_item->mName = item_name_to_add;
added_item->setEquipped(equipped);
added_item->mItemUse = getItemUse(item_name_to_add);
if (added_item->mType == PouchItemType::Arrow) {
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Bow));
}
if (isPouchItemArmor(type)) {
if (ksys::act::InfoData::instance()->hasTag(item_name_to_add.cstr(),
ksys::act::tags::ArmorDye)) {
if (value >= 1 && value <= 15) {
added_item->mValue = value;
} else {
added_item->mValue = 0;
}
} else {
added_item->mValue = -1;
}
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Armor));
mLastAddedItem = added_item;
resetItem();
_444fc = 0;
return;
}
switch (type) {
case PouchItemType::Sword:
if (value <= 0 && !isMasterSwordActorName(item_name_to_add)) {
value = 1;
}
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Sword));
setItemModifier(*added_item, modifier);
break;
case PouchItemType::Bow:
value = std::max(value, 1);
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Bow));
setItemModifier(*added_item, modifier);
break;
case PouchItemType::Shield:
value = std::max(value, 1);
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Shield));
setItemModifier(*added_item, modifier);
break;
case PouchItemType::Material:
value = std::clamp(value, 0, ItemStackSizeMax);
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Material));
break;
case PouchItemType::Food:
value = std::clamp(value, 0, ItemStackSizeMax);
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Food));
break;
case PouchItemType::KeyItem:
if (ksys::act::InfoData::instance()->hasTag(item_name_to_add.cstr(),
ksys::act::tags::HeroSoul)) {
if (!is_inventory_load) {
// when getting champion ability, it's enabled by default
added_item->mEquipped = true;
}
// NOLINTBEGIN(bugprone-branch-clone) merging branch doesn't match
if (sValues.Obj_HeroSoul_Zora == item_name_to_add) {
mZoraSoulItem = added_item;
} else if (sValues.Obj_HeroSoul_Rito == item_name_to_add) {
mRitoSoulItem = added_item;
} else if (sValues.Obj_HeroSoul_Goron == item_name_to_add) {
mGoronSoulItem = added_item;
} else if (sValues.Obj_HeroSoul_Gerudo == item_name_to_add) {
mGerudoSoulItem = added_item;
} else if (sValues.Obj_DLC_HeroSoul_Zora == item_name_to_add) {
mZoraSoulItem = added_item;
} else if (sValues.Obj_DLC_HeroSoul_Rito == item_name_to_add) {
mRitoSoulItem = added_item;
} else if (sValues.Obj_DLC_HeroSoul_Goron == item_name_to_add) {
mGoronSoulItem = added_item;
} else if (sValues.Obj_DLC_HeroSoul_Gerudo == item_name_to_add) {
mGerudoSoulItem = added_item;
}
// NOLINTEND(bugprone-branch-clone)
}
value = std::clamp(value, 0, ItemStackSizeMax);
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::KeyItem));
break;
default:
break;
}
added_item->mValue = value;
mLastAddedItem = added_item;
resetItem();
_444fc = 0;
}
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;
}
void PauseMenuDataMgr::unequipAll(PouchItemType type) {
const auto lock = sead::makeScopedLock(mCritSection);
if (type == PouchItemType::Arrow)
return;
for (auto& item : getItems()) {
if (item.getType() > PouchItemType::ArmorLower)
break;
if (!item.isEquipped())
continue;
if (type == PouchItemType::Invalid) {
if (item.getType() != PouchItemType::Arrow) {
item.mEquipped = false;
}
} else if (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 (!uking::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::createPlayerEquipment() {
constexpr const char* Caller = "PauseMenuDataMgr_FromSaveData";
const auto lock = sead::makeScopedLock(mCritSection);
auto* create_mgr = act::CreatePlayerEquipActorMgr::instance();
if (!create_mgr) {
return;
}
std::array<PouchItem*, (u64)act::CreateEquipmentSlot::Length> equipment_items{};
const auto& items = getItems();
PouchItem* item = items.isEmpty() ? nullptr : items.nth(0);
bool need_head_b = false;
if (item) {
for (; item; item = items.next(item)) {
auto type = item->getType();
if (type > PouchItemType::Material) {
break;
}
if (item->isInInventory() && item->isEquipped()) {
u64 slot = (u64)getCreateEquipmentSlot(type);
if (slot < equipment_items.size()) {
equipment_items[slot] = item;
}
}
}
const auto* head = equipment_items[(u64)act::CreateEquipmentSlot::ArmorHead];
if (head && equipment_items[(u64)act::CreateEquipmentSlot::ArmorUpper]) {
need_head_b = act::needsArmorHeadB(
head->getName(),
equipment_items[(u64)act::CreateEquipmentSlot::ArmorUpper]->getName());
}
}
auto* player = uking::act::PlayerInfo::instance()->getPlayer();
s32 slot = 0; // <- this is required to match
for (u64 i = 0; i != equipment_items.size(); slot = s32(++i)) {
if (equipment_items[i]) {
if (!need_head_b || i != (u64)act::CreateEquipmentSlot::ArmorHead) {
createEquipmentFromItem(equipment_items[i], Caller);
} else {
create_mgr->requestCreateArmorHeadB(equipment_items[i]->getName(),
equipment_items[i]->getValue(), Caller);
}
continue;
}
if (i > (u64)act::CreateEquipmentSlot::WeaponBow) {
create_mgr->requestCreateDefaultArmor(slot, Caller);
continue;
}
if (player) {
auto equipment_slot = getEquipmentSlot((act::CreateEquipmentSlot)i);
player->switchEquipment(getDefaultEquipment(equipment_slot), 30);
}
#ifdef MATCHING_HACK_NX_CLANG
asm(""); // force no unroll
#endif
}
}
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(s32(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, s32(i));
if (sead::SafeString(item_name).isEmpty())
break;
if (sead::SafeString(item_name) == name) {
count = ksys::gdt::getFlag_PorchItem_Value1(s32(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;
}
}
namespace {
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;
}
} // namespace
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() != f32(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() != f32(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);
}
// NOLINTBEGIN(misc-use-anonymous-namespace)
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;
}
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 += s32(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);
}
// NOLINTEND(misc-use-anonymous-namespace)
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(s32(cat1));
return (*fn)(lhs, rhs, info_data);
}
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(s32(cat1));
return (*fn)(lhs, rhs, info_data);
}
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);
}
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;
int 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();
}
// 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[s32(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