diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 114de9fd4..b6069475e 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -184,6 +184,7 @@ typedef struct { u8 seedIcons[5]; u8 dungeonsDone[8]; u8 trialsDone[6]; + u8 cowsMilked[10]; u8 temporaryWeapon; } SaveContext; // size = 0x1428 diff --git a/soh/soh/Enhancements/randomizer/3drando/settings.cpp b/soh/soh/Enhancements/randomizer/3drando/settings.cpp index 6bf207069..9c110cd1f 100644 --- a/soh/soh/Enhancements/randomizer/3drando/settings.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/settings.cpp @@ -2500,6 +2500,7 @@ namespace Settings { ShuffleRewards.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_DUNGEON_REWARDS]); ShuffleSongs.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_SONGS]); Tokensanity.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_TOKENS]); + ShuffleCows.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_COWS]); ShuffleKokiriSword.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_KOKIRI_SWORD]); ShuffleOcarinas.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_OCARINA]); diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 3a6b23bb7..692fbecd4 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -1394,6 +1394,7 @@ std::unordered_map SpoilerfileSettingNameToEn { "Open Settings:Token Count", RSK_RAINBOW_BRIDGE_TOKEN_COUNT }, { "Open Settings:Random Ganon's Trials", RSK_RANDOM_TRIALS }, { "Open Settings:Trial Count", RSK_TRIAL_COUNT }, + { "Shuffle Settings:Shuffle Cows", RSK_SHUFFLE_COWS }, { "Start with Deku Shield", RSK_STARTING_DEKU_SHIELD }, { "Start with Kokiri Sword", RSK_STARTING_KOKIRI_SWORD }, { "Start with Fairy Ocarina", RSK_STARTING_OCARINA }, @@ -1602,6 +1603,7 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) { numericValueString = it.value(); gSaveContext.randoSettings[index].value = std::stoi(numericValueString); break; + case RSK_SHUFFLE_COWS: case RSK_RANDOM_TRIALS: if(it.value() == "Off") { gSaveContext.randoSettings[index].value = 0; @@ -3438,6 +3440,7 @@ void GenerateRandomizerImgui() { cvarSettings[RSK_SHUFFLE_DUNGEON_REWARDS] = CVar_GetS32("gRandomizeShuffleDungeonReward", 0); cvarSettings[RSK_SHUFFLE_SONGS] = CVar_GetS32("gRandomizeShuffleSongs", 0); cvarSettings[RSK_SHUFFLE_TOKENS] = CVar_GetS32("gRandomizeShuffleTokens", 0); + cvarSettings[RSK_SHUFFLE_COWS] = CVar_GetS32("gRandomizeShuffleCows", 0); cvarSettings[RSK_SKIP_CHILD_ZELDA] = CVar_GetS32("gRandomizeSkipChildZelda", 0); // if we skip child zelda, we start with zelda's letter, and malon starts @@ -3956,6 +3959,12 @@ void DrawRandoEditor(bool& open) { "expected to be collected after getting Sun's Song."); PaddedSeparator(); + // Shuffle Cows + ImGui::Text(Settings::ShuffleCows.GetName().c_str()); + InsertHelpHoverText("Cows give a randomized item from the pool upon performing Epona's Song in front of them."); + SohImGui::EnhancementCombobox("gRandomizeShuffleCows", randoShuffleCows, 2, 0); + PaddedSeparator(); + if(CVar_GetS32("gRandomizeStartingKokiriSword", 0) == 0) { // Shuffle Kokiri Sword SohImGui::EnhancementCheckbox(Settings::ShuffleKokiriSword.GetName().c_str(), "gRandomizeShuffleKokiriSword"); diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 04e647783..f095ac068 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -975,6 +975,7 @@ typedef enum { RSK_SHUFFLE_DUNGEON_REWARDS, RSK_SHUFFLE_SONGS, RSK_SHUFFLE_TOKENS, + RSK_SHUFFLE_COWS, RSK_SHUFFLE_WEIRD_EGG, RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD, RSK_ITEM_POOL, diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 4fe0b1cfd..fa3ff4e88 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -736,6 +736,10 @@ void SaveManager::LoadBaseVersion1() { SaveManager::Instance->LoadArray("trialsDone", ARRAY_COUNT(gSaveContext.trialsDone), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.trialsDone[i]); }); + + SaveManager::Instance->LoadArray("cowsMilked", ARRAY_COUNT(gSaveContext.cowsMilked), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.cowsMilked[i]); + }); } void SaveManager::LoadBaseVersion2() { @@ -896,6 +900,10 @@ void SaveManager::LoadBaseVersion2() { SaveManager::Instance->LoadArray("trialsDone", ARRAY_COUNT(gSaveContext.trialsDone), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.trialsDone[i]); }); + + SaveManager::Instance->LoadArray("cowsMilked", ARRAY_COUNT(gSaveContext.cowsMilked), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.cowsMilked[i]); + }); } void SaveManager::SaveBase() { @@ -1052,6 +1060,10 @@ void SaveManager::SaveBase() { SaveManager::Instance->SaveArray("trialsDone", ARRAY_COUNT(gSaveContext.trialsDone), [](size_t i) { SaveManager::Instance->SaveData("", gSaveContext.trialsDone[i]); }); + + SaveManager::Instance->SaveArray("cowsMilked", ARRAY_COUNT(gSaveContext.cowsMilked), [](size_t i) { + SaveManager::Instance->SaveData("", gSaveContext.cowsMilked[i]); + }); } void SaveManager::SaveArray(const std::string& name, const size_t size, SaveArrayFunc func) { diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index 080c5ea9d..ddc28450f 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -5,6 +5,7 @@ #define NUM_DUNGEONS 8 #define NUM_TRIALS 6 +#define NUM_COWS 10 /** * Initialize new save. @@ -619,6 +620,11 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { gSaveContext.trialsDone[i] = 0; } + // Sets all cows to unmilked when generating a rando save. + for (u8 i = 0; i < NUM_COWS; i++) { + gSaveContext.cowsMilked[i] = 0; + } + // Set Cutscene flags to skip them gSaveContext.eventChkInf[0xC] |= 0x10; // returned to tot with medallions gSaveContext.eventChkInf[0xC] |= 0x20; //sheik at tot pedestal diff --git a/soh/src/overlays/actors/ovl_En_Cow/z_en_cow.c b/soh/src/overlays/actors/ovl_En_Cow/z_en_cow.c index 90ff14c31..b5e7992e2 100644 --- a/soh/src/overlays/actors/ovl_En_Cow/z_en_cow.c +++ b/soh/src/overlays/actors/ovl_En_Cow/z_en_cow.c @@ -18,6 +18,9 @@ void func_809E0070(Actor* thisx, GlobalContext* globalCtx); void func_809DF494(EnCow* this, GlobalContext* globalCtx); void func_809DF6BC(EnCow* this, GlobalContext* globalCtx); +struct CowInfo EnCow_GetInfo(EnCow* this, GlobalContext* globalCtx); +void EnCow_MoveForRandomizer(EnCow* this, GlobalContext* globalCtx); +GetItemID EnCow_GetRandomizerItem(EnCow* this, GlobalContext* globalCtx); void func_809DF778(EnCow* this, GlobalContext* globalCtx); void func_809DF7D8(EnCow* this, GlobalContext* globalCtx); void func_809DF870(EnCow* this, GlobalContext* globalCtx); @@ -106,6 +109,10 @@ void EnCow_Init(Actor* thisx, GlobalContext* globalCtx) { EnCow* this = (EnCow*)thisx; s32 pad; + if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_COWS)) { + EnCow_MoveForRandomizer(thisx, globalCtx); + } + ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 72.0f); switch (this->actor.params) { case 0: @@ -209,12 +216,116 @@ void func_809DF730(EnCow* this, GlobalContext* globalCtx) { } } +struct CowInfo { + int cowId; + RandomizerCheck randomizerCheck; +}; + +struct CowInfo EnCow_GetInfo(EnCow* this, GlobalContext* globalCtx) { + struct CowInfo cowInfo; + + cowInfo.cowId = -1; + cowInfo.randomizerCheck = RC_UNKNOWN_CHECK; + + switch (globalCtx->sceneNum) { + case SCENE_SOUKO: // Lon Lon Tower + if (this->actor.world.pos.x == -229 && this->actor.world.pos.z == 157) { + cowInfo.cowId = 0; + cowInfo.randomizerCheck = RC_LLR_TOWER_LEFT_COW; + } else if (this->actor.world.pos.x == -142 && this->actor.world.pos.z == -140) { + cowInfo.cowId = 1; + cowInfo.randomizerCheck = RC_LLR_TOWER_RIGHT_COW; + } + break; + case SCENE_MALON_STABLE: + if (this->actor.world.pos.x == 116 && this->actor.world.pos.z == -254) { + cowInfo.cowId = 2; + cowInfo.randomizerCheck = RC_LLR_STABLES_RIGHT_COW; + } else if (this->actor.world.pos.x == -122 && this->actor.world.pos.z == -254) { + cowInfo.cowId = 3; + cowInfo.randomizerCheck = RC_LLR_STABLES_LEFT_COW; + } + break; + case SCENE_KAKUSIANA: // Grotto + if (this->actor.world.pos.x == 2444 && this->actor.world.pos.z == -471) { + cowInfo.cowId = 4; + cowInfo.randomizerCheck = RC_DMT_COW_GROTTO_COW; + } else if (this->actor.world.pos.x == 3485 && this->actor.world.pos.z == -291) { + cowInfo.cowId = 5; + cowInfo.randomizerCheck = RC_HF_COW_GROTTO_COW; + } + break; + case SCENE_LINK_HOME: + cowInfo.cowId = 6; + cowInfo.randomizerCheck = RC_KF_LINKS_HOUSE_COW; + break; + case SCENE_LABO: // Impa's house + cowInfo.cowId = 7; + cowInfo.randomizerCheck = RC_KAK_IMPAS_HOUSE_COW; + break; + case SCENE_SPOT09: // Gerudo Valley + cowInfo.cowId = 8; + cowInfo.randomizerCheck = RC_GV_COW; + break; + case SCENE_SPOT08: // Jabu's Belly + cowInfo.cowId = 9; + cowInfo.randomizerCheck = RC_JABU_JABUS_BELLY_MQ_COW; + break; + } + + return cowInfo; +} + +void EnCow_MoveForRandomizer(EnCow* this, GlobalContext* globalCtx) { + // Only move the cow body (the tail will be moved with the body) + if (this->actor.params != 0) { + return; + } + + // Move left cow in lon lon tower + if (globalCtx->sceneNum == SCENE_SOUKO && this->actor.world.pos.x == -108 && this->actor.world.pos.z == -65) { + this->actor.world.pos.x = -229.0f; + this->actor.world.pos.z = 157.0f; + this->actor.shape.rot.y = 15783.0f; + // Move right cow in lon lon stable + } else if (globalCtx->sceneNum == SCENE_MALON_STABLE && this->actor.world.pos.x == -3 && this->actor.world.pos.z == -254) { + this->actor.world.pos.x += 119.0f; + } +} + +void EnCow_SetCowMilked(EnCow* this, GlobalContext* globalCtx) { + struct CowInfo cowInfo = EnCow_GetInfo(this, globalCtx); + gSaveContext.cowsMilked[cowInfo.cowId] = 1; +} + +GetItemID EnCow_GetRandomizerItem(EnCow* this, GlobalContext* globalCtx) { + GetItemID itemId = ITEM_NONE; + struct CowInfo cowInfo = EnCow_GetInfo(this, globalCtx); + + if (!gSaveContext.cowsMilked[cowInfo.cowId]) { + itemId = Randomizer_GetItemIdFromKnownCheck(cowInfo.randomizerCheck, GI_MILK); + } else if (Inventory_HasEmptyBottle()) { + itemId = GI_MILK; + } + + return itemId; +} + void func_809DF778(EnCow* this, GlobalContext* globalCtx) { if (Actor_HasParent(&this->actor, globalCtx)) { this->actor.parent = NULL; this->actionFunc = func_809DF730; } else { - func_8002F434(&this->actor, globalCtx, GI_MILK, 10000.0f, 100.0f); + if (gSaveContext.n64ddFlag) { + GetItemID itemId = EnCow_GetRandomizerItem(this, globalCtx); + func_8002F434(&this->actor, globalCtx, itemId, 10000.0f, 100.0f); + EnCow_SetCowMilked(this, globalCtx); + if (itemId == GI_ICE_TRAP) { + Message_StartTextbox(globalCtx, 0xF8, &this->actor); + } + } else { + func_8002F434(&this->actor, globalCtx, GI_MILK, 10000.0f, 100.0f); + } } } @@ -223,13 +334,15 @@ void func_809DF7D8(EnCow* this, GlobalContext* globalCtx) { this->actor.flags &= ~ACTOR_FLAG_16; Message_CloseTextbox(globalCtx); this->actionFunc = func_809DF778; - func_8002F434(&this->actor, globalCtx, GI_MILK, 10000.0f, 100.0f); + if (!gSaveContext.n64ddFlag) { + func_8002F434(&this->actor, globalCtx, GI_MILK, 10000.0f, 100.0f); + } } } void func_809DF870(EnCow* this, GlobalContext* globalCtx) { if ((Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_EVENT) && Message_ShouldAdvance(globalCtx)) { - if (Inventory_HasEmptyBottle()) { + if (Inventory_HasEmptyBottle() || (gSaveContext.n64ddFlag && EnCow_GetRandomizerItem(this, globalCtx) != ITEM_NONE)) { Message_ContinueTextbox(globalCtx, 0x2007); this->actionFunc = func_809DF7D8; } else {