mirror of https://github.com/zeldaret/botw.git
2823 lines
93 KiB
C++
2823 lines
93 KiB
C++
#include "Game/UI/uiPauseMenuDataMgr.h"
|
|
#include <algorithm>
|
|
#include <container/seadBuffer.h>
|
|
#include <limits>
|
|
#include <math/seadMathCalcCommon.h>
|
|
#include <prim/seadScopedLock.h>
|
|
#include "Game/Actor/actWeapon.h"
|
|
#include "Game/Cooking/cookManager.h"
|
|
#include "Game/DLC/aocManager.h"
|
|
#include "Game/UI/uiUtils.h"
|
|
#include "Game/gameItemUtils.h"
|
|
#include "Game/gameScene.h"
|
|
#include "KingSystem/ActorSystem/Profiles/actPlayerBase.h"
|
|
#include "KingSystem/ActorSystem/actActorConstDataAccess.h"
|
|
#include "KingSystem/ActorSystem/actActorHeapUtil.h"
|
|
#include "KingSystem/ActorSystem/actActorUtil.h"
|
|
#include "KingSystem/ActorSystem/actBaseProcLink.h"
|
|
#include "KingSystem/ActorSystem/actInfoCommon.h"
|
|
#include "KingSystem/ActorSystem/actInfoData.h"
|
|
#include "KingSystem/ActorSystem/actPlayerInfo.h"
|
|
#include "KingSystem/GameData/gdtCommonFlagsUtils.h"
|
|
#include "KingSystem/GameData/gdtSpecialFlags.h"
|
|
#include "KingSystem/System/PlayReportMgr.h"
|
|
#include "KingSystem/Utils/Byaml/Byaml.h"
|
|
#include "KingSystem/Utils/HeapUtil.h"
|
|
#include "KingSystem/Utils/InitTimeInfo.h"
|
|
|
|
namespace uking::ui {
|
|
|
|
SEAD_SINGLETON_DISPOSER_IMPL(PauseMenuDataMgr)
|
|
|
|
sead::Vector2f sEmptyCookEffect{f32(CookEffectId::None), 0};
|
|
|
|
namespace {
|
|
|
|
sead::SafeArray<CookTagInfo, 11> sCookItemOrder_{{
|
|
{1, "CookFruit", ksys::act::tags::CookFruit},
|
|
{1, "CookMushroom", ksys::act::tags::CookMushroom},
|
|
{1, "CookPlant", ksys::act::tags::CookPlant},
|
|
{1, "CookMeat", ksys::act::tags::CookMeat},
|
|
{1, "CookSpice", ksys::act::tags::CookSpice},
|
|
{1, "CookFish", ksys::act::tags::CookFish},
|
|
{0, "Animal_Insect_F", 0},
|
|
{1, "CookInsect", ksys::act::tags::CookInsect},
|
|
{1, "CookOre", ksys::act::tags::CookOre},
|
|
{1, "CookEnemy", ksys::act::tags::CookEnemy},
|
|
{0, "Obj_FireWoodBundle", 0},
|
|
}};
|
|
|
|
struct PouchStaticData {
|
|
ksys::util::InitTimeInfoEx info;
|
|
|
|
u32 last_added_weapon_add_type{};
|
|
u32 last_added_weapon_add_value{};
|
|
f32 _20 = std::numeric_limits<f32>::infinity();
|
|
f32 _24 = std::numeric_limits<f32>::infinity();
|
|
f32 _28 = std::numeric_limits<f32>::infinity();
|
|
u32 _2c{};
|
|
u32 _30{};
|
|
sead::SafeArray<f32, 70> _34{{
|
|
0.0, 1.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0,
|
|
1.0, 1.0, 1.0, 0.5, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.5, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0,
|
|
0.0, 0.5, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.5, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.5,
|
|
1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.5, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.5,
|
|
}};
|
|
u32 _14c{};
|
|
u32 _150{};
|
|
u32 _154{};
|
|
u32 _158{};
|
|
|
|
sead::SafeString WeaponSmallSword = "WeaponSmallSword";
|
|
sead::SafeString WeaponLargeSword = "WeaponLargeSword";
|
|
sead::SafeString WeaponSpear = "WeaponSpear";
|
|
sead::SafeString WeaponBow = "WeaponBow";
|
|
sead::SafeString WeaponShield = "WeaponShield";
|
|
sead::SafeString ArmorHead = "ArmorHead";
|
|
sead::SafeString ArmorUpper = "ArmorUpper";
|
|
sead::SafeString ArmorLower = "ArmorLower";
|
|
sead::SafeString Item = "Item";
|
|
sead::SafeString PlayerItem = "PlayerItem";
|
|
sead::SafeString HorseReins = "HorseReins";
|
|
|
|
sead::SafeString None = "None";
|
|
|
|
sead::SafeString Obj_KorokNuts = "Obj_KorokNuts";
|
|
sead::SafeString Obj_DungeonClearSeal = "Obj_DungeonClearSeal";
|
|
sead::SafeString Animal_Insect_F = "Animal_Insect_F";
|
|
|
|
sead::SafeString Obj_HeroSoul_Zora = "Obj_HeroSoul_Zora";
|
|
sead::SafeString Obj_HeroSoul_Rito = "Obj_HeroSoul_Rito";
|
|
sead::SafeString Obj_HeroSoul_Goron = "Obj_HeroSoul_Goron";
|
|
sead::SafeString Obj_HeroSoul_Gerudo = "Obj_HeroSoul_Gerudo";
|
|
|
|
sead::SafeString Obj_DLC_HeroSoul_Zora = "Obj_DLC_HeroSoul_Zora";
|
|
sead::SafeString Obj_DLC_HeroSoul_Rito = "Obj_DLC_HeroSoul_Rito";
|
|
sead::SafeString Obj_DLC_HeroSoul_Goron = "Obj_DLC_HeroSoul_Goron";
|
|
sead::SafeString Obj_DLC_HeroSoul_Gerudo = "Obj_DLC_HeroSoul_Gerudo";
|
|
sead::SafeString Obj_WarpDLC = "Obj_WarpDLC";
|
|
|
|
sead::SafeString Armor_116_Upper = "Armor_116_Upper";
|
|
sead::SafeString Armor_148_Upper = "Armor_148_Upper";
|
|
sead::SafeString Armor_149_Upper = "Armor_149_Upper";
|
|
sead::SafeString Armor_150_Upper = "Armor_150_Upper";
|
|
sead::SafeString Armor_151_Upper = "Armor_151_Upper";
|
|
|
|
sead::Buffer<CookTagInfo> cook_item_order{sCookItemOrder_.size(),
|
|
sCookItemOrder_.getBufferPtr()};
|
|
|
|
sead::SafeArray<sead::SafeString, 7> default_equipment_names{{
|
|
"Weapon_Default_Right",
|
|
"Weapon_Default_Bow",
|
|
"Weapon_Default_Arrow",
|
|
"Weapon_Default_Left",
|
|
"Armor_Default_Head",
|
|
"Armor_Default_Upper",
|
|
"Armor_Default_Lower",
|
|
}};
|
|
|
|
sead::SafeArray<sead::SafeString, 5 * 4> divine_helms{{
|
|
"Armor_168_Head", "Armor_169_Head", "Armor_181_Head", "Armor_182_Head", "Armor_183_Head",
|
|
"Armor_184_Head", "Armor_186_Head", "Armor_187_Head", "Armor_188_Head", "Armor_189_Head",
|
|
"Armor_190_Head", "Armor_191_Head", "Armor_192_Head", "Armor_193_Head", "Armor_194_Head",
|
|
"Armor_195_Head", "Armor_196_Head", "Armor_197_Head", "Armor_198_Head", "Armor_199_Head",
|
|
}};
|
|
};
|
|
|
|
PouchStaticData sValues;
|
|
|
|
void getSameGroupActorName(sead::SafeString* group, const sead::SafeString& item,
|
|
al::ByamlIter* iter = nullptr) {
|
|
if (iter) {
|
|
ksys::act::getSameGroupActorName(group, item, iter);
|
|
} else {
|
|
ksys::act::getSameGroupActorName(group, item);
|
|
}
|
|
}
|
|
|
|
int getWeaponModifierSortKey(sead::TypedBitFlag<act::WeaponModifier> flags) {
|
|
if (flags.isOn(act::WeaponModifier::AddAtk))
|
|
return flags.isOn(act::WeaponModifier::IsYellow) ? 0 : 1;
|
|
|
|
if (flags.isOn(act::WeaponModifier::AddLife))
|
|
return flags.isOn(act::WeaponModifier::IsYellow) ? 4 : 5;
|
|
|
|
if (flags.isOn(act::WeaponModifier::AddThrow))
|
|
return 6;
|
|
|
|
if (flags.isOn(act::WeaponModifier::AddCrit))
|
|
return 7;
|
|
|
|
if (flags.isOn(act::WeaponModifier::AddGuard))
|
|
return flags.isOn(act::WeaponModifier::IsYellow) ? 2 : 3;
|
|
|
|
if (flags.isOn(act::WeaponModifier::AddSurfMaster))
|
|
return 8;
|
|
|
|
if (flags.isOn(act::WeaponModifier::AddSpreadFire))
|
|
return 10;
|
|
|
|
if (flags.isOn(act::WeaponModifier::AddZoomRapid))
|
|
return 11;
|
|
|
|
if (flags.isOn(act::WeaponModifier::AddRapidFire))
|
|
return 9;
|
|
|
|
return 12;
|
|
}
|
|
|
|
int getFoodSortKey(int* effect_level, const PouchItem* item) {
|
|
const CookEffectId type = item->getCookData().getEffect();
|
|
const int level = int(item->getCookData().getEffectLevel());
|
|
*effect_level = level;
|
|
|
|
switch (type) {
|
|
case CookEffectId::LifeRecover:
|
|
return 0;
|
|
case CookEffectId::LifeMaxUp:
|
|
return 1;
|
|
case CookEffectId::GutsRecover:
|
|
return 2;
|
|
case CookEffectId::ExGutsMaxUp:
|
|
return 3;
|
|
case CookEffectId::MovingSpeed:
|
|
return 4;
|
|
case CookEffectId::Fireproof:
|
|
return 5;
|
|
case CookEffectId::ResistCold:
|
|
return 6;
|
|
case CookEffectId::ResistHot:
|
|
return 7;
|
|
case CookEffectId::ResistElectric:
|
|
return 8;
|
|
case CookEffectId::AttackUp:
|
|
return 9;
|
|
case CookEffectId::DefenseUp:
|
|
return 10;
|
|
case CookEffectId::Quietness:
|
|
return 11;
|
|
default:
|
|
*effect_level = 0;
|
|
if (ksys::act::InfoData::instance()->hasTag(item->getName().cstr(),
|
|
ksys::act::tags::CookResult)) {
|
|
return 0;
|
|
}
|
|
return 12;
|
|
}
|
|
}
|
|
|
|
int getWeaponPowerSortValue(const PouchItem* item, ksys::act::InfoData* data) {
|
|
#ifdef MATCHING_HACK_NX_CLANG
|
|
__builtin_assume(data); // Force LLVM to keep data (perhaps N had an "assume not null" macro?)
|
|
#endif
|
|
|
|
WeaponStats stats;
|
|
getWeaponStats(*item, &stats);
|
|
int value = stats.power;
|
|
if (item->getType() == PouchItemType::Bow && stats.bow_add_value > 1)
|
|
value *= stats.bow_add_value;
|
|
return value;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int pouchItemSortPredicate(const PouchItem* lhs, const PouchItem* rhs);
|
|
int pouchItemSortPredicateForArrow(const PouchItem* lhs, const PouchItem* rhs);
|
|
|
|
PauseMenuDataMgr::PauseMenuDataMgr() {
|
|
mListHeads.fill(nullptr);
|
|
for (s32 i = 0; i < NumTabMax; ++i) {
|
|
mTabs[i] = nullptr;
|
|
mTabsType[i] = PouchItemType::Invalid;
|
|
}
|
|
for (auto& x : mGrabbedItems)
|
|
x = {};
|
|
resetEquippedItemArray();
|
|
}
|
|
|
|
PauseMenuDataMgr::~PauseMenuDataMgr() = default;
|
|
|
|
PouchItem::PouchItem() {
|
|
mData.cook.mEffect = sEmptyCookEffect;
|
|
for (s32 i = 0; i < NumIngredientsMax; ++i)
|
|
mIngredients.emplaceBack();
|
|
}
|
|
|
|
void PauseMenuDataMgr::resetItem() {
|
|
mNewlyAddedItem.mType = PouchItemType::Invalid;
|
|
mNewlyAddedItem.mItemUse = ItemUse::Invalid;
|
|
mNewlyAddedItem.mValue = 0;
|
|
mNewlyAddedItem.mEquipped = false;
|
|
mNewlyAddedItem.mInInventory = false;
|
|
mNewlyAddedItem.mName.clear();
|
|
mNewlyAddedItem.mData.cook = {};
|
|
mNewlyAddedItem.mData.cook.mEffect = sEmptyCookEffect;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
void PauseMenuDataMgr::setItemModifier(PouchItem& item, const act::WeaponModifierInfo* modifier) {
|
|
if (modifier && !modifier->flags.isZero()) {
|
|
item.setWeaponModifier(modifier->flags.getDirect());
|
|
item.setWeaponModifierValue(static_cast<u32>(modifier->value));
|
|
} else {
|
|
item.setWeaponModifier(0);
|
|
}
|
|
}
|
|
|
|
void PauseMenuDataMgr::init(sead::Heap* heap) {}
|
|
|
|
void PauseMenuDataMgr::initForNewSave() {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
for (auto* item = getItems().popFront(); item; item = getItems().popFront())
|
|
destroyAndRecycleItem(item);
|
|
|
|
mListHeads.fill(nullptr);
|
|
for (s32 i = 0; i < NumTabMax; ++i) {
|
|
mTabs[i] = nullptr;
|
|
mTabsType[i] = PouchItemType::Invalid;
|
|
}
|
|
|
|
mNumTabs = 0;
|
|
ksys::gdt::setFlag_KorokNutsNum(0);
|
|
ksys::gdt::setFlag_DungeonClearSealNum(0);
|
|
ksys::gdt::setFlag_FairyCountCheck(false);
|
|
_444fc = {};
|
|
resetItemAndPointers();
|
|
mIsPouchForQuest = false;
|
|
for (auto& x : mGrabbedItems)
|
|
x = {};
|
|
_44504 = {};
|
|
_44508 = {};
|
|
_4450c = {};
|
|
_44510 = {};
|
|
_44514 = {};
|
|
mRitoSoulItem = {};
|
|
mGoronSoulItem = {};
|
|
mZoraSoulItem = {};
|
|
mGerudoSoulItem = {};
|
|
mCanSeeHealthBar = false;
|
|
mEquippedWeapons.fill({});
|
|
|
|
auto* player = ksys::act::PlayerInfo::instance()->getPlayer();
|
|
if (player) {
|
|
player->switchEquipment(getDefaultEquipment(EquipmentSlot::WeaponRight), 1);
|
|
player->switchEquipment(getDefaultEquipment(EquipmentSlot::WeaponLeft), 1);
|
|
player->switchEquipment(getDefaultEquipment(EquipmentSlot::WeaponBow), 1);
|
|
player->switchEquipment(getDefaultEquipment(EquipmentSlot::ArmorHead), 30);
|
|
player->switchEquipment(getDefaultEquipment(EquipmentSlot::ArmorUpper), 30);
|
|
player->switchEquipment(getDefaultEquipment(EquipmentSlot::ArmorLower), 30);
|
|
}
|
|
}
|
|
|
|
void PauseMenuDataMgr::loadFromGameData() {
|
|
doLoadFromGameData();
|
|
|
|
for (auto& x : mGrabbedItems)
|
|
x = {};
|
|
resetItemAndPointers();
|
|
_444fc = 0;
|
|
mIsPouchForQuest = false;
|
|
resetEquippedItemArray();
|
|
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
updateInventoryInfo(getItems());
|
|
}
|
|
|
|
void PauseMenuDataMgr::doLoadFromGameData() {
|
|
namespace gdt = ksys::gdt;
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
auto& lists = mItemLists;
|
|
|
|
for (auto* item = lists.list1.popFront(); item; item = lists.list1.popFront())
|
|
destroyAndRecycleItem(item);
|
|
|
|
mListHeads.fill(nullptr);
|
|
mRitoSoulItem = nullptr;
|
|
mGoronSoulItem = nullptr;
|
|
mZoraSoulItem = nullptr;
|
|
mGerudoSoulItem = nullptr;
|
|
for (s32 i = 0; i < NumTabMax; ++i) {
|
|
mTabs[i] = nullptr;
|
|
mTabsType[i] = PouchItemType::Invalid;
|
|
}
|
|
|
|
s32 num_food = 0;
|
|
s32 num_swords = 0;
|
|
s32 num_shields = 0;
|
|
s32 num_bows = 0;
|
|
mLastAddedItem = nullptr;
|
|
bool found_travel_medallion = false;
|
|
|
|
for (u32 idx = 0; idx < u32(NumPouchItemsMax); ++idx) {
|
|
const char* item_name;
|
|
gdt::getFlag_PorchItem(&item_name, idx);
|
|
|
|
if (found_travel_medallion || sValues.Obj_WarpDLC == item_name)
|
|
found_travel_medallion = true;
|
|
|
|
if (sead::SafeString(item_name).isEmpty())
|
|
break;
|
|
|
|
const bool equipped = gdt::getFlag_PorchItem_EquipFlag(idx);
|
|
const s32 value = gdt::getFlag_PorchItem_Value1(idx);
|
|
const auto type = getType(item_name);
|
|
|
|
switch (type) {
|
|
case PouchItemType::Sword: {
|
|
act::WeaponModifierInfo info{};
|
|
info.loadPorchSwordFlag(num_swords);
|
|
addToPouch(item_name, type, lists, value, equipped, &info, true);
|
|
++num_swords;
|
|
break;
|
|
}
|
|
case PouchItemType::Bow: {
|
|
act::WeaponModifierInfo info{};
|
|
info.loadPorchBowFlag(num_bows);
|
|
addToPouch(item_name, type, lists, value, equipped, &info, true);
|
|
++num_bows;
|
|
break;
|
|
}
|
|
case PouchItemType::Shield: {
|
|
act::WeaponModifierInfo info{};
|
|
info.loadPorchShieldFlag(num_shields);
|
|
addToPouch(item_name, type, lists, value, equipped, &info, true);
|
|
++num_shields;
|
|
break;
|
|
}
|
|
default:
|
|
addToPouch(item_name, type, lists, value, equipped, nullptr, true);
|
|
break;
|
|
}
|
|
|
|
if (!mLastAddedItem)
|
|
continue;
|
|
|
|
if (type == PouchItemType::Food) {
|
|
sead::Vector2f v{0, 0};
|
|
|
|
gdt::getFlag_StaminaRecover(&v, num_food);
|
|
mLastAddedItem->getCookData().setHealthRecover(v.x);
|
|
mLastAddedItem->getCookData().setEffectDuration(v.y);
|
|
|
|
gdt::getFlag_CookEffect0(&v, num_food);
|
|
mLastAddedItem->getCookData().setEffect(v);
|
|
|
|
gdt::getFlag_CookEffect1(&v, num_food);
|
|
mLastAddedItem->getCookData().setSellPrice(v.x);
|
|
|
|
gdt::getFlag_CookMaterialName0(&item_name, num_food);
|
|
mLastAddedItem->setIngredient(0, item_name);
|
|
|
|
gdt::getFlag_CookMaterialName1(&item_name, num_food);
|
|
mLastAddedItem->setIngredient(1, item_name);
|
|
|
|
gdt::getFlag_CookMaterialName2(&item_name, num_food);
|
|
mLastAddedItem->setIngredient(2, item_name);
|
|
|
|
gdt::getFlag_CookMaterialName3(&item_name, num_food);
|
|
mLastAddedItem->setIngredient(3, item_name);
|
|
|
|
gdt::getFlag_CookMaterialName4(&item_name, num_food);
|
|
mLastAddedItem->setIngredient(4, item_name);
|
|
|
|
++num_food;
|
|
|
|
} else if (type == PouchItemType::Sword &&
|
|
isMasterSwordActorName(mLastAddedItem->getName()) &&
|
|
gdt::getFlag_MasterSwordRecoverTime() <= sead::Mathf::epsilon() &&
|
|
mLastAddedItem->getValue() <= 0) {
|
|
const s32 new_value = getWeaponInventoryLife(mLastAddedItem->getName());
|
|
mLastAddedItem->mValue = new_value;
|
|
gdt::setFlag_PorchItem_Value1(new_value, idx);
|
|
}
|
|
}
|
|
|
|
// Add the Travel Medallion (Obj_WarpDLC) to the inventory if it is missing for some reason
|
|
if (aoc::Manager::instance()->hasAoc2() &&
|
|
!(found_travel_medallion | !gdt::getFlag_IsGet_Obj_WarpDLC())) {
|
|
addToPouch(sValues.Obj_WarpDLC.cstr(), PouchItemType::KeyItem, lists, 1, false, nullptr,
|
|
true);
|
|
}
|
|
|
|
// Add missing champion powers and fix some divine beast related flags
|
|
// in case something went wrong with the "divine beast cleared" cutscenes
|
|
bool was_missing_hero_soul = false;
|
|
s32 num_cleared_beasts = 0;
|
|
|
|
if (gdt::getFlag_Clear_RemainsWind()) {
|
|
++num_cleared_beasts;
|
|
if (!mRitoSoulItem) {
|
|
was_missing_hero_soul = true;
|
|
addToPouch(sValues.Obj_HeroSoul_Rito.cstr(), PouchItemType::KeyItem, lists, 1, true,
|
|
nullptr, true);
|
|
gdt::setFlag_IsPlayed_Demo119_0(true);
|
|
}
|
|
}
|
|
|
|
if (gdt::getFlag_Clear_RemainsFire()) {
|
|
++num_cleared_beasts;
|
|
if (!mGoronSoulItem) {
|
|
was_missing_hero_soul = true;
|
|
addToPouch(sValues.Obj_HeroSoul_Goron.cstr(), PouchItemType::KeyItem, lists, 1, true,
|
|
nullptr, true);
|
|
gdt::setFlag_IsPlayed_Demo116_0(true);
|
|
}
|
|
}
|
|
|
|
if (gdt::getFlag_Clear_RemainsWater()) {
|
|
++num_cleared_beasts;
|
|
if (!mZoraSoulItem) {
|
|
was_missing_hero_soul = true;
|
|
addToPouch(sValues.Obj_HeroSoul_Zora.cstr(), PouchItemType::KeyItem, lists, 1, true,
|
|
nullptr, true);
|
|
gdt::setFlag_IsPlayed_Demo122_0(true);
|
|
}
|
|
}
|
|
|
|
if (gdt::getFlag_Clear_RemainsElectric()) {
|
|
++num_cleared_beasts;
|
|
if (!mGerudoSoulItem) {
|
|
was_missing_hero_soul = true;
|
|
addToPouch(sValues.Obj_HeroSoul_Gerudo.cstr(), PouchItemType::KeyItem, lists, 1, true,
|
|
nullptr, true);
|
|
gdt::setFlag_IsPlayed_Demo125_0(true);
|
|
}
|
|
}
|
|
|
|
if (was_missing_hero_soul)
|
|
updateDivineBeastClearFlags(num_cleared_beasts);
|
|
|
|
mLastAddedItemTab = -1;
|
|
mLastAddedItemSlot = -1;
|
|
}
|
|
|
|
bool PauseMenuDataMgr::cannotGetItem(const sead::SafeString& name, int n) const {
|
|
namespace act = ksys::act;
|
|
|
|
al::ByamlIter iter;
|
|
if (!act::InfoData::instance()->getActorIter(&iter, name.cstr()))
|
|
return true;
|
|
|
|
if (!act::InfoData::instance()->hasTag(iter, act::tags::CanGetPouch))
|
|
return true;
|
|
|
|
if (act::InfoData::instance()->getSortKey(name.cstr()) == 99999)
|
|
return true;
|
|
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
const auto type = getType(name);
|
|
|
|
if (act::InfoData::instance()->hasTag(iter, act::tags::CanStack))
|
|
return !hasFreeSpaceForItem(mItemLists, name, n);
|
|
|
|
if (type <= PouchItemType::Shield) {
|
|
switch (type) {
|
|
case PouchItemType::Sword: {
|
|
const auto limit = ksys::gdt::getFlag_WeaponPorchStockNum();
|
|
return isList2Empty() || limit < countItems(PouchItemType::Sword) + n;
|
|
}
|
|
case PouchItemType::Shield: {
|
|
const auto limit = ksys::gdt::getFlag_ShieldPorchStockNum();
|
|
return isList2Empty() || limit < countItems(PouchItemType::Shield) + n;
|
|
}
|
|
case PouchItemType::Bow: {
|
|
const auto limit = ksys::gdt::getFlag_BowPorchStockNum();
|
|
return isList2Empty() || limit < countItems(PouchItemType::Bow) + n;
|
|
}
|
|
default: {
|
|
const auto limit = ksys::gdt::getFlag_WeaponPorchStockNum();
|
|
return isList2Empty() || limit < countItems(PouchItemType::Sword, true) + n;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == PouchItemType::Food)
|
|
return isList2Empty() || countItems(PouchItemType::Food, true) + n > NumFoodMax;
|
|
|
|
if (type == PouchItemType::Material)
|
|
return isList2Empty() || countItems(PouchItemType::Material) + n > NumMaterialsMax;
|
|
|
|
if (type == PouchItemType::KeyItem)
|
|
return isList2Empty() || countItems(PouchItemType::KeyItem) + n > NumKeyItemsMax;
|
|
|
|
return isList2Empty() || countItems(PouchItemType::ArmorHead) + n > NumArmorsMax;
|
|
}
|
|
|
|
PouchItemType PauseMenuDataMgr::getType(const sead::SafeString& item, al::ByamlIter* iter) {
|
|
sead::SafeString group;
|
|
getSameGroupActorName(&group, item, iter);
|
|
|
|
al::ByamlIter actor_iter;
|
|
if (iter && iter->isValid() && group == item) {
|
|
actor_iter = *iter;
|
|
} else {
|
|
if (!ksys::act::InfoData::instance()->getActorIter(&actor_iter, group.cstr()))
|
|
return PouchItemType::Invalid;
|
|
}
|
|
|
|
if (ksys::act::InfoData::instance()->hasTag(actor_iter, ksys::act::tags::Arrow))
|
|
return PouchItemType::Arrow;
|
|
|
|
const char* profile_c;
|
|
if (!ksys::act::InfoData::instance()->getActorProfile(&profile_c, actor_iter))
|
|
return PouchItemType::Invalid;
|
|
|
|
const sead::SafeString profile = profile_c;
|
|
|
|
if (profile == sValues.WeaponSmallSword)
|
|
return PouchItemType::Sword;
|
|
if (profile == sValues.WeaponLargeSword)
|
|
return PouchItemType::Sword;
|
|
if (profile == sValues.WeaponSpear)
|
|
return PouchItemType::Sword;
|
|
|
|
if (profile == sValues.WeaponBow)
|
|
return PouchItemType::Bow;
|
|
|
|
if (profile == sValues.WeaponShield)
|
|
return PouchItemType::Shield;
|
|
|
|
if (profile == sValues.ArmorHead)
|
|
return PouchItemType::ArmorHead;
|
|
if (profile == sValues.ArmorUpper)
|
|
return PouchItemType::ArmorUpper;
|
|
if (profile == sValues.ArmorLower)
|
|
return PouchItemType::ArmorLower;
|
|
|
|
if (profile == sValues.HorseReins)
|
|
return PouchItemType::KeyItem;
|
|
|
|
if (ksys::act::InfoData::instance()->hasTag(actor_iter, ksys::act::tags::CookResult))
|
|
return PouchItemType::Food;
|
|
if (ksys::act::InfoData::instance()->hasTag(actor_iter, ksys::act::tags::RoastItem))
|
|
return PouchItemType::Food;
|
|
|
|
if (ksys::act::InfoData::instance()->hasTag(actor_iter, ksys::act::tags::Important))
|
|
return PouchItemType::KeyItem;
|
|
|
|
return PouchItemType::Material;
|
|
}
|
|
|
|
bool PauseMenuDataMgr::hasFreeSpaceForItem(const PauseMenuDataMgr::Lists& lists,
|
|
const sead::SafeString& name, int n) const {
|
|
sead::SafeString group_name;
|
|
getSameGroupActorName(&group_name, name);
|
|
|
|
const int count = getItemCount(group_name);
|
|
const bool is_arrow =
|
|
ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::Arrow);
|
|
|
|
if (count == 0 && is_arrow) {
|
|
for (const auto& item : lists.list1) {
|
|
if (item.getType() == PouchItemType::Arrow && group_name == item.getName())
|
|
return true;
|
|
}
|
|
|
|
return countItems(PouchItemType::Arrow) < NumArrowItemsMax;
|
|
}
|
|
|
|
if (count == 0) {
|
|
const auto type = getType(name);
|
|
|
|
if (type == PouchItemType::Food)
|
|
return !lists.list2.isEmpty() && countItems(PouchItemType::Food, true) < NumFoodMax;
|
|
|
|
if (type == PouchItemType::Material)
|
|
return !lists.list2.isEmpty() && countItems(PouchItemType::Material) < NumMaterialsMax;
|
|
|
|
if (type == PouchItemType::KeyItem)
|
|
return !lists.list2.isEmpty() && countItems(PouchItemType::KeyItem) < NumKeyItemsMax;
|
|
|
|
return false;
|
|
}
|
|
|
|
return count + n <= ItemStackSizeMax;
|
|
}
|
|
|
|
int PauseMenuDataMgr::countItems(PouchItemType type, bool count_any_weapon) const {
|
|
if (isPouchItemInvalid(type) || getItems().isEmpty())
|
|
return 0;
|
|
|
|
const auto& list = getItems();
|
|
|
|
if (count_any_weapon) {
|
|
PouchItem** head = nullptr;
|
|
|
|
if (type <= PouchItemType::Shield) {
|
|
int count = 0;
|
|
for (auto* item = list.nth(0); item && item->getType() <= PouchItemType::Shield;
|
|
item = list.next(item)) {
|
|
count += item->isInInventory();
|
|
}
|
|
return count;
|
|
|
|
} else if (type <= PouchItemType::ArmorLower) {
|
|
int count = 0;
|
|
for (auto* item = getItemHead(PouchCategory::Armor);
|
|
item && item->getType() <= PouchItemType::ArmorLower; item = list.next(item)) {
|
|
count += item->isInInventory();
|
|
}
|
|
return count;
|
|
|
|
} else if (type == PouchItemType::Material) {
|
|
head = getItemHeadp(PouchCategory::Material);
|
|
if (!head)
|
|
return 0;
|
|
|
|
} else if (type == PouchItemType::Food) {
|
|
head = getItemHeadp(PouchCategory::Food);
|
|
if (!head)
|
|
return 0;
|
|
|
|
} else if (type == PouchItemType::KeyItem) {
|
|
head = getItemHeadp(PouchCategory::KeyItem);
|
|
if (!head)
|
|
return 0;
|
|
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
if (!head)
|
|
return 0;
|
|
|
|
int count = 0;
|
|
for (auto* item = *head; item && item->getType() == type; item = list.next(item)) {
|
|
count += item->isInInventory();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
PouchItem* first = nullptr;
|
|
|
|
if (type == PouchItemType::Sword) {
|
|
first = getItemHead(PouchCategory::Sword);
|
|
|
|
} else if (type == PouchItemType::Bow) {
|
|
first = getItemHead(PouchCategory::Bow);
|
|
|
|
} else if (type == PouchItemType::Arrow) {
|
|
for (auto* item = getItemHead(PouchCategory::Bow);
|
|
item && item->getType() <= PouchItemType::Arrow; item = list.next(item)) {
|
|
if (item->getType() == PouchItemType::Arrow) {
|
|
first = item;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else if (type == PouchItemType::Shield) {
|
|
first = getItemHead(PouchCategory::Shield);
|
|
|
|
} else if (type <= PouchItemType::ArmorLower) {
|
|
int count = 0;
|
|
for (auto* item = getItemHead(PouchCategory::Armor);
|
|
item && item->getType() <= PouchItemType::ArmorLower; item = list.next(item)) {
|
|
count += item->isInInventory();
|
|
}
|
|
return count;
|
|
|
|
} else if (type == PouchItemType::Material) {
|
|
first = getItemHead(PouchCategory::Material);
|
|
|
|
} else if (type == PouchItemType::Food) {
|
|
first = getItemHead(PouchCategory::Food);
|
|
|
|
} else if (type == PouchItemType::KeyItem) {
|
|
first = getItemHead(PouchCategory::KeyItem);
|
|
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
int count = 0;
|
|
for (auto* item = first; item && item->getType() == type; item = list.next(item)) {
|
|
count += item->isInInventory();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool PauseMenuDataMgr::isWeaponSectionFull(const sead::SafeString& weapon_type) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
const auto check = [this](auto get_flag, PouchCategory category, PouchItemType type) {
|
|
const s32 num_slots = get_flag(false);
|
|
if (mItemLists.list2.isEmpty())
|
|
return true;
|
|
|
|
s32 num = 0;
|
|
if (!getItems().isEmpty()) {
|
|
for (auto item = getItemHead(category); item; item = nextItem(item)) {
|
|
if (item->getType() != type)
|
|
break;
|
|
num += item->mInInventory;
|
|
}
|
|
}
|
|
|
|
return num_slots <= num;
|
|
};
|
|
|
|
if (weapon_type == sValues.WeaponSmallSword || weapon_type == sValues.WeaponLargeSword ||
|
|
weapon_type == sValues.WeaponSpear) {
|
|
return check(ksys::gdt::getFlag_WeaponPorchStockNum, PouchCategory::Sword,
|
|
PouchItemType::Sword);
|
|
}
|
|
|
|
if (weapon_type == sValues.WeaponBow) {
|
|
return check(ksys::gdt::getFlag_BowPorchStockNum, PouchCategory::Bow, PouchItemType::Bow);
|
|
}
|
|
|
|
if (weapon_type == sValues.WeaponShield) {
|
|
return check(ksys::gdt::getFlag_ShieldPorchStockNum, PouchCategory::Shield,
|
|
PouchItemType::Shield);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void PauseMenuDataMgr::itemGet(const sead::SafeString& name, int value,
|
|
const act::WeaponModifierInfo* modifier) {
|
|
if (name.include("Default") || name.include("Extra"))
|
|
return;
|
|
|
|
const auto type = getType(name);
|
|
|
|
mNewlyAddedItem.mType = type;
|
|
mNewlyAddedItem.mValue = value;
|
|
mNewlyAddedItem.mInInventory = true;
|
|
mNewlyAddedItem.mName = name;
|
|
mNewlyAddedItem.mEquipped = false;
|
|
|
|
if (modifier) {
|
|
setItemModifier(mNewlyAddedItem, modifier);
|
|
const auto add_type = modifier->flags.getDirect();
|
|
const auto add_value = mNewlyAddedItem.getWeaponModifierValue();
|
|
sValues.last_added_weapon_add_type = add_type;
|
|
sValues.last_added_weapon_add_value = add_value;
|
|
}
|
|
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
auto& lists = mItemLists;
|
|
ksys::PlayReportMgr::instance()->reportDebug("PouchGet", name);
|
|
addToPouch(name, type, lists, value, false, modifier);
|
|
saveToGameData(lists.list1);
|
|
}
|
|
|
|
void PauseMenuDataMgr::updateAfterAddingItem(bool only_sort) {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
if (getItems().isEmpty())
|
|
return;
|
|
|
|
mCategoryToSort = PouchCategory::Invalid;
|
|
auto& items = getItems();
|
|
items.sort(pouchItemSortPredicateForArrow);
|
|
if (!only_sort) {
|
|
updateInventoryInfo(items);
|
|
updateListHeads();
|
|
saveToGameData(items);
|
|
}
|
|
}
|
|
|
|
void PauseMenuDataMgr::updateListHeads() {
|
|
for (s32 i = 0; i < mListHeads.size(); ++i)
|
|
mListHeads[i] = nullptr;
|
|
|
|
const auto set_if_null = [&](PouchCategory cat, s32 i) {
|
|
if (!mListHeads[s32(cat)])
|
|
mListHeads[s32(cat)] = &mTabs[i];
|
|
};
|
|
|
|
for (s32 i = 0; i < NumTabMax; ++i) {
|
|
if (mTabsType[i] == PouchItemType::Invalid)
|
|
continue;
|
|
|
|
switch (mTabsType[i]) {
|
|
case PouchItemType::Sword:
|
|
set_if_null(PouchCategory::Sword, i);
|
|
break;
|
|
case PouchItemType::Bow:
|
|
case PouchItemType::Arrow:
|
|
set_if_null(PouchCategory::Bow, i);
|
|
break;
|
|
case PouchItemType::Shield:
|
|
set_if_null(PouchCategory::Shield, i);
|
|
break;
|
|
case PouchItemType::ArmorHead:
|
|
case PouchItemType::ArmorUpper:
|
|
case PouchItemType::ArmorLower:
|
|
set_if_null(PouchCategory::Armor, i);
|
|
break;
|
|
case PouchItemType::Material:
|
|
set_if_null(PouchCategory::Material, i);
|
|
break;
|
|
case PouchItemType::Food:
|
|
set_if_null(PouchCategory::Food, i);
|
|
break;
|
|
case PouchItemType::KeyItem:
|
|
set_if_null(PouchCategory::KeyItem, i);
|
|
break;
|
|
case PouchItemType::Invalid:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PauseMenuDataMgr::addToPouch(const sead::SafeString& name, PouchItemType type, Lists& lists,
|
|
int value, bool equipped, const act::WeaponModifierInfo* modifier,
|
|
bool is_inventory_load) {
|
|
if (ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::CanGetCollectSet))
|
|
return;
|
|
|
|
if (type == PouchItemType::KeyItem) {
|
|
// If this is a key item and duplicates are not allowed, we need to check
|
|
// whether the item is already in the inventory. If it is, do not add it again.
|
|
if (!ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::CanStack)) {
|
|
for (auto* item = getItemHead(PouchCategory::KeyItem);
|
|
item && item->getType() == PouchItemType::KeyItem; item = lists.list1.next(item)) {
|
|
if (item->isInInventory() && item->getName() == name)
|
|
return;
|
|
}
|
|
}
|
|
|
|
} else if (type == PouchItemType::Sword && isMasterSwordActorName(name)) {
|
|
// Duplicates are not allowed for the Master Sword.
|
|
// Adding a second Master Sword should just refresh the item value or equipped status.
|
|
for (auto* item = getItemHead(PouchCategory::Sword);
|
|
item && item->getType() == PouchItemType::Sword; item = lists.list1.next(item)) {
|
|
if (!item->isInInventory() || item->getName() != name)
|
|
continue;
|
|
|
|
if (ksys::gdt::getFlag_MasterSwordRecoverTime() <= sead::Mathf::epsilon()) {
|
|
if (item->getValue() <= 0)
|
|
item->mValue = value;
|
|
} else {
|
|
item->mValue = 0;
|
|
item->mEquipped = false;
|
|
}
|
|
|
|
mLastAddedItem = item->mValue > 0 ? item : nullptr;
|
|
resetItem();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (type != PouchItemType::Invalid) {
|
|
doAddToPouch(type, name, lists, value, equipped, modifier, is_inventory_load);
|
|
|
|
updateAfterAddingItem(true);
|
|
updateInventoryInfo(lists.list1);
|
|
updateListHeads();
|
|
|
|
if (name == sValues.Obj_KorokNuts)
|
|
ksys::gdt::setFlag_KorokNutsNum(getItemCount(name));
|
|
else if (name == sValues.Obj_DungeonClearSeal)
|
|
ksys::gdt::setFlag_DungeonClearSealNum(getItemCount(name));
|
|
}
|
|
|
|
sead::SafeString same_group_actor_name;
|
|
ksys::act::getSameGroupActorName(&same_group_actor_name, name);
|
|
ksys::gdt::setIsGetItem(same_group_actor_name, true);
|
|
ksys::gdt::setIsGetItem2(same_group_actor_name, true);
|
|
}
|
|
|
|
void PauseMenuDataMgr::saveToGameData(const sead::OffsetList<PouchItem>& list) const {
|
|
if (mIsPouchForQuest)
|
|
return;
|
|
|
|
auto* item = list.size() > 0 ? list.nth(0) : nullptr;
|
|
s32 num_food = 0;
|
|
s32 idx = 0;
|
|
s32 num_swords = 0;
|
|
s32 num_shields = 0;
|
|
s32 num_bows = 0;
|
|
|
|
for (idx = 0; idx < NumPouchItemsMax; ++idx) {
|
|
if (!item) {
|
|
ksys::gdt::setFlag_PorchItem({}, idx);
|
|
ksys::gdt::setFlag_PorchItem_EquipFlag(false, idx);
|
|
ksys::gdt::setFlag_PorchItem_Value1(0, idx);
|
|
continue;
|
|
}
|
|
|
|
while (item && !item->isInInventory()) {
|
|
#ifdef MATCHING_HACK_NX_CLANG
|
|
asm(""); // Prevent list.mOffset from being loaded too early
|
|
#endif
|
|
item = list.next(item);
|
|
}
|
|
|
|
if (!item) {
|
|
ksys::gdt::setFlag_PorchItem({}, idx);
|
|
ksys::gdt::setFlag_PorchItem_EquipFlag(false, idx);
|
|
ksys::gdt::setFlag_PorchItem_Value1(0, idx);
|
|
return;
|
|
}
|
|
|
|
sead::FixedSafeString<64> name{item->getName()};
|
|
s32 value = item->getValue();
|
|
for (const auto& entry : mGrabbedItems) {
|
|
if (entry.item == item) {
|
|
value +=
|
|
ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::CanStack);
|
|
}
|
|
}
|
|
const auto type = item->getType();
|
|
ksys::gdt::setFlag_PorchItem(name, idx);
|
|
ksys::gdt::setFlag_PorchItem_EquipFlag(item->isEquipped(), idx);
|
|
ksys::gdt::setFlag_PorchItem_Value1(value, idx);
|
|
switch (type) {
|
|
case PouchItemType::Sword:
|
|
if (num_swords < NumSwordsMax) {
|
|
act::WeaponModifierInfo(*item).savePorchSwordFlag(num_swords);
|
|
++num_swords;
|
|
}
|
|
break;
|
|
case PouchItemType::Bow:
|
|
if (num_bows < NumBowsMax) {
|
|
act::WeaponModifierInfo(*item).savePorchBowFlag(num_bows);
|
|
++num_bows;
|
|
}
|
|
break;
|
|
case PouchItemType::Arrow:
|
|
break;
|
|
case PouchItemType::Shield:
|
|
if (num_shields < NumShieldsMax) {
|
|
act::WeaponModifierInfo(*item).savePorchShieldFlag(num_shields);
|
|
++num_shields;
|
|
}
|
|
break;
|
|
case PouchItemType::ArmorHead:
|
|
case PouchItemType::ArmorUpper:
|
|
case PouchItemType::ArmorLower:
|
|
case PouchItemType::Material:
|
|
break;
|
|
case PouchItemType::Food:
|
|
if (num_food >= NumFoodMax)
|
|
break;
|
|
|
|
ksys::gdt::setFlag_StaminaRecover(
|
|
{static_cast<f32>(item->getCookData().mHealthRecover),
|
|
static_cast<f32>(item->getCookData().mEffectDuration) * 30.0f / 30.0f},
|
|
num_food);
|
|
ksys::gdt::setFlag_CookEffect0(item->getCookData().mEffect, num_food);
|
|
ksys::gdt::setFlag_CookEffect1({f32(item->getCookData().mSellPrice), 0.0}, num_food);
|
|
ksys::gdt::setFlag_CookMaterialName0(item->getIngredient(0), num_food);
|
|
ksys::gdt::setFlag_CookMaterialName1(item->getIngredient(1), num_food);
|
|
ksys::gdt::setFlag_CookMaterialName2(item->getIngredient(2), num_food);
|
|
ksys::gdt::setFlag_CookMaterialName3(item->getIngredient(3), num_food);
|
|
ksys::gdt::setFlag_CookMaterialName4(item->getIngredient(4), num_food);
|
|
++num_food;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
item = list.next(item);
|
|
}
|
|
}
|
|
|
|
void PauseMenuDataMgr::cookItemGet(const uking::CookItem& cook_item) {
|
|
const auto* info = ksys::act::InfoData::instance();
|
|
if (!info->hasTag(cook_item.actor_name.cstr(), ksys::act::tags::CookResult))
|
|
return;
|
|
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
auto& lists = mItemLists;
|
|
|
|
ksys::PlayReportMgr::instance()->reportDebug("PouchGet", cook_item.actor_name);
|
|
const auto type = getType(cook_item.actor_name);
|
|
addToPouch(cook_item.actor_name, type, lists, 1, false);
|
|
setCookDataOnLastAddedItem(cook_item);
|
|
saveToGameData(lists.list1);
|
|
}
|
|
|
|
void PauseMenuDataMgr::setCookDataOnLastAddedItem(const uking::CookItem& cook_item) {
|
|
if (!mLastAddedItem)
|
|
return;
|
|
|
|
mLastAddedItem->getCookData().setEffectDuration(cook_item.effect_time);
|
|
mLastAddedItem->getCookData().setHealthRecover(static_cast<int>(cook_item.life_recover));
|
|
mLastAddedItem->getCookData().setSellPrice(cook_item.sell_price);
|
|
const int level = int(cook_item.vitality_boost);
|
|
const CookEffectId effect_id = cook_item.effect_id;
|
|
mLastAddedItem->getCookData().setEffect({float(effect_id), float(level)});
|
|
for (s32 i = 0; i < cook_item.ingredients.size(); ++i)
|
|
mLastAddedItem->setIngredient(i, cook_item.ingredients[i]);
|
|
mLastAddedItem->sortIngredients();
|
|
}
|
|
|
|
void PauseMenuDataMgr::autoEquipLastAddedItem() {
|
|
if (mLastAddedItem && mLastAddedItem->getType() <= PouchItemType::ArmorLower) {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
autoEquip(mLastAddedItem, getItems());
|
|
}
|
|
}
|
|
|
|
const sead::SafeString& PauseMenuDataMgr::autoEquip(PouchItem* item,
|
|
const sead::OffsetList<PouchItem>& list) {
|
|
const auto type = item->getType();
|
|
if (type == PouchItemType::KeyItem)
|
|
item->mEquipped = true;
|
|
|
|
if (type >= PouchItemType::Material)
|
|
return sead::SafeString::cEmptyString;
|
|
|
|
switch (type) {
|
|
case PouchItemType::Sword:
|
|
case PouchItemType::Bow:
|
|
case PouchItemType::Shield:
|
|
case PouchItemType::Arrow:
|
|
for (auto& other : list) {
|
|
if (other.getType() > PouchItemType::Shield)
|
|
break;
|
|
if (other.getType() == type)
|
|
other.mEquipped = false;
|
|
}
|
|
break;
|
|
case PouchItemType::ArmorHead:
|
|
case PouchItemType::ArmorUpper:
|
|
case PouchItemType::ArmorLower:
|
|
for (auto& other : list) {
|
|
if (other.getType() > PouchItemType::ArmorLower)
|
|
break;
|
|
if (other.getType() == type)
|
|
other.mEquipped = false;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
item->mEquipped = true;
|
|
updateInventoryInfo(list);
|
|
saveToGameData(list);
|
|
return sead::SafeString::cEmptyString;
|
|
}
|
|
|
|
// NON_MATCHING: harmless reordering
|
|
void PauseMenuDataMgr::unequipAll(PouchItemType type) {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
if (type == PouchItemType::Arrow)
|
|
return;
|
|
|
|
for (auto& item : getItems()) {
|
|
if (type == PouchItemType::Invalid) {
|
|
if (item.getType() > PouchItemType::ArmorLower)
|
|
break;
|
|
if (item.isEquipped() && item.getType() != PouchItemType::Arrow)
|
|
item.mEquipped = false;
|
|
|
|
} else {
|
|
if (item.getType() > PouchItemType::ArmorLower)
|
|
break;
|
|
if (item.isEquipped() && item.getType() == type) {
|
|
item.mEquipped = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
KSYS_ALWAYS_INLINE inline void
|
|
PauseMenuDataMgr::deleteItem_(const sead::OffsetList<PouchItem>& list, PouchItem* item,
|
|
const sead::SafeString& name) {
|
|
destroyAndRecycleItem(mItemLists, item);
|
|
ksys::PlayReportMgr::instance()->reportDebug("PouchDelete", name);
|
|
saveToGameData(list);
|
|
updateInventoryInfo(list);
|
|
updateListHeads();
|
|
}
|
|
|
|
void PauseMenuDataMgr::removeItem(const sead::SafeString& name) {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
const auto& items = getItems();
|
|
|
|
PouchItem* item = nullptr;
|
|
for (auto& it : items) {
|
|
if (name == it.getName() && !it.isEquipped()) {
|
|
item = ⁢
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!item)
|
|
return;
|
|
|
|
// If the item is stackable, just remove one copy of the item if possible.
|
|
auto* info = ksys::act::InfoData::instance();
|
|
if (info->hasTag(item->getName().cstr(), ksys::act::tags::CanStack) && item->getValue() > 1) {
|
|
item->mValue -= 1;
|
|
ksys::PlayReportMgr::instance()->reportDebug("PouchDelete", name);
|
|
updateInventoryInfo(items);
|
|
updateListHeads();
|
|
saveToGameData(items);
|
|
return;
|
|
}
|
|
|
|
// Otherwise, delete the PouchItem entirely.
|
|
deleteItem_(items, item, name);
|
|
}
|
|
|
|
void PauseMenuDataMgr::removeWeaponIfEquipped(const sead::SafeString& name) {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
if (!ksys::act::PlayerInfo::instance()->getPlayer())
|
|
return;
|
|
|
|
const auto& items = getItems();
|
|
|
|
PouchItem* item = nullptr;
|
|
for (auto& it : items) {
|
|
if (name == it.getName() && it.isEquipped()) {
|
|
item = ⁢
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (item)
|
|
deleteItem_(items, item, name);
|
|
}
|
|
|
|
void PauseMenuDataMgr::removeArrow(const sead::SafeString& arrow_name, int count) {
|
|
if (!ksys::act::InfoData::instance()->hasTag(arrow_name.cstr(), ksys::act::tags::Arrow))
|
|
return;
|
|
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
s32 idx = 0;
|
|
for (auto& item : getItems()) {
|
|
if (arrow_name == item.getName()) {
|
|
if (count < item.getCount()) {
|
|
item.mValue -= count;
|
|
break;
|
|
}
|
|
|
|
count -= item.getCount();
|
|
item.mValue = 0;
|
|
}
|
|
++idx;
|
|
}
|
|
|
|
const auto num = getItemCount(arrow_name);
|
|
if (!mIsPouchForQuest && idx >= 0)
|
|
ksys::gdt::setFlag_PorchItem_Value1(num, idx);
|
|
}
|
|
|
|
// NON_MATCHING: branch merging -- but this is pretty clearly equivalent
|
|
int PauseMenuDataMgr::getItemCount(const sead::SafeString& name, bool count_equipped) const {
|
|
const auto type = getType(name);
|
|
if (isPouchItemInvalid(type))
|
|
return 0;
|
|
|
|
const auto& items = getItems();
|
|
sead::SafeString group_name;
|
|
getSameGroupActorName(&group_name, name);
|
|
|
|
PouchItem* first = nullptr;
|
|
switch (type) {
|
|
case PouchItemType::Sword:
|
|
first = getItemHead(PouchCategory::Sword);
|
|
break;
|
|
case PouchItemType::Bow:
|
|
first = getItemHead(PouchCategory::Bow);
|
|
break;
|
|
case PouchItemType::Arrow:
|
|
for (auto* item = getItemHead(PouchCategory::Bow); item; item = items.next(item)) {
|
|
if (item->getType() > PouchItemType::Arrow)
|
|
break;
|
|
if (item->getType() == PouchItemType::Arrow) {
|
|
first = item;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case PouchItemType::Shield:
|
|
first = getItemHead(PouchCategory::Shield);
|
|
break;
|
|
case PouchItemType::Food:
|
|
first = getItemHead(PouchCategory::Food);
|
|
break;
|
|
case PouchItemType::KeyItem:
|
|
first = getItemHead(PouchCategory::KeyItem);
|
|
break;
|
|
default:
|
|
if (type > PouchItemType::Material)
|
|
first = getItemHead(PouchCategory::Material);
|
|
else
|
|
first = getItemHead(PouchCategory::Armor);
|
|
break;
|
|
}
|
|
|
|
if (ksys::act::InfoData::instance()->hasTag(group_name.cstr(), ksys::act::tags::CanStack)) {
|
|
for (auto* item = first; item; item = items.next(item)) {
|
|
if (group_name == item->getName())
|
|
return item->getValue();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
s32 count = 0;
|
|
if (count_equipped) {
|
|
for (auto* item = first; item; item = items.next(item)) {
|
|
if (group_name == item->getName())
|
|
count += item->isInInventory();
|
|
}
|
|
} else {
|
|
for (auto* item = first; item; item = items.next(item)) {
|
|
if (group_name == item->getName() && item->isInInventory())
|
|
count += !item->isEquipped();
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void PauseMenuDataMgr::setEquippedWeaponItemValue(s32 value, PouchItemType type) {
|
|
if (isPouchItemNotWeapon(type))
|
|
return;
|
|
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
s32 idx = 0;
|
|
for (auto& item : getItems()) {
|
|
if (!item.isEquipped() || item.getType() != type) {
|
|
++idx;
|
|
continue;
|
|
}
|
|
|
|
item.mValue = value;
|
|
if (idx >= 0 && !mIsPouchForQuest)
|
|
ksys::gdt::setFlag_PorchItem_Value1(value, idx);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
const sead::SafeString& PauseMenuDataMgr::getDefaultEquipment(EquipmentSlot idx) const {
|
|
if (idx != EquipmentSlot::WeaponArrow &&
|
|
u32(idx) < u32(sValues.default_equipment_names.size())) {
|
|
return sValues.default_equipment_names(u32(idx));
|
|
}
|
|
return sead::SafeString::cEmptyString;
|
|
}
|
|
|
|
bool PauseMenuDataMgr::hasItem(const sead::SafeString& name) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
bool ret = false;
|
|
for (auto& item : getItems()) {
|
|
if (name != item.getName())
|
|
continue;
|
|
|
|
if (ksys::act::InfoData::instance()->hasTag(name.cstr(), ksys::act::tags::CanStack))
|
|
ret = item.getValue() > 0;
|
|
else
|
|
ret = true;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
PouchItem* PauseMenuDataMgr::getMasterSword() const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
for (auto* item = getItemHead(PouchCategory::Sword); item; item = nextItem(item)) {
|
|
if (item->getType() != PouchItemType::Sword)
|
|
return nullptr;
|
|
if (item->mInInventory && item->getName() == "Weapon_Sword_070")
|
|
return item;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void PauseMenuDataMgr::removeGrabbedItems() {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
for (s32 i = 0, n = mGrabbedItems.size(); i < n; ++i) {
|
|
auto& entry = mGrabbedItems[i];
|
|
auto* item = entry.item;
|
|
if (item && item->getValue() == 0 && !entry._9)
|
|
destroyAndRecycleItem(mItemLists, item);
|
|
entry = {};
|
|
}
|
|
|
|
const auto& items = getItems();
|
|
mLastAddedItem = nullptr;
|
|
updateInventoryInfo(items);
|
|
updateListHeads();
|
|
saveToGameData(items);
|
|
}
|
|
|
|
bool PauseMenuDataMgr::removeGrabbedItem(ksys::act::BaseProcLink* link) {
|
|
bool found = false;
|
|
if (link && link->hasProc()) {
|
|
ksys::act::ActorConstDataAccess accessor;
|
|
ksys::act::acquireActor(link, &accessor);
|
|
const auto name = accessor.getName();
|
|
auto& cs = mCritSection;
|
|
const auto& items = getItems();
|
|
|
|
for (s32 i = 0; i < NumGrabbableItems; ++i) {
|
|
auto& entry = mGrabbedItems[i];
|
|
if (found) {
|
|
mGrabbedItems[i - 1].item = entry.item;
|
|
mGrabbedItems[i - 1]._8 = entry._8;
|
|
mGrabbedItems[i - 1]._9 = entry._9;
|
|
continue;
|
|
}
|
|
|
|
if (!entry.item || name != entry.item->getName())
|
|
continue;
|
|
|
|
if (entry.item->getValue() == 0 && !entry._9) {
|
|
const auto lock = sead::makeScopedLock(cs);
|
|
auto* item = entry.item;
|
|
destroyAndRecycleItem(mItemLists, item);
|
|
updateInventoryInfo(items);
|
|
updateListHeads();
|
|
saveToGameData(items);
|
|
mLastAddedItem = nullptr;
|
|
}
|
|
|
|
found = true;
|
|
entry = {};
|
|
}
|
|
mGrabbedItems[4] = {};
|
|
}
|
|
return found;
|
|
}
|
|
|
|
bool PauseMenuDataMgr::getEquippedArrowType(sead::BufferedSafeString* name, int* count) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
for (const auto* item = getItemHead(PouchCategory::Bow); item; item = nextItem(item)) {
|
|
if (item->getType() > PouchItemType::Arrow)
|
|
break;
|
|
if (item->getType() == PouchItemType::Arrow && item->isInInventory() &&
|
|
item->isEquipped()) {
|
|
if (name)
|
|
name->copy(item->getName());
|
|
if (count)
|
|
*count = item->getValue();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int PauseMenuDataMgr::getArrowCount(const sead::SafeString& name) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
for (auto* item = getItemHead(PouchCategory::Bow); item; item = nextItem(item)) {
|
|
if (item->getType() > PouchItemType::Arrow)
|
|
break;
|
|
if (item->getType() == PouchItemType::Arrow && item->mInInventory &&
|
|
item->getName() == name)
|
|
return item->getCount();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int PauseMenuDataMgr::getRealArrowCount(const sead::SafeString& name) const {
|
|
if (!mIsPouchForQuest)
|
|
return getArrowCount(name);
|
|
|
|
s32 count;
|
|
s32 status = 2;
|
|
for (u32 i = 0; i < u32(NumPouchItemsMax); ++i) {
|
|
const char* item_name;
|
|
ksys::gdt::getFlag_PorchItem(&item_name, i);
|
|
if (sead::SafeString(item_name).isEmpty())
|
|
break;
|
|
if (sead::SafeString(item_name) == name) {
|
|
count = ksys::gdt::getFlag_PorchItem_Value1(i);
|
|
status = 1;
|
|
break;
|
|
}
|
|
}
|
|
return status == 2 ? 0 : count;
|
|
}
|
|
|
|
void PauseMenuDataMgr::breakMasterSword() {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
s32 idx = 0;
|
|
for (auto& item : getItems()) {
|
|
if (item.getType() == PouchItemType::Sword && isMasterSwordActorName(item.getName())) {
|
|
item.mValue = 0;
|
|
item.mEquipped = false;
|
|
if (!mIsPouchForQuest && idx >= 0) {
|
|
ksys::gdt::setFlag_PorchItem_Value1(0, idx);
|
|
ksys::gdt::setFlag_PorchItem_EquipFlag(false, idx);
|
|
}
|
|
break;
|
|
}
|
|
++idx;
|
|
}
|
|
}
|
|
|
|
void PauseMenuDataMgr::restoreMasterSword(bool only_if_broken) {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
s32 idx = 0;
|
|
for (auto& item : getItems()) {
|
|
if (item.getType() == PouchItemType::Sword && isMasterSwordActorName(item.getName())) {
|
|
if (only_if_broken && item.getValue() > 0)
|
|
break;
|
|
|
|
item.mValue = getWeaponInventoryLife(item.getName());
|
|
if (!mIsPouchForQuest && idx >= 0)
|
|
ksys::gdt::setFlag_PorchItem_Value1(item.mValue, idx);
|
|
|
|
break;
|
|
}
|
|
++idx;
|
|
}
|
|
}
|
|
|
|
static s32 checkItemRemoval(const sead::OffsetList<PouchItem>& items, const sead::SafeString& name,
|
|
int num_to_remove, bool include_equipped_items, bool stackable) {
|
|
s32 total = 0;
|
|
for (const auto& item : items) {
|
|
if (name == item.getName() && (stackable || include_equipped_items || !item.isEquipped())) {
|
|
total += stackable ? item.getValue() : 1;
|
|
if (total >= num_to_remove)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PauseMenuDataMgr::checkAddOrRemoveItem(const sead::SafeString& name, int count,
|
|
bool include_equipped_items) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
static_cast<void>(getType(name));
|
|
if (count < 0) {
|
|
const auto* info = ksys::act::InfoData::instance();
|
|
const int num_to_remove = -count;
|
|
const auto& items = getItems();
|
|
if (!info->hasTag(name.cstr(), ksys::act::tags::CanStack))
|
|
return checkItemRemoval(items, name, num_to_remove, include_equipped_items, false);
|
|
return checkItemRemoval(items, name, num_to_remove, true, true);
|
|
}
|
|
|
|
return !cannotGetItem(name, count);
|
|
}
|
|
|
|
int PauseMenuDataMgr::getFreeSlotCount() const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
const s32 num_items = getItems().size();
|
|
const s32 num_weapons = countItems(PouchItemType::Sword, true);
|
|
const s32 num_food = countItems(PouchItemType::Food);
|
|
|
|
return NumArmorsMax + NumMaterialsMax + NumKeyItemsMax - num_items + num_weapons + num_food;
|
|
}
|
|
|
|
int PauseMenuDataMgr::calculateEnemyMaterialMamo() const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
int value = 0;
|
|
for (const auto& item : getItems()) {
|
|
al::ByamlIter iter;
|
|
if (ksys::act::InfoData::instance()->getActorIter(&iter, item.getName().cstr()) &&
|
|
ksys::act::InfoData::instance()->hasTag(iter, ksys::act::tags::EnemyMaterial)) {
|
|
value += ksys::act::getMonsterShopSellMamo(iter) * item.getValue();
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
void PauseMenuDataMgr::removeAllEnemyMaterials() {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
const auto& items = getItems();
|
|
|
|
// Materials cannot be removed from the linked list immediately as we need to traverse it
|
|
// at the same time. Instead, only remove a material *after* we have moved to the next item.
|
|
PouchItem* material_to_remove = nullptr;
|
|
for (auto& item : items) {
|
|
if (material_to_remove != nullptr) {
|
|
getItems().erase(material_to_remove);
|
|
destroyAndRecycleItem(material_to_remove);
|
|
}
|
|
|
|
auto* info = ksys::act::InfoData::instance();
|
|
material_to_remove =
|
|
info->hasTag(item.getName().cstr(), ksys::act::tags::EnemyMaterial) ? &item : nullptr;
|
|
}
|
|
|
|
if (material_to_remove)
|
|
destroyAndRecycleItem(mItemLists, material_to_remove);
|
|
|
|
saveToGameData(items);
|
|
updateInventoryInfo(items);
|
|
updateListHeads();
|
|
}
|
|
|
|
int PauseMenuDataMgr::countItemsWithProfile(const sead::SafeString& profile,
|
|
bool count_stacked_items) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
s32 count = 0;
|
|
for (const auto& item : getItems()) {
|
|
const char* item_profile;
|
|
al::ByamlIter iter;
|
|
if (ksys::act::InfoData::instance()->getActorIter(&iter, item.getName().cstr()) &&
|
|
ksys::act::InfoData::instance()->getActorProfile(&item_profile, iter) &&
|
|
profile == item_profile) {
|
|
if (count_stacked_items &&
|
|
ksys::act::InfoData::instance()->hasTag(iter, ksys::act::tags::CanStack)) {
|
|
count += item.getValue();
|
|
} else {
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int PauseMenuDataMgr::countItemsWithTag(u32 tag, bool count_stacked_items) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
s32 count = 0;
|
|
for (const auto& item : getItems()) {
|
|
al::ByamlIter iter;
|
|
if (ksys::act::InfoData::instance()->getActorIter(&iter, item.getName().cstr()) &&
|
|
ksys::act::InfoData::instance()->hasTag(iter, tag)) {
|
|
if (count_stacked_items &&
|
|
ksys::act::InfoData::instance()->hasTag(iter, ksys::act::tags::CanStack)) {
|
|
count += item.getValue();
|
|
} else {
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int PauseMenuDataMgr::countCookResults(const sead::SafeString& name, s32 effect_type,
|
|
bool check_effect_type) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
auto* info = ksys::act::InfoData::instance();
|
|
if (!info)
|
|
return 0;
|
|
|
|
const bool check_name = !name.isEmpty();
|
|
if (check_name && !info->hasTag(name.cstr(), ksys::act::tags::CookResult))
|
|
return 0;
|
|
|
|
s32 count = 0;
|
|
for (auto* item = getItemHead(PouchCategory::Food); item; item = nextItem(item)) {
|
|
if (item->getType() != PouchItemType::Food)
|
|
break;
|
|
if (!item->isInInventory())
|
|
continue;
|
|
if (!info->hasTag(item->getName().cstr(), ksys::act::tags::CookResult))
|
|
continue;
|
|
if (check_effect_type && item->getCookData().getEffectId() != effect_type)
|
|
continue;
|
|
if (check_name && item->getName() != name)
|
|
continue;
|
|
++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int PauseMenuDataMgr::countItemsWithCategory(PouchCategory category) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
if (u32(category) > 6)
|
|
return 0;
|
|
s32 count = 0;
|
|
for (auto* item = getItemHead(category); item; item = nextItem(item)) {
|
|
const auto type = item->getType();
|
|
if (getCategoryForType(type) != category)
|
|
break;
|
|
count += item->isInInventory();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
PouchCategory PauseMenuDataMgr::getCategoryForType(PouchItemType type) const {
|
|
if (type == PouchItemType::Sword)
|
|
return PouchCategory::Sword;
|
|
|
|
if (isPouchItemBowOrArrow(type))
|
|
return PouchCategory::Bow;
|
|
|
|
switch (type) {
|
|
case PouchItemType::Shield:
|
|
return PouchCategory::Shield;
|
|
case PouchItemType::ArmorHead:
|
|
case PouchItemType::ArmorUpper:
|
|
case PouchItemType::ArmorLower:
|
|
return PouchCategory::Armor;
|
|
case PouchItemType::Material:
|
|
return PouchCategory::Material;
|
|
case PouchItemType::Food:
|
|
return PouchCategory::Food;
|
|
case PouchItemType::KeyItem:
|
|
return PouchCategory::KeyItem;
|
|
default:
|
|
return PouchCategory::Invalid;
|
|
}
|
|
}
|
|
|
|
// NON_MATCHING: two harmless reorderings
|
|
void PauseMenuDataMgr::removeCookResult(const sead::SafeString& name, s32 effect_type,
|
|
bool check_effect) {
|
|
auto* info = ksys::act::InfoData::instance();
|
|
if (!info)
|
|
return;
|
|
|
|
const bool check_name = !name.isEmpty();
|
|
if (check_name && !info->hasTag(name.cstr(), ksys::act::tags::CookResult))
|
|
return;
|
|
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
const auto& items = getItems();
|
|
|
|
auto min_hp = std::numeric_limits<s32>::max();
|
|
auto min_stam = std::numeric_limits<f32>::infinity();
|
|
auto min_level = std::numeric_limits<f32>::infinity();
|
|
PouchItem* to_remove = nullptr;
|
|
|
|
for (auto* item = getItemHead(PouchCategory::Food); item; item = items.next(item)) {
|
|
if (item->getType() != PouchItemType::Food)
|
|
break;
|
|
if (!item->isInInventory())
|
|
continue;
|
|
if (!info->hasTag(item->getName().cstr(), ksys::act::tags::CookResult))
|
|
continue;
|
|
if (item->getCookData().getEffectId() != effect_type && check_effect)
|
|
continue;
|
|
if (check_name && item->getName() != name)
|
|
continue;
|
|
|
|
const auto stam = f32(item->getCookData().mEffectDuration) * 30.0f;
|
|
if (stam < min_stam) {
|
|
min_hp = item->getCookData().mHealthRecover;
|
|
min_stam = stam;
|
|
to_remove = item;
|
|
min_level = item->getCookData().getEffectLevel();
|
|
} else if (stam == min_stam) {
|
|
const auto hp = item->getCookData().mHealthRecover;
|
|
if (hp < min_hp) {
|
|
min_hp = hp;
|
|
to_remove = item;
|
|
min_level = item->getCookData().getEffectLevel();
|
|
} else if (check_effect && hp == min_hp &&
|
|
item->getCookData().getEffectLevel() < min_level) {
|
|
min_level = item->getCookData().getEffectLevel();
|
|
to_remove = item;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!to_remove)
|
|
return;
|
|
|
|
destroyAndRecycleItem(mItemLists, to_remove);
|
|
ksys::PlayReportMgr::instance()->reportDebug("PouchDeleteFromFlow", name);
|
|
saveToGameData(items);
|
|
updateInventoryInfo(items);
|
|
updateListHeads();
|
|
}
|
|
|
|
bool PauseMenuDataMgr::switchEquipment(const sead::SafeString& name, int* value,
|
|
act::WeaponModifierInfo* modifier) {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
const auto& items = getItems();
|
|
|
|
sead::SafeString group_name;
|
|
getSameGroupActorName(&group_name, name);
|
|
|
|
PouchItem* target = nullptr;
|
|
|
|
const auto type = getType(group_name);
|
|
if (type <= PouchItemType::Shield) {
|
|
for (auto& item : items) {
|
|
if (item.getType() > PouchItemType::Shield)
|
|
break;
|
|
if (item.getName() == group_name && (!target || item.getValue() < target->getValue()))
|
|
target = &item;
|
|
}
|
|
} else {
|
|
if (type >= PouchItemType::Material)
|
|
return false;
|
|
for (auto& item : items) {
|
|
if (item.getType() >= PouchItemType::Material)
|
|
break;
|
|
if (item.getName() == group_name) {
|
|
target = &item;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!target)
|
|
return false;
|
|
|
|
if (value)
|
|
*value = target->getValue();
|
|
|
|
if (type == PouchItemType::Arrow) {
|
|
if (target->getValue() > 0) {
|
|
autoEquip(target, items);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
autoEquip(target, items);
|
|
|
|
const bool is_weapon = type <= PouchItemType::Shield;
|
|
if (modifier && is_weapon)
|
|
modifier->fromItem(*target);
|
|
|
|
return true;
|
|
}
|
|
|
|
void PauseMenuDataMgr::initPouchForQuest() {
|
|
mIsPouchForQuest = true;
|
|
resetItemAndPointers();
|
|
|
|
{
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
const auto& items = getItems();
|
|
PouchItem* to_remove = nullptr;
|
|
|
|
for (auto& item : items) {
|
|
if (to_remove != nullptr) {
|
|
getItems().erase(to_remove);
|
|
destroyAndRecycleItem(to_remove);
|
|
}
|
|
|
|
to_remove = item.getType() == PouchItemType::KeyItem ? nullptr : &item;
|
|
}
|
|
|
|
if (to_remove) {
|
|
getItems().erase(to_remove);
|
|
destroyAndRecycleItem(to_remove);
|
|
}
|
|
|
|
updateInventoryInfo(items);
|
|
updateListHeads();
|
|
}
|
|
|
|
ksys::gdt::setFlag_FairyCountCheck(false);
|
|
createPlayerEquipment();
|
|
}
|
|
|
|
void PauseMenuDataMgr::restorePouchForQuest() {
|
|
mIsPouchForQuest = false;
|
|
doLoadFromGameData();
|
|
for (auto& entry : mGrabbedItems)
|
|
entry = {};
|
|
resetItemAndPointers();
|
|
createPlayerEquipment();
|
|
}
|
|
|
|
ItemUse getItemUse(const sead::SafeString& name) {
|
|
const char* profile_c;
|
|
if (!ksys::act::InfoData::instance()->getActorProfile(&profile_c, name.cstr()))
|
|
return ItemUse::Invalid;
|
|
|
|
const sead::SafeString profile = profile_c;
|
|
|
|
if (profile == sValues.WeaponSmallSword)
|
|
return ItemUse::WeaponSmallSword;
|
|
|
|
if (profile == sValues.WeaponLargeSword)
|
|
return ItemUse::WeaponLargeSword;
|
|
|
|
if (profile == sValues.WeaponSpear)
|
|
return ItemUse::WeaponSpear;
|
|
|
|
if (profile == sValues.WeaponBow)
|
|
return ItemUse::WeaponBow;
|
|
|
|
if (profile == sValues.WeaponShield)
|
|
return ItemUse::WeaponShield;
|
|
|
|
if (profile == sValues.ArmorHead)
|
|
return ItemUse::ArmorHead;
|
|
|
|
if (profile == sValues.ArmorUpper)
|
|
return ItemUse::ArmorUpper;
|
|
|
|
if (profile == sValues.ArmorLower)
|
|
return ItemUse::ArmorLower;
|
|
|
|
if (profile != sValues.Item && profile != sValues.PlayerItem)
|
|
return ItemUse::Item;
|
|
|
|
al::ByamlIter iter;
|
|
if (!ksys::act::InfoData::instance()->getActorIter(&iter, name.cstr()))
|
|
return ItemUse::Item;
|
|
|
|
if (ksys::act::InfoData::instance()->hasTag(iter, ksys::act::tags::CureItem))
|
|
return ItemUse::CureItem;
|
|
|
|
if (ksys::act::InfoData::instance()->hasTag(iter, ksys::act::tags::Important))
|
|
return ItemUse::ImportantItem;
|
|
|
|
return ItemUse::Item;
|
|
}
|
|
|
|
void PauseMenuDataMgr::sortItems(PouchCategory category, bool do_not_save) {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
auto& items = getItems();
|
|
|
|
if (items.isEmpty())
|
|
return;
|
|
|
|
mCategoryToSort = category;
|
|
items.mergeSort(pouchItemSortPredicate);
|
|
|
|
switch (category) {
|
|
case PouchCategory::Sword:
|
|
ksys::gdt::setFlag_SortTypeWeaponPouch(!ksys::gdt::getFlag_SortTypeWeaponPouch());
|
|
break;
|
|
case PouchCategory::Bow:
|
|
ksys::gdt::setFlag_SortTypeBowPouch(!ksys::gdt::getFlag_SortTypeBowPouch());
|
|
break;
|
|
case PouchCategory::Shield:
|
|
ksys::gdt::setFlag_SortTypeShieldPouch(!ksys::gdt::getFlag_SortTypeShieldPouch());
|
|
break;
|
|
case PouchCategory::Armor:
|
|
ksys::gdt::setFlag_SortTypeArmorPouch(!ksys::gdt::getFlag_SortTypeArmorPouch());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (do_not_save)
|
|
return;
|
|
|
|
updateInventoryInfo(items);
|
|
updateListHeads();
|
|
saveToGameData(items);
|
|
}
|
|
|
|
using SortPredicate = int (*)(const PouchItem* lhs, const PouchItem* rhs,
|
|
ksys::act::InfoData* data);
|
|
|
|
static PouchCategory getCategoryForTypeWithLookupTable(PouchItemType type) {
|
|
static constexpr sead::SafeArray<PouchCategory, NumPouchItemTypes> sMap{{
|
|
PouchCategory::Sword, // Weapon
|
|
PouchCategory::Bow, // Bow
|
|
PouchCategory::Bow, // Arrow
|
|
PouchCategory::Shield, // Shield
|
|
PouchCategory::Armor, // ArmorHead
|
|
PouchCategory::Armor, // ArmorUpper
|
|
PouchCategory::Armor, // ArmorLower
|
|
PouchCategory::Material, // Material
|
|
PouchCategory::Food, // Food
|
|
PouchCategory::KeyItem, // KeyItem
|
|
}};
|
|
return sMap[s32(type)];
|
|
}
|
|
|
|
static auto getSortPredicateTable() {
|
|
sead::SafeArray<SortPredicate, 7> table{{compareWeapon, compareBow, compareShield, compareArmor,
|
|
compareMaterial, compareFood, compareKeyItem}};
|
|
return table;
|
|
}
|
|
|
|
int pouchItemSortPredicate(const PouchItem* lhs, const PouchItem* rhs) {
|
|
if (!lhs || !rhs)
|
|
return 0;
|
|
|
|
auto* info_data = ksys::act::InfoData::instance();
|
|
if (!info_data || !lhs->isInInventory() || !rhs->isInInventory())
|
|
return 0;
|
|
|
|
const auto cat1 = getCategoryForTypeWithLookupTable(lhs->getType());
|
|
const auto cat2 = getCategoryForTypeWithLookupTable(rhs->getType());
|
|
if (cat1 != cat2)
|
|
return 0;
|
|
|
|
const auto cat3 = PauseMenuDataMgr::instance()->getCategoryToSort();
|
|
if (cat3 != PouchCategory::Invalid && cat1 != cat3)
|
|
return 0;
|
|
|
|
auto predicate_table = getSortPredicateTable();
|
|
const auto* fn = &predicate_table[0];
|
|
if (u32(cat1) < u32(predicate_table.size()))
|
|
fn = &predicate_table(u32(cat1));
|
|
return (*fn)(lhs, rhs, info_data);
|
|
}
|
|
|
|
static s32 compareSortKeys(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
|
|
const auto a = data->getSortKey(lhs->getName().cstr());
|
|
const auto b = data->getSortKey(rhs->getName().cstr());
|
|
if (a < b)
|
|
return -1;
|
|
if (a > b)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static s32 compareItemValues(const PouchItem* lhs, const PouchItem* rhs) {
|
|
const int val1 = lhs->getValue();
|
|
const int val2 = rhs->getValue();
|
|
// Higher is better
|
|
if (val1 > val2)
|
|
return -1;
|
|
if (val1 < val2)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int doCompareWeapon(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
|
|
const auto use1 = lhs->getItemUse();
|
|
const auto use2 = rhs->getItemUse();
|
|
if (use1 < use2)
|
|
return -1;
|
|
if (use1 > use2)
|
|
return 1;
|
|
|
|
const auto power1 = getWeaponPowerSortValue(lhs, data);
|
|
const auto power2 = getWeaponPowerSortValue(rhs, data);
|
|
if (power1 > power2)
|
|
return -1;
|
|
if (power1 < power2)
|
|
return 1;
|
|
|
|
const auto mod1 = getWeaponModifierSortKey(lhs->getWeaponModifier());
|
|
const auto mod2 = getWeaponModifierSortKey(rhs->getWeaponModifier());
|
|
if (mod1 < mod2)
|
|
return -1;
|
|
if (mod1 > mod2)
|
|
return 1;
|
|
|
|
return compareItemValues(lhs, rhs);
|
|
}
|
|
|
|
static int compareWeaponType0(const PouchItem* lhs, const PouchItem* rhs,
|
|
ksys::act::InfoData* data) {
|
|
if (auto cmp = doCompareWeapon(lhs, rhs, data))
|
|
return cmp;
|
|
return compareSortKeys(lhs, rhs, data);
|
|
}
|
|
|
|
static int compareWeaponType1(const PouchItem* lhs, const PouchItem* rhs,
|
|
ksys::act::InfoData* data) {
|
|
if (auto cmp = compareSortKeys(lhs, rhs, data))
|
|
return cmp;
|
|
return doCompareWeapon(lhs, rhs, data);
|
|
}
|
|
|
|
static int getShieldGuardPower(const PouchItem* item, ksys::act::InfoData* data) {
|
|
int power = ksys::act::getWeaponCommonGuardPower(data, item->getName().cstr());
|
|
if (item->getWeaponModifier().isOn(act::WeaponModifier::AddGuard))
|
|
power += item->getWeaponModifierValue();
|
|
return power;
|
|
}
|
|
|
|
static int doCompareShield(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
|
|
const int gp1 = getShieldGuardPower(lhs, data);
|
|
const int gp2 = getShieldGuardPower(rhs, data);
|
|
// Higher is better
|
|
if (gp1 > gp2)
|
|
return -1;
|
|
if (gp1 < gp2)
|
|
return 1;
|
|
|
|
const int mod1 = getWeaponModifierSortKey(lhs->getWeaponModifier());
|
|
const int mod2 = getWeaponModifierSortKey(rhs->getWeaponModifier());
|
|
// Lower is better
|
|
if (mod1 < mod2)
|
|
return -1;
|
|
if (mod1 > mod2)
|
|
return 1;
|
|
|
|
return compareItemValues(lhs, rhs);
|
|
}
|
|
|
|
int compareWeapon(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
|
|
if (ksys::gdt::getFlag_SortTypeWeaponPouch())
|
|
return compareWeaponType1(lhs, rhs, data);
|
|
return compareWeaponType0(lhs, rhs, data);
|
|
}
|
|
|
|
int compareBow(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
|
|
if (lhs->getType() == PouchItemType::Arrow)
|
|
return compareSortKeys(lhs, rhs, data);
|
|
|
|
if (ksys::gdt::getFlag_SortTypeBowPouch())
|
|
return compareWeaponType1(lhs, rhs, data);
|
|
return compareWeaponType0(lhs, rhs, data);
|
|
}
|
|
|
|
int compareShield(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
|
|
if (ksys::gdt::getFlag_SortTypeShieldPouch()) {
|
|
if (auto cmp = compareSortKeys(lhs, rhs, data))
|
|
return cmp;
|
|
|
|
return doCompareShield(lhs, rhs, data);
|
|
|
|
} else {
|
|
if (auto cmp = doCompareShield(lhs, rhs, data))
|
|
return cmp;
|
|
|
|
return compareSortKeys(lhs, rhs, data);
|
|
}
|
|
}
|
|
|
|
int compareArmor(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
|
|
if (ksys::gdt::getFlag_SortTypeArmorPouch()) {
|
|
if (auto cmp = compareSortKeys(lhs, rhs, data))
|
|
return cmp;
|
|
|
|
if (lhs->getType() < rhs->getType())
|
|
return -1;
|
|
if (lhs->getType() > rhs->getType())
|
|
return 1;
|
|
} else {
|
|
if (lhs->getType() < rhs->getType())
|
|
return -1;
|
|
if (lhs->getType() > rhs->getType())
|
|
return 1;
|
|
|
|
if (auto cmp = compareSortKeys(lhs, rhs, data))
|
|
return cmp;
|
|
}
|
|
|
|
if (lhs->getCount() < rhs->getCount())
|
|
return -1;
|
|
if (lhs->getCount() > rhs->getCount())
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int compareMaterial(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
|
|
const int order1 = getCookItemOrder(lhs, data);
|
|
const int order2 = getCookItemOrder(rhs, data);
|
|
// Lower is better
|
|
if (order1 < order2)
|
|
return -1;
|
|
if (order1 > order2)
|
|
return 1;
|
|
|
|
const int hp1 = getItemHitPointRecover(lhs->getName().cstr());
|
|
const int hp2 = getItemHitPointRecover(rhs->getName().cstr());
|
|
// Higher is better
|
|
if (hp1 > hp2)
|
|
return -1;
|
|
if (hp1 < hp2)
|
|
return 1;
|
|
|
|
return compareSortKeys(lhs, rhs, data);
|
|
}
|
|
|
|
int compareFood(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
|
|
int e1, e2;
|
|
const int k1 = getFoodSortKey(&e1, lhs);
|
|
const int k2 = getFoodSortKey(&e2, rhs);
|
|
// Lower key is better
|
|
if (k1 < k2)
|
|
return -1;
|
|
if (k1 > k2)
|
|
return 1;
|
|
|
|
if (auto cmp = compareSortKeys(lhs, rhs, data))
|
|
return cmp;
|
|
|
|
// Higher is better
|
|
if (e1 > e2)
|
|
return -1;
|
|
if (e1 < e2)
|
|
return 1;
|
|
|
|
const int st1 = lhs->getCookData().mHealthRecover;
|
|
const int st2 = rhs->getCookData().mHealthRecover;
|
|
// Higher is better
|
|
if (st1 > st2)
|
|
return -1;
|
|
if (st1 < st2)
|
|
return 1;
|
|
|
|
const auto sv1 = lhs->getCookData().getEffectDurationFrames();
|
|
const auto sv2 = rhs->getCookData().getEffectDurationFrames();
|
|
// Higher is better
|
|
if (sv1 > sv2)
|
|
return -1;
|
|
if (sv1 < sv2)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int compareKeyItem(const PouchItem* lhs, const PouchItem* rhs, ksys::act::InfoData* data) {
|
|
if (auto cmp = compareSortKeys(lhs, rhs, data))
|
|
return cmp;
|
|
return 0;
|
|
}
|
|
|
|
int getCookItemOrder(const PouchItem* item, ksys::act::InfoData* data) {
|
|
const auto& order = sValues.cook_item_order;
|
|
al::ByamlIter iter;
|
|
if (data->getActorIter(&iter, item->getName().cstr())) {
|
|
for (auto it = order.begin(), end = order.end(); it != end; ++it) {
|
|
if (it->is_tag == 0 && item->getName() == it->name.cstr())
|
|
return it.getIndex();
|
|
}
|
|
|
|
for (auto it = order.begin(), end = order.end(); it != end; ++it) {
|
|
if (it->is_tag == 1 && data->hasTag(iter, it->hash))
|
|
return it.getIndex();
|
|
}
|
|
}
|
|
return order.size();
|
|
}
|
|
|
|
int pouchItemSortPredicateForArrow(const PouchItem* lhs, const PouchItem* rhs) {
|
|
if (!lhs || !rhs)
|
|
return 0;
|
|
|
|
static constexpr sead::SafeArray<int, NumPouchItemTypes> sMap{{0, 1, 2, 3, 4, 4, 4, 7, 8, 9}};
|
|
const auto x1 = sMap[s32(lhs->getType())];
|
|
const auto x2 = sMap[s32(rhs->getType())];
|
|
if (x1 < x2)
|
|
return -1;
|
|
if (x1 > x2)
|
|
return 1;
|
|
|
|
if (lhs->getType() != PouchItemType::Arrow)
|
|
return 0;
|
|
|
|
auto* info_data = ksys::act::InfoData::instance();
|
|
if (!info_data || !lhs->isInInventory() || !rhs->isInInventory())
|
|
return 0;
|
|
|
|
const auto cat1 = getCategoryForTypeWithLookupTable(lhs->getType());
|
|
const auto cat2 = getCategoryForTypeWithLookupTable(rhs->getType());
|
|
if (cat1 != cat2)
|
|
return 0;
|
|
|
|
const auto cat3 = PauseMenuDataMgr::instance()->getCategoryToSort();
|
|
if (cat3 != PouchCategory::Invalid && cat1 != cat3)
|
|
return 0;
|
|
|
|
const auto predicate_table = getSortPredicateTable();
|
|
const auto* fn = &predicate_table[0];
|
|
if (u32(cat1) < u32(predicate_table.size()))
|
|
fn = &predicate_table(u32(cat1));
|
|
return (*fn)(lhs, rhs, info_data);
|
|
}
|
|
|
|
// NON_MATCHING: branching, but this is so trivial it isn't worth spending time on matching this
|
|
const sead::SafeString* PauseMenuDataMgr::getEquippedItemName(PouchItemType type) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
const auto& items = getItems();
|
|
|
|
if (!isPouchItemEquipment(type) || items.isEmpty())
|
|
return nullptr;
|
|
|
|
auto* first = type <= PouchItemType::Shield ? items.nth(0) : getItemHead(PouchCategory::Armor);
|
|
for (auto* item = first; item; item = items.next(item)) {
|
|
if (item->isEquipped() && item->getType() == type)
|
|
return &item->getName();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const PouchItem* PauseMenuDataMgr::getEquippedItem(PouchItemType type) const {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
const auto& items = getItems();
|
|
|
|
if (!isPouchItemEquipment(type) || items.isEmpty())
|
|
return nullptr;
|
|
|
|
auto* first = type <= PouchItemType::Shield ? items.nth(0) : getItemHead(PouchCategory::Armor);
|
|
for (auto* item = first; item; item = items.next(item)) {
|
|
if (item->isEquipped() && item->getType() == type)
|
|
return item;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int PauseMenuDataMgr::getItemValue(const sead::SafeString& name) const {
|
|
const auto type = getType(name);
|
|
if (isPouchItemInvalid(type))
|
|
return 0;
|
|
|
|
sead::SafeString group_name;
|
|
getSameGroupActorName(&group_name, name);
|
|
|
|
if (ksys::act::InfoData::instance()->hasTag(group_name.cstr(), ksys::act::tags::CanStack)) {
|
|
for (const auto& item : mItemLists.buffer) {
|
|
if (item.isInInventory() && item.getType() == type &&
|
|
!group_name.comparen(item.getName(), 64))
|
|
return item.getValue();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
s32 count = 0;
|
|
for (const auto& item : mItemLists.buffer) {
|
|
if (item.isInInventory() && item.getType() == type &&
|
|
!group_name.comparen(item.getName(), 64))
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool PauseMenuDataMgr::getFromShop(const sead::SafeString& name, int value,
|
|
const act::WeaponModifierInfo* modifier) {
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
auto& lists = mItemLists;
|
|
const auto type = getType(name);
|
|
|
|
al::ByamlIter iter;
|
|
namespace act = ksys::act;
|
|
act::InfoData::instance()->getActorIter(&iter, name.cstr());
|
|
|
|
if (iter.isValid() && act::InfoData::instance()->hasTag(iter, act::tags::CanStack)) {
|
|
addToPouch(name, type, lists, value, false, modifier);
|
|
} else if (iter.isValid() && act::InfoData::instance()->hasTag(iter, act::tags::ArmorDye)) {
|
|
addToPouch(name, type, lists, 0, false, modifier);
|
|
} else if (type > PouchItemType::Shield || type == PouchItemType::Arrow) {
|
|
addToPouch(name, type, lists, 1, false, modifier);
|
|
} else {
|
|
int life = getItemGeneralLife(name.cstr());
|
|
if (modifier)
|
|
life += modifier->flags.isOn(uking::act::WeaponModifier::AddLife) ? modifier->value : 0;
|
|
addToPouch(name, type, lists, life, false, modifier);
|
|
}
|
|
|
|
ksys::PlayReportMgr::instance()->reportDebug("PouchGetFromShop", name);
|
|
saveToGameData(lists.list1);
|
|
return true;
|
|
}
|
|
|
|
int PauseMenuDataMgr::countArmorDye() const {
|
|
int count = 0;
|
|
using namespace ksys::act;
|
|
for (const auto& item : getItems()) {
|
|
if (item.isInInventory() &&
|
|
InfoData::instance()->hasTag(item.getName().cstr(), tags::ArmorDye))
|
|
++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int PauseMenuDataMgr::countAlreadyDyedArmor() const {
|
|
int count = 0;
|
|
using namespace ksys::act;
|
|
for (const auto& item : getItems()) {
|
|
if (item.isInInventory() &&
|
|
InfoData::instance()->hasTag(item.getName().cstr(), tags::ArmorDye) &&
|
|
item.getValue() > 0) {
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int PauseMenuDataMgr::getNextGrabbedItemIndex() const {
|
|
for (int i = 0; i < mGrabbedItems.size(); ++i) {
|
|
if (mGrabbedItems[i].item == nullptr)
|
|
return i;
|
|
}
|
|
return mGrabbedItems.size();
|
|
}
|
|
|
|
bool PauseMenuDataMgr::canGrabAnotherItem() const {
|
|
return std::any_of(mGrabbedItems.begin(), mGrabbedItems.end(),
|
|
[](const auto& entry) { return entry.item == nullptr; });
|
|
}
|
|
|
|
bool PauseMenuDataMgr::isNothingBeingGrabbed() const {
|
|
return std::all_of(mGrabbedItems.begin(), mGrabbedItems.end(),
|
|
[](const auto& entry) { return entry.item == nullptr; });
|
|
}
|
|
|
|
bool PauseMenuDataMgr::isHeroSoulEnabled(const sead::SafeString& name) const {
|
|
if (name == sValues.Obj_HeroSoul_Zora || name == sValues.Obj_DLC_HeroSoul_Zora) {
|
|
if (mZoraSoulItem)
|
|
return mZoraSoulItem->isEquipped();
|
|
}
|
|
|
|
if (name == sValues.Obj_HeroSoul_Rito || name == sValues.Obj_DLC_HeroSoul_Rito) {
|
|
if (mRitoSoulItem)
|
|
return mRitoSoulItem->isEquipped();
|
|
}
|
|
|
|
if (name == sValues.Obj_HeroSoul_Goron || name == sValues.Obj_DLC_HeroSoul_Goron) {
|
|
if (mGoronSoulItem)
|
|
return mGoronSoulItem->isEquipped();
|
|
}
|
|
|
|
if (name == sValues.Obj_HeroSoul_Gerudo || name == sValues.Obj_DLC_HeroSoul_Gerudo) {
|
|
if (mGerudoSoulItem)
|
|
return mGerudoSoulItem->isEquipped();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
bool PauseMenuDataMgr::hasRitoSoulPlus() const {
|
|
return ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Rito() && aoc::Manager::instance()->hasAoc3();
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
bool PauseMenuDataMgr::hasGoronSoulPlus() const {
|
|
return ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Goron() && aoc::Manager::instance()->hasAoc3();
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
bool PauseMenuDataMgr::hasGerudoSoulPlus() const {
|
|
return ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Gerudo() &&
|
|
aoc::Manager::instance()->hasAoc3();
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
bool PauseMenuDataMgr::hasZoraSoulPlus() const {
|
|
return ksys::gdt::getFlag_IsGet_Obj_DLC_HeroSoul_Zora() && aoc::Manager::instance()->hasAoc3();
|
|
}
|
|
|
|
int PauseMenuDataMgr::countItemsWithCategoryByType(PouchCategory category) const {
|
|
switch (category) {
|
|
case PouchCategory::Sword:
|
|
return countItems(PouchItemType::Sword);
|
|
case PouchCategory::Bow:
|
|
return countItems(PouchItemType::Bow) + countItems(PouchItemType::Arrow);
|
|
case PouchCategory::Shield:
|
|
return countItems(PouchItemType::Shield);
|
|
case PouchCategory::Armor:
|
|
return countItems(PouchItemType::ArmorLower);
|
|
case PouchCategory::Material:
|
|
return countItems(PouchItemType::Material);
|
|
case PouchCategory::Food:
|
|
return countItems(PouchItemType::Food);
|
|
case PouchCategory::KeyItem:
|
|
return countItems(PouchItemType::KeyItem);
|
|
case PouchCategory::Invalid:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const PouchItem* PauseMenuDataMgr::getItemByIndex(PouchItemType type, int index) const {
|
|
const auto& list = getItems();
|
|
PouchItem* item = nullptr;
|
|
switch (type) {
|
|
case PouchItemType::Sword:
|
|
item = getItemHead(PouchCategory::Sword);
|
|
break;
|
|
case PouchItemType::Bow:
|
|
item = getItemHead(PouchCategory::Bow);
|
|
break;
|
|
case PouchItemType::Arrow:
|
|
for (auto* item_ = getItemHead(PouchCategory::Bow);
|
|
item_ && item_->getType() <= PouchItemType::Arrow; item_ = list.next(item_)) {
|
|
if (item_->getType() == PouchItemType::Arrow) {
|
|
item = item_;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case PouchItemType::Shield:
|
|
item = getItemHead(PouchCategory::Shield);
|
|
break;
|
|
case PouchItemType::ArmorHead:
|
|
case PouchItemType::ArmorUpper:
|
|
case PouchItemType::ArmorLower:
|
|
item = getItemHead(PouchCategory::Armor);
|
|
break;
|
|
case PouchItemType::Material:
|
|
item = getItemHead(PouchCategory::Material);
|
|
break;
|
|
case PouchItemType::Food:
|
|
item = getItemHead(PouchCategory::Food);
|
|
break;
|
|
case PouchItemType::KeyItem:
|
|
item = getItemHead(PouchCategory::KeyItem);
|
|
break;
|
|
case PouchItemType::Invalid:
|
|
break;
|
|
}
|
|
|
|
if (!item)
|
|
return nullptr;
|
|
|
|
for (int i = 0; i < index; ++i) {
|
|
if (!item)
|
|
return nullptr;
|
|
item = list.next(item);
|
|
}
|
|
|
|
if (item) {
|
|
if (isPouchItemArmor(type))
|
|
return isPouchItemArmor(item->getType()) ? item : nullptr;
|
|
if (item->getType() == type)
|
|
return item;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const PouchItem* PauseMenuDataMgr::getItemByIndex(PouchCategory category, int index) const {
|
|
switch (category) {
|
|
case PouchCategory::Sword:
|
|
return getItemByIndex(PouchItemType::Sword, index);
|
|
case PouchCategory::Bow: {
|
|
const auto num_bows = ksys::gdt::getFlag_BowPorchStockNum();
|
|
if (index < num_bows)
|
|
return getItemByIndex(PouchItemType::Bow, index);
|
|
return getItemByIndex(PouchItemType::Arrow, index - num_bows);
|
|
}
|
|
case PouchCategory::Shield:
|
|
return getItemByIndex(PouchItemType::Shield, index);
|
|
case PouchCategory::Armor:
|
|
return getItemByIndex(PouchItemType::ArmorLower, index);
|
|
case PouchCategory::Material:
|
|
return getItemByIndex(PouchItemType::Material, index);
|
|
case PouchCategory::Food:
|
|
return getItemByIndex(PouchItemType::Food, index);
|
|
case PouchCategory::KeyItem:
|
|
return getItemByIndex(PouchItemType::KeyItem, index);
|
|
case PouchCategory::Invalid:
|
|
break;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool PauseMenuDataMgr::hasItemDye() const {
|
|
int counts[1 + NumDyeColors]{};
|
|
for (const auto& item : getItems()) {
|
|
auto* info = ksys::act::InfoData::instance();
|
|
const int color = ksys::act::getItemStainColor(info, item.getName().cstr());
|
|
if (color >= FirstDyeColorIndex && color <= LastDyeColorIndex && item.isInInventory()) {
|
|
counts[color] += item.getValue();
|
|
if (counts[color] >= NumRequiredDyeItemsPerColor)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PauseMenuDataMgr::hasItemDye(int color) const {
|
|
int count = 0;
|
|
for (const auto& item : getItems()) {
|
|
auto* info = ksys::act::InfoData::instance();
|
|
if (ksys::act::getItemStainColor(info, item.getName().cstr()) == color &&
|
|
item.isInInventory()) {
|
|
count += item.getValue();
|
|
if (count >= NumRequiredDyeItemsPerColor)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const PouchItem* PauseMenuDataMgr::getLastAddedItem() const {
|
|
if (!mNewlyAddedItem.getName().isEmpty())
|
|
return &mNewlyAddedItem;
|
|
if (mLastAddedItem)
|
|
return mLastAddedItem;
|
|
return &mNewlyAddedItem;
|
|
}
|
|
|
|
void PauseMenuDataMgr::updateEquippedItemArray() {
|
|
mEquippedWeapons.fill({});
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
for (auto& item : getItems()) {
|
|
if (item.getType() > PouchItemType::Shield)
|
|
break;
|
|
if (item.isEquipped())
|
|
mEquippedWeapons[u32(item.getType())] = &item;
|
|
}
|
|
}
|
|
|
|
void PauseMenuDataMgr::resetEquippedItemArray() {
|
|
mEquippedWeapons.fill({});
|
|
}
|
|
|
|
bool PauseMenuDataMgr::isOverCategoryLimit(PouchItemType type) const {
|
|
const auto count = countItems(type);
|
|
switch (type) {
|
|
case PouchItemType::Sword:
|
|
return ksys::gdt::getFlag_WeaponPorchStockNum() <= count || count >= NumSwordsMax;
|
|
case PouchItemType::Bow:
|
|
return ksys::gdt::getFlag_BowPorchStockNum() <= count || count >= NumBowsMax;
|
|
case PouchItemType::Arrow:
|
|
return count >= NumArrowItemsMax;
|
|
case PouchItemType::Shield:
|
|
return ksys::gdt::getFlag_ShieldPorchStockNum() <= count || count >= NumShieldsMax;
|
|
case PouchItemType::ArmorHead:
|
|
case PouchItemType::ArmorUpper:
|
|
case PouchItemType::ArmorLower:
|
|
return count >= NumArmorsMax;
|
|
case PouchItemType::Material:
|
|
return count >= NumMaterialsMax;
|
|
case PouchItemType::Food:
|
|
return count >= NumFoodMax;
|
|
case PouchItemType::KeyItem:
|
|
return count >= NumKeyItemsMax;
|
|
case PouchItemType::Invalid:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// NON_MATCHING: branching (really weird issue...)
|
|
int PauseMenuDataMgr::countArmors(const sead::SafeString& lowest_rank_armor_name) const {
|
|
if (!isPouchItemArmor(getType(lowest_rank_armor_name)))
|
|
return 0;
|
|
|
|
if (!getItemHead(PouchCategory::Armor))
|
|
return 0;
|
|
|
|
auto* info = ksys::act::InfoData::instance();
|
|
if (!info)
|
|
return 0;
|
|
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
int count = 0;
|
|
sead::FixedSafeString<64> armor_name{lowest_rank_armor_name};
|
|
while (!armor_name.isEmpty()) {
|
|
for (auto* item = *mListHeads[u32(PouchCategory::Armor)]; item; item = nextItem(item)) {
|
|
if (item->getType() > PouchItemType::ArmorLower)
|
|
break;
|
|
if (item->isInInventory() && armor_name == item->getName())
|
|
++count;
|
|
}
|
|
armor_name = ksys::act::getArmorNextRankName(info, armor_name.cstr());
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void PauseMenuDataMgr::addNonDefaultItem(const sead::SafeString& name, int value,
|
|
const act::WeaponModifierInfo* modifier) {
|
|
if (name.include("Default") || name.include("Extra"))
|
|
return;
|
|
|
|
const auto type = getType(name);
|
|
if (type != PouchItemType::Arrow && value >= 0 && isPouchItemWeapon(type))
|
|
value *= act::WeaponModifierInfo::getLifeMultiplier();
|
|
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
addToPouch(name, type, mItemLists, value, false, modifier);
|
|
}
|
|
|
|
void PauseMenuDataMgr::openItemCategoryIfNeeded() const {
|
|
for (s32 i = 0; i < NumTabMax; ++i) {
|
|
const auto type = mTabsType[i];
|
|
if (isPouchItemArmor(type)) {
|
|
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Armor));
|
|
} else {
|
|
switch (type) {
|
|
case PouchItemType::Sword:
|
|
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Sword));
|
|
break;
|
|
case PouchItemType::Bow:
|
|
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Bow));
|
|
break;
|
|
case PouchItemType::Shield:
|
|
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Shield));
|
|
break;
|
|
case PouchItemType::Material:
|
|
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Material));
|
|
break;
|
|
case PouchItemType::Food:
|
|
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::Food));
|
|
break;
|
|
case PouchItemType::KeyItem:
|
|
ksys::gdt::setFlag_IsOpenItemCategory(true, u32(PouchCategory::KeyItem));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PauseMenuDataMgr::initInventoryForOpenWorldDemo() {
|
|
if (!GameScene::isOpenWorldDemo())
|
|
return;
|
|
|
|
initForNewSave();
|
|
|
|
auto* info = ksys::act::InfoData::instance();
|
|
sead::FixedSafeString<64> unused{""};
|
|
|
|
addNonDefaultItem("Weapon_Sword_001",
|
|
info ? ksys::act::getGeneralLife(info, "Weapon_Sword_001") : 30);
|
|
autoEquipLastAddedItem();
|
|
|
|
addNonDefaultItem("Weapon_Shield_001",
|
|
info ? ksys::act::getGeneralLife(info, "Weapon_Shield_001") : 30);
|
|
autoEquipLastAddedItem();
|
|
|
|
addNonDefaultItem("Weapon_Bow_001",
|
|
info ? ksys::act::getGeneralLife(info, "Weapon_Bow_001") : 30);
|
|
autoEquipLastAddedItem();
|
|
|
|
addNonDefaultItem("Armor_001_Head", 0);
|
|
autoEquipLastAddedItem();
|
|
|
|
addNonDefaultItem("Armor_116_Upper", 0);
|
|
autoEquipLastAddedItem();
|
|
|
|
addNonDefaultItem("Armor_001_Lower", 0);
|
|
autoEquipLastAddedItem();
|
|
|
|
for (int i = 0; i < 10; ++i) {
|
|
addNonDefaultItem("Obj_ArrowBundle_A_01", 1);
|
|
autoEquipLastAddedItem();
|
|
}
|
|
|
|
if (!ksys::util::getDebugHeap()) {
|
|
addItemForDebug("Obj_BombArrow_A_01", 100);
|
|
addItemForDebug("Obj_AncientArrow_A_01", 100);
|
|
addItemForDebug("Obj_FireArrow_A_01", 100);
|
|
addItemForDebug("Obj_ElectricArrow_A_01", 100);
|
|
addItemForDebug("Obj_IceArrow_A_01", 100);
|
|
addItemForDebug("PlayerStole2", 1);
|
|
addItemForDebug("Obj_DRStone_Get", 1);
|
|
addItemForDebug("GameRomHorseSaddle_01", 1);
|
|
addItemForDebug("GameRomHorseSaddle_02", 1);
|
|
addItemForDebug("GameRomHorseSaddle_03", 1);
|
|
addItemForDebug("GameRomHorseSaddle_04", 1);
|
|
addItemForDebug("GameRomHorseSaddle_05", 1);
|
|
addItemForDebug("GameRomHorseReins_01", 1);
|
|
addItemForDebug("GameRomHorseReins_02", 1);
|
|
addItemForDebug("GameRomHorseReins_03", 1);
|
|
addItemForDebug("GameRomHorseReins_04", 1);
|
|
addItemForDebug("GameRomHorseReins_05", 1);
|
|
addItemForDebug("Weapon_Lsword_056", 1);
|
|
addItemForDebug("Weapon_Spear_001", 1);
|
|
addItemForDebug("Weapon_Lsword_032", 1);
|
|
addItemForDebug("Weapon_Shield_002", 1);
|
|
addItemForDebug("Weapon_Bow_002", 1);
|
|
addItemForDebug("Weapon_Bow_027", 1);
|
|
addItemForDebug("Weapon_Sword_070", 1);
|
|
addItemForDebug("Weapon_Sword_043", 1);
|
|
addItemForDebug("Weapon_Sword_006", 1);
|
|
}
|
|
|
|
const auto lock = sead::makeScopedLock(mCritSection);
|
|
saveToGameData(getItems());
|
|
}
|
|
|
|
[[gnu::noinline]] void PouchItem::sortIngredients() {
|
|
mIngredients.sort(
|
|
[](const sead::FixedSafeString<64>* lhs, const sead::FixedSafeString<64>* rhs) {
|
|
auto* info = ksys::act::InfoData::instance();
|
|
if (!lhs || !rhs || !info)
|
|
return 0;
|
|
|
|
if (rhs->isEmpty()) {
|
|
if (lhs->isEmpty())
|
|
return 0;
|
|
return -1;
|
|
}
|
|
|
|
const s32 key1 = info->getSortKey(lhs->cstr());
|
|
const s32 key2 = info->getSortKey(rhs->cstr());
|
|
if (key1 < key2)
|
|
return -1;
|
|
if (key1 > key2)
|
|
return 1;
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
void PauseMenuDataMgr::updateDivineBeastClearFlags(int num_cleared_beasts) {
|
|
switch (num_cleared_beasts) {
|
|
case 1:
|
|
ksys::gdt::setFlag_Find_4Relic_1stClear(true);
|
|
break;
|
|
case 2:
|
|
ksys::gdt::setFlag_Find_4Relic_2ndClear(true);
|
|
ksys::gdt::setFlag_BattleArena_Level1(false, true);
|
|
ksys::gdt::setFlag_BattleArena_Level2(true, true);
|
|
break;
|
|
case 3:
|
|
ksys::gdt::setFlag_Find_4Relic_3rdClear(true);
|
|
ksys::gdt::setFlag_BattleArena_Level1(false, true);
|
|
ksys::gdt::setFlag_BattleArena_Level2(false, true);
|
|
ksys::gdt::setFlag_BattleArena_Level3(true, true);
|
|
break;
|
|
case 4:
|
|
ksys::gdt::setFlag_Find_4Relic_4thClear(true);
|
|
ksys::gdt::setFlag_BattleArena_Level1(false, true);
|
|
ksys::gdt::setFlag_BattleArena_Level2(false, true);
|
|
ksys::gdt::setFlag_BattleArena_Level3(false, true);
|
|
ksys::gdt::setFlag_BattleArena_Level4(true, true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PauseMenuDataMgr::equipWeapon(PouchItem* weapon) {
|
|
if (!weapon) {
|
|
return;
|
|
}
|
|
auto lock = sead::makeScopedLock(mCritSection);
|
|
auto* item = getItems().nth(0);
|
|
while (item && item->isWeapon()) {
|
|
if (item->mType == weapon->mType) {
|
|
item->mEquipped = false;
|
|
}
|
|
item = getItems().next(item);
|
|
}
|
|
weapon->mEquipped = true;
|
|
saveToGameData(getItems());
|
|
}
|
|
|
|
void PauseMenuDataMgr::unequip(PouchItem* item) {
|
|
if (!item) {
|
|
return;
|
|
}
|
|
auto lock = sead::makeScopedLock(mCritSection);
|
|
item->mEquipped = false;
|
|
saveToGameData(getItems());
|
|
}
|
|
|
|
// FIXME: types
|
|
bool PauseMenuDataMgr::useItemFromRecipeAndSave(void* unk, int multiplier, PouchItem* item) {
|
|
auto lock = sead::makeScopedLock(mCritSection);
|
|
useItemFromRecipe(&mItemLists, unk, multiplier, item);
|
|
saveToGameData(getItems());
|
|
return true;
|
|
}
|
|
|
|
void PauseMenuDataMgr::grabbedItemStuff(PouchItem* item) {
|
|
auto lock = sead::makeScopedLock(mCritSection);
|
|
|
|
for (auto& cur : getItems()) {
|
|
if (&cur == item && item->mType == PouchItemType::Material) {
|
|
if (item->mValue > 1) {
|
|
item->mValue -= 1;
|
|
} else {
|
|
item->mEquipped = false;
|
|
item->mValue = 0;
|
|
}
|
|
|
|
for (auto& info : mGrabbedItems) {
|
|
if (info.item) {
|
|
if (info.item == item) {
|
|
info._9 = true;
|
|
}
|
|
} else {
|
|
info.item = item;
|
|
info._8 = true;
|
|
info._9 = false;
|
|
spawnDroppedInventoryItem(
|
|
item->getName().cstr(),
|
|
ksys::act::ActorHeapUtil::instance()->getBaseProcHeap(), -1,
|
|
SleepAfterInit::No, nullptr, SpawnViaCarryBox::Yes, 0.8, -0.8);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
updateInventoryInfo(getItems());
|
|
saveToGameData(getItems());
|
|
}
|
|
}
|
|
|
|
} // namespace uking::ui
|