From 2340dc7bc09a354ff6e487e723c667d873dd7073 Mon Sep 17 00:00:00 2001 From: Briggs Baltzell Date: Fri, 27 Jan 2023 11:30:24 -0600 Subject: [PATCH] Implement CookingMgr class (#113) * Added CookingMgr fields and started on ctor * Implemented CookItem::copy * Implemented CookingMgr dtor * Marked CookingMgr createInstance as matching * Implemented CookingMgr::getCookItem * Added CookingMgr::cook decl * Added CookingMgr method decls * Replaced part of CookingMgr with sead::FixedObjArray * Added line separators in cookManager.h * Defined structs Ingredient and BoostArg in CookingMgr * Defined structs CookArg and CookIngredient * Renamed some fields of CookingMgr * Added struct CookingMgr::CookingEffectEntry * Started implementing CookingMgr::init * Set more entries in init * Set field _548 * Set field default values in init * Loaded string values from config * Set mCookingEffectEntries in init * Added conditional mConfig allocation * Added FixedTreeMap class to sead * Used FixedTreeMap in CookingMgr * Set CookingMgr constructor to default and marked it as matching * Removed field _31C from CookingMgr * Updated sead * Cleared _548 before filling * Used name hash for _548 keys * Fixed path setup * Changed type of _548 to sead::FixedTreeMap * Set mNMSSR to correct values * Fixed field init order * Rearranged variables * Swapped entry_hash comparisons * Changed CRC32 names and index values * Cast i to u64 * Renamed static crc32 variables * Renamed _548 to mCookingEffectNameIdMap * Marked static vars as const and used decimal numbers * Used sead::SafeArray for mNMMR and mNMSSR * Rearranged mNMMR and mNMSSR conditions * Removed size check from mNMMR and mNMSSR conditions * Removed size check from CEI conditions * Renamed mRes2 to mResHandle * Renamed cook args * Moved crc32 statics into Constants struct * Marked CookingMgr::init as matching * Renamed Constants to Crc32Constants * Added cookFail and cookFailForMissingConfig * Added NumIngredientsMax, NumEffects, and NumEffectSlots constants * Renamed getCookItem to setCookItem * Renamed x to getCookItem * Implemented getCookItem * Made setCookItem take a const ref * Decompiled cookCalcItemPrice * Marked cookCalcItemPrice as const * Fixed both cookFail and cookFailForMissingConfig * Added CookItem::reset * Added CookingMgr::resetCookItem * Moved ctor and dtor below static variables * Changed cookFailForMissingConfig arg to sead::SafeString * Started on cookCalcBoost * Moved CookItem into cookManager.h * Moved Cooking into Game * Moved CookingMgr to uking namespace * Inlined CookItem::reset * Renamed CookItem members * Fixed CookItem mangled names in uking_functions.csv * Added enum class CookEffectId * Moved CookingMgr ctor below disposer impl * Updated sead * Started on cookHandleBoostSuccessInner * Made changes to cookCalcBoost and cookHandleBoostSuccessInner * Extracted cookHandleBoostMonsterExtractInner * Removed found_monster_extract in cookCalcBoost * Marked cookCalcBoost as matching * Got cookHandleBoostMonsterExtractInner matching * Made changes to cookHandleBoostSuccessInner * Renamed discriminator to bonus in cookHandleBoostSuccessInner * Added CookingMgr::cookCalcSpiceBoost * Made changes to CookingMgr::cookCalcSpiceBoost * Added CookingMgr::cookCalcPotencyBoost * Mostly matched CookingMgr::cookCalcPotencyBoost * Added CookingMgr::getCookEffectId * Used getCookEffectId in cookCalcPotencyBoost * Set access modifiers in CookingMgr * Made cookCalcPotencyBoost const-correct * Marked global init as mismatching * Renamed CookingMgr functions in uking_functions.csv * Added CookingMgr::prepareCookArg * Added IngredientArray type * Added cookWithItems * Added CookingMgr::prepareCookArg * Fixed getCookEffectId * Fixed cookCalcPotencyBoost * Made changes to prepareCookArg * Got cookWithItems matching * Marked vars const * Removed commented-out destructor and UnkItem * Renamed CookIngredient._58 to count * Started on CookingMgr::cook * Extracted cookAdjustItem * Fixed ingredient setup * Extracted actor_info_data * Fixed some control flow for single ingredients * Fixed more control flow for single ingredients * Handled no effect and empty actor name * Renamed Ingredient::_10 to used_in_recipe * Fixed recipe control flow * Matched cookAdjustItem * Fixed InfoData instance uses * Removed BAD_RECIPE label * Rearranged to remove goto * Added cookCalcRecipeBoost * Extracted getCookEffectId * Extracted isCookFailure and isMedicine * Extracted getCookEffectIdFromTreeMap * Extracted findIngredientByName and findIngredientByTag * Marked nonmatching functions * Added resetArgCookData * Matched resetArgCookData and prepareCookArg * Added fields and handleMessage to CookPotRoot * Started on CookPotRoot::init_ * Added hasMonsterExtract * Added to CookPotRoot::handleMessage_ * Removed Ingredient::_4 field * Renamed CookItem::_224 to is_crit * Renamed CookingEffectEntry members * Renamed CookingMgr fields * Changed comparison in cook * Renamed actor_tag_iter to hash_iter and added num checks * Got cookCalcSpiceBoost closer to matching * Removed int_val init from cookCalcSpiceBoost * Inlined actor_data * Added `using namespace ksys::act` * Moved int_val outside loop * Marked cookCalcSpiceBoost as matching * Rearranged cookHandleBoostSuccessInner * Renamed cookCalcPotencyBoost to cookCalcIngredientsBoost * Renamed stamina_recover to vitality_boost * Renamed CookItem args to cook_item * Renamed cookFailForMissingConfig arg * Used IngredientArray in findIngredient functions * Renamed crit functions * Renamed getCookEffectId arg * Renamed getCookEffectId to getCookEffectIdByName * Added SetCookItemInDemo::oneShot_ * Matched SetCookItemInDemo::oneShot_ * Added comment about CookArg constructor and formatted * Removed TODO from CookingMgr * Marked WIP functions as non-matching * Fixed formatting * Matched sead::SafeArray::SafeArray * Added BoostArg default member initializers * Used auto for cooking_mgr in CookPotRoot::handleMessage_ * Used designated initializers for boost_arg and added whitespace * Used angle brackets for sead import * Removed [[maybe_unused]] from declaration * Used safeDelete * Inverted condition in CookingMgr::cookCalcCritBoost * Added getCookingEffectEntry getter and moved CookingEffectEntry up * Cleaned up CookingMgr::cookCalcSpiceBoost * Used loops to init ingredient num members * Converted CookingMgr fields to s32 * Cleaned up CookingMgr::cookCalcItemPrice * Renamed CookItem::item_price to sell_price * Used min and max in CookingMgr::cookCalcItemPrice * Inlined count vars in CookingMgr::cookCalcItemPrice * Removed explicit casts in CookingMgr::cookCalcItemPrice * Cleaned up and reformatted CookingMgr::cookCalcIngredientsBoost * Added casts back into CookingMgr::cookCalcItemPrice to silence warnings --- data/uking_functions.csv | 61 +- src/Game/AI/AI/aiCookPotRoot.cpp | 85 +- src/Game/AI/AI/aiCookPotRoot.h | 24 + .../AI/Action/actionSetCookItemInDemo.cpp | 23 + src/Game/AI/Action/actionSetCookItemInDemo.h | 1 + src/Game/CMakeLists.txt | 1 + .../Cooking/CMakeLists.txt | 2 - src/Game/Cooking/cookManager.cpp | 1195 +++++++++++++++++ src/Game/Cooking/cookManager.h | 181 +++ src/Game/UI/uiPauseMenuDataMgr.cpp | 24 +- src/Game/UI/uiPauseMenuDataMgr.h | 8 +- src/KingSystem/CMakeLists.txt | 1 - src/KingSystem/Cooking/cookItem.cpp | 7 - src/KingSystem/Cooking/cookItem.h | 24 - src/KingSystem/Cooking/cookManager.cpp | 1 - src/KingSystem/Cooking/cookManager.h | 15 - 16 files changed, 1556 insertions(+), 97 deletions(-) rename src/{KingSystem => Game}/Cooking/CMakeLists.txt (69%) create mode 100644 src/Game/Cooking/cookManager.cpp create mode 100644 src/Game/Cooking/cookManager.h delete mode 100644 src/KingSystem/Cooking/cookItem.cpp delete mode 100644 src/KingSystem/Cooking/cookItem.h delete mode 100644 src/KingSystem/Cooking/cookManager.cpp delete mode 100644 src/KingSystem/Cooking/cookManager.h diff --git a/data/uking_functions.csv b/data/uking_functions.csv index 977b2e8a..17da63b8 100644 --- a/data/uking_functions.csv +++ b/data/uking_functions.csv @@ -386,7 +386,7 @@ Address,Quality,Size,Name 0x0000007100009ffc,O,000140,_ZNK4sead15RuntimeTypeInfo6DeriveIN4ksys4phys7UserTagEE9isDerivedEPKNS0_9InterfaceE 0x000000710000a088,U,000140, 0x000000710000a114,U,000316,CookResult::construct -0x000000710000a250,O,000384,_ZN4ksys8CookItemC1Ev +0x000000710000a250,O,000384,_ZN5uking8CookItemC1Ev 0x000000710000a3d0,U,000532, 0x000000710000a5e4,U,000736,act::copyCookResultToCookItem 0x000000710000a8c4,U,000004,j_GameObject::m0 @@ -15657,7 +15657,7 @@ Address,Quality,Size,Name 0x0000007100246168,O,000020,_ZN5uking6action17SetCookItemInDemoD1Ev 0x000000710024617c,O,000052,_ZN5uking6action17SetCookItemInDemoD0Ev 0x00000071002461b0,O,000008,_ZN5uking6action17SetCookItemInDemo5init_EPN4sead4HeapE -0x00000071002461b8,U,000208,_ZN5uking6action17SetCookItemInDemo8oneShot_Ev +0x00000071002461b8,O,000208,_ZN5uking6action17SetCookItemInDemo8oneShot_Ev 0x0000007100246288,O,000360,_ZN5uking6action17SetCookItemInDemo11loadParams_Ev 0x00000071002463f0,O,000288,_ZNK5uking6action17SetCookItemInDemo27checkDerivedRuntimeTypeInfoEPKN4sead15RuntimeTypeInfo9InterfaceE 0x0000007100246510,O,000092,_ZNK5uking6action17SetCookItemInDemo18getRuntimeTypeInfoEv @@ -22375,17 +22375,17 @@ Address,Quality,Size,Name 0x0000007100357d1c,U,000008,uking::ai::DragonRootBase::m39 0x0000007100357d24,U,000004,j__ZdlPv_95 0x0000007100357d28,U,000176,AI_AI_CookPotRoot::ctor -0x0000007100357dd8,U,000596,CookArg::ctor +0x0000007100357dd8,O,000596,_ZN4sead9SafeArrayIN5uking14CookIngredientELi5EEC2Ev 0x000000710035802c,U,000112,_ZN5uking2ai11CookPotRootD1Ev 0x000000710035809c,U,000004,nullsub_1060 0x00000071003580a0,U,000120,_ZN5uking2ai11CookPotRootD0Ev -0x0000007100358118,U,000640,_ZN5uking2ai11CookPotRoot5init_EPN4sead4HeapE +0x0000007100358118,M,000640,_ZN5uking2ai11CookPotRoot5init_EPN4sead4HeapE 0x0000007100358398,U,000316,_ZN5uking2ai11CookPotRoot6enter_EPN4ksys3act2ai15InlineParamPackE 0x00000071003584d4,U,000320,AI_AI_CookPotRoot::x 0x0000007100358614,U,000488,_ZN5uking2ai11CookPotRoot5calc_Ev 0x00000071003587fc,U,000192,_ZN5uking2ai11CookPotRoot6leave_Ev 0x00000071003588bc,O,000100,_ZN5uking2ai11CookPotRoot11loadParams_Ev -0x0000007100358920,U,000660,_ZN5uking2ai11CookPotRoot14handleMessage_ERKN4ksys7MessageE +0x0000007100358920,M,000660,_ZN5uking2ai11CookPotRoot14handleMessage_ERKN4ksys7MessageE 0x0000007100358bb4,O,000288,_ZNK5uking2ai11CookPotRoot27checkDerivedRuntimeTypeInfoEPKN4sead15RuntimeTypeInfo9InterfaceE 0x0000007100358cd4,O,000092,_ZNK5uking2ai11CookPotRoot18getRuntimeTypeInfoEv 0x0000007100358d30,U,000204, @@ -53023,29 +53023,30 @@ Address,Quality,Size,Name 0x000000710089f8c0,O,000140,_ZNK4sead15RuntimeTypeInfo6DeriveIN4ksys3res16EntryFactoryBaseEE9isDerivedEPKNS0_9InterfaceE 0x000000710089f94c,U,000004,j__ZdlPv_390 0x000000710089f950,U,000004,nullsub_5418 -0x000000710089f954,U,000792,sinitCookingEffectsAndCrc32 -0x000000710089fc6c,U,000084, -0x000000710089fcc0,U,000528,CookItem::copy -0x000000710089fed0,U,000120,CookingMgr::Disposer::dtor -0x000000710089ff48,U,000128,CookingMgr::Disposer::dtorDelete -0x000000710089ffc8,U,000128,CookingMgr::createInstance -0x00000071008a0048,U,000664,CookingMgr::ctor -0x00000071008a02e0,U,000484,CookingMgr::cookFail -0x00000071008a04c4,U,000644,CookingMgr::cookFailForMissingConfig -0x00000071008a0748,U,001112,CookingMgr::cookCalcBoost -0x00000071008a0ba0,U,000436,CookingMgr::cookHandleBoostSuccessInner -0x00000071008a0d54,U,000512,CookingMgr::cookCalc3 -0x00000071008a0f54,U,000604,CookingMgr::cookCalcItemPrice -0x00000071008a11b0,U,001412,CookingMgr::cookCalc1 -0x00000071008a1734,U,000124,CookingMgr::__auto0 -0x00000071008a17b0,U,002908,CookingMgr::init -0x00000071008a230c,U,003308,CookingMgr::cook -0x00000071008a2ff8,U,001648,CookingMgr::resetArgCookData -0x00000071008a3668,U,001628, -0x00000071008a3cc4,U,001496,CookingMgr::cookWithItems -0x00000071008a429c,U,000016,CookingMgr::getCookItem -0x00000071008a42ac,U,000084, -0x00000071008a4300,U,000008,CookingMgr::x +0x000000710089f954,M,000792,_GLOBAL__sub_I_cookManager.cpp +0x000000710089fc6c,O,000084,_ZN5uking8CookItem5resetEv +0x000000710089fcc0,O,000528,_ZNK5uking8CookItem4copyERS0_ +0x000000710089fed0,O,000120,_ZN5uking10CookingMgr18SingletonDisposer_D1Ev +0x000000710089ff48,O,000128,_ZN5uking10CookingMgr18SingletonDisposer_D0Ev +0x000000710089ffc8,O,000128,_ZN5uking10CookingMgr14createInstanceEPN4sead4HeapE +0x00000071008a0048,O,000664,_ZN5uking10CookingMgrC1Ev +0x00000071008a02e0,O,000484,_ZN5uking10CookingMgr8cookFailERNS_8CookItemE +0x00000071008a04c4,O,000644,_ZN5uking10CookingMgr24cookFailForMissingConfigERNS_8CookItemERKN4sead14SafeStringBaseIcEE +0x00000071008a0748,O,000708,_ZNK5uking10CookingMgr17cookCalcCritBoostERKN4sead9SafeArrayINS0_10IngredientELi5EEERNS_8CookItemEPKNS0_8BoostArgE +0x00000071008a0a0c,O,000404,_ZNK5uking10CookingMgr24cookHandleMonsterExtractERKN4sead9SafeArrayINS0_10IngredientELi5EEERNS_8CookItemE +0x00000071008a0ba0,M,000436,_ZNK5uking10CookingMgr14cookHandleCritERKN4sead9SafeArrayINS0_10IngredientELi5EEERNS_8CookItemE +0x00000071008a0d54,O,000512,_ZNK5uking10CookingMgr18cookCalcSpiceBoostERKN4sead9SafeArrayINS0_10IngredientELi5EEERNS_8CookItemE +0x00000071008a0f54,O,000604,_ZNK5uking10CookingMgr17cookCalcItemPriceERKN4sead9SafeArrayINS0_10IngredientELi5EEERNS_8CookItemE +0x00000071008a11b0,O,001412,_ZNK5uking10CookingMgr24cookCalcIngredientsBoostERKN4sead9SafeArrayINS0_10IngredientELi5EEERNS_8CookItemE +0x00000071008a1734,O,000124,_ZNK5uking10CookingMgr21getCookEffectIdByNameERKN4sead14SafeStringBaseIcEE +0x00000071008a17b0,O,002908,_ZN5uking10CookingMgr4initEPN4sead4HeapE +0x00000071008a230c,M,003308,_ZN5uking10CookingMgr4cookERKNS_7CookArgERNS_8CookItemERKNS0_8BoostArgE +0x00000071008a2ff8,O,001648,_ZNK5uking10CookingMgr16resetArgCookDataERNS_7CookArgERKN4sead6BufferINS3_15FixedSafeStringILi64EEEEEiRNS_8CookItemE +0x00000071008a3668,O,001628,_ZNK5uking10CookingMgr14prepareCookArgERNS_7CookArgERKN4sead9SafeArrayINS3_15FixedSafeStringILi64EEELi5EEEiRNS_8CookItemE +0x00000071008a3cc4,O,001496,_ZN5uking10CookingMgr13cookWithItemsERKN4sead14SafeStringBaseIcEES5_S5_S5_S5_RNS_8CookItemERKNS0_8BoostArgE +0x00000071008a429c,O,000016,_ZN5uking10CookingMgr11setCookItemERKNS_8CookItemE +0x00000071008a42ac,O,000084,_ZN5uking10CookingMgr13resetCookItemEv +0x00000071008a4300,O,000008,_ZNK5uking10CookingMgr11getCookItemERNS_8CookItemE 0x00000071008a4308,U,000016, 0x00000071008a4318,U,000116, 0x00000071008a438c,U,000048, @@ -56385,8 +56386,8 @@ Address,Quality,Size,Name 0x000000710096efb8,O,000688,_ZN5uking2ui16PauseMenuDataMgr7itemGetERKN4sead14SafeStringBaseIcEEiPKNS_3act18WeaponModifierInfoE 0x000000710096f268,O,001876,_ZN5uking2ui16PauseMenuDataMgr10addToPouchERKN4sead14SafeStringBaseIcEENS0_13PouchItemTypeERNS1_5ListsEibPKNS_3act18WeaponModifierInfoEb 0x000000710096f9bc,O,001700,_ZNK5uking2ui16PauseMenuDataMgr14saveToGameDataERKN4sead10OffsetListINS0_9PouchItemEEE -0x0000007100970060,O,000248,_ZN5uking2ui16PauseMenuDataMgr11cookItemGetERKN4ksys8CookItemE -0x0000007100970158,O,000268,_ZN5uking2ui16PauseMenuDataMgr26setCookDataOnLastAddedItemERKN4ksys8CookItemE +0x0000007100970060,O,000248,_ZN5uking2ui16PauseMenuDataMgr11cookItemGetERKNS_8CookItemE +0x0000007100970158,O,000268,_ZN5uking2ui16PauseMenuDataMgr26setCookDataOnLastAddedItemERKNS_8CookItemE 0x0000007100970264,O,000116,_ZN5uking2ui16PauseMenuDataMgr22autoEquipLastAddedItemEv 0x00000071009702d8,m,000268,_ZN5uking2ui16PauseMenuDataMgr9autoEquipEPNS0_9PouchItemERKN4sead10OffsetListIS2_EE 0x00000071009703e4,m,000216,_ZN5uking2ui16PauseMenuDataMgr10unequipAllENS0_13PouchItemTypeE diff --git a/src/Game/AI/AI/aiCookPotRoot.cpp b/src/Game/AI/AI/aiCookPotRoot.cpp index f55ecf24..011c7cf1 100644 --- a/src/Game/AI/AI/aiCookPotRoot.cpp +++ b/src/Game/AI/AI/aiCookPotRoot.cpp @@ -1,4 +1,11 @@ #include "Game/AI/AI/aiCookPotRoot.h" +#include "Game/DLC/aocHardModeManager.h" +#include "Game/UI/uiPauseMenuDataMgr.h" +#include "KingSystem/ActorSystem/Attention/actAttention.h" +#include "KingSystem/ActorSystem/actActor.h" +#include "KingSystem/Utils/Thread/Message.h" +#include "KingSystem/World/worldEnvMgr.h" +#include "KingSystem/World/worldManager.h" namespace uking::ai { @@ -6,8 +13,20 @@ CookPotRoot::CookPotRoot(const InitArg& arg) : ksys::act::ai::Ai(arg) {} CookPotRoot::~CookPotRoot() = default; +// NON_MATCHING bool CookPotRoot::init_(sead::Heap* heap) { - return ksys::act::ai::Ai::init_(heap); + auto* ingredients = + new (heap, std::nothrow) sead::FixedSafeString<64>[CookingMgr::NumIngredientsMax]; + mCookIngredients.setBuffer(CookingMgr::NumIngredientsMax, ingredients); + if (!mCookIngredients.isBufferReady()) { + return false; + } + if (*mInitBurnState_m) { + // TODO + } + mActor->getChemicalStuff(); + mProcLink.reset(); + return true; } void CookPotRoot::enter_(ksys::act::ai::InlineParamPack* params) { @@ -23,4 +42,68 @@ void CookPotRoot::loadParams_() { getAITreeVariable(&mCurrentCookResultHolder_a, "CurrentCookResultHolder"); } +// NON_MATCHING +bool CookPotRoot::handleMessage_(const ksys::Message& message) { + if (message.getType().value == (u32)ksys::act::AttActionCode::Cook) { + // 着火: "ignition" or "on fire" + // This is checking if the pot is lit. + if (isCurrentChild("着火")) { + // TODO: sub_7100EDC4B8 + // TODO: GameSceneSubsys12 class + // GameSceneSubsys12 handles carried items. + // This needs to check if the player is targeting the pot. + if (true /* TODO */) { + if (!_48 /* TODO */) { + _48 = true; + + const int num_ingredients = 5; // TODO + auto* cooking_mgr = CookingMgr::instance(); + + cooking_mgr->resetArgCookData(mCookArg, mCookIngredients, num_ingredients, + mCookItem); + + CookingMgr::BoostArg boost_arg{.always_boost = false, + .enable_random_boost = true}; + + if (ksys::world::Manager::instance()->getEnvMgr()->isInBloodMoonTimeRange()) { + boost_arg.always_boost = true; + } + + if (cooking_mgr->cook(mCookArg, mCookItem, boost_arg)) { + if (const auto* hard_mode_manager = aoc::HardModeManager::instance()) { + if (hard_mode_manager->checkFlag( + aoc::HardModeManager::Flag::EnableHardMode)) { + if (hard_mode_manager->isHardModeChangeOn( + aoc::HardModeManager::HardModeChange::NerfHpRestore)) { + hard_mode_manager->nerfHpRestore(&mCookItem.life_recover); + } + } + } + if (true /* TODO: callCookingDemo */) { + ui::PauseMenuDataMgr::instance()->removeGrabbedItems(); + cooking_mgr->setCookItem(mCookItem); + mHasFinishedCookItem = true; + return true; + } + _48 = false; + mHasFinishedCookItem = false; + // TODO + } else { + _48 = false; + mHasFinishedCookItem = false; + // TODO + } + } + } + } + } + + if (message.getType().value != (u32)ksys::act::AttActionCode::KillTime) { + return false; + } + + // TODO: callDemo007_1 + return isCurrentChild("着火"); +} + } // namespace uking::ai diff --git a/src/Game/AI/AI/aiCookPotRoot.h b/src/Game/AI/AI/aiCookPotRoot.h index fdedb902..ae67eea8 100644 --- a/src/Game/AI/AI/aiCookPotRoot.h +++ b/src/Game/AI/AI/aiCookPotRoot.h @@ -1,6 +1,8 @@ #pragma once +#include "Game/Cooking/cookManager.h" #include "KingSystem/ActorSystem/actAiAi.h" +#include "KingSystem/ActorSystem/actBaseProcLink.h" namespace uking::ai { @@ -14,12 +16,34 @@ public: void enter_(ksys::act::ai::InlineParamPack* params) override; void leave_() override; void loadParams_() override; + bool handleMessage_(const ksys::Message& message) override; protected: // map_unit_param at offset 0x38 const bool* mInitBurnState_m{}; // aitree_variable at offset 0x40 void* mCurrentCookResultHolder_a{}; + bool _48; + bool mHasFinishedCookItem; + u8 _4A[6]; + CookArg mCookArg; + sead::Buffer> mCookIngredients; + u8 _240; + u8 _241; + u8 _242[6]; + // vtable + void* _248; + s64 _250; + s32 _258; + u8 _25C[4]; + u8 _260[8]; + u32 _268; + ksys::act::BaseProcLink mProcLink; + s32 _280; + // vtable + void* _288; + CookItem mCookItem; }; +KSYS_CHECK_SIZE_NX150(CookPotRoot, 0x4B8); } // namespace uking::ai diff --git a/src/Game/AI/Action/actionSetCookItemInDemo.cpp b/src/Game/AI/Action/actionSetCookItemInDemo.cpp index 2116c2ed..df8b987a 100644 --- a/src/Game/AI/Action/actionSetCookItemInDemo.cpp +++ b/src/Game/AI/Action/actionSetCookItemInDemo.cpp @@ -1,4 +1,6 @@ #include "Game/AI/Action/actionSetCookItemInDemo.h" +#include "Game/Cooking/cookManager.h" +#include "Game/UI/uiPauseMenuDataMgr.h" namespace uking::action { @@ -10,6 +12,27 @@ bool SetCookItemInDemo::init_(sead::Heap* heap) { return ksys::act::ai::Action::init_(heap); } +bool SetCookItemInDemo::oneShot_() { + CookItem cook_item{}; + + CookingMgr::BoostArg boost_arg{}; + + s32 adjusted_set_num = sead::Mathi::clampMin(*mSetNum_d, 1); + + bool result = CookingMgr::instance()->cookWithItems(mPorchItemName01_d, mPorchItemName02_d, + mPorchItemName03_d, mPorchItemName04_d, + mPorchItemName05_d, cook_item, boost_arg); + + if (!ui::PauseMenuDataMgr::instance() || !result) + return false; + + for (int i = 0; i < adjusted_set_num; i++) { + ui::PauseMenuDataMgr::instance()->cookItemGet(cook_item); + } + + return true; +} + void SetCookItemInDemo::loadParams_() { getDynamicParam(&mSetNum_d, "SetNum"); getDynamicParam(&mPorchItemName01_d, "PorchItemName01"); diff --git a/src/Game/AI/Action/actionSetCookItemInDemo.h b/src/Game/AI/Action/actionSetCookItemInDemo.h index d7123653..ee8cb2b2 100644 --- a/src/Game/AI/Action/actionSetCookItemInDemo.h +++ b/src/Game/AI/Action/actionSetCookItemInDemo.h @@ -11,6 +11,7 @@ public: ~SetCookItemInDemo() override; bool init_(sead::Heap* heap) override; + bool oneShot_() override; void loadParams_() override; protected: diff --git a/src/Game/CMakeLists.txt b/src/Game/CMakeLists.txt index 183a945e..91759382 100644 --- a/src/Game/CMakeLists.txt +++ b/src/Game/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(Actor) add_subdirectory(AI) +add_subdirectory(Cooking) add_subdirectory(Damage) add_subdirectory(DLC) add_subdirectory(UI) diff --git a/src/KingSystem/Cooking/CMakeLists.txt b/src/Game/Cooking/CMakeLists.txt similarity index 69% rename from src/KingSystem/Cooking/CMakeLists.txt rename to src/Game/Cooking/CMakeLists.txt index 484a3df6..7ac93f21 100644 --- a/src/KingSystem/Cooking/CMakeLists.txt +++ b/src/Game/Cooking/CMakeLists.txt @@ -1,6 +1,4 @@ target_sources(uking PRIVATE - cookItem.cpp - cookItem.h cookManager.cpp cookManager.h ) diff --git a/src/Game/Cooking/cookManager.cpp b/src/Game/Cooking/cookManager.cpp new file mode 100644 index 00000000..3b6a31ff --- /dev/null +++ b/src/Game/Cooking/cookManager.cpp @@ -0,0 +1,1195 @@ +#include "Game/Cooking/cookManager.h" +#include +#include +#include +#include "KingSystem/ActorSystem/actInfoData.h" +#include "KingSystem/Resource/resLoadRequest.h" +#include "KingSystem/Utils/InitTimeInfo.h" +#include "KingSystem/Utils/SafeDelete.h" + +namespace uking { + +struct CookingEffect { + sead::SafeString name; + CookEffectId effect_id; +}; + +static const CookingEffect sCookingEffects[CookingMgr::NumEffects]{ + {"None", CookEffectId::None}, + {"LifeRecover", CookEffectId::LifeRecover}, + {"LifeMaxUp", CookEffectId::LifeMaxUp}, + {"ResistHot", CookEffectId::ResistHot}, + {"ResistCold", CookEffectId::ResistCold}, + {"ResistElectric", CookEffectId::ResistElectric}, + {"AttackUp", CookEffectId::AttackUp}, + {"DefenseUp", CookEffectId::DefenseUp}, + {"Quietness", CookEffectId::Quietness}, + {"MovingSpeed", CookEffectId::MovingSpeed}, + {"GutsRecover", CookEffectId::GutsRecover}, + {"ExGutsMaxUp", CookEffectId::ExGutsMaxUp}, + {"Fireproof", CookEffectId::Fireproof}, +}; + +struct Crc32Constants { + ksys::util::InitConstants init_constants; + const u32 crc32_life_recover = sead::HashCRC32::calcStringHash("LifeRecover"); + const u32 crc32_guts_performance = sead::HashCRC32::calcStringHash("GutsPerformance"); + const u32 crc32_stamina_recover = sead::HashCRC32::calcStringHash("StaminaRecover"); + const u32 crc32_life_max_up = sead::HashCRC32::calcStringHash("LifeMaxUp"); + const u32 crc32_resist_hot = sead::HashCRC32::calcStringHash("ResistHot"); + const u32 crc32_resist_cold = sead::HashCRC32::calcStringHash("ResistCold"); + const u32 crc32_resist_electric = sead::HashCRC32::calcStringHash("ResistElectric"); + const u32 crc32_all_speed = sead::HashCRC32::calcStringHash("AllSpeed"); + const u32 crc32_attack_up = sead::HashCRC32::calcStringHash("AttackUp"); + const u32 crc32_defense_up = sead::HashCRC32::calcStringHash("DefenseUp"); + const u32 crc32_quietness = sead::HashCRC32::calcStringHash("Quietness"); + const u32 crc32_fireproof = sead::HashCRC32::calcStringHash("Fireproof"); +}; + +static Crc32Constants sCrc32Constants; + +CookItem::CookItem() = default; + +void CookItem::reset() { + actor_name.clear(); + life_recover = 0.0f; + effect_time = 0; + is_crit = false; + sell_price = 0; + effect_id = CookEffectId::None; + vitality_boost = 0.0f; + for (auto& ingredient : ingredients) { + ingredient.clear(); + } +} + +void CookItem::copy(CookItem& to) const { + to.actor_name = actor_name; + to.life_recover = life_recover; + to.effect_time = effect_time; + to.is_crit = is_crit; + to.sell_price = sell_price; + to.vitality_boost = vitality_boost; + to.effect_id = effect_id; + to.ingredients = ingredients; +} + +SEAD_SINGLETON_DISPOSER_IMPL(CookingMgr) + +CookingMgr::CookingMgr() = default; + +CookingMgr::~CookingMgr() { + if (mConfig) { + ksys::util::safeDelete(mConfig); + } +} + +void CookingMgr::cookFail(CookItem& cook_item) { + if (cook_item.actor_name.isEmpty()) + cook_item.actor_name.copy(mFailActorName); + + f32 life_recover; + if (cook_item.actor_name == mFailActorName) { + // Dubious food + cook_item.effect_time = 0; + const f32 min_recovery = (f32)mFailActorLifeRecover; + life_recover = + min_recovery > cook_item.life_recover ? min_recovery : cook_item.life_recover; + } else { + // Rock-hard food + cook_item.effect_time = 0; + life_recover = (f32)mStoneFoodActorLifeRecover; + } + + cook_item.life_recover = life_recover; + cook_item.vitality_boost = 0.0f; + cook_item.effect_id = CookEffectId::None; + cook_item.sell_price = 2; +} + +void CookingMgr::cookFailForMissingConfig(CookItem& cook_item, const sead::SafeString& actor_name) { + f32 life_recover; + if (actor_name.isEmpty() || actor_name == mFailActorName) { + cook_item.actor_name.copy(mFailActorName); + cook_item.effect_time = 0; + life_recover = (f32)mFailActorLifeRecover; + } else { + cook_item.actor_name = actor_name; + cook_item.effect_time = 0; + life_recover = (f32)mStoneFoodActorLifeRecover; + } + + cook_item.life_recover = life_recover; + cook_item.vitality_boost = 0.0f; + cook_item.effect_id = CookEffectId::None; + cook_item.sell_price = 1; +} + +void CookingMgr::cookCalcCritBoost(const IngredientArray& ingredients, CookItem& cook_item, + const BoostArg* boost_arg) const { + // Find if any of the ingredients are Monster Extract. + if (hasMonsterExtract(ingredients)) { + cookHandleMonsterExtract(ingredients, cook_item); + return; + } + + if (!boost_arg || !boost_arg->always_boost) { + if (boost_arg && !boost_arg->enable_random_boost) + return; + + s32 threshold = 0; + s32 num_ingredients = 0; + s32 success_rate = 0; + + for (int i = 0; i < NumIngredientsMax; i++) { + const auto& ingredient = ingredients[i]; + + if (!ingredient.arg) + continue; + + num_ingredients++; + + if (ingredient.actor_data.tryGetIntByKey(&success_rate, "cookSpiceBoostSuccessRate") && + success_rate > threshold) { + threshold = success_rate; + } + } + + if (num_ingredients >= 1) + threshold += mIngredientNumSuccessRates[num_ingredients - 1]; + + if ((s32)sead::GlobalRandom::instance()->getU32(100) >= threshold) + return; + } + cookHandleCrit(ingredients, cook_item); +} + +void CookingMgr::cookHandleMonsterExtract([[maybe_unused]] const IngredientArray& ingredients, + CookItem& cook_item) const { + // Monster Extract found; calculate boosts. + + s32 effect_min = 0; + s32 effect_max = 4; + + if (cook_item.life_recover <= 0.0f || cook_item.effect_id == CookEffectId::LifeMaxUp) + effect_min = 2; + + if (cook_item.effect_id == CookEffectId::None) + effect_max = 2; + + switch (sead::GlobalRandom::instance()->getS32Range(effect_min, effect_max)) { + case 0: + cook_item.life_recover += (f32)getCookingEffectEntry(CookEffectId::LifeRecover).ssa; + break; + case 1: + cook_item.life_recover = (f32)getCookingEffectEntry(CookEffectId::LifeRecover).min; + break; + case 2: + if (cook_item.effect_id != CookEffectId::None) { + if (cook_item.vitality_boost > 0.0f && cook_item.vitality_boost < 1.0f) { + cook_item.vitality_boost = 1.0; + } + cook_item.vitality_boost = (f32)((s32)cook_item.vitality_boost + + getCookingEffectEntry(cook_item.effect_id).ssa); + } + break; + case 3: + if (cook_item.effect_id != CookEffectId::None) { + cook_item.vitality_boost = (f32)getCookingEffectEntry(cook_item.effect_id).min; + } + break; + default: + break; + } + + // Effect time + if (cook_item.effect_time >= 1) { + const u32 roll = sead::GlobalRandom::instance()->getU32(3); + + if (roll == 0) + // 1 minute + cook_item.effect_time = 60; + if (roll == 1) + // 10 minutes + cook_item.effect_time = 600; + if (roll == 2) + // 30 minutes + cook_item.effect_time = 1800; + } +} + +// NON_MATCHING +void CookingMgr::cookHandleCrit([[maybe_unused]] const IngredientArray& ingredients, + CookItem& cook_item) const { + enum Bonus { + LifeBonus = 0, + VitalityBonus = 1, + TimeBonus = 2, + }; + + Bonus bonus = LifeBonus; + + cook_item.is_crit = true; + + const auto& life_entry = getCookingEffectEntry(CookEffectId::LifeRecover); + + if (cook_item.effect_id != CookEffectId::None) { + const f32 life_recover = cook_item.life_recover; + const s32 vitality_bonus = sead::Mathi::clampMin((s32)cook_item.vitality_boost, 1); + + const f32 life_recover_max = (f32)life_entry.max; + const s32 vitality_bonus_max = getCookingEffectEntry(cook_item.effect_id).max; + + const bool life_recover_maxed = life_recover >= life_recover_max; + const bool vitality_bonus_maxed = vitality_bonus >= vitality_bonus_max; + + switch (cook_item.effect_id) { + case CookEffectId::LifeMaxUp: + bonus = VitalityBonus; + break; + + case CookEffectId::GutsRecover: + case CookEffectId::ExGutsMaxUp: + if (vitality_bonus_maxed) + bonus = LifeBonus; + else if (life_recover_maxed) + bonus = VitalityBonus; + else + bonus = sead::GlobalRandom::instance()->getBool() ? VitalityBonus : LifeBonus; + break; + + default: + if (vitality_bonus_maxed) { + if (life_recover_maxed) { + bonus = TimeBonus; + } else { + bonus = sead::GlobalRandom::instance()->getBool() ? TimeBonus : LifeBonus; + } + } else { + if (life_recover_maxed) { + bonus = sead::GlobalRandom::instance()->getBool() ? TimeBonus : VitalityBonus; + } else { + bonus = (Bonus)sead::GlobalRandom::instance()->getU32(3); + } + } + break; + } + } + + switch (bonus) { + case VitalityBonus: + if (cook_item.effect_id != CookEffectId::None) { + if (cook_item.vitality_boost > 0.0f && cook_item.vitality_boost < 1.0f) + cook_item.vitality_boost = 1.0f; + cook_item.vitality_boost = (f32)((int)cook_item.vitality_boost + + getCookingEffectEntry(cook_item.effect_id).ssa); + } + break; + case TimeBonus: + cook_item.effect_time += mCritEffectTime; + break; + case LifeBonus: + cook_item.life_recover += (f32)life_entry.ssa; + break; + } +} + +void CookingMgr::cookCalcSpiceBoost(const IngredientArray& ingredients, CookItem& cook_item) const { + using namespace ksys::act; + + int int_val; + + for (int i = 0; i < NumIngredientsMax; i++) { + if (!ingredients[i].arg) + continue; + + if (InfoData::instance()->hasTag(ingredients[i].actor_data, tags::CookEnemy) || + !InfoData::instance()->hasTag(ingredients[i].actor_data, tags::CookSpice)) { + continue; + } + + if (ingredients[i].actor_data.tryGetIntByKey(&int_val, "cookSpiceBoostHitPointRecover") && + int_val > 0) { + cook_item.life_recover += (f32)int_val; + } + + if (ingredients[i].actor_data.tryGetIntByKey(&int_val, "cookSpiceBoostEffectiveTime") && + int_val > 0) { + cook_item.effect_time += int_val; + } + + // The following loops are buggy, but will never be run, as their config values are left 0. + + if (ingredients[i].actor_data.tryGetIntByKey(&int_val, "cookSpiceBoostMaxHeartLevel") && + int_val > 0) { + // i < 1 check needs to come after others. + for ([[maybe_unused]] int _ = 0; i < 1; i++) { + if (cook_item.effect_id == CookEffectId::LifeMaxUp) { + cook_item.vitality_boost += (f32)int_val; + } + } + } + + if (ingredients[i].actor_data.tryGetIntByKey(&int_val, "cookSpiceBoostStaminaLevel") && + int_val > 0) { + // i < 1 check needs to come after others. + for ([[maybe_unused]] int _ = 0; i < 1; i++) { + if (cook_item.effect_id == CookEffectId::GutsRecover || + cook_item.effect_id == CookEffectId::ExGutsMaxUp) { + cook_item.vitality_boost += (f32)int_val; + } + } + } + } +} + +void CookingMgr::cookCalcItemPrice(const IngredientArray& ingredients, CookItem& cook_item) const { + cook_item.sell_price = 0; + + if (mFairyTonicName == cook_item.actor_name) { + // Fairy Tonic is sold for 2 rupees. + cook_item.sell_price = 2; + return; + } + + s32 int_val = 0; + s32 max_price = 0; + s32 mult_idx = 0; + + for (int i = 0; i < NumIngredientsMax; ++i) { + const auto& ingredient = ingredients[i]; + const auto& actor_data = ingredient.actor_data; + + if (!ingredient.arg) + break; + + if (ksys::act::InfoData::instance()->hasTag(actor_data, ksys::act::tags::CookLowPrice)) { + // This ingredient is only worth 1 rupee. + mult_idx += ingredient.arg->count; + cook_item.sell_price += ingredient.arg->count; + max_price += ingredient.arg->count; + } else { + if (actor_data.tryGetIntByKey(&int_val, "itemSellingPrice")) { + mult_idx += ingredient.arg->count; + cook_item.sell_price += int_val * ingredient.arg->count; + } + if (actor_data.tryGetIntByKey(&int_val, "itemBuyingPrice")) { + max_price += int_val * ingredient.arg->count; + } + } + } + + if (mult_idx >= 1) { + cook_item.sell_price = + (s32)(mIngredientNumMultipliers[mult_idx - 1] * (f32)cook_item.sell_price); + } + + if (cook_item.sell_price >= 1) { + // Round up to the nearest power of 10 + if (cook_item.sell_price % 10 != 0) { + cook_item.sell_price = cook_item.sell_price + 10 - cook_item.sell_price % 10; + } + } + + // clamp and clampMin don't work here. + cook_item.sell_price = sead::Mathi::min(max_price, cook_item.sell_price); + cook_item.sell_price = sead::Mathi::max(cook_item.sell_price, 2); +} + +void CookingMgr::cookCalcIngredientsBoost(const IngredientArray& ingredients, + CookItem& cook_item) const { + const bool is_medicine = isMedicine(cook_item); + const bool is_not_fairy_tonic = mFairyTonicName != cook_item.actor_name; + + sead::SafeArray effect_counts{}; + sead::SafeArray cure_levels{}; + + s32 stamina_boost = 0; + s32 life_boost = 0; + s32 time_boost = 0; + s32 total_count = 0; + s32 life_recover = 0; + s32 int_val; + + for (int i = 0; i < NumIngredientsMax; i++) { + if (!ingredients[i].arg) + break; + + const al::ByamlIter& actor_data = ingredients[i].actor_data; + const s32 count = ingredients[i].arg->count; + + total_count += count; + + if (ksys::act::InfoData::instance()->hasTag(actor_data, ksys::act::tags::CookEnemy)) { + if (actor_data.tryGetIntByKey(&int_val, "cookSpiceBoostEffectiveTime") && int_val > 0) { + time_boost += int_val * count; + } + + if (actor_data.tryGetIntByKey(&int_val, "cookSpiceBoostMaxHeartLevel") && int_val > 0) { + life_boost += int_val * count; + } + + if (actor_data.tryGetIntByKey(&int_val, "cookSpiceBoostStaminaLevel") && int_val > 0) { + stamina_boost += int_val * count; + } + } else { + if (actor_data.tryGetIntByKey(&int_val, "cureItemHitPointRecover") && int_val > 0) { + life_recover += int_val * count; + } + + if (actor_data.tryGetIntByKey(&int_val, "cureItemEffectLevel") && int_val > 0) { + const char* string_val = nullptr; + if (actor_data.tryGetStringByKey(&string_val, "cureItemEffectType")) { + const auto effect_id = getCookEffectIdByName(string_val); + if (effect_id != CookEffectId::None) { + effect_counts[(int)effect_id] += count; + cure_levels[(int)effect_id] += int_val * count; + } + } + } + } + } + + bool effect_found = false; + for (int i = 0; i < NumEffectSlots; i++) { + const s32 effect_count = effect_counts[i]; + if (effect_count > 0) { + if (effect_found) { + // Finding a second effect makes them cancel out. + effect_found = false; + cook_item.vitality_boost = 0.0f; + cook_item.effect_id = CookEffectId::None; + cook_item.effect_time = 0; + break; + } + + const auto& entry = mCookingEffectEntries[i]; + + cook_item.vitality_boost = (f32)cure_levels[i] * entry.multiplier; + + const auto effect_id = (CookEffectId)i; + cook_item.effect_id = effect_id; + + const s32 boost_time = entry.boost_time; + if (boost_time > 0) + cook_item.effect_time = time_boost + 30 * total_count + boost_time * effect_count; + + if (effect_id == CookEffectId::LifeMaxUp) { + cook_item.vitality_boost += (f32)life_boost; + } else if (effect_id == CookEffectId::GutsRecover || + effect_id == CookEffectId::ExGutsMaxUp) { + cook_item.vitality_boost += (f32)stamina_boost; + } + + effect_found = true; + } + } + + if (!is_not_fairy_tonic && effect_found) { + effect_found = false; + cook_item.vitality_boost = 0.0f; + cook_item.effect_id = CookEffectId::None; + cook_item.effect_time = 0; + } + + if (is_medicine && !effect_found) { + cook_item.actor_name = mFailActorName; + } + + if (isCookFailure(cook_item)) { + cook_item.life_recover = (f32)life_recover * mFailActorLifeRecoverMultiplier; + } else if (effect_found) { + cook_item.life_recover = (f32)life_recover * mLifeRecoverMultiplier; + } else { + cook_item.life_recover = + (f32)life_recover * getCookingEffectEntry(CookEffectId::LifeRecover).multiplier; + } + + if (cook_item.effect_id != CookEffectId::None) { + const s32 max = getCookingEffectEntry(cook_item.effect_id).max; + if (cook_item.vitality_boost > (f32)max) + cook_item.vitality_boost = (f32)max; + } +} + +bool CookingMgr::findIngredientByName(CookingMgr::IngredientArray& ingredients, u32 name_hash, + int num_ingredients) const { + for (int ingredient_idx = 0; ingredient_idx < num_ingredients; ingredient_idx++) { + Ingredient& ingredient = ingredients[ingredient_idx]; + if (!ingredient.used_in_recipe && ingredient.name_hash == name_hash) { + ingredient.used_in_recipe = true; + return true; + } + } + return false; +} + +bool CookingMgr::findIngredientByTag(CookingMgr::IngredientArray& ingredients, u32 tag_hash, + int num_ingredients) const { + for (int ingredient_idx = 0; ingredient_idx < num_ingredients; ingredient_idx++) { + Ingredient& ingredient = ingredients[ingredient_idx]; + if (!ingredient.used_in_recipe && + ksys::act::InfoData::instance()->hasTag(ingredient.actor_data, tag_hash)) { + ingredient.used_in_recipe = true; + return true; + } + } + return false; +} + +bool CookingMgr::isMedicine(const CookItem& cook_item) const { + return !cook_item.actor_name.isEmpty() && + ksys::act::InfoData::instance()->hasTag(cook_item.actor_name.cstr(), + ksys::act::tags::CookEMedicine); +} + +bool CookingMgr::isCookFailure(const CookItem& cook_item) const { + return !cook_item.actor_name.isEmpty() && + ksys::act::InfoData::instance()->hasTag(cook_item.actor_name.cstr(), + ksys::act::tags::CookFailure); +} + +bool CookingMgr::hasMonsterExtract(const CookingMgr::IngredientArray& ingredients) const { + for (int i = 0; i < NumIngredientsMax; i++) { + const auto& ingredient = ingredients[i]; + if (ingredient.arg && ingredient.arg->name == mMonsterExtractName) { + return true; + } + } + return false; +} + +CookEffectId CookingMgr::getCookEffectId(u32 name_hash) const { + CookEffectId entry_idx; + + if (sCrc32Constants.crc32_life_recover == name_hash) + entry_idx = CookEffectId::LifeRecover; + else if (sCrc32Constants.crc32_guts_performance == name_hash) + entry_idx = CookEffectId::ExGutsMaxUp; + else if (sCrc32Constants.crc32_stamina_recover == name_hash) + entry_idx = CookEffectId::GutsRecover; + else if (sCrc32Constants.crc32_life_max_up == name_hash) + entry_idx = CookEffectId::LifeMaxUp; + else if (sCrc32Constants.crc32_resist_hot == name_hash) + entry_idx = CookEffectId::ResistHot; + else if (sCrc32Constants.crc32_resist_cold == name_hash) + entry_idx = CookEffectId::ResistCold; + else if (sCrc32Constants.crc32_resist_electric == name_hash) + entry_idx = CookEffectId::ResistElectric; + else if (sCrc32Constants.crc32_all_speed == name_hash) + entry_idx = CookEffectId::MovingSpeed; + else if (sCrc32Constants.crc32_attack_up == name_hash) + entry_idx = CookEffectId::AttackUp; + else if (sCrc32Constants.crc32_defense_up == name_hash) + entry_idx = CookEffectId::DefenseUp; + else if (sCrc32Constants.crc32_quietness == name_hash) + entry_idx = CookEffectId::Quietness; + else if (sCrc32Constants.crc32_fireproof == name_hash) + entry_idx = CookEffectId::Fireproof; + else + entry_idx = CookEffectId::None; + return entry_idx; +} + +CookEffectId CookingMgr::getCookEffectIdByName(const sead::SafeString& effect_name) const { + const auto name_hash = sead::HashCRC32::calcStringHash(effect_name); + return getCookEffectIdFromTreeMap(name_hash); +} + +CookEffectId CookingMgr::getCookEffectIdFromTreeMap(const u32 name_hash) const { + if (const auto* node = mCookingEffectNameIdMap.find(name_hash)) { + return node->value(); + } + return CookEffectId::None; +} + +void CookingMgr::init(sead::Heap* heap) { + ksys::res::LoadRequest req; + + req.mRequester = "CookingMgr"; + req._22 = false; + + sead::FixedSafeString<0x80> path; + path.format("Cooking/CookData.byml"); + + auto* res = sead::DynamicCast(mResHandle.load(path, &req)); + if (!res) + return; + + mConfig = mConfig ? new (mConfig) al::ByamlIter(res->getRawData()) : + new (heap) al::ByamlIter(res->getRawData()); + + mCookingEffectNameIdMap.clear(); + + for (int effect_idx = 0; effect_idx < NumEffects; effect_idx++) { + auto& effect = sCookingEffects[effect_idx]; + const u32 name_hash = sead::HashCRC32::calcStringHash(effect.name); + mCookingEffectNameIdMap.insert(name_hash, effect.effect_id); + } + + for (int i = 0; i < NumEffectSlots; i++) { + mCookingEffectEntries[i] = CookingEffectEntry{}; + mCookingEffectEntries[i].ssa = 0; + } + + for (int i = 0; i < NumIngredientsMax; i++) { + mIngredientNumMultipliers[i] = 1.0f; + } + + // Must be separate from previous loop. + for (int i = 0; i < NumIngredientsMax; i++) { + mIngredientNumSuccessRates[i] = 5 * i; + } + + mFairyTonicName = "Item_Cook_C_16"; + mFairyTonicNameHash = sead::HashCRC32::calcStringHash(mFairyTonicName); + + mFailActorName = "Item_Cook_O_01"; + mFailActorNameHash = sead::HashCRC32::calcStringHash(mFailActorName); + + mMonsterExtractName = "Item_Material_08"; + mMonsterExtractNameHash = sead::HashCRC32::calcStringHash(mMonsterExtractName); + + mLifeRecoverMultiplier = 1.0; + mStoneFoodActorLifeRecover = 1; + mCritEffectTime = 300; + mFailActorLifeRecoverMultiplier = 1.0; + mFailActorLifeRecover = 4; + + al::ByamlIter iter; + al::ByamlIter cei_iter; + al::ByamlIter entry_iter; + + const char* string_val = nullptr; + int int_val; + u32 uint_val; + float float_val; + + if (mConfig->tryGetIterByKey(&iter, "System")) { + if (iter.tryGetStringByKey(&string_val, "FA")) { + mFailActorName = string_val; + mFailActorNameHash = sead::HashCRC32::calcStringHash(mFailActorName); + } + if (iter.tryGetStringByKey(&string_val, "FCA")) { + mFairyTonicName = string_val; + mFairyTonicNameHash = sead::HashCRC32::calcStringHash(mFairyTonicName); + } + if (iter.tryGetStringByKey(&string_val, "MEA")) { + mMonsterExtractName = string_val; + mMonsterExtractNameHash = sead::HashCRC32::calcStringHash(mMonsterExtractName); + } + + if (iter.tryGetFloatByKey(&float_val, "LRMR") && float_val >= 0) + mLifeRecoverMultiplier = float_val; + if (iter.tryGetFloatByKey(&float_val, "FALRMR") && float_val >= 0) + mFailActorLifeRecoverMultiplier = float_val; + if (iter.tryGetIntByKey(&int_val, "FALR") && int_val >= 0) + mFailActorLifeRecover = int_val; + if (iter.tryGetIntByKey(&int_val, "SFALR") && int_val >= 0) + mStoneFoodActorLifeRecover = int_val; + if (iter.tryGetIntByKey(&int_val, "SSAET") && int_val >= 0) + mCritEffectTime = int_val; + + if (iter.tryGetIterByKey(&cei_iter, "CEI")) { + const int size = cei_iter.getSize(); + + for (int i = 0; i < size; i++) { + if (cei_iter.tryGetIterByIndex(&entry_iter, i) && + entry_iter.tryGetUIntByKey(&uint_val, "T")) { + const u32 name_hash = uint_val; + + const CookEffectId entry_idx = getCookEffectId(name_hash); + + if (entry_idx == CookEffectId::None) + continue; + + if (entry_iter.tryGetIntByKey(&int_val, "BT")) + getCookingEffectEntry(entry_idx).boost_time = int_val; + + if (entry_iter.tryGetIntByKey(&int_val, "Ma")) + getCookingEffectEntry(entry_idx).max = int_val; + + if (entry_iter.tryGetIntByKey(&int_val, "Mi")) + getCookingEffectEntry(entry_idx).min = int_val; + + if (entry_iter.tryGetFloatByKey(&float_val, "MR")) + getCookingEffectEntry(entry_idx).multiplier = float_val; + + if (entry_iter.tryGetIntByKey(&int_val, "SSA")) + getCookingEffectEntry(entry_idx).ssa = int_val; + } + } + } + + if (iter.tryGetIterByKey(&cei_iter, "NMMR")) { + const int size = cei_iter.getSize(); + + for (int i = 0; i < size; i++) { + if (cei_iter.tryGetFloatByIndex(&float_val, i) && i < NumIngredientsMax) { + mIngredientNumMultipliers[i] = sead::Mathf::clamp(float_val, 0.0f, 5.0f); + } + } + } + + if (iter.tryGetIterByKey(&cei_iter, "NMSSR")) { + const int size = cei_iter.getSize(); + + for (int i = 0; i < size; i++) { + if (cei_iter.tryGetIntByIndex(&int_val, i) && i < NumIngredientsMax) { + mIngredientNumSuccessRates[i] = sead::Mathi::clamp(int_val, -100, 100); + } + } + } + } +} + +// NON_MATCHING +bool CookingMgr::cook(const CookArg& arg, CookItem& cook_item, + const CookingMgr::BoostArg& boost_arg) { + ksys::act::InfoData* actor_info_data = ksys::act::InfoData::instance(); + + al::ByamlIter recipes_iter; + sead::SafeArray ingredients; + + int num_ingredients = 0; + if (mConfig && actor_info_data) { + for (int i = 0; i < NumIngredientsMax; i++) { + const auto& cook_ingredient = arg.ingredients[i]; + if (!cook_ingredient.name.isEmpty()) { + const u32 name_hash = sead::HashCRC32::calcStringHash(cook_ingredient.name); + auto& ingredient = ingredients[num_ingredients]; + if (actor_info_data->getActorIter(&ingredient.actor_data, name_hash)) { + ingredient.name_hash = name_hash; + ingredient.arg = &cook_ingredient; + num_ingredients++; + } + } + } + } + + if (!mConfig || !actor_info_data || num_ingredients == 0) { + // Unused label: + // COOK_FAILURE_FOR_MISSING_CONFIG: + cookFailForMissingConfig(cook_item, mFailActorName); + return false; + } + + const Ingredient* single_ingredient = nullptr; + bool multiple_non_spice_ingredients = false; + + if (num_ingredients > 1) { + if (mConfig->tryGetIterByKey(&recipes_iter, "Recipes")) { + const s32 num_recipes = recipes_iter.getSize(); + const char* string_val = nullptr; + u32 uint_val = 0; + al::ByamlIter recipe_iter; + al::ByamlIter hash_iter; + al::ByamlIter actors_iter; + al::ByamlIter tags_iter; + + if (num_recipes > 0) { + for (int recipe_idx = 0; recipe_idx < num_recipes; recipe_idx++) { + if (!recipes_iter.tryGetIterByIndex(&recipe_iter, recipe_idx)) + continue; + + recipe_iter.tryGetStringByKey(&string_val, "Result"); + recipe_iter.tryGetUIntByKey(&uint_val, "Recipe"); + const s32 num_actors = recipe_iter.tryGetIterByKey(&actors_iter, "Actors") ? + actors_iter.getSize() : + 0; + const s32 num_tags = + recipe_iter.tryGetIterByKey(&tags_iter, "Tags") ? tags_iter.getSize() : 0; + + if (num_actors + num_tags > num_ingredients || + (num_actors == 0 && num_tags == 0)) + continue; + + ingredients[0].used_in_recipe = false; + ingredients[1].used_in_recipe = false; + ingredients[2].used_in_recipe = false; + ingredients[3].used_in_recipe = false; + ingredients[4].used_in_recipe = false; + + // Each recipe entry can have a list of sets of Actors, and a list of sets of + // Tags. An ingredient must be found for each set of Actors and Tags. + + if (num_actors > 0) { + bool any_actors_missed = false; + + for (int actor_idx = 0; actor_idx < num_actors; actor_idx++) { + if (actors_iter.tryGetIterByIndex(&hash_iter, actor_idx)) { + const s32 num_hashes = hash_iter.getSize(); + if (num_hashes < 1) + continue; + bool found = false; + for (int hash_idx = 0; hash_idx < num_hashes; hash_idx++) { + u32 hash_val; + if (hash_iter.tryGetUIntByIndex(&hash_val, hash_idx)) { + // Any actor in this list will work. + found = findIngredientByName(ingredients, hash_val, + num_ingredients); + if (found) + break; + } + } + if (!found) { + any_actors_missed = true; + break; + } + } + if (any_actors_missed) + break; + } + + if (any_actors_missed) + continue; + } + + if (num_tags > 0) { + bool any_tags_missed = false; + + for (int tag_idx = 0; tag_idx < num_tags; tag_idx++) { + if (tags_iter.tryGetIterByIndex(&hash_iter, tag_idx)) { + const s32 num_hashes = hash_iter.getSize(); + bool found = false; + for (int hash_idx = 0; hash_idx < num_hashes; hash_idx++) { + u32 hash_val; + if (hash_iter.tryGetUIntByIndex(&hash_val, hash_idx)) { + // Any tag in this list will work. + found = findIngredientByTag(ingredients, hash_val, + num_ingredients); + if (found) + break; + } + } + if (!found) { + any_tags_missed = true; + break; + } + } + if (any_tags_missed) + break; + } + + if (any_tags_missed) + continue; + } + + al::ByamlIter actor_iter; + if (!actor_info_data->getActorIter(&actor_iter, uint_val)) + continue; + + actor_iter.tryGetStringByKey(&string_val, "name"); + cook_item.actor_name = string_val; + + cookCalcIngredientsBoost(ingredients, cook_item); + + if (isCookFailure(cook_item)) { + goto COOK_FAILURE; + } + + cookCalcCritBoost(ingredients, cook_item, &boost_arg); + cookCalcSpiceBoost(ingredients, cook_item); + + cookCalcRecipeBoost(recipe_iter, cook_item); + + cookAdjustItem(cook_item); + + cookCalcItemPrice(ingredients, cook_item); + return true; + } + } + } + + single_ingredient = nullptr; + for (int ingredient_idx = 0; ingredient_idx < num_ingredients; ingredient_idx++) { + const auto& ingredient = ingredients[ingredient_idx]; + if (actor_info_data->hasTag(ingredient.actor_data, ksys::act::tags::CookSpice)) + continue; + + if (single_ingredient) { + multiple_non_spice_ingredients = true; + break; + } + + single_ingredient = &ingredient; + } + } else { + single_ingredient = &ingredients[0]; + } + + if (single_ingredient && !multiple_non_spice_ingredients) { + if (mConfig->tryGetIterByKey(&recipes_iter, "SingleRecipes")) { + const s32 num_recipes = recipes_iter.getSize(); + const char* string_val = nullptr; + s32 int_val; + u32 uint_val = 0; + al::ByamlIter recipe_iter; + al::ByamlIter actors_iter; + al::ByamlIter tags_iter; + + if (num_recipes > 0) { + for (int recipe_idx = 0; recipe_idx < num_recipes; recipe_idx++) { + if (!recipes_iter.tryGetIterByIndex(&recipe_iter, recipe_idx)) + continue; + + recipe_iter.tryGetStringByKey(&string_val, "Result"); + recipe_iter.tryGetUIntByKey(&uint_val, "Recipe"); + + if (!recipe_iter.tryGetIntByKey(&int_val, "Num") || num_ingredients < int_val) + continue; + + const s32 num_actors = recipe_iter.tryGetIterByKey(&actors_iter, "Actors") ? + actors_iter.getSize() : + 0; + const s32 num_tags = + recipe_iter.tryGetIterByKey(&tags_iter, "Tags") ? tags_iter.getSize() : 0; + + if (num_actors + num_tags > num_ingredients || + (num_actors == 0 && num_tags == 0)) + continue; + + if (num_actors > 0) { + bool found = false; + for (int hash_idx = 0; hash_idx < num_actors; hash_idx++) { + u32 hash_val; + if (actors_iter.tryGetUIntByIndex(&hash_val, hash_idx) && + single_ingredient->name_hash == hash_val) { + found = true; + break; + } + } + if (!found) + continue; + } + + if (num_tags > 0) { + bool found = false; + for (int hash_idx = 0; hash_idx < num_tags; hash_idx++) { + u32 hash_val; + if (tags_iter.tryGetUIntByIndex(&hash_val, hash_idx) && + actor_info_data->hasTag(single_ingredient->actor_data, hash_val)) { + found = true; + break; + } + } + if (!found) + continue; + } + + al::ByamlIter actorIter; + if (!actor_info_data->getActorIter(&actorIter, uint_val)) + continue; + + actorIter.tryGetStringByKey(&string_val, "name"); + cook_item.actor_name = string_val; + + cookCalcIngredientsBoost(ingredients, cook_item); + + if (isCookFailure(cook_item)) { + goto COOK_FAILURE; + } + + cookCalcCritBoost(ingredients, cook_item, &boost_arg); + cookCalcSpiceBoost(ingredients, cook_item); + + cookCalcRecipeBoost(recipe_iter, cook_item); + + cookAdjustItem(cook_item); + + cookCalcItemPrice(ingredients, cook_item); + return true; + } + } + } + } + + cook_item.actor_name = mFailActorName; + cookCalcIngredientsBoost(ingredients, cook_item); + +COOK_FAILURE: + cookFail(cook_item); + return true; +} + +void CookingMgr::cookCalcRecipeBoost(const al::ByamlIter& recipe_iter, CookItem& cook_item) const { + int int_val; + + if (recipe_iter.tryGetIntByKey(&int_val, "HB")) + cook_item.life_recover += (f32)int_val; + + if (recipe_iter.tryGetIntByKey(&int_val, "TB")) { + if (cook_item.effect_time > 0) { + cook_item.effect_time += int_val; + } + } +} + +void CookingMgr::cookAdjustItem(CookItem& cook_item) const { + cook_item.life_recover = (f32)(s32)cook_item.life_recover; + + const f32 life_recover_max = (f32)getCookingEffectEntry(CookEffectId::LifeRecover).max; + if (cook_item.life_recover > life_recover_max) + cook_item.life_recover = life_recover_max; + if (cook_item.life_recover < 0.0f) + cook_item.life_recover = 0.0f; + + if (cook_item.effect_id == CookEffectId::None) { + if (cook_item.life_recover == 0.0f) + cook_item.life_recover = 1.0f; + } else { + if (cook_item.vitality_boost > 0.0f && cook_item.vitality_boost < 1.0f) + cook_item.vitality_boost = 1.0f; + + s32 vitality_boost = (s32)cook_item.vitality_boost; + + const s32 vitality_boost_max = getCookingEffectEntry(cook_item.effect_id).max; + if (vitality_boost > vitality_boost_max) + vitality_boost = vitality_boost_max; + + if (cook_item.effect_id == CookEffectId::GutsRecover) + vitality_boost = vitality_boost * 200; + + f32 vitality_boost_f = cook_item.vitality_boost = (f32)vitality_boost; + + if (cook_item.effect_id == CookEffectId::LifeMaxUp) { + if ((s32)vitality_boost_f % 4 != 0) { + // Round up to whole heart. + vitality_boost_f = (f32)(((s32)vitality_boost_f + 4) & ~3u); + cook_item.vitality_boost = vitality_boost_f; + } + + if (vitality_boost_f < 4.0f) { + vitality_boost_f = 4.0f; + cook_item.vitality_boost = 4.0f; + } + + cook_item.life_recover = vitality_boost_f; + } + } + + cook_item.effect_time = sead::Mathi::clamp(cook_item.effect_time, 0, 1800); +} + +void CookingMgr::resetArgCookData(CookArg& arg, + const sead::Buffer>& ingredient_names, + int num_ingredients, CookItem& cook_item) const { + for (int i = 0; i < NumIngredientsMax; i++) { + arg.ingredients[i].name = ""; + arg.ingredients[i].count = 0; + } + + arg.ingredients[0].count = 1; + arg.ingredients[0].name = ingredient_names[0]; + + for (int i = 1; i < num_ingredients; i++) { + for (int j = 0; j < NumIngredientsMax; j++) { + auto& ingredient = arg.ingredients[j]; + if (ingredient.name == ingredient_names[i]) { + ingredient.count++; + break; + } + if (ingredient.name.isEmpty()) { + ingredient.count = 1; + ingredient.name = ingredient_names[i]; + break; + } + } + } + + cook_item.reset(); + + for (int i = 0; i < NumIngredientsMax && i < num_ingredients; i++) { + cook_item.ingredients[i] = ingredient_names[i]; + } +} + +void CookingMgr::prepareCookArg( + CookArg& arg, const sead::SafeArray, NumIngredientsMax>& item_names, + int num_items, CookItem& cook_item) const { + for (int i = 0; i < NumIngredientsMax; i++) { + arg.ingredients[i].name = ""; + arg.ingredients[i].count = 0; + } + + arg.ingredients[0].count = 1; + arg.ingredients[0].name = item_names[0]; + + for (int i = 1; i < num_items; i++) { + const auto& item_name = item_names[i]; + for (int j = 0; j < NumIngredientsMax; j++) { + auto& ingredient = arg.ingredients[j]; + if (ingredient.name == item_name) { + ingredient.count++; + break; + } + if (ingredient.name.isEmpty()) { + ingredient.count = 1; + ingredient.name = item_name; + break; + } + } + } + + cook_item.reset(); + + for (int i = 0; i < NumIngredientsMax && i < num_items; i++) { + cook_item.ingredients[i] = item_names[i]; + } +} + +bool CookingMgr::cookWithItems(const sead::SafeString& item1, const sead::SafeString& item2, + const sead::SafeString& item3, const sead::SafeString& item4, + const sead::SafeString& item5, CookItem& cook_item, + const CookingMgr::BoostArg& boost_arg) { + CookArg arg; + sead::SafeArray, NumIngredientsMax> item_names; + + int num_items = 0; + + if (!item1.isEmpty()) { + item_names[num_items].copy(item1); + num_items++; + } + + if (!item2.isEmpty()) { + item_names[num_items].copy(item2); + num_items++; + } + + if (!item3.isEmpty()) { + item_names[num_items].copy(item3); + num_items++; + } + + if (!item4.isEmpty()) { + item_names[num_items].copy(item4); + num_items++; + } + + if (!item5.isEmpty()) { + item_names[num_items].copy(item5); + num_items++; + } + + if (num_items > 0) { + prepareCookArg(arg, item_names, num_items, cook_item); + if (cook(arg, cook_item, boost_arg)) + return true; + } + return false; +} + +void CookingMgr::setCookItem(const CookItem& from) { + from.copy(mCookItem); +} + +void CookingMgr::resetCookItem() { + mCookItem.reset(); +} + +void CookingMgr::getCookItem(CookItem& to) const { + mCookItem.copy(to); +} + +} // namespace uking diff --git a/src/Game/Cooking/cookManager.h b/src/Game/Cooking/cookManager.h new file mode 100644 index 00000000..d90a95ab --- /dev/null +++ b/src/Game/Cooking/cookManager.h @@ -0,0 +1,181 @@ +#pragma once + +#include +#include +#include +#include +#include "KingSystem/Resource/resHandle.h" +#include "KingSystem/Utils/Byaml/Byaml.h" +#include "KingSystem/Utils/Types.h" + +namespace uking { + +struct CookArg; +struct CookIngredient; + +enum class CookEffectId : s32 { + None = -1, + LifeRecover = 1, + LifeMaxUp = 2, + ResistHot = 4, + ResistCold = 5, + ResistElectric = 6, + AttackUp = 10, + DefenseUp = 11, + Quietness = 12, + MovingSpeed = 13, + GutsRecover = 14, + ExGutsMaxUp = 15, + Fireproof = 16, +}; + +struct CookItem { + CookItem(); + + void reset(); + void copy(CookItem& to) const; + + sead::FixedSafeString<64> actor_name{""}; + sead::SafeArray, 5> ingredients; + f32 life_recover{}; + s32 effect_time{}; + s32 sell_price{}; + CookEffectId effect_id = CookEffectId::None; + /// Can refer to life bonus, stamina recover, or stamina bonus, depending on `effect_id`. + f32 vitality_boost{}; + bool is_crit{}; +}; +KSYS_CHECK_SIZE_NX150(CookItem, 0x228); + +class CookingMgr { + SEAD_SINGLETON_DISPOSER(CookingMgr) +public: + static constexpr s32 NumIngredientsMax = 5; + static constexpr s32 NumEffects = 13; + static constexpr s32 NumEffectSlots = 17; + + struct Ingredient { + u32 name_hash{}; + const CookIngredient* arg{}; + bool used_in_recipe{}; + al::ByamlIter actor_data{}; + }; + + using IngredientArray = sead::SafeArray; + + struct BoostArg { + bool always_boost = false; + bool enable_random_boost = false; + }; + + CookingMgr(); + ~CookingMgr(); + +private: + struct CookingEffectEntry { + int boost_time = 0; + int max = 0; + int min = 0; + float multiplier = 1.0f; + int ssa = 1; + }; + + void cookFail(CookItem& cook_item); + void cookFailForMissingConfig(CookItem& cook_item, const sead::SafeString& actor_name); + void cookCalcCritBoost(const IngredientArray& ingredients, CookItem& cook_item, + const BoostArg* boost_arg) const; + void cookHandleMonsterExtract(const IngredientArray& ingredients, CookItem& cook_item) const; + void cookHandleCrit(const IngredientArray& ingredients, CookItem& cook_item) const; + void cookCalcSpiceBoost(const IngredientArray& ingredients, CookItem& cook_item) const; + void cookCalcItemPrice(const IngredientArray& ingredients, CookItem& cook_item) const; + void cookCalcIngredientsBoost(const IngredientArray& ingredients, CookItem& cook_item) const; + void cookCalcRecipeBoost(const al::ByamlIter& recipe_iter, CookItem& cook_item) const; + + void cookAdjustItem(CookItem& cook_item) const; + + bool findIngredientByName(IngredientArray& ingredients, u32 name_hash, + int num_ingredients) const; + bool findIngredientByTag(IngredientArray& ingredients, u32 tag_hash, int num_ingredients) const; + + bool isCookFailure(const CookItem& cook_item) const; + bool isMedicine(const CookItem& cook_item) const; + bool hasMonsterExtract(const IngredientArray& ingredients) const; + + CookingEffectEntry& getCookingEffectEntry(CookEffectId id) { + return mCookingEffectEntries[(int)id]; + } + const CookingEffectEntry& getCookingEffectEntry(CookEffectId id) const { + return mCookingEffectEntries[(int)id]; + } + + CookEffectId getCookEffectId(u32 name_hash) const; + CookEffectId getCookEffectIdFromTreeMap(u32 name_hash) const; + +public: + CookEffectId getCookEffectIdByName(const sead::SafeString& effect_name) const; + + void init(sead::Heap* heap); + + bool cook(const CookArg& arg, CookItem& cook_item, const BoostArg& boost_arg); + + void resetArgCookData(CookArg& arg, + const sead::Buffer>& ingredient_names, + int num_ingredients, CookItem& cook_item) const; + + void + prepareCookArg(CookArg& arg, + const sead::SafeArray, NumIngredientsMax>& item_names, + int num_items, CookItem& cook_item) const; + + bool cookWithItems(const sead::SafeString& item1, const sead::SafeString& item2, + const sead::SafeString& item3, const sead::SafeString& item4, + const sead::SafeString& item5, CookItem& cook_item, + const CookingMgr::BoostArg& boost_arg); + + void setCookItem(const CookItem& from); + void resetCookItem(); + void getCookItem(CookItem& to) const; + +private: + al::ByamlIter* mConfig = nullptr; + + ksys::res::Handle mResHandle; + + sead::FixedSafeString<64> mFailActorName; + sead::FixedSafeString<64> mFairyTonicName; + sead::FixedSafeString<64> mMonsterExtractName; + + u32 mFailActorNameHash = 0; + u32 mFairyTonicNameHash = 0; + u32 mMonsterExtractNameHash = 0; + + f32 mLifeRecoverMultiplier = 1.0f; + f32 mFailActorLifeRecoverMultiplier = 1.0f; + s32 mFailActorLifeRecover = 4; + s32 mStoneFoodActorLifeRecover = 1; + s32 mCritEffectTime = 300; + + sead::SafeArray mCookingEffectEntries; + + sead::SafeArray mIngredientNumMultipliers; + + sead::SafeArray mIngredientNumSuccessRates; + + CookItem mCookItem; + + sead::FixedTreeMap mCookingEffectNameIdMap{}; +}; +KSYS_CHECK_SIZE_NX150(CookingMgr, 0x7D8); + +struct CookIngredient { + sead::FixedSafeString<64> name{""}; + int count{}; +}; +KSYS_CHECK_SIZE_NX150(CookIngredient, 0x60); + +struct CookArg { + sead::SafeArray ingredients; +}; +KSYS_CHECK_SIZE_NX150(CookArg, 0x1E0); + +} // namespace uking diff --git a/src/Game/UI/uiPauseMenuDataMgr.cpp b/src/Game/UI/uiPauseMenuDataMgr.cpp index 40faa561..6416633b 100644 --- a/src/Game/UI/uiPauseMenuDataMgr.cpp +++ b/src/Game/UI/uiPauseMenuDataMgr.cpp @@ -5,6 +5,7 @@ #include #include #include "Game/Actor/actWeapon.h" +#include "Game/Cooking/cookManager.h" #include "Game/DLC/aocManager.h" #include "Game/UI/uiUtils.h" #include "Game/gameItemUtils.h" @@ -17,7 +18,6 @@ #include "KingSystem/ActorSystem/actInfoCommon.h" #include "KingSystem/ActorSystem/actInfoData.h" #include "KingSystem/ActorSystem/actPlayerInfo.h" -#include "KingSystem/Cooking/cookItem.h" #include "KingSystem/GameData/gdtCommonFlagsUtils.h" #include "KingSystem/GameData/gdtSpecialFlags.h" #include "KingSystem/System/PlayReportMgr.h" @@ -1017,30 +1017,30 @@ void PauseMenuDataMgr::saveToGameData(const sead::OffsetList& list) c } } -void PauseMenuDataMgr::cookItemGet(const ksys::CookItem& cook_item) { +void PauseMenuDataMgr::cookItemGet(const uking::CookItem& cook_item) { const auto* info = ksys::act::InfoData::instance(); - if (!info->hasTag(cook_item.name.cstr(), ksys::act::tags::CookResult)) + 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.name); - const auto type = getType(cook_item.name); - addToPouch(cook_item.name, type, lists, 1, false); + 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 ksys::CookItem& cook_item) { +void PauseMenuDataMgr::setCookDataOnLastAddedItem(const uking::CookItem& cook_item) { if (!mLastAddedItem) return; - mLastAddedItem->getCookData().setStaminaRecoverY(cook_item.stamina_recover_y); - mLastAddedItem->getCookData().setStaminaRecoverX(cook_item.stamina_recover_x); - mLastAddedItem->getCookData().setCookEffect1(cook_item.cook_effect_1); - const int y = cook_item.cook_effect_0_y; - const int x = cook_item.cook_effect_0_x; + mLastAddedItem->getCookData().setStaminaRecoverY(cook_item.effect_time); + mLastAddedItem->getCookData().setStaminaRecoverX(cook_item.life_recover); + mLastAddedItem->getCookData().setCookEffect1(cook_item.sell_price); + const int y = cook_item.vitality_boost; + const CookEffectId x = cook_item.effect_id; mLastAddedItem->getCookData().setCookEffect0({float(x), float(y)}); for (s32 i = 0; i < cook_item.ingredients.size(); ++i) mLastAddedItem->setIngredient(i, cook_item.ingredients[i]); diff --git a/src/Game/UI/uiPauseMenuDataMgr.h b/src/Game/UI/uiPauseMenuDataMgr.h index a1b2144e..f05df471 100644 --- a/src/Game/UI/uiPauseMenuDataMgr.h +++ b/src/Game/UI/uiPauseMenuDataMgr.h @@ -27,9 +27,9 @@ enum class WeaponModifier : u32; struct WeaponModifierInfo; } // namespace uking::act -namespace ksys { +namespace uking { struct CookItem; -} +} // namespace uking namespace uking::ui { @@ -262,9 +262,9 @@ public: bool isWeaponSectionFull(const sead::SafeString& get_flag) const; void itemGet(const sead::SafeString& name, int value, const act::WeaponModifierInfo* modifier); - void cookItemGet(const ksys::CookItem& cook_item); + void cookItemGet(const uking::CookItem& cook_item); - void setCookDataOnLastAddedItem(const ksys::CookItem& cook_item); + void setCookDataOnLastAddedItem(const uking::CookItem& cook_item); void autoEquipLastAddedItem(); const sead::SafeString& autoEquip(PouchItem* item, const sead::OffsetList& list); diff --git a/src/KingSystem/CMakeLists.txt b/src/KingSystem/CMakeLists.txt index 550bad89..e70ac77a 100644 --- a/src/KingSystem/CMakeLists.txt +++ b/src/KingSystem/CMakeLists.txt @@ -4,7 +4,6 @@ add_subdirectory(Resource) add_subdirectory(ActorSystem) add_subdirectory(Chemical) -add_subdirectory(Cooking) add_subdirectory(Ecosystem) add_subdirectory(Effect) add_subdirectory(Event) diff --git a/src/KingSystem/Cooking/cookItem.cpp b/src/KingSystem/Cooking/cookItem.cpp deleted file mode 100644 index 192fb7f5..00000000 --- a/src/KingSystem/Cooking/cookItem.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "KingSystem/Cooking/cookItem.h" - -namespace ksys { - -CookItem::CookItem() = default; - -} // namespace ksys diff --git a/src/KingSystem/Cooking/cookItem.h b/src/KingSystem/Cooking/cookItem.h deleted file mode 100644 index bf168545..00000000 --- a/src/KingSystem/Cooking/cookItem.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include -#include -#include "KingSystem/Utils/Types.h" - -namespace ksys { - -struct CookItem { - CookItem(); - - sead::FixedSafeString<64> name{""}; - sead::SafeArray, 5> ingredients; - f32 stamina_recover_x{}; - s32 stamina_recover_y{}; - s32 cook_effect_1{}; - s32 cook_effect_0_x = -1; - f32 cook_effect_0_y{}; - bool _224{}; -}; -KSYS_CHECK_SIZE_NX150(CookItem, 0x228); - -} // namespace ksys diff --git a/src/KingSystem/Cooking/cookManager.cpp b/src/KingSystem/Cooking/cookManager.cpp deleted file mode 100644 index d49d1923..00000000 --- a/src/KingSystem/Cooking/cookManager.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "KingSystem/Cooking/cookManager.h" diff --git a/src/KingSystem/Cooking/cookManager.h b/src/KingSystem/Cooking/cookManager.h deleted file mode 100644 index f87f9a84..00000000 --- a/src/KingSystem/Cooking/cookManager.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -namespace ksys { - -// TODO -class CookingMgr { - SEAD_SINGLETON_DISPOSER(CookingMgr) - CookingMgr(); - // TODO: inline - ~CookingMgr(); -}; - -} // namespace ksys