botw/src/Game/Cooking/cookManager.cpp

1196 lines
43 KiB
C++

#include "Game/Cooking/cookManager.h"
#include <codec/seadHashCRC32.h>
#include <random/seadGlobalRandom.h>
#include <typeindex>
#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<s32, NumEffectSlots> effect_counts{};
sead::SafeArray<s32, NumEffectSlots> 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<sead::DirectResource>(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<Ingredient, NumIngredientsMax> 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<sead::FixedSafeString<64>>& 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<sead::FixedSafeString<64>, 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<sead::FixedSafeString<64>, 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