botw/src/Game/UI/uiPauseMenuDataMgr.cpp

1312 lines
44 KiB
C++

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