diff --git a/CMake/automate-vcpkg.cmake b/CMake/automate-vcpkg.cmake index 4091949594..238b9ddcd0 100644 --- a/CMake/automate-vcpkg.cmake +++ b/CMake/automate-vcpkg.cmake @@ -82,11 +82,7 @@ if (WIN32) endif() if(NOT DEFINED VCPKG_ROOT) - if(NOT DEFINED ENV{VCPKG_ROOT}) - set(VCPKG_ROOT ${VCPKG_FALLBACK_ROOT}) - else() - set(VCPKG_ROOT $ENV{VCPKG_ROOT}) - endif() + set(VCPKG_ROOT ${VCPKG_FALLBACK_ROOT}) endif() # Installs a new copy of Vcpkg or updates an existing one diff --git a/README.md b/README.md index 70a4fafb0d..ba3b6dd857 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,8 @@ If you want to playtest a continuous integration build, you can find them at the * [Windows](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-windows.zip) * [macOS](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-mac.zip) -* [Linux](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-linux.zip) +* [Linux X86_64](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-linux-x86_64.zip) +* [Linux ARM](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-linux-aarch64.zip) ### Further Reading More detailed documentation can be found in the 'docs' directory, including the aforementioned [building instructions](docs/BUILDING.md). diff --git a/soh/assets/custom/lang/en_US.json b/soh/assets/custom/lang/en_US.json index eeebbecbc8..1599b007e2 100644 --- a/soh/assets/custom/lang/en_US.json +++ b/soh/assets/custom/lang/en_US.json @@ -1171,6 +1171,38 @@ "rewards on slider does not change." ] }, + "wincon": { + "name": "Win Condition", + "description": [ + "Defeat Ganon - Defeat Ganon to complete the game.\n", + "\n", + "Anywhere - Collect the Triforce to complete the game. It can appear anywhere in the world.\n", + "\n", + "Trigger - These settings put the completing the game on a trigger, ", + "playing the credits when the requirements are 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 into ", + "the blue warp after the boss.\n", + "- Tokens: Obtain the specified amount of Skulltula tokens.\n", + "- Triforce Pieces: Obtain the specified number of Triforce Pieces." + ] + }, + "wincon_options": { + "name": "Win Condition Options", + "description": [ + "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." + ] + }, "big_poe_count": { "name": "Big Poe Target Count", "description": "The Poe collector will give a reward for turning in this many Big Poes." @@ -1568,12 +1600,6 @@ "ganons_soul_triforce_count": { "name": "Ganon's Soul Triforce Piece Count" }, - "wincon": { - "name": "Win Condition" - }, - "wincon_options": { - "name": "Win Condition Reward Options" - }, "wincon_medallion_count": { "name": "Win Condition Medallion Count" }, @@ -2303,7 +2329,7 @@ }, "pass_guards_with_nothing": { "name": "Sneak Past Moving Gerudo Guards with No Items", - "description": "The logic normally guarantees Bow or Hookshot to stun them from a distance, but every moving guard can be passed with basic movement and AI manipulation" + "description": "The logic normally guarantees Bow or Hookshot to stun them from a distance, but every moving guard can be passed with basic movement and AI manipulation. This trick also enables reading the sign outside GTG without Gerudo card, which is barely possible." }, "gf_wasteland_gate_sidehop_skip": { "name": "Gerudo's Fortress Gate Skip with Sidehop", @@ -2677,7 +2703,7 @@ }, "water_dragon_jump_dive": { "name": "Water Temple Dragon Statue Jump Dive", - "description": "If you come into the dragon statue room from the serpent river, you can sidehop down from above and get into the tunnel without needing either Iron Boots or a Scale. Alternativly, you can do this while equipping irons in midair to get into the tunnel without swim. This trick applies to both Vanilla and Master Quest. In Vanilla, you must shoot the switch from above with the Bow, and then quickly get through the tunnel before the gate closes." + "description": "If you come into the dragon statue room from the serpent river, you can sidehop down from above and get into the tunnel without needing either Iron Boots or a Scale. Alternatively, you can do this while equipping irons in midair to get into the tunnel without swim. This trick applies to both Vanilla and Master Quest. In Vanilla, you must shoot the switch from above with the Bow, and then quickly get through the tunnel before the gate closes." }, "water_adult_dragon": { "name": "Water Temple Dragon Statue Switch from Above the Water as Adult", @@ -2913,7 +2939,7 @@ }, "gtg_mq_with_hookshot": { "name": "Gerudo Training Ground MQ Left Side Silver Rupees with Hookshot", - "description": "The highest Silver Rupee can be obtained by hookshooting the target and then immediately jumpslashing toward the Rupee." + "description": "The highest Silver Rupee can be obtained by hookshotting the target and then immediately jumpslashing toward the Rupee." }, "gtg_mq_without_hookshot": { "name": "Gerudo Training Ground MQ Left Side Silver Rupees without Hookshot", diff --git a/soh/include/z64item.h b/soh/include/z64item.h index 137ed09a85..2e21a9c2fd 100644 --- a/soh/include/z64item.h +++ b/soh/include/z64item.h @@ -594,8 +594,9 @@ typedef enum { /* 0x7A */ GID_SONG_TIME, /* 0x7B */ GID_SONG_STORM, /* 0x7C */ GID_TRIFORCE_PIECE, - /* 0x7D */ GID_FISHING_POLE, - /* 0x7E */ GID_MAXIMUM + /* 0x7D */ GID_TRIFORCE, + /* 0x7E */ GID_FISHING_POLE, + /* 0x7F */ GID_MAXIMUM } GetItemDrawID; diff --git a/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp b/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp index 7c57ed7dca..f8ca31b56f 100644 --- a/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp +++ b/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp @@ -1,5 +1,6 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/ShipInit.hpp" +#include "soh/Enhancements/randomizer/SeedContext.h" extern "C" { #include "z64save.h" @@ -23,7 +24,9 @@ static void RegisterBossDefeatTimestamps() { BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_GANON, TIMESTAMP_DEFEAT_GANONDORF); COND_ID_HOOK(OnBossDefeat, ACTOR_BOSS_GANON2, true, [](void* refActor) { gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = static_cast(GAMEPLAYSTAT_TOTAL_TIME); - gSaveContext.ship.stats.gameComplete = true; + if (!IS_RANDO || RAND_GET_OPTION(RSK_WINCON).Is(RO_WINCON_DEFEAT_GANON)) { + gSaveContext.ship.stats.gameComplete = true; + } }); } diff --git a/soh/soh/Enhancements/Minigames/BombchuBowling.cpp b/soh/soh/Enhancements/Minigames/BombchuBowling.cpp index 8ce959e02c..84f08b146e 100644 --- a/soh/soh/Enhancements/Minigames/BombchuBowling.cpp +++ b/soh/soh/Enhancements/Minigames/BombchuBowling.cpp @@ -88,11 +88,22 @@ static void RegisterBombchuBowlingNoBigCucco() { } static void RegisterBombchuBowlingAmmo() { - COND_VB_SHOULD(VB_SET_BOMBCHU_BOWLING_AMMO, - CVAR_BOWLING_VALUE && (CVAR_BOWLING_AMMO_VALUE != CUCCO_BOWLING_AMMO_DEFAULT), { - gPlayState->bombchuBowlingStatus = CVAR_BOWLING_AMMO_VALUE; - *should = false; - }); + static s32 prevAmmoValue = CUCCO_BOWLING_AMMO_DEFAULT; + s32 nextAmmoValue = CVAR_BOWLING_VALUE ? CVAR_BOWLING_AMMO_VALUE : CUCCO_BOWLING_AMMO_DEFAULT; + + if ((prevAmmoValue != nextAmmoValue) && (gPlayState->bombchuBowlingStatus > 0)) { + gPlayState->bombchuBowlingStatus += nextAmmoValue - prevAmmoValue; + if (gPlayState->bombchuBowlingStatus <= 0) { + gPlayState->bombchuBowlingStatus = -1; + } + prevAmmoValue = nextAmmoValue; + } + + COND_VB_SHOULD(VB_SET_BOMBCHU_BOWLING_AMMO, nextAmmoValue != CUCCO_BOWLING_AMMO_DEFAULT, { + prevAmmoValue = CVAR_BOWLING_AMMO_VALUE; + gPlayState->bombchuBowlingStatus = prevAmmoValue; + *should = false; + }); } static void RegisterBombchuBowlingFirstPrize() { diff --git a/soh/soh/Enhancements/SpeedModifiers.cpp b/soh/soh/Enhancements/SpeedModifiers.cpp index 17f0689ba9..4d6ab8feb4 100644 --- a/soh/soh/Enhancements/SpeedModifiers.cpp +++ b/soh/soh/Enhancements/SpeedModifiers.cpp @@ -64,6 +64,9 @@ static void RegisterSpeedModifiers() { COND_VB_SHOULD(VB_PLAYER_LIMIT_JUMP_SPEED, speedModifierActive || bunnyHoodActive, { Player* player = va_arg(args, Player*); if (ShouldAmplifyJump(player)) { + f32 maxSpeed = + (R_RUN_SPEED_LIMIT / 100.0f) * Ship_GetBunnyHoodJumpFactor(player) * GetSpeedModifierJumpFactor(); + player->linearVelocity = CLAMP(player->linearVelocity, -maxSpeed, maxSpeed); *should = false; } }); diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/GanonIntros.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/GanonIntros.cpp index 1f634a8425..e7dc93f1e6 100644 --- a/soh/soh/Enhancements/TimeSavers/SkipCutscene/GanonIntros.cpp +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/GanonIntros.cpp @@ -2,7 +2,9 @@ #include +#include "soh/Enhancements/custom-message/CustomMessageTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/randomizer/SeedContext.h" #include "soh/ShipInit.hpp" extern "C" { @@ -29,7 +31,13 @@ void RegisterGanonIntros() { } }); - COND_VB_SHOULD(VB_PLAY_GANONDORF_INTRO_CS, CVAR_VALUE || IS_RANDO || IS_BOSS_RUSH, { *should = false; }); + COND_VB_SHOULD(VB_PLAY_GANONDORF_INTRO_CS, CVAR_VALUE || IS_RANDO || IS_BOSS_RUSH, { + va_arg(args, void*); + u16 textId = va_arg(args, int); + if (textId != TEXT_GANONDORF || !IS_RANDO || !RAND_GET_OPTION(RSK_GANONDORF_HINT)) { + *should = false; + } + }); // Skip Ganon rising from the rubble, transforming and landing. Skip to knocking sword away. COND_VB_SHOULD(VB_PLAY_GANON_INTRO_CS, CVAR_VALUE, { diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipKingZoraMoveOver.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipKingZoraMoveOver.cpp index 836fec9860..811917e250 100644 --- a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipKingZoraMoveOver.cpp +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipKingZoraMoveOver.cpp @@ -8,6 +8,7 @@ extern "C" { s32 EnKz_FollowPath(EnKz* enKz, PlayState* play); s32 EnKz_SetMovedPos(EnKz* enKz, PlayState* play); +void EnKz_Mweep(EnKz* enKz, PlayState* play); void EnKz_SetupMweep(EnKz* enKz, PlayState* play); void EnKz_Wait(EnKz* enKz, PlayState* play); } @@ -35,7 +36,11 @@ static void EnKz_MweepNoCutscene(EnKz* enKz, PlayState* play) { static void EnKz_InterceptMweep(void* actorPtr) { EnKz* enKz = (EnKz*)actorPtr; - if (enKz->actionFunc != EnKz_SetupMweep) { + if (enKz->actionFunc == EnKz_Mweep) { + Play_ChangeCameraStatus(gPlayState, enKz->gameplayCamera, CAM_STAT_ACTIVE); + Play_ClearCamera(gPlayState, enKz->cutsceneCamera); + Player_SetCsActionWithHaltedActors(gPlayState, &enKz->actor, 7); + } else if (enKz->actionFunc != EnKz_SetupMweep) { return; } diff --git a/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp index 010c409d93..80e8c0b22b 100644 --- a/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp @@ -468,7 +468,7 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li *should = false; // We should still come out of a softlock if the setting was changed during forced dialogue - if (GET_PLAYER(gPlayState)->csAction != 7) { + if (GET_PLAYER(gPlayState)->csAction == 8) { Player_SetCsAction(gPlayState, NULL, 7); } } diff --git a/soh/soh/Enhancements/customequipment.cpp b/soh/soh/Enhancements/customequipment.cpp index a162d3d10b..94f5bda98e 100644 --- a/soh/soh/Enhancements/customequipment.cpp +++ b/soh/soh/Enhancements/customequipment.cpp @@ -61,6 +61,26 @@ static Gfx* LoadCustomGfx(const char* path) { return ResourceMgr_LoadGfxByName(path); } +// Shooting Gallery being weird giving bow and then load slingshot model when child Link +// This function is used to determine if bow or slingshot model should be loaded +static bool UsesBowModel(Player* player) { + if (CVarGetInteger(CVAR_ENHANCEMENT("BowSlingshotAmmoFix"), 0) || + CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) { + return !Player_HoldsSlingshot(player); + } + return LINK_IS_ADULT; +} + +// Detects cutscenes that swap the left hand DL without updating leftHandType. +static bool IsLeftHandSwappedByCutscene(Player* player) { + Gfx** dLists = player->leftHandDLists; + if (dLists == &gPlayerLeftHandOpenDLs[0] || dLists == &gPlayerLeftHandOpenDLs[1] || + dLists == &gPlayerLeftHandClosedDLs[0] || dLists == &gPlayerLeftHandClosedDLs[1]) { + return true; + } + return dLists == &gPlayerLeftHandBgsDLs[1] && player->leftHandType != PLAYER_MODELTYPE_LH_BGS; +} + static u8 sLastValidSwordEquip = EQUIP_VALUE_SWORD_NONE; static u8 GetEquippedSwordValue(PlayState* play) { @@ -250,6 +270,8 @@ static void RegisterCustomEquipment() { } break; } + if (IsLeftHandSwappedByCutscene(player)) + break; switch ((u8)player->leftHandType) { case PLAYER_MODELTYPE_LH_SWORD: { if (player == GET_PLAYER(play)) { @@ -317,7 +339,7 @@ static void RegisterCustomEquipment() { case PLAYER_MODELTYPE_RH_BOW_SLINGSHOT_2: fpsHand = isAdult ? gCustomAdultFPSHandDL : gCustomChildFPSHandDL; fpsWeapon = - Player_HoldsBow(player) + UsesBowModel(player) ? ResolveCustomChain({ gCustomFPSBowDL, gCustomBowDL, nullptr }) : ResolveCustomChain({ gCustomFPSSlingshotDL, gCustomSlingshotDL, nullptr }); break; @@ -391,7 +413,7 @@ static void RegisterCustomEquipment() { break; case PLAYER_MODELTYPE_RH_BOW_SLINGSHOT: case PLAYER_MODELTYPE_RH_BOW_SLINGSHOT_2: - customDL = Player_HoldsBow(player) ? gCustomBowDL : gCustomSlingshotDL; + customDL = UsesBowModel(player) ? gCustomBowDL : gCustomSlingshotDL; break; case PLAYER_MODELTYPE_RH_HOOKSHOT: customDL = (player->heldItemAction == PLAYER_IA_HOOKSHOT) ? gCustomHookshotDL diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index e2dd08762e..438b490f8e 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -436,6 +436,34 @@ std::string decodeNTSCPlayerNameChar(int code) { return ret; } +char decodePlayerNameChar(uint8_t code, uint8_t language) { + if (language == NAME_LANGUAGE_PAL) { + return z2ASCII(code); + } + const std::string decoded = decodeNTSCPlayerNameChar(code); + return (decoded.length() == 1 && decoded[0] >= 32 && decoded[0] <= 126) ? decoded[0] : ' '; +} + +uint8_t encodePlayerNameChar(char c, uint8_t language) { + const bool pal = language == NAME_LANGUAGE_PAL; + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'Z') { + return (pal ? 10 : 171) + (c - 'A'); + } + if (c >= 'a' && c <= 'z') { + return (pal ? 36 : 197) + (c - 'a'); + } + if (c == '-') { + return pal ? 0x3F : 0xE4; + } + if (c == '.') { + return pal ? 0x40 : 0xEA; + } + return pal ? 0x3E : 0xDF; +} + enum MagicLevel { MAGIC_LEVEL_NONE, MAGIC_LEVEL_SINGLE, MAGIC_LEVEL_DOUBLE }; std::map magicLevelMap = { @@ -528,13 +556,8 @@ void DrawGeneralTab() { // Convert current name to ASCII for display if (!nameInputInitialized || ImGui::IsWindowFocused()) { for (int i = 0; i < 8; i++) { - if (gSaveContext.ship.filenameLanguage == NAME_LANGUAGE_PAL) { - playerNameInput[i] = z2ASCII(gSaveContext.playerName[i]); - } else { - const std::string decoded = decodeNTSCPlayerNameChar(gSaveContext.playerName[i]); - playerNameInput[i] = - (decoded.length() == 1 && decoded[0] >= 32 && decoded[0] <= 126) ? decoded[0] : ' '; - } + playerNameInput[i] = + decodePlayerNameChar(gSaveContext.playerName[i], gSaveContext.ship.filenameLanguage); } playerNameInput[8] = '\0'; nameInputInitialized = true; @@ -545,12 +568,11 @@ void DrawGeneralTab() { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); if (ImGui::InputText("##PlayerNameInput", playerNameInput, 9)) { // Convert ASCII input back to game encoding + bool ended = false; for (int i = 0; i < 8; i++) { - if (playerNameInput[i] == '\0') { - gSaveContext.playerName[i] = 223; - } else { - gSaveContext.playerName[i] = playerNameInput[i]; - } + ended = ended || playerNameInput[i] == '\0'; + gSaveContext.playerName[i] = + encodePlayerNameChar(ended ? ' ' : playerNameInput[i], gSaveContext.ship.filenameLanguage); } nameInputInitialized = false; } @@ -563,6 +585,7 @@ void DrawGeneralTab() { // Filename encoding const bool hasPAL = (sGerMessageEntryTablePtr != nullptr) && (sFraMessageEntryTablePtr != nullptr); const bool hasNTSC = (sJpnMessageEntryTablePtr != nullptr); + const uint8_t oldNameLanguage = gSaveContext.ship.filenameLanguage; if (hasPAL && hasNTSC) { Combobox("Player Name Language", &gSaveContext.ship.filenameLanguage, filenameLanguageMap, comboboxOptionsBase.Tooltip("Encoding used for Player Name")); @@ -576,6 +599,13 @@ void DrawGeneralTab() { comboboxOptionsBase.Tooltip("Encoding used for Player Name")); ImGui::EndDisabled(); } + if (canEditName && gSaveContext.ship.filenameLanguage != oldNameLanguage) { + for (int i = 0; i < 8; i++) { + gSaveContext.playerName[i] = encodePlayerNameChar( + decodePlayerNameChar(gSaveContext.playerName[i], oldNameLanguage), gSaveContext.ship.filenameLanguage); + } + nameInputInitialized = false; + } UIWidgets::EndCard(); UIWidgets::BeginCard("healthCard"); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 850a7c36aa..85ed017e1b 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -41,6 +41,14 @@ typedef enum { // - None VB_AMY_SOLVE, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Color_RGB8` + VB_APPLY_MAGIC_METER_FILL_COLOR, + // #### `result` // ```c // true @@ -50,6 +58,14 @@ typedef enum { // - `s8*` sCsState VB_BARINADE_DEATH_SCENE, + // #### `result` + // ```c + // Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE) + // ``` + // #### `args` + // - `*BossVa` + VB_BARINADE_PLAY_REMATCH_INTRO, + // #### `result` // ```c // this->actor.textId == 0x401A @@ -1858,6 +1874,14 @@ typedef enum { // - `*EnMs` VB_MAGIC_BEAN_SALESMAN_TAKE_MONEY, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_MAGIC_METER_DEPLETE_WHILE_CONSUMING, + // #### `result` // ```c // CHECK_QUEST_ITEM(QUEST_SONG_EPONA) @@ -2265,6 +2289,7 @@ typedef enum { // ``` // #### `args` // - `*BossGanon` + // - `u16` text ID VB_PLAY_GANONDORF_INTRO_CS, // #### `result` diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index 71ae03c92d..66d90c74a4 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -293,6 +293,7 @@ void LoadStatsVersion1() { SaveManager::Instance->LoadData("fileCreatedAt", gSaveContext.ship.stats.fileCreatedAt); SaveManager::Instance->LoadData("playTimer", gSaveContext.ship.stats.playTimer); SaveManager::Instance->LoadData("pauseTimer", gSaveContext.ship.stats.pauseTimer); + SaveManager::Instance->LoadData("gameComplete", gSaveContext.ship.stats.gameComplete); SaveManager::Instance->LoadArray( "itemTimestamps", ARRAY_COUNT(gSaveContext.ship.stats.itemTimestamp), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.ship.stats.itemTimestamp[i]); }); @@ -343,6 +344,7 @@ void SaveStats(const SaveContext& saveContext, int sectionID, bool fullSave) { SaveManager::Instance->SaveData("fileCreatedAt", saveContext.ship.stats.fileCreatedAt); SaveManager::Instance->SaveData("playTimer", saveContext.ship.stats.playTimer); SaveManager::Instance->SaveData("pauseTimer", saveContext.ship.stats.pauseTimer); + SaveManager::Instance->SaveData("gameComplete", saveContext.ship.stats.gameComplete); SaveManager::Instance->SaveArray( "itemTimestamps", ARRAY_COUNT(saveContext.ship.stats.itemTimestamp), [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.ship.stats.itemTimestamp[i]); }); diff --git a/soh/soh/Enhancements/gameplaystats.h b/soh/soh/Enhancements/gameplaystats.h index bf9d651a6c..e9121bfc43 100644 --- a/soh/soh/Enhancements/gameplaystats.h +++ b/soh/soh/Enhancements/gameplaystats.h @@ -25,10 +25,10 @@ char* GameplayStats_GetCurrentTime(); ? (!gSaveContext.ship.stats.firstInput \ ? 0 \ : ((GetUnixTimestamp() - gSaveContext.ship.stats.firstInput) / 100)) \ - : (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \ - ? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \ - : (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \ - ? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \ + : (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \ + ? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \ + : (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \ + ? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \ : gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TIMESPLITS_COMPLETED]))) \ : (gSaveContext.ship.stats.playTimer / 2 + gSaveContext.ship.stats.pauseTimer / 3)) #define CURRENT_MODE_TIMER \ diff --git a/soh/soh/Enhancements/randomizer/3drando/hint_list.cpp b/soh/soh/Enhancements/randomizer/3drando/hint_list.cpp index 2cbff54b26..38db0742cc 100644 --- a/soh/soh/Enhancements/randomizer/3drando/hint_list.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/hint_list.cpp @@ -2152,6 +2152,11 @@ void StaticData::HintTable_Init() { | WINCON HINT TEXT | ---------------------------*/ + hintTextTable[RHT_WINCON_DEFEAT_GANON_HINT] = HintText(CustomMessage("$wAnd the #Triforce# will be granted once #the evil one# is defeated.^", + /*german*/ "$wUnd das #Triforce# wird gewährt, sobald #der Böse# besiegt wurde.^", + /*french*/ "$wAussi, la #Triforce# sera accordée une fois que #le Malin# aura été vaincu.^", + {QM_YELLOW, QM_PINK})); + 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#.^", diff --git a/soh/soh/Enhancements/randomizer/ShuffleFairies.cpp b/soh/soh/Enhancements/randomizer/ShuffleFairies.cpp index 4eef04b33c..450dd0387e 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleFairies.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleFairies.cpp @@ -64,6 +64,7 @@ CheckIdentity ShuffleFairies_GetFairyIdentity(int32_t params, ActorID id) { CheckIdentity fairyIdentity; s16 sceneNum = gPlayState->sceneNum; fairyIdentity.randomizerInf = RAND_INF_MAX; + fairyIdentity.randomizerCheck = RC_UNKNOWN_CHECK; if (sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_NIGHT || sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_RUINS) { sceneNum = SCENE_TEMPLE_OF_TIME_EXTERIOR_DAY; @@ -73,7 +74,6 @@ CheckIdentity ShuffleFairies_GetFairyIdentity(int32_t params, ActorID id) { if (location->GetRandomizerCheck() == RC_UNKNOWN_CHECK) { SPDLOG_WARN("FairyGetIdentity did not receive a valid RC value ({}).", location->GetRandomizerCheck()); - assert(false); } else { fairyIdentity.randomizerInf = static_cast(location->GetCollectionCheck().flag); fairyIdentity.randomizerCheck = location->GetRandomizerCheck(); @@ -84,6 +84,9 @@ CheckIdentity ShuffleFairies_GetFairyIdentity(int32_t params, ActorID id) { static bool SpawnFairy(f32 posX, f32 posY, f32 posZ, int32_t params, FairyType fairyType, ActorID id) { CheckIdentity fairyIdentity = ShuffleFairies_GetFairyIdentity(params, id); + if (fairyIdentity.randomizerCheck == RC_UNKNOWN_CHECK) { + return false; + } if (!Flags_GetRandomizerInf(fairyIdentity.randomizerInf)) { Actor* fairy = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_ELF, posX, posY - 30.0f, posZ, 0, 0, 0, fairyType); diff --git a/soh/soh/Enhancements/randomizer/ShuffleSilver.cpp b/soh/soh/Enhancements/randomizer/ShuffleSilver.cpp index 0d32ba0473..c4fb0e4756 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleSilver.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleSilver.cpp @@ -334,8 +334,8 @@ void Rando::StaticData::RegisterSilverLocations() { locationTable[RC_SPIRIT_NEAR_DOOR_SUN_SILVER] = Location::Collectable(RC_SPIRIT_NEAR_DOOR_SUN_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(-984, -450), "Near Door Sun Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_NEAR_DOOR_SUN_SILVER)); locationTable[RC_SPIRIT_NW_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_NW_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1284, -1355), "Northwest Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_NW_BOULDER_SILVER)); locationTable[RC_SPIRIT_SW_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_SW_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1284, -813), "Southwest Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_SW_BOULDER_SILVER)); - locationTable[RC_SPIRIT_SE_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_SE_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1856, -944), "Southeast Boulder Super", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_SE_BOULDER_SILVER)); - locationTable[RC_SPIRIT_NE_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_NE_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1856, -1219), "Notheast Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_NE_BOULDER_SILVER)); + locationTable[RC_SPIRIT_SE_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_SE_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1856, -944), "Southeast Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_SE_BOULDER_SILVER)); + locationTable[RC_SPIRIT_NE_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_NE_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1856, -1219), "Northeast Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_NE_BOULDER_SILVER)); locationTable[RC_SPIRIT_AIRBORNE_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_AIRBORNE_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1573, -920), "Airborne Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_AIRBORNE_BOULDER_SILVER)); locationTable[RC_BOTW_B1_LADDER_SILVER] = Location::Collectable(RC_BOTW_B1_LADDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_BOTTOM_OF_THE_WELL, TWO_ACTOR_PARAMS(-796, -150), "B1 Ladder Silver", RHT_BOTW_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_BOTW_B1_LADDER_SILVER)); locationTable[RC_BOTW_B2_LADDER_SILVER] = Location::Collectable(RC_BOTW_B2_LADDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_BOTTOM_OF_THE_WELL, TWO_ACTOR_PARAMS(-614, -297), "B2 Ladder Silver", RHT_BOTW_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_BOTW_B2_LADDER_SILVER)); @@ -378,7 +378,7 @@ void Rando::StaticData::RegisterSilverLocations() { locationTable[RC_GANONS_CASTLE_UNDER_LEDGE_FOREST_SILVER] = Location::Collectable(RC_GANONS_CASTLE_UNDER_LEDGE_FOREST_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(1651, 2021), "Under Ledge Forest Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_UNDER_LEDGE_FOREST_SILVER)); locationTable[RC_GANONS_CASTLE_ON_TARGET_FOREST_SILVER] = Location::Collectable(RC_GANONS_CASTLE_ON_TARGET_FOREST_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(1634, 1550), "On Target Forest Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_ON_TARGET_FOREST_SILVER)); locationTable[RC_GANONS_CASTLE_TORCH_SLUG_FIRE_SILVER] = Location::Collectable(RC_GANONS_CASTLE_TORCH_SLUG_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-854, -3679), "Torch Slug Fire Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_TORCH_SLUG_FIRE_SILVER)); - locationTable[RC_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER] = Location::Collectable(RC_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-1555, -2317), "Flame Jets Fire Temple Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER)); + locationTable[RC_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER] = Location::Collectable(RC_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-1555, -2317), "Flame Jets Fire Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER)); locationTable[RC_GANONS_CASTLE_DISTANT_PLATFORM_FIRE_SILVER] = Location::Collectable(RC_GANONS_CASTLE_DISTANT_PLATFORM_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-515, -3253), "Distant Platform Fire Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_DISTANT_PLATFORM_FIRE_SILVER)); locationTable[RC_GANONS_CASTLE_CLOSE_PLATFORM_FIRE_SILVER] = Location::Collectable(RC_GANONS_CASTLE_CLOSE_PLATFORM_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-604, -2592), "Close Platform Fire Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_CLOSE_PLATFORM_FIRE_SILVER)); locationTable[RC_GANONS_GANONS_CASTLE_UNDER_PILLAR_FIRE_SILVER] = Location::Collectable(RC_GANONS_GANONS_CASTLE_UNDER_PILLAR_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-1867, -2754), "Under Pillar Fire Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_GANONS_CASTLE_UNDER_PILLAR_FIRE_SILVER)); diff --git a/soh/soh/Enhancements/randomizer/draw.cpp b/soh/soh/Enhancements/randomizer/draw.cpp index d93bd39bdd..65b8a51a61 100644 --- a/soh/soh/Enhancements/randomizer/draw.cpp +++ b/soh/soh/Enhancements/randomizer/draw.cpp @@ -357,6 +357,10 @@ extern "C" void Randomizer_DrawTriforcePieceGI(PlayState* play, GetItemEntry get if (rando->GetRandoSettingValue(RSK_GANONS_SOUL) == RO_GANONS_SOUL_TRIFORCE_PIECES) { fullTriforce = fullTriforce || (rando->GetRandoSettingValue(RSK_GANONS_SOUL_TRIFORCE_COUNT) == current); } + if (getItemEntry.getItemId == RG_TRIFORCE) { + fullTriforce = true; + triforcePieceScale = 0.035f; + } Matrix_Scale(triforcePieceScale, triforcePieceScale, triforcePieceScale, MTXMODE_APPLY); diff --git a/soh/soh/Enhancements/randomizer/dungeon.cpp b/soh/soh/Enhancements/randomizer/dungeon.cpp index e0fb53132e..d730287635 100644 --- a/soh/soh/Enhancements/randomizer/dungeon.cpp +++ b/soh/soh/Enhancements/randomizer/dungeon.cpp @@ -177,6 +177,11 @@ int8_t GetSceneTotalSmallKeys(const SaveContext* saveContext, const SceneID scen return FindCurrentSmallKeys(saveContext, scene); } +int8_t GetSceneUnspentSmallKeyMax(const SaveContext* saveContext, const SceneID scene) { + int8_t used = GetSceneTotalSmallKeys(saveContext, scene) - FindCurrentSmallKeys(saveContext, scene); + return GetSceneSmallKeyMax(scene) - used; +} + int8_t DungeonInfo::GetUsedSmallKeys(SaveContext* saveContext) const { return FindUsedSmallKeys(saveContext, scene, GetDoorFlags()); } diff --git a/soh/soh/Enhancements/randomizer/dungeon.h b/soh/soh/Enhancements/randomizer/dungeon.h index f956b7bfe3..4e5ca931b7 100644 --- a/soh/soh/Enhancements/randomizer/dungeon.h +++ b/soh/soh/Enhancements/randomizer/dungeon.h @@ -83,6 +83,8 @@ int8_t FindTotalSmallKeys(const SaveContext* saveContext, const SceneID scene, s uint8_t GetSceneSmallKeyMax(SceneID scene); /// Small keys held for the scene plus the ones already spent there. int8_t GetSceneTotalSmallKeys(const SaveContext* saveContext, SceneID scene); +/// The scene's small key max, minus keys already used on doors. Key rings and skeleton key set held keys to this. +int8_t GetSceneUnspentSmallKeyMax(const SaveContext* saveContext, SceneID scene); typedef enum { DEKU_TREE, diff --git a/soh/soh/Enhancements/randomizer/hint.cpp b/soh/soh/Enhancements/randomizer/hint.cpp index d5f52a4dde..45639dd476 100644 --- a/soh/soh/Enhancements/randomizer/hint.cpp +++ b/soh/soh/Enhancements/randomizer/hint.cpp @@ -650,7 +650,9 @@ CustomMessage Hint::GetWinconText() { auto ctx = Rando::Context::GetInstance(); CustomMessage winconMessage; - if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_ANYWHERE)) { + if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_DEFEAT_GANON)) { + return StaticData::hintTextTable[RHT_WINCON_DEFEAT_GANON_HINT].GetHintMessage(); + } else 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(); diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 730fe43ed3..74f7e456c4 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -11,6 +11,7 @@ #include "soh/Enhancements/randomizer/fishsanity.h" #include "soh/Enhancements/randomizer/static_data.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/SohGui/ImGuiUtils.h" #include "soh/Notification/Notification.h" #include "soh/SaveManager.h" @@ -478,6 +479,9 @@ static void PlantAllBeans() { gSaveContext.sceneFlags[patch.scene].swch |= 1 << patch.swchFlag; if (gPlayState->sceneNum == patch.scene) { Flags_SetSwitch(gPlayState, patch.swchFlag); + } else { + // Flags_SetSwitch fires this for the current scene, other scenes need it so Anchor syncs them + GameInteractor_ExecuteOnSceneFlagSet(patch.scene, FLAG_SCENE_SWITCH, patch.swchFlag); } } ObjBean* bean = (ObjBean*)Actor_Find(&gPlayState->actorCtx, ACTOR_OBJ_BEAN, ACTORCAT_BG); @@ -1018,6 +1022,24 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l case VB_CRAWL: *should = *should && Flags_GetRandomizerInf(RAND_INF_CAN_CRAWL); break; + case VB_BARINADE_PLAY_REMATCH_INTRO: + *should = + *should && (!RAND_GET_OPTION(RSK_SHUFFLE_BOSS_SOULS) || Flags_GetRandomizerInf(RAND_INF_BARINADE_SOUL)); + break; + case VB_MAGIC_METER_DEPLETE_WHILE_CONSUMING: + *should = *should && !Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MAGIC_METER); + break; + case VB_APPLY_MAGIC_METER_FILL_COLOR: { + Color_RGB8* color = va_arg(args, Color_RGB8*); + + if (Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MAGIC_METER)) { + *color = { 0, 0, 200 }; + if (CVarGetInteger(CVAR_COSMETIC("Consumable_MagicInfinite.Changed"), 0)) { + *color = CVarGetColor24(CVAR_COSMETIC("Consumable_MagicInfinite.Value"), *color); + } + } + break; + } case VB_CAN_BUY_SHOP_SHIELD_OR_TUNIC: { // Gate non-randomized shop shields/tunics behind finding a non-shop copy. if (RAND_GET_OPTION(RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL).Is(RO_GENERIC_ON)) { @@ -1868,7 +1890,7 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_GANONS_TOWER); randomizerQueuedChecks.push(RC_GANON); CheckTriggers(); - gPlayState->nextEntranceIndex = ENTR_OUTSIDE_GANONS_CASTLE_1_2; + gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_OUTSIDE_GANONS_CASTLE_1_2); gPlayState->transitionTrigger = TRANS_TRIGGER_START; gPlayState->transitionType = TRANS_TYPE_FADE_WHITE; gSaveContext.nextTransitionType = TRANS_TYPE_FADE_WHITE_SLOW; @@ -2742,9 +2764,6 @@ void RandomizerOnPlayerUpdateHandler() { // Warp to credits once item queue has drained to avoid losing queued items if (GameInteractor::State::TriforceHuntCreditsWarpActive && randomizerQueuedChecks.empty() && randomizerQueuedCheck == RC_UNKNOWN_CHECK) { - gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] = - static_cast(GAMEPLAYSTAT_TOTAL_TIME); - gSaveContext.ship.stats.gameComplete = 1; Play_PerformSave(gPlayState); Notification::Emit({ .message = "Game autosaved" }); gPlayState->nextEntranceIndex = ENTR_CHAMBER_OF_THE_SAGES_0; diff --git a/soh/soh/Enhancements/randomizer/item_list.cpp b/soh/soh/Enhancements/randomizer/item_list.cpp index 7f4e8b1d05..b54ee3d5f5 100644 --- a/soh/soh/Enhancements/randomizer/item_list.cpp +++ b/soh/soh/Enhancements/randomizer/item_list.cpp @@ -472,7 +472,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_DEKU_NUT_BAG].SetCustomDrawFunc(Randomizer_DrawNutBag); - 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_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, 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 "}); diff --git a/soh/soh/Enhancements/randomizer/location_access.cpp b/soh/soh/Enhancements/randomizer/location_access.cpp index db36f78c14..b1466d1237 100644 --- a/soh/soh/Enhancements/randomizer/location_access.cpp +++ b/soh/soh/Enhancements/randomizer/location_access.cpp @@ -42,7 +42,7 @@ bool LocationAccess::CheckConditionAtAgeTime(bool& age, bool& time) const { } bool LocationAccess::ConditionsMet(Region* parentRegion, bool calculatingAvailableChecks) const { - // WARNING enterance validation can run this after resetting the access for sphere 0 validation + // WARNING entrance validation can run this after resetting the access for sphere 0 validation // When refactoring ToD access, either fix the above or do not assume that we // have any access at all just because this is being run bool conditionsMet = false; diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp index f2c639d8f9..82ac277fe7 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp @@ -644,7 +644,7 @@ void RegionTable_Init_FireTemple() { //Only adult is tall enough to use a bow shot without climbing to UPPER //Logical ways to cross are hovers, longshot or running through the lava and then climbing back up as adult (child can't reach the ledge). //The Damage logic here is for jumping down and running across the lava to get in dins range of the south torch - //Fairies cannot be used for this as it is time sensetive, and NL is only useful with sticks as it disables other magic while in use, so it's tunic or raw damage taking ability. + //Fairies cannot be used for this as it is time sensitive, and NL is only useful with sticks as it disables other magic while in use, so it's tunic or raw damage taking ability. //testing tells me you take 3 ticks of lava damage, which is 12 internal damage or 3/4 of a heart at x1 damage multiplier, performing this run LOCATION(RC_FIRE_TEMPLE_MQ_NEAR_BOSS_CHEST, logic->CanUse(RG_DINS_FIRE) && logic->HasItem(RG_OPEN_CHEST) && (logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_LONGSHOT) || logic->CanMegajump() || diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp index ccbe983d38..4fff1fdd11 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp @@ -744,7 +744,7 @@ void RegionTable_Init_ForestTemple() { LOCATION(RC_FOREST_TEMPLE_MQ_RAISED_ISLAND_COURTYARD_LOWER_CHEST, logic->HasItem(RG_OPEN_CHEST)), }, { //Exits - //Skipping swim here is non-trival, needs a roll-jump. If a swim lock is added it's probably wise to copy deku baba events here + //Skipping swim here is non-trivial, needs a roll-jump. If a swim lock is added it's probably wise to copy deku baba events here ENTRANCE(RR_FOREST_TEMPLE_MQ_NE_COURTYARD, true), ENTRANCE(RR_FOREST_TEMPLE_MQ_NE_COURTYARD_LEDGE_ABOVE_ISLAND, logic->CanUse(RG_SONG_OF_TIME)), }); diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp index 46d2a227ce..9efe96ce0b 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp @@ -638,7 +638,7 @@ void RegionTable_Init_GanonsCastle() { LOCATION(RC_GANONS_CASTLE_MQ_FIRE_TRIAL_POT_2, logic->CanBreakPots()), }, { //Exits - ENTRANCE(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_BARRED_DOOR, true), + ENTRANCE(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_FROM_BARRED, true), }); areaTable[RR_GANONS_CASTLE_MQ_WATER_TRIAL_GEYSER_ROOM] = Region("Ganon's Castle MQ Water Trial Geyser Room", SCENE_INSIDE_GANONS_CASTLE, { @@ -939,7 +939,7 @@ void RegionTable_Init_GanonsCastle() { //Exits //I got the trick going backwards, but only while taking damage. this isn't relevant anyway though ENTRANCE(RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_BOULDER_ROOM_FRONT, logic->CanUse(RG_HOOKSHOT) || ctx->GetTrickOption(RT_GANON_MQ_LIGHT_TRIAL) || (logic->IsAdult && logic->CanGroundJump())), - ENTRANCE(RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_FINAL_ROOM, logic->SmallKeys(SCENE_INSIDE_GANONS_CASTLE, 3)), + ENTRANCE(RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_FAKE_FINAL_ROOM, logic->SmallKeys(SCENE_INSIDE_GANONS_CASTLE, 3)), }); areaTable[RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_FAKE_FINAL_ROOM] = Region("Ganon's Castle MQ Light Trial Fake Final Room", SCENE_INSIDE_GANONS_CASTLE, {}, {}, { @@ -955,10 +955,7 @@ void RegionTable_Init_GanonsCastle() { //Locations LOCATION(RC_GANONS_CASTLE_MQ_LIGHT_TRIAL_POT_1, logic->CanBreakPots()), LOCATION(RC_GANONS_CASTLE_MQ_LIGHT_TRIAL_POT_2, logic->CanBreakPots()), - }, { - //Exits - ENTRANCE(RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_BOULDER_ROOM_BACK, logic->SmallKeys(SCENE_INSIDE_GANONS_CASTLE, 3)), - }); + }, {}); #pragma endregion diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp index 328a30eaa9..0787191e59 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp @@ -328,7 +328,7 @@ void RegionTable_Init_IceCavern() { LOCATION(RC_ICE_CAVERN_MQ_MAP_RED_ICE, logic->BlueFire()), }, {}); - //RANDOTODO the default state of the toggle switchs in MQ ice are wierd, investigate for doordsanity + //RANDOTODO the default state of the toggle switches in MQ ice are weird, investigate for doorsanity areaTable[RR_ICE_CAVERN_MQ_SCARECROW_ROOM] = Region("Ice Cavern MQ Scarecrow Room", SCENE_ICE_CAVERN, {}, { //Locations //Implies being able to kill the skull if you hit the switch diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp index a4c0b41666..9feb33cb9f 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp @@ -736,7 +736,7 @@ void RegionTable_Init_ShadowTemple() { ENTRANCE(RR_SHADOW_TEMPLE_MQ_SPIKES_DOOR_PLATFORM, (((ctx->GetTrickOption(RT_LENS_SHADOW_MQ) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->CanUse(logic->IsAdult && AnyAgeTime([]{return logic->CanKillEnemy(RE_REDEAD);}) ? RG_HOOKSHOT : RG_LONGSHOT)) && //regardless, we can't assume the player does not have the silvers, so we need to cross from the glass to the platform with hovers or a jumpslash jump (as adult) (logic->CanUse(RG_HOVER_BOOTS) || (logic->IsAdult && logic->CanJumpslash()))) || - //alternativly, a midair groundjump from the invisible spikes takes adult directly there + //alternatively, a midair groundjump from the invisible spikes takes adult directly there ((ctx->GetTrickOption(RT_LENS_SHADOW_MQ) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->IsAdult && logic->CanMidairGroundJump())) }); diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/spirit_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/spirit_temple.cpp index 47de87b38b..3947f997e3 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/spirit_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/spirit_temple.cpp @@ -262,7 +262,7 @@ void RegionTable_Init_SpiritTemple() { RR_SPIRIT_TEMPLE_STATUE_ROOM_CHILD, []{return logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA, ED_LONGSHOT);})), }, { //Exits - //Adult can barely make it with just a jump, but it's a bit tight with a bad retry if it is relevent, so it would be it's own trick. + //Adult can barely make it with just a jump, but it's a bit tight with a bad retry if it is relevant, so it would be it's own trick. ENTRANCE(RR_SPIRIT_TEMPLE_STATUE_ROOM_CHILD, logic->CanUse(RG_HOVER_BOOTS) || logic->BunnyHood() || logic->CanJumpslash() || logic->CanUse(RG_HOOKSHOT)), ENTRANCE(RR_SPIRIT_TEMPLE_STATUE_ROOM, true), ENTRANCE(RR_SPIRIT_TEMPLE_GS_LEDGE, logic->IsAdult && (ctx->GetTrickOption(RT_SPIRIT_STATUE_JUMP) || logic->BunnyHood())), @@ -582,7 +582,7 @@ void RegionTable_Init_SpiritTemple() { // Exits ENTRANCE(RR_SPIRIT_TEMPLE_STATUE_ROOM, true), //Bunny hood jumps with a jumpslash can reach either hand and with good timing the platform as child. - //The latter is definitely a trick, the former may not be but I am dissalowing it because I would have to spend a week writing a new branch of spirit key logic. + //The latter is definitely a trick, the former may not be but I am disallowing it because I would have to spend a week writing a new branch of spirit key logic. //If this interaction with the hands is added, SpiritSharedLogic needs updating for it //RANDOTODO bunny hood megajumps let child into adult side too, saving for a future PR ENTRANCE(RR_SPIRIT_TEMPLE_INNER_WEST_HAND, logic->CanUse(RG_HOVER_BOOTS) || (logic->BunnyHood() && logic->CanJumpslash() && logic->IsAdult) || logic->CanMegajump()), @@ -673,7 +673,7 @@ void RegionTable_Init_SpiritTemple() { LOCATION(RC_SPIRIT_TEMPLE_MQ_GIBDO_BOULDER, logic->BlastOrSmash()), }, { //Exits - //climbing the grate through the celing allows you to go backwards here, but that's a clip not visible collision + //climbing the grate through the ceiling allows you to go backwards here, but that's a clip not visible collision ENTRANCE(RR_SPIRIT_TEMPLE_MQ_TURNTABLE, logic->Get(LOGIC_SPIRIT_MQ_GIBDOS_CLEARED)), }); @@ -1211,7 +1211,7 @@ void RegionTable_Init_SpiritTemple() { // Exits ENTRANCE(RR_SPIRIT_TEMPLE_MQ_STATUE_ROOM, true), //Bunny hood jumps with a jumpslash can reach either hand and with good timing the platform as child. - //The latter is definitely a trick, the former may not be but I am dissalowing it because I would have to spend a week writing a new branch of spirit key logic. + //The latter is definitely a trick, the former may not be but I am disallowing it because I would have to spend a week writing a new branch of spirit key logic. //If this interaction with the hands is added, SpiritSharedLogic needs updating for it ENTRANCE(RR_SPIRIT_TEMPLE_MQ_INNER_RIGHT_HAND, logic->CanUse(RG_HOVER_BOOTS) || (logic->BunnyHood() && logic->CanJumpslash() && logic->IsAdult) || logic->CanMegajump()), ENTRANCE(RR_SPIRIT_TEMPLE_MQ_INNER_LEFT_HAND, logic->CanUse(RG_HOVER_BOOTS) || (logic->BunnyHood() && logic->CanJumpslash() && logic->IsAdult)), diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp index 539f33607a..4e1b1dca61 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp @@ -258,11 +258,11 @@ void RegionTable_Init_WaterTemple() { //Implies CanAvoid(RE_STINGER) //the full logic for the puzzle, as it is cut down here for optimisation because of how the water rising starts requiring scale //EVENT_ACCESS(LOGIC_WATER_PUSHED_B1_BLOCK, logic->HasItem(RG_GORONS_BRACELET) && logic->HasExplosives() && - // (logic->CanUse(RG_HOOKSHOT) || logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_HOVER_BOOTS) || (logic->IsAdult && logiic->BunnyHood()))), + // (logic->CanUse(RG_HOOKSHOT) || logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_HOVER_BOOTS) || (logic->IsAdult && logic->BunnyHood()))), }, { //Locations //we can always get the pots by shooting them from afar and diving for the item... LOCATION(RC_WATER_TEMPLE_BASEMENT_BLOCK_PUZZLE_POT_1, (logic->CanBreakPots(ED_LONGSHOT, true, true) && logic->HasItem(RG_BRONZE_SCALE)) || - //or we can use irons and hookshot the pot, hookshoting to the target door if needed to cross the gap + //or we can use irons and hookshot the pot, hookshotting to the target door if needed to cross the gap (logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT) && logic->WaterTimer() >= 8)), LOCATION(RC_WATER_TEMPLE_BASEMENT_BLOCK_PUZZLE_POT_2, (logic->CanBreakPots(ED_LONGSHOT, true, true) && logic->HasItem(RG_BRONZE_SCALE)) || (logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT) && logic->WaterTimer() >= 8)), @@ -1122,7 +1122,7 @@ void RegionTable_Init_WaterTemple() { LOCATION(RC_WATER_TEMPLE_MQ_WONDER_HOOKSHOT_STAIRCASE_LEFT_3, logic->CanUse(RG_HOOKSHOT)), }, { //Exits - //you can also make it with hover boots and good timing, using the momentom of the slope, but it's a trick + //you can also make it with hover boots and good timing, using the momentum of the slope, but it's a trick ENTRANCE(RR_WATER_TEMPLE_MQ_WATERFALL, logic->CanUse(RG_LONGSHOT) && logic->CanHitSwitch(ED_FAR)), ENTRANCE(RR_WATER_TEMPLE_MQ_STALFOS_PIT, true), }); @@ -1142,7 +1142,7 @@ void RegionTable_Init_WaterTemple() { //Exits ENTRANCE(RR_WATER_TEMPLE_MQ_STALFOS_PIT, logic->CanUse(RG_HOOKSHOT) && (logic->IsAdult || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8))), //Child can use the second method (going via the wall target near the half raised target) with bunny hood, but while the first jump is just wall assisted, the second is inconsistent - //It is also possible for them to jump around the statue with bunny, or even climb up sometimes. This needs more investigation before catagorisation as they also seem inconsistent without a setup + //It is also possible for them to jump around the statue with bunny, or even climb up sometimes. This needs more investigation before categorisation as they also seem inconsistent without a setup ENTRANCE(RR_WATER_TEMPLE_MQ_STALFOS_PIT_POTS, (logic->IsAdult && logic->CanUse(RG_HOOKSHOT)) || (logic->CanUse(RG_HOOKSHOT) && (logic->IsAdult || logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8) && (logic->CanUse(RG_HOVER_BOOTS) || logic->Get(LOGIC_WATER_MQ_STALFOS_PIT)))), ENTRANCE(RR_WATER_TEMPLE_MQ_STALFOS_PIT_UPPER, logic->Get(LOGIC_WATER_MQ_STALFOS_PIT) && (logic->IsAdult || logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8) && logic->CanUse(RG_HOOKSHOT)), }); diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp index e308776eca..eb93451b56 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp @@ -49,7 +49,7 @@ void RegionTable_Init_GerudoFortress() { EVENT_ACCESS(LOGIC_GTG_GATE_OPEN, logic->IsAdult && logic->HasItem(RG_GERUDO_MEMBERSHIP_CARD) && logic->HasItem(RG_CHILD_WALLET) && logic->HasItem(RG_SPEAK_GERUDO)), }, { //Locations - LOCATION(RC_GF_GTG_ENTRANCE_RECTANGLE_SIGN, logic->IsAdult && logic->CanRead()), + LOCATION(RC_GF_GTG_ENTRANCE_RECTANGLE_SIGN, logic->IsAdult && logic->CanRead() && (ctx->GetTrickOption(RT_PASS_GUARDS_WITH_NOTHING) || logic->HasItem(RG_GERUDO_MEMBERSHIP_CARD))), }, { //Exits ENTRANCE(RR_GF_TO_GTG, logic->Get(LOGIC_GTG_GATE_OPEN) && (logic->IsAdult || ctx->GetOption(RSK_SHUFFLE_DUNGEON_ENTRANCES))), @@ -57,7 +57,7 @@ void RegionTable_Init_GerudoFortress() { ENTRANCE(RR_GF_JAIL_WINDOW, logic->CanUse(RG_HOOKSHOT)), ENTRANCE(RR_GF_OUTSKIRTS, true), ENTRANCE(RR_GF_NEAR_GROTTO, logic->IsChild || logic->CanPassEnemy(RE_GERUDO_GUARD)), - // RANDTODO: Add tricks for getting past the gerudo guarding the hba range + // RANDOTODO: Add tricks for getting past the gerudo guarding the hba range ENTRANCE(RR_GF_ABOVE_GTG, logic->IsChild || logic->CanPassEnemy(RE_GERUDO_GUARD)), ENTRANCE(RR_GF_TOP_OF_UPPER_VINES, logic->HasItem(RG_GERUDO_MEMBERSHIP_CARD) && logic->CanUse(RG_LONGSHOT)), ENTRANCE(RR_GF_HBA_RANGE, logic->IsChild || logic->HasItem(RG_GERUDO_MEMBERSHIP_CARD)), diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_valley.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_valley.cpp index e3e318d918..7034043102 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_valley.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_valley.cpp @@ -121,10 +121,10 @@ void RegionTable_Init_GerudoValley() { LOCATION(RC_GV_CRATE_BRIDGE_2, logic->IsChild && logic->CanBreakCrates()), LOCATION(RC_GV_CRATE_BRIDGE_3, logic->IsChild && logic->CanBreakCrates()), LOCATION(RC_GV_CRATE_BRIDGE_4, logic->IsChild && logic->CanBreakCrates()), - LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_1, logic->IsAdult), - LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_2, logic->IsAdult), - LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_3, logic->IsAdult), - LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_4, logic->IsAdult), + LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_1, logic->IsAdult && logic->CanBreakRocks()), + LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_2, logic->IsAdult && logic->CanBreakRocks()), + LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_3, logic->IsAdult && logic->CanBreakRocks()), + LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_4, logic->IsAdult && logic->CanBreakRocks()), LOCATION(RC_GV_BOULDER_ACROSS_BRIDGE, logic->IsAdult && logic->BlastOrSmash()), LOCATION(RC_GV_BRONZE_BOULDER_ACROSS_BRIDGE_1, logic->IsAdult && logic->CanUse(RG_MEGATON_HAMMER)), LOCATION(RC_GV_BRONZE_BOULDER_ACROSS_BRIDGE_2, logic->IsAdult && logic->CanUse(RG_MEGATON_HAMMER)), diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/hyrule_field.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/hyrule_field.cpp index 94facdf5cc..c2b354f13a 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/hyrule_field.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/hyrule_field.cpp @@ -14,54 +14,54 @@ void RegionTable_Init_HyruleField() { LOCATION(RC_HF_OCARINA_OF_TIME_ITEM, logic->IsChild && logic->StoneCount() == 3 && logic->HasItem(RG_BRONZE_SCALE)), LOCATION(RC_SONG_FROM_OCARINA_OF_TIME, logic->IsChild && logic->StoneCount() == 3 && logic->HasItem(RG_BRONZE_SCALE)), LOCATION(RC_HF_POND_STORMS_FAIRY, logic->CanUse(RG_SONG_OF_STORMS)), - LOCATION(RC_HF_CENTRAL_GRASS_1, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_2, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_3, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_4, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_5, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_6, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_7, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_8, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_9, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_10, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_11, logic->CanCutShrubs()), - LOCATION(RC_HF_CENTRAL_GRASS_12, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_1, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_2, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_3, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_4, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_5, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_6, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_7, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_8, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_9, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_10, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_11, logic->CanCutShrubs()), - LOCATION(RC_HF_SOUTH_GRASS_12, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_1, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_2, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_3, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_4, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_5, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_6, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_7, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_8, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_9, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_10, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_11, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_MARKET_GRASS_12, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_1, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_2, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_3, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_4, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_5, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_6, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_7, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_8, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_9, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_10, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_11, logic->CanCutShrubs()), - LOCATION(RC_HF_NEAR_KF_GRASS_12, logic->CanCutShrubs()), + LOCATION(RC_HF_CENTRAL_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_3, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_4, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_5, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_6, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_7, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_8, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_9, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_10, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_11, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_CENTRAL_GRASS_12, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_3, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_4, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_5, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_6, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_7, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_8, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_9, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_10, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_11, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_SOUTH_GRASS_12, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_3, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_4, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_5, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_6, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_7, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_8, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_9, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_10, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_11, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_MARKET_GRASS_12, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_3, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_4, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_5, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_6, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_7, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_8, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_9, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_10, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_11, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_HF_NEAR_KF_GRASS_12, logic->CanCutShrubs() || logic->SummonEpona()), LOCATION(RC_HF_SILVER_BOULDER, logic->CanUse(RG_SILVER_GAUNTLETS)), LOCATION(RC_HF_ROCK_1, logic->CanBreakRocks()), LOCATION(RC_HF_ROCK_2, logic->CanBreakRocks()), diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/lake_hylia.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/lake_hylia.cpp index df2bce01d0..91c187ae09 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/lake_hylia.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/lake_hylia.cpp @@ -39,48 +39,48 @@ void RegionTable_Init_LakeHylia() { LOCATION(RC_LH_LAB_GOSSIP_STONE, true), LOCATION(RC_LH_SOUTHEAST_GOSSIP_STONE, true), LOCATION(RC_LH_SOUTHWEST_GOSSIP_STONE, true), - LOCATION(RC_LH_GRASS_1, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_2, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_3, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_4, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_5, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_6, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_7, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_8, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_9, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_10, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_11, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_12, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_13, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_14, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_15, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_16, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_17, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_18, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_19, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_20, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_21, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_22, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_23, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_24, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_25, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_26, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_27, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_28, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_29, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_30, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_31, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_32, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_33, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_34, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_35, logic->CanCutShrubs()), - LOCATION(RC_LH_GRASS_36, logic->CanCutShrubs()), - LOCATION(RC_LH_CHILD_GRASS_1, logic->IsChild && logic->CanCutShrubs()), - LOCATION(RC_LH_CHILD_GRASS_2, logic->IsChild && logic->CanCutShrubs()), - LOCATION(RC_LH_CHILD_GRASS_3, logic->IsChild && logic->CanCutShrubs()), - LOCATION(RC_LH_CHILD_GRASS_4, logic->IsChild && logic->CanCutShrubs()), - LOCATION(RC_LH_WARP_PAD_GRASS_1, logic->CanCutShrubs()), - LOCATION(RC_LH_WARP_PAD_GRASS_2, logic->CanCutShrubs()), + LOCATION(RC_LH_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_3, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_4, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_5, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_6, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_7, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_8, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_9, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_10, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_11, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_12, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_13, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_14, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_15, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_16, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_17, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_18, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_19, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_20, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_21, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_22, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_23, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_24, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_25, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_26, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_27, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_28, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_29, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_30, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_31, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_32, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_33, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_34, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_35, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_GRASS_36, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_CHILD_GRASS_1, logic->IsChild && (logic->CanCutShrubs() || logic->SummonEpona())), + LOCATION(RC_LH_CHILD_GRASS_2, logic->IsChild && (logic->CanCutShrubs() || logic->SummonEpona())), + LOCATION(RC_LH_CHILD_GRASS_3, logic->IsChild && (logic->CanCutShrubs() || logic->SummonEpona())), + LOCATION(RC_LH_CHILD_GRASS_4, logic->IsChild && (logic->CanCutShrubs() || logic->SummonEpona())), + LOCATION(RC_LH_WARP_PAD_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()), + LOCATION(RC_LH_WARP_PAD_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()), LOCATION(RC_LH_SCARECROW_BUTTERFLY_FAIRY, logic->IsChild && logic->CanUse(RG_STICKS)), LOCATION(RC_LH_LAB_RECTANGLE_SIGN, logic->CanRead()), LOCATION(RC_LH_NORTH_EXIT_ARROW_SIGN, logic->CanRead()), diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index a6998f2a41..416cfcc5d3 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -1079,7 +1079,7 @@ bool Logic::CanKillEnemy(RandomizerEnemy enemy, EnemyDistance distance, bool wal // RANDOTODO make a function to track our ammo vs his HP when ammo capacity is taken into account in logic // all swords can at least trade blows with dark link, and even with 1 damage a slash it works out return CanUseSword() || - // Boomerang is a relaible, infinite ammo stun, so it enables any way to get enough damage with the + // Boomerang is a reliable, infinite ammo stun, so it enables any way to get enough damage with the // ammo we have Max HP dark link has 40 HP, bows and bombs do 2 so 20 ammo, stick jumpslash does 4 so // 10 sticks (CanUse(RG_BOOMERANG) && @@ -1209,7 +1209,7 @@ bool Logic::CanPassEnemy(RandomizerEnemy enemy, EnemyDistance distance, bool wal } // Can we avoid this enemy while climbing up a wall, or doing a difficult platforming challenge? -// use grounded if the challenge is such that the enemy interfears even if it cannot hit link out of the air +// use grounded if the challenge is such that the enemy interferes even if it cannot hit link out of the air bool Logic::CanAvoidEnemy(RandomizerEnemy enemy, EnemyDistance distance, bool grounded, uint8_t quantity) { // DISTANCE AND WALL ASSUMED, add more arguments later if needed if (CanKillEnemy(enemy, distance, true, quantity)) { @@ -1505,7 +1505,7 @@ bool Logic::WaterRisingTargetTo3FCentral() { * changed out of logic * * Extending from these 3, LOGIC_WATER_COULD_LOW_FROM_HIGH and LOGIC_WATER_COULD_HIGH_FROM_MID tell us if we can move - * from 1 level to the next, without us first having to confirm we can always do the preceeding level first. These allow + * from 1 level to the next, without us first having to confirm we can always do the preceding level first. These allow * us to check for conditions where we can complete a water level loop and reach any level from any level before we know * for sure we have real access. MIDDLE_EMBLEM always requires low water, so FROM_LOW is implied in * LOGIC_WATER_COULD_MIDDLE. @@ -1838,7 +1838,7 @@ uint16_t Logic::FireTimer() { } // Tunic is not required if you are using irons to do something that a simple gold scale dive could do, and you are not -// in water temple. (celing swimming and long walks through water do not count) +// in water temple. (ceiling swimming and long walks through water do not count) uint16_t Logic::WaterTimer() { if (CanUse(RG_ZORA_TUNIC)) { return UINT16_MAX; diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index dee09dd55e..c1edfd9aad 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -1172,13 +1172,13 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { // This isn't technically necessary, because keys will no longer be consumed, // but for the player's sanity we display that they _have_ keys. for (Rando::DungeonInfo* dungeon : Rando::Context::GetInstance()->GetDungeons()->GetDungeonList()) { - uint8_t keys = Rando::GetSceneSmallKeyMax(dungeon->GetScene()); - if (keys > 0) { - gSaveContext.inventory.dungeonKeys[dungeon->GetScene()] = keys; + if (Rando::GetSceneSmallKeyMax(dungeon->GetScene()) > 0) { + gSaveContext.inventory.dungeonKeys[dungeon->GetScene()] = + Rando::GetSceneUnspentSmallKeyMax(&gSaveContext, dungeon->GetScene()); } } gSaveContext.inventory.dungeonKeys[SCENE_THIEVES_HIDEOUT] = - Rando::GetSceneSmallKeyMax(SCENE_THIEVES_HIDEOUT); + Rando::GetSceneUnspentSmallKeyMax(&gSaveContext, SCENE_THIEVES_HIDEOUT); } else if (item >= RG_KEATON_MASK && item <= RG_MASK_OF_TRUTH) { if (!ChildTradeSlotOccupied()) { INV_CONTENT(ITEM_TRADE_CHILD) = (int)ITEM_MASK_KEATON + (item - RG_KEATON_MASK); @@ -1351,7 +1351,8 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { if ((item >= RG_FOREST_TEMPLE_KEY_RING) && (item <= RG_TREASURE_GAME_KEY_RING)) { u8 keysOnRing = Rando::GetSceneSmallKeyMax((SceneID)mapIndex); gSaveContext.ship.stats.dungeonKeys[mapIndex] = keysOnRing; - gSaveContext.inventory.dungeonKeys[mapIndex] = keysOnRing; + gSaveContext.inventory.dungeonKeys[mapIndex] = + Rando::GetSceneUnspentSmallKeyMax(&gSaveContext, (SceneID)mapIndex); return Return_Item_Entry(giEntry, RG_NONE); } @@ -1401,6 +1402,9 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { } break; case RG_TRIFORCE: + gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] = + static_cast(GAMEPLAYSTAT_TOTAL_TIME); + gSaveContext.ship.stats.gameComplete = 1; GameInteractor_SetTriforceHuntCreditsWarpActive(true); break; case RG_TRIFORCE_PIECE: diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerHintTextKey.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerHintTextKey.h index 4816204845..a96f5ad85e 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerHintTextKey.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerHintTextKey.h @@ -1258,6 +1258,7 @@ 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_DEFEAT_GANON_HINT) RANDO_ENUM_ITEM(RHT_WINCON_ANYWHERE_HINT) RANDO_ENUM_ITEM(RHT_WINCON_STONES_HINT) RANDO_ENUM_ITEM(RHT_WINCON_MEDALLIONS_HINT) diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerTrick.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerTrick.h index 0a91d30832..eb4d529e2c 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerTrick.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerTrick.h @@ -13,7 +13,7 @@ // Randomizer tricks and glitches (beta) based on knowledge of what is achievable in Ship of Harkinian randomizer, // excludes Item manipulation (e.g. RBA/GIM/SRM) and Wrong Warping and Ganon BK Skip -// TODO Fill and alphabetize (area based tricks must have area in name and come alphetized after general tricks) +// TODO Fill and alphabetize (area based tricks must have area in name and come alphabetized after general tricks) // TODO test commented out tricks to see if consistent with console on SoH // TODO add area specific tricks RANDO_ENUM_BEGIN(RandomizerTrick) diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index f463483ef3..5597309021 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include #include #include @@ -280,65 +282,83 @@ std::vector silverRupeeItems = { ITEM_TRACKER_RG_CUSTOM(RG_GANONS_CASTLE_MQ_SILVER_SHADOW, "ShaTri", ITEM_RUPEE_SILVER, 0, DrawItem), }; -std::vector itemTrackerDungeonsWithMapsHorizontal = { - { SCENE_DEKU_TREE, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_DODONGOS_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_JABU_JABU, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_ICE_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } }, -}; +// Dungeon items stacked top to bottom in one grid column, scene in data. Unused slots have no drawFunc +using ItemTrackerDungeonColumn = std::array; -std::vector itemTrackerDungeonsHorizontal = { - { SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL } }, - { SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } }, -}; +static ItemTrackerItem Key(SceneID scene) { + return ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, "", static_cast(scene), DrawDungeonItem); +} +static ItemTrackerItem Boss(SceneID scene) { + return ITEM_TRACKER_ITEM(ITEM_KEY_BOSS, "", static_cast(scene), DrawDungeonItem); +} +static ItemTrackerItem Map(SceneID scene) { + return ITEM_TRACKER_ITEM(ITEM_DUNGEON_MAP, "", static_cast(scene), DrawDungeonItem); +} +static ItemTrackerItem Compass(SceneID scene) { + return ITEM_TRACKER_ITEM(ITEM_COMPASS, "", static_cast(scene), DrawDungeonItem); +} -std::vector itemTrackerDungeonsWithMapsCompact = { - { SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_DEKU_TREE, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_DODONGOS_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_JABU_JABU, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_ICE_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, - { SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } }, -}; +static const auto itemTrackerDungeonsWithMapsHorizontal = std::to_array({ + { Map(SCENE_DEKU_TREE), Compass(SCENE_DEKU_TREE) }, + { Map(SCENE_DODONGOS_CAVERN), Compass(SCENE_DODONGOS_CAVERN) }, + { Map(SCENE_JABU_JABU), Compass(SCENE_JABU_JABU) }, + { Key(SCENE_FOREST_TEMPLE), Boss(SCENE_FOREST_TEMPLE), Map(SCENE_FOREST_TEMPLE), Compass(SCENE_FOREST_TEMPLE) }, + { Key(SCENE_FIRE_TEMPLE), Boss(SCENE_FIRE_TEMPLE), Map(SCENE_FIRE_TEMPLE), Compass(SCENE_FIRE_TEMPLE) }, + { Key(SCENE_WATER_TEMPLE), Boss(SCENE_WATER_TEMPLE), Map(SCENE_WATER_TEMPLE), Compass(SCENE_WATER_TEMPLE) }, + { Key(SCENE_SPIRIT_TEMPLE), Boss(SCENE_SPIRIT_TEMPLE), Map(SCENE_SPIRIT_TEMPLE), Compass(SCENE_SPIRIT_TEMPLE) }, + { Key(SCENE_SHADOW_TEMPLE), Boss(SCENE_SHADOW_TEMPLE), Map(SCENE_SHADOW_TEMPLE), Compass(SCENE_SHADOW_TEMPLE) }, + { Key(SCENE_INSIDE_GANONS_CASTLE), Boss(SCENE_GANONS_TOWER) }, + { Key(SCENE_BOTTOM_OF_THE_WELL), Map(SCENE_BOTTOM_OF_THE_WELL), Compass(SCENE_BOTTOM_OF_THE_WELL) }, + { Map(SCENE_ICE_CAVERN), Compass(SCENE_ICE_CAVERN) }, + { Key(SCENE_GERUDO_TRAINING_GROUND), Key(SCENE_THIEVES_HIDEOUT), Key(SCENE_TREASURE_BOX_SHOP) }, +}); -std::vector itemTrackerDungeonsCompact = { - { SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, - { SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL } }, - { SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } }, - { SCENE_THIEVES_HIDEOUT, { ITEM_KEY_SMALL } }, -}; +static const auto itemTrackerDungeonsHorizontal = std::to_array({ + { Key(SCENE_FOREST_TEMPLE), Boss(SCENE_FOREST_TEMPLE) }, + { Key(SCENE_FIRE_TEMPLE), Boss(SCENE_FIRE_TEMPLE) }, + { Key(SCENE_WATER_TEMPLE), Boss(SCENE_WATER_TEMPLE) }, + { Key(SCENE_SPIRIT_TEMPLE), Boss(SCENE_SPIRIT_TEMPLE) }, + { Key(SCENE_SHADOW_TEMPLE), Boss(SCENE_SHADOW_TEMPLE) }, + { Key(SCENE_INSIDE_GANONS_CASTLE), Boss(SCENE_GANONS_TOWER) }, + { Key(SCENE_BOTTOM_OF_THE_WELL), Key(SCENE_TREASURE_BOX_SHOP) }, + { Key(SCENE_GERUDO_TRAINING_GROUND), Key(SCENE_THIEVES_HIDEOUT) }, +}); + +static const auto itemTrackerDungeonsWithMapsCompact = std::to_array({ + { Key(SCENE_FOREST_TEMPLE), Boss(SCENE_FOREST_TEMPLE), Map(SCENE_FOREST_TEMPLE), Compass(SCENE_FOREST_TEMPLE) }, + { Key(SCENE_FIRE_TEMPLE), Boss(SCENE_FIRE_TEMPLE), Map(SCENE_FIRE_TEMPLE), Compass(SCENE_FIRE_TEMPLE) }, + { Key(SCENE_WATER_TEMPLE), Boss(SCENE_WATER_TEMPLE), Map(SCENE_WATER_TEMPLE), Compass(SCENE_WATER_TEMPLE) }, + { Key(SCENE_SPIRIT_TEMPLE), Boss(SCENE_SPIRIT_TEMPLE), Map(SCENE_SPIRIT_TEMPLE), Compass(SCENE_SPIRIT_TEMPLE) }, + { Key(SCENE_SHADOW_TEMPLE), Boss(SCENE_SHADOW_TEMPLE), Map(SCENE_SHADOW_TEMPLE), Compass(SCENE_SHADOW_TEMPLE) }, + { Key(SCENE_BOTTOM_OF_THE_WELL), Map(SCENE_BOTTOM_OF_THE_WELL), Compass(SCENE_BOTTOM_OF_THE_WELL), + Key(SCENE_TREASURE_BOX_SHOP) }, + { Map(SCENE_DEKU_TREE), Compass(SCENE_DEKU_TREE) }, + { Map(SCENE_DODONGOS_CAVERN), Compass(SCENE_DODONGOS_CAVERN) }, + { Map(SCENE_JABU_JABU), Compass(SCENE_JABU_JABU) }, + { Map(SCENE_ICE_CAVERN), Compass(SCENE_ICE_CAVERN) }, + { Key(SCENE_INSIDE_GANONS_CASTLE), Boss(SCENE_GANONS_TOWER) }, + { Key(SCENE_GERUDO_TRAINING_GROUND), Key(SCENE_THIEVES_HIDEOUT) }, +}); + +static const auto itemTrackerDungeonsCompact = std::to_array({ + { Key(SCENE_FOREST_TEMPLE), Boss(SCENE_FOREST_TEMPLE) }, + { Key(SCENE_FIRE_TEMPLE), Boss(SCENE_FIRE_TEMPLE) }, + { Key(SCENE_WATER_TEMPLE), Boss(SCENE_WATER_TEMPLE) }, + { Key(SCENE_SPIRIT_TEMPLE), Boss(SCENE_SPIRIT_TEMPLE) }, + { Key(SCENE_SHADOW_TEMPLE), Boss(SCENE_SHADOW_TEMPLE) }, + { Key(SCENE_INSIDE_GANONS_CASTLE), Boss(SCENE_GANONS_TOWER) }, + { Key(SCENE_BOTTOM_OF_THE_WELL) }, + { Key(SCENE_GERUDO_TRAINING_GROUND) }, + { Key(SCENE_THIEVES_HIDEOUT) }, + { Key(SCENE_TREASURE_BOX_SHOP) }, +}); std::map itemTrackerDungeonShortNames = { { SCENE_FOREST_TEMPLE, "FRST" }, { SCENE_FIRE_TEMPLE, "FIRE" }, { SCENE_WATER_TEMPLE, "WATR" }, { SCENE_SPIRIT_TEMPLE, "SPRT" }, { SCENE_SHADOW_TEMPLE, "SHDW" }, { SCENE_BOTTOM_OF_THE_WELL, "BOTW" }, { SCENE_DEKU_TREE, "DEKU" }, { SCENE_DODONGOS_CAVERN, "DCVN" }, { SCENE_JABU_JABU, "JABU" }, { SCENE_ICE_CAVERN, "ICE" }, { SCENE_INSIDE_GANONS_CASTLE, "GANON" }, { SCENE_GERUDO_TRAINING_GROUND, "GTG" }, - { SCENE_THIEVES_HIDEOUT, "HIDE" }, + { SCENE_THIEVES_HIDEOUT, "HIDE" }, { SCENE_TREASURE_BOX_SHOP, "TCG" }, }; std::vector dungeonItems = {}; @@ -1439,55 +1459,56 @@ void DrawItemsInACircle(std::vector items) { } } +static bool ShowDungeonItem(const ItemTrackerItem& item) { + if (item.data == SCENE_TREASURE_BOX_SHOP) { + return IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_CHEST_MINIGAME); + } + if (item.data == SCENE_THIEVES_HIDEOUT) { + return !IS_RANDO || RAND_GET_OPTION(RSK_GERUDO_FORTRESS).IsNot(RO_GF_CARPENTERS_FREE); + } + return true; +} + /** * GetDungeonItemsVector - * Loops over dungeons and creates vectors of items in the correct order - * to then call DrawItemsInRows + * Lays out columns in blocks of `columns` wide, each block as tall as its tallest column, + * and flattens them row by row for DrawItemsInRows */ -std::vector GetDungeonItemsVector(std::vector dungeons, size_t columns = 6) { - std::vector dungeonItems = {}; - - size_t rowCount = 0; - for (size_t i = 0; i < dungeons.size(); i++) { - if (dungeons[i].items.size() > rowCount) - rowCount = static_cast(dungeons[i].items.size()); - } - - for (size_t i = 0; i < rowCount; i++) { - for (size_t j = 0; j < std::min(dungeons.size(), columns); j++) { - if (dungeons[j].items.size() > i) { - switch (dungeons[j].items[i]) { - case ITEM_KEY_SMALL: - dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, "", dungeons[j].id, DrawDungeonItem)); - break; - case ITEM_KEY_BOSS: - // Swap Ganon's Castle boss key to the right scene ID manually - if (dungeons[j].id == SCENE_INSIDE_GANONS_CASTLE) { - dungeonItems.push_back( - ITEM_TRACKER_ITEM(ITEM_KEY_BOSS, "", SCENE_GANONS_TOWER, DrawDungeonItem)); - } else { - dungeonItems.push_back( - ITEM_TRACKER_ITEM(ITEM_KEY_BOSS, "", dungeons[j].id, DrawDungeonItem)); - } - break; - case ITEM_DUNGEON_MAP: - dungeonItems.push_back( - ITEM_TRACKER_ITEM(ITEM_DUNGEON_MAP, "", dungeons[j].id, DrawDungeonItem)); - break; - case ITEM_COMPASS: - dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_COMPASS, "", dungeons[j].id, DrawDungeonItem)); - break; - } - } else { - dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, "", 0, DrawItem)); +std::vector GetDungeonItemsVector(std::span layout, + size_t columns = 6) { + struct ShownColumn { + ItemTrackerDungeonColumn items; + size_t size = 0; + }; + std::vector shown; + for (const auto& column : layout) { + ShownColumn kept; + for (const auto& item : column) { + if (item.drawFunc != nullptr && ShowDungeonItem(item)) { + kept.items[kept.size++] = item; } } + if (kept.size > 0) { + shown.push_back(kept); + } } - if (dungeons.size() > columns) { - std::vector nextDungeonItems = - GetDungeonItemsVector(std::vector(dungeons.begin() + columns, dungeons.end()), columns); - dungeonItems.insert(dungeonItems.end(), nextDungeonItems.begin(), nextDungeonItems.end()); + std::vector dungeonItems = {}; + for (size_t start = 0; start < shown.size(); start += columns) { + size_t end = std::min(start + columns, shown.size()); + size_t rowCount = 0; + for (size_t j = start; j < end; j++) { + rowCount = std::max(rowCount, shown[j].size); + } + for (size_t i = 0; i < rowCount; i++) { + for (size_t j = start; j < end; j++) { + if (i < shown[j].size) { + dungeonItems.push_back(shown[j].items[i]); + } else { + dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, "", 0, DrawItem)); + } + } + } } return dungeonItems; @@ -1563,18 +1584,12 @@ void UpdateVectors() { SECTION_DISPLAY_SEPARATE) { if (CVarGetInteger(CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), 1)) { dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsWithMapsHorizontal, 12); - // Manually adding Thieves Hideout to an open spot so we don't get an additional row for one item - dungeonItems[23] = ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, "", SCENE_THIEVES_HIDEOUT, DrawDungeonItem); } else { - // Manually adding Thieves Hideout to an open spot so we don't get an additional row for one item dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsHorizontal, 8); - dungeonItems[15] = ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, "", SCENE_THIEVES_HIDEOUT, DrawDungeonItem); } } else { if (CVarGetInteger(CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), 1)) { dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsWithMapsCompact); - // Manually adding Thieves Hideout to an open spot so we don't get an additional row for one item - dungeonItems[35] = ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, "", SCENE_THIEVES_HIDEOUT, DrawDungeonItem); } else { dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsCompact); } diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.h index 5e9b136bfb..c1d2146599 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.h +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.h @@ -72,11 +72,6 @@ static std::array itemTrackerWindowIDs = { "Item Tracker", "Total Checks" }; void ItemTracker_LoadFromPreset(const nlohmann::json& trackerInfo); -typedef struct ItemTrackerDungeon { - uint32_t id; - std::vector items; -} ItemTrackerDungeon; - class ItemTrackerSettingsWindow final : public Ship::GuiWindow { public: using GuiWindow::GuiWindow; diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 97aa82a507..0af91ef6bc 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -447,7 +447,7 @@ void Settings::CreateOptions() { OPT_U8(RSK_MQ_DUNGEON_RANDOM, {"None", "Set Number", "Random", "Selection Only"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("MQDungeons"), WIDGET_CVAR_COMBOBOX, RO_MQ_DUNGEONS_NONE, false, nullptr, IMFLAG_NONE); OPT_CALLBACK(RSK_MQ_DUNGEON_RANDOM, { switch (CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeons"), RO_MQ_DUNGEONS_NONE)) { - // If No MQ Dungeons, add a separator after the combobx and hide + // If No MQ Dungeons, add a separator after the combobox and hide // the count slider and the toggle for individual dungeon selections. case RO_MQ_DUNGEONS_NONE: mOptions[RSK_MQ_DUNGEON_COUNT].Hide(); @@ -1099,7 +1099,7 @@ void Settings::CreateOptions() { mOptions[RSK_LOACH_HINT].Enable(); } else { mOptions[RSK_LOACH_HINT].Disable( - "Loach hint is only avaliable with \"Fishsanity\" set to \"Shuffle only Hyrule Loach\"\nas that's the only " + "Loach hint is only available with \"Fishsanity\" set to \"Shuffle only Hyrule Loach\"\nas that's the only " "setting where you present the loach to the fishing pond owner."); } }); @@ -2771,7 +2771,7 @@ void Context::FinalizeSettings(const std::set& excludedLocation mqSet += 1; break; case RO_MQ_SET_RANDOM: - // 50% per dungeon, rolled separatly so people can either have a linear distribtuion + // 50% per dungeon, rolled separately so people can either have a linear distribution // or a bell curve for the number of MQ dungeons per seed. if (Random(0, 2)) { dungeon->SetMQ(); @@ -2815,7 +2815,7 @@ void Context::FinalizeSettings(const std::set& excludedLocation } // otherwise, every dungeon is possible } else { - // if count is MAX_MQ_DUNGEON_COUNT, we know everything is MQ, so can skip some setps and not set Known + // if count is MAX_MQ_DUNGEON_COUNT, we know everything is MQ, so can skip some steps and not set Known if (mOptions[RSK_MQ_DUNGEON_RANDOM].Is(RO_MQ_DUNGEONS_SET_NUMBER) && mqCount == MAX_MQ_DUNGEON_COUNT) { randMQOption.resize(MAX_MQ_DUNGEON_COUNT); for (int i = 0; i < MAX_MQ_DUNGEON_COUNT; i++) { diff --git a/soh/soh/Extractor/Extract.cpp b/soh/soh/Extractor/Extract.cpp index 89da6e1b78..de16a265aa 100644 --- a/soh/soh/Extractor/Extract.cpp +++ b/soh/soh/Extractor/Extract.cpp @@ -318,6 +318,18 @@ bool Extractor::GetRomPathFromBox() { } mCurrentRomPath = nameBuffer; #else + if (!pfd::settings::available()) { + std::string msg = + "No file picker was found on your system. Ship of Harkinian needs one of these to select a ROM:\n\n" + " - GNOME, Xfce, Cinnamon and other GTK desktops: install \"zenity\"\n" + " - KDE Plasma: install \"kdialog\"\n\n" + "On other distributions or window managers, search your package manager.\n\n" + "Alternatively, place your ROM in:\n" + + mSearchPath + "\nand restart."; + ShowErrorBox("File Picker Unavailable", msg.c_str()); + return false; + } + auto selection = pfd::open_file("Select a file", mSearchPath, { "N64 Roms", "*.z64 *.n64 *.v64" }).result(); if (selection.empty()) { @@ -354,7 +366,7 @@ size_t Extractor::GetCurRomSize() const { bool Extractor::ValidateAndFixRom() { // The MQ debug rom sometimes has the header patched to look like a US rom. - // Check the crc as if it were changed back, but leave mRomData as the original dump for torch. + // Check the crc as if it were changed back. CallTorch changes it back for real. const uint8_t region = mRomData[0x3E]; if (GetRomVerCrc() == OOT_PAL_GC_MQ_DBG) { mRomData[0x3E] = 'P'; @@ -654,6 +666,13 @@ bool Extractor::CallTorch(std::string installPath, std::string exportdir, std::a *totalExtract = SohTorch::CountAssetFiles(srcDir + "/" + GetTorchVersionDir()); *extractCount = 0; + // MQ debug dumps only hold 54MB of data, the rest is dump-specific padding, and some have + // the region byte changed. Trim and restore it so every dump matches the one config.yml hash. + if (GetRomVerCrc() == OOT_PAL_GC_MQ_DBG && mRomData.size() == MB64) { + mRomData.resize(MB54); + mRomData[0x3E] = 'P'; + } + // config.yml decides whether this is oot.o2r or oot-mq.o2r. std::string archiveName = SohTorch::Extract(std::move(mRomData), srcDir, tempdir, portVersion, extractCount); bool success = !archiveName.empty(); diff --git a/soh/soh/Network/Anchor/DummyPlayer.cpp b/soh/soh/Network/Anchor/DummyPlayer.cpp index fae13d5849..71943745c4 100644 --- a/soh/soh/Network/Anchor/DummyPlayer.cpp +++ b/soh/soh/Network/Anchor/DummyPlayer.cpp @@ -254,4 +254,10 @@ void DummyPlayer_Destroy(Actor* actor, PlayState* play) { // asserts. Set the id back to ACTOR_PLAYER so that `numLoaded` will be decremented // correctly. actor->id = ACTOR_PLAYER; + + // Drop client's pointer so packet handlers don't use after free + uint32_t clientId = Anchor::Instance->GetDummyPlayerClientId(actor); + if (Anchor::Instance->clients.contains(clientId) && Anchor::Instance->clients[clientId].player == (Player*)actor) { + Anchor::Instance->clients[clientId].player = nullptr; + } } diff --git a/soh/soh/Network/Anchor/JsonConversions.hpp b/soh/soh/Network/Anchor/JsonConversions.hpp index 83c2f77304..bb8e9df9d5 100644 --- a/soh/soh/Network/Anchor/JsonConversions.hpp +++ b/soh/soh/Network/Anchor/JsonConversions.hpp @@ -104,18 +104,50 @@ inline void from_json(const json& j, SohStats& sohStats) { j.at("fileCreatedAt").get_to(sohStats.fileCreatedAt); } +#define SILVER_RUPEE_FIELDS(X) \ + X(silverShadowBlades) \ + X(silverShadowPit) \ + X(silverShadowSpikes) \ + X(silverSpiritChild) \ + X(silverSpiritSun) \ + X(silverSpiritBoulders) \ + X(silverBotw) \ + X(silverIceCavernBlades) \ + X(silverIceCavernBlock) \ + X(silverGtgSlope) \ + X(silverGtgLava) \ + X(silverGtgWater) \ + X(silverGanonLight) \ + X(silverGanonForest) \ + X(silverGanonFire) \ + X(silverGanonSpirit) \ + X(silverMqDodongosCavern) \ + X(silverMqShadowInvisibleBlades) \ + X(silverMqSpiritLobby) \ + X(silverMqSpiritBigWall) \ + X(silverMqGanonWater) \ + X(silverMqGanonShadow) + inline void to_json(json& j, const ShipRandomizerSaveContextData& shipRandomizerSaveContextData) { j = json{ { "triforcePiecesCollected", shipRandomizerSaveContextData.triforcePiecesCollected }, { "bombchuUpgradeLevel", shipRandomizerSaveContextData.bombchuUpgradeLevel }, }; +#define SILVER_TO_JSON(field) j[#field] = shipRandomizerSaveContextData.field; + SILVER_RUPEE_FIELDS(SILVER_TO_JSON) +#undef SILVER_TO_JSON } inline void from_json(const json& j, ShipRandomizerSaveContextData& shipRandomizerSaveContextData) { j.at("triforcePiecesCollected").get_to(shipRandomizerSaveContextData.triforcePiecesCollected); j.at("bombchuUpgradeLevel").get_to(shipRandomizerSaveContextData.bombchuUpgradeLevel); +#define SILVER_FROM_JSON(field) shipRandomizerSaveContextData.field = j.value(#field, (s8)0); + SILVER_RUPEE_FIELDS(SILVER_FROM_JSON) +#undef SILVER_FROM_JSON } +#undef SILVER_RUPEE_FIELDS + inline void to_json(json& j, const ShipQuestSpecificSaveContextData& shipQuestSpecificSaveContextData) { j = json{ { "randomizer", shipQuestSpecificSaveContextData.randomizer }, diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index d336c3c5bf..79a804b994 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -430,7 +430,12 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) { Extractor extract; PromptSteps promptStep = PS_FILE_CHECK; bool generatedIsMQ = false; + // Whether generatedIsMQ is set, i.e. the first prompted rom extracted fine + bool generatedFirst = false; + bool lastExtractFailed = false; std::atomic extractCount = 0, totalExtract = 0; + // Set by the extraction thread, shown as a popup by the main thread once the task finishes + std::atomic extractFailed = false; std::string installPath = std::filesystem::absolute(Ship::Context::GetAppBundlePath()).string(); std::string dataPath = std::filesystem::absolute(Ship::Context::GetAppDirectoryPath(appShortName)).string(); @@ -613,15 +618,17 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) { std::string msg = "Archive for current ROM, " + archive + ", already exists.\nExtract again?"; SohGui::RegisterPopup("Confirm Re-extract", msg.c_str(), "Yes", "No", [&]() { extractionTask = threadPool->submit_task([&]() -> void { - extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName), - &extractCount, &totalExtract); + extractFailed = + !extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName), + &extractCount, &totalExtract); extractCount = totalExtract = 0; }); }); } else { extractionTask = threadPool->submit_task([&]() -> void { - extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName), - &extractCount, &totalExtract); + extractFailed = + !extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName), + &extractCount, &totalExtract); extractCount = totalExtract = 0; }); } @@ -677,9 +684,13 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) { continue; } extractionTask = threadPool->submit_task([&]() -> void { - extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName), - &extractCount, &totalExtract); - generatedIsMQ = extract.IsMasterQuest(); + extractFailed = + !extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName), + &extractCount, &totalExtract); + if (!extractFailed) { + generatedIsMQ = extract.IsMasterQuest(); + generatedFirst = true; + } promptStep = PS_SECOND; extractCount = 0; totalExtract = 0; @@ -687,17 +698,28 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) { continue; } case PS_SECOND: { + // Keep asking until the user says no, so a failed or cancelled rom can be retried SohGui::RegisterPopup( - "Extraction Complete", "ROM Extracted. Extract another?", "Yes", "No", + lastExtractFailed ? "Extract Another" : "Extraction Complete", + lastExtractFailed ? "Try extracting another ROM?" : "ROM Extracted. Extract another?", + "Yes", "No", [&]() { - if (!extract.ManuallySearchForRomMatchingType(generatedIsMQ ? RomSearchMode::Vanilla - : RomSearchMode::MQ)) { - extractStep = ES_VERIFY; - } else { + RomSearchMode mode = RomSearchMode::Both; + if (generatedFirst) { + mode = generatedIsMQ ? RomSearchMode::Vanilla : RomSearchMode::MQ; + } + if (extract.ManuallySearchForRomMatchingType(mode)) { extractionTask = threadPool->submit_task([&]() -> void { - extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName), - &extractCount, &totalExtract); - extractStep = ES_VERIFY; + extractFailed = !extract.CallTorch( + installPath, Ship::Context::GetAppDirectoryPath(appShortName), + &extractCount, &totalExtract); + if (!extractFailed && generatedFirst) { + extractStep = ES_VERIFY; + } else if (!extractFailed) { + // A retry of the first rom worked, so still offer the other type + generatedIsMQ = extract.IsMasterQuest(); + generatedFirst = true; + } extractCount = 0; totalExtract = 0; }); @@ -754,6 +776,12 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) { } catch (const std::exception& e) { SohGui::RegisterPopup("Extraction Crashed", e.what(), "Close", "", []() { exit(1); }); } + lastExtractFailed = extractFailed; + if (extractFailed) { + SohGui::RegisterPopup("Extraction Failed", + "The ROM could not be extracted. Check the log for details.", "OK", ""); + extractFailed = false; + } extractionTask.reset(); } else { if (!ImGui::IsPopupOpen("ROM Extraction")) { diff --git a/soh/src/code/audio_seqplayer.c b/soh/src/code/audio_seqplayer.c index 92efecd02e..b3bfdc5715 100644 --- a/soh/src/code/audio_seqplayer.c +++ b/soh/src/code/audio_seqplayer.c @@ -1186,12 +1186,12 @@ void AudioSeq_SequenceChannelProcessScript(SequenceChannel* channel) { u16 seqId = AudioEditor_GetReplacementSeq(seqPlayer->seqId); SequenceData sDat = ResourceMgr_LoadSeqByName(sequenceMap[seqId]); - // The game apparantely would sometimes do negative array lookups, the result of which would + // The game apparently would sometimes do negative array lookups, the result of which would // get rejected by AudioHeap_SearchCaches, never changing the actual fontid. - if (result > sDat.numFonts) + if (result >= sDat.numFonts) break; - fontId = sDat.fonts[(sDat.numFonts - result - 1)]; + fontId = sDat.fonts[sDat.numFonts - result - 1]; } if (AudioHeap_SearchCaches(FONT_TABLE, CACHE_EITHER, fontId)) { diff --git a/soh/src/code/main.c b/soh/src/code/main.c index 98c632665b..4e68d5da15 100644 --- a/soh/src/code/main.c +++ b/soh/src/code/main.c @@ -54,6 +54,8 @@ int SDL_main(int argc, char* argv[]) { #endif // Allow non-ascii characters for Windows setlocale(LC_ALL, ".UTF8"); + // Keep '.' as the decimal point, user locale may use ',' + setlocale(LC_NUMERIC, "C"); #else //_WIN32 int main(int argc, char* argv[]) { diff --git a/soh/src/code/z_draw.c b/soh/src/code/z_draw.c index 4706aaf7a7..06a7190788 100644 --- a/soh/src/code/z_draw.c +++ b/soh/src/code/z_draw.c @@ -114,6 +114,7 @@ void GetItem_DrawJewelGoron(PlayState* play, s16 drawId); void GetItem_DrawJewelZora(PlayState* play, s16 drawId); void GetItem_DrawGenericMusicNote(PlayState* play, s16 drawId); void GetItem_DrawTriforcePiece(PlayState* play, s16 drawId); +void GetItem_DrawTriforce(PlayState* play, s16 drawId); void GetItem_DrawFishingPole(PlayState* play, s16 drawId); typedef struct { @@ -384,14 +385,15 @@ DrawItemTableEntry sDrawItemTable[] = { { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Generic - { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Zelda's Lullaby - { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Epona's song - { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Saria's song - { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Sun's song - { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Song of time - { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Song of storms - { GetItem_DrawTriforcePiece, { gTriforcePiece0DL } }, // Triforce Piece - { GetItem_DrawFishingPole, { gGiFishingPoleDL } }, // Fishing Pole + { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Zelda's Lullaby + { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Epona's song + { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Saria's song + { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Sun's song + { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Song of time + { GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Song of storms + { GetItem_DrawTriforcePiece, { gTriforcePiece0DL } }, // Triforce Piece + { GetItem_DrawTriforce, { gTriforcePieceCompletedDL } }, // Triforce + { GetItem_DrawFishingPole, { gGiFishingPoleDL } }, // Fishing Pole }; /** @@ -1027,6 +1029,24 @@ void GetItem_DrawTriforcePiece(PlayState* play, s16 drawId) { CLOSE_DISPS(play->state.gfxCtx); } +void GetItem_DrawTriforce(PlayState* play, s16 drawId) { + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + + Matrix_Scale(0.035f, 0.035f, 0.035f, MTXMODE_APPLY); + + uint8_t index = gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected % 3; + Gfx* triforceDL = (Gfx*)gTriforcePieceCompletedDL; + + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), + G_MTX_MODELVIEW | G_MTX_LOAD); + + gSPDisplayList(POLY_OPA_DISP++, triforceDL); + + CLOSE_DISPS(play->state.gfxCtx); +} + void GetItem_DrawFishingPole(PlayState* play, s16 drawId) { Vec3f pos; OPEN_DISPS(play->state.gfxCtx); diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index d630dbebdd..88e857da54 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -3263,7 +3263,7 @@ void Interface_UpdateMagicBar(PlayState* play) { break; case MAGIC_STATE_CONSUME: - if (!Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MAGIC_METER)) { + if (GameInteractor_Should(VB_MAGIC_METER_DEPLETE_WHILE_CONSUMING, true)) { gSaveContext.magic -= 2; if (gSaveContext.magic <= 0) { gSaveContext.magic = 0; @@ -3454,7 +3454,6 @@ void Interface_DrawMagicBar(PlayState* play) { Color_RGB8 magicbar_yellow = { 250, 250, 0 }; // Magic bar being used Color_RGB8 magicbar_green = { R_MAGIC_FILL_COLOR(0), R_MAGIC_FILL_COLOR(1), R_MAGIC_FILL_COLOR(2) }; // Magic bar fill - Color_RGB8 magicbar_blue = { 0, 0, 200 }; // Infinite magic bar if (CVarGetInteger(CVAR_COSMETIC("Consumable.MagicActive.Changed"), 0)) { magicbar_yellow = CVarGetColor24(CVAR_COSMETIC("Consumable.MagicActive.Value"), magicbar_yellow); @@ -3462,9 +3461,7 @@ void Interface_DrawMagicBar(PlayState* play) { if (CVarGetInteger(CVAR_COSMETIC("Consumable.Magic.Changed"), 0)) { magicbar_green = CVarGetColor24(CVAR_COSMETIC("Consumable.Magic.Value"), magicbar_green); } - if (CVarGetInteger("gCosmetics.Consumable_MagicInfinite.Changed", 0)) { - magicbar_blue = CVarGetColor24("gCosmetics.Consumable_MagicInfinite.Value", magicbar_blue); - } + GameInteractor_Should(VB_APPLY_MAGIC_METER_FILL_COLOR, true, &magicbar_green); OPEN_DISPS(play->state.gfxCtx); @@ -3591,30 +3588,16 @@ void Interface_DrawMagicBar(PlayState* play) { // Fill the rest of the bar with the normal magic color gDPPipeSync(OVERLAY_DISP++); - if (Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MAGIC_METER)) { - // Blue magic - gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_blue.r, magicbar_blue.g, magicbar_blue.b, - interfaceCtx->magicAlpha); - } else { - // Green magic (default) - gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_green.r, magicbar_green.g, magicbar_green.b, - interfaceCtx->magicAlpha); - } + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_green.r, magicbar_green.g, magicbar_green.b, + interfaceCtx->magicAlpha); gSPWideTextureRectangle(OVERLAY_DISP++, rMagicFillX << 2, (magicBarY + 3) << 2, (rMagicFillX + gSaveContext.magicTarget) << 2, (magicBarY + 10) << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); } else { // Fill the whole bar with the normal magic color - if (Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MAGIC_METER)) { - // Blue magic - gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_blue.r, magicbar_blue.g, magicbar_blue.b, - interfaceCtx->magicAlpha); - } else { - // Green magic (default) - gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_green.r, magicbar_green.g, magicbar_green.b, - interfaceCtx->magicAlpha); - } + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_green.r, magicbar_green.g, magicbar_green.b, + interfaceCtx->magicAlpha); gDPLoadMultiBlock_4b(OVERLAY_DISP++, gMagicMeterFillTex, 0, G_TX_RENDERTILE, G_IM_FMT_I, 16, 16, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, diff --git a/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c b/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c index 6311e0bdbf..58bd1b0164 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c @@ -918,7 +918,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) { this->csTimer = 0; this->csCamFov = 60.0f; BossGanon_SetIntroCsCamera(this, 12); - if (GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this)) { + if (GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this, 0x70CB)) { Message_StartTextbox(play, 0x70CB, NULL); } } @@ -942,7 +942,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) { this->csState = 19; this->csTimer = 0; - if (GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this)) { + if (GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this, 0x70CC)) { Message_StartTextbox(play, 0x70CC, NULL); } Animation_MorphToPlayOnce(&this->skelAnime, &gGanondorfRaiseHandStartAnim, -5.0f); @@ -985,7 +985,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) { } if ((this->csTimer > 80) && (Message_GetState(&play->msgCtx) == TEXT_STATE_NONE)) { - if (!GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this)) { + if (!GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this, 0)) { this->timers[2] = 30; this->csCamAt.x = this->unk_1FC.x - 10.0f; this->csCamAt.y = this->unk_1FC.y + 30.0f; diff --git a/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c b/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c index dc434fbd9e..73337ce6bb 100644 --- a/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c +++ b/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c @@ -672,43 +672,39 @@ void BossVa_Init(Actor* thisx, PlayState* play2) { } else { this->actor.colChkInfo.damageTable = sDamageTable; sPhase2Timer = 0xFFFF; - if (Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE)) { - if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_SOULS) && - !Flags_GetRandomizerInf(RAND_INF_BARINADE_SOUL)) { - sCsState = BOSSVA_BATTLE; - } else { - sCsState = INTRO_CALL_BARI; - sDoorState = 100; - Player_SetCsActionWithHaltedActors(play, &this->actor, 1); - play->envCtx.screenFillColor[0] = 0xDC; - play->envCtx.screenFillColor[1] = 0xDC; - play->envCtx.screenFillColor[2] = 0xBE; - play->envCtx.screenFillColor[3] = 0xD2; - func_80064520(play, &play->csCtx); - sSubCamId = Play_CreateSubCamera(play); - Play_ChangeCameraStatus(play, CAM_ID_MAIN, CAM_STAT_WAIT); - Play_ChangeCameraStatus(play, sSubCamId, CAM_STAT_ACTIVE); - sSubCamEyeNext.x = sSubCamEye.x = 140.0f; - sSubCamEyeNext.y = sSubCamEye.y = 205.0f; - sSubCamEyeNext.z = sSubCamEye.z = -20.0f; - sSubCamAtNext.x = sSubCamAt.x = 10.0f; - sSubCamAtNext.y = sSubCamAt.y = 50.0f; - sSubCamAtNext.z = sSubCamAt.z = -220.0f; - Play_CameraSetAtEye(play, sSubCamId, &sSubCamAt, &sSubCamEye); - this->timer = 20; + if (GameInteractor_Should(VB_BARINADE_PLAY_REMATCH_INTRO, + Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE), this)) { + sCsState = INTRO_CALL_BARI; + sDoorState = 100; + Player_SetCsActionWithHaltedActors(play, &this->actor, 1); + play->envCtx.screenFillColor[0] = 0xDC; + play->envCtx.screenFillColor[1] = 0xDC; + play->envCtx.screenFillColor[2] = 0xBE; + play->envCtx.screenFillColor[3] = 0xD2; + func_80064520(play, &play->csCtx); + sSubCamId = Play_CreateSubCamera(play); + Play_ChangeCameraStatus(play, CAM_ID_MAIN, CAM_STAT_WAIT); + Play_ChangeCameraStatus(play, sSubCamId, CAM_STAT_ACTIVE); + sSubCamEyeNext.x = sSubCamEye.x = 140.0f; + sSubCamEyeNext.y = sSubCamEye.y = 205.0f; + sSubCamEyeNext.z = sSubCamEye.z = -20.0f; + sSubCamAtNext.x = sSubCamAt.x = 10.0f; + sSubCamAtNext.y = sSubCamAt.y = 50.0f; + sSubCamAtNext.z = sSubCamAt.z = -220.0f; + Play_CameraSetAtEye(play, sSubCamId, &sSubCamAt, &sSubCamEye); + this->timer = 20; - for (i = BOSSVA_BARI_LOWER_5; i >= BOSSVA_BARI_UPPER_1; i--) { - Actor_SpawnAsChild( - &play->actorCtx, &this->actor, play, ACTOR_BOSS_VA, - sInitPosOffsets[i].x + this->actor.world.pos.x, - sInitPosOffsets[i].y + this->actor.world.pos.y, - sInitPosOffsets[i].z + this->actor.world.pos.z, sInitRot[i].x + this->actor.world.rot.x, - sInitRot[i].y + this->actor.world.rot.y, sInitRot[i].z + this->actor.world.rot.z, i); - } - - sSubCamAtMaxVelFrac = sSubCamEyeMaxVelFrac = sZeroVec; + for (i = BOSSVA_BARI_LOWER_5; i >= BOSSVA_BARI_UPPER_1; i--) { + Actor_SpawnAsChild( + &play->actorCtx, &this->actor, play, ACTOR_BOSS_VA, + sInitPosOffsets[i].x + this->actor.world.pos.x, + sInitPosOffsets[i].y + this->actor.world.pos.y, + sInitPosOffsets[i].z + this->actor.world.pos.z, sInitRot[i].x + this->actor.world.rot.x, + sInitRot[i].y + this->actor.world.rot.y, sInitRot[i].z + this->actor.world.rot.z, i); } + sSubCamAtMaxVelFrac = sSubCamEyeMaxVelFrac = sZeroVec; + } else { sCsState = INTRO_START; sDoorState = 5;