From 015440fdffd21578fb8791f9aa98b76d8bb6e4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= <159546+serprex@users.noreply.github.com> Date: Sat, 4 Jul 2026 20:04:09 +0000 Subject: [PATCH] Decouple GBK from LACS (#5838) Triforce Pieces can now be tokens for bridge or ganon's soul. & can be tokens for multiple rewards Wincon can now be arbitrary conditions Ganon's Soul (removed from existing boss soul options) can now be arbitrary conditions Co-authored-by: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> --- .../Rando Seed Settings - Advanced.json | 2 +- .../Rando Seed Settings - Beginner.json | 2 +- .../Rando Seed Settings - Hell Mode.json | 4 +- .../Rando Seed Settings - Standard.json | 2 +- soh/include/functions.h | 1 - .../TimeSavers/timesaver_hook_handlers.cpp | 7 +- .../custom-message/CustomMessageManager.cpp | 2 +- .../Enhancements/debugger/debugSaveEditor.cpp | 3 +- .../game-interactor/GameInteractor.h | 8 +- .../game-interactor/GameInteractor_State.cpp | 8 +- .../vanilla-behavior/GIVanillaBehavior.h | 8 + soh/soh/Enhancements/kaleido.cpp | 7 +- .../Enhancements/randomizer/3drando/fill.cpp | 45 +- .../randomizer/3drando/hint_list.cpp | 135 ++++-- .../Enhancements/randomizer/3drando/hints.cpp | 65 ++- .../randomizer/3drando/item_pool.cpp | 54 +-- .../Enhancements/randomizer/3drando/menu.cpp | 2 +- .../randomizer/3drando/starting_inventory.cpp | 5 +- .../randomizer/Messages/ItemMessages.cpp | 145 ++++-- .../Enhancements/randomizer/Plandomizer.cpp | 3 + .../Enhancements/randomizer/SeedContext.cpp | 34 +- soh/soh/Enhancements/randomizer/SeedContext.h | 20 +- soh/soh/Enhancements/randomizer/Traps.cpp | 6 + soh/soh/Enhancements/randomizer/draw.cpp | 21 +- soh/soh/Enhancements/randomizer/hint.cpp | 100 +++- soh/soh/Enhancements/randomizer/hint.h | 2 + .../Enhancements/randomizer/hook_handlers.cpp | 305 +++++++----- soh/soh/Enhancements/randomizer/item.cpp | 3 +- soh/soh/Enhancements/randomizer/item_list.cpp | 4 +- .../overworld/temple_of_time.cpp | 4 +- .../randomizer/location_access/root.cpp | 4 +- .../Enhancements/randomizer/location_list.cpp | 5 +- soh/soh/Enhancements/randomizer/logic.cpp | 105 ++++- soh/soh/Enhancements/randomizer/logic.h | 5 +- .../randomizer/option_descriptions.cpp | 40 +- .../Enhancements/randomizer/randomizer.cpp | 37 +- soh/soh/Enhancements/randomizer/randomizer.h | 1 + .../randomizer/randomizerEnums/LogicVal.h | 1 - .../randomizerEnums/RandomizerCheck.h | 4 +- .../randomizerEnums/RandomizerHintTextKey.h | 32 +- .../randomizerEnums/RandomizerInf.h | 2 +- .../randomizerEnums/RandomizerOptions.h | 81 ++-- .../randomizerEnums/RandomizerSettingKey.h | 32 +- .../randomizer/randomizer_check_objects.cpp | 35 +- .../randomizer/randomizer_check_tracker.cpp | 24 +- .../randomizer/randomizer_item_tracker.cpp | 30 +- soh/soh/Enhancements/randomizer/settings.cpp | 438 ++++++++++++------ soh/soh/OTRGlobals.cpp | 1 + soh/soh/SaveManager.cpp | 10 +- soh/soh/config/ConfigUpdaters.cpp | 79 ++++ soh/soh/config/ConfigUpdaters.h | 6 + soh/soh/z_scene_otr.cpp | 18 +- soh/src/code/z_play.c | 22 +- soh/src/code/z_player_lib.c | 3 +- .../actors/ovl_Boss_Ganon2/z_boss_ganon2.c | 20 +- 55 files changed, 1379 insertions(+), 663 deletions(-) diff --git a/soh/assets/custom/presets/Rando Seed Settings - Advanced.json b/soh/assets/custom/presets/Rando Seed Settings - Advanced.json index f8fb2b9d16..461a9349f6 100644 --- a/soh/assets/custom/presets/Rando Seed Settings - Advanced.json +++ b/soh/assets/custom/presets/Rando Seed Settings - Advanced.json @@ -24,7 +24,7 @@ "IncludeTycoonWallet": 1, "KakarikoGate": 1, "Keysanity": 5, - "LacsRewardCount": 8, + "GbkRewardCount": 8, "MalonHint": 1, "MerchantText": 1, "RainbowBridge": 7, diff --git a/soh/assets/custom/presets/Rando Seed Settings - Beginner.json b/soh/assets/custom/presets/Rando Seed Settings - Beginner.json index be4c185675..5943e40d01 100644 --- a/soh/assets/custom/presets/Rando Seed Settings - Beginner.json +++ b/soh/assets/custom/presets/Rando Seed Settings - Beginner.json @@ -27,7 +27,7 @@ "IncludeTycoonWallet": 1, "KakarikoGate": 1, "Keysanity": 2, - "LacsRewardCount": 6, + "GbkRewardCount": 6, "MalonHint": 1, "MerchantText": 1, "RainbowBridge": 7, diff --git a/soh/assets/custom/presets/Rando Seed Settings - Hell Mode.json b/soh/assets/custom/presets/Rando Seed Settings - Hell Mode.json index f5baa80047..fd62ae7355 100644 --- a/soh/assets/custom/presets/Rando Seed Settings - Hell Mode.json +++ b/soh/assets/custom/presets/Rando Seed Settings - Hell Mode.json @@ -18,8 +18,8 @@ "IncludeTycoonWallet": 1, "KakarikoGate": 1, "Keysanity": 5, - "LacsRewardCount": 10, - "LacsRewardOptions": 1, + "GbkRewardCount": 10, + "GbkRewardOptions": 1, "LockOverworldDoors": 1, "MedallionLockedTrials": 1, "MixBosses": 1, diff --git a/soh/assets/custom/presets/Rando Seed Settings - Standard.json b/soh/assets/custom/presets/Rando Seed Settings - Standard.json index b89a36abc2..bc90673f12 100644 --- a/soh/assets/custom/presets/Rando Seed Settings - Standard.json +++ b/soh/assets/custom/presets/Rando Seed Settings - Standard.json @@ -27,7 +27,7 @@ "IncludeTycoonWallet": 1, "KakarikoGate": 1, "Keysanity": 5, - "LacsRewardCount": 7, + "GbkRewardCount": 7, "MalonHint": 1, "MerchantText": 1, "RainbowBridge": 7, diff --git a/soh/include/functions.h b/soh/include/functions.h index 066b1d1d9e..00af797489 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -1540,7 +1540,6 @@ u8 CheckStoneCount(); u8 CheckMedallionCount(); u8 CheckDungeonCount(); u8 CheckBridgeRewardCount(); -u8 CheckLACSRewardCount(); s32 Play_InCsMode(PlayState* play); f32 func_800BFCB8(PlayState* play, MtxF* mf, Vec3f* vec); void* Play_LoadFile(PlayState* play, RomFile* file); diff --git a/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp index d3bfc85909..3a76bd723b 100644 --- a/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp @@ -175,12 +175,9 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { // LACS - bool meetsLACSRequirements = - LINK_IS_ADULT && - (gEntranceTable[((void)0, gSaveContext.entranceIndex)].scene == SCENE_TEMPLE_OF_TIME) && + if (LINK_IS_ADULT && (gEntranceTable[gSaveContext.entranceIndex].scene == SCENE_TEMPLE_OF_TIME) && CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW) && - !Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS); - if (GameInteractor_Should(VB_BE_ELIGIBLE_FOR_LIGHT_ARROWS, meetsLACSRequirements)) { + !Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS)) { Flags_SetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS); if (GameInteractor_Should(VB_GIVE_ITEM_LIGHT_ARROW, true)) { Item_Give(gPlayState, ITEM_ARROW_LIGHT); diff --git a/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp b/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp index f052c21684..0b3fb17cf8 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp +++ b/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp @@ -35,7 +35,7 @@ static const std::unordered_map altarIcons = { { "l", ITEM_ARROW_LIGHT }, { "b", ITEM_KEY_BOSS }, { "o", ITEM_SWORD_MASTER }, { "c", ITEM_OCARINA_FAIRY }, { "i", ITEM_OCARINA_TIME }, { "L", ITEM_BOW_ARROW_LIGHT }, { "k", ITEM_TUNIC_KOKIRI }, { "m", ITEM_DUNGEON_MAP }, { "C", ITEM_COMPASS }, - { "s", ITEM_SKULL_TOKEN }, { "g", ITEM_MASK_GORON }, + { "s", ITEM_SKULL_TOKEN }, { "g", ITEM_MASK_GORON }, { "w", ITEM_CUSTOM }, }; static std::map pixelWidthTable = { diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index 3a53670551..6b698e597a 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -406,8 +406,7 @@ void DrawInfoTab() { Combobox("Z Target Mode", &gSaveContext.zTargetSetting, zTargetMap, comboboxOptionsBase.Tooltip("Z-Targeting behavior")); - if (IS_RANDO && - (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT) != RO_TRIFORCE_HUNT_OFF)) { + if (IS_RANDO && (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_TOTAL) > 0)) { PushStyleInput(THEME_COLOR); ImGui::InputScalar("Triforce Pieces", ImGuiDataType_U8, &gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 7c0fed98f3..ffae89ca30 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -74,8 +74,8 @@ uint8_t GameInteractor_GetRandomWindActive(); uint8_t GameInteractor_GetRandomBonksActive(); uint8_t GameInteractor_GetSlipperyFloorActive(); uint8_t GameInteractor_SecondCollisionUpdate(); -void GameInteractor_SetTriforceHuntPieceGiven(uint8_t state); -void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state); +void GameInteractor_SetTriforceHuntPieceGiven(bool state); +void GameInteractor_SetTriforceHuntCreditsWarpActive(bool state); #ifdef __cplusplus } #endif @@ -203,8 +203,8 @@ class GameInteractor { static uint8_t RandomBonksActive; static uint8_t SlipperyFloorActive; static uint8_t SecondCollisionUpdate; - static uint8_t TriforceHuntPieceGiven; - static uint8_t TriforceHuntCreditsWarpActive; + static bool TriforceHuntPieceGiven; + static bool TriforceHuntCreditsWarpActive; static void SetPacifistMode(bool active); }; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_State.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_State.cpp index bdcb4f37a7..51fa0649f6 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_State.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_State.cpp @@ -20,8 +20,8 @@ uint8_t GameInteractor::State::RandomWindSecondsSinceLastDirectionChange = 0; uint8_t GameInteractor::State::RandomBonksActive = 0; uint8_t GameInteractor::State::SlipperyFloorActive = 0; uint8_t GameInteractor::State::SecondCollisionUpdate = 0; -uint8_t GameInteractor::State::TriforceHuntPieceGiven = 0; -uint8_t GameInteractor::State::TriforceHuntCreditsWarpActive = 0; +bool GameInteractor::State::TriforceHuntPieceGiven = false; +bool GameInteractor::State::TriforceHuntCreditsWarpActive = false; void GameInteractor::State::SetPacifistMode(bool active) { PacifistModeActive = active; @@ -131,11 +131,11 @@ uint8_t GameInteractor_SecondCollisionUpdate() { } // MARK: - GameInteractor::State::TriforceHuntPieceGiven -void GameInteractor_SetTriforceHuntPieceGiven(uint8_t state) { +void GameInteractor_SetTriforceHuntPieceGiven(bool state) { GameInteractor::State::TriforceHuntPieceGiven = state; } // MARK: - GameInteractor::State::TriforceHuntCreditsWarpActive -void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state) { +void GameInteractor_SetTriforceHuntCreditsWarpActive(bool state) { GameInteractor::State::TriforceHuntCreditsWarpActive = state; } diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 2e1c717ba9..66b5f728ba 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2411,6 +2411,14 @@ typedef enum { // - None VB_SKIP_TALKING, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_SLAY_GANON, + // #### `result` // ```c // (collectible >= 0) && (collectible <= 0x19 diff --git a/soh/soh/Enhancements/kaleido.cpp b/soh/soh/Enhancements/kaleido.cpp index c576cd92b8..3c8e6b73df 100644 --- a/soh/soh/Enhancements/kaleido.cpp +++ b/soh/soh/Enhancements/kaleido.cpp @@ -144,12 +144,11 @@ Kaleido::Kaleido() { gItemIconFishingPoleTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 }, FlagType::FLAG_RANDOMIZER_INF, static_cast(RAND_INF_FISHING_POLE_FOUND), "Fishing Pole")); } - if (ctx->GetOption(RSK_TRIFORCE_HUNT).IsNot(RO_TRIFORCE_HUNT_OFF)) { + if (ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get() > 0) { mEntries.push_back(std::make_shared( gTriforcePieceTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 }, reinterpret_cast(&gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected), - ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_REQUIRED).Get() + 1, - ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get() + 1)); + ctx->GetOption(RSK_WINCON_TRIFORCE_COUNT).Get(), ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get())); } if (ctx->GetOption(RSK_SKELETON_KEY)) { mEntries.push_back(std::make_shared( @@ -170,7 +169,7 @@ Kaleido::Kaleido() { FlagType::FLAG_RANDOMIZER_INF, i, bossSoulNames[i - RAND_INF_GOHMA_SOUL])); } } - if (ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).Is(RO_BOSS_SOULS_ON_PLUS_GANON)) { + if (ctx->GetOption(RSK_GANONS_SOUL).IsNot(RO_GANONS_SOUL_STARTWITH)) { mEntries.push_back(std::make_shared( gBossSoulTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 }, FlagType::FLAG_RANDOMIZER_INF, RAND_INF_GANON_SOUL, "Ganon's Soul")); diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.cpp b/soh/soh/Enhancements/randomizer/3drando/fill.cpp index 383dc9d8d9..fad4178726 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.cpp @@ -230,16 +230,6 @@ void ProcessExits(Region* region, GetAccessibleLocationsStruct& gals, Randomizer // Get the max number of tokens that can possibly be useful static int GetMaxGSCount() { auto ctx = Rando::Context::GetInstance(); - // If bridge or LACS is set to tokens, get how many are required - int maxBridge = 0; - int maxLACS = 0; - if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS)) { - maxBridge = ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Get(); - } - if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_TOKENS)) { - maxLACS = ctx->GetOption(RSK_LACS_TOKEN_COUNT).Get(); - } - maxBridge = std::max(maxBridge, maxLACS); // Get the max amount of GS which could be useful from token reward locations int maxUseful = 0; // If the highest advancement item is a token, we know it is useless since it won't lead to an otherwise useful item @@ -262,8 +252,21 @@ static int GetMaxGSCount() { ctx->GetItemLocation(RC_KAK_10_GOLD_SKULLTULA_REWARD)->GetPlacedItem().GetItemType() != ITEMTYPE_TOKEN) { maxUseful = 10; } + // If bridge, GBK, Ganon's Soul, or win condition is set to tokens, get how many are required + if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS)) { + maxUseful = std::max(maxUseful, (int)ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Get()); + } + if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_TOKENS)) { + maxUseful = std::max(maxUseful, (int)ctx->GetOption(RSK_GBK_TOKEN_COUNT).Get()); + } + if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_TOKENS)) { + maxUseful = std::max(maxUseful, (int)ctx->GetOption(RSK_GANONS_SOUL_TOKEN_COUNT).Get()); + } + if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_TOKENS)) { + maxUseful = std::max(maxUseful, (int)ctx->GetOption(RSK_WINCON_TOKEN_COUNT).Get()); + } // Return max of the two possible reasons tokens could be important, minus the tokens in the starting inventory - return std::max(maxUseful, maxBridge) - ctx->GetOption(RSK_STARTING_SKULLTULA_TOKEN).Get(); + return maxUseful - ctx->GetOption(RSK_STARTING_SKULLTULA_TOKEN).Get(); } std::string GetShopItemBaseName(std::string itemName) { @@ -369,12 +372,12 @@ void AddToPlaythrough(LocationAccess& locPair, GetAccessibleLocationsStruct& gal if (!exclude) { gals.itemSphere.push_back(loc); } - } - // Triforce has been found, seed is beatable, nothing else in this or future spheres matters - else if (location->GetPlacedRandomizerGet() == RG_TRIFORCE) { - gals.itemSphere.clear(); - gals.itemSphere.push_back(loc); - ctx->playthroughBeatable = true; + // Triforce has been found, seed is beatable, nothing else in this or future spheres matters + if (location->GetPlacedRandomizerGet() == RG_TRIFORCE) { + gals.itemSphere.clear(); + gals.itemSphere.push_back(loc); + ctx->playthroughBeatable = true; + } } } @@ -1159,6 +1162,14 @@ static void RandomizeDungeonItems() { } } + if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_ANY_DUNGEON)) { + auto ganonSoul = FilterAndEraseFromPool(itemPool, [](const auto i) { return i == RG_GANON_SOUL; }); + AddElementsToPool(anyDungeonItems, ganonSoul); + } else if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_OVERWORLD)) { + auto ganonSoul = FilterAndEraseFromPool(itemPool, [](const auto i) { return i == RG_GANON_SOUL; }); + AddElementsToPool(overworldItems, ganonSoul); + } + if (ctx->GetOption(RSK_GERUDO_KEYS).Is(RO_GERUDO_KEYS_ANY_DUNGEON)) { auto gerudoKeys = FilterAndEraseFromPool(itemPool, [](const auto i) { return i == RG_GERUDO_FORTRESS_SMALL_KEY || i == RG_GERUDO_FORTRESS_KEY_RING; diff --git a/soh/soh/Enhancements/randomizer/3drando/hint_list.cpp b/soh/soh/Enhancements/randomizer/3drando/hint_list.cpp index c524953c19..ee8580b600 100644 --- a/soh/soh/Enhancements/randomizer/3drando/hint_list.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/hint_list.cpp @@ -2013,8 +2013,13 @@ void StaticData::HintTable_Init() { {QM_YELLOW}, {}, TEXTBOX_TYPE_BLUE)); // /*spanish*/$sLos sabios aguardarán a que el héroe obtenga #[[d]] símbolo||s| de skulltula dorada#.^ + hintTextTable[RHT_BRIDGE_TRIFORCE_PIECES_HINT] = HintText(CustomMessage("$wThe awakened ones will await for the Hero to collect #[[d]] Triforce Piece||s|#.^", + /*german*/ "$wDie Weisen werden darauf&warten, daß der Held&#[[d]] Triforce-Fragment||e|# sammelt.^", + /*french*/ "$wLes êtres de sagesse attendront le héros muni de #[[d]] Morceau||x| de Triforce#.^", + {QM_YELLOW}, {}, TEXTBOX_TYPE_BLUE)); + hintTextTable[RHT_BRIDGE_GREG_HINT] = HintText(CustomMessage("$gThe awakened ones will await for the Hero to find #Greg#.^", - /*german*/ "$gDie Weisen werden darauf&warten, daß der Held&#Greg# findet.^", + /*german*/ "$gDie Weisen werden darauf&warten, daß der Held&#Greg# findet.^", /*french*/ "$gLes êtres de sagesse attendront le héros muni de #Greg#.^", {QM_GREEN}, {}, TEXTBOX_TYPE_BLUE)); @@ -2059,56 +2064,118 @@ void StaticData::HintTable_Init() { {QM_PINK, QM_BLUE})); // /*spanish*/$bY la llave del #señor del mal# aguardará en #cualquier lugar de Hyrule#.^ - hintTextTable[RHT_GANON_BK_TRIFORCE_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be given to the Hero once the #Triforce## is completed.^", - /*german*/ "$bUnd der #Schlüssel des Bösen# wird verliehen, sobald das #Triforce# vervollständigt wurde.^", - /*french*/ "$bAussi, la #clé du Malin# se&révèlera une fois la #Triforce#&assemblée.^", - {QM_PINK, QM_YELLOW})); - // /*spanish*/$bY el héroe recibirá la llave del #señor del mal# cuando haya completado la #Trifuerza#.^ - - hintTextTable[RHT_GANON_BK_SKULLTULA_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided by the cursed rich man once #100 Gold Skulltula Tokens# are retrieved.^", - /*german*/ "$bUnd der #Schlüssel des Bösen# wird von einem verfluchten reichen Mann verliehen, sobald #100 Skulltula-Symbole# gesammelt wurden.^", - /*french*/ "$bAussi, la #clé du Malin# sera&donnée par l'homme maudit une fois que #100 Symboles de Skulltula d'or# auront été trouvés.^", - {QM_PINK, QM_YELLOW})); - // /*spanish*/$bY el rico maldito entregará la llave&del #señor de mal# tras obtener&100 símbolos de skulltula dorada#.^ - - hintTextTable[RHT_LACS_VANILLA_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided by #Zelda# once the #Shadow and Spirit Medallions# are retrieved.^", - /*german*/ "$bUnd der #Schlüssel des Bösen# wird von #Zelda# verliehen, sobald #die Amulette des Schattens und der Geister# geborgen wurden.^", - /*french*/ "$bAussi, la #clé du Malin# sera fournie par #Zelda# une fois que les #Médaillons de l'Ombre et de l'Esprit# seront récupérés.^", - {QM_PINK, QM_YELLOW, QM_RED})); - // /*spanish*/$bY #Zelda# entregará la llave del #señor del mal# tras obtener #el medallón de las sombras y del espíritu#.^ - - hintTextTable[RHT_LACS_MEDALLIONS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided by #Zelda# once #[[d]] Medallion|# is|s# are| retrieved.^", - /*german*/ "$bUnd der #Schlüssel des Bösen# wird von #Zelda# verliehen, sobald #[[d]] Amulett|# geborgen wurde|e# geborgen wurden|.^", - /*french*/ "$bAussi, la #clé du Malin# sera fournie par #Zelda# une fois |qu' #[[d]] Médaillon# aura été récupéré|que #[[d]] Médaillons# auront été récupérés|.^", + hintTextTable[RHT_GBK_MEDALLIONS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided once #[[d]] Medallion|# is|s# are| retrieved.^", + /*german*/ "$bUnd der #Schlüssel des Bösen# wird verliehen, sobald #[[d]] Amulett|# geborgen wurde|e# geborgen wurden|.^", + /*french*/ "$bAussi, la #clé du Malin# sera fournie une fois |qu' #[[d]] Médaillon# aura été récupéré|que #[[d]] Médaillons# auront été récupérés|.^", {QM_PINK, QM_YELLOW, QM_RED})); // /*spanish*/$bY #Zelda# entregará la llave&del #señor del mal# tras obtener #[[d]] |medallón|medallones|#.^ - hintTextTable[RHT_LACS_STONES_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided by #Zelda# once #[[d]] Spiritual Stone|# is|s# are| retrieved.^", - /*german*/ "$bUnd der #Schlüssel des Bösen# wird von #Zelda# verliehen, sobald #[[d]] Heilige|r Stein# geborgen wurde| Steine# geborgen wurden|.^", - /*french*/ "$bAussi, la #clé du Malin# sera fournie par #Zelda# une fois |qu' #[[d]] Pierre Ancestrale# aura été&récupérée|que #[[d]] Pierres Ancestrales# auront été récupérées|.^", + hintTextTable[RHT_GBK_STONES_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided once #[[d]] Spiritual Stone|# is|s# are| retrieved.^", + /*german*/ "$bUnd der #Schlüssel des Bösen# wird verliehen, sobald #[[d]] Heilige|r Stein# geborgen wurde| Steine# geborgen wurden|.^", + /*french*/ "$bAussi, la #clé du Malin# sera fournie une fois |qu' #[[d]] Pierre Ancestrale# aura été&récupérée|que #[[d]] Pierres Ancestrales# auront été récupérées|.^", {QM_PINK, QM_YELLOW, QM_BLUE})); // /*spanish*/$bY #Zelda# entregará la llave del #señor del mal# tras obtener #[[d]] piedra| espiritual|s espirituales|#.^ - hintTextTable[RHT_LACS_REWARDS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided by #Zelda# once #[[d]]# #Spiritual Stone|# or #Medallion# is|s# and #Medallions# are| retrieved.^", - /*german*/ "$bUnd der #Schlüssel des Bösen# wird von #Zelda# verliehen, sobald #[[d]]# #Heilige|r Stein# oder #Amulett#&geborgen wurde| Steine# oder #Amulette#&geborgen wurden|.^", - /*french*/ "$bAussi, la #clé du Malin# sera fournie par #Zelda# une fois qu|' #[[d]]# #Pierre Ancestrale# ou #[[d]] Médaillon# sera récupéré|e&#[[d]]# #Pierres Ancestrales# et&#Médaillons# seront récupérés|.^", + hintTextTable[RHT_GBK_REWARDS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided once #[[d]]# #Spiritual Stone|# or #Medallion# is|s# and #Medallions# are| retrieved.^", + /*german*/ "$bUnd der #Schlüssel des Bösen# wird verliehen, sobald #[[d]]# #Heilige|r Stein# oder #Amulett#&geborgen wurde| Steine# oder #Amulette#&geborgen wurden|.^", + /*french*/ "$bAussi, la #clé du Malin# sera fournie une fois qu|' #[[d]]# #Pierre Ancestrale# ou #[[d]] Médaillon# sera récupéré|e&#[[d]]# #Pierres Ancestrales# et&#Médaillons# seront récupérés|.^", {QM_PINK, QM_YELLOW, QM_YELLOW, QM_BLUE, QM_RED})); // /*spanish*/$bY #Zelda# entregará la llave del #señor del mal# tras obtener #[[d]]# piedra| espiritual o medallón|s espirituales o medallones|#.^ - hintTextTable[RHT_LACS_DUNGEONS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided by #Zelda# once #[[d]] Dungeon|# is|s# are| conquered.^", - /*german*/ "$bUnd der #Schlüssel des Bösen# wird von #Zelda# verliehen, sobald #[[d]] Labyrinth|# abgeschloßen wurde|e# abgeschloßen wurden|.^", - /*french*/ "$bAussi, la #clé du Malin# sera fournie par #Zelda# une fois qu|' #[[d]] donjon #sera conquis|e #[[d]] donjons# seront conquis|.^", + hintTextTable[RHT_GBK_DUNGEONS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided once #[[d]] Dungeon|# is|s# are| conquered.^", + /*german*/ "$bUnd der #Schlüssel des Bösen# wird verliehen, sobald #[[d]] Labyrinth|# abgeschloßen wurde|e# abgeschloßen wurden|.^", + /*french*/ "$bAussi, la #clé du Malin# sera fournie une fois qu|' #[[d]] donjon #sera conquis|e #[[d]] donjons# seront conquis|.^", {QM_PINK, QM_YELLOW, QM_PINK})); // /*spanish*/$bY #Zelda# entregará la llave del #señor del mal# tras completar #[[d]] mazmorra||s|#.^ - hintTextTable[RHT_LACS_TOKENS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided by #Zelda# once #[[d]] Gold Skulltula Token|# is|s# are| retrieved.^", - /*german*/ "$bUnd der #Schlüssel des Bösen# wird von #Zelda# verliehen, sobald #[[d]] Skulltula-Symbol|# gesammelt wurde|e# gesammelt wurden|.^", - /*french*/ "$bAussi, la #clé du Malin# sera fournie par #Zelda# une fois |qu' #[[d]] symbole de Skulltula d'or #sera récupuéré" + hintTextTable[RHT_GBK_TOKENS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided once #[[d]] Gold Skulltula Token|# is|s# are| retrieved.^", + /*german*/ "$bUnd der #Schlüssel des Bösen# wird verliehen, sobald #[[d]] Skulltula-Symbol|# gesammelt wurde|e# gesammelt wurden|.^", + /*french*/ "$bAussi, la #clé du Malin# sera fournie une fois |qu' #[[d]] symbole de Skulltula d'or #sera récupuéré" "|que &#[[d]] symboles de Skulltula d'or&#seront recupérés|.^", {QM_PINK, QM_YELLOW, QM_YELLOW})); // /*spanish*/$bY #Zelda# entregará la llave del #señor del mal# tras obtener #[[d]] símbolo // ||s| de skulltula dorada#.^ + hintTextTable[RHT_GBK_TRIFORCE_PIECES_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s key will be provided once #[[d]] Triforce Piece|# is|s# are| retrieved.^", + /*german*/ "$bUnd der #Schlüssel des Bösen# wird verliehen, sobald #[[d]] Triforce-Fragment|# gesammelt wurde|e# gesammelt wurden|.^", + /*french*/ "$bAussi, la #clé du Malin# sera fournie une fois |qu' #[[d]] Morceau de Triforce# aura été récupéré|que #[[d]] Morceaux de Triforce# auront été récupérés|.^", + {QM_PINK, QM_YELLOW, QM_YELLOW})); + + /*-------------------------- + | GANON'S SOUL HINT TEXT | + ---------------------------*/ + + hintTextTable[RHT_GANONS_SOUL_MEDALLIONS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s soul will be provided once #[[d]] Medallion|# is|s# are| retrieved.^", + /*german*/ "$bUnd die #Seele des Bösen# wird verliehen, sobald #[[d]] Amulett|# geborgen wurde|e# geborgen wurden|.^", + /*french*/ "$bAussi, l'#âme du Malin# sera fournie une fois |qu' #[[d]] Médaillon# aura été récupéré|que #[[d]] Médaillons# auront été récupérés|.^", + {QM_PINK, QM_YELLOW, QM_RED})); + + hintTextTable[RHT_GANONS_SOUL_STONES_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s soul will be provided once #[[d]] Spiritual Stone|# is|s# are| retrieved.^", + /*german*/ "$bUnd die #Seele des Bösen# wird verliehen, sobald #[[d]] Heilige|r Stein# geborgen wurde| Steine# geborgen wurden|.^", + /*french*/ "$bAussi, l'#âme du Malin# sera fournie une fois |qu' #[[d]] Pierre Ancestrale# aura été&récupérée|que #[[d]] Pierres Ancestrales# auront été récupérées|.^", + {QM_PINK, QM_YELLOW, QM_BLUE})); + + hintTextTable[RHT_GANONS_SOUL_REWARDS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s soul will be provided once #[[d]]# #Spiritual Stone|# or #Medallion# is|s# and #Medallions# are| retrieved.^", + /*german*/ "$bUnd die #Seele des Bösen# wird verliehen, sobald #[[d]]# #Heilige|r Stein# oder #Amulett#&geborgen wurde| Steine# oder #Amulette#&geborgen wurden|.^", + /*french*/ "$bAussi, l'#âme du Malin# sera fournie une fois qu|' #[[d]]# #Pierre Ancestrale# ou #[[d]] Médaillon# sera récupéré|e&#[[d]]# #Pierres Ancestrales# et&#Médaillons# seront récupérés|.^", + {QM_PINK, QM_YELLOW, QM_YELLOW, QM_BLUE, QM_RED})); + + hintTextTable[RHT_GANONS_SOUL_DUNGEONS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s soul will be provided once #[[d]] Dungeon|# is|s# are| conquered.^", + /*german*/ "$bUnd die #Seele des Bösen# wird verliehen, sobald #[[d]] Labyrinth|# abgeschloßen wurde|e# abgeschloßen wurden|.^", + /*french*/ "$bAussi, l'#âme du Malin# sera fournie une fois qu|' #[[d]] donjon #sera conquis|e #[[d]] donjons# seront conquis|.^", + {QM_PINK, QM_YELLOW, QM_PINK})); + + hintTextTable[RHT_GANONS_SOUL_TOKENS_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s soul will be provided once #[[d]] Gold Skulltula Token|# is|s# are| retrieved.^", + /*german*/ "$bUnd die #Seele des Bösen# wird verliehen, sobald #[[d]] Skulltula-Symbol|# gesammelt wurde|e# gesammelt wurden|.^", + /*french*/ "$bAussi, l'#âme du Malin# sera fournie une fois |qu' #[[d]] symbole de Skulltula d'or #sera récupuéré" + "|que &#[[d]] symboles de Skulltula d'or&#seront recupérés|.^", + {QM_PINK, QM_YELLOW, QM_YELLOW})); + + hintTextTable[RHT_GANONS_SOUL_TRIFORCE_PIECES_HINT] = HintText(CustomMessage("$bAnd the #evil one#'s soul will be provided once #[[d]] Triforce Piece|# is|s# are| retrieved.^", + /*german*/ "$bUnd die #Seele des Bösen# wird verliehen, sobald #[[d]] Triforce-Fragment|# gesammelt wurde|e# gesammelt wurden|.^", + /*french*/ "$bAussi, l'#âme du Malin# sera fournie une fois |qu' #[[d]] Morceau de Triforce# aura été récupéré|que #[[d]] Morceaux de Triforce# auront été récupérés|.^", + {QM_PINK, QM_YELLOW, QM_YELLOW})); + + /*-------------------------- + | WINCON HINT TEXT | + ---------------------------*/ + + hintTextTable[RHT_WINCON_ANYWHERE_HINT] = HintText(CustomMessage("$wAnd the #Triforce# will be hidden somewhere&#in Hyrule#.^", + /*german*/ "$wUnd das #Triforce# wird irgendwo #in Hyrule# zu finden sein.^", + /*french*/ "$wAussi, la #Triforce# se trouve quelque part #dans Hyrule#.^", + {QM_YELLOW, QM_BLUE})); + + hintTextTable[RHT_WINCON_MEDALLIONS_HINT] = HintText(CustomMessage("$wAnd the #Triforce# will be granted once #[[d]] Medallion|# is|s# are| retrieved.^", + /*german*/ "$wUnd das #Triforce# wird gewährt, sobald #[[d]] Amulett|# geborgen wurde|e# geborgen wurden|.^", + /*french*/ "$wAussi, la #Triforce# sera accordée une fois |qu' #[[d]] Médaillon# aura été récupéré|que #[[d]] Médaillons# auront été récupérés|.^", + {QM_YELLOW, QM_YELLOW, QM_RED})); + + hintTextTable[RHT_WINCON_STONES_HINT] = HintText(CustomMessage("$wAnd the #Triforce# will be granted once #[[d]] Spiritual Stone|# is|s# are| retrieved.^", + /*german*/ "$wUnd das #Triforce# wird gewährt, sobald #[[d]] Heilige|r Stein# geborgen wurde| Steine# geborgen wurden|.^", + /*french*/ "$wAussi, la #Triforce# sera accordée une fois |qu' #[[d]] Pierre Ancestrale# aura été&récupérée|que #[[d]] Pierres Ancestrales# auront été récupérées|.^", + {QM_YELLOW, QM_YELLOW, QM_BLUE})); + + hintTextTable[RHT_WINCON_REWARDS_HINT] = HintText(CustomMessage("$wAnd the #Triforce# will be granted once #[[d]]# #Spiritual Stone|# or #Medallion# is|s# and #Medallions# are| retrieved.^", + /*german*/ "$wUnd das #Triforce# wird gewährt, sobald #[[d]]# #Heilige|r Stein# oder #Amulett#&geborgen wurde| Steine# oder #Amulette#&geborgen wurden|.^", + /*french*/ "$wAussi, la #Triforce# sera accordée une fois qu|' #[[d]]# #Pierre Ancestrale# ou #[[d]] Médaillon# sera récupéré|e&#[[d]]# #Pierres Ancestrales# et&#Médaillons# seront récupérés|.^", + {QM_YELLOW, QM_YELLOW, QM_YELLOW, QM_BLUE, QM_RED})); + + hintTextTable[RHT_WINCON_DUNGEONS_HINT] = HintText(CustomMessage("$wAnd the #Triforce# will be granted once #[[d]] Dungeon|# is|s# are| conquered.^", + /*german*/ "$wUnd das #Triforce# wird gewährt, sobald #[[d]] Labyrinth|# abgeschloßen wurde|e# abgeschloßen wurden|.^", + /*french*/ "$wAussi, la #Triforce# sera accordée une fois qu|' #[[d]] donjon #sera conquis|e #[[d]] donjons# seront conquis|.^", + {QM_YELLOW, QM_YELLOW, QM_PINK})); + + hintTextTable[RHT_WINCON_TOKENS_HINT] = HintText(CustomMessage("$wAnd the #Triforce# will be granted once #[[d]] Gold Skulltula Token|# is|s# are| retrieved.^", + /*german*/ "$wUnd das #Triforce# wird gewährt, sobald #[[d]] Skulltula-Symbol|# gesammelt wurde|e# gesammelt wurden|.^", + /*french*/ "$wAussi, la #Triforce# sera accordée une fois |qu' #[[d]] symbole de Skulltula d'or #sera récupuéré" + "|que &#[[d]] symboles de Skulltula d'or&#seront recupérés|.^", + {QM_YELLOW, QM_YELLOW, QM_YELLOW})); + + hintTextTable[RHT_WINCON_TRIFORCE_PIECES_HINT] = HintText(CustomMessage("$wAnd the #Triforce# will be granted once #[[d]] Triforce Piece|# is|s# are| retrieved.^", + /*german*/ "$wUnd das #Triforce# wird gewährt, sobald #[[d]] Triforce-Fragment|# gesammelt wurde|e# gesammelt wurden|.^", + /*french*/ "$wAussi, la #Triforce# sera accordée une fois |qu' #[[d]] Morceau de Triforce# aura été récupéré|que #[[d]] Morceaux de Triforce# auront été récupérés|.^", + {QM_YELLOW, QM_YELLOW, QM_YELLOW})); + /*-------------------------- | TRIAL HINT TEXT | ---------------------------*/ diff --git a/soh/soh/Enhancements/randomizer/3drando/hints.cpp b/soh/soh/Enhancements/randomizer/3drando/hints.cpp index 904ab18793..e0335a054a 100644 --- a/soh/soh/Enhancements/randomizer/3drando/hints.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/hints.cpp @@ -211,39 +211,72 @@ const std::array hintSettingTable{{ }}; struct BridgeReqConfig { - RandomizerSettingKey bridgeDirectKey; - RandomizerSettingKey lacsDirectKey; + RandomizerSettingKey bridgeKey; + RandomizerSettingKey gbkKey; + RandomizerSettingKey soulKey; + RandomizerSettingKey winKey; RandoOptionRainbowBridge bridgeEnum; - RandoOptionGanonsBossKey lacsEnum; + RandoOptionGanonsBossKey gbkEnum; + RandoOptionGanonsSoul soulEnum; + RandoOptionWincon winEnum; uint8_t offset; }; -static constexpr BridgeReqConfig StonesConfig{ RSK_RAINBOW_BRIDGE_STONE_COUNT, RSK_LACS_STONE_COUNT, RO_BRIDGE_STONES, - RO_GANON_BOSS_KEY_LACS_STONES, 6 }; -static constexpr BridgeReqConfig MedallionsConfig{ RSK_RAINBOW_BRIDGE_MEDALLION_COUNT, RSK_LACS_MEDALLION_COUNT, - RO_BRIDGE_MEDALLIONS, RO_GANON_BOSS_KEY_LACS_MEDALLIONS, 3 }; -static constexpr BridgeReqConfig TokensConfig{ RSK_RAINBOW_BRIDGE_TOKEN_COUNT, RSK_LACS_TOKEN_COUNT, RO_BRIDGE_TOKENS, - RO_GANON_BOSS_KEY_LACS_TOKENS, 0 }; +static constexpr BridgeReqConfig StonesConfig{ + RSK_RAINBOW_BRIDGE_STONE_COUNT, RSK_GBK_STONE_COUNT, RSK_GANONS_SOUL_STONE_COUNT, + RSK_WINCON_STONE_COUNT, RO_BRIDGE_STONES, RO_GANON_BOSS_KEY_STONES, + RO_GANONS_SOUL_STONES, RO_WINCON_STONES, 6 +}; +static constexpr BridgeReqConfig MedallionsConfig{ + RSK_RAINBOW_BRIDGE_MEDALLION_COUNT, RSK_GBK_MEDALLION_COUNT, RSK_GANONS_SOUL_MEDALLION_COUNT, + RSK_WINCON_MEDALLION_COUNT, RO_BRIDGE_MEDALLIONS, RO_GANON_BOSS_KEY_MEDALLIONS, + RO_GANONS_SOUL_MEDALLIONS, RO_WINCON_MEDALLIONS, 3 +}; +static constexpr BridgeReqConfig TokensConfig{ + RSK_RAINBOW_BRIDGE_TOKEN_COUNT, RSK_GBK_TOKEN_COUNT, RSK_GANONS_SOUL_TOKEN_COUNT, + RSK_WINCON_TOKEN_COUNT, RO_BRIDGE_TOKENS, RO_GANON_BOSS_KEY_TOKENS, + RO_GANONS_SOUL_TOKENS, RO_WINCON_TOKENS, 0 +}; static uint8_t RequiredBySettings(const BridgeReqConfig& cfg) { auto ctx = Rando::Context::GetInstance(); uint8_t count = 0; + if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(cfg.bridgeEnum)) { - count = ctx->GetOption(cfg.bridgeDirectKey).Get(); + count = ctx->GetOption(cfg.bridgeKey).Get(); } else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEON_REWARDS)) { count = ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).Get() - cfg.offset; } else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEONS) && ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) { count = ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Get() - cfg.offset; } - if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(cfg.lacsEnum)) { - count = std::max(count, ctx->GetOption(cfg.lacsDirectKey).Get()); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_REWARDS)) { - count = std::max(count, (uint8_t)(ctx->GetOption(RSK_LACS_REWARD_COUNT).Get() - cfg.offset)); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_DUNGEONS) && + if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(cfg.gbkEnum)) { + count = std::max(count, ctx->GetOption(cfg.gbkKey).Get()); + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_REWARDS)) { + count = std::max(count, (uint8_t)(ctx->GetOption(RSK_GBK_REWARD_COUNT).Get() - cfg.offset)); + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_DUNGEONS) && ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) { - count = std::max(count, (uint8_t)(ctx->GetOption(RSK_LACS_DUNGEON_COUNT).Get() - cfg.offset)); + count = std::max(count, (uint8_t)(ctx->GetOption(RSK_GBK_DUNGEON_COUNT).Get() - cfg.offset)); } + + if (ctx->GetOption(RSK_GANONS_SOUL).Is(cfg.soulEnum)) { + count = std::max(count, ctx->GetOption(cfg.soulKey).Get()); + } else if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_REWARDS)) { + count = std::max(count, (uint8_t)(ctx->GetOption(RSK_GANONS_SOUL_REWARD_COUNT).Get() - cfg.offset)); + } else if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_DUNGEONS) && + ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) { + count = std::max(count, (uint8_t)(ctx->GetOption(RSK_GANONS_SOUL_DUNGEON_COUNT).Get() - cfg.offset)); + } + + if (ctx->GetOption(RSK_WINCON).Is(cfg.winEnum)) { + count = std::max(count, ctx->GetOption(cfg.winKey).Get()); + } else if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_REWARDS)) { + count = std::max(count, (uint8_t)(ctx->GetOption(RSK_WINCON_REWARD_COUNT).Get() - cfg.offset)); + } else if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_DUNGEONS) && + ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) { + count = std::max(count, (uint8_t)(ctx->GetOption(RSK_WINCON_DUNGEON_COUNT).Get() - cfg.offset)); + } + return count; } diff --git a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp index e6146641c5..07b57b6f76 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp @@ -326,27 +326,27 @@ void GenerateItemPool() { AddFixedItemToPool(RG_SHADOW_MEDALLION, 1, rewardIceTraps); AddFixedItemToPool(RG_LIGHT_MEDALLION, 1, rewardIceTraps); - if (ctx->GetOption(RSK_TRIFORCE_HUNT).IsNot(RO_TRIFORCE_HUNT_OFF)) { - AddFixedItemToPool(RG_TRIFORCE_PIECE, ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get() + 1, false); - - switch (ctx->GetOption(RSK_TRIFORCE_HUNT).Get()) { - case RO_TRIFORCE_HUNT_OFF: - break; - case RO_TRIFORCE_HUNT_WIN: - ctx->PlaceItemInLocation(RC_TRIFORCE_COMPLETED, RG_TRIFORCE); // Win condition - ctx->PlaceItemInLocation(RC_GANON, RG_BLUE_RUPEE, false, true); - break; - case RO_TRIFORCE_HUNT_GBK: - ctx->PlaceItemInLocation(RC_TRIFORCE_COMPLETED, RG_GANONS_CASTLE_BOSS_KEY); - ctx->PlaceItemInLocation(RC_GANON, RG_TRIFORCE); // Win condition - break; - } - } else { - ctx->PlaceItemInLocation(RC_GANON, RG_TRIFORCE); // Win condition + if (ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get() > 0) { + AddFixedItemToPool(RG_TRIFORCE_PIECE, ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get(), false); } // Fixed item locations ctx->PlaceItemInLocation(RC_HC_ZELDAS_LETTER, RG_ZELDAS_LETTER); + ctx->PlaceItemInLocation(RC_GANONS_BOSS_KEY, RG_BLUE_RUPEE); // placeholder, filled by setting + ctx->PlaceItemInLocation(RC_GANON_SOUL, RG_BLUE_RUPEE); // placeholder, filled by setting + ctx->PlaceItemInLocation(RC_WINCON, RG_BLUE_RUPEE); // placeholder, filled by setting + + if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_DEFEAT_GANON)) { + ctx->PlaceItemInLocation(RC_GANON, RG_TRIFORCE); // Win condition + } else { + // Ganon isn't the win condition, so slaying him is optional and just hands out a junk reward. + ctx->PlaceItemInLocation(RC_GANON, RG_BLUE_RUPEE, false, true); + if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_ANYWHERE)) { + AddFixedItemToPool(RG_TRIFORCE, 1); + } else { + ctx->PlaceItemInLocation(RC_WINCON, RG_TRIFORCE); + } + } if (!ctx->GetOption(RSK_STARTING_KOKIRI_SWORD)) { if (ctx->GetOption(RSK_SHUFFLE_KOKIRI_SWORD)) { @@ -625,9 +625,6 @@ void GenerateItemPool() { AddItemToPool(RG_MORPHA_SOUL, 2, 1, 1, 1); AddItemToPool(RG_BONGO_BONGO_SOUL, 2, 1, 1, 1); AddItemToPool(RG_TWINROVA_SOUL, 2, 1, 1, 1); - if (ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).Is(RO_BOSS_SOULS_ON_PLUS_GANON)) { - AddItemToPool(RG_GANON_SOUL, 2, 1, 1, 1); - } } // Gerudo Fortress @@ -727,12 +724,9 @@ void GenerateItemPool() { AddItemToPool(RG_SHADOW_TEMPLE_BOSS_KEY, 2, 1, 1, 1); } - // Don't add GBK to the pool at all for Triforce Hunt or if we start with it. - if (!(ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_STARTWITH) || ctx->GetOption(RSK_TRIFORCE_HUNT))) { - if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_KAK_TOKENS)) { - ctx->PlaceItemInLocation(RC_KAK_100_GOLD_SKULLTULA_REWARD, RG_GANONS_CASTLE_BOSS_KEY); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Get() >= RO_GANON_BOSS_KEY_LACS_VANILLA) { - ctx->PlaceItemInLocation(RC_TOT_LIGHT_ARROWS_CUTSCENE, RG_GANONS_CASTLE_BOSS_KEY); + if (!(ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_STARTWITH))) { + if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Get() >= RO_GANON_BOSS_KEY_STONES) { + ctx->PlaceItemInLocation(RC_GANONS_BOSS_KEY, RG_GANONS_CASTLE_BOSS_KEY); } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_VANILLA)) { ctx->PlaceItemInLocation(RC_GANONS_TOWER_BOSS_KEY_CHEST, RG_GANONS_CASTLE_BOSS_KEY); } else { @@ -740,6 +734,14 @@ void GenerateItemPool() { } } + if (!(ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_STARTWITH))) { + if (ctx->GetOption(RSK_GANONS_SOUL).Get() >= RO_GANONS_SOUL_STONES) { + ctx->PlaceItemInLocation(RC_GANON_SOUL, RG_GANON_SOUL); + } else { + AddItemToPool(RG_GANON_SOUL, 2, 1, 1, 1); + } + } + // Shopsanity if (ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_OFF) || (ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_SPECIFIC_COUNT) && diff --git a/soh/soh/Enhancements/randomizer/3drando/menu.cpp b/soh/soh/Enhancements/randomizer/3drando/menu.cpp index d891abd129..44d9eba403 100644 --- a/soh/soh/Enhancements/randomizer/3drando/menu.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/menu.cpp @@ -43,7 +43,7 @@ bool GenerateRandomizer(std::set excludedLocations, std::setClearItemLocations(); int ret = Playthrough::Playthrough_Init(ctx->GetSeed(), excludedLocations, enabledTricks); if (ret < 0) { - if (ret == -1) { // Failed to generate after 5 tries + if (ret == -1) { SPDLOG_ERROR("Failed to generate after 5 tries."); return false; } else { diff --git a/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp b/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp index a7fe551020..f3c023a6ff 100644 --- a/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp @@ -53,10 +53,7 @@ void GenerateStartingInventory() { AddItemToInventory(RG_SHADOW_TEMPLE_BOSS_KEY); } - // Add Ganon's Boss key with Triforce Hunt's Win setting so the game thinks it's obtainable from the start. - // During save init, the boss key isn't actually given and it's instead given when completing the triforce. - if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_STARTWITH) || - ctx->GetOption(RSK_TRIFORCE_HUNT).Is(RO_TRIFORCE_HUNT_WIN)) { + if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_STARTWITH)) { AddItemToInventory(RG_GANONS_CASTLE_BOSS_KEY); } diff --git a/soh/soh/Enhancements/randomizer/Messages/ItemMessages.cpp b/soh/soh/Enhancements/randomizer/Messages/ItemMessages.cpp index d52dba31cb..3624448e8f 100644 --- a/soh/soh/Enhancements/randomizer/Messages/ItemMessages.cpp +++ b/soh/soh/Enhancements/randomizer/Messages/ItemMessages.cpp @@ -15,6 +15,7 @@ #include #include +#include extern "C" { #include @@ -24,47 +25,111 @@ extern PlayState* gPlayState; } void BuildTriforcePieceMessage(CustomMessage& msg) { + auto rando = OTRGlobals::Instance->gRandomizer; uint8_t current = gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected + 1; - uint8_t required = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) + 1; - uint8_t remaining = required - current; - float percentageCollected = (float)current / (float)required; + // if any settings are off, 0 them out here as a precaution + uint8_t bridge = rando->GetRandoSettingValue(RSK_RAINBOW_BRIDGE) == RO_BRIDGE_TRIFORCE_PIECES + ? rando->GetRandoSettingValue(RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT) + : 0; + uint8_t wincon = rando->GetRandoSettingValue(RSK_WINCON) == RO_WINCON_TRIFORCE_PIECES + ? rando->GetRandoSettingValue(RSK_WINCON_TRIFORCE_COUNT) + : 0; + uint8_t GBK = rando->GetRandoSettingValue(RSK_GANONS_BOSS_KEY) == RO_GANON_BOSS_KEY_TRIFORCE_PIECES + ? rando->GetRandoSettingValue(RSK_GBK_TRIFORCE_COUNT) + : 0; + uint8_t soul = rando->GetRandoSettingValue(RSK_GANONS_SOUL) == RO_GANONS_SOUL_TRIFORCE_PIECES + ? rando->GetRandoSettingValue(RSK_GANONS_SOUL_TRIFORCE_COUNT) + : 0; - if (percentageCollected <= 0.25) { - msg = { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. It's a start!", - "Ein %yTriforce-Splitter%w! Du hast&%g[[current]]%w von %c[[required]]%w gefunden. Es ist ein&Anfang!", - "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste " - "%c[[remaining]]%w à trouver. C'est un début!" }; - } else if (percentageCollected <= 0.5) { - msg = { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Progress!", - "Ein %yTriforce-Splitter%w! Du hast&%g[[current]]%w von %c[[required]]%w gefunden. Es geht voran!", - "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste " - "%c[[remaining]]%w à trouver. Ça avance!" }; - } else if (percentageCollected <= 0.75) { - msg = { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Over half-way&there!", - "Ein %yTriforce-Splitter%w! Du hast&schon %g[[current]]%w von %c[[required]]%w gefunden. Schon&über " - "die Hälfte!", - "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste " - "%c[[remaining]]%w à trouver. Il en reste un&peu moins que la moitié!" }; - } else if (percentageCollected < 1.0) { - msg = { - "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Almost done!", - "Ein %yTriforce-Splitter%w! Du hast&schon %g[[current]]%w von %c[[required]]%w gefunden. Fast&geschafft!", - "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w " - "à trouver. C'est presque&terminé!" - }; - } else if (current == required) { - msg = { "You completed the %yTriforce of&Courage%w! %gGG%w!", - "Das %yTriforce des Mutes%w! Du hast&alle Splitter gefunden. %gGut gemacht%w!", - "Vous avez complété la %yTriforce&du Courage%w! %gFélicitations%w!" }; + // If we reach wincon, we win! + if (current == wincon) { + msg = { "You completed the %yTriforce of Courage%w! %gGG%w!", + "Das %yTriforce des Mutes%w! Du hast alle Splitter gefunden. %gGut gemacht%w!", + "Vous avez complété la %yTriforce du Courage%w! %gFélicitations%w!" }; + // otherwise prioritise the different triggers + } else if (current == bridge) { + msg = { "You made your wish to the %yTriforce%w! %rTh%ye R%gai%cnb%bow %pBr%rid%yge %gha%cs r%bai%psed%w!", + TODO_TRANSLATE, TODO_TRANSLATE }; + } else if (current == GBK) { + msg = { "You completed the %yTriforce of Power%w! %rThe Key to Evil is yours%w!", TODO_TRANSLATE, + TODO_TRANSLATE }; + } else if (current == soul) { + msg = { "You completed the %yTriforce of Wisdom%w! %bGanon's soul is reclaimed%w!", TODO_TRANSLATE, + TODO_TRANSLATE }; + // if everything is zero, then there's no goal... + } else if (bridge + wincon + GBK + soul == 0) { + msg = { "You found a %yTriforce Piece%w! But it's %puseless%w...", TODO_TRANSLATE, TODO_TRANSLATE }; } else { - msg = { "You found a spare %yTriforce Piece%w!&You only needed %c[[required]]%w, but you have %g[[current]]%w!", - "Ein übriger %yTriforce-Splitter%w! Du&hast nun %g[[current]]%w von %c[[required]]%w nötigen gefunden.", - "Vous avez trouvé un %yFragment de&Triforce%w en plus! Vous n'aviez besoin&que de %c[[required]]%w, " - "mais vous en avez %g[[current]]%w en&tout!" }; + // if nothing is complete, we need to check is we have more than we need + uint8_t highest = std::max({ current, bridge, wincon, GBK, soul }); + if (highest == current) { + // RANDOTODO TODO_TRANSLATE you could maybe make this sound cleaner because InsertNumber allows for dynamic + // plurals + msg = { "You found a spare %yTriforce Piece%w! You only needed %c[[d]]%w, but you have %g[[current]]%w!", + "Ein übriger %yTriforce-Splitter%w! Du hast nun %g[[current]]%w von %c[[d]]%w nötigen gefunden.", + "Vous avez trouvé un %yFragment de Triforce%w en plus! Vous n'aviez besoin que de %c[[d]]%w, " + "mais vous en avez %g[[current]]%w en tout!" }; + msg.InsertNumber(std::max({ bridge, wincon, GBK, soul })); + } else { + // find the next goal by setting everything below current (including failed conditions set to 0 before) + // to a high number, then looking for the lowest. + // if we have the exact amount, it will be caught by the first check, so no worries there + if (bridge < current) { + bridge = 255; + } + if (GBK < current) { + GBK = 255; + } + if (soul < current) { + soul = 255; + } + if (wincon < current) { + wincon = 255; + } + uint8_t next = std::min({ bridge, GBK, soul, wincon }); + + uint8_t remaining = next - current; + float percentageCollected = (float)current / (float)next; + + if (percentageCollected <= 0.25) { + msg = { "You found a %yTriforce Piece%w! %g[[current]]%w down, %c[[d]]%w more and you [[condition]]! " + "It's a start!", + TODO_TRANSLATE, TODO_TRANSLATE }; + } else if (percentageCollected <= 0.5) { + msg = { "You found a %yTriforce Piece%w! that makes %g[[current]]%w, %c[[d]]%w to go until you " + "[[condition]]! Progress!", + TODO_TRANSLATE, TODO_TRANSLATE }; + } else if (percentageCollected <= 0.75) { + msg = { "You found a %yTriforce Piece%w! You have %g[[current]]%w and need %c[[d]]%w more and you " + "[[condition]]! Over half-way there!", + TODO_TRANSLATE, TODO_TRANSLATE }; + } else if (percentageCollected < 1.0) { + msg = { "You found a %yTriforce Piece%w! %g[[current]]%w down, %c[[d]]%w left until you [[condition]]! " + "Almost done!", + TODO_TRANSLATE, TODO_TRANSLATE }; + } + + // default condition is soul + CustomMessage condition = { "%brelease Ganons Soul%w", TODO_TRANSLATE, TODO_TRANSLATE }; + if (next == wincon) { + condition = { "%gWin the game%w", TODO_TRANSLATE, TODO_TRANSLATE }; + } else if (next == bridge) { + condition = { "%csummon the Rainbow Bridge%w", TODO_TRANSLATE, TODO_TRANSLATE }; + } else if (next == GBK) { + condition = { "%rfind the key to Ganondorf's Lair%w", TODO_TRANSLATE, TODO_TRANSLATE }; + } + msg.Replace("[[condition]]", condition); + msg.InsertNumber(remaining); + } } msg.Replace("[[current]]", std::to_string(current)); - msg.Replace("[[remaining]]", std::to_string(remaining)); - msg.Replace("[[required]]", std::to_string(required)); + msg.AutoFormat(ITEM_CUSTOM); +} + +void BuildTriforceMessage(CustomMessage& msg) { + msg = { "You completed the %yTriforce of&Courage%w! %gGG%w!", + "Das %yTriforce des Mutes%w! Du hast&alle Splitter gefunden. %gGut gemacht%w!", + "Vous avez complété la %yTriforce&du Courage%w! %gFélicitations%w!" }; msg.Format(ITEM_CUSTOM); } @@ -100,12 +165,16 @@ void LoadCustomItemIcon(bool displayAsEnglish) { RandomizerGet rgid = static_cast(player->getItemEntry.getItemId); customIcon = Rando::StaticData::RetrieveItem(rgid).GetCustomIcon(); iconSize = Rando::StaticData::RetrieveItem(rgid).GetCustomIconSize(); + } else { + // if we're seeing an icon and we don't have a GI, assume we're in the alter text showing a triforce piece + customIcon = Rando::StaticData::RetrieveItem(RG_TRIFORCE_PIECE).GetCustomIcon(); + iconSize = Rando::StaticData::RetrieveItem(RG_TRIFORCE_PIECE).GetCustomIconSize(); } if (customIcon != nullptr) { static int16_t sIconItem32XOffsets[] = { 74, 74, 74, 54 }; static int16_t sIconItem24XOffsets[] = { 72, 72, 72, 50 }; MessageContext* msgCtx = &gPlayState->msgCtx; - uint8_t language = displayAsEnglish ? LANGUAGE_ENG : gSaveContext.language; + uint8_t language = displayAsEnglish ? LANGUAGE_ENG : (Language)gSaveContext.language; if (iconSize == ICON_SIZE_32) { R_TEXTBOX_ICON_XPOS = R_TEXT_INIT_XPOS - sIconItem32XOffsets[language]; R_TEXTBOX_ICON_YPOS = (R_TEXTBOX_Y + 10) + 6; @@ -150,6 +219,8 @@ void BuildItemMessage(u16* textId, bool* loadFromMessageTable) { Rando::Traps::BuildIceTrapMessage(msg, player->getItemEntry); } else if (player->getItemEntry.getItemId == RG_TRIFORCE_PIECE) { BuildTriforcePieceMessage(msg); + } else if (player->getItemEntry.getItemId == RG_TRIFORCE) { + BuildTriforceMessage(msg); } else { BuildCustomItemMessage(player, msg); } diff --git a/soh/soh/Enhancements/randomizer/Plandomizer.cpp b/soh/soh/Enhancements/randomizer/Plandomizer.cpp index f77a1d3098..81fbd7ed27 100644 --- a/soh/soh/Enhancements/randomizer/Plandomizer.cpp +++ b/soh/soh/Enhancements/randomizer/Plandomizer.cpp @@ -255,6 +255,7 @@ std::unordered_map itemImageMap = { { RG_FISHING_POLE, "ITEM_FISHING_POLE" }, { RG_SOLD_OUT, "ITEM_SOLD_OUT" }, { RG_TRIFORCE_PIECE, "TRIFORCE_PIECE" }, + { RG_TRIFORCE, "TRIFORCE" }, { RG_SKELETON_KEY, "ITEM_KEY_SMALL" } }; @@ -1207,4 +1208,6 @@ void PlandomizerWindow::InitElement() { ->LoadGuiTexture("BOSS_SOUL", gBossSoulTex, ImVec4(1, 1, 1, 1)); std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()) ->LoadGuiTexture("TRIFORCE_PIECE", gTriforcePieceTex, ImVec4(1, 1, 1, 1)); + std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()) + ->LoadGuiTexture("TRIFORCE", gTriforcePieceTex, ImVec4(1, 1, 1, 1)); } diff --git a/soh/soh/Enhancements/randomizer/SeedContext.cpp b/soh/soh/Enhancements/randomizer/SeedContext.cpp index c07c2f7ff3..b150f804fa 100644 --- a/soh/soh/Enhancements/randomizer/SeedContext.cpp +++ b/soh/soh/Enhancements/randomizer/SeedContext.cpp @@ -152,6 +152,10 @@ bool Context::IsQuestOfLocationActive(RandomizerCheck rc) { void Context::GenerateLocationPool() { allLocations.clear(); overworldLocations.clear(); + // add wincon here so it is properly logged + if (ctx->GetOption(RSK_WINCON).Get() > RO_WINCON_ANYWHERE) { + AddLocation(RC_WINCON); + } for (auto dungeon : ctx->GetDungeons()->GetDungeonList()) { dungeon->locations.clear(); } @@ -159,7 +163,9 @@ void Context::GenerateLocationPool() { // skip RCs that shouldn't be in the pool for any reason (i.e. settings, unsupported check type, etc.) // TODO: Exclude checks for some of the older shuffles from the pool too i.e. Frog Songs, Scrubs, etc.) if (location.GetRandomizerCheck() == RC_UNKNOWN_CHECK || - location.GetRandomizerCheck() == RC_TRIFORCE_COMPLETED || // already in pool + location.GetRandomizerCheck() == RC_WINCON || // already in pool + (location.GetRandomizerCheck() == RC_GANONS_BOSS_KEY && mGBKCondition == RO_CHECK_TRIGGER_NONE) || + (location.GetRandomizerCheck() == RC_GANON_SOUL && mGanonsSoulCondition == RO_CHECK_TRIGGER_NONE) || (location.GetRandomizerCheck() == RC_TOT_MASTER_SWORD && mOptions[RSK_SHUFFLE_MASTER_SWORD].Is(RO_GENERIC_OFF)) || (location.GetRandomizerCheck() == RC_KAK_100_GOLD_SKULLTULA_REWARD && @@ -264,7 +270,7 @@ void Context::GenerateLocationPool() { void Context::AddExcludedOptions() { for (auto& loc : StaticData::GetLocationTable()) { // Checks of these types don't have items, skip them. - if (loc.GetRandomizerCheck() == RC_UNKNOWN_CHECK || loc.GetRandomizerCheck() == RC_TRIFORCE_COMPLETED || + if (loc.GetRandomizerCheck() == RC_UNKNOWN_CHECK || loc.GetRandomizerCheck() == RC_WINCON || loc.GetRCType() == RCTYPE_CHEST_GAME || loc.GetRCType() == RCTYPE_STATIC_HINT || loc.GetRCType() == RCTYPE_GOSSIP_STONE) { continue; @@ -533,12 +539,28 @@ OptionValue& Context::GetLocationOption(const RandomizerCheck key) { return itemLocationTable[key].GetExcludedOption(); } -RandoOptionLACSCondition Context::LACSCondition() const { - return mLACSCondition; +RandoOptionCheckTrigger Context::GBKCondition() const { + return mGBKCondition; } -void Context::LACSCondition(RandoOptionLACSCondition lacsCondition) { - mLACSCondition = lacsCondition; +void Context::GBKCondition(RandoOptionCheckTrigger condition) { + mGBKCondition = condition; +} + +RandoOptionCheckTrigger Context::GanonsSoulCondition() const { + return mGanonsSoulCondition; +} + +void Context::GanonsSoulCondition(RandoOptionCheckTrigger condition) { + mGanonsSoulCondition = condition; +} + +RandoOptionWincon Context::WinCondition() const { + return mWinCondition; +} + +void Context::WinCondition(RandoOptionWincon condition) { + mWinCondition = condition; } std::shared_ptr Context::GetKaleido() { diff --git a/soh/soh/Enhancements/randomizer/SeedContext.h b/soh/soh/Enhancements/randomizer/SeedContext.h index 40c9fb22bf..08510eb036 100644 --- a/soh/soh/Enhancements/randomizer/SeedContext.h +++ b/soh/soh/Enhancements/randomizer/SeedContext.h @@ -100,20 +100,24 @@ class Context { OptionValue& GetLocationOption(RandomizerCheck key); /** - * @brief Gets the resolved Light Arrow CutScene check condition. + * @brief Gets the resolved GBK check condition. * There is no direct option for this, it is inferred based on the value of a few other options. * - * @return RandoOptionLACSCondition + * @return RandoOptionCheckTriggerCondition */ - RandoOptionLACSCondition LACSCondition() const; + RandoOptionCheckTrigger GBKCondition() const; + RandoOptionCheckTrigger GanonsSoulCondition() const; + RandoOptionWincon WinCondition() const; /** - * @brief Sets the resolved Light Arrow CutScene check condition. + * @brief Sets the resolved GBK check condition. * There is no direct option for this, it is inferred based on the value of a few other options. * - * @param lacsCondition + * @param condition */ - void LACSCondition(RandoOptionLACSCondition lacsCondition); + void GBKCondition(RandoOptionCheckTrigger condition); + void GanonsSoulCondition(RandoOptionCheckTrigger condition); + void WinCondition(RandoOptionWincon condition); GetItemEntry GetFinalGIEntry(RandomizerCheck rc, bool checkObtainability = true, GetItemID ogItemId = GI_NONE); void ParseSpoiler(const char* spoilerFileName); @@ -183,7 +187,9 @@ class Context { std::array itemLocationTable = {}; std::array mOptions; std::array mTrickOptions; - RandoOptionLACSCondition mLACSCondition = RO_LACS_VANILLA; + RandoOptionCheckTrigger mGBKCondition = RO_CHECK_TRIGGER_NONE; + RandoOptionCheckTrigger mGanonsSoulCondition = RO_CHECK_TRIGGER_NONE; + RandoOptionWincon mWinCondition = RO_WINCON_DEFEAT_GANON; std::shared_ptr mEntranceShuffler; std::shared_ptr mDungeons; std::shared_ptr mLogic; diff --git a/soh/soh/Enhancements/randomizer/Traps.cpp b/soh/soh/Enhancements/randomizer/Traps.cpp index c140031f51..7d6389222b 100644 --- a/soh/soh/Enhancements/randomizer/Traps.cpp +++ b/soh/soh/Enhancements/randomizer/Traps.cpp @@ -661,6 +661,12 @@ static void InitTrickNames() { Text{ "Triforce Shard", "Éclat de Triforce", "Triforce-Fragment" }, // "Triforce Shard" Text{ "Shiny Rock", "Caillou Brillant", "glänzender Stein" }, // "Shiny Rock" }; + trickNameTable[RG_TRIFORCE] = { + // TODO_TRANSLATE + Text{ "Cheese Triangle" }, + Text{ "Triumph Fork" }, + Text{ "Force Gem" }, + }; trickNameTable[RG_ROCS_FEATHER] = { Text{ "Chicken Wing", "Chicken Wing", "Chicken Wing" }, // "Chicken Wing" Text{ "Roc's Leg", "Roc's Leg", "Roc's Leg" }, // "Roc's Leg" diff --git a/soh/soh/Enhancements/randomizer/draw.cpp b/soh/soh/Enhancements/randomizer/draw.cpp index 9fabbb2fd3..541813726a 100644 --- a/soh/soh/Enhancements/randomizer/draw.cpp +++ b/soh/soh/Enhancements/randomizer/draw.cpp @@ -408,8 +408,21 @@ extern "C" void Randomizer_DrawTriforcePieceGI(PlayState* play, GetItemEntry get Gfx_SetupDL_25Xlu(play->state.gfxCtx); + auto rando = OTRGlobals::Instance->gRandomizer; uint8_t current = gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected; - uint8_t required = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) + 1; + bool fullTriforce = false; + if (rando->GetRandoSettingValue(RSK_RAINBOW_BRIDGE) == RO_BRIDGE_TRIFORCE_PIECES) { + fullTriforce = rando->GetRandoSettingValue(RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT) == current; + } + if (rando->GetRandoSettingValue(RSK_WINCON) == RO_WINCON_TRIFORCE_PIECES) { + fullTriforce = fullTriforce || (rando->GetRandoSettingValue(RSK_WINCON_TRIFORCE_COUNT) == current); + } + if (rando->GetRandoSettingValue(RSK_GANONS_BOSS_KEY) == RO_GANON_BOSS_KEY_TRIFORCE_PIECES) { + fullTriforce = fullTriforce || (rando->GetRandoSettingValue(RSK_GBK_TRIFORCE_COUNT) == current); + } + if (rando->GetRandoSettingValue(RSK_GANONS_SOUL) == RO_GANONS_SOUL_TRIFORCE_PIECES) { + fullTriforce = fullTriforce || (rando->GetRandoSettingValue(RSK_GANONS_SOUL_TRIFORCE_COUNT) == current); + } Matrix_Scale(triforcePieceScale, triforcePieceScale, triforcePieceScale, MTXMODE_APPLY); @@ -421,7 +434,7 @@ extern "C" void Randomizer_DrawTriforcePieceGI(PlayState* play, GetItemEntry get // Animation. When not the completed triforce, create delay before showing the piece to bypass interpolation. // If the completed triforce, make it grow slowly. - if (current != required) { + if (!fullTriforce) { if (triforcePieceScale > 0.00008f && triforcePieceScale < 0.034f) { triforcePieceScale = 0.034f; } else if (triforcePieceScale < 0.035f) { @@ -436,13 +449,13 @@ extern "C" void Randomizer_DrawTriforcePieceGI(PlayState* play, GetItemEntry get // Show piece when not currently completing the triforce. Use the scale to create a delay so interpolation doesn't // make the triforce twitch when the size is set to a higher value. - if (current != required && triforcePieceScale > 0.035f) { + if (!fullTriforce && triforcePieceScale > 0.035f) { // Get shard DL. Remove one before division to account for triforce piece given in the textbox // to match up the shard from the overworld model. Gfx* triforcePieceDL = Randomizer_GetTriforcePieceDL((current - 1) % 3); gSPDisplayList(POLY_XLU_DISP++, triforcePieceDL); - } else if (current == required && triforcePieceScale > 0.00008f) { + } else if (fullTriforce && triforcePieceScale > 0.00008f) { gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gTriforcePieceCompletedDL); } diff --git a/soh/soh/Enhancements/randomizer/hint.cpp b/soh/soh/Enhancements/randomizer/hint.cpp index 17328f8d13..ab73250cb6 100644 --- a/soh/soh/Enhancements/randomizer/hint.cpp +++ b/soh/soh/Enhancements/randomizer/hint.cpp @@ -306,7 +306,7 @@ const CustomMessage Hint::GetHintMessage(MessageFormat format, size_t id) const } else { hintText.SetTextBoxType(TEXTBOX_TYPE_BLUE); } - hintText += GetBridgeReqsText() + GetGanonBossKeyText() + + hintText += GetBridgeReqsText() + GetGanonBossKeyText() + GetGanonsSoulText() + GetWinconText() + StaticData::hintTextTable[RHT_ADULT_ALTAR_TEXT_END].GetHintMessage(); } else { hintText = GetHintText(id).GetHintMessage(chosenMessage); @@ -570,6 +570,9 @@ CustomMessage Hint::GetBridgeReqsText() { } else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS)) { bridgeMessage = StaticData::hintTextTable[RHT_BRIDGE_TOKENS_HINT].GetHintMessage(); bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Get()); + } else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TRIFORCE_PIECES)) { + bridgeMessage = StaticData::hintTextTable[RHT_BRIDGE_TRIFORCE_PIECES_HINT].GetHintMessage(); + bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT).Get()); } else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_GREG)) { return StaticData::hintTextTable[RHT_BRIDGE_GREG_HINT].GetHintMessage(); } @@ -580,10 +583,6 @@ CustomMessage Hint::GetGanonBossKeyText() { auto ctx = Rando::Context::GetInstance(); CustomMessage ganonBossKeyMessage; - if (ctx->GetOption(RSK_TRIFORCE_HUNT).IsNot(RO_TRIFORCE_HUNT_OFF)) { - return StaticData::hintTextTable[RHT_GANON_BK_TRIFORCE_HINT].GetHintMessage(); - } - if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_STARTWITH)) { return StaticData::hintTextTable[RHT_GANON_BK_START_WITH_HINT].GetHintMessage(); } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_VANILLA)) { @@ -596,29 +595,82 @@ CustomMessage Hint::GetGanonBossKeyText() { return StaticData::hintTextTable[RHT_GANON_BK_OVERWORLD_HINT].GetHintMessage(); } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_ANYWHERE)) { return StaticData::hintTextTable[RHT_GANON_BK_ANYWHERE_HINT].GetHintMessage(); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_KAK_TOKENS)) { - return StaticData::hintTextTable[RHT_GANON_BK_SKULLTULA_HINT].GetHintMessage(); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_VANILLA)) { - return StaticData::hintTextTable[RHT_LACS_VANILLA_HINT].GetHintMessage(); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_STONES)) { - ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_STONES_HINT].GetHintMessage(); - ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_STONE_COUNT).Get()); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_MEDALLIONS)) { - ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_MEDALLIONS_HINT].GetHintMessage(); - ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_MEDALLION_COUNT).Get()); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_REWARDS)) { - ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_REWARDS_HINT].GetHintMessage(); - ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_REWARD_COUNT).Get()); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_DUNGEONS)) { - ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_DUNGEONS_HINT].GetHintMessage(); - ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_DUNGEON_COUNT).Get()); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_TOKENS)) { - ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_TOKENS_HINT].GetHintMessage(); - ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_TOKEN_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_STONES)) { + ganonBossKeyMessage = StaticData::hintTextTable[RHT_GBK_STONES_HINT].GetHintMessage(); + ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_GBK_STONE_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_MEDALLIONS)) { + ganonBossKeyMessage = StaticData::hintTextTable[RHT_GBK_MEDALLIONS_HINT].GetHintMessage(); + ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_GBK_MEDALLION_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_REWARDS)) { + ganonBossKeyMessage = StaticData::hintTextTable[RHT_GBK_REWARDS_HINT].GetHintMessage(); + ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_GBK_REWARD_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_DUNGEONS)) { + ganonBossKeyMessage = StaticData::hintTextTable[RHT_GBK_DUNGEONS_HINT].GetHintMessage(); + ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_GBK_DUNGEON_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_TOKENS)) { + ganonBossKeyMessage = StaticData::hintTextTable[RHT_GBK_TOKENS_HINT].GetHintMessage(); + ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_GBK_TOKEN_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_TRIFORCE_PIECES)) { + ganonBossKeyMessage = StaticData::hintTextTable[RHT_GBK_TRIFORCE_PIECES_HINT].GetHintMessage(); + ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_GBK_TRIFORCE_COUNT).Get()); } return ganonBossKeyMessage; } +CustomMessage Hint::GetGanonsSoulText() { + auto ctx = Rando::Context::GetInstance(); + CustomMessage ganonsSoulMessage; + + if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_STONES)) { + ganonsSoulMessage = StaticData::hintTextTable[RHT_GANONS_SOUL_STONES_HINT].GetHintMessage(); + ganonsSoulMessage.InsertNumber(ctx->GetOption(RSK_GANONS_SOUL_STONE_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_MEDALLIONS)) { + ganonsSoulMessage = StaticData::hintTextTable[RHT_GANONS_SOUL_MEDALLIONS_HINT].GetHintMessage(); + ganonsSoulMessage.InsertNumber(ctx->GetOption(RSK_GANONS_SOUL_MEDALLION_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_REWARDS)) { + ganonsSoulMessage = StaticData::hintTextTable[RHT_GANONS_SOUL_REWARDS_HINT].GetHintMessage(); + ganonsSoulMessage.InsertNumber(ctx->GetOption(RSK_GANONS_SOUL_REWARD_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_DUNGEONS)) { + ganonsSoulMessage = StaticData::hintTextTable[RHT_GANONS_SOUL_DUNGEONS_HINT].GetHintMessage(); + ganonsSoulMessage.InsertNumber(ctx->GetOption(RSK_GANONS_SOUL_DUNGEON_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_TOKENS)) { + ganonsSoulMessage = StaticData::hintTextTable[RHT_GANONS_SOUL_TOKENS_HINT].GetHintMessage(); + ganonsSoulMessage.InsertNumber(ctx->GetOption(RSK_GANONS_SOUL_TOKEN_COUNT).Get()); + } else if (ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_TRIFORCE_PIECES)) { + ganonsSoulMessage = StaticData::hintTextTable[RHT_GANONS_SOUL_TRIFORCE_PIECES_HINT].GetHintMessage(); + ganonsSoulMessage.InsertNumber(ctx->GetOption(RSK_GANONS_SOUL_TRIFORCE_COUNT).Get()); + } + return ganonsSoulMessage; +} + +CustomMessage Hint::GetWinconText() { + auto ctx = Rando::Context::GetInstance(); + CustomMessage winconMessage; + + if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_ANYWHERE)) { + return StaticData::hintTextTable[RHT_WINCON_ANYWHERE_HINT].GetHintMessage(); + } else if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_STONES)) { + winconMessage = StaticData::hintTextTable[RHT_WINCON_STONES_HINT].GetHintMessage(); + winconMessage.InsertNumber(ctx->GetOption(RSK_WINCON_STONE_COUNT).Get()); + } else if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_MEDALLIONS)) { + winconMessage = StaticData::hintTextTable[RHT_WINCON_MEDALLIONS_HINT].GetHintMessage(); + winconMessage.InsertNumber(ctx->GetOption(RSK_WINCON_MEDALLION_COUNT).Get()); + } else if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_REWARDS)) { + winconMessage = StaticData::hintTextTable[RHT_WINCON_REWARDS_HINT].GetHintMessage(); + winconMessage.InsertNumber(ctx->GetOption(RSK_WINCON_REWARD_COUNT).Get()); + } else if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_DUNGEONS)) { + winconMessage = StaticData::hintTextTable[RHT_WINCON_DUNGEONS_HINT].GetHintMessage(); + winconMessage.InsertNumber(ctx->GetOption(RSK_WINCON_DUNGEON_COUNT).Get()); + } else if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_TOKENS)) { + winconMessage = StaticData::hintTextTable[RHT_WINCON_TOKENS_HINT].GetHintMessage(); + winconMessage.InsertNumber(ctx->GetOption(RSK_WINCON_TOKEN_COUNT).Get()); + } else if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_TRIFORCE_PIECES)) { + winconMessage = StaticData::hintTextTable[RHT_WINCON_TRIFORCE_PIECES_HINT].GetHintMessage(); + winconMessage.InsertNumber(ctx->GetOption(RSK_WINCON_TRIFORCE_COUNT).Get()); + } + return winconMessage; +} + void Hint::AddHintedLocation(RandomizerCheck location) { locations.push_back(location); } diff --git a/soh/soh/Enhancements/randomizer/hint.h b/soh/soh/Enhancements/randomizer/hint.h index 4dbce61a6d..e73e79184e 100644 --- a/soh/soh/Enhancements/randomizer/hint.h +++ b/soh/soh/Enhancements/randomizer/hint.h @@ -34,6 +34,8 @@ class Hint { const CustomMessage GetAreaName(uint8_t slot) const; static CustomMessage GetBridgeReqsText(); static CustomMessage GetGanonBossKeyText(); + static CustomMessage GetGanonsSoulText(); + static CustomMessage GetWinconText(); void AddHintedLocation(RandomizerCheck location); std::vector GetHintedLocations() const; void SetHintType(HintType type); diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index e105b364fa..2618055830 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -123,42 +123,97 @@ RandomizerCheck GetRandomizerCheckFromSceneFlag(int16_t sceneNum, int16_t flagTy return RC_UNKNOWN_CHECK; } -bool MeetsLACSRequirements() { - switch (RAND_GET_OPTION(RSK_GANONS_BOSS_KEY).Get()) { - case RO_GANON_BOSS_KEY_LACS_STONES: - if ((CheckStoneCount() + CheckLACSRewardCount()) >= RAND_GET_OPTION(RSK_LACS_STONE_COUNT).Get()) { - return true; - } - break; - case RO_GANON_BOSS_KEY_LACS_MEDALLIONS: - if ((CheckMedallionCount() + CheckLACSRewardCount()) >= RAND_GET_OPTION(RSK_LACS_MEDALLION_COUNT).Get()) { - return true; - } - break; - case RO_GANON_BOSS_KEY_LACS_REWARDS: - if ((CheckMedallionCount() + CheckStoneCount() + CheckLACSRewardCount()) >= - RAND_GET_OPTION(RSK_LACS_REWARD_COUNT).Get()) { - return true; - } - break; - case RO_GANON_BOSS_KEY_LACS_DUNGEONS: - if ((CheckDungeonCount() + CheckLACSRewardCount()) >= RAND_GET_OPTION(RSK_LACS_DUNGEON_COUNT).Get()) { - return true; - } - break; - case RO_GANON_BOSS_KEY_LACS_TOKENS: - if (gSaveContext.inventory.gsTokens >= RAND_GET_OPTION(RSK_LACS_TOKEN_COUNT).Get()) { - return true; - } - break; - default: - if (CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW)) { - return true; +bool MeetsGBKRequirements() { + u8 bonusRewardCount = 0; + switch (RAND_GET_OPTION(RSK_GBK_OPTIONS).Get()) { + case RO_CHECK_TRIGGER_WILDCARD_REWARD: + case RO_CHECK_TRIGGER_GREG_REWARD: + if (Flags_GetRandomizerInf(RAND_INF_GREG_FOUND)) { + bonusRewardCount = 1; } break; } - return false; + switch (RAND_GET_OPTION(RSK_GANONS_BOSS_KEY).Get()) { + case RO_GANON_BOSS_KEY_STONES: + return (CheckStoneCount() + bonusRewardCount) >= RAND_GET_OPTION(RSK_GBK_STONE_COUNT).Get(); + case RO_GANON_BOSS_KEY_MEDALLIONS: + return (CheckMedallionCount() + bonusRewardCount) >= RAND_GET_OPTION(RSK_GBK_MEDALLION_COUNT).Get(); + case RO_GANON_BOSS_KEY_REWARDS: + return (CheckMedallionCount() + CheckStoneCount() + bonusRewardCount) >= + RAND_GET_OPTION(RSK_GBK_REWARD_COUNT).Get(); + case RO_GANON_BOSS_KEY_DUNGEONS: + return (CheckDungeonCount() + bonusRewardCount) >= RAND_GET_OPTION(RSK_GBK_DUNGEON_COUNT).Get(); + case RO_GANON_BOSS_KEY_TOKENS: + return gSaveContext.inventory.gsTokens >= RAND_GET_OPTION(RSK_GBK_TOKEN_COUNT).Get(); + case RO_GANON_BOSS_KEY_TRIFORCE_PIECES: + return gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected >= + RAND_GET_OPTION(RSK_GBK_TRIFORCE_COUNT).Get(); + default: + return false; + } +} + +bool MeetsGanonsSoulRequirements() { + u8 bonusRewardCount = 0; + switch (RAND_GET_OPTION(RSK_GANONS_SOUL_OPTIONS).Get()) { + case RO_CHECK_TRIGGER_WILDCARD_REWARD: + case RO_CHECK_TRIGGER_GREG_REWARD: + if (Flags_GetRandomizerInf(RAND_INF_GREG_FOUND)) { + bonusRewardCount = 1; + } + break; + } + + switch (RAND_GET_OPTION(RSK_GANONS_SOUL).Get()) { + case RO_GANONS_SOUL_STONES: + return (CheckStoneCount() + bonusRewardCount) >= RAND_GET_OPTION(RSK_GANONS_SOUL_STONE_COUNT).Get(); + case RO_GANONS_SOUL_MEDALLIONS: + return (CheckMedallionCount() + bonusRewardCount) >= RAND_GET_OPTION(RSK_GANONS_SOUL_MEDALLION_COUNT).Get(); + case RO_GANONS_SOUL_REWARDS: + return (CheckMedallionCount() + CheckStoneCount() + bonusRewardCount) >= + RAND_GET_OPTION(RSK_GANONS_SOUL_REWARD_COUNT).Get(); + case RO_GANONS_SOUL_DUNGEONS: + return (CheckDungeonCount() + bonusRewardCount) >= RAND_GET_OPTION(RSK_GANONS_SOUL_DUNGEON_COUNT).Get(); + case RO_GANONS_SOUL_TOKENS: + return gSaveContext.inventory.gsTokens >= RAND_GET_OPTION(RSK_GANONS_SOUL_TOKEN_COUNT).Get(); + case RO_GANONS_SOUL_TRIFORCE_PIECES: + return gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected >= + RAND_GET_OPTION(RSK_GANONS_SOUL_TRIFORCE_COUNT).Get(); + default: + return false; + } +} + +bool MeetsWinconRequirements() { + u8 bonusRewardCount = 0; + switch (RAND_GET_OPTION(RSK_WINCON_OPTIONS).Get()) { + case RO_CHECK_TRIGGER_WILDCARD_REWARD: + case RO_CHECK_TRIGGER_GREG_REWARD: + if (Flags_GetRandomizerInf(RAND_INF_GREG_FOUND)) { + bonusRewardCount = 1; + } + break; + } + + switch (RAND_GET_OPTION(RSK_WINCON).Get()) { + case RO_WINCON_STONES: + return (CheckStoneCount() + bonusRewardCount) >= RAND_GET_OPTION(RSK_WINCON_STONE_COUNT).Get(); + case RO_WINCON_MEDALLIONS: + return (CheckMedallionCount() + bonusRewardCount) >= RAND_GET_OPTION(RSK_WINCON_MEDALLION_COUNT).Get(); + case RO_WINCON_REWARDS: + return (CheckMedallionCount() + CheckStoneCount() + bonusRewardCount) >= + RAND_GET_OPTION(RSK_WINCON_REWARD_COUNT).Get(); + case RO_WINCON_DUNGEONS: + return (CheckDungeonCount() + bonusRewardCount) >= RAND_GET_OPTION(RSK_WINCON_DUNGEON_COUNT).Get(); + case RO_WINCON_TOKENS: + return gSaveContext.inventory.gsTokens >= RAND_GET_OPTION(RSK_WINCON_TOKEN_COUNT).Get(); + case RO_WINCON_TRIFORCE_PIECES: + return gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected >= + RAND_GET_OPTION(RSK_WINCON_TRIFORCE_COUNT).Get(); + default: + return false; + } } bool CompletedAllTrials() { @@ -172,59 +227,33 @@ bool CompletedAllTrials() { bool MeetsRainbowBridgeRequirements() { switch (RAND_GET_OPTION(RSK_RAINBOW_BRIDGE).Get()) { - case RO_BRIDGE_VANILLA: { - if (CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW) && - (INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT)) { - return true; - } - break; - } - case RO_BRIDGE_STONES: { - if ((CheckStoneCount() + CheckBridgeRewardCount()) >= - RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_STONE_COUNT).Get()) { - return true; - } - break; - } - case RO_BRIDGE_MEDALLIONS: { - if ((CheckMedallionCount() + CheckBridgeRewardCount()) >= - RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT).Get()) { - return true; - } - break; - } - case RO_BRIDGE_DUNGEON_REWARDS: { - if ((CheckMedallionCount() + CheckStoneCount() + CheckBridgeRewardCount()) >= - RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_REWARD_COUNT).Get()) { - return true; - } - break; - } - case RO_BRIDGE_DUNGEONS: { - if ((CheckDungeonCount() + CheckBridgeRewardCount()) >= - RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Get()) { - return true; - } - break; - } - case RO_BRIDGE_TOKENS: { - if (gSaveContext.inventory.gsTokens >= RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Get()) { - return true; - } - break; - } - case RO_BRIDGE_GREG: { - if (Flags_GetRandomizerInf(RAND_INF_GREG_FOUND)) { - return true; - } - break; - } - case RO_BRIDGE_ALWAYS_OPEN: { + case RO_BRIDGE_VANILLA: + return CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW) && + INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT; + case RO_BRIDGE_STONES: + return (CheckStoneCount() + CheckBridgeRewardCount()) >= + RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_STONE_COUNT).Get(); + case RO_BRIDGE_MEDALLIONS: + return (CheckMedallionCount() + CheckBridgeRewardCount()) >= + RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT).Get(); + case RO_BRIDGE_DUNGEON_REWARDS: + return (CheckMedallionCount() + CheckStoneCount() + CheckBridgeRewardCount()) >= + RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_REWARD_COUNT).Get(); + case RO_BRIDGE_DUNGEONS: + return (CheckDungeonCount() + CheckBridgeRewardCount()) >= + RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Get(); + case RO_BRIDGE_TOKENS: + return gSaveContext.inventory.gsTokens >= RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Get(); + case RO_BRIDGE_TRIFORCE_PIECES: + return gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected >= + RAND_GET_OPTION(RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT).Get(); + case RO_BRIDGE_GREG: + return Flags_GetRandomizerInf(RAND_INF_GREG_FOUND); + case RO_BRIDGE_ALWAYS_OPEN: return true; - } + default: + return false; } - - return false; } // Todo Move this to randomizer context, clear it out on save load etc @@ -232,9 +261,26 @@ static std::queue randomizerQueuedChecks; static RandomizerCheck randomizerQueuedCheck = RC_UNKNOWN_CHECK; static GetItemEntry randomizerQueuedItemEntry = GET_ITEM_NONE; +void CheckTriggers() { + if (!(gSaveContext.inventory.dungeonItems[SCENE_GANONS_TOWER] & 1) && MeetsGBKRequirements()) { + SPDLOG_INFO("Queuing RC: RC_GANONS_BOSS_KEY"); + randomizerQueuedChecks.push(RC_GANONS_BOSS_KEY); + } + + if (!Flags_GetRandomizerInf(RAND_INF_GANON_SOUL) && MeetsGanonsSoulRequirements()) { + SPDLOG_INFO("Queuing RC: RC_GANON_SOUL"); + randomizerQueuedChecks.push(RC_GANON_SOUL); + } + + if (MeetsWinconRequirements()) { + SPDLOG_INFO("Queuing RC: RC_WINCON"); + randomizerQueuedChecks.push(RC_WINCON); + } +} + void RandomizerOnFlagSetHandler(int16_t flagType, int16_t flag) { // Consume adult trade items - if (RAND_GET_OPTION(RSK_SHUFFLE_ADULT_TRADE) && flagType == FLAG_RANDOMIZER_INF) { + if (RAND_GET_OPTION(RSK_SHUFFLE_ADULT_TRADE).Get() && flagType == FLAG_RANDOMIZER_INF) { switch (flag) { case RAND_INF_ADULT_TRADES_DMT_TRADE_BROKEN_SWORD: Flags_UnsetRandomizerInf(RAND_INF_ADULT_TRADES_HAS_SWORD_BROKEN); @@ -550,6 +596,8 @@ void RandomizerOnItemReceiveHandler(GetItemEntry receivedItemEntry) { Flags_SetEventChkInf(EVENTCHKINF_NABOORU_CAPTURED_BY_TWINROVA); } } + + CheckTriggers(); } void EnExItem_DrawRandomizedItem(EnExItem* enExItem, PlayState* play) { @@ -1098,14 +1146,9 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l case VB_BE_ELIGIBLE_FOR_DARUNIAS_JOY_REWARD: *should = !Flags_GetRandomizerInf(RAND_INF_DARUNIAS_JOY); break; - case VB_BE_ELIGIBLE_FOR_LIGHT_ARROWS: - *should = LINK_IS_ADULT && (gEntranceTable[gSaveContext.entranceIndex].scene == SCENE_TEMPLE_OF_TIME) && - !Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS) && - MeetsLACSRequirements(); - break; case VB_BE_ELIGIBLE_FOR_NOCTURNE_OF_SHADOW: *should = !Flags_GetEventChkInf(EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL) && LINK_IS_ADULT && - gEntranceTable[((void)0, gSaveContext.entranceIndex)].scene == SCENE_KAKARIKO_VILLAGE && + gEntranceTable[gSaveContext.entranceIndex].scene == SCENE_KAKARIKO_VILLAGE && CHECK_QUEST_ITEM(QUEST_MEDALLION_FOREST) && CHECK_QUEST_ITEM(QUEST_MEDALLION_FIRE) && CHECK_QUEST_ITEM(QUEST_MEDALLION_WATER) && gSaveContext.cutsceneIndex < 0xFFF0; break; @@ -1821,6 +1864,18 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l } break; } + case VB_SLAY_GANON: + if (RAND_GET_OPTION(RSK_WINCON).IsNot(RO_WINCON_DEFEAT_GANON)) { + *should = false; + Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_GANONS_TOWER); + randomizerQueuedChecks.push(RC_GANON); + CheckTriggers(); + gPlayState->nextEntranceIndex = ENTR_OUTSIDE_GANONS_CASTLE_1_2; + gPlayState->transitionTrigger = TRANS_TRIGGER_START; + gPlayState->transitionType = TRANS_TYPE_FADE_WHITE; + gSaveContext.nextTransitionType = TRANS_TYPE_FADE_WHITE_SLOW; + } + break; case VB_DRAW_AMMO_COUNT: { s16 item = *va_arg(args, s16*); // don't draw ammo count if you have the infinite upgrade @@ -1871,8 +1926,8 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l } case VB_SKIP_SCARECROWS_SONG: { int ocarinaButtonCount = 0; - for (int i = VB_HAVE_OCARINA_NOTE_A4; i <= VB_HAVE_OCARINA_NOTE_F4; i++) { - if (GameInteractor_Should((GIVanillaBehavior)i, true)) { + for (int i = RAND_INF_HAS_OCARINA_A; i <= RAND_INF_HAS_OCARINA_C_RIGHT; i++) { + if (Flags_GetRandomizerInf((RandomizerInf)i)) { ocarinaButtonCount++; } } @@ -2022,6 +2077,8 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l break; } case VB_FREEZE_ON_SKULL_TOKEN: + CheckTriggers(); + [[fallthrough]]; case VB_TRADE_TIMER_ODD_MUSHROOM: case VB_TRADE_TIMER_FROG: case VB_GIVE_ITEM_FROM_TARGET_IN_WOODS: @@ -2124,6 +2181,9 @@ void RandomizerOnSceneInitHandler(int16_t sceneNum) { Entrance_OverrideSpawnScene(sceneNum, gPlayState->curSpawn); } + // Check here in case queued item got lost by poorly timed save & quit + CheckTriggers(); + // LACS & Prelude checks static uint32_t updateHook = 0; @@ -2146,7 +2206,9 @@ void RandomizerOnSceneInitHandler(int16_t sceneNum) { } // We're always in rando here, and rando always overrides this should so we can just pass false - if (GameInteractor_Should(VB_BE_ELIGIBLE_FOR_LIGHT_ARROWS, false)) { + if (LINK_IS_ADULT && (gEntranceTable[gSaveContext.entranceIndex].scene == SCENE_TEMPLE_OF_TIME) && + CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW) && + !Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS)) { Flags_SetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS); } @@ -2424,9 +2486,9 @@ void RandomizerOnActorInitHandler(void* actorRef) { Actor_Kill(actor); } + RandomizerInf currentBossSoulRandInf = RAND_INF_MAX; if (RAND_GET_OPTION(RSK_SHUFFLE_BOSS_SOULS)) { // Boss souls require an additional item (represented by a RAND_INF) to spawn a boss in a particular lair - RandomizerInf currentBossSoulRandInf = RAND_INF_MAX; switch (gPlayState->sceneNum) { case SCENE_DEKU_TREE_BOSS: currentBossSoulRandInf = RAND_INF_GOHMA_SOUL; @@ -2452,30 +2514,33 @@ void RandomizerOnActorInitHandler(void* actorRef) { case SCENE_SPIRIT_TEMPLE_BOSS: currentBossSoulRandInf = RAND_INF_TWINROVA_SOUL; break; - case SCENE_GANONDORF_BOSS: - case SCENE_GANON_BOSS: - if (RAND_GET_OPTION(RSK_SHUFFLE_BOSS_SOULS).Is(RO_BOSS_SOULS_ON_PLUS_GANON)) { - currentBossSoulRandInf = RAND_INF_GANON_SOUL; - } - break; default: break; } + } - // Deletes all actors in the boss category if the soul isn't found. - // Some actors, like Dark Link, Arwings, and Zora's Sapphire...?, are in this category despite not being actual - // bosses, so ignore any "boss" if `currentBossSoulRandInf` doesn't change from RAND_INF_MAX. Iron Knuckle - // (Nabooru) in Twinrova's room is a special exception, so exclude knuckles too. - if (currentBossSoulRandInf != RAND_INF_MAX) { - if (!Flags_GetRandomizerInf(currentBossSoulRandInf) && actor->category == ACTORCAT_BOSS && - actor->id != ACTOR_EN_IK) { - Actor_Delete(&gPlayState->actorCtx, actor, gPlayState); - } - // Special case for Phantom Ganon's horse (and fake), as they're considered "background actors", - // but still control the boss fight flow. - if (!Flags_GetRandomizerInf(RAND_INF_PHANTOM_GANON_SOUL) && actor->id == ACTOR_EN_FHG) { - Actor_Delete(&gPlayState->actorCtx, actor, gPlayState); - } + if (RAND_GET_OPTION(RSK_GANONS_SOUL).IsNot(RO_GANONS_SOUL_STARTWITH)) { + switch (gPlayState->sceneNum) { + case SCENE_GANONDORF_BOSS: + case SCENE_GANON_BOSS: + currentBossSoulRandInf = RAND_INF_GANON_SOUL; + break; + } + } + + // Deletes all actors in the boss category if the soul isn't found. + // Some actors, like Dark Link, Arwings, and Zora's Sapphire...?, are in this category despite not being actual + // bosses, so ignore any "boss" if `currentBossSoulRandInf` doesn't change from RAND_INF_MAX. Iron Knuckle + // (Nabooru) in Twinrova's room is a special exception, so exclude knuckles too. + if (currentBossSoulRandInf != RAND_INF_MAX) { + if (!Flags_GetRandomizerInf(currentBossSoulRandInf) && actor->category == ACTORCAT_BOSS && + actor->id != ACTOR_EN_IK) { + Actor_Delete(&gPlayState->actorCtx, actor, gPlayState); + } + // Special case for Phantom Ganon's horse (and fake), as they're considered "background actors", + // but still control the boss fight flow. + if (!Flags_GetRandomizerInf(RAND_INF_PHANTOM_GANON_SOUL) && actor->id == ACTOR_EN_FHG) { + Actor_Delete(&gPlayState->actorCtx, actor, gPlayState); } } @@ -2744,15 +2809,7 @@ void RandomizerOnPlayerUpdateHandler() { } } - // Triforce Hunt needs the check if the player isn't being teleported to the credits scene. - if (!GameInteractor::IsGameplayPaused() && Flags_GetRandomizerInf(RAND_INF_GRANT_GANONS_BOSSKEY) && - gPlayState->transitionTrigger != TRANS_TRIGGER_START && - (1 << 0 & gSaveContext.inventory.dungeonItems[SCENE_GANONS_TOWER]) == 0) { - GiveItemEntryWithoutActor(gPlayState, - *Rando::StaticData::GetItemTable().at(RG_GANONS_CASTLE_BOSS_KEY).GetGIEntry()); - } - - if (!GameInteractor::IsGameplayPaused() && RAND_GET_OPTION(RSK_TRIFORCE_HUNT).IsNot(RO_TRIFORCE_HUNT_OFF)) { + if (!GameInteractor::IsGameplayPaused()) { // Warp to credits once item queue has drained to avoid losing queued items if (GameInteractor::State::TriforceHuntCreditsWarpActive && randomizerQueuedChecks.empty() && randomizerQueuedCheck == RC_UNKNOWN_CHECK) { @@ -2775,7 +2832,7 @@ void RandomizerOnPlayerUpdateHandler() { // to ensure it's done at that point in time specifically. if (GameInteractor::State::TriforceHuntPieceGiven) { triforcePieceScale = 0.0f; - GameInteractor::State::TriforceHuntPieceGiven = 0; + GameInteractor::State::TriforceHuntPieceGiven = false; } } } diff --git a/soh/soh/Enhancements/randomizer/item.cpp b/soh/soh/Enhancements/randomizer/item.cpp index ffbb7f130f..1e12868573 100644 --- a/soh/soh/Enhancements/randomizer/item.cpp +++ b/soh/soh/Enhancements/randomizer/item.cpp @@ -433,7 +433,8 @@ bool Item::IsBottleItem() const { bool Item::IsMajorItem() const { const auto ctx = Context::GetInstance(); if (type == ITEMTYPE_TOKEN) { - return ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS) || ctx->LACSCondition() == RO_LACS_TOKENS; + return ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS) || + ctx->GBKCondition() == RO_CHECK_TRIGGER_TOKENS; } if (type == ITEMTYPE_DROP || type == ITEMTYPE_EVENT || type == ITEMTYPE_SHOP || type == ITEMTYPE_MAP || diff --git a/soh/soh/Enhancements/randomizer/item_list.cpp b/soh/soh/Enhancements/randomizer/item_list.cpp index 378df39b5e..dd9c5aef3d 100644 --- a/soh/soh/Enhancements/randomizer/item_list.cpp +++ b/soh/soh/Enhancements/randomizer/item_list.cpp @@ -422,7 +422,7 @@ void Rando::StaticData::InitItemTable() { itemTable[RG_DEKU_NUT_BAG] = Item(RG_DEKU_NUT_BAG, Text{ "Deku Nut Bag", "Sac de Noix Mojo", "Deku-Nuß-Tasche" }, ITEMTYPE_ITEM, GI_NUT_UPGRADE_30, true, LOGIC_PROGRESSIVE_NUT_BAG, RHT_NONE, RG_DEKU_NUT_BAG, OBJECT_GI_NUTS, GID_NUTS, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"a ", "un ", "eine "}).CustomIcon(gItemIconDekuNutTex); - itemTable[RG_TRIFORCE] = Item(RG_TRIFORCE, Text{ "Triforce", "Triforce", "Triforce" }, ITEMTYPE_EVENT, RG_TRIFORCE, false, LOGIC_NONE, RHT_NONE, ITEM_CATEGORY_MAJOR, {"the ", "la ", "die "}); + itemTable[RG_TRIFORCE] = Item(RG_TRIFORCE, Text{ "Triforce", "Triforce", "Triforce" }, ITEMTYPE_ITEM, RG_TRIFORCE, true, LOGIC_NONE, RHT_NONE, RG_TRIFORCE, OBJECT_GI_BOMB_2, GID_TRIFORCE_PIECE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"the ", "la ", "die "}).CustomIcon(gTriforcePieceTex);; itemTable[RG_HINT] = Item(RG_HINT, Text{ "Hint", "Indice", "Hinweis" }, ITEMTYPE_EVENT, RG_HINT, false, LOGIC_NONE, RHT_NONE, ITEM_CATEGORY_LESSER); // Individual stages of progressive items (only here for GetItemEntry purposes, not for use in seed gen) itemTable[RG_HOOKSHOT] = Item(RG_HOOKSHOT, Text{ "Hookshot", "Grappin", "Fanghaken" }, ITEMTYPE_ITEM, GI_HOOKSHOT, true, LOGIC_PROGRESSIVE_HOOKSHOT, RHT_HOOKSHOT, ITEM_HOOKSHOT, OBJECT_GI_HOOKSHOT, GID_HOOKSHOT, 0x36, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, {"the ", "le ", "den "}); @@ -453,7 +453,7 @@ void Rando::StaticData::InitItemTable() { itemTable[RG_DEKU_STICK_CAPACITY_30] = Item(RG_DEKU_STICK_CAPACITY_30, Text{ "Deku Stick Capacity (30)", "Capacité de Bâtons Mojo (30)", "Deku-Stab-Kapazität (30)" }, ITEMTYPE_ITEM, GI_STICK_UPGRADE_30, true, LOGIC_PROGRESSIVE_STICK_BAG, RHT_DEKU_STICK_CAPACITY_30, ITEM_STICK_UPGRADE_30, OBJECT_GI_STICK, GID_STICK, 0x91, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE).CustomIcon(gItemIconDekuStickTex); itemTable[RG_MAGIC_SINGLE] = Item(RG_MAGIC_SINGLE, Text{ "Magic Meter", "Jauge de Magie", "Magisches Maß" }, ITEMTYPE_ITEM, 0x8A, true, LOGIC_PROGRESSIVE_MAGIC, RHT_MAGIC_SINGLE, RG_MAGIC_SINGLE, OBJECT_GI_MAGICPOT, GID_MAGIC_SMALL, 0xE4, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"the ", "la ", "das "}).CustomIcon(gQuestIconMagicJarSmallTex, ICON_SIZE_24); itemTable[RG_MAGIC_DOUBLE] = Item(RG_MAGIC_DOUBLE, Text{ "Enhanced Magic Meter", "Jauge de Magie améliorée", "Verbessertes Magisches Maß" }, ITEMTYPE_ITEM, 0x8A, true, LOGIC_PROGRESSIVE_MAGIC, RHT_MAGIC_DOUBLE, RG_MAGIC_DOUBLE, OBJECT_GI_MAGICPOT, GID_MAGIC_LARGE, 0xE8, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER, {"the ", "la ", "das "}).CustomIcon(gQuestIconMagicJarBigTex, ICON_SIZE_24); - itemTable[RG_TRIFORCE_PIECE] = Item(RG_TRIFORCE_PIECE, Text{ "Triforce Piece", "Morceau de Triforce", "Triforce-Fragment" }, ITEMTYPE_ITEM, 0xDF, true, LOGIC_TRIFORCE_PIECES, RHT_TRIFORCE_PIECE, RG_TRIFORCE_PIECE, OBJECT_GI_BOMB_2, GID_TRIFORCE_PIECE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"a ", "un ", "ein "}).CustomIcon(gTriforcePieceTex); + itemTable[RG_TRIFORCE_PIECE] = Item(RG_TRIFORCE_PIECE, Text{ "Triforce Piece", "Morceau de Triforce", "Triforce-Fragment" }, ITEMTYPE_ITEM, 0xDF, true, LOGIC_NONE, RHT_TRIFORCE_PIECE, RG_TRIFORCE_PIECE, OBJECT_GI_BOMB_2, GID_TRIFORCE_PIECE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"a ", "un ", "ein "}).CustomIcon(gTriforcePieceTex); itemTable[RG_ROCS_FEATHER] = Item(RG_ROCS_FEATHER, Text{ "Roc's Feather", "Plume de Roc", "Grefenfeider" }, ITEMTYPE_ITEM, 0xE0, true, LOGIC_ROCS_FEATHER, RHT_ROCS_FEATHER, RG_ROCS_FEATHER, OBJECT_GI_BOMB_2, GID_STONE_OF_AGONY, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"a ", "la ", "ein "}).CustomIcon(gRocsFeatherTex); itemTable[RG_ROCS_FEATHER].SetCustomDrawFunc(Randomizer_DrawRocsFeather); diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/temple_of_time.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/temple_of_time.cpp index 3089f75ef4..96f0300777 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/temple_of_time.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/temple_of_time.cpp @@ -30,7 +30,7 @@ void RegionTable_Init_TempleOfTime() { areaTable[RR_TEMPLE_OF_TIME] = Region("Temple of Time", SCENE_TEMPLE_OF_TIME, {}, { //Locations - LOCATION(RC_TOT_LIGHT_ARROWS_CUTSCENE, logic->IsAdult && logic->CanTriggerLACS()), + LOCATION(RC_TOT_LIGHT_ARROWS_CUTSCENE, logic->IsAdult && logic->HasItem(RG_SHADOW_MEDALLION) && logic->HasItem(RG_SPIRIT_MEDALLION)), LOCATION(RC_ALTAR_HINT_CHILD, logic->IsChild), LOCATION(RC_ALTAR_HINT_ADULT, logic->IsAdult), LOCATION(RC_TOT_SHEIK_HINT, logic->IsAdult && logic->HasItem(RG_SPEAK_HYLIAN)), @@ -45,7 +45,7 @@ void RegionTable_Init_TempleOfTime() { //Locations LOCATION(RC_TOT_MASTER_SWORD, logic->IsAdult), LOCATION(RC_GIFT_FROM_RAURU, logic->IsAdult), - LOCATION(RC_SHEIK_AT_TEMPLE, logic->HasItem(RG_FOREST_MEDALLION) && logic->IsAdult), + LOCATION(RC_SHEIK_AT_TEMPLE, logic->IsAdult && logic->HasItem(RG_FOREST_MEDALLION)), }, { //Exits ENTRANCE(RR_TEMPLE_OF_TIME, true), diff --git a/soh/soh/Enhancements/randomizer/location_access/root.cpp b/soh/soh/Enhancements/randomizer/location_access/root.cpp index 6807672a63..72b591017e 100644 --- a/soh/soh/Enhancements/randomizer/location_access/root.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/root.cpp @@ -18,7 +18,9 @@ void RegionTable_Init_Root() { }, { //Locations LOCATION(RC_LINKS_POCKET, true), - LOCATION(RC_TRIFORCE_COMPLETED, logic->GetSaveContext()->ship.quest.data.randomizer.triforcePiecesCollected >= ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_REQUIRED).Get() + 1;), + LOCATION(RC_GANONS_BOSS_KEY, logic->CanTriggerGBK()), + LOCATION(RC_GANON_SOUL, logic->CanTriggerGanonsSoul()), + LOCATION(RC_WINCON, logic->CanTriggerWincon()), LOCATION(RC_SARIA_SONG_HINT, logic->CanUse(RG_SARIAS_SONG)), LOCATION(RC_SONG_FROM_IMPA, (bool)ctx->GetOption(RSK_SKIP_CHILD_ZELDA)), LOCATION(RC_HC_MALON_EGG, (bool)ctx->GetOption(RSK_SKIP_CHILD_ZELDA)), diff --git a/soh/soh/Enhancements/randomizer/location_list.cpp b/soh/soh/Enhancements/randomizer/location_list.cpp index 77444cc420..605acf35fc 100644 --- a/soh/soh/Enhancements/randomizer/location_list.cpp +++ b/soh/soh/Enhancements/randomizer/location_list.cpp @@ -1001,7 +1001,10 @@ void Rando::StaticData::InitLocationTable() { locationTable[RC_BIGGORON_HINT] = Location::OtherHint(RC_BIGGORON_HINT, RCQUEST_BOTH, ACTOR_EN_GO2, SCENE_DEATH_MOUNTAIN_TRAIL, "Biggoron Hint"); locationTable[RC_MASK_SHOP_HINT] = Location::OtherHint(RC_MASK_SHOP_HINT, RCQUEST_BOTH, ACTOR_ID_MAX, SCENE_HAPPY_MASK_SHOP, "Mask Shop Hint"); - locationTable[RC_TRIFORCE_COMPLETED] = Location::Base(RC_TRIFORCE_COMPLETED, RCQUEST_BOTH, RCTYPE_STANDARD, RCAREA_MARKET, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, "Completed Triforce", "Completed Triforce", RHT_NONE, RG_NONE); + // Conditional + locationTable[RC_WINCON] = Location::Base(RC_WINCON, RCQUEST_BOTH, RCTYPE_STANDARD, RCAREA_MARKET, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, "Win Condition", "Win Condition", RHT_NONE, RG_NONE); + locationTable[RC_GANONS_BOSS_KEY] = Location::Base(RC_GANONS_BOSS_KEY, RCQUEST_BOTH, RCTYPE_STANDARD, RCAREA_GANONS_CASTLE, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, "Ganon's Boss Key", "Ganon's Boss Key", RHT_NONE, RG_NONE); + locationTable[RC_GANON_SOUL] = Location::Base(RC_GANON_SOUL, RCQUEST_BOTH, RCTYPE_STANDARD, RCAREA_GANONS_CASTLE, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, "Ganon's Soul", "Ganon's Soul", RHT_NONE, RG_NONE); // clang-format on // Init locationNameToEnum diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index 30daebc8ab..5121f03f95 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -199,7 +199,7 @@ bool Logic::HasItem(RandomizerGet itemName) { case RG_TWINROVA_SOUL: return !ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS) || CheckRandoInf(StaticData::RandoGetToRandInf.at(itemName)); case RG_GANON_SOUL: - return !ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).Is(RO_BOSS_SOULS_ON_PLUS_GANON) || + return ctx->GetOption(RSK_GANONS_SOUL).Is(RO_GANONS_SOUL_STARTWITH) || CheckRandoInf(StaticData::RandoGetToRandInf.at(itemName)); // Overworld Keys case RG_GUARD_HOUSE_KEY: @@ -1704,25 +1704,92 @@ bool Logic::CanBuildRainbowBridge() { ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Get()) || (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS) && GetGSCount() >= ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Get()) || + (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TRIFORCE_PIECES) && + GetTriforcePieceCount() >= ctx->GetOption(RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT).Get()) || (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_GREG) && HasItem(RG_GREG_RUPEE)); } -bool Logic::CanTriggerLACS() { - return (ctx->LACSCondition() == RO_LACS_VANILLA && HasItem(RG_SHADOW_MEDALLION) && HasItem(RG_SPIRIT_MEDALLION)) || - (ctx->LACSCondition() == RO_LACS_STONES && - StoneCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= - ctx->GetOption(RSK_LACS_STONE_COUNT).Get()) || - (ctx->LACSCondition() == RO_LACS_MEDALLIONS && - MedallionCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= - ctx->GetOption(RSK_LACS_MEDALLION_COUNT).Get()) || - (ctx->LACSCondition() == RO_LACS_REWARDS && - StoneCount() + MedallionCount() + - (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= - ctx->GetOption(RSK_LACS_REWARD_COUNT).Get()) || - (ctx->LACSCondition() == RO_LACS_DUNGEONS && - DungeonCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= - ctx->GetOption(RSK_LACS_DUNGEON_COUNT).Get()) || - (ctx->LACSCondition() == RO_LACS_TOKENS && GetGSCount() >= ctx->GetOption(RSK_LACS_TOKEN_COUNT).Get()); +bool Logic::CanTriggerGBK() { + switch (ctx->GBKCondition()) { + case RO_CHECK_TRIGGER_STONES: + return StoneCount() + + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_GBK_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_GBK_STONE_COUNT).Get(); + case RO_CHECK_TRIGGER_MEDALLIONS: + return MedallionCount() + + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_GBK_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_GBK_MEDALLION_COUNT).Get(); + case RO_CHECK_TRIGGER_REWARDS: + return StoneCount() + MedallionCount() + + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_GBK_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_GBK_REWARD_COUNT).Get(); + case RO_CHECK_TRIGGER_DUNGEONS: + return DungeonCount() + + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_GBK_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_GBK_DUNGEON_COUNT).Get(); + case RO_CHECK_TRIGGER_TOKENS: + return GetGSCount() >= ctx->GetOption(RSK_GBK_TOKEN_COUNT).Get(); + case RO_CHECK_TRIGGER_TRIFORCE_PIECES: + return GetTriforcePieceCount() >= ctx->GetOption(RSK_GBK_TRIFORCE_COUNT).Get(); + default: + return false; + } +} + +bool Logic::CanTriggerGanonsSoul() { + switch (ctx->GanonsSoulCondition()) { + case RO_CHECK_TRIGGER_STONES: + return StoneCount() + (HasItem(RG_GREG_RUPEE) && + ctx->GetOption(RSK_GANONS_SOUL_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_GANONS_SOUL_STONE_COUNT).Get(); + case RO_CHECK_TRIGGER_MEDALLIONS: + return MedallionCount() + (HasItem(RG_GREG_RUPEE) && + ctx->GetOption(RSK_GANONS_SOUL_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_GANONS_SOUL_MEDALLION_COUNT).Get(); + case RO_CHECK_TRIGGER_REWARDS: + return StoneCount() + MedallionCount() + + (HasItem(RG_GREG_RUPEE) && + ctx->GetOption(RSK_GANONS_SOUL_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_GANONS_SOUL_REWARD_COUNT).Get(); + case RO_CHECK_TRIGGER_DUNGEONS: + return DungeonCount() + (HasItem(RG_GREG_RUPEE) && + ctx->GetOption(RSK_GANONS_SOUL_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_GANONS_SOUL_DUNGEON_COUNT).Get(); + case RO_CHECK_TRIGGER_TOKENS: + return GetGSCount() >= ctx->GetOption(RSK_GANONS_SOUL_TOKEN_COUNT).Get(); + case RO_CHECK_TRIGGER_TRIFORCE_PIECES: + return GetTriforcePieceCount() >= ctx->GetOption(RSK_GANONS_SOUL_TRIFORCE_COUNT).Get(); + default: + return false; + } +} + +bool Logic::CanTriggerWincon() { + switch (ctx->WinCondition()) { + case RO_WINCON_STONES: + return StoneCount() + (HasItem(RG_GREG_RUPEE) && + ctx->GetOption(RSK_WINCON_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_WINCON_STONE_COUNT).Get(); + case RO_WINCON_MEDALLIONS: + return MedallionCount() + (HasItem(RG_GREG_RUPEE) && + ctx->GetOption(RSK_WINCON_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_WINCON_MEDALLION_COUNT).Get(); + case RO_WINCON_REWARDS: + return StoneCount() + MedallionCount() + + (HasItem(RG_GREG_RUPEE) && + ctx->GetOption(RSK_WINCON_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_WINCON_REWARD_COUNT).Get(); + case RO_WINCON_DUNGEONS: + return DungeonCount() + (HasItem(RG_GREG_RUPEE) && + ctx->GetOption(RSK_WINCON_OPTIONS).Is(RO_CHECK_TRIGGER_GREG_REWARD)) >= + ctx->GetOption(RSK_WINCON_DUNGEON_COUNT).Get(); + case RO_WINCON_TOKENS: + return GetGSCount() >= ctx->GetOption(RSK_WINCON_TOKEN_COUNT).Get(); + case RO_WINCON_TRIFORCE_PIECES: + return GetTriforcePieceCount() >= ctx->GetOption(RSK_WINCON_TRIFORCE_COUNT).Get(); + default: + return false; + } } bool Logic::SmallKeys(SceneID scene, uint8_t requiredAmount) { @@ -2667,6 +2734,10 @@ uint8_t Logic::GetGSCount() { return static_cast(mSaveContext->inventory.gsTokens); } +uint8_t Logic::GetTriforcePieceCount() { + return mSaveContext->ship.quest.data.randomizer.triforcePiecesCollected; +} + uint8_t Logic::GetAmmo(uint32_t item) { return mSaveContext->inventory.ammo[gItemSlots[item]]; } diff --git a/soh/soh/Enhancements/randomizer/logic.h b/soh/soh/Enhancements/randomizer/logic.h index ec5ad9e265..22e9dbd6d8 100644 --- a/soh/soh/Enhancements/randomizer/logic.h +++ b/soh/soh/Enhancements/randomizer/logic.h @@ -114,7 +114,9 @@ class Logic { bool CanShield(); bool CanUseProjectile(); bool CanBuildRainbowBridge(); - bool CanTriggerLACS(); + bool CanTriggerGBK(); + bool CanTriggerGanonsSoul(); + bool CanTriggerWincon(); bool IsFireLoopLocked(); bool ReachScarecrow(); bool ReachDistantScarecrow(); @@ -143,6 +145,7 @@ class Logic { void SetRandoInf(uint32_t flag, bool state); bool CheckEventChkInf(int32_t flag); uint8_t GetGSCount(); + uint8_t GetTriforcePieceCount(); void SetEventChkInf(int32_t flag, bool state); uint8_t GetAmmo(uint32_t item); void SetAmmo(uint32_t item, uint8_t count); diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index a6b4b5a12c..e230906edf 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -124,16 +124,13 @@ void Settings::CreateOptionDescriptions() { "here will be guaranteed to be Vanilla. If Set Number is higher than the amount of dungeons " "set to either MQ or Random here, you will have fewer MQ Dungeons than the number you " "set."; - mOptionDescriptions[RSK_TRIFORCE_HUNT] = - "Pieces of the Triforce of Courage have been scattered across the world. Find them all to finish the game!\n" - "\n" - "If set to Win: the game is saved and the credits roll, though you can load back in to receive Ganon's " - "Castle Boss Key. Keep in mind that Ganon might not be logically reachable when \"All Locations Reachable\" " - "is disabled."; mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL] = - "The amount of Triforce pieces that will be placed in the world. " - "Keep in mind seed generation can fail if more pieces are placed than there are junk items in the item pool."; - mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED] = "The amount of Triforce pieces required to win the game."; + "The amount of Triforce pieces that will be placed in the world. Set to 0 to disable Triforce Hunt.\n" + "\n" + "Triforce Pieces can be used as a requirement for the Rainbow Bridge, Ganon's Boss Key, Ganon's Soul, or the " + "win condition. Keep in mind seed generation can fail if more pieces are placed than there are junk items in " + "the item pool."; + mOptionDescriptions[RSK_WINCON_TRIFORCE_COUNT] = "The amount of Triforce pieces required to win the game."; mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_LOCATION] = "Any dungeon - Triforce pieces can only appear inside of any dungeon.\n" "\n" @@ -657,19 +654,15 @@ void Settings::CreateOptionDescriptions() { "\n" "Anywhere - Ganon's Boss Key Key can appear anywhere in the world.\n" "\n" - "LACS - These settings put the boss key on the Light Arrow Cutscene location, from Zelda in Temple of Time as " - "adult, with differing requirements:\n" - "- Vanilla: Obtain the Shadow Medallion and Spirit Medallion\n" + "Trigger - These settings put the boss key on a trigger, " + "granting key once requirements met:\n" "- Stones: Obtain the specified amount of Spiritual Stones.\n" "- Medallions: Obtain the specified amount of medallions.\n" "- Dungeon rewards: Obtain the specified total sum of Spiritual Stones or medallions.\n" "- Dungeons: Complete the specified amount of dungeons. Dungeons are considered complete after stepping in to " "the blue warp after the boss.\n" - "- Tokens: Obtain the specified amount of Skulltula tokens.\n" - "\n" - "100 GS Reward - Ganon's Boss Key will be awarded by the cursed rich man after you collect 100 Gold Skulltula " - "Tokens."; - mOptionDescriptions[RSK_LACS_OPTIONS] = + "- Tokens: Obtain the specified amount of Skulltula tokens."; + mOptionDescriptions[RSK_GBK_OPTIONS] = "Standard Rewards - Greg does not change logic, Greg does not help obtain GBK, max " "number of rewards on slider does not change.\n" "\n" @@ -679,6 +672,16 @@ void Settings::CreateOptionDescriptions() { "\n" "Greg as Wildcard - Greg does not change logic, Greg helps obtain GBK, max number of " "rewards on slider does not change."; + mOptionDescriptions[RSK_GANONS_SOUL_OPTIONS] = + "Standard Rewards - Greg does not change logic, Greg does not help obtain Ganon's Soul, max " + "number of rewards on slider does not change.\n" + "\n" + "Greg as Reward - Greg does change logic (can be part of expected path for obtaining " + "Ganon's Soul), Greg helps obtain Ganon's Soul, max number of rewards on slider increases by 1 to " + "account for Greg. \n" + "\n" + "Greg as Wildcard - Greg does not change logic, Greg helps obtain Ganon's Soul, max number of " + "rewards on slider does not change."; mOptionDescriptions[RSK_BIG_POE_COUNT] = "The Poe collector will give a reward for turning in this many Big Poes."; mOptionDescriptions[RSK_SKIP_CHILD_STEALTH] = "The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards."; @@ -866,7 +869,6 @@ void Settings::CreateOptionDescriptions() { "Shuffle 10 bean souls which must be found to spawn corresponding soil / plant."; mOptionDescriptions[RSK_SHUFFLE_BOSS_SOULS] = "Shuffles 8 boss souls (one for each blue warp dungeon). A boss will not appear until you collect its " - "respective soul." - "\n\"On + Ganon\" will also hide Ganon and Ganondorf behind a boss soul."; + "respective soul."; } } // namespace Rando diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index c50730f5ca..6dcbd57efc 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -261,7 +261,6 @@ ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerGet(RandomizerGe switch (randoGet) { case RG_NONE: - case RG_TRIFORCE: case RG_HINT: case RG_MAX: case RG_SOLD_OUT: @@ -668,6 +667,7 @@ ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerGet(RandomizerGe case RG_TREASURE_GAME_GREEN_RUPEE: case RG_BUY_HEART: case RG_TRIFORCE_PIECE: + case RG_TRIFORCE: default: return CAN_OBTAIN; } @@ -862,6 +862,23 @@ u8 Randomizer::GetRandoSettingValue(RandomizerSettingKey randoSettingKey) { return Rando::Context::GetInstance()->GetOption(randoSettingKey).Get(); } +u8 Randomizer::GetTriforcePiecesRequired() { + u8 required = 0; + if (GetRandoSettingValue(RSK_RAINBOW_BRIDGE) == RO_BRIDGE_TRIFORCE_PIECES) { + required = std::max(required, GetRandoSettingValue(RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT)); + } + if (GetRandoSettingValue(RSK_GANONS_BOSS_KEY) == RO_GANON_BOSS_KEY_TRIFORCE_PIECES) { + required = std::max(required, GetRandoSettingValue(RSK_GBK_TRIFORCE_COUNT)); + } + if (GetRandoSettingValue(RSK_GANONS_SOUL) == RO_GANONS_SOUL_TRIFORCE_PIECES) { + required = std::max(required, GetRandoSettingValue(RSK_GANONS_SOUL_TRIFORCE_COUNT)); + } + if (GetRandoSettingValue(RSK_WINCON) == RO_WINCON_TRIFORCE_PIECES) { + required = std::max(required, GetRandoSettingValue(RSK_WINCON_TRIFORCE_COUNT)); + } + return required; +} + GetItemEntry Randomizer::GetItemFromKnownCheck(RandomizerCheck randomizerCheck, GetItemID ogItemId, bool checkObtainability) { return Rando::Context::GetInstance()->GetFinalGIEntry(randomizerCheck, checkObtainability); @@ -1291,22 +1308,14 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { Rupees_ChangeBy(999); } break; + case RG_TRIFORCE: + GameInteractor_SetTriforceHuntCreditsWarpActive(true); + break; case RG_TRIFORCE_PIECE: gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected++; GameInteractor_SetTriforceHuntPieceGiven(true); - - // Give Ganon's Boss Key and teleport to credits if set to Win when goal is reached. - if (gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected == - (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) + 1)) { - Flags_SetRandomizerInf(RAND_INF_GRANT_GANONS_BOSSKEY); - - if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT) == - RO_TRIFORCE_HUNT_WIN) { - // Save and warp are deferred until item queue drains - GameInteractor_SetTriforceHuntCreditsWarpActive(true); - } - } - + // Reward/win triggers (Ganon's Boss Key, Ganon's Soul, win condition) are evaluated by + // CheckTriggers() on item receive, so Triforce Piece thresholds are handled there. break; case RG_PROGRESSIVE_BOMBCHU_BAG: OTRGlobals::Instance->gRandoContext->HandleGetBombchuBag(); diff --git a/soh/soh/Enhancements/randomizer/randomizer.h b/soh/soh/Enhancements/randomizer/randomizer.h index 6a75509efa..38e298d1c8 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.h +++ b/soh/soh/Enhancements/randomizer/randomizer.h @@ -41,6 +41,7 @@ class Randomizer { bool SpoilerFileExists(const char* spoilerFileName); bool IsTrialRequired(s32 trialFlag); u8 GetRandoSettingValue(RandomizerSettingKey randoSettingKey); + u8 GetTriforcePiecesRequired(); RandomizerCheck GetCheckFromRandomizerInf(RandomizerInf randomizerInf); RandomizerInf GetRandomizerInfFromCheck(RandomizerCheck rc); Rando::Location* GetCheckObjectFromActor(s16 actorId, s16 sceneNum, s32 actorParams); diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/LogicVal.h b/soh/soh/Enhancements/randomizer/randomizerEnums/LogicVal.h index 5626983a8c..bb8dc4e829 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/LogicVal.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/LogicVal.h @@ -177,7 +177,6 @@ RANDO_ENUM_ITEM(LOGIC_OCARINA_C_UP_BUTTON) RANDO_ENUM_ITEM(LOGIC_OCARINA_C_DOWN_BUTTON) RANDO_ENUM_ITEM(LOGIC_OCARINA_C_LEFT_BUTTON) RANDO_ENUM_ITEM(LOGIC_OCARINA_C_RIGHT_BUTTON) -RANDO_ENUM_ITEM(LOGIC_TRIFORCE_PIECES) RANDO_ENUM_ITEM(LOGIC_ROCS_FEATHER) RANDO_ENUM_ITEM(LOGIC_CAN_BORROW_MASKS) RANDO_ENUM_ITEM(LOGIC_BORROW_SKULL_MASK) diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerCheck.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerCheck.h index 22a8189bad..d35584a517 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerCheck.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerCheck.h @@ -14,6 +14,8 @@ RANDO_ENUM_BEGIN(RandomizerCheck) RANDO_ENUM_ITEM(RC_UNKNOWN_CHECK) RANDO_ENUM_ITEM(RC_LINKS_POCKET) +RANDO_ENUM_ITEM(RC_GANONS_BOSS_KEY) +RANDO_ENUM_ITEM(RC_GANON_SOUL) RANDO_ENUM_ITEM(RC_QUEEN_GOHMA) RANDO_ENUM_ITEM(RC_KING_DODONGO) RANDO_ENUM_ITEM(RC_BARINADE) @@ -2063,7 +2065,7 @@ RANDO_ENUM_ITEM(RC_COLOSSUS_DEKU_SCRUB_GROTTO_BEEHIVE) RANDO_ENUM_ITEM(RC_GANONDORF_HINT) RANDO_ENUM_ITEM(RC_SHEIK_HINT_GC) RANDO_ENUM_ITEM(RC_SHEIK_HINT_MQ_GC) -RANDO_ENUM_ITEM(RC_TRIFORCE_COMPLETED) +RANDO_ENUM_ITEM(RC_WINCON) RANDO_ENUM_ITEM(RC_DAMPE_HINT) RANDO_ENUM_ITEM(RC_GREG_HINT) RANDO_ENUM_ITEM(RC_SARIA_SONG_HINT) diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerHintTextKey.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerHintTextKey.h index 6f550dc63c..02fc97d94e 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerHintTextKey.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerHintTextKey.h @@ -1223,6 +1223,7 @@ RANDO_ENUM_ITEM(RHT_BRIDGE_MEDALLIONS_HINT) RANDO_ENUM_ITEM(RHT_BRIDGE_REWARDS_HINT) RANDO_ENUM_ITEM(RHT_BRIDGE_DUNGEONS_HINT) RANDO_ENUM_ITEM(RHT_BRIDGE_TOKENS_HINT) +RANDO_ENUM_ITEM(RHT_BRIDGE_TRIFORCE_PIECES_HINT) RANDO_ENUM_ITEM(RHT_BRIDGE_GREG_HINT) // Ganon Boss Key RANDO_ENUM_ITEM(RHT_GANON_BK_START_WITH_HINT) @@ -1231,15 +1232,28 @@ RANDO_ENUM_ITEM(RHT_GANON_BK_OWN_DUNGEON_HINT) RANDO_ENUM_ITEM(RHT_GANON_BK_OVERWORLD_HINT) RANDO_ENUM_ITEM(RHT_GANON_BK_ANY_DUNGEON_HINT) RANDO_ENUM_ITEM(RHT_GANON_BK_ANYWHERE_HINT) -RANDO_ENUM_ITEM(RHT_GANON_BK_TRIFORCE_HINT) -RANDO_ENUM_ITEM(RHT_GANON_BK_SKULLTULA_HINT) -// LACS -RANDO_ENUM_ITEM(RHT_LACS_VANILLA_HINT) -RANDO_ENUM_ITEM(RHT_LACS_MEDALLIONS_HINT) -RANDO_ENUM_ITEM(RHT_LACS_STONES_HINT) -RANDO_ENUM_ITEM(RHT_LACS_REWARDS_HINT) -RANDO_ENUM_ITEM(RHT_LACS_DUNGEONS_HINT) -RANDO_ENUM_ITEM(RHT_LACS_TOKENS_HINT) +// GBK +RANDO_ENUM_ITEM(RHT_GBK_MEDALLIONS_HINT) +RANDO_ENUM_ITEM(RHT_GBK_STONES_HINT) +RANDO_ENUM_ITEM(RHT_GBK_REWARDS_HINT) +RANDO_ENUM_ITEM(RHT_GBK_DUNGEONS_HINT) +RANDO_ENUM_ITEM(RHT_GBK_TOKENS_HINT) +RANDO_ENUM_ITEM(RHT_GBK_TRIFORCE_PIECES_HINT) +// Ganon's Soul +RANDO_ENUM_ITEM(RHT_GANONS_SOUL_MEDALLIONS_HINT) +RANDO_ENUM_ITEM(RHT_GANONS_SOUL_STONES_HINT) +RANDO_ENUM_ITEM(RHT_GANONS_SOUL_REWARDS_HINT) +RANDO_ENUM_ITEM(RHT_GANONS_SOUL_DUNGEONS_HINT) +RANDO_ENUM_ITEM(RHT_GANONS_SOUL_TOKENS_HINT) +RANDO_ENUM_ITEM(RHT_GANONS_SOUL_TRIFORCE_PIECES_HINT) +// Wincon +RANDO_ENUM_ITEM(RHT_WINCON_ANYWHERE_HINT) +RANDO_ENUM_ITEM(RHT_WINCON_STONES_HINT) +RANDO_ENUM_ITEM(RHT_WINCON_MEDALLIONS_HINT) +RANDO_ENUM_ITEM(RHT_WINCON_REWARDS_HINT) +RANDO_ENUM_ITEM(RHT_WINCON_DUNGEONS_HINT) +RANDO_ENUM_ITEM(RHT_WINCON_TOKENS_HINT) +RANDO_ENUM_ITEM(RHT_WINCON_TRIFORCE_PIECES_HINT) // Trials RANDO_ENUM_ITEM(RHT_SIX_TRIALS) RANDO_ENUM_ITEM(RHT_ZERO_TRIALS) diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerInf.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerInf.h index 24cd55a5ce..8e48853e45 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerInf.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerInf.h @@ -15,6 +15,7 @@ RANDO_ENUM_BEGIN(RandomizerInf) RANDO_ENUM_ITEM(RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE) RANDO_ENUM_ITEM(RAND_INF_DUNGEONS_DONE_SHADOW_TEMPLE) +RANDO_ENUM_ITEM(RAND_INF_DUNGEONS_DONE_GANONS_TOWER) RANDO_ENUM_ITEM(RAND_INF_COWS_MILKED_KF_LINKS_HOUSE_COW) RANDO_ENUM_ITEM(RAND_INF_COWS_MILKED_HF_COW_GROTTO_COW) @@ -195,7 +196,6 @@ RANDO_ENUM_ITEM(RAND_INF_ADULT_FISH_15) RANDO_ENUM_ITEM(RAND_INF_ADULT_LOACH) RANDO_ENUM_ITEM(RAND_INF_10_BIG_POES) -RANDO_ENUM_ITEM(RAND_INF_GRANT_GANONS_BOSSKEY) RANDO_ENUM_ITEM(RAND_INF_DEATH_MOUNTAIN_CRATER_BEAN_SOUL) RANDO_ENUM_ITEM(RAND_INF_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL) diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h index 96e802bf85..cd0aeb6a03 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h @@ -92,6 +92,7 @@ RANDO_ENUM_ITEM(RO_BRIDGE_MEDALLIONS) RANDO_ENUM_ITEM(RO_BRIDGE_DUNGEON_REWARDS) RANDO_ENUM_ITEM(RO_BRIDGE_DUNGEONS) RANDO_ENUM_ITEM(RO_BRIDGE_TOKENS) +RANDO_ENUM_ITEM(RO_BRIDGE_TRIFORCE_PIECES) RANDO_ENUM_ITEM(RO_BRIDGE_GREG) RANDO_ENUM_END(RandoOptionRainbowBridge) @@ -155,7 +156,6 @@ RANDO_ENUM_END(RandoOptionBombchuBag) RANDO_ENUM_BEGIN(RandoOptionBossSouls) RANDO_ENUM_ITEM(RO_BOSS_SOULS_OFF) RANDO_ENUM_ITEM(RO_BOSS_SOULS_ON) -RANDO_ENUM_ITEM(RO_BOSS_SOULS_ON_PLUS_GANON) RANDO_ENUM_END(RandoOptionBossSouls) // Fishsanity settings (off, loach only, pond only, grottos only, both) @@ -209,8 +209,7 @@ RANDO_ENUM_ITEM(RO_KEYRING_FOR_DUNGEON_RANDOM) RANDO_ENUM_ITEM(RO_KEYRING_FOR_DUNGEON_ON) RANDO_ENUM_END(RandoOptionKeyringForDungeon) -// Ganon's Boss Key Settings (vanilla, own dungeon, start with, -// overworld, anywhere, 100 GS reward) +// Ganon's Boss Key Settings RANDO_ENUM_BEGIN(RandoOptionGanonsBossKey) RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_VANILLA) RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_OWN_DUNGEON) @@ -218,30 +217,57 @@ RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_STARTWITH) RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_ANY_DUNGEON) RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_OVERWORLD) RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_ANYWHERE) -RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_LACS_VANILLA) -RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_LACS_STONES) -RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_LACS_MEDALLIONS) -RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_LACS_REWARDS) -RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_LACS_DUNGEONS) -RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_LACS_TOKENS) -RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_KAK_TOKENS) +RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_STONES) +RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_MEDALLIONS) +RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_REWARDS) +RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_DUNGEONS) +RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_TOKENS) +RANDO_ENUM_ITEM(RO_GANON_BOSS_KEY_TRIFORCE_PIECES) RANDO_ENUM_END(RandoOptionGanonsBossKey) -RANDO_ENUM_BEGIN(RandoOptionLACSCondition) -RANDO_ENUM_ITEM(RO_LACS_VANILLA) -RANDO_ENUM_ITEM(RO_LACS_STONES) -RANDO_ENUM_ITEM(RO_LACS_MEDALLIONS) -RANDO_ENUM_ITEM(RO_LACS_REWARDS) -RANDO_ENUM_ITEM(RO_LACS_DUNGEONS) -RANDO_ENUM_ITEM(RO_LACS_TOKENS) -RANDO_ENUM_END(RandoOptionLACSCondition) +// Ganon's Soul Settings +RANDO_ENUM_BEGIN(RandoOptionGanonsSoul) +RANDO_ENUM_ITEM(RO_GANONS_SOUL_STARTWITH) +RANDO_ENUM_ITEM(RO_GANONS_SOUL_ANY_DUNGEON) +RANDO_ENUM_ITEM(RO_GANONS_SOUL_OVERWORLD) +RANDO_ENUM_ITEM(RO_GANONS_SOUL_ANYWHERE) +RANDO_ENUM_ITEM(RO_GANONS_SOUL_STONES) +RANDO_ENUM_ITEM(RO_GANONS_SOUL_MEDALLIONS) +RANDO_ENUM_ITEM(RO_GANONS_SOUL_REWARDS) +RANDO_ENUM_ITEM(RO_GANONS_SOUL_DUNGEONS) +RANDO_ENUM_ITEM(RO_GANONS_SOUL_TOKENS) +RANDO_ENUM_ITEM(RO_GANONS_SOUL_TRIFORCE_PIECES) +RANDO_ENUM_END(RandoOptionGanonsSoul) -// LACS Reward Options settings (Standard rewards, Greg as reward, Greg as wildcard) -RANDO_ENUM_BEGIN(RandoOptionLACSRewards) -RANDO_ENUM_ITEM(RO_LACS_STANDARD_REWARD) -RANDO_ENUM_ITEM(RO_LACS_GREG_REWARD) -RANDO_ENUM_ITEM(RO_LACS_WILDCARD_REWARD) -RANDO_ENUM_END(RandoOptionLACSRewards) +// Wincon Triggers +RANDO_ENUM_BEGIN(RandoOptionWincon) +RANDO_ENUM_ITEM(RO_WINCON_DEFEAT_GANON) +RANDO_ENUM_ITEM(RO_WINCON_ANYWHERE) +RANDO_ENUM_ITEM(RO_WINCON_STONES) +RANDO_ENUM_ITEM(RO_WINCON_MEDALLIONS) +RANDO_ENUM_ITEM(RO_WINCON_REWARDS) +RANDO_ENUM_ITEM(RO_WINCON_DUNGEONS) +RANDO_ENUM_ITEM(RO_WINCON_TOKENS) +RANDO_ENUM_ITEM(RO_WINCON_TRIFORCE_PIECES) +RANDO_ENUM_END(RandoOptionWincon) + +// Reward Triggers +RANDO_ENUM_BEGIN(RandoOptionCheckTrigger) +RANDO_ENUM_ITEM(RO_CHECK_TRIGGER_NONE) +RANDO_ENUM_ITEM(RO_CHECK_TRIGGER_STONES) +RANDO_ENUM_ITEM(RO_CHECK_TRIGGER_MEDALLIONS) +RANDO_ENUM_ITEM(RO_CHECK_TRIGGER_REWARDS) +RANDO_ENUM_ITEM(RO_CHECK_TRIGGER_DUNGEONS) +RANDO_ENUM_ITEM(RO_CHECK_TRIGGER_TOKENS) +RANDO_ENUM_ITEM(RO_CHECK_TRIGGER_TRIFORCE_PIECES) +RANDO_ENUM_END(RandoOptionCheckTrigger) + +// Reward Options settings (Standard rewards, Greg as reward, Greg as wildcard) +RANDO_ENUM_BEGIN(RandoOptionCheckTriggerRewards) +RANDO_ENUM_ITEM(RO_CHECK_TRIGGER_STANDARD_REWARD) +RANDO_ENUM_ITEM(RO_CHECK_TRIGGER_GREG_REWARD) +RANDO_ENUM_ITEM(RO_CHECK_TRIGGER_WILDCARD_REWARD) +RANDO_ENUM_END(RandoOptionCheckTriggerRewards) // Ganon's Trials RANDO_ENUM_BEGIN(RandoOptionGanonsTrials) @@ -470,13 +496,6 @@ RANDO_ENUM_ITEM(RO_MQ_DUNGEONS_RANDOM_NUMBER) RANDO_ENUM_ITEM(RO_MQ_DUNGEONS_SELECTION) RANDO_ENUM_END(RandoOptionMQDungeons) -// Triforce Hunt settings (off, win, Ganon's Boss Key) -RANDO_ENUM_BEGIN(RandoOptionTriforceHunt) -RANDO_ENUM_ITEM(RO_TRIFORCE_HUNT_OFF) -RANDO_ENUM_ITEM(RO_TRIFORCE_HUNT_WIN) -RANDO_ENUM_ITEM(RO_TRIFORCE_HUNT_GBK) -RANDO_ENUM_END(RandoOptionTriforceHunt) - // Trifoce Hunt location RANDO_ENUM_BEGIN(RandoOptionTriforceHuntLocation) RANDO_ENUM_ITEM(RO_TRIFORCE_HUNT_LOCATION_ANY_DUNGEON) diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h index 6f6ea57ee7..06d2b8bd66 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h @@ -29,6 +29,7 @@ RANDO_ENUM_ITEM(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT) RANDO_ENUM_ITEM(RSK_RAINBOW_BRIDGE_REWARD_COUNT) RANDO_ENUM_ITEM(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT) RANDO_ENUM_ITEM(RSK_RAINBOW_BRIDGE_TOKEN_COUNT) +RANDO_ENUM_ITEM(RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT) RANDO_ENUM_ITEM(RSK_BRIDGE_OPTIONS) RANDO_ENUM_ITEM(RSK_GANONS_TRIALS) RANDO_ENUM_ITEM(RSK_TRIAL_COUNT) @@ -139,6 +140,7 @@ RANDO_ENUM_ITEM(RSK_KEYSANITY) RANDO_ENUM_ITEM(RSK_GERUDO_KEYS) RANDO_ENUM_ITEM(RSK_BOSS_KEYSANITY) RANDO_ENUM_ITEM(RSK_GANONS_BOSS_KEY) +RANDO_ENUM_ITEM(RSK_GANONS_SOUL) RANDO_ENUM_ITEM(RSK_SKIP_CHILD_STEALTH) RANDO_ENUM_ITEM(RSK_SKIP_CHILD_ZELDA) RANDO_ENUM_ITEM(RSK_STARTING_STICKS) @@ -221,12 +223,20 @@ RANDO_ENUM_ITEM(RSK_MQ_BOTTOM_OF_THE_WELL) RANDO_ENUM_ITEM(RSK_MQ_ICE_CAVERN) RANDO_ENUM_ITEM(RSK_MQ_GTG) RANDO_ENUM_ITEM(RSK_MQ_GANONS_CASTLE) -RANDO_ENUM_ITEM(RSK_LACS_STONE_COUNT) -RANDO_ENUM_ITEM(RSK_LACS_MEDALLION_COUNT) -RANDO_ENUM_ITEM(RSK_LACS_REWARD_COUNT) -RANDO_ENUM_ITEM(RSK_LACS_DUNGEON_COUNT) -RANDO_ENUM_ITEM(RSK_LACS_TOKEN_COUNT) -RANDO_ENUM_ITEM(RSK_LACS_OPTIONS) +RANDO_ENUM_ITEM(RSK_GBK_STONE_COUNT) +RANDO_ENUM_ITEM(RSK_GBK_MEDALLION_COUNT) +RANDO_ENUM_ITEM(RSK_GBK_REWARD_COUNT) +RANDO_ENUM_ITEM(RSK_GBK_DUNGEON_COUNT) +RANDO_ENUM_ITEM(RSK_GBK_TOKEN_COUNT) +RANDO_ENUM_ITEM(RSK_GBK_TRIFORCE_COUNT) +RANDO_ENUM_ITEM(RSK_GBK_OPTIONS) +RANDO_ENUM_ITEM(RSK_GANONS_SOUL_STONE_COUNT) +RANDO_ENUM_ITEM(RSK_GANONS_SOUL_MEDALLION_COUNT) +RANDO_ENUM_ITEM(RSK_GANONS_SOUL_REWARD_COUNT) +RANDO_ENUM_ITEM(RSK_GANONS_SOUL_DUNGEON_COUNT) +RANDO_ENUM_ITEM(RSK_GANONS_SOUL_TOKEN_COUNT) +RANDO_ENUM_ITEM(RSK_GANONS_SOUL_TRIFORCE_COUNT) +RANDO_ENUM_ITEM(RSK_GANONS_SOUL_OPTIONS) RANDO_ENUM_ITEM(RSK_KEYRINGS) RANDO_ENUM_ITEM(RSK_KEYRINGS_RANDOM_COUNT) RANDO_ENUM_ITEM(RSK_KEYRINGS_GERUDO_FORTRESS) @@ -262,9 +272,15 @@ RANDO_ENUM_ITEM(RSK_ALL_LOCATIONS_REACHABLE) RANDO_ENUM_ITEM(RSK_SHUFFLE_BOSS_ENTRANCES) RANDO_ENUM_ITEM(RSK_SHUFFLE_GANONS_TOWER_ENTRANCE) RANDO_ENUM_ITEM(RSK_SHUFFLE_100_GS_REWARD) -RANDO_ENUM_ITEM(RSK_TRIFORCE_HUNT) RANDO_ENUM_ITEM(RSK_TRIFORCE_HUNT_PIECES_TOTAL) -RANDO_ENUM_ITEM(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) +RANDO_ENUM_ITEM(RSK_WINCON) +RANDO_ENUM_ITEM(RSK_WINCON_STONE_COUNT) +RANDO_ENUM_ITEM(RSK_WINCON_MEDALLION_COUNT) +RANDO_ENUM_ITEM(RSK_WINCON_REWARD_COUNT) +RANDO_ENUM_ITEM(RSK_WINCON_DUNGEON_COUNT) +RANDO_ENUM_ITEM(RSK_WINCON_TOKEN_COUNT) +RANDO_ENUM_ITEM(RSK_WINCON_TRIFORCE_COUNT) +RANDO_ENUM_ITEM(RSK_WINCON_OPTIONS) RANDO_ENUM_ITEM(RSK_TRIFORCE_HUNT_PIECES_LOCATION) RANDO_ENUM_ITEM(RSK_SHUFFLE_BEAN_SOULS) RANDO_ENUM_ITEM(RSK_SHUFFLE_BOSS_SOULS) diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp index 5571d601a3..b90d8c62b2 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp @@ -239,24 +239,33 @@ void RandomizerCheckObjects::UpdateImGuiVisibility() { RO_DUNGEON_ITEM_LOC_VANILLA) && (location.GetRCType() != RCTYPE_GANON_BOSS_KEY || CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) != - RO_GANON_BOSS_KEY_VANILLA || - CVarGetInteger(CVAR_RANDOMIZER_SETTING("TriforceHunt"), 0)) && - (location.GetRandomizerCheck() != RC_TOT_LIGHT_ARROWS_CUTSCENE || + RO_GANON_BOSS_KEY_VANILLA) && // vanilla ganon boss key + (location.GetRandomizerCheck() != RC_GANONS_BOSS_KEY || (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) != - RO_GANON_BOSS_KEY_LACS_DUNGEONS && + RO_GANON_BOSS_KEY_DUNGEONS && CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) != - RO_GANON_BOSS_KEY_LACS_MEDALLIONS && + RO_GANON_BOSS_KEY_MEDALLIONS && CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) != - RO_GANON_BOSS_KEY_LACS_REWARDS && + RO_GANON_BOSS_KEY_REWARDS && CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) != - RO_GANON_BOSS_KEY_LACS_STONES && + RO_GANON_BOSS_KEY_STONES && CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) != - RO_GANON_BOSS_KEY_LACS_TOKENS && - CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) != - RO_GANON_BOSS_KEY_LACS_VANILLA)) && // LACS ganon boss key - (location.GetRandomizerCheck() != RC_KAK_100_GOLD_SKULLTULA_REWARD || - CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) != - RO_GANON_BOSS_KEY_KAK_TOKENS) && // 100 skull reward ganon boss key + RO_GANON_BOSS_KEY_TOKENS) && + CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) != + RO_GANON_BOSS_KEY_TRIFORCE_PIECES) && // ganon boss key condition + (location.GetRandomizerCheck() != RC_GANON_SOUL || + (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonsSoul"), RO_GANONS_SOUL_STARTWITH) != + RO_GANONS_SOUL_DUNGEONS && + CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonsSoul"), RO_GANONS_SOUL_STARTWITH) != + RO_GANONS_SOUL_MEDALLIONS && + CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonsSoul"), RO_GANONS_SOUL_STARTWITH) != + RO_GANONS_SOUL_REWARDS && + CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonsSoul"), RO_GANONS_SOUL_STARTWITH) != + RO_GANONS_SOUL_STONES && + CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonsSoul"), RO_GANONS_SOUL_STARTWITH) != + RO_GANONS_SOUL_TOKENS) && + CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonsSoul"), RO_GANONS_SOUL_STARTWITH) != + RO_GANONS_SOUL_TRIFORCE_PIECES) && // ganon's soul condition (location.GetRCType() != RCTYPE_GF_KEY && location.GetRandomizerCheck() != RC_TH_FREED_CARPENTERS || (CVarGetInteger(CVAR_RANDOMIZER_SETTING("FortressCarpenters"), RO_GF_CARPENTERS_NORMAL) == RO_GF_CARPENTERS_FREE && diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index 75d29df08b..f300430044 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -1633,23 +1633,23 @@ void LoadSettings() { } switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GANONS_BOSS_KEY)) { - case RO_GANON_BOSS_KEY_LACS_STONES: - Rando::Context::GetInstance()->LACSCondition(RO_LACS_STONES); + case RO_GANON_BOSS_KEY_STONES: + Rando::Context::GetInstance()->GBKCondition(RO_CHECK_TRIGGER_STONES); break; - case RO_GANON_BOSS_KEY_LACS_MEDALLIONS: - Rando::Context::GetInstance()->LACSCondition(RO_LACS_MEDALLIONS); + case RO_GANON_BOSS_KEY_MEDALLIONS: + Rando::Context::GetInstance()->GBKCondition(RO_CHECK_TRIGGER_MEDALLIONS); break; - case RO_GANON_BOSS_KEY_LACS_REWARDS: - Rando::Context::GetInstance()->LACSCondition(RO_LACS_REWARDS); + case RO_GANON_BOSS_KEY_REWARDS: + Rando::Context::GetInstance()->GBKCondition(RO_CHECK_TRIGGER_REWARDS); break; - case RO_GANON_BOSS_KEY_LACS_DUNGEONS: - Rando::Context::GetInstance()->LACSCondition(RO_LACS_DUNGEONS); + case RO_GANON_BOSS_KEY_DUNGEONS: + Rando::Context::GetInstance()->GBKCondition(RO_CHECK_TRIGGER_DUNGEONS); break; - case RO_GANON_BOSS_KEY_LACS_TOKENS: - Rando::Context::GetInstance()->LACSCondition(RO_LACS_TOKENS); + case RO_GANON_BOSS_KEY_TOKENS: + Rando::Context::GetInstance()->GBKCondition(RO_CHECK_TRIGGER_TOKENS); break; default: - Rando::Context::GetInstance()->LACSCondition(RO_LACS_VANILLA); + Rando::Context::GetInstance()->GBKCondition(RO_CHECK_TRIGGER_NONE); break; } } @@ -1672,7 +1672,7 @@ bool IsCheckShuffled(RandomizerCheck rc) { (showShops && OTRGlobals::Instance->gRandomizer->IdentifyShopItem(loc->GetScene(), loc->GetActorParams() + 1) .enGirlAShopItem == 50)) && - (rc != RC_TRIFORCE_COMPLETED) && (rc != RC_GANON) && + (rc != RC_WINCON) && (rc != RC_GANON) && (loc->GetRCType() != RCTYPE_SCRUB || showScrubs || (showMajorScrubs && (rc == RC_LW_DEKU_SCRUB_NEAR_BRIDGE || // The 3 scrubs that are always randomized rc == RC_HF_DEKU_SCRUB_GROTTO || rc == RC_LW_DEKU_SCRUB_GROTTO_FRONT))) && diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index 4bd8a8371d..2502e357bb 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -756,15 +756,17 @@ void DrawItemCount(ItemTrackerItem item, bool hideMax) { ImGui::Text("%s", maxString.c_str()); ImGui::PopStyleColor(); } else if (item.id == RG_TRIFORCE_PIECE && IS_RANDO && - (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT) != RO_TRIFORCE_HUNT_OFF) && + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_TOTAL) > 0) && IsValidSaveFile()) { std::string currentString = ""; std::string requiredString = ""; std::string maxString = ""; - uint8_t piecesRequired = - (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) + 1); - uint8_t piecesTotal = - (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_TOTAL) + 1); + uint8_t piecesTotal = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_TOTAL); + uint8_t piecesRequired = OTRGlobals::Instance->gRandomizer->GetTriforcePiecesRequired(); + // If no trigger uses Triforce Pieces they're just filler; gauge progress against the whole pool. + if (piecesRequired == 0) { + piecesRequired = piecesTotal; + } ImU32 currentColor = gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected >= piecesRequired ? IM_COL_GREEN : IM_COL_WHITE; @@ -827,11 +829,15 @@ void DrawQuest(ItemTrackerItem item) { }; bool HasBossSoul(RandomizerInf bossSoul) { - uint8_t soulSetting = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_BOSS_SOULS); - bool isSoulRandomized = IS_RANDO && (soulSetting == RO_BOSS_SOULS_ON_PLUS_GANON || - (soulSetting == RO_BOSS_SOULS_ON && bossSoul != RAND_INF_GANON_SOUL)); - - return isSoulRandomized ? Flags_GetRandomizerInf(bossSoul) : true; + if (!IS_RANDO) { + return false; + } else if (bossSoul == RAND_INF_GANON_SOUL) { + return OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GANONS_SOUL) == RO_GANONS_SOUL_STARTWITH || + Flags_GetRandomizerInf(RAND_INF_GANON_SOUL); + } else { + return OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_BOSS_SOULS) && + Flags_GetRandomizerInf(bossSoul); + } } void DrawItem(ItemTrackerItem item) { @@ -885,8 +891,8 @@ void DrawItem(ItemTrackerItem item) { break; case RG_TRIFORCE_PIECE: actualItemId = item.id; - hasItem = IS_RANDO && (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT) != - RO_TRIFORCE_HUNT_OFF); + hasItem = IS_RANDO && + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_TOTAL) > 0); itemName = "Triforce Piece"; break; case ITEM_NAYRUS_LOVE: diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 5b824b47d9..b15df87098 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -196,7 +196,7 @@ void Settings::CreateOptions() { mOptions[RSK_KEYRINGS_GERUDO_FORTRESS].Enable(); } }); - OPT_U8(RSK_RAINBOW_BRIDGE, "Rainbow Bridge", {"Vanilla", "Always open", "Stones", "Medallions", "Dungeon rewards", "Dungeons", "Tokens", "Greg"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("RainbowBridge"), mOptionDescriptions[RSK_RAINBOW_BRIDGE], WIDGET_CVAR_COMBOBOX, RO_BRIDGE_VANILLA, false, nullptr, IMFLAG_NONE); + OPT_U8(RSK_RAINBOW_BRIDGE, "Rainbow Bridge", {"Vanilla", "Always open", "Stones", "Medallions", "Dungeon rewards", "Dungeons", "Tokens", "Triforce Pieces", "Greg"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("RainbowBridge"), mOptionDescriptions[RSK_RAINBOW_BRIDGE], WIDGET_CVAR_COMBOBOX, RO_BRIDGE_VANILLA, false, nullptr, IMFLAG_NONE); OPT_CALLBACK(RSK_RAINBOW_BRIDGE, { mOptions[RSK_BRIDGE_OPTIONS].Hide(); mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].Hide(); @@ -204,37 +204,38 @@ void Settings::CreateOptions() { mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].Hide(); mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].Hide(); mOptions[RSK_RAINBOW_BRIDGE_TOKEN_COUNT].Hide(); + mOptions[RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT].Hide(); switch (CVarGetInteger(CVAR_RANDOMIZER_SETTING("RainbowBridge"), RO_BRIDGE_VANILLA)) { case RO_BRIDGE_STONES: - // Show Bridge Options and Stone Count slider mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); mOptions[RSK_BRIDGE_OPTIONS].Unhide(); mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].Unhide(); break; case RO_BRIDGE_MEDALLIONS: - // Show Bridge Options and Medallion Count Slider mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); mOptions[RSK_BRIDGE_OPTIONS].Unhide(); mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].Unhide(); break; case RO_BRIDGE_DUNGEON_REWARDS: - // Show Bridge Options and Dungeon Reward Count Slider mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); mOptions[RSK_BRIDGE_OPTIONS].Unhide(); mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].Unhide(); break; case RO_BRIDGE_DUNGEONS: - // Show Bridge Options and Dungeon Count Slider mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); mOptions[RSK_BRIDGE_OPTIONS].Unhide(); mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].Unhide(); break; case RO_BRIDGE_TOKENS: - // Show token count slider (not bridge options) mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); mOptions[RSK_BRIDGE_OPTIONS].Hide(); mOptions[RSK_RAINBOW_BRIDGE_TOKEN_COUNT].Unhide(); break; + case RO_BRIDGE_TRIFORCE_PIECES: + mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_BRIDGE_OPTIONS].Hide(); + mOptions[RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT].Unhide(); + break; default: break; } @@ -244,35 +245,20 @@ void Settings::CreateOptions() { OPT_U8(RSK_RAINBOW_BRIDGE_REWARD_COUNT, "Bridge Reward Count", {NumOpts(0, 10)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("RewardCount"), "", WIDGET_CVAR_SLIDER_INT, 9, true); OPT_U8(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT, "Bridge Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("DungeonCount"), "", WIDGET_CVAR_SLIDER_INT, 8, true); OPT_U8(RSK_RAINBOW_BRIDGE_TOKEN_COUNT, "Bridge Token Count", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("TokenCount"), "", WIDGET_CVAR_SLIDER_INT, 100, true); + OPT_U8(RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT, "Bridge Triforce Piece Count", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("TriforcePieceCount"), "", WIDGET_CVAR_SLIDER_INT, 100, true); OPT_U8(RSK_BRIDGE_OPTIONS, "Bridge Reward Options", {"Standard Rewards", "Greg as Reward", "Greg as Wildcard"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("BridgeRewardOptions"), mOptionDescriptions[RSK_BRIDGE_OPTIONS], WIDGET_CVAR_COMBOBOX, RO_BRIDGE_STANDARD_REWARD, false, nullptr, IMFLAG_NONE); OPT_CALLBACK(RSK_BRIDGE_OPTIONS, { const uint8_t bridgeOpt = CVarGetInteger(CVAR_RANDOMIZER_SETTING("BridgeRewardOptions"), RO_BRIDGE_STANDARD_REWARD); if (bridgeOpt == RO_BRIDGE_GREG_REWARD) { - if (mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].GetOptionCount() == 4) { - mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].ChangeOptions(NumOpts(0, 4)); - } - if (mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].GetOptionCount() == 7) { - mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 7)); - } - if (mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].GetOptionCount() == 10) { - mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].ChangeOptions(NumOpts(0, 10)); - } - if (mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].GetOptionCount() == 9) { - mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 9)); - } + mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].ChangeOptions(NumOpts(0, 4)); + mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 7)); + mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].ChangeOptions(NumOpts(0, 10)); + mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 9)); } else { - if (mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].GetOptionCount() == 5) { - mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].ChangeOptions(NumOpts(0, 3)); - } - if (mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].GetOptionCount() == 8) { - mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 6)); - } - if (mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].GetOptionCount() == 11) { - mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].ChangeOptions(NumOpts(0, 9)); - } - if (mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].GetOptionCount() == 10) { - mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 8)); - } + mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].ChangeOptions(NumOpts(0, 3)); + mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 6)); + mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].ChangeOptions(NumOpts(0, 9)); + mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 8)); } }); OPT_U8(RSK_GANONS_TRIALS, "Ganon's Trials", {"Skip", "Set Number", "Random Number"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GanonTrial"), mOptionDescriptions[RSK_GANONS_TRIALS], WIDGET_CVAR_COMBOBOX, RO_GANONS_TRIALS_SET_NUMBER); @@ -431,32 +417,29 @@ void Settings::CreateOptions() { OPT_U8(RSK_BOMBCHU_BAG, "Bombchu Bag", {"None", "Single Bag", "Progressive Bags"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("BombchuBag"), mOptionDescriptions[RSK_BOMBCHU_BAG], WIDGET_CVAR_COMBOBOX, RO_BOMBCHU_BAG_NONE); OPT_U8(RSK_ENABLE_BOMBCHU_DROPS, "Bombchu Drops", {"No", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("EnableBombchuDrops"), mOptionDescriptions[RSK_ENABLE_BOMBCHU_DROPS], WIDGET_CVAR_COMBOBOX, RO_AMMO_DROPS_ON); // TODO: AmmoDrops and/or HeartDropRefill, combine with/separate Ammo Drops from Bombchu Drops? - OPT_U8(RSK_TRIFORCE_HUNT, "Triforce Hunt", {"Off", "Win", "Ganon's Boss Key"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("TriforceHunt"), mOptionDescriptions[RSK_TRIFORCE_HUNT]); - OPT_CALLBACK(RSK_TRIFORCE_HUNT, { - // Remove the pieces required/total sliders and add a separator after Triforce Hunt if Triforce Hunt is off - if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("TriforceHunt"), RO_TRIFORCE_HUNT_OFF) == RO_TRIFORCE_HUNT_OFF) { - mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED].Hide(); - mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL].Hide(); - mOptions[RSK_TRIFORCE_HUNT_PIECES_LOCATION].Hide(); - mOptions[RSK_GANONS_BOSS_KEY].Enable(); - } else { - mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED].Unhide(); - mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL].Unhide(); - mOptions[RSK_TRIFORCE_HUNT_PIECES_LOCATION].Unhide(); - mOptions[RSK_GANONS_BOSS_KEY].Disable( - "This option is disabled because Triforce Hunt is enabled." - "Ganon's Boss key\nwill instead be given to you after Triforce Hunt completion."); - } - }); - OPT_U8(RSK_TRIFORCE_HUNT_PIECES_TOTAL, "Triforce Hunt Total Pieces", {NumOpts(1, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("TriforceHuntTotalPieces"), mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL], WIDGET_CVAR_SLIDER_INT, 29, false, nullptr, IMFLAG_NONE); + // Triforce Hunt: the total piece count is the on/off control. Zero disables the hunt entirely; any + // positive value adds that many Triforce Pieces to the pool and unlocks the pieces-location option. + OPT_U8(RSK_TRIFORCE_HUNT_PIECES_TOTAL, "Triforce Hunt Total Pieces", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("TriforceHuntTotalPieces"), mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL], WIDGET_CVAR_SLIDER_INT, 0, false, nullptr, IMFLAG_NONE); OPT_CALLBACK(RSK_TRIFORCE_HUNT_PIECES_TOTAL, { - // Update triforce pieces required to be capped at the current value for pieces total. - const uint8_t triforceTotal = CVarGetInteger(CVAR_RANDOMIZER_SETTING("TriforceHuntTotalPieces"), 30); - if (mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED].GetOptionCount() != triforceTotal + 1) { - mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED].ChangeOptions(NumOpts(1, triforceTotal + 1)); + const uint8_t triforceTotal = CVarGetInteger(CVAR_RANDOMIZER_SETTING("TriforceHuntTotalPieces"), 0); + if (triforceTotal == 0) { + mOptions[RSK_TRIFORCE_HUNT_PIECES_LOCATION].Hide(); + } else { + mOptions[RSK_TRIFORCE_HUNT_PIECES_LOCATION].Unhide(); + } + if (mOptions[RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT].GetOptionCount() != triforceTotal + 1) { + mOptions[RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT].ChangeOptions(NumOpts(0, triforceTotal)); + } + if (mOptions[RSK_GBK_TRIFORCE_COUNT].GetOptionCount() != triforceTotal + 1) { + mOptions[RSK_GBK_TRIFORCE_COUNT].ChangeOptions(NumOpts(0, triforceTotal)); + } + if (mOptions[RSK_GANONS_SOUL_TRIFORCE_COUNT].GetOptionCount() != triforceTotal + 1) { + mOptions[RSK_GANONS_SOUL_TRIFORCE_COUNT].ChangeOptions(NumOpts(0, triforceTotal)); + } + if (mOptions[RSK_WINCON_TRIFORCE_COUNT].GetOptionCount() != triforceTotal + 1) { + mOptions[RSK_WINCON_TRIFORCE_COUNT].ChangeOptions(NumOpts(0, triforceTotal)); } }); - OPT_U8(RSK_TRIFORCE_HUNT_PIECES_REQUIRED, "Triforce Hunt Required Pieces", {NumOpts(1, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("TriforceHuntRequiredPieces"), mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED], WIDGET_CVAR_SLIDER_INT, 19); OPT_U8(RSK_TRIFORCE_HUNT_PIECES_LOCATION, "Triforce Hunt Pieces Location", {"Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("TriforceHuntPiecesLocation"), mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_LOCATION], WIDGET_CVAR_COMBOBOX, RO_TRIFORCE_HUNT_LOCATION_ANYWHERE); OPT_U8(RSK_MQ_DUNGEON_RANDOM, "MQ Dungeon Setting", {"None", "Set Number", "Random", "Selection Only"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("MQDungeons"), mOptionDescriptions[RSK_MQ_DUNGEON_RANDOM], WIDGET_CVAR_COMBOBOX, RO_MQ_DUNGEONS_NONE, false, nullptr, IMFLAG_NONE); OPT_CALLBACK(RSK_MQ_DUNGEON_RANDOM, { @@ -1069,7 +1052,7 @@ void Settings::CreateOptions() { } }); OPT_BOOL(RSK_SHUFFLE_BEAN_SOULS, "Shuffle Bean Souls", CVAR_RANDOMIZER_SETTING("ShuffleBeanSouls"), mOptionDescriptions[RSK_SHUFFLE_BEAN_SOULS], IMFLAG_SEPARATOR_BOTTOM, WIDGET_CVAR_CHECKBOX, RO_GENERIC_OFF); - OPT_U8(RSK_SHUFFLE_BOSS_SOULS, "Shuffle Boss Souls", {"Off", "On", "On + Ganon"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleBossSouls"), mOptionDescriptions[RSK_SHUFFLE_BOSS_SOULS], WIDGET_CVAR_COMBOBOX); + OPT_U8(RSK_SHUFFLE_BOSS_SOULS, "Shuffle Boss Souls", {"Off", "On"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleBossSouls"), mOptionDescriptions[RSK_SHUFFLE_BOSS_SOULS], WIDGET_CVAR_COMBOBOX); OPT_BOOL(RSK_SHUFFLE_DEKU_STICK_BAG, "Shuffle Deku Stick Bag", CVAR_RANDOMIZER_SETTING("ShuffleDekuStickBag"), mOptionDescriptions[RSK_SHUFFLE_DEKU_STICK_BAG], IMFLAG_SEPARATOR_BOTTOM, WIDGET_CVAR_CHECKBOX, RO_GENERIC_OFF); OPT_CALLBACK(RSK_SHUFFLE_DEKU_STICK_BAG, { if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleDekuStickBag"), 0)) { @@ -1141,78 +1124,169 @@ void Settings::CreateOptions() { } }); OPT_U8(RSK_BOSS_KEYSANITY, "Boss Key Shuffle", {"Start With", "Vanilla", "Own Dungeon", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("BossKeysanity"), mOptionDescriptions[RSK_BOSS_KEYSANITY], WIDGET_CVAR_COMBOBOX, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - OPT_U8(RSK_GANONS_BOSS_KEY, "Ganon's Boss Key", {"Vanilla", "Own Dungeon", "Start With", "Any Dungeon", "Overworld", "Anywhere", "LACS-Vanilla", "LACS-Stones", "LACS-Medallions", "LACS-Rewards", "LACS-Dungeons", "LACS-Tokens", "100 GS Reward"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), mOptionDescriptions[RSK_GANONS_BOSS_KEY], WIDGET_CVAR_COMBOBOX, RO_GANON_BOSS_KEY_VANILLA); + OPT_U8(RSK_GANONS_BOSS_KEY, "Ganon's Boss Key", {"Vanilla", "Own Dungeon", "Start With", "Any Dungeon", "Overworld", "Anywhere", "Trigger-Stones", "Trigger-Medallions", "Trigger-Rewards", "Trigger-Dungeons", "Trigger-Tokens", "Trigger-Triforce Pieces"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), mOptionDescriptions[RSK_GANONS_BOSS_KEY], WIDGET_CVAR_COMBOBOX, RO_GANON_BOSS_KEY_VANILLA); OPT_CALLBACK(RSK_GANONS_BOSS_KEY, { - // Shuffle 100 GS Reward - Force-Enabled if Ganon's Boss Key is on the 100 GS Reward - if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) == - RO_GANON_BOSS_KEY_KAK_TOKENS) { - mOptions[RSK_SHUFFLE_100_GS_REWARD].Disable( - "This option is force-enabled because \"Ganon's Boss Key\" is set to \"100 GS Reward\"."); - } else { - mOptions[RSK_SHUFFLE_100_GS_REWARD].Enable(); - } - mOptions[RSK_LACS_OPTIONS].Hide(); - mOptions[RSK_LACS_STONE_COUNT].Hide(); - mOptions[RSK_LACS_MEDALLION_COUNT].Hide(); - mOptions[RSK_LACS_REWARD_COUNT].Hide(); - mOptions[RSK_LACS_DUNGEON_COUNT].Hide(); - mOptions[RSK_LACS_TOKEN_COUNT].Hide(); + mOptions[RSK_GBK_OPTIONS].Hide(); + mOptions[RSK_GBK_STONE_COUNT].Hide(); + mOptions[RSK_GBK_MEDALLION_COUNT].Hide(); + mOptions[RSK_GBK_REWARD_COUNT].Hide(); + mOptions[RSK_GBK_DUNGEON_COUNT].Hide(); + mOptions[RSK_GBK_TOKEN_COUNT].Hide(); + mOptions[RSK_GBK_TRIFORCE_COUNT].Hide(); switch (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA)) { - case RO_GANON_BOSS_KEY_LACS_STONES: - mOptions[RSK_LACS_OPTIONS].Unhide(); - mOptions[RSK_LACS_STONE_COUNT].Unhide(); + case RO_GANON_BOSS_KEY_STONES: + mOptions[RSK_GBK_OPTIONS].Unhide(); + mOptions[RSK_GBK_STONE_COUNT].Unhide(); break; - case RO_GANON_BOSS_KEY_LACS_MEDALLIONS: - mOptions[RSK_LACS_OPTIONS].Unhide(); - mOptions[RSK_LACS_MEDALLION_COUNT].Unhide(); + case RO_GANON_BOSS_KEY_MEDALLIONS: + mOptions[RSK_GBK_OPTIONS].Unhide(); + mOptions[RSK_GBK_MEDALLION_COUNT].Unhide(); break; - case RO_GANON_BOSS_KEY_LACS_REWARDS: - mOptions[RSK_LACS_OPTIONS].Unhide(); - mOptions[RSK_LACS_REWARD_COUNT].Unhide(); + case RO_GANON_BOSS_KEY_REWARDS: + mOptions[RSK_GBK_OPTIONS].Unhide(); + mOptions[RSK_GBK_REWARD_COUNT].Unhide(); break; - case RO_GANON_BOSS_KEY_LACS_DUNGEONS: - mOptions[RSK_LACS_OPTIONS].Unhide(); - mOptions[RSK_LACS_DUNGEON_COUNT].Unhide(); + case RO_GANON_BOSS_KEY_DUNGEONS: + mOptions[RSK_GBK_OPTIONS].Unhide(); + mOptions[RSK_GBK_DUNGEON_COUNT].Unhide(); break; - case RO_GANON_BOSS_KEY_LACS_TOKENS: - mOptions[RSK_LACS_TOKEN_COUNT].Unhide(); + case RO_GANON_BOSS_KEY_TOKENS: + mOptions[RSK_GBK_TOKEN_COUNT].Unhide(); + break; + case RO_GANON_BOSS_KEY_TRIFORCE_PIECES: + mOptions[RSK_GBK_TRIFORCE_COUNT].Unhide(); break; } }); - OPT_U8(RSK_LACS_STONE_COUNT, "GCBK Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsStoneCount"), "", WIDGET_CVAR_SLIDER_INT, 3, true); - OPT_U8(RSK_LACS_MEDALLION_COUNT, "GCBK Medallion Count", {NumOpts(0, 7)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsMedallionCount"), "", WIDGET_CVAR_SLIDER_INT, 6, true); - OPT_U8(RSK_LACS_REWARD_COUNT, "GCBK Reward Count", {NumOpts(0, 10)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsRewardCount"), "", WIDGET_CVAR_SLIDER_INT, 9, true); - OPT_U8(RSK_LACS_DUNGEON_COUNT, "GCBK Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsDungeonCount"), "", WIDGET_CVAR_SLIDER_INT, 8, true); - OPT_U8(RSK_LACS_TOKEN_COUNT, "GCBK Token Count", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsTokenCount"), "", WIDGET_CVAR_SLIDER_INT, 100, true); - OPT_U8(RSK_LACS_OPTIONS, "GCBK LACS Reward Options", {"Standard Reward", "Greg as Reward", "Greg as Wildcard"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsRewardOptions"), mOptionDescriptions[RSK_LACS_OPTIONS], WIDGET_CVAR_COMBOBOX, RO_LACS_STANDARD_REWARD); - OPT_CALLBACK(RSK_LACS_OPTIONS, { - const uint8_t lacsOpts = CVarGetInteger(CVAR_RANDOMIZER_SETTING("LacsRewardOptions"), RO_LACS_STANDARD_REWARD); - if (lacsOpts == RO_LACS_GREG_REWARD) { - if (mOptions[RSK_LACS_STONE_COUNT].GetOptionCount() == 4) { - mOptions[RSK_LACS_STONE_COUNT].ChangeOptions(NumOpts(0, 4)); - } - if (mOptions[RSK_LACS_MEDALLION_COUNT].GetOptionCount() == 7) { - mOptions[RSK_LACS_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 7)); - } - if (mOptions[RSK_LACS_REWARD_COUNT].GetOptionCount() == 10) { - mOptions[RSK_LACS_REWARD_COUNT].ChangeOptions(NumOpts(0, 10)); - } - if (mOptions[RSK_LACS_DUNGEON_COUNT].GetOptionCount() == 9) { - mOptions[RSK_LACS_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 9)); - } + OPT_U8(RSK_GBK_STONE_COUNT, "GBK Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GbkStoneCount"), "", WIDGET_CVAR_SLIDER_INT, 3, true); + OPT_U8(RSK_GBK_MEDALLION_COUNT, "GBK Medallion Count", {NumOpts(0, 7)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GbkMedallionCount"), "", WIDGET_CVAR_SLIDER_INT, 6, true); + OPT_U8(RSK_GBK_REWARD_COUNT, "GBK Reward Count", {NumOpts(0, 10)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GbkRewardCount"), "", WIDGET_CVAR_SLIDER_INT, 9, true); + OPT_U8(RSK_GBK_DUNGEON_COUNT, "GBK Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GbkDungeonCount"), "", WIDGET_CVAR_SLIDER_INT, 8, true); + OPT_U8(RSK_GBK_TOKEN_COUNT, "GBK Token Count", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GbkTokenCount"), "", WIDGET_CVAR_SLIDER_INT, 100, true); + OPT_U8(RSK_GBK_TRIFORCE_COUNT, "GBK Triforce Piece Count", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GbkTriforceCount"), "", WIDGET_CVAR_SLIDER_INT, 100, true); + OPT_U8(RSK_GBK_OPTIONS, "GBK Reward Options", {"Standard Reward", "Greg as Reward", "Greg as Wildcard"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GbkRewardOptions"), mOptionDescriptions[RSK_GBK_OPTIONS], WIDGET_CVAR_COMBOBOX, RO_CHECK_TRIGGER_STANDARD_REWARD); + OPT_CALLBACK(RSK_GBK_OPTIONS, { + const uint8_t gbkOpts = CVarGetInteger(CVAR_RANDOMIZER_SETTING("GbkRewardOptions"), RO_CHECK_TRIGGER_STANDARD_REWARD); + if (gbkOpts == RO_CHECK_TRIGGER_GREG_REWARD) { + mOptions[RSK_GBK_STONE_COUNT].ChangeOptions(NumOpts(0, 4)); + mOptions[RSK_GBK_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 7)); + mOptions[RSK_GBK_REWARD_COUNT].ChangeOptions(NumOpts(0, 10)); + mOptions[RSK_GBK_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 9)); } else { - if (mOptions[RSK_LACS_STONE_COUNT].GetOptionCount() == 5) { - mOptions[RSK_LACS_STONE_COUNT].ChangeOptions(NumOpts(0, 3)); - } - if (mOptions[RSK_LACS_MEDALLION_COUNT].GetOptionCount() == 8) { - mOptions[RSK_LACS_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 6)); - } - if (mOptions[RSK_LACS_REWARD_COUNT].GetOptionCount() == 11) { - mOptions[RSK_LACS_REWARD_COUNT].ChangeOptions(NumOpts(0, 9)); - } - if (mOptions[RSK_LACS_DUNGEON_COUNT].GetOptionCount() == 10) { - mOptions[RSK_LACS_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 8)); - } + mOptions[RSK_GBK_STONE_COUNT].ChangeOptions(NumOpts(0, 3)); + mOptions[RSK_GBK_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 6)); + mOptions[RSK_GBK_REWARD_COUNT].ChangeOptions(NumOpts(0, 9)); + mOptions[RSK_GBK_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 8)); + } + }); + OPT_U8(RSK_GANONS_SOUL, "Ganon's Soul", {"Start With", "Any Dungeon", "Overworld", "Anywhere", "Trigger-Stones", "Trigger-Medallions", "Trigger-Rewards", "Trigger-Dungeons", "Trigger-Tokens", "Trigger-Triforce Pieces"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleGanonsSoul"), mOptionDescriptions[RSK_GANONS_SOUL], WIDGET_CVAR_COMBOBOX, RO_GANONS_SOUL_STARTWITH); + OPT_CALLBACK(RSK_GANONS_SOUL, { + mOptions[RSK_GANONS_SOUL_OPTIONS].Hide(); + mOptions[RSK_GANONS_SOUL_STONE_COUNT].Hide(); + mOptions[RSK_GANONS_SOUL_MEDALLION_COUNT].Hide(); + mOptions[RSK_GANONS_SOUL_REWARD_COUNT].Hide(); + mOptions[RSK_GANONS_SOUL_DUNGEON_COUNT].Hide(); + mOptions[RSK_GANONS_SOUL_TOKEN_COUNT].Hide(); + mOptions[RSK_GANONS_SOUL_TRIFORCE_COUNT].Hide(); + switch (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonsSoul"), RO_GANONS_SOUL_STARTWITH)) { + case RO_GANONS_SOUL_STONES: + mOptions[RSK_GANONS_SOUL_OPTIONS].Unhide(); + mOptions[RSK_GANONS_SOUL_STONE_COUNT].Unhide(); + break; + case RO_GANONS_SOUL_MEDALLIONS: + mOptions[RSK_GANONS_SOUL_OPTIONS].Unhide(); + mOptions[RSK_GANONS_SOUL_MEDALLION_COUNT].Unhide(); + break; + case RO_GANONS_SOUL_REWARDS: + mOptions[RSK_GANONS_SOUL_OPTIONS].Unhide(); + mOptions[RSK_GANONS_SOUL_REWARD_COUNT].Unhide(); + break; + case RO_GANONS_SOUL_DUNGEONS: + mOptions[RSK_GANONS_SOUL_OPTIONS].Unhide(); + mOptions[RSK_GANONS_SOUL_DUNGEON_COUNT].Unhide(); + break; + case RO_GANONS_SOUL_TOKENS: + mOptions[RSK_GANONS_SOUL_TOKEN_COUNT].Unhide(); + break; + case RO_GANONS_SOUL_TRIFORCE_PIECES: + mOptions[RSK_GANONS_SOUL_TRIFORCE_COUNT].Unhide(); + break; + } + }); + OPT_U8(RSK_GANONS_SOUL_STONE_COUNT, "Ganon's Soul Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GanonsSoulStoneCount"), "", WIDGET_CVAR_SLIDER_INT, 3, true); + OPT_U8(RSK_GANONS_SOUL_MEDALLION_COUNT, "Ganon's Soul Medallion Count", {NumOpts(0, 7)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GanonsSoulMedallionCount"), "", WIDGET_CVAR_SLIDER_INT, 6, true); + OPT_U8(RSK_GANONS_SOUL_REWARD_COUNT, "Ganon's Soul Reward Count", {NumOpts(0, 10)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GanonsSoulRewardCount"), "", WIDGET_CVAR_SLIDER_INT, 9, true); + OPT_U8(RSK_GANONS_SOUL_DUNGEON_COUNT, "Ganon's Soul Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GanonsSoulDungeonCount"), "", WIDGET_CVAR_SLIDER_INT, 8, true); + OPT_U8(RSK_GANONS_SOUL_TOKEN_COUNT, "Ganon's Soul Token Count", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GanonsSoulTokenCount"), "", WIDGET_CVAR_SLIDER_INT, 100, true); + OPT_U8(RSK_GANONS_SOUL_TRIFORCE_COUNT, "Ganon's Soul Triforce Piece Count", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GanonsSoulTriforceCount"), "", WIDGET_CVAR_SLIDER_INT, 100, true); + OPT_U8(RSK_GANONS_SOUL_OPTIONS, "Ganon's Soul Reward Options", {"Standard Reward", "Greg as Reward", "Greg as Wildcard"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GanonsSoulRewardOptions"), mOptionDescriptions[RSK_GANONS_SOUL_OPTIONS], WIDGET_CVAR_COMBOBOX, RO_CHECK_TRIGGER_STANDARD_REWARD); + OPT_CALLBACK(RSK_GANONS_SOUL_OPTIONS, { + const uint8_t soulOpts = CVarGetInteger(CVAR_RANDOMIZER_SETTING("GanonsSoulRewardOptions"), RO_CHECK_TRIGGER_STANDARD_REWARD); + if (soulOpts == RO_CHECK_TRIGGER_GREG_REWARD) { + mOptions[RSK_GANONS_SOUL_STONE_COUNT].ChangeOptions(NumOpts(0, 4)); + mOptions[RSK_GANONS_SOUL_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 7)); + mOptions[RSK_GANONS_SOUL_REWARD_COUNT].ChangeOptions(NumOpts(0, 10)); + mOptions[RSK_GANONS_SOUL_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 9)); + } else { + mOptions[RSK_GANONS_SOUL_STONE_COUNT].ChangeOptions(NumOpts(0, 3)); + mOptions[RSK_GANONS_SOUL_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 6)); + mOptions[RSK_GANONS_SOUL_REWARD_COUNT].ChangeOptions(NumOpts(0, 9)); + mOptions[RSK_GANONS_SOUL_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 8)); + } + }); + OPT_U8(RSK_WINCON, "Win Condition", {"Defeat Ganon", "Anywhere", "Trigger-Stones", "Trigger-Medallions", "Trigger-Rewards", "Trigger-Dungeons", "Trigger-Tokens", "Trigger-Triforce Pieces"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleWincon"), mOptionDescriptions[RSK_WINCON], WIDGET_CVAR_COMBOBOX, RO_WINCON_DEFEAT_GANON); + OPT_CALLBACK(RSK_WINCON, { + mOptions[RSK_WINCON_OPTIONS].Hide(); + mOptions[RSK_WINCON_STONE_COUNT].Hide(); + mOptions[RSK_WINCON_MEDALLION_COUNT].Hide(); + mOptions[RSK_WINCON_REWARD_COUNT].Hide(); + mOptions[RSK_WINCON_DUNGEON_COUNT].Hide(); + mOptions[RSK_WINCON_TOKEN_COUNT].Hide(); + mOptions[RSK_WINCON_TRIFORCE_COUNT].Hide(); + switch (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleWincon"), RO_WINCON_DEFEAT_GANON)) { + case RO_WINCON_STONES: + mOptions[RSK_WINCON_OPTIONS].Unhide(); + mOptions[RSK_WINCON_STONE_COUNT].Unhide(); + break; + case RO_WINCON_MEDALLIONS: + mOptions[RSK_WINCON_OPTIONS].Unhide(); + mOptions[RSK_WINCON_MEDALLION_COUNT].Unhide(); + break; + case RO_WINCON_REWARDS: + mOptions[RSK_WINCON_OPTIONS].Unhide(); + mOptions[RSK_WINCON_REWARD_COUNT].Unhide(); + break; + case RO_WINCON_DUNGEONS: + mOptions[RSK_WINCON_OPTIONS].Unhide(); + mOptions[RSK_WINCON_DUNGEON_COUNT].Unhide(); + break; + case RO_WINCON_TOKENS: + mOptions[RSK_WINCON_TOKEN_COUNT].Unhide(); + break; + case RO_WINCON_TRIFORCE_PIECES: + mOptions[RSK_WINCON_TRIFORCE_COUNT].Unhide(); + break; + } + }); + OPT_U8(RSK_WINCON_STONE_COUNT, "Win Condition Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("WinconStoneCount"), "", WIDGET_CVAR_SLIDER_INT, 3, true); + OPT_U8(RSK_WINCON_MEDALLION_COUNT, "Win Condition Medallion Count", {NumOpts(0, 7)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("WinconMedallionCount"), "", WIDGET_CVAR_SLIDER_INT, 6, true); + OPT_U8(RSK_WINCON_REWARD_COUNT, "Win Condition Reward Count", {NumOpts(0, 10)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("WinconRewardCount"), "", WIDGET_CVAR_SLIDER_INT, 9, true); + OPT_U8(RSK_WINCON_DUNGEON_COUNT, "Win Condition Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("WinconDungeonCount"), "", WIDGET_CVAR_SLIDER_INT, 8, true); + OPT_U8(RSK_WINCON_TOKEN_COUNT, "Win Condition Token Count", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("WinconTokenCount"), "", WIDGET_CVAR_SLIDER_INT, 100, true); + OPT_U8(RSK_WINCON_TRIFORCE_COUNT, "Win Condition Triforce Piece Count", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("WinconTriforceCount"), "", WIDGET_CVAR_SLIDER_INT, 100, true); + OPT_U8(RSK_WINCON_OPTIONS, "Win Condition Reward Options", {"Standard Reward", "Greg as Reward", "Greg as Wildcard"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("WinconRewardOptions"), mOptionDescriptions[RSK_WINCON_OPTIONS], WIDGET_CVAR_COMBOBOX, RO_CHECK_TRIGGER_STANDARD_REWARD); + OPT_CALLBACK(RSK_WINCON_OPTIONS, { + const uint8_t winconOpts = CVarGetInteger(CVAR_RANDOMIZER_SETTING("WinconRewardOptions"), RO_CHECK_TRIGGER_STANDARD_REWARD); + if (winconOpts == RO_CHECK_TRIGGER_GREG_REWARD) { + mOptions[RSK_WINCON_STONE_COUNT].ChangeOptions(NumOpts(0, 4)); + mOptions[RSK_WINCON_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 7)); + mOptions[RSK_WINCON_REWARD_COUNT].ChangeOptions(NumOpts(0, 10)); + mOptions[RSK_WINCON_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 9)); + } else { + mOptions[RSK_WINCON_STONE_COUNT].ChangeOptions(NumOpts(0, 3)); + mOptions[RSK_WINCON_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 6)); + mOptions[RSK_WINCON_REWARD_COUNT].ChangeOptions(NumOpts(0, 9)); + mOptions[RSK_WINCON_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 8)); } }); OPT_U8(RSK_KEYRINGS, "Key Rings", {"Off", "Random", "Count", "Selection"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRings"), mOptionDescriptions[RSK_KEYRINGS], WIDGET_CVAR_COMBOBOX, RO_KEYRINGS_OFF); @@ -1822,14 +1896,34 @@ void Settings::CreateOptions() { &mOptions[RSK_SKIP_SCARECROWS_SONG], }, WidgetContainerType::SECTION); - mOptionGroups[RSG_MENU_SECTION_WINCON] = OptionGroup::SubGroup( - "Win Condition", - { &mOptions[RSK_TRIFORCE_HUNT], &mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL], - &mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED], &mOptions[RSK_TRIFORCE_HUNT_PIECES_LOCATION], - &mOptions[RSK_GANONS_BOSS_KEY], &mOptions[RSK_LACS_OPTIONS], &mOptions[RSK_LACS_MEDALLION_COUNT], - &mOptions[RSK_LACS_STONE_COUNT], &mOptions[RSK_LACS_DUNGEON_COUNT], &mOptions[RSK_LACS_REWARD_COUNT], - &mOptions[RSK_LACS_TOKEN_COUNT] }, - WidgetContainerType::SECTION); + mOptionGroups[RSG_MENU_SECTION_WINCON] = OptionGroup::SubGroup("Win Condition", + { &mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL], + &mOptions[RSK_TRIFORCE_HUNT_PIECES_LOCATION], + &mOptions[RSK_GANONS_BOSS_KEY], + &mOptions[RSK_GBK_OPTIONS], + &mOptions[RSK_GBK_MEDALLION_COUNT], + &mOptions[RSK_GBK_STONE_COUNT], + &mOptions[RSK_GBK_DUNGEON_COUNT], + &mOptions[RSK_GBK_REWARD_COUNT], + &mOptions[RSK_GBK_TOKEN_COUNT], + &mOptions[RSK_GBK_TRIFORCE_COUNT], + &mOptions[RSK_GANONS_SOUL], + &mOptions[RSK_GANONS_SOUL_OPTIONS], + &mOptions[RSK_GANONS_SOUL_MEDALLION_COUNT], + &mOptions[RSK_GANONS_SOUL_STONE_COUNT], + &mOptions[RSK_GANONS_SOUL_DUNGEON_COUNT], + &mOptions[RSK_GANONS_SOUL_REWARD_COUNT], + &mOptions[RSK_GANONS_SOUL_TOKEN_COUNT], + &mOptions[RSK_GANONS_SOUL_TRIFORCE_COUNT], + &mOptions[RSK_WINCON], + &mOptions[RSK_WINCON_OPTIONS], + &mOptions[RSK_WINCON_MEDALLION_COUNT], + &mOptions[RSK_WINCON_STONE_COUNT], + &mOptions[RSK_WINCON_DUNGEON_COUNT], + &mOptions[RSK_WINCON_REWARD_COUNT], + &mOptions[RSK_WINCON_TOKEN_COUNT], + &mOptions[RSK_WINCON_TRIFORCE_COUNT] }, + WidgetContainerType::SECTION); mOptionGroups[RSG_MENU_COLUMN_LOGIC_WINCON] = OptionGroup::SubGroup("", std::initializer_list{ &mOptionGroups[RSG_ITEM_POOL], @@ -1855,6 +1949,7 @@ void Settings::CreateOptions() { &mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT], &mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT], &mOptions[RSK_RAINBOW_BRIDGE_TOKEN_COUNT], + &mOptions[RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT], &mOptions[RSK_GANONS_TRIALS], &mOptions[RSK_TRIAL_COUNT], &mOptions[RSK_MEDALLION_LOCKED_TRIALS], @@ -2104,6 +2199,7 @@ void Settings::CreateOptions() { &mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT], &mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT], &mOptions[RSK_RAINBOW_BRIDGE_TOKEN_COUNT], + &mOptions[RSK_RAINBOW_BRIDGE_TRIFORCE_COUNT], &mOptions[RSK_BRIDGE_OPTIONS], &mOptions[RSK_GANONS_TRIALS], &mOptions[RSK_TRIAL_COUNT], @@ -2132,9 +2228,7 @@ void Settings::CreateOptions() { &mOptions[RSK_DECOUPLED_ENTRANCES], &mOptions[RSK_BOMBCHU_BAG], &mOptions[RSK_ENABLE_BOMBCHU_DROPS], - &mOptions[RSK_TRIFORCE_HUNT], &mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL], - &mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED], &mOptions[RSK_TRIFORCE_HUNT_PIECES_LOCATION], &mOptions[RSK_MQ_DUNGEON_RANDOM], &mOptions[RSK_MQ_DUNGEON_COUNT], @@ -2239,12 +2333,29 @@ void Settings::CreateOptions() { &mOptions[RSK_GERUDO_KEYS], &mOptions[RSK_BOSS_KEYSANITY], &mOptions[RSK_GANONS_BOSS_KEY], - &mOptions[RSK_LACS_STONE_COUNT], - &mOptions[RSK_LACS_MEDALLION_COUNT], - &mOptions[RSK_LACS_DUNGEON_COUNT], - &mOptions[RSK_LACS_REWARD_COUNT], - &mOptions[RSK_LACS_TOKEN_COUNT], - &mOptions[RSK_LACS_OPTIONS], + &mOptions[RSK_GBK_STONE_COUNT], + &mOptions[RSK_GBK_MEDALLION_COUNT], + &mOptions[RSK_GBK_DUNGEON_COUNT], + &mOptions[RSK_GBK_REWARD_COUNT], + &mOptions[RSK_GBK_TOKEN_COUNT], + &mOptions[RSK_GBK_TRIFORCE_COUNT], + &mOptions[RSK_GBK_OPTIONS], + &mOptions[RSK_GANONS_SOUL], + &mOptions[RSK_GANONS_SOUL_STONE_COUNT], + &mOptions[RSK_GANONS_SOUL_MEDALLION_COUNT], + &mOptions[RSK_GANONS_SOUL_DUNGEON_COUNT], + &mOptions[RSK_GANONS_SOUL_REWARD_COUNT], + &mOptions[RSK_GANONS_SOUL_TOKEN_COUNT], + &mOptions[RSK_GANONS_SOUL_TRIFORCE_COUNT], + &mOptions[RSK_GANONS_SOUL_OPTIONS], + &mOptions[RSK_WINCON], + &mOptions[RSK_WINCON_STONE_COUNT], + &mOptions[RSK_WINCON_MEDALLION_COUNT], + &mOptions[RSK_WINCON_DUNGEON_COUNT], + &mOptions[RSK_WINCON_REWARD_COUNT], + &mOptions[RSK_WINCON_TOKEN_COUNT], + &mOptions[RSK_WINCON_TRIFORCE_COUNT], + &mOptions[RSK_WINCON_OPTIONS], &mOptions[RSK_KEYRINGS], &mOptions[RSK_KEYRINGS_RANDOM_COUNT], &mOptions[RSK_KEYRINGS_GERUDO_FORTRESS], @@ -2535,11 +2646,6 @@ void Context::FinalizeSettings(const std::set& excludedLocation mOptions[RSK_STARTING_AGE].Set(RO_AGE_CHILD); } - // Force 100 GS Shuffle if that's where Ganon's Boss Key is - if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_KAK_TOKENS)) { - mOptions[RSK_SHUFFLE_100_GS_REWARD].Set(1); - } - // If we only have MQ, set all dungeons to MQ if (OTRGlobals::Instance->HasMasterQuest() && !OTRGlobals::Instance->HasOriginal()) { mOptions[RSK_MQ_DUNGEON_RANDOM].Set(RO_MQ_DUNGEONS_SET_NUMBER); @@ -2890,18 +2996,54 @@ void Context::FinalizeSettings(const std::set& excludedLocation // TODO: Random Starting Time - if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_LACS_STONES)) { - mLACSCondition = RO_LACS_STONES; - } else if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_LACS_MEDALLIONS)) { - mLACSCondition = RO_LACS_MEDALLIONS; - } else if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_LACS_REWARDS)) { - mLACSCondition = RO_LACS_REWARDS; - } else if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_LACS_DUNGEONS)) { - mLACSCondition = RO_LACS_DUNGEONS; - } else if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_LACS_TOKENS)) { - mLACSCondition = RO_LACS_TOKENS; + if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_STONES)) { + mGBKCondition = RO_CHECK_TRIGGER_STONES; + } else if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_MEDALLIONS)) { + mGBKCondition = RO_CHECK_TRIGGER_MEDALLIONS; + } else if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_REWARDS)) { + mGBKCondition = RO_CHECK_TRIGGER_REWARDS; + } else if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_DUNGEONS)) { + mGBKCondition = RO_CHECK_TRIGGER_DUNGEONS; + } else if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_TOKENS)) { + mGBKCondition = RO_CHECK_TRIGGER_TOKENS; + } else if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_TRIFORCE_PIECES)) { + mGBKCondition = RO_CHECK_TRIGGER_TRIFORCE_PIECES; } else { - mLACSCondition = RO_LACS_VANILLA; + mGBKCondition = RO_CHECK_TRIGGER_NONE; + } + + if (mOptions[RSK_GANONS_SOUL].Is(RO_GANONS_SOUL_STONES)) { + mGanonsSoulCondition = RO_CHECK_TRIGGER_STONES; + } else if (mOptions[RSK_GANONS_SOUL].Is(RO_GANONS_SOUL_MEDALLIONS)) { + mGanonsSoulCondition = RO_CHECK_TRIGGER_MEDALLIONS; + } else if (mOptions[RSK_GANONS_SOUL].Is(RO_GANONS_SOUL_REWARDS)) { + mGanonsSoulCondition = RO_CHECK_TRIGGER_REWARDS; + } else if (mOptions[RSK_GANONS_SOUL].Is(RO_GANONS_SOUL_DUNGEONS)) { + mGanonsSoulCondition = RO_CHECK_TRIGGER_DUNGEONS; + } else if (mOptions[RSK_GANONS_SOUL].Is(RO_GANONS_SOUL_TOKENS)) { + mGanonsSoulCondition = RO_CHECK_TRIGGER_TOKENS; + } else if (mOptions[RSK_GANONS_SOUL].Is(RO_GANONS_SOUL_TRIFORCE_PIECES)) { + mGanonsSoulCondition = RO_CHECK_TRIGGER_TRIFORCE_PIECES; + } else { + mGanonsSoulCondition = RO_CHECK_TRIGGER_NONE; + } + + if (mOptions[RSK_WINCON].Is(RO_WINCON_STONES)) { + mWinCondition = RO_WINCON_STONES; + } else if (mOptions[RSK_WINCON].Is(RO_WINCON_MEDALLIONS)) { + mWinCondition = RO_WINCON_MEDALLIONS; + } else if (mOptions[RSK_WINCON].Is(RO_WINCON_REWARDS)) { + mWinCondition = RO_WINCON_REWARDS; + } else if (mOptions[RSK_WINCON].Is(RO_WINCON_DUNGEONS)) { + mWinCondition = RO_WINCON_DUNGEONS; + } else if (mOptions[RSK_WINCON].Is(RO_WINCON_TOKENS)) { + mWinCondition = RO_WINCON_TOKENS; + } else if (mOptions[RSK_WINCON].Is(RO_WINCON_TRIFORCE_PIECES)) { + mWinCondition = RO_WINCON_TRIFORCE_PIECES; + } else if (mOptions[RSK_WINCON].Is(RO_WINCON_ANYWHERE)) { + mWinCondition = RO_WINCON_ANYWHERE; + } else { + mWinCondition = RO_WINCON_DEFEAT_GANON; } if (!mOptions[RSK_SHUFFLE_WARP_SONGS]) { diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index cc21f4646d..b77a8341db 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1533,6 +1533,7 @@ extern "C" void InitOTR(int argc, char* argv[]) { conf->RegisterVersionUpdater(std::make_shared()); conf->RegisterVersionUpdater(std::make_shared()); conf->RegisterVersionUpdater(std::make_shared()); + conf->RegisterVersionUpdater(std::make_shared()); conf->RunVersionUpdates(); SohGui::SetupGuiElements(); diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index c7252a9378..400289edd7 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -572,10 +572,7 @@ void SaveManager::StartupCheckAndInitMeta(int fileNum) { (int16_t)baseBlock["randomizerInf"][RAND_INF_HAS_WALLET >> 4] & (1 << (RAND_INF_HAS_WALLET & 0xF)); fileMetaInfo[fileNum].triforcePieces = randoBlock.value("triforcePiecesCollected", 0); nlohmann::json& randoSettings = randoBlock["randoSettings"]; - if (randoSettings[RSK_TRIFORCE_HUNT].get() != 0) { - fileMetaInfo[fileNum].maxTriforcePieces = - randoSettings[RSK_TRIFORCE_HUNT_PIECES_REQUIRED].get() + 1; - } + fileMetaInfo[fileNum].maxTriforcePieces = randoSettings[RSK_TRIFORCE_HUNT_PIECES_TOTAL].get(); fileMetaInfo[fileNum].hasFishingRod = (int16_t)baseBlock["randomizerInf"][RAND_INF_FISHING_POLE_FOUND >> 4] & (1 << (RAND_INF_FISHING_POLE_FOUND & 0xF)); fileMetaInfo[fileNum].fishingPoleShuffled = randoSettings[RSK_SHUFFLE_FISHING_POLE].get() != 0; @@ -621,9 +618,8 @@ void SaveManager::InitMeta(int fileNum) { fileMetaInfo[fileNum].health = gSaveContext.health; auto randoContext = Rando::Context::GetInstance(); - fileMetaInfo[fileNum].maxTriforcePieces = IS_RANDO && (bool)randoContext->GetOption(RSK_TRIFORCE_HUNT) - ? randoContext->GetOption(RSK_TRIFORCE_HUNT_PIECES_REQUIRED).Get() + 1 - : 0; + fileMetaInfo[fileNum].maxTriforcePieces = + IS_RANDO ? randoContext->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get() : 0; fileMetaInfo[fileNum].fishingPoleShuffled = IS_RANDO ? (bool)randoContext->GetOption(RSK_SHUFFLE_FISHING_POLE) : false; diff --git a/soh/soh/config/ConfigUpdaters.cpp b/soh/soh/config/ConfigUpdaters.cpp index b08fb7512e..9034b187e1 100644 --- a/soh/soh/config/ConfigUpdaters.cpp +++ b/soh/soh/config/ConfigUpdaters.cpp @@ -1,6 +1,7 @@ #include "ConfigUpdaters.h" #include +#include "soh/Enhancements/randomizer/randomizerEnums.h" namespace SOH { struct Migration { @@ -1447,6 +1448,16 @@ static const Migration version6Migrations[] = { { nullptr, nullptr }, }; +static const Migration version7Migrations[] = { + { "gRandoSettings.LacsStoneCount", "gRandoSettings.GbkStoneCount" }, + { "gRandoSettings.LacsMedallionCount", "gRandoSettings.GbkMedallionCount" }, + { "gRandoSettings.LacsRewardCount", "gRandoSettings.GbkRewardCount" }, + { "gRandoSettings.LacsDungeonCount", "gRandoSettings.GbkDungeonCount" }, + { "gRandoSettings.LacsTokenCount", "gRandoSettings.GbkTokenCount" }, + { "gRandoSettings.LacsRewardOptions", "gRandoSettings.GbkRewardOptions" }, + { nullptr, nullptr }, +}; + static void ApplyMigrationActions(const Migration* migrations) { while (migrations->from != nullptr) { if (migrations->to != nullptr) { @@ -1469,6 +1480,8 @@ ConfigVersion5Updater::ConfigVersion5Updater() : ConfigVersionUpdater(5) { } ConfigVersion6Updater::ConfigVersion6Updater() : ConfigVersionUpdater(6) { } +ConfigVersion7Updater::ConfigVersion7Updater() : ConfigVersionUpdater(7) { +} void ConfigVersion1Updater::Update(Ship::Config* conf) { if (conf->GetInt("Window.Width", 640) == 640) { @@ -1581,4 +1594,70 @@ void ConfigVersion5Updater::Update(Ship::Config* conf) { void ConfigVersion6Updater::Update(Ship::Config* conf) { ApplyMigrationActions(version6Migrations); } + +void ConfigVersion7Updater::Update(Ship::Config* conf) { + ApplyMigrationActions(version7Migrations); + + // Ganon's Boss Key: the LACS-* values were replaced by generic Trigger-* values + switch (CVarGetInteger("gRandoSettings.ShuffleGanonBossKey", 0)) { + case 6: // LACS-Vanilla (Shadow and Spirit Medallions) + CVarSetInteger("gRandoSettings.ShuffleGanonBossKey", RO_GANON_BOSS_KEY_MEDALLIONS); + CVarSetInteger("gRandoSettings.GbkMedallionCount", 2); + break; + case 7: // LACS-Stones + CVarSetInteger("gRandoSettings.ShuffleGanonBossKey", RO_GANON_BOSS_KEY_STONES); + break; + case 8: // LACS-Medallions + CVarSetInteger("gRandoSettings.ShuffleGanonBossKey", RO_GANON_BOSS_KEY_MEDALLIONS); + break; + case 9: // LACS-Rewards + CVarSetInteger("gRandoSettings.ShuffleGanonBossKey", RO_GANON_BOSS_KEY_REWARDS); + break; + case 10: // LACS-Dungeons + CVarSetInteger("gRandoSettings.ShuffleGanonBossKey", RO_GANON_BOSS_KEY_DUNGEONS); + break; + case 11: // LACS-Tokens + CVarSetInteger("gRandoSettings.ShuffleGanonBossKey", RO_GANON_BOSS_KEY_TOKENS); + break; + case 12: // 100 GS Reward + CVarSetInteger("gRandoSettings.ShuffleGanonBossKey", RO_GANON_BOSS_KEY_TOKENS); + CVarSetInteger("gRandoSettings.GbkTokenCount", 100); + break; + } + + // Triforce Hunt: the Off/Win/GBK combobox was folded into TriforceHuntTotalPieces (0 = off) + // and the required piece count moved to the Wincon/GBK trigger counts + switch (CVarGetInteger("gRandoSettings.TriforceHunt", 0)) { + case 1: // Win + CVarSetInteger("gRandoSettings.ShuffleWincon", RO_WINCON_TRIFORCE_PIECES); + CVarSetInteger("gRandoSettings.WinconTriforceCount", + CVarGetInteger("gRandoSettings.TriforceHuntRequiredPieces", 19)); + CVarSetInteger("gRandoSettings.TriforceHuntTotalPieces", + CVarGetInteger("gRandoSettings.TriforceHuntTotalPieces", 29)); + break; + case 2: // Ganon's Boss Key + CVarSetInteger("gRandoSettings.ShuffleGanonBossKey", RO_GANON_BOSS_KEY_TRIFORCE_PIECES); + CVarSetInteger("gRandoSettings.GbkTriforceCount", + CVarGetInteger("gRandoSettings.TriforceHuntRequiredPieces", 19)); + CVarSetInteger("gRandoSettings.TriforceHuntTotalPieces", + CVarGetInteger("gRandoSettings.TriforceHuntTotalPieces", 29)); + break; + default: // Off; a leftover total would now silently enable the hunt + CVarClear("gRandoSettings.TriforceHuntTotalPieces"); + break; + } + CVarClear("gRandoSettings.TriforceHunt"); + CVarClear("gRandoSettings.TriforceHuntRequiredPieces"); + + // Rainbow Bridge: Triforce Pieces was inserted at 7, shifting Greg from 7 to 8 + if (CVarGetInteger("gRandoSettings.RainbowBridge", 0) == 7) { + CVarSetInteger("gRandoSettings.RainbowBridge", RO_BRIDGE_GREG); + } + + // Boss Souls: On + Ganon was split into On plus the standalone Ganon's Soul setting + if (CVarGetInteger("gRandoSettings.ShuffleBossSouls", 0) == 2) { + CVarSetInteger("gRandoSettings.ShuffleBossSouls", RO_BOSS_SOULS_ON); + CVarSetInteger("gRandoSettings.ShuffleGanonsSoul", RO_GANONS_SOUL_ANYWHERE); + } +} } // namespace SOH diff --git a/soh/soh/config/ConfigUpdaters.h b/soh/soh/config/ConfigUpdaters.h index eba834af27..069c4ee0ec 100644 --- a/soh/soh/config/ConfigUpdaters.h +++ b/soh/soh/config/ConfigUpdaters.h @@ -36,4 +36,10 @@ class ConfigVersion6Updater final : public Ship::ConfigVersionUpdater { ConfigVersion6Updater(); void Update(Ship::Config* conf); }; + +class ConfigVersion7Updater final : public Ship::ConfigVersionUpdater { + public: + ConfigVersion7Updater(); + void Update(Ship::Config* conf); +}; } // namespace SOH diff --git a/soh/soh/z_scene_otr.cpp b/soh/soh/z_scene_otr.cpp index 5b0132da2c..8662903db4 100644 --- a/soh/soh/z_scene_otr.cpp +++ b/soh/soh/z_scene_otr.cpp @@ -42,8 +42,8 @@ bool Scene_CommandSpawnList(PlayState* play, SOH::ISceneCommand* cmd) { ActorEntry* entries = (ActorEntry*)cmdStartPos->GetRawPointer(); play->linkActorEntry = &entries[play->setupEntranceList[play->curSpawn].spawn]; - play->linkAgeOnLoad = ((void)0, gSaveContext.linkAge); - s16 linkObjectId = gLinkObjectIds[((void)0, gSaveContext.linkAge)]; + play->linkAgeOnLoad = gSaveContext.linkAge; + s16 linkObjectId = gLinkObjectIds[gSaveContext.linkAge]; Object_Spawn(&play->objectCtx, linkObjectId); @@ -262,13 +262,13 @@ bool Scene_CommandTimeSettings(PlayState* play, SOH::ISceneCommand* cmd) { gTimeIncrement = play->envCtx.timeIncrement; } - play->envCtx.sunPos.x = -(Math_SinS(((void)0, gSaveContext.dayTime) - 0x8000) * 120.0f) * 25.0f; - play->envCtx.sunPos.y = (Math_CosS(((void)0, gSaveContext.dayTime) - 0x8000) * 120.0f) * 25.0f; - play->envCtx.sunPos.z = (Math_CosS(((void)0, gSaveContext.dayTime) - 0x8000) * 20.0f) * 25.0f; + play->envCtx.sunPos.x = -(Math_SinS(gSaveContext.dayTime - 0x8000) * 120.0f) * 25.0f; + play->envCtx.sunPos.y = (Math_CosS(gSaveContext.dayTime - 0x8000) * 120.0f) * 25.0f; + play->envCtx.sunPos.z = (Math_CosS(gSaveContext.dayTime - 0x8000) * 20.0f) * 25.0f; if (((play->envCtx.timeIncrement == 0) && (gSaveContext.cutsceneIndex < 0xFFF0)) || (gSaveContext.entranceIndex == ENTR_LAKE_HYLIA_WARP_PAD)) { - gSaveContext.skyboxTime = ((void)0, gSaveContext.dayTime); + gSaveContext.skyboxTime = gSaveContext.dayTime; if ((gSaveContext.skyboxTime >= 0x2AAC) && (gSaveContext.skyboxTime < 0x4555)) { gSaveContext.skyboxTime = 0x3556; } else if ((gSaveContext.skyboxTime >= 0x4555) && (gSaveContext.skyboxTime < 0x5556)) { @@ -336,9 +336,9 @@ bool Scene_CommandAlternateHeaderList(PlayState* play, SOH::ISceneCommand* cmd) // s32 pad; // SceneCmd* altHeader; - // osSyncPrintf("\n[ZU]sceneset age =[%X]", ((void)0, gSaveContext.linkAge)); - // osSyncPrintf("\n[ZU]sceneset time =[%X]", ((void)0, gSaveContext.cutsceneIndex)); - // osSyncPrintf("\n[ZU]sceneset counter=[%X]", ((void)0, gSaveContext.sceneSetupIndex)); + // osSyncPrintf("\n[ZU]sceneset age =[%X]", gSaveContext.linkAge); + // osSyncPrintf("\n[ZU]sceneset time =[%X]", gSaveContext.cutsceneIndex); + // osSyncPrintf("\n[ZU]sceneset counter=[%X]", gSaveContext.sceneSetupIndex); if (gSaveContext.sceneSetupIndex != 0) { SOH::Scene* desiredHeader = diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 0816b5a6b4..e4d707918c 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -327,6 +327,10 @@ u8 CheckDungeonCount() { dungeonCount++; } + if (Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_GANONS_TOWER)) { + dungeonCount++; + } + return dungeonCount; } @@ -348,24 +352,6 @@ u8 CheckBridgeRewardCount() { return bridgeRewardCount; } -u8 CheckLACSRewardCount() { - u8 lacsRewardCount = 0; - - switch (Randomizer_GetSettingValue(RSK_LACS_OPTIONS)) { - case RO_LACS_WILDCARD_REWARD: - if (Flags_GetRandomizerInf(RAND_INF_GREG_FOUND)) { - lacsRewardCount += 1; - } - break; - case RO_LACS_GREG_REWARD: - if (Flags_GetRandomizerInf(RAND_INF_GREG_FOUND)) { - lacsRewardCount += 1; - } - break; - } - return lacsRewardCount; -} - void Play_Init(GameState* thisx) { PlayState* play = (PlayState*)thisx; GraphicsContext* gfxCtx = play->state.gfxCtx; diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index b7132bc599..b02a591c28 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -1656,7 +1656,8 @@ void Player_DrawGetItemImpl(PlayState* play, Player* this, Vec3f* refPos, s32 dr if (this->getItemEntry.modIndex == MOD_RANDOMIZER && this->getItemEntry.getItemId == RG_ICE_TRAP) { Player_DrawGetItemIceTrap(play, this, refPos, drawIdPlusOne, height); - } else if (this->getItemEntry.modIndex == MOD_RANDOMIZER && this->getItemEntry.getItemId == RG_TRIFORCE_PIECE) { + } else if (this->getItemEntry.modIndex == MOD_RANDOMIZER && + (this->getItemEntry.getItemId == RG_TRIFORCE_PIECE || this->getItemEntry.getItemId == RG_TRIFORCE)) { Randomizer_DrawTriforcePieceGI(play, this->getItemEntry); } else if (this->getItemEntry.drawFunc != NULL) { this->getItemEntry.drawFunc(play, &this->getItemEntry); diff --git a/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c b/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c index 21a849cfaa..e0ac9826f6 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c @@ -1692,14 +1692,18 @@ void func_8090120C(BossGanon2* this, PlayState* play) { (player->meleeWeaponState != 0) && (player->heldItemAction == PLAYER_IA_SWORD_MASTER)) { func_80064520(play, &play->csCtx); GameInteractor_ExecuteOnBossDefeat(&this->actor); - this->subCamId = Play_CreateSubCamera(play); - Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_WAIT); - Play_ChangeCameraStatus(play, this->subCamId, CAM_STAT_ACTIVE); - this->csState = 7; - this->csTimer = 0; - Animation_MorphToPlayOnce(&this->skelAnime, &gGanonFinalBlowAnim, 0.0f); - this->unk_194 = Animation_GetLastFrame(&gGanonFinalBlowAnim); - play->startPlayerCutscene(play, &this->actor, 0x61); + if (GameInteractor_Should(VB_SLAY_GANON, true)) { + this->subCamId = Play_CreateSubCamera(play); + Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_WAIT); + Play_ChangeCameraStatus(play, this->subCamId, CAM_STAT_ACTIVE); + this->csState = 7; + this->csTimer = 0; + Animation_MorphToPlayOnce(&this->skelAnime, &gGanonFinalBlowAnim, 0.0f); + this->unk_194 = Animation_GetLastFrame(&gGanonFinalBlowAnim); + play->startPlayerCutscene(play, &this->actor, 0x61); + } else { + break; + } } else { break; }