From b42f3f345a8c5abebb3b98ee4237cbd32aab39fc Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:01:04 -0500 Subject: [PATCH 01/44] Implement Dinolfos, Boss03 reset funcs (#1371) Re-implement Boss07 reset func --- mm/src/overlays/actors/ovl_Boss_03/z_boss_03.c | 2 ++ mm/src/overlays/actors/ovl_Boss_07/z_boss_07.c | 3 +++ mm/src/overlays/actors/ovl_En_Dinofos/z_en_dinofos.c | 7 +++++++ 3 files changed, 12 insertions(+) diff --git a/mm/src/overlays/actors/ovl_Boss_03/z_boss_03.c b/mm/src/overlays/actors/ovl_Boss_03/z_boss_03.c index 8360897cac..a99eade58e 100644 --- a/mm/src/overlays/actors/ovl_Boss_03/z_boss_03.c +++ b/mm/src/overlays/actors/ovl_Boss_03/z_boss_03.c @@ -81,6 +81,7 @@ void Boss03_Init(Actor* thisx, PlayState* play2); void Boss03_Destroy(Actor* thisx, PlayState* play); void Boss03_Update(Actor* thisx, PlayState* play2); void Boss03_Draw(Actor* thisx, PlayState* play); +void Boss03_Reset(void); void func_809E344C(Boss03* this, PlayState* play); void func_809E34B8(Boss03* this, PlayState* play); @@ -288,6 +289,7 @@ ActorProfile Boss_03_Profile = { /**/ Boss03_Destroy, /**/ Boss03_Update, /**/ Boss03_Draw, + /**/ Boss03_Reset, }; // The limbs referenced here are not used. The spheres are positioned manually by Boss03_PostLimbDraw diff --git a/mm/src/overlays/actors/ovl_Boss_07/z_boss_07.c b/mm/src/overlays/actors/ovl_Boss_07/z_boss_07.c index 8c42cb333a..21831890ca 100644 --- a/mm/src/overlays/actors/ovl_Boss_07/z_boss_07.c +++ b/mm/src/overlays/actors/ovl_Boss_07/z_boss_07.c @@ -371,6 +371,8 @@ void Boss07_Remains_Draw(Actor* thisx, PlayState* play2); void Boss07_Top_Draw(Actor* thisx, PlayState* play2); void Boss07_BattleHandler_Draw(Actor* thisx, PlayState* play2); +void Boss07_Reset(void); + void Boss07_Wrath_SetupIntroCutscene(Boss07* this, PlayState* play); void Boss07_Wrath_IntroCutscene(Boss07* this, PlayState* play); void Boss07_Wrath_DeathCutscene(Boss07* this, PlayState* play); @@ -642,6 +644,7 @@ ActorProfile Boss_07_Profile = { /**/ Boss07_Destroy, /**/ Boss07_Wrath_Update, /**/ Boss07_Wrath_Draw, + /**/ Boss07_Reset, }; // The limbs referenced here are not used. The spheres are positioned manually by Boss07_Wrath_PostLimbDraw. diff --git a/mm/src/overlays/actors/ovl_En_Dinofos/z_en_dinofos.c b/mm/src/overlays/actors/ovl_En_Dinofos/z_en_dinofos.c index f4a6bf18b2..a81d7c37fe 100644 --- a/mm/src/overlays/actors/ovl_En_Dinofos/z_en_dinofos.c +++ b/mm/src/overlays/actors/ovl_En_Dinofos/z_en_dinofos.c @@ -15,6 +15,7 @@ void EnDinofos_Init(Actor* thisx, PlayState* play); void EnDinofos_Destroy(Actor* thisx, PlayState* play); void EnDinofos_Update(Actor* thisx, PlayState* play2); void EnDinofos_Draw(Actor* thisx, PlayState* play); +void EnDinofos_Reset(void); void EnDinofos_Idle(EnDinofos* this, PlayState* play); void EnDinofos_PlayCutscene(EnDinofos* this, PlayState* play); @@ -67,6 +68,7 @@ ActorProfile En_Dinofos_Profile = { /**/ EnDinofos_Destroy, /**/ EnDinofos_Update, /**/ EnDinofos_Draw, + /**/ EnDinofos_Reset, }; static ColliderJntSphElementInit sJntSphElementsInit[9] = { @@ -1574,3 +1576,8 @@ void EnDinofos_Draw(Actor* thisx, PlayState* play) { CLOSE_DISPS(play->state.gfxCtx); } + +void EnDinofos_Reset(void) { + sCsId = CS_ID_NONE; + sNumAlive = 0; +} From b250261944dfd0831b0da0b872b40ac4f4a3cd9c Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:01:40 -0500 Subject: [PATCH 02/44] Map chest grotto grass actorListIndex to RC (#1374) --- mm/2s2h/Rando/ActorBehavior/ObjGrass.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/2s2h/Rando/ActorBehavior/ObjGrass.cpp b/mm/2s2h/Rando/ActorBehavior/ObjGrass.cpp index b267993bae..f8fa23662f 100644 --- a/mm/2s2h/Rando/ActorBehavior/ObjGrass.cpp +++ b/mm/2s2h/Rando/ActorBehavior/ObjGrass.cpp @@ -131,6 +131,8 @@ std::map chestGrottoMap = { { -107, RC_ZORA_CAPE_GROTTO_GRASS_01 }, }; +constexpr s8 chestGrottoActorIdsToBaseRc[20] = { 0, 1, 2, 3, -1, 4, 5, -1, -1, -1, 6, 7, 8, -1, 9, -1, 10, 11, 12, 13 }; + // For batches of grass spawned by Obj_Mure2 std::map, RandoCheckId> objMure2GrassMap = { { { SCENE_10YUKIYAMANOMURA2, 0, 29 }, RC_MOUNTAIN_VILLAGE_SPRING_GRASS_04 }, @@ -330,7 +332,7 @@ void Rando::ActorBehavior::InitObjGrassBehavior() { if (gPlayState->sceneId == SCENE_KAKUSIANA && actor->room == 4) { // Common chest grotto auto it = chestGrottoMap.find(gSaveContext.respawn[RESPAWN_MODE_UNK_3].data); if (it != chestGrottoMap.end()) { - randoCheckId = static_cast(it->second + actorListIndex); + randoCheckId = static_cast(it->second + chestGrottoActorIdsToBaseRc[actorListIndex]); } } else { auto it = enKusaMap.find({ gPlayState->sceneId, actor->room, actorListIndex }); From 24f47c260e2c8099750ffce40eeff8de5b7e3efd Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:01:44 -0500 Subject: [PATCH 03/44] Skip Woodfall Clear cutscene on repeats (#1377) * Skip Woodfall Clear cutscene on repeats * Forgot a flag --- .../Cutscenes/StoryCutscenes/SkipGiantsChamber.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm/2s2h/Enhancements/Cutscenes/StoryCutscenes/SkipGiantsChamber.cpp b/mm/2s2h/Enhancements/Cutscenes/StoryCutscenes/SkipGiantsChamber.cpp index 2d9481f0e9..19b8c72a44 100644 --- a/mm/2s2h/Enhancements/Cutscenes/StoryCutscenes/SkipGiantsChamber.cpp +++ b/mm/2s2h/Enhancements/Cutscenes/StoryCutscenes/SkipGiantsChamber.cpp @@ -146,6 +146,12 @@ void RegisterSkipGiantsChamber() { GameInteractor::Instance->events.erase(it); handleGiantsCheck((SceneId)transition.transitionType); } + } else if (gSaveContext.save.entrance == ENTRANCE(WOODFALL, 0) && gSaveContext.save.cutsceneIndex == 0xFFF0) { + // Odolwa's Lair repeat warps go straight to the Woodfall clear cutscene. Skip that too. + SET_WEEKEVENTREG(WEEKEVENTREG_CLEARED_WOODFALL_TEMPLE); + SET_WEEKEVENTREG(WEEKEVENTREG_ENTERED_WOODFALL_TEMPLE_PRISON); + gSaveContext.save.entrance = ENTRANCE(WOODFALL_TEMPLE, 1); + gSaveContext.save.cutsceneIndex = 0; } }); From 71972474a13499e7cec690dd54ebc8159099a2a4 Mon Sep 17 00:00:00 2001 From: Glought Date: Thu, 11 Dec 2025 13:35:29 -0800 Subject: [PATCH 04/44] Fixed issue where the Input Viewer window on first use would be a large (#1382) black box till left or right analog stick angle values text is enabled. Fixed issue where the analog stick text vertical wasn't set to its default position. Move the Input Viewer window default position up so the analog stick text is visible and not a black box due to being considered off the game window when the left or right analog stick angle values text is enabled. --- mm/2s2h/BenGui/InputViewer.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mm/2s2h/BenGui/InputViewer.cpp b/mm/2s2h/BenGui/InputViewer.cpp index be8908a548..4dead5bce7 100644 --- a/mm/2s2h/BenGui/InputViewer.cpp +++ b/mm/2s2h/BenGui/InputViewer.cpp @@ -177,12 +177,15 @@ void InputViewer::DrawElement() { ImVec2(scaledBGSize.x, scaledBGSize.y + (showLeftAnalogAngles || showRightAnalogAngles ? 15 * scale * maxScale : 0))); } else { + + ImGui::SetNextWindowSize(ImVec2(scaledBGSize.x + 20, scaledBGSize.y + 20)); + ImGui::SetNextWindowContentSize( ImVec2(mainPos.x + size.x - scaledBGSize.x - 30, mainPos.y + size.y - scaledBGSize.y - 30)); } ImGui::SetNextWindowPos( - ImVec2(mainPos.x + size.x - scaledBGSize.x - 30, mainPos.y + size.y - scaledBGSize.y - 30), + ImVec2(mainPos.x + size.x - scaledBGSize.x - 30, mainPos.y + size.y - scaledBGSize.y - 50), ImGuiCond_FirstUseEver); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); @@ -403,7 +406,7 @@ void InputViewer::DrawElement() { if (showLeftAnalogAngles) { ImGui::SetCursorPos(ImVec2( aPos.x + 10 + CVarGetInteger(CVAR_INPUT_VIEWER("LeftAnalogAngles.HortizontalOffset"), 0) * scale, - aPos.y + 10 + CVarGetInteger(CVAR_INPUT_VIEWER("LeftAnalogAngles.VerticalOffset"), 0) * scale)); + aPos.y + 10 + CVarGetInteger(CVAR_INPUT_VIEWER("LeftAnalogAngles.VerticalOffset"), 173) * scale)); // Scale font with input viewer scale float oldFontScale = ImGui::GetFont()->Scale; ImGui::GetFont()->Scale *= scale * CVarGetFloat(CVAR_INPUT_VIEWER("LeftAnalogAngles.Scale"), 1.0f); @@ -449,7 +452,7 @@ void InputViewer::DrawElement() { if (showRightAnalogAngles) { ImGui::SetCursorPos(ImVec2( aPos.x + 10 + CVarGetInteger(CVAR_INPUT_VIEWER("RightAnalogAngles.HortizontalOffset"), 170) * scale, - aPos.y + 10 + CVarGetInteger(CVAR_INPUT_VIEWER("RightAnalogAngles.VerticalOffset"), 0) * scale)); + aPos.y + 10 + CVarGetInteger(CVAR_INPUT_VIEWER("RightAnalogAngles.VerticalOffset"), 173) * scale)); // Scale font with input viewer scale float oldFontScale = ImGui::GetFont()->Scale; ImGui::GetFont()->Scale *= scale * CVarGetFloat(CVAR_INPUT_VIEWER("RightAnalogAngles.Scale"), 1.0f); From 5c7b8a1020f7d49b17379a91a4ff3873bdfcc7e1 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Thu, 11 Dec 2025 16:35:36 -0500 Subject: [PATCH 05/44] Add option to skip the soaring cutscene when warping (#1383) * Add option to skip the soaring cutscene when warping * Make hook functions static * Move entrances array to ShipUtils --- mm/2s2h/BenGui/BenMenu.cpp | 3 ++ mm/2s2h/Enhancements/Songs/PauseOwlWarp.cpp | 20 +-------- .../Songs/SkipSoaringCutscene.cpp | 41 +++++++++++++++++++ mm/2s2h/ShipUtils.cpp | 17 ++++++++ mm/2s2h/ShipUtils.h | 1 + 5 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 mm/2s2h/Enhancements/Songs/SkipSoaringCutscene.cpp diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 610372d9dd..956a8d5072 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -1265,6 +1265,9 @@ void BenMenu::AddEnhancements() { AddWidget(path, "Skip Song of Time cutscenes", WIDGET_CVAR_CHECKBOX) .CVar("gEnhancements.Songs.SkipSoTCutscenes") .Options(CheckboxOptions().Tooltip("Skips the cutscenes when playing any of the Song of Time songs.")); + AddWidget(path, "Skip Soaring cutscene", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Songs.SkipSoaringCutscene") + .Options(CheckboxOptions().Tooltip("Skips the cutscene when using the Song of Soaring to warp.")); // Time Savers path = { "Enhancements", "Time Savers", SECTION_COLUMN_1 }; diff --git a/mm/2s2h/Enhancements/Songs/PauseOwlWarp.cpp b/mm/2s2h/Enhancements/Songs/PauseOwlWarp.cpp index 76c174c4f0..9900fe4c95 100644 --- a/mm/2s2h/Enhancements/Songs/PauseOwlWarp.cpp +++ b/mm/2s2h/Enhancements/Songs/PauseOwlWarp.cpp @@ -1,6 +1,7 @@ #include #include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/ShipInit.hpp" +#include "2s2h/ShipUtils.h" extern "C" { #include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h" @@ -16,23 +17,6 @@ extern s32 gHorseIsMounted; #define CVAR_NAME "gEnhancements.Songs.PauseOwlWarp" #define CVAR CVarGetInteger(CVAR_NAME, 0) -/* - * This became a static var in decomp, so we cannot extern it. It is already redundantly defined in z_sram_NES.c and - * z_en_test7.c, so let's just define it a third time for now instead of modifying source. - */ -static u16 sOwlWarpEntrances[OWL_WARP_MAX - 1] = { - ENTRANCE(GREAT_BAY_COAST, 11), // OWL_WARP_GREAT_BAY_COAST - ENTRANCE(ZORA_CAPE, 6), // OWL_WARP_ZORA_CAPE - ENTRANCE(SNOWHEAD, 3), // OWL_WARP_SNOWHEAD - ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 8), // OWL_WARP_MOUNTAIN_VILLAGE - ENTRANCE(SOUTH_CLOCK_TOWN, 9), // OWL_WARP_CLOCK_TOWN - ENTRANCE(MILK_ROAD, 4), // OWL_WARP_MILK_ROAD - ENTRANCE(WOODFALL, 4), // OWL_WARP_WOODFALL - ENTRANCE(SOUTHERN_SWAMP_POISONED, 10), // OWL_WARP_SOUTHERN_SWAMP - ENTRANCE(IKANA_CANYON, 4), // OWL_WARP_IKANA_CANYON - ENTRANCE(STONE_TOWER, 3), // OWL_WARP_STONE_TOWER -}; - extern "C" bool PauseOwlWarp_IsOwlWarpEnabled() { return CVAR && CHECK_QUEST_ITEM(QUEST_SONG_SOARING) && gSaveContext.save.saveInfo.playerData.owlActivationFlags != 0 && @@ -72,7 +56,7 @@ void HandleConfirmingState(PauseContext* pauseCtx, Input* input) { Message_CloseTextbox(gPlayState); - gPlayState->nextEntrance = sOwlWarpEntrances[pauseCtx->cursorPoint[PAUSE_WORLD_MAP]]; + gPlayState->nextEntrance = sOwlWarpEntrancesForMods[pauseCtx->cursorPoint[PAUSE_WORLD_MAP]]; gPlayState->transitionTrigger = TRANS_TRIGGER_START; gPlayState->transitionType = TRANS_TYPE_FADE_WHITE; } else { // No diff --git a/mm/2s2h/Enhancements/Songs/SkipSoaringCutscene.cpp b/mm/2s2h/Enhancements/Songs/SkipSoaringCutscene.cpp new file mode 100644 index 0000000000..5a42346f7c --- /dev/null +++ b/mm/2s2h/Enhancements/Songs/SkipSoaringCutscene.cpp @@ -0,0 +1,41 @@ +#include +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" +#include "2s2h/ShipUtils.h" + +extern "C" { +#include "variables.h" +#include "overlays/actors/ovl_En_Test7/z_en_test7.h" +} + +#define CVAR_NAME "gEnhancements.Songs.SkipSoaringCutscene" +#define CVAR CVarGetInteger(CVAR_NAME, 0) + +static void SkipSoaringCutscene(Actor* actor, bool* should) { + s16 ocarinaMode = OWL_WARP_CS_GET_OCARINA_MODE(actor); + if (ocarinaMode == ENTEST7_ARRIVE) { + return; + } + + *should = false; + + if (gPlayState->sceneId == SCENE_SECOM) { + gPlayState->nextEntrance = ENTRANCE(IKANA_CANYON, 6); + } else if (ocarinaMode == OCARINA_MODE_WARP_TO_ENTRANCE) { + func_80169F78(gPlayState); + gSaveContext.respawn[RESPAWN_MODE_TOP].playerParams = + PLAYER_PARAMS(gSaveContext.respawn[RESPAWN_MODE_TOP].playerParams, PLAYER_START_MODE_OWL); + gSaveContext.respawnFlag = -6; + } else { + gPlayState->nextEntrance = sOwlWarpEntrancesForMods[ocarinaMode - OCARINA_MODE_WARP_TO_GREAT_BAY_COAST]; + } + + gPlayState->transitionTrigger = TRANS_TRIGGER_START; + gPlayState->transitionType = TRANS_TYPE_FADE_BLACK; +} + +static void RegisterSkipSoaringCutscene() { + COND_ID_HOOK(ShouldActorInit, ACTOR_EN_TEST7, CVAR, SkipSoaringCutscene); +} + +static RegisterShipInitFunc initFunc(RegisterSkipSoaringCutscene, { CVAR_NAME }); diff --git a/mm/2s2h/ShipUtils.cpp b/mm/2s2h/ShipUtils.cpp index 3ef51387f9..bb0b7a66dc 100644 --- a/mm/2s2h/ShipUtils.cpp +++ b/mm/2s2h/ShipUtils.cpp @@ -43,6 +43,23 @@ std::unordered_map sceneNames = { #undef DEFINE_SCENE #undef DEFINE_SCENE_UNSET +/* + * This is identical to `sOwlWarpEntrances` in decomp code, which is a static variable defined in multiple places and + * cannot be externed. We redundantly define it here and extern it, for use in port enhancements. + */ +extern u16 sOwlWarpEntrancesForMods[OWL_WARP_MAX - 1] = { + ENTRANCE(GREAT_BAY_COAST, 11), // OWL_WARP_GREAT_BAY_COAST + ENTRANCE(ZORA_CAPE, 6), // OWL_WARP_ZORA_CAPE + ENTRANCE(SNOWHEAD, 3), // OWL_WARP_SNOWHEAD + ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 8), // OWL_WARP_MOUNTAIN_VILLAGE + ENTRANCE(SOUTH_CLOCK_TOWN, 9), // OWL_WARP_CLOCK_TOWN + ENTRANCE(MILK_ROAD, 4), // OWL_WARP_MILK_ROAD + ENTRANCE(WOODFALL, 4), // OWL_WARP_WOODFALL + ENTRANCE(SOUTHERN_SWAMP_POISONED, 10), // OWL_WARP_SOUTHERN_SWAMP + ENTRANCE(IKANA_CANYON, 4), // OWL_WARP_IKANA_CANYON + ENTRANCE(STONE_TOWER, 3), // OWL_WARP_STONE_TOWER +}; + // These textures are not in existing lists that we iterate over. std::array miscellaneousTextures = { gArcheryScoreIconTex, diff --git a/mm/2s2h/ShipUtils.h b/mm/2s2h/ShipUtils.h index ffdec0ff52..4845d15fc5 100644 --- a/mm/2s2h/ShipUtils.h +++ b/mm/2s2h/ShipUtils.h @@ -17,6 +17,7 @@ std::string convertEnumToReadableName(const std::string& input); std::vector convertStartingItemsToRandoItemId(const std::string& input, const std::string& delimiter); std::string CreateStartingItemsToCvar(std::vector startingItemList); std::string Ship_RemoveSpecialCharacters(const std::string& str); +extern u16 sOwlWarpEntrancesForMods[]; extern std::array digitList; extern const char* fairyIconTextures[]; extern std::string Ship_FormatTimeDisplay(uint32_t value); From 16ad7342c9e6453b185723dd129d736412274a17 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Thu, 11 Dec 2025 16:35:46 -0500 Subject: [PATCH 06/44] Skip Hungry Goron's forced dialogue (#1384) * Skip Hungry Goron's forced dialogue * Oops - remove all that build-related stuff --- .../MiscInteractions/SkipHungryGoronDialogue.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipHungryGoronDialogue.cpp diff --git a/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipHungryGoronDialogue.cpp b/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipHungryGoronDialogue.cpp new file mode 100644 index 0000000000..859b31b6d8 --- /dev/null +++ b/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipHungryGoronDialogue.cpp @@ -0,0 +1,16 @@ +#include +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" + +#define CVAR_NAME "gEnhancements.Cutscenes.SkipMiscInteractions" +#define CVAR CVarGetInteger(CVAR_NAME, 0) + +static void RegisterSkipHungryGoronDialogue() { + COND_ID_HOOK(ShouldActorInit, ACTOR_EN_GEG, CVAR, [](Actor*, bool*) { + if (!CHECK_WEEKEVENTREG(WEEKEVENTREG_35_40)) { + SET_WEEKEVENTREG(WEEKEVENTREG_35_40); + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterSkipHungryGoronDialogue, { CVAR_NAME }); From 3431c008ba815894c13a9a5747fb9297a481ff26 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Thu, 11 Dec 2025 16:35:54 -0500 Subject: [PATCH 07/44] Add accessibility menu and options to disable screen flashes on enemy kills (#1386) --- mm/2s2h/BenGui/BenMenu.cpp | 18 +++++++++++------- .../NoScreenFlashForEnemyKill.cpp | 12 ++++++++++++ mm/2s2h/GameInteractor/GameInteractor.h | 1 + mm/src/code/z_play.c | 3 ++- 4 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 mm/2s2h/Enhancements/Accessibility/NoScreenFlashForEnemyKill.cpp diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 956a8d5072..3ad03f5dfb 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -1020,6 +1020,17 @@ void BenMenu::AddEnhancements() { "-Hug: Get the hugging cutscene\n" "-Rupee: Get the rupee reward") .ComboVec(&cremiaRewardOptions)); + AddWidget(path, "Accessibility", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Disable Screen Flash for Enemy Kills", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.A11y.NoScreenFlashForEnemyKill") + .Options(CheckboxOptions().Tooltip("Disables the white screen flash on enemy kill.")); + AddWidget(path, "Bow Reticle", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Graphics.BowReticle") + .Options(CheckboxOptions().Tooltip("Gives the bow a reticle when you draw an arrow.")); + AddWidget(path, "Mark Shooting Gallery Octoroks", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Minigames.MarkShootingGalleryOctoroks") + .Options(CheckboxOptions().Tooltip("Places markers on the Town Shooting Gallery Octoroks, indicating whether " + "they should be hit.")); path.column = SECTION_COLUMN_3; AddWidget(path, "Saving", WIDGET_SEPARATOR_TEXT); AddWidget(path, "3rd Save File Slot", WIDGET_CVAR_CHECKBOX) @@ -1153,9 +1164,6 @@ void BenMenu::AddEnhancements() { .CVar("gEnhancements.Graphics.AuthenticLogo") .Options(CheckboxOptions().Tooltip("Hide the game version and build details and display the authentic " "model and texture on the boot logo start screen.")); - AddWidget(path, "Bow Reticle", WIDGET_CVAR_CHECKBOX) - .CVar("gEnhancements.Graphics.BowReticle") - .Options(CheckboxOptions().Tooltip("Gives the bow a reticle when you draw an arrow.")); AddWidget(path, "Disable Black Bar Letterboxes", WIDGET_CVAR_CHECKBOX) .CVar("gEnhancements.Graphics.DisableBlackBars") .Options(CheckboxOptions().Tooltip( @@ -1578,10 +1586,6 @@ void BenMenu::AddEnhancements() { .Min(1) .Max(20) .DefaultValue(20)); - AddWidget(path, "Mark Shooting Gallery Octoroks", WIDGET_CVAR_CHECKBOX) - .CVar("gEnhancements.Minigames.MarkShootingGalleryOctoroks") - .Options(CheckboxOptions().Tooltip("Places markers on the Town Shooting Gallery Octoroks, indicating whether " - "they should be hit.")); AddWidget(path, "Goron Race", WIDGET_CVAR_COMBOBOX) .CVar("gEnhancements.DifficultyOptions.GoronRace") .Options(ComboboxOptions() diff --git a/mm/2s2h/Enhancements/Accessibility/NoScreenFlashForEnemyKill.cpp b/mm/2s2h/Enhancements/Accessibility/NoScreenFlashForEnemyKill.cpp new file mode 100644 index 0000000000..ba1384c808 --- /dev/null +++ b/mm/2s2h/Enhancements/Accessibility/NoScreenFlashForEnemyKill.cpp @@ -0,0 +1,12 @@ +#include +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" + +#define CVAR_NAME "gEnhancements.A11y.NoScreenFlashForEnemyKill" +#define CVAR CVarGetInteger(CVAR_NAME, 0) + +static void RegisterDisableScreenFlash() { + COND_VB_SHOULD(VB_FLASH_SCREEN_FOR_ENEMY_KILL, CVAR, { *should = false; }); +} + +static RegisterShipInitFunc initFunc(RegisterDisableScreenFlash, { CVAR_NAME }); diff --git a/mm/2s2h/GameInteractor/GameInteractor.h b/mm/2s2h/GameInteractor/GameInteractor.h index bd2ed856dc..b50bb2e619 100644 --- a/mm/2s2h/GameInteractor/GameInteractor.h +++ b/mm/2s2h/GameInteractor/GameInteractor.h @@ -250,6 +250,7 @@ typedef enum { VB_BE_NEAR_DOOR, VB_LOAD_PLAYER_ANIMATION_FRAME, VB_PLAY_SCENE_SEQUENCE, + VB_FLASH_SCREEN_FOR_ENEMY_KILL, VB_DISABLE_ITEM_UNDERWATER, VB_PLAY_SLOW_CHEST_CS, VB_BE_CLIMBABLE_SURFACE, diff --git a/mm/src/code/z_play.c b/mm/src/code/z_play.c index 9d0680869c..a0a3b3a994 100644 --- a/mm/src/code/z_play.c +++ b/mm/src/code/z_play.c @@ -1033,7 +1033,8 @@ void Play_UpdateMain(PlayState* this) { if ((this->actorCtx.freezeFlashTimer != 0) && ((this->actorCtx.freezeFlashTimer--) < 5)) { freezeFlashTimer = this->actorCtx.freezeFlashTimer; - if ((freezeFlashTimer > 0) && ((freezeFlashTimer % 2) != 0)) { + if (GameInteractor_Should(VB_FLASH_SCREEN_FOR_ENEMY_KILL, + (freezeFlashTimer > 0) && ((freezeFlashTimer % 2) != 0))) { this->envCtx.fillScreen = true; this->envCtx.screenFillColor[0] = this->envCtx.screenFillColor[1] = this->envCtx.screenFillColor[2] = 150; From 1fc285e5b9c88a46df7f813867bd6419c70c6616 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Thu, 11 Dec 2025 16:35:59 -0500 Subject: [PATCH 08/44] Bugfix: Don't skip Dotour's entry into Bomber's Notebook (#1387) * Bugfix: Don't skip Dotour's entry into Bomber's Notebook * Oops, forgot to flip condition for setting should * Or, to be less confusing, rename function to match * Move comment explaining skip logic --- .../SkipMayorsOfficeCutscene.cpp | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipMayorsOfficeCutscene.cpp b/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipMayorsOfficeCutscene.cpp index ffca305c3d..503424a960 100644 --- a/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipMayorsOfficeCutscene.cpp +++ b/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipMayorsOfficeCutscene.cpp @@ -4,42 +4,48 @@ extern "C" { #include "overlays/actors/ovl_En_Dt/z_en_dt.h" -void EnDt_UpdateMeetingCutscene(EnDt* enDt, PlayState* play); } #define CVAR_NAME "gEnhancements.Cutscenes.SkipMiscInteractions" #define CVAR CVarGetInteger(CVAR_NAME, 0) // These skips still allow the first textboxes to display, but they do ignore most of the scenes -void RegisterSkipMayorsOfficeCutscene() { +static bool ShouldStartMayorsOfficeCutscene(s16 csId) { + /* + * The Mayor starts these cutscenes but passes the target actor, which always seems to be Viscen for the start. + * The Mayor also handles the logic for progressing and ending these cutscenes. We need to alter Dotour's state + * but cannot rely on the cutscene start actor for that. Because of this, we use Actor_FindNearby to find the + * EnDt in this scene. + */ + EnDt* enDt = + (EnDt*)Actor_FindNearby(gPlayState, &GET_PLAYER(gPlayState)->actor, ACTOR_EN_DT, ACTORCAT_NPC, 99999.9f); + if (enDt != nullptr) { + if (csId == 17) { // Argument scenes without Couples Mask + enDt->csIdIndex = 26; + enDt->cutsceneState = 2; // EN_DT_CS_STATE_PLAYING + enDt->textIdIndex = 8; + Message_BombersNotebookQueueEvent(gPlayState, BOMBERS_NOTEBOOK_PERSON_MAYOR_DOTOUR); + return false; + } else if (csId == 21) { // Couples Mask scene + // Set flags to trigger scene transition and reward + enDt->timer = 0; + enDt->textIdIndex = 20; + enDt->cutsceneState = 0; // EN_DT_CS_STATE_NONE + return false; + } + } + + return true; +} + +static void RegisterSkipMayorsOfficeCutscene() { COND_VB_SHOULD(VB_START_CUTSCENE, CVAR, { - if (gPlayState->sceneId == SCENE_SONCHONOIE) { // At Mayor's Residence - s16* csId = va_arg(args, s16*); - /* - * The Mayor starts these cutscenes but passes the target actor, which always seems to be Viscen for - * the start. The Mayor also handles the logic for progressing and ending these cutscenes. We need to - * alter Dotour's state but cannot rely on the cutscene start actor for that. Because of this, we use - * Actor_FindNearby to find the EnDt in this scene. - */ - EnDt* enDt = (EnDt*)Actor_FindNearby(gPlayState, &GET_PLAYER(gPlayState)->actor, ACTOR_EN_DT, ACTORCAT_NPC, - 99999.9f); - if (enDt != nullptr) { - if (*csId == 17) { // Argument scenes without Couples Mask - enDt->actionFunc = EnDt_UpdateMeetingCutscene; - enDt->csIdIndex = 26; - enDt->cutsceneState = 2; // EN_DT_CS_STATE_PLAYING - enDt->textIdIndex = 8; - *should = false; - } else if (*csId == 21) { // Couples Mask scene - // Set flags to trigger scene transition and reward - enDt->actionFunc = EnDt_UpdateMeetingCutscene; - enDt->timer = 0; - enDt->textIdIndex = 20; - enDt->cutsceneState = 0; // EN_DT_CS_STATE_NONE - *should = false; - } - } + if (gPlayState->sceneId != SCENE_SONCHONOIE) { + return; } + + s16* csId = va_arg(args, s16*); + *should = ShouldStartMayorsOfficeCutscene(*csId); }); } From 30a16e4b47f06b7688dd7e65daa56fbf74a18449 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Thu, 11 Dec 2025 16:36:36 -0500 Subject: [PATCH 09/44] [Enhancement] Difficulty options to tweak Swamp Boat Archery minigame (#1376) * Add options to tweak the Swamp Boat Archery minigame * Add missing include * Adjustments to score widget * Move health CVar to VB hook * Change timing of score reset to before first attempt * Add option to make Koume invincible * Add condition to Koume's health hook --- mm/2s2h/BenGui/BenMenu.cpp | 29 ++++++++++++++ mm/2s2h/BenGui/MenuTypes.h | 1 + mm/2s2h/Enhancements/Minigames/Archery.cpp | 39 ++++++++++++++++++- .../Enhancements/Timesavers/SwampBoatSkip.cpp | 6 ++- mm/2s2h/GameInteractor/GameInteractor.h | 2 + .../actors/ovl_En_Tru_Mt/z_en_tru_mt.c | 7 +++- 6 files changed, 78 insertions(+), 6 deletions(-) diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 3ad03f5dfb..b4f8d1039b 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -1595,6 +1595,30 @@ void BenMenu::AddEnhancements() { "- Skip: Instantly win the race.\n") .DefaultIndex(GoronRaceDifficultyOptions::GORON_RACE_DIFFICULTY_VANILLA) .ComboVec(&goronRaceDifficultyOptions)); + AddWidget(path, "Swamp Boat Archery Target Score", WIDGET_CVAR_SLIDER_INT) + .CVar("gEnhancements.Minigames.BoatArcheryScore") + .Options(IntSliderOptions() + .Tooltip("Sets the initial target score of the Swamp Boat Archery minigame. The target score " + "gets set the first time you play the minigame in each cycle.") + .Min(1) + .Max(50) + .DefaultValue(20)); + AddWidget(path, "Koume's Health", WIDGET_CVAR_SLIDER_INT) + .CVar("gEnhancements.Minigames.BoatArcheryHealth") + .PreFunc([](WidgetInfo& info) { + if (mBenMenu->disabledMap.at(DISABLE_FOR_KOUME_INVINCIBLE).active) { + info.activeDisables.push_back(DISABLE_FOR_KOUME_INVINCIBLE); + } + }) + .Options(IntSliderOptions() + .Tooltip("Sets Koume's health in the Swamp Boat Archery minigame. If Koume is hit this many " + "times, the minigame will end.") + .Min(1) + .Max(30) + .DefaultValue(10)); + AddWidget(path, "Invincible", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Minigames.BoatArcheryInvincible") + .Options(CheckboxOptions().Tooltip("Koume's health does not decrease when hit.")); path.column = SECTION_COLUMN_3; AddWidget(path, "Other", WIDGET_SEPARATOR_TEXT); @@ -1985,6 +2009,11 @@ void BenMenu::InitElement() { return !CVarGetInteger("gAudioEditor.LinkVoiceFreqMultiplier.Enable", 0); }, "Enable Link's Voice Pitch Multiplier is Disabled" } }, + { DISABLE_FOR_KOUME_INVINCIBLE, + { [](disabledInfo& info) -> bool { + return CVarGetInteger("gEnhancements.Minigames.BoatArcheryInvincible", 0); + }, + "Koume is Invincible" } }, }; } diff --git a/mm/2s2h/BenGui/MenuTypes.h b/mm/2s2h/BenGui/MenuTypes.h index 92552d7346..e8a152d2c9 100644 --- a/mm/2s2h/BenGui/MenuTypes.h +++ b/mm/2s2h/BenGui/MenuTypes.h @@ -31,6 +31,7 @@ typedef enum { DISABLE_FOR_ADVANCED_RESOLUTION_OFF, DISABLE_FOR_VERTICAL_RESOLUTION_OFF, DISABLE_FOR_LINKS_VOICE_PITCH_MULTIPLIER_OFF, + DISABLE_FOR_KOUME_INVINCIBLE, } DisableOption; struct WidgetInfo; diff --git a/mm/2s2h/Enhancements/Minigames/Archery.cpp b/mm/2s2h/Enhancements/Minigames/Archery.cpp index 566b5fcbf9..a01de3b880 100644 --- a/mm/2s2h/Enhancements/Minigames/Archery.cpp +++ b/mm/2s2h/Enhancements/Minigames/Archery.cpp @@ -13,8 +13,14 @@ void EnSyatekiMan_Town_RunGame(EnSyatekiMan* enSyatekiMan, PlayState* play); #define SWAMP_CVAR CVarGetInteger(SWAMP_CVAR_NAME, 2180) #define TOWN_CVAR_NAME "gEnhancements.Minigames.TownArcheryScore" #define TOWN_CVAR CVarGetInteger(TOWN_CVAR_NAME, 50) +#define BOAT_CVAR_NAME "gEnhancements.Minigames.BoatArcheryScore" +#define BOAT_CVAR CVarGetInteger(BOAT_CVAR_NAME, 20) +#define BOAT_HEALTH_CVAR_NAME "gEnhancements.Minigames.BoatArcheryHealth" +#define BOAT_HEALTH_CVAR CVarGetInteger(BOAT_HEALTH_CVAR_NAME, 10) +#define BOAT_NO_DAMAGE_CVAR_NAME "gEnhancements.Minigames.BoatArcheryInvincible" +#define BOAT_NO_DAMAGE_CVAR CVarGetInteger(BOAT_NO_DAMAGE_CVAR_NAME, 0) -void RegisterArchery() { +static void RegisterSwampArchery() { COND_ID_HOOK(ShouldActorUpdate, ACTOR_EN_SYATEKI_MAN, SWAMP_CVAR != 2180, [](Actor* actor, bool* should) { EnSyatekiMan* enSyatekiMan = (EnSyatekiMan*)actor; @@ -38,7 +44,9 @@ void RegisterArchery() { *sBonusTimer = 11; *should = true; }); +} +static void RegisterTownArchery() { COND_ID_HOOK(ShouldActorUpdate, ACTOR_EN_SYATEKI_MAN, TOWN_CVAR != 50, [](Actor* actor, bool* should) { EnSyatekiMan* enSyatekiMan = (EnSyatekiMan*)actor; @@ -51,4 +59,31 @@ void RegisterArchery() { }); } -static RegisterShipInitFunc initFunc(RegisterArchery, { SWAMP_CVAR_NAME, TOWN_CVAR_NAME }); +static void ResetBoatArcheryScore(UNUSED Actor* actor) { + if (!CHECK_WEEKEVENTREG(WEEKEVENTREG_25_20)) { + HS_SET_BOAT_ARCHERY_HIGH_SCORE(BOAT_CVAR - 1); + } +} + +static void RegisterBoatArchery() { + COND_ID_HOOK(OnActorInit, ACTOR_EN_DNH, BOAT_CVAR != 20, ResetBoatArcheryScore); +} + +static bool ShouldFailBoatArchery() { + return gSaveContext.minigameHiddenScore >= BOAT_HEALTH_CVAR; +} + +static void RegisterKoumeHealth() { + COND_VB_SHOULD(VB_FAIL_BOAT_ARCHERY, BOAT_HEALTH_CVAR != 10, { *should = ShouldFailBoatArchery(); }); +} + +static void RegisterKoumeInvincible() { + COND_VB_SHOULD(VB_KOUME_TAKE_DAMAGE, BOAT_NO_DAMAGE_CVAR, { *should = false; }); +} + +static RegisterShipInitFunc initFunc_Swamp(RegisterSwampArchery, { SWAMP_CVAR_NAME }); +static RegisterShipInitFunc initFunc_Town(RegisterTownArchery, { TOWN_CVAR_NAME }); + +static RegisterShipInitFunc initFunc_Boat(RegisterBoatArchery, { BOAT_CVAR_NAME }); +static RegisterShipInitFunc initFunc_Health(RegisterKoumeHealth, { BOAT_HEALTH_CVAR_NAME }); +static RegisterShipInitFunc initFunc_Invincible(RegisterKoumeInvincible, { BOAT_NO_DAMAGE_CVAR_NAME }); diff --git a/mm/2s2h/Enhancements/Timesavers/SwampBoatSkip.cpp b/mm/2s2h/Enhancements/Timesavers/SwampBoatSkip.cpp index 5bb6cdd318..6e7dea2333 100644 --- a/mm/2s2h/Enhancements/Timesavers/SwampBoatSkip.cpp +++ b/mm/2s2h/Enhancements/Timesavers/SwampBoatSkip.cpp @@ -8,6 +8,8 @@ extern "C" { #define CVAR_NAME "gEnhancements.Timesavers.SwampBoatSpeed" #define CVAR CVarGetInteger(CVAR_NAME, 0) +#define SCORE_CVAR_NAME "gEnhancements.Minigames.BoatArcheryScore" +#define SCORE_CVAR CVarGetInteger(SCORE_CVAR_NAME, 20) void RegisterSwampBoatSpeed() { COND_ID_HOOK(OnActorUpdate, ACTOR_BG_INGATE, CVAR, [](Actor* actor) { @@ -20,14 +22,14 @@ void RegisterSwampBoatSpeed() { } else { boat->timePathTimeSpeed = 4; // Default speed } - } else if (CHECK_EVENTINF(EVENTINF_35) && HS_GET_BOAT_ARCHERY_HIGH_SCORE() >= 20) { + } else if (CHECK_EVENTINF(EVENTINF_35) && HS_GET_BOAT_ARCHERY_HIGH_SCORE() >= SCORE_CVAR) { // Archery Minigame if (CHECK_BTN_ALL(CONTROLLER1(&gPlayState->state)->cur.button, BTN_Z)) { boat->timePathTimeSpeed = (s16)(1 * 5); } else { boat->timePathTimeSpeed = 1; // Default speed } - } else if (CHECK_EVENTINF(EVENTINF_35) && gSaveContext.minigameScore >= 20) { // Current score + } else if (CHECK_EVENTINF(EVENTINF_35) && gSaveContext.minigameScore >= SCORE_CVAR) { // Current score // Update boat archery high score early // Leaving minigame early prevents the score from being updated if (gSaveContext.minigameScore > HS_GET_BOAT_ARCHERY_HIGH_SCORE()) { diff --git a/mm/2s2h/GameInteractor/GameInteractor.h b/mm/2s2h/GameInteractor/GameInteractor.h index b50bb2e619..e9deaaef98 100644 --- a/mm/2s2h/GameInteractor/GameInteractor.h +++ b/mm/2s2h/GameInteractor/GameInteractor.h @@ -197,6 +197,8 @@ typedef enum { VB_ALLOW_SONG_DOUBLE_TIME_ON_FINAL_NIGHT, VB_OWL_TELL_ABOUT_SHRINE, VB_ARCHERY_ADD_BONUS_POINTS, + VB_FAIL_BOAT_ARCHERY, + VB_KOUME_TAKE_DAMAGE, VB_HONEY_AND_DARLING_MINIGAME_FINISH, VB_MINIMAP_TOGGLE, VB_SHIELD_FROM_BUTTON_HOLD, diff --git a/mm/src/overlays/actors/ovl_En_Tru_Mt/z_en_tru_mt.c b/mm/src/overlays/actors/ovl_En_Tru_Mt/z_en_tru_mt.c index 306d49c025..2bc378e710 100644 --- a/mm/src/overlays/actors/ovl_En_Tru_Mt/z_en_tru_mt.c +++ b/mm/src/overlays/actors/ovl_En_Tru_Mt/z_en_tru_mt.c @@ -6,6 +6,7 @@ #include "z_en_tru_mt.h" #include "overlays/actors/ovl_En_Jc_Mato/z_en_jc_mato.h" +#include "2s2h/GameInteractor/GameInteractor.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -181,7 +182,9 @@ s32 func_80B761FC(EnTruMt* this, PlayState* play) { this->unk_3A4 = 0; Actor_PlaySfx(&this->actor, NA_SE_EN_KOUME_DAMAGE2); } - play->interfaceCtx.minigameHiddenPoints = 1; + if (GameInteractor_Should(VB_KOUME_TAKE_DAMAGE, true)) { + play->interfaceCtx.minigameHiddenPoints = 1; + } Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_RED, 255, COLORFILTER_BUFFLAG_OPA, 25); return true; } @@ -337,7 +340,7 @@ void func_80B76924(EnTruMt* this) { void func_80B76980(EnTruMt* this, PlayState* play) { Player* player = GET_PLAYER(play); - if (gSaveContext.minigameHiddenScore >= 10) { + if (GameInteractor_Should(VB_FAIL_BOAT_ARCHERY, gSaveContext.minigameHiddenScore >= 10)) { Message_StartTextbox(play, 0x87F, &this->actor); SET_EVENTINF(EVENTINF_36); SET_EVENTINF(EVENTINF_40); From d3c592930d6a1ee7be094ea8c8ccbe320ad89134 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Thu, 11 Dec 2025 18:43:19 -0500 Subject: [PATCH 10/44] Organize `GIVanillaBehavior` enum (#1390) * Organize `GIVanillaBehavior` enum * Add disparate conditions on `VB_BE_NEAR_DOOR` Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> --------- Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> --- .../SkipGivingBombersNotebook.cpp | 2 +- .../MiscInteractions/SkipTatlInterrupts.cpp | 8 +- mm/2s2h/GameInteractor/GameInteractor.h | 240 +- .../GameInteractor_VanillaBehavior.h | 1984 +++++++++++++++++ mm/2s2h/Rando/ActorBehavior/EnBomBowlMan.cpp | 2 +- .../overlays/actors/ovl_Elf_Msg/z_elf_msg.c | 2 +- .../overlays/actors/ovl_Elf_Msg3/z_elf_msg3.c | 2 +- .../overlays/actors/ovl_Elf_Msg4/z_elf_msg4.c | 2 +- .../overlays/actors/ovl_Elf_Msg6/z_elf_msg6.c | 2 +- .../ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c | 2 +- 10 files changed, 1997 insertions(+), 249 deletions(-) create mode 100644 mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h diff --git a/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipGivingBombersNotebook.cpp b/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipGivingBombersNotebook.cpp index fa818bc18e..250b3ed125 100644 --- a/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipGivingBombersNotebook.cpp +++ b/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipGivingBombersNotebook.cpp @@ -19,7 +19,7 @@ void CutsceneManager_End(); void EnBomBowlMan_WaitForPlayer(EnBomBowlMan* enBomBowlMan, PlayState* play) { Player* player = GET_PLAYER(play); - if (GameInteractor_Should(VB_BE_ELIGBLE_FOR_BOMBERS_NOTEBOOK, + if (GameInteractor_Should(VB_BE_ELIGIBLE_FOR_BOMBERS_NOTEBOOK, CHECK_WEEKEVENTREG(WEEKEVENTREG_73_80) && !CHECK_QUEST_ITEM(QUEST_BOMBERS_NOTEBOOK), enBomBowlMan)) { if (player->actor.world.pos.x < 1510.0f && player->transformation != PLAYER_FORM_DEKU && diff --git a/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipTatlInterrupts.cpp b/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipTatlInterrupts.cpp index 858c9fe57d..8bfe2b754f 100644 --- a/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipTatlInterrupts.cpp +++ b/mm/2s2h/Enhancements/Cutscenes/MiscInteractions/SkipTatlInterrupts.cpp @@ -24,7 +24,7 @@ void RegisterSkipTatlInterrupts() { }); // General Interupt - COND_VB_SHOULD(VB_TATL_INTERUPT_MSG, CVAR, { + COND_VB_SHOULD(VB_TATL_INTERRUPT_MSG, CVAR, { if (*should) { Actor* actor = va_arg(args, Actor*); *should = false; @@ -36,7 +36,7 @@ void RegisterSkipTatlInterrupts() { }); // General interupt (3) - COND_VB_SHOULD(VB_TATL_INTERUPT_MSG3, CVAR, { + COND_VB_SHOULD(VB_TATL_INTERRUPT_MSG3, CVAR, { if (*should) { Actor* actor = va_arg(args, Actor*); *should = false; @@ -48,7 +48,7 @@ void RegisterSkipTatlInterrupts() { }); // General interupt (4) - COND_VB_SHOULD(VB_TATL_INTERUPT_MSG4, CVAR, { + COND_VB_SHOULD(VB_TATL_INTERRUPT_MSG4, CVAR, { if (*should) { Actor* actor = va_arg(args, Actor*); *should = false; @@ -60,7 +60,7 @@ void RegisterSkipTatlInterrupts() { }); // General interupt (6) (the flags were directly copied from the original code) - COND_VB_SHOULD(VB_TATL_INTERUPT_MSG6, CVAR, { + COND_VB_SHOULD(VB_TATL_INTERRUPT_MSG6, CVAR, { if (*should) { Actor* actor = va_arg(args, Actor*); *should = false; diff --git a/mm/2s2h/GameInteractor/GameInteractor.h b/mm/2s2h/GameInteractor/GameInteractor.h index e9deaaef98..8585c28545 100644 --- a/mm/2s2h/GameInteractor/GameInteractor.h +++ b/mm/2s2h/GameInteractor/GameInteractor.h @@ -13,6 +13,8 @@ extern "C" { } #endif +#include "GameInteractor_VanillaBehavior.h" + typedef enum { FLAG_NONE, FLAG_WEEK_EVENT_REG, @@ -33,244 +35,6 @@ typedef enum { FLAG_RANDO_INF, } FlagType; -typedef enum { - // Vanilla condition: gSaveContext.showTitleCard - VB_SHOW_TITLE_CARD, - VB_PLAY_ENTRANCE_CS, - VB_KALEIDO_UNPAUSE_CLOSE, - VB_DISABLE_FD_MASK, - VB_DOGGY_RACE_SET_MAX_SPEED, - VB_LOWER_RAZOR_SWORD_DURABILITY, - VB_SET_BLAST_MASK_COOLDOWN_TIMER, - VB_PATCH_POWER_CROUCH_STAB, - VB_PATCH_SIDEROLL, - VB_TATL_CONVERSATION_AVAILABLE, - VB_PREVENT_MASK_TRANSFORMATION_CS, - VB_RESET_PUTAWAY_TIMER, - VB_SET_CLIMB_SPEED, - VB_PREVENT_CLOCK_DISPLAY, - VB_SONG_AVAILABLE_TO_PLAY, - VB_USE_CUSTOM_CAMERA, - VB_DELETE_OWL_SAVE, - VB_MSG_SCRIPT_DEL_ITEM, - VB_CONSIDER_BUNNY_HOOD_EQUIPPED, - VB_USE_ITEM_EQUIP_MASK, - VB_KALEIDO_DISPLAY_ITEM_TEXT, - VB_USE_ITEM_CONSIDER_LINK_HUMAN, - VB_DRAW_ITEM_EQUIPPED_OUTLINE, - VB_PLAY_TRANSITION_CS, - VB_PLAY_SONG_OF_TIME_CS, - VB_TATL_INTERUPT_MSG, - VB_TATL_INTERUPT_MSG3, - VB_TATL_INTERUPT_MSG4, - VB_TATL_INTERUPT_MSG6, - VB_ITEM_BE_RESTRICTED, - VB_APPLY_AIR_CONTROL, - VB_DISABLE_LETTERBOX, - VB_START_GREAT_FAIRY_CUTSCENE, - VB_GREAT_FAIRY_GIVE_DOUBLE_DEFENSE_HEARTS, - VB_KILL_CLOCK_TOWN_STRAY_FAIRY, - VB_CLOCK_TOWER_OPENING_CONSIDER_THIS_FIRST_CYCLE, - VB_SET_DRAW_FOR_SAVED_STRAY_FAIRY, - VB_DRAW_SLIME_BODY_ITEM, - VB_DRAW_OCARINA_IN_STK_HAND, - VB_DRAW_FILE_SELECT_SMALL_EXTRA_INFO_BOX, - VB_DRAW_FILE_SELECT_EXTRA_INFO_DETAILS, - VB_DRAW_FILE_SELECT_OWL_SAVE_ICON, - VB_OVERRIDE_CHAR02_LIMB, - VB_STK_HAVE_OCARINA, - VB_POST_CHAR02_LIMB, - VB_ZTARGET_SPEED_CHECK, - VB_GIVE_ITEM_FROM_ELFORG, - VB_GIVE_ITEM_FROM_ITEM00, - VB_GIVE_ITEM_FROM_SI, - VB_GIVE_ITEM_FROM_CHEST, - VB_POT_DROP_COLLECTIBLE, - VB_GIVE_ITEM_FROM_COW, - VB_COW_CONSIDER_EPONAS_SONG_PLAYED, - VB_GIVE_ITEM_FROM_GREAT_FAIRY, - VB_GIVE_ITEM_FROM_STRAY_FAIRY_MANAGER, - VB_OSN_CONSIDER_ELIGIBLE_FOR_SONG_OF_HEALING, - // Vanilla condition: (gSaveContext.save.saveInfo.inventory.items[SLOT_OCARINA] != ITEM_NONE) && - // !CHECK_QUEST_ITEM(QUEST_SONG_HEALING) - VB_OSN_TEACH_SONG_OF_HEALING, - VB_GIVE_ITEM_FROM_OSN, - VB_GIVE_ITEM_FROM_MOONS_TEAR, - VB_REVEAL_MOON_STONE_IN_CRATER, - VB_AKINDONUTS_CONSIDER_ELIGIBLE_FOR_BOMB_BAG, - VB_AKINDONUTS_CONSIDER_ELIGIBLE_FOR_POTION_REFILL, - VB_AKINDONUTS_CONSIDER_ELIGIBLE_FOR_BEAN_REFILL, - VB_AKINDONUTS_CONSIDER_BOMB_BAG_PURCHASED, - // Vanilla Condition: INV_CONTENT(ITEM_MASK_KAFEIS_MASK) != ITEM_MASK_KAFEIS_MASK - VB_MADAME_AROMA_ASK_FOR_HELP, - VB_GIVE_PENDANT_OF_MEMORIES_FROM_KAFEI, - VB_HAVE_ROMANI_MASK, - VB_GIVE_ROMANI_MASK, - VB_JG_THINK_YOU_KNOW_LULLABY, - VB_GIVE_ITEM_FROM_OFFER, - VB_EXEC_MSG_EVENT, - VB_THIEF_BIRD_STEAL, - VB_PLAY_CREMIA_HUG_CUTSCENE, - VB_FD_ALWAYS_WIELD_SWORD, - VB_SHOULD_PUTAWAY, - VB_ELEGY_CHECK_SCENE, - VB_NEED_SCARECROW_SONG, - VB_CHECK_HELD_ITEM_BUTTON_PRESS, - VB_MAGIC_SPIN_ATTACK_CHECK_FORM, - VB_TRANSFORM_THUNDER_MATRIX, - VB_SAKON_TAKE_DAMAGE, - VB_HAVE_KAMAROS_MASK, - VB_START_CUTSCENE, - VB_QUEUE_CUTSCENE, - VB_CAMERA_SET_FOCAL_ACTOR, - VB_GIVE_ITEM_FROM_MNK, - VB_GIVE_ITEM_FROM_JG, - VB_TERMINA_FIELD_BE_EMPTY, - VB_FASTER_FIRST_CYCLE, - VB_CHECK_FOR_ROOM_KEY, - VB_DRAW_BOSS_REMAINS, - VB_SPAWN_BOSS_REMAINS, - VB_ACTIVATE_BOSS_WARP_PAD, - VB_BE_HOOKSHOT_SURFACE, - VB_GIVE_ITEM_FROM_KNIGHT, - VB_STONE_HEISHI_SET_ACTION, - VB_CONSIDER_DARMANI_HEALED, - VB_CONSIDER_MIKAU_HEALED, - VB_GIVE_ITEM_FROM_DMCHAR05, - VB_DRAW_ITEM_FROM_DMCHAR05, - VB_DAMAGE_MULTIPLIER, - VB_DAMAGE_EFFECT, - VB_DRAW_DAMAGE_EFFECT, - VB_USE_NULL_FOR_DRAW_DAMAGE_EFFECTS, - VB_CHECK_BUMPER_COLLISION, - VB_PLAY_HEART_CONTAINER_GET_FANFARE, - VB_DEKU_GUARD_SHOW_SEARCH_BALLS, - VB_DISPLAY_SONG_OF_DOUBLE_TIME_PROMPT, - VB_SMITHY_START_UPGRADING_SWORD, - VB_SMITHY_CHECK_FOR_SWORD, - VB_SMITHY_CHECK_FOR_RAZOR_SWORD, - VB_SMITHY_CHECK_FOR_GILDED_SWORD, - VB_HAVE_BLAST_MASK, - VB_GIVE_NEW_WAVE_BOSSA_NOVA, - VB_BANKER_GIVE_REWARD, - VB_PASS_FIRST_BANK_THRESHOLD, - VB_PASS_INTEREST_BANK_THRESHOLD, - VB_PASS_SECOND_BANK_THRESHOLD, - VB_PASS_SECOND_BANK_THRESHOLD_ALT, - VB_CLEAR_B_BUTTON_FOR_NO_BOW, - VB_NOT_AFFORD_TINGLE_MAP, - VB_ALREADY_HAVE_TINGLE_MAP, - VB_TINGLE_GIVE_MAP_UNLOCK, - VB_OWL_STATUE_ACTIVATE, - VB_OWL_STATUE_BE_ACTIVE, - VB_HAVE_HEALED_PAMELAS_FATHER, - VB_WIN_ROMANI_PRACTICE, - VB_ROMANI_CONSIDER_EPONA_SONG_GIVEN, - VB_GIVE_ITEM_FROM_ROMANI, - VB_DOOR_HEALTH_CHECK_FAIL, - VB_GIVE_LOTTERY_WINNINGS, - VB_GIVE_HONEY_DARLING_REWARD, - VB_GS_CONTINUE_TEXTBOX, - VB_GS_CONSIDER_MASK_OF_TRUTH_EQUIPPED, - VB_HAVE_GARO_MASK, - VB_COLLECT_PLAYGROUND_RUPEE, - VB_GUAY_DROP_RUPEE, - VB_GREAT_BAY_GEAR_CLAMP_PUSH_SPEED, - VB_BLOCK_BEGIN_MOVE, - VB_BLOCK_BE_FINISHED_PULLING, - VB_SKATE_BLOCK_BEGIN_MOVE, - VB_PUSH_BLOCK_SET_SPEED, - VB_PUSH_BLOCK_SET_TIMER, - VB_GIVE_DON_GERO_MASK, - VB_TOILET_HAND_TAKE_ITEM, - VB_ITEM_GIVE_SWORD_SET_FORM_EQUIP, - VB_POT_DRAW_BE_OVERRIDDEN, - VB_CRATE_DRAW_BE_OVERRIDDEN, - VB_KUSA_BUSH_DRAW_BE_OVERRIDDEN, - VB_OBJGRASS_OPA_DRAW_BE_OVERRIDDEN, - VB_OBJGRASS_XLU_DRAW_BE_OVERRIDDEN, - VB_CARRY_GRASS_DRAW_BE_OVERRIDDEN, - VB_HAVE_MAGIC_FOR_TINGLE, - VB_GIVE_KEATON_MASK, - VB_GIVE_LETTER_TO_MAMA, - VB_BARREL_OR_CRATE_DROP_COLLECTIBLE, - VB_ALLOW_SONG_DOUBLE_TIME_ON_FINAL_NIGHT, - VB_OWL_TELL_ABOUT_SHRINE, - VB_ARCHERY_ADD_BONUS_POINTS, - VB_FAIL_BOAT_ARCHERY, - VB_KOUME_TAKE_DAMAGE, - VB_HONEY_AND_DARLING_MINIGAME_FINISH, - VB_MINIMAP_TOGGLE, - VB_SHIELD_FROM_BUTTON_HOLD, - VB_EXIT_FIRST_PERSON_MODE_FROM_BUTTON, - VB_KILL_GORON_VILLAGE_OWL, - VB_MONKEY_WAIT_TO_TALK_AFTER_APPROACH, - VB_SETUP_EAST_CLOCK_TOWN_BOM_BOWL_MAN, - VB_BE_ELIGBLE_FOR_BOMBERS_NOTEBOOK, - VB_BOM_BOWL_MAN_GIVE_ITEM, - VB_DRAW_ITEM_FROM_SOB1, - VB_MULTIPLY_INFLICTED_DMG, - VB_ELEGY_STATUE_FADE_IN_OUT, - VB_GORON_ROLL_CONSUME_MAGIC, - VB_GORON_ROLL_INCREASE_SPIKE_LEVEL, - VB_GORON_ROLL_DISABLE_SPIKE_MODE, - VB_DEKU_LINK_SPIN_ON_LAST_HOP, - VB_BALLAD_PLAYED_FORM, - VB_CONTINUE_BANKER_DIALOGUE, - VB_FISH2_SPAWN_HEART_PIECE, - VB_PLAY_DEFEAT_CAPTAIN_SEQUENCE, - VB_CLAMP_ANIMATION_SPEED, - VB_LINK_DIVE_OVER_WATER, - VB_GIBDO_TRADE_SEQUENCE_SUFFICIENT_QUANTITY_PRESENTED, - VB_GIBDO_TRADE_SEQUENCE_ACCEPT_RED_POTION, - VB_GIBDO_TRADE_SEQUENCE_TAKE_MORE_THAN_ONE_ITEM, - VB_GIBDO_TRADE_SEQUENCE_DO_TRADE, - VB_GET_ITEM_ACTION_FROM_MASK, - VB_GRASS_DROP_COLLECTIBLE, - VB_GRANT_MAGIC_UPON_REQUEST, - VB_SCOPENUTS_CONSIDER_FIRST_CYCLE, - VB_JS_OVERRIDE_MASK_CHECK, - VB_JS_CONSIDER_ELIGIBLE_FOR_DEITY, - VB_MEET_MOON_REQUIREMENTS, - VB_OPEN_WOODFALL_FROM_SONG, - VB_OPEN_GREAT_BAY_FROM_SONG, - VB_OPEN_SNOWHEAD_FROM_SONG, - VB_GOHT_UNFREEZE, - VB_PERFORM_AC_COLLISION, - VB_GIVE_ITEM_FROM_GK_LULLABY, - VB_BUY_GORMAN_MILK, - VB_PLAY_LOW_HP_ALARM, - VB_PLAY_GORON_CHILD_CRY, - VB_PLAY_ENEMY_PROXIMITY_MUSIC, - VB_PLAY_TATL_CALL_AUDIO, - VB_LINK_VOICE_PITCH_MULTIPLIER, - VB_SNOWBALL_DROP_COLLECTIBLE, - VB_SNOWBALL_SET_FLAG, - VB_START_JUMPSLASH, - VB_DESPAWN_FROG, - VB_SETUP_TRANSITION, - VB_BE_NEAR_DOOR, - VB_LOAD_PLAYER_ANIMATION_FRAME, - VB_PLAY_SCENE_SEQUENCE, - VB_FLASH_SCREEN_FOR_ENEMY_KILL, - VB_DISABLE_ITEM_UNDERWATER, - VB_PLAY_SLOW_CHEST_CS, - VB_BE_CLIMBABLE_SURFACE, - VB_PLAYER_CUTSCENE_ACTION, - VB_SET_CAMERA_AT_EYE, - VB_SET_CAMERA_FOV, - VB_USE_ITEM_CONSIDER_ITEM_ACTION, - VB_ENEMY_DROP_COLLECTIBLE, - VB_DRAW_SLIME_RANDO_ITEM, - VB_ENABLE_OBJECT_DEPENDENCY, - VB_OBJ_MURE2_SET_CHILD_ROOM, - VB_OBJ_MURE3_DROP_COLLECTIBLE, - VB_SET_PLAYER_CYLINDER_OC_FLAGS, - VB_GORON_RACE_RUBBERBANDING, - VB_HAVE_ALL_SKULLTULA_TOKENS, - VB_NOT_HAVE_ALL_SKULLTULA_TOKENS, -} GIVanillaBehavior; - typedef enum { GI_INVERT_CAMERA_RIGHT_STICK_X, GI_INVERT_CAMERA_RIGHT_STICK_Y, diff --git a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h new file mode 100644 index 0000000000..58302e0f33 --- /dev/null +++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h @@ -0,0 +1,1984 @@ +#pragma once + +#ifndef GI_VANILLA_BEHAVIOR_H +#define GI_VANILLA_BEHAVIOR_H + +typedef enum { + // #### `result` + // ```c + // ((DOORWARP1_GET_FF(&this->dyna.actor) == ENDOORWARP1_FF_2) && CHECK_QUEST_ITEM(QUEST_REMAINS_ODOLWA)) || + // ((DOORWARP1_GET_FF(&this->dyna.actor) == ENDOORWARP1_FF_3) && CHECK_QUEST_ITEM(QUEST_REMAINS_GOHT)) || + // ((DOORWARP1_GET_FF(&this->dyna.actor) == ENDOORWARP1_FF_4) && CHECK_QUEST_ITEM(QUEST_REMAINS_GYORG)) || + // ((DOORWARP1_GET_FF(&this->dyna.actor) == ENDOORWARP1_FF_5) && CHECK_QUEST_ITEM(QUEST_REMAINS_TWINMOLD)) + // ``` + // #### `args` + // - `*DoorWarp1` + VB_ACTIVATE_BOSS_WARP_PAD, + + // #### `result` + // ```c + // GET_CUR_UPG_VALUE(UPG_BOMB_BAG) == 3 + // ``` + // #### `args` + // - `*EnAkindonuts` (unused) + VB_AKINDONUTS_CONSIDER_BOMB_BAG_PURCHASED, + + // #### `result` + // ```c + // !((u32)INV_CONTENT(ITEM_MAGIC_BEANS) != ITEM_MAGIC_BEANS) + // ``` + // #### `args` + // - `*EnAkindonuts` (unused) + VB_AKINDONUTS_CONSIDER_ELIGIBLE_FOR_BEAN_REFILL, + + // #### `result` + // ```c + // !(GET_CUR_UPG_VALUE(UPG_BOMB_BAG) < 2) + // ``` + // #### `args` + // - `*EnAkindonuts` (unused) + VB_AKINDONUTS_CONSIDER_ELIGIBLE_FOR_BOMB_BAG, + + // #### `result` + // ```c + // Inventory_HasEmptyBottle() + // ``` + // #### `args` + // - `*EnAkindonuts` + VB_AKINDONUTS_CONSIDER_ELIGIBLE_FOR_POTION_REFILL, + + // #### `result` + // ```c + // (CURRENT_DAY != 3) || (gSaveContext.save.isNight == 0) + // ``` + // #### `args` + // - None + VB_ALLOW_SONG_DOUBLE_TIME_ON_FINAL_NIGHT, + + // #### `result` + // ```c + // EnBal_CheckIfMapUnlocked(this, play) + // ``` + // #### `args` + // - `*EnBal` (unused) + VB_ALREADY_HAVE_TINGLE_MAP, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*f32` (speed) + VB_APPLY_AIR_CONTROL, + + // #### `result` + // ```c + // !play->interfaceCtx.perfectLettersOn + // ``` + // #### `args` + // - `*EnSyatekiMan` (unused) + // - `*s32` (sBonusTimer) + VB_ARCHERY_ADD_BONUS_POINTS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_BALLAD_PLAYED_FORM, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnGinkoMan` + VB_BANKER_GIVE_REWARD, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + VB_BARREL_OR_CRATE_DROP_COLLECTIBLE, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - None + VB_BE_CLIMBABLE_SURFACE, + + // #### `result` + // ```c + // CHECK_WEEKEVENTREG(WEEKEVENTREG_73_80) && !CHECK_QUEST_ITEM(QUEST_BOMBERS_NOTEBOOK) + // ``` + // #### `args` + // - `*EnBomBowlMan` (unused) + VB_BE_ELIGIBLE_FOR_BOMBERS_NOTEBOOK, + + // #### `result` + // ```c + // SurfaceType_GetData(colCtx, poly, bgId, 1) >> 17 & 1 + // ``` + // #### `args` + // - `*CollisionPoly` (unused) + // - `s32` (bgId, unused) + VB_BE_HOOKSHOT_SURFACE, + + // #### `result` + // #### In `EnDoor_Idle`, for `*EnDoor`: + // ```c + // fabsf(playerPosRelToDoor.z) < 50.0f + // ``` + // #### `args` + // - `*f32` + // #### In `func_808A0E28`, for `*DoorShutter`: + // ```c + // fabsf(temp_f0) < 50.0f + // ``` + // #### `args` + // - `*f32` + VB_BE_NEAR_DOOR, + + // #### `result` + // ##### In `func_80A25D28`, for `*ObjIceblock`: + // ```c + // this->unk_26E[sp30] >= 11 + // ``` + // ##### In `func_809A3A74`, for `*ObjPzlblock`: + // ```c + // this->unk_16E[sp20] >= 11 + // ``` + // #### `args` + // - `*ObjIceblock` or `*ObjPzlblock` (unused) + VB_BLOCK_BEGIN_MOVE, + + // #### `result` + // ##### In `func_80B7F290`: + // ```c + // Math_StepToF(this->unk_164, this->unk_168, this->unk_16C) + // ``` + // ##### In `func_80A25E50`: + // ```c + // Math_StepToF(this->unk_264, this->unk_268, + // CLAMP_MAX((Math_SinS(fabsf(this->unk_268 - *this->unk_264) * 546.13336f) * 2.8f) + 1.2f, 3.5f)) + // ``` + // ##### In `func_809A3BC0`: + // ```c + // Math_StepToF(this->unk_164, this->unk_168, 2.3f) + // ``` + // #### `args` + // - `*f32` (pValue) + // - `f64` (target) + // - `f64` (step) + // - `f64` (maxStep) + VB_BLOCK_BE_FINISHED_PULLING, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnBomBowlMan` + VB_BOM_BOWL_MAN_GIVE_ITEM, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `*s32` (bool) + // - `*EnIn` + VB_BUY_GORMAN_MILK, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + VB_CAMERA_SET_FOCAL_ACTOR, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ObjGrassCarry` + VB_CARRY_GRASS_DRAW_BE_OVERRIDDEN, + + // #### `result` + // ```c + // !(atElem->atDmgInfo.dmgFlags & acElem->acDmgInfo.dmgFlags) + // ``` + // #### `args` + // - `*ColliderElement atElem` + // - `*ColliderElement acElem` + VB_CHECK_BUMPER_COLLISION, + + // #### `result` + // ```c + // INV_CONTENT(ITEM_ROOM_KEY) == ITEM_ROOM_KEY + // ``` + // #### `args` + // - None + VB_CHECK_FOR_ROOM_KEY, + + // #### `result` + // ```c + // CHECK_BTN_ALL(sPlayerControlInput->cur.button, BTN_B) + // ``` + // #### `args` + // - `uint16_t sDpadItemButtons[4]` + // - `uint16_t sPlayerItemButtons[4]` + VB_CHECK_HELD_ITEM_BUTTON_PRESS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*f32` (speed) + VB_CLAMP_ANIMATION_SPEED, + + // #### `result` + // ```c + // gSaveContext.save.saveInfo.inventory.items[SLOT_BOW] == ITEM_NONE + // ``` + // #### `args` + // - None + VB_CLEAR_B_BUTTON_FOR_NO_BOW, + + // #### `result` + // ```c + // gSaveContext.save.saveInfo.inventory.items[SLOT_OCARINA] == ITEM_NONE + // ``` + // #### `args` + // - `*EnTest4` (unused) + VB_CLOCK_TOWER_OPENING_CONSIDER_THIS_FIRST_CYCLE, + + // #### `result` + // ```c + // this->collider.base.ocFlags1 & OC1_HIT + // ``` + // #### `args` + // - `*EnGamelupy` + VB_COLLECT_PLAYGROUND_RUPEE, + + // #### `result` + // ```c + // this->currentMask == PLAYER_MASK_BUNNY + // ``` + // #### `args` + // - `*Player` + VB_CONSIDER_BUNNY_HOOD_EQUIPPED, + + // #### `result` + // ```c + // INV_CONTENT(ITEM_MASK_GORON) == ITEM_MASK_GORON + // ``` + // #### `args` + // - None + VB_CONSIDER_DARMANI_HEALED, + + // #### `result` + // ```c + // INV_CONTENT(ITEM_MASK_ZORA) == ITEM_MASK_ZORA + // ``` + // ##### Changed to `!=` in `func_80A44DE8` + // #### `args` + // - `bool` (false if `result` operator is `!=`, true otherwise) + VB_CONSIDER_MIKAU_HEALED, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnGinkoMan` + VB_CONTINUE_BANKER_DIALOGUE, + + // #### `result` + // ```c + // gHorsePlayedEponasSong + // ``` + // #### `args` + // - `*EnCow` + VB_COW_CONSIDER_EPONAS_SONG_PLAYED, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ObjKibako` + VB_CRATE_DRAW_BE_OVERRIDDEN, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `s32` + // - `*DamageTable` + // - `*u32` + // - `*Actor` + VB_DAMAGE_EFFECT, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `s32` + // - `*DamageTable` + // - `*f32` + // - `f32 sDamageMultipliers[16]` + VB_DAMAGE_MULTIPLIER, + + // #### `result` + // ```c + // gSaveContext.save.isNight + // ``` + // #### `args` + // - None + VB_DEKU_GUARD_SHOW_SEARCH_BALLS, + + // #### `result` + // ```c + // this->remainingHopsCounter == 0 + // ``` + // #### `args` + // - None + VB_DEKU_LINK_SPIN_ON_LAST_HOP, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_DELETE_OWL_SAVE, + + // #### `result` + // ```c + // CHECK_WEEKEVENTREG(sIsFrogReturnedFlags[this->frogIndex]) + // ``` + // #### `args` + // - `*EnMinifrog` + VB_DESPAWN_FROG, + + // #### `result` + // ```c + // (play->sceneId != SCENE_MITURIN_BS) && + // (play->sceneId != SCENE_HAKUGIN_BS) && + // (play->sceneId != SCENE_SEA_BS) && + // (play->sceneId != SCENE_INISIE_BS) && + // (play->sceneId != SCENE_LAST_BS) + // ``` + // #### `args` + // - None + VB_DISABLE_FD_MASK, + + // #### `result` + // ```c + // GET_CUR_FORM_BTN_ITEM(i) != ITEM_MASK_ZORA + // ``` + // ##### Alt: `DPAD_GET_CUR_FORM_BTN_ITEM` + // #### `args` + // - `s32` (item) + VB_DISABLE_ITEM_UNDERWATER, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - None + VB_DISABLE_LETTERBOX, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_DISPLAY_SONG_OF_DOUBLE_TIME_PROMPT, + + // #### `result` + // ```c + // this->actor.speed > 7.5f + // ``` + // #### `args` + // - None + VB_DOGGY_RACE_SET_MAX_SPEED, + + // #### `result` + // ```c + // gSaveContext.save.saveInfo.playerData.healthCapacity < (DOORSHUTTER_GET_1F(&this->slidingDoor.dyna.actor) * 0x10) + // ``` + // #### `args` + // - `*DoorShutter` (unused) + VB_DOOR_HEALTH_CHECK_FAIL, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*DmHina` + VB_DRAW_BOSS_REMAINS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + // - `*u8` (type) + VB_DRAW_DAMAGE_EFFECT, + + // #### `result` + // ```c + // this->isOwlSave[fileIndex + FILE_NUM_OWL_SAVE_OFFSET] + // ``` + // #### `args` + // - `s16` (file index) + VB_DRAW_FILE_SELECT_EXTRA_INFO_DETAILS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `s16` (file index) + VB_DRAW_FILE_SELECT_OWL_SAVE_ICON, + + // #### `result` + // ```c + // this->isOwlSave[i + FILE_NUM_OWL_SAVE_OFFSET] + // ``` + // #### `args` + // - `s16` (file index) + VB_DRAW_FILE_SELECT_SMALL_EXTRA_INFO_BOX, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ItemId` + VB_DRAW_ITEM_EQUIPPED_OUTLINE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `GetItemDrawId` + VB_DRAW_ITEM_FROM_DMCHAR05, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` (unused) + VB_DRAW_ITEM_FROM_SOB1, + + // #### `result` + // ```c + // (play->sceneId != SCENE_LOST_WOODS) || (gSaveContext.sceneLayer != 1) + // ``` + // #### `args` + // - `*DmStk` (unused) + VB_DRAW_OCARINA_IN_STK_HAND, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnSlime` + VB_DRAW_SLIME_BODY_ITEM, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnSlime` + VB_DRAW_SLIME_RANDO_ITEM, + + // #### `result` + // ```c + // (play->sceneId == SCENE_F40) || (play->sceneId == SCENE_F41) || + // (play->sceneId == SCENE_IKANAMAE) || (play->sceneId == SCENE_CASTLE) || + // (play->sceneId == SCENE_IKNINSIDE) || (play->sceneId == SCENE_IKANA) || + // (play->sceneId == SCENE_INISIE_N) || (play->sceneId == SCENE_INISIE_R) || + // (play->sceneId == SCENE_INISIE_BS) || (play->sceneId == SCENE_RANDOM) || + // (play->sceneId == SCENE_REDEAD) || (play->sceneId == SCENE_TOUGITES) + // ``` + // #### `args` + // - None + VB_ELEGY_CHECK_SCENE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnTorch2` + // - `*u16` (target alpha) + VB_ELEGY_STATUE_FADE_IN_OUT, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `s16` (object ID) + VB_ENABLE_OBJECT_DEPENDENCY, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Vec3f` (spawn position) + VB_ENEMY_DROP_COLLECTIBLE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `u8` + // - `*Actor` + // - `*MsgScript` + // - `*MsgScriptCallback` + VB_EXEC_MSG_EVENT, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_EXIT_FIRST_PERSON_MODE_FROM_BUTTON, + + // #### `result` + // ```c + // gSaveContext.minigameHiddenScore >= 10 + // ``` + // #### `args` + // - None + VB_FAIL_BOAT_ARCHERY, + + // #### `result` + // ```c + // (gSaveContext.save.saveInfo.inventory.items[SLOT_OCARINA] == ITEM_NONE) && (play->envCtx.sceneTimeSpeed != 0) + // ``` + // #### `args` + // - None + VB_FASTER_FIRST_CYCLE, + + // #### `result` + // ```c + // (i >= EQUIP_SLOT_A) && (this->transformation == PLAYER_FORM_FIERCE_DEITY) && + // (this->heldItemAction != PLAYER_IA_SWORD_TWO_HANDED) + // ``` + // #### `args` + // - None + VB_FD_ALWAYS_WIELD_SWORD, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnFish2` + VB_FISH2_SPAWN_HEART_PIECE, + + // #### `result` + // ```c + // (freezeFlashTimer > 0) && ((freezeFlashTimer % 2) != 0) + // ``` + // #### `args` + // - None + VB_FLASH_SCREEN_FOR_ENEMY_KILL, + + // #### `result` + // ```c + // itemAction == GET_IA_FROM_MASK(this->currentMask) + // ``` + // #### `args` + // - `PlayerItemAction` + VB_GET_ITEM_ACTION_FROM_MASK, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `PlayerItemAction` (requested item) + // - `PlayerItemAction` (presented item) + // - `**EnTalkGibudRequestedItem` + VB_GIBDO_TRADE_SEQUENCE_ACCEPT_RED_POTION, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnTalkGibud` + // - `int` (bool, should show end trade message?) + VB_GIBDO_TRADE_SEQUENCE_DO_TRADE, + + // #### `result` + // ```c + // AMMO(requestedItem->item) >= requestedItem->amount + // ``` + // #### `args` + // - `ItemId` (requested item) + VB_GIBDO_TRADE_SEQUENCE_SUFFICIENT_QUANTITY_PRESENTED, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnTalkGibudRequestedItem` + VB_GIBDO_TRADE_SEQUENCE_TAKE_MORE_THAN_ONE_ITEM, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnGeg` + VB_GIVE_DON_GERO_MASK, + + // unused + VB_GIVE_HONEY_DARLING_REWARD, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnBox` (unused) + VB_GIVE_ITEM_FROM_CHEST, + + // #### `result` + // ```c + // (this->actor.xzDistToPlayer < 150.0f) && + // ABS_ALT(BINANG_SUB(this->actor.yawTowardsPlayer, this->actor.shape.rot.y)) < 25000 + // ``` + // #### `args` + // - `*EnCow` + VB_GIVE_ITEM_FROM_COW, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `ItemId` + VB_GIVE_ITEM_FROM_DMCHAR05, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnElforg` + VB_GIVE_ITEM_FROM_ELFORG, + + // #### `result` + // ```c + // !CHECK_QUEST_ITEM(QUEST_SONG_LULLABY) + // ``` + // #### `args` + // - None + VB_GIVE_ITEM_FROM_GK_LULLABY, + + // #### `result` + // ```c + // !Flags_GetSwitch(play, GREAT_FAIRY_GET_SWITCHFLAG(&this->actor)) + // ``` + // #### `args` + // - `*BgDyYoseizo` + VB_GIVE_ITEM_FROM_GREAT_FAIRY, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnItem00` (unused) + VB_GIVE_ITEM_FROM_ITEM00, + + // #### `result` + // ```c + // !CHECK_QUEST_ITEM(QUEST_SONG_LULLABY_INTRO) + // ``` + // #### `args` + // - None + VB_GIVE_ITEM_FROM_JG, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_GIVE_ITEM_FROM_KNIGHT, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_GIVE_ITEM_FROM_MNK, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ObjMoonStone` + VB_GIVE_ITEM_FROM_MOONS_TEAR, + + // #### `result` + // ```c + // (getItemId != GI_NONE) || (player->getItemDirection < absYawDiff) + // ``` + // #### `args` + // - `*GetItemId` + // - `*Actor` + VB_GIVE_ITEM_FROM_OFFER, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnOsn` (unused) + VB_GIVE_ITEM_FROM_OSN, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnMa4` (unused) + VB_GIVE_ITEM_FROM_ROMANI, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnSi` + VB_GIVE_ITEM_FROM_SI, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnElfgrp` + VB_GIVE_ITEM_FROM_STRAY_FAIRY_MANAGER, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnFsn` + VB_GIVE_KEATON_MASK, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnFsn` + VB_GIVE_LETTER_TO_MAMA, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnKujiya` + VB_GIVE_LOTTERY_WINNINGS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnMk` (unused) + VB_GIVE_NEW_WAVE_BOSSA_NOVA, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_GIVE_PENDANT_OF_MEMORIES_FROM_KAFEI, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnMaYto` + VB_GIVE_ROMANI_MASK, + + // #### `result` + // ```c + // (this->iceCollider.base.acFlags & AC_HIT) && (this->iceCollider.elem.acHitElem->atDmgInfo.dmgFlags == + // DMG_FIRE_ARROW) + // ``` + // #### `args` + // - None + VB_GOHT_UNFREEZE, + + // #### `result` + // ```c + // phi_f0 > 0.0f + // ``` + // #### `args` + // - `*f32` (phi_f0: rubberbanded speed) + // - `*f32` (phi_f2: player's speed or 20, whichever is greater) + VB_GORON_RACE_RUBBERBANDING, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_GORON_ROLL_CONSUME_MAGIC, + + // #### `result` + // ```c + // (this->stateFlags3 & PLAYER_STATE3_80000) && + // (!CHECK_BTN_ALL(sPlayerControlInput->cur.button, BTN_A) || + // (gSaveContext.save.saveInfo.playerData.magic == 0) || + // ((this->av1.actionVar1 == 4) && (this->unk_B08 < 12.0f))) + // ``` + // #### `args` + // - None + VB_GORON_ROLL_DISABLE_SPIKE_MODE, + + // #### `result` + // ```c + // (gSaveContext.magicState == MAGIC_STATE_IDLE) && + // (gSaveContext.save.saveInfo.playerData.magic >= 2) && + // (this->av2.actionVar2 >= 0x36B0) + // ``` + // #### `args` + // - None + VB_GORON_ROLL_INCREASE_SPIKE_LEVEL, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `ActorId` + // - `*Actor` (type matches ActorId) + VB_GRASS_DROP_COLLECTIBLE, + + // #### `result` + // ```c + // gSaveContext.isMagicRequested + // ``` + // #### `args` + // - None + VB_GRANT_MAGIC_UPON_REQUEST, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*BgDblueMovebg` + VB_GREAT_BAY_GEAR_CLAMP_PUSH_SPEED, + + // #### `result` + // ```c + // (this->timer < 50) && (GREAT_FAIRY_GET_TYPE(&this->actor) == GREAT_FAIRY_TYPE_COURAGE) + // ``` + // #### `args` + // - `*BgDyYoseizo` (unused) + VB_GREAT_FAIRY_GIVE_DOUBLE_DEFENSE_HEARTS, + + // #### `result` + // ```c + // Player_GetMask(play) == PLAYER_MASK_TRUTH + // ``` + // #### `args` + // - `*EnGs` (unused) + VB_GS_CONSIDER_MASK_OF_TRUTH_EQUIPPED, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnGs` (unused) + VB_GS_CONTINUE_TEXTBOX, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnRuppecrow` + VB_GUAY_DROP_RUPEE, + + // #### `result` + // ```c + // Inventory_GetSkullTokenCount(play->sceneId) >= SPIDER_HOUSE_TOKENS_REQUIRED + // ``` + // #### `args` + // - None + VB_HAVE_ALL_SKULLTULA_TOKENS, + + // #### `result` + // ```c + // INV_CONTENT(ITEM_MASK_BLAST) == ITEM_MASK_BLAST + // ``` + // #### `args` + // - None + VB_HAVE_BLAST_MASK, + + // #### `result` + // ```c + // INV_CONTENT(ITEM_MASK_GARO) == ITEM_MASK_GARO + // ``` + // #### `args` + // - None + VB_HAVE_GARO_MASK, + + // #### `result` + // ```c + // INV_CONTENT(ITEM_MASK_GIBDO) == ITEM_MASK_GIBDO + // ``` + // #### `args` + // - None + VB_HAVE_HEALED_PAMELAS_FATHER, + + // #### `result` + // ```c + // INV_CONTENT(ITEM_MASK_KAMARO) == ITEM_MASK_KAMARO + // ``` + // #### `args` + // - None + VB_HAVE_KAMAROS_MASK, + + // #### `result` + // ```c + // gSaveContext.save.saveInfo.playerData.isMagicAcquired + // ``` + // #### `args` + // - None + VB_HAVE_MAGIC_FOR_TINGLE, + + // #### `result` + // ```c + // INV_CONTENT(ITEM_MASK_ROMANI) == ITEM_MASK_ROMANI + // ``` + // #### `args` + // - None + VB_HAVE_ROMANI_MASK, + + // #### `result` + // ```c + // (!DynaPolyActor_IsPlayerAbove((DynaPolyActor*)this->actor.child) && + // (player->actor.bgCheckFlags & BGCHECKFLAG_GROUND)) || + // (gSaveContext.timerCurTimes[TIMER_ID_MINIGAME_2] <= SECONDS_TO_TIMER(0)) || + // (this->unk_548 == this->unk_54C) + // ``` + // #### `args` + // - `*EnFu` + VB_HONEY_AND_DARLING_MINIGAME_FINISH, + + // #### `result` + // ```c + // !gPlayerFormItemRestrictions[GET_PLAYER_FORM][itemId] + // ``` + // #### `args` + // - `*ItemId` + VB_ITEM_BE_RESTRICTED, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*u8` (`*ItemId`) + VB_ITEM_GIVE_SWORD_SET_FORM_EQUIP, + + // #### `result` + // ```c + // CHECK_WEEKEVENTREG(WEEKEVENTREG_24_40) || + // CHECK_QUEST_ITEM(QUEST_SONG_LULLABY) || + // CHECK_QUEST_ITEM(QUEST_SONG_LULLABY_INTRO) + // ``` + // #### `args` + // - `*EnJg` (unused) + VB_JG_THINK_YOU_KNOW_LULLABY, + + // #### `result` + // ```c + // func_80968E38(0) >= 20 + // ``` + // #### `args` + // - None + VB_JS_CONSIDER_ELIGIBLE_FOR_DEITY, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `*s32` + // - `*bool` + VB_JS_OVERRIDE_MASK_CHECK, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `u16` (item under cursor) + VB_KALEIDO_DISPLAY_ITEM_TEXT, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_KALEIDO_UNPAUSE_CLOSE, + + // #### `result` + // ```c + // CHECK_WEEKEVENTREG(WEEKEVENTREG_08_80) + // ``` + // #### `args` + // - `*EnElforg` (unused) + VB_KILL_CLOCK_TOWN_STRAY_FAIRY, + + // #### `result` + // ```c + // gSaveContext.save.saveInfo.inventory.items[ITEM_LENS_OF_TRUTH] == ITEM_LENS_OF_TRUTH + // ``` + // #### `args` + // - None + VB_KILL_GORON_VILLAGE_OWL, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_KOUME_TAKE_DAMAGE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnKusa` + VB_KUSA_BUSH_DRAW_BE_OVERRIDDEN, + + // #### `result` + // ```c + // WaterBox_GetSurface1(play, &play->colCtx, sp4C.x, sp4C.z, &sp44, &waterBox) && ((sp44 - sp48) > 50.0f) + // ``` + // #### `args` + // - None + VB_LINK_DIVE_OVER_WATER, + + // unused + // #### `args` + // - `*u16` (sfxId) + VB_LINK_VOICE_PITCH_MULTIPLIER, + + // #### `result` + // ```c + // task != NULL + // ``` + // #### `args` + // - `*AnimTask task` + // - `*PlayerAnimationHeader animation` + // - `s32 frame` + // - `s32 limbCount` + // - `*Vec3s frameTable` + VB_LOAD_PLAYER_ANIMATION_FRAME, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_LOWER_RAZOR_SWORD_DURABILITY, + + // #### `result` + // ```c + // INV_CONTENT(ITEM_MASK_KAFEIS_MASK) != ITEM_MASK_KAFEIS_MASK + // ``` + // #### `args` + // - `*EnAl` (unused) + VB_MADAME_AROMA_ASK_FOR_HELP, + + // #### `result` + // ```c + // this->transformation == PLAYER_FORM_HUMAN + // ``` + // #### `args` + // - None + VB_MAGIC_SPIN_ATTACK_CHECK_FORM, + + // #### `result` + // ```c + // CHECK_QUEST_ITEM(QUEST_REMAINS_ODOLWA) && CHECK_QUEST_ITEM(QUEST_REMAINS_GOHT) && + // CHECK_QUEST_ITEM(QUEST_REMAINS_GYORG) && CHECK_QUEST_ITEM(QUEST_REMAINS_TWINMOLD) + // ``` + // #### `args` + // - None + VB_MEET_MOON_REQUIREMENTS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_MINIMAP_TOGGLE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnMnk` + VB_MONKEY_WAIT_TO_TALK_AFTER_APPROACH, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + // - `s16` (item ID) + VB_MSG_SCRIPT_DEL_ITEM, + + // #### `result` + // ```c + // this->currentMask == PLAYER_MASK_GIANT + // ``` + // #### `args` + // - `*s32` (damage) + VB_MULTIPLY_INFLICTED_DMG, + + // #### `result` + // ```c + // CHECK_WEEKEVENTREG(WEEKEVENTREG_79_08) && (this->picto.actor.xzDistToPlayer < this->songSummonDist) && + // ((BREG(1) != 0) || (play->msgCtx.ocarinaMode == OCARINA_MODE_PLAYED_SCARECROW_SPAWN)) + // ``` + // #### `args` + // - `*EnKakasi` + VB_NEED_SCARECROW_SONG, + + // #### `result` + // ```c + // gSaveContext.save.saveInfo.playerData.rupees < price + // ``` + // #### `args` + // - `*EnBal` (unused) + // - `*s32` (price) + VB_NOT_AFFORD_TINGLE_MAP, + + // #### `result` + // ```c + // Inventory_GetSkullTokenCount(play->sceneId) < SPIDER_HOUSE_TOKENS_REQUIRED + // ``` + // #### `args` + // - `*EnSth` (unused) + VB_NOT_HAVE_ALL_SKULLTULA_TOKENS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ObjGrass` + // - `*ObjGrassElement` + // - `*s32` (index of grass element in group) + VB_OBJGRASS_OPA_DRAW_BE_OVERRIDDEN, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ObjGrass` + // - `*ObjGrassElement` + VB_OBJGRASS_XLU_DRAW_BE_OVERRIDDEN, + + // #### `result` + // ```c + // this->actors[i] != NULL + // ``` + // #### `args` + // - `*ObjMure2` + // - `s32` (index of child to spawn) + VB_OBJ_MURE2_SET_CHILD_ROOM, + + // #### `result` + // ```c + // !((this->unk164 >> i) & 1) + // ``` + // #### `args` + // - `*ObjMure3 this` + // - `s32 i` + VB_OBJ_MURE3_DROP_COLLECTIBLE, + + // #### `result` + // ```c + // (player->transformation == PLAYER_FORM_ZORA) && (play->msgCtx.ocarinaMode == OCARINA_MODE_EVENT) && + // (play->msgCtx.lastPlayedSong == OCARINA_SONG_NEW_WAVE) + // ``` + // #### `args` + // - `*DmChar08` (unused) + VB_OPEN_GREAT_BAY_FROM_SONG, + + // #### `result` + // ```c + // (player->transformation == PLAYER_FORM_GORON) && (play->msgCtx.ocarinaMode == OCARINA_MODE_EVENT) && + // (play->msgCtx.lastPlayedSong == OCARINA_SONG_GORON_LULLABY) + // ``` + // #### `args` + // - `*EnDai` (unused) + VB_OPEN_SNOWHEAD_FROM_SONG, + + // #### `result` + // ```c + // (player->transformation == PLAYER_FORM_DEKU) && (play->msgCtx.ocarinaMode == OCARINA_MODE_EVENT) && + // (play->msgCtx.lastPlayedSong == OCARINA_SONG_SONATA) + // ``` + // #### `args` + // - `*DmChar01` (unused) + VB_OPEN_WOODFALL_FROM_SONG, + + // #### `result` + // ```c + // (gSaveContext.save.saveInfo.inventory.items[SLOT_OCARINA] != ITEM_NONE) && !CHECK_QUEST_ITEM(QUEST_SONG_HEALING) + // ``` + // #### `args` + // - `*EnOsn` (unused) + VB_OSN_CONSIDER_ELIGIBLE_FOR_SONG_OF_HEALING, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnOsn` + VB_OSN_TEACH_SONG_OF_HEALING, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `**Gfx` (dList) + VB_OVERRIDE_CHAR02_LIMB, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `u8` + VB_OWL_STATUE_ACTIVATE, + + // #### `result` + // ```c + // GET_OWL_STATUE_ACTIVATED(OBJ_WARPSTONE_GET_OWL_WARP_ID(&this->dyna.actor)) + // ``` + // #### `args` + // - `u8` + VB_OWL_STATUE_BE_ACTIVE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnOwl` + VB_OWL_TELL_ABOUT_SHRINE, + + // #### `result` + // ```c + // (HS_GET_BANK_RUPEES() >= 200) && (this->previousBankValue < 200) && !CHECK_WEEKEVENTREG(WEEKEVENTREG_59_40) + // ``` + // #### `args` + // - `*EnGinkoMan` + VB_PASS_FIRST_BANK_THRESHOLD, + + // #### `result` + // ```c + // (HS_GET_BANK_RUPEES() >= 1000) && (this->previousBankValue < 1000) && !CHECK_WEEKEVENTREG(WEEKEVENTREG_59_80) + // ``` + // #### `args` + // - `*EnGinkoMan` + VB_PASS_INTEREST_BANK_THRESHOLD, + + // #### `result` + // ```c + // HS_GET_BANK_RUPEES() >= 5000 + // ``` + // #### `args` + // - `*EnGinkoMan` (unused) + VB_PASS_SECOND_BANK_THRESHOLD, + + // #### `result` + // ```c + // (this->previousBankValue < 5000) && !CHECK_WEEKEVENTREG(WEEKEVENTREG_60_01) + // ``` + // #### `args` + // - `*EnGinkoMan` + VB_PASS_SECOND_BANK_THRESHOLD_ALT, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_PATCH_POWER_CROUCH_STAB, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_PATCH_SIDEROLL, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Collider atCol` + // - `*Collider acCol` + // - `*ColliderElement atElem` (unused) + // - `*ColliderElement acElem` + VB_PERFORM_AC_COLLISION, + + // #### `result` + // ```c + // Rand_Next() & 0x80 + // ``` + // #### `args` + // - None + VB_PLAY_CREMIA_HUG_CUTSCENE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_PLAY_DEFEAT_CAPTAIN_SEQUENCE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_PLAY_ENEMY_PROXIMITY_MUSIC, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_PLAY_ENTRANCE_CS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_PLAY_GORON_CHILD_CRY, + + // #### `result` + // ```c + // (this->getItemId == GI_HEART_CONTAINER) || + // ((this->getItemId == GI_HEART_PIECE) && EQ_MAX_QUEST_HEART_PIECE_COUNT) + // ``` + // #### `args` + // - `GetItemId` + VB_PLAY_HEART_CONTAINER_GET_FANFARE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_PLAY_LOW_HP_ALARM, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `*u16` (requested scene sequence ID) + // - `*u16` (previous main BGM sequence ID) + // - `*u16` (sequence ID) + VB_PLAY_SCENE_SEQUENCE, + + // #### `result` + // ```c + // (giEntry->itemId != ITEM_NONE) && (giEntry->gid >= 0) && (Item_CheckObtainability(giEntry->itemId) == ITEM_NONE) + // ``` + // #### `args` + // - `*EnBox` (unused) + VB_PLAY_SLOW_CHEST_CS, + + // #### `result` + // ```c + // this->getItemId == GI_OCARINA_OF_TIME + // ``` + // #### `args` + // - `*Player` (unused) + VB_PLAY_SONG_OF_TIME_CS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_PLAY_TATL_CALL_AUDIO, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_PLAY_TRANSITION_CS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + VB_PLAYER_CUTSCENE_ACTION, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` (unused) + VB_POST_CHAR02_LIMB, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ObjTsubo` + VB_POT_DRAW_BE_OVERRIDDEN, + + // #### `result` + // ##### In `func_8092762C`: + // ```c + // !OBJ_TSUBO_P0010(&this->actor) && (OBJ_TSUBO_ZROT(&this->actor) != 2) + // ``` + // ##### In `func_80927690`: + // ```c + // !this->unk_197 && (OBJ_TSUBO_ZROT(&this->actor) != 2) + // ``` + // #### `args` + // - `*ObjTsubo` + VB_POT_DROP_COLLECTIBLE, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - None + VB_PREVENT_CLOCK_DISPLAY, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - None + VB_PREVENT_MASK_TRANSFORMATION_CS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ObjOshihiki` + VB_PUSH_BLOCK_SET_SPEED, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ObjOshihiki` or `*BgIkanaBlock` + VB_PUSH_BLOCK_SET_TIMER, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*s16` (cutscene ID) + VB_QUEUE_CUTSCENE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ObjMoonStone` + VB_REVEAL_MOON_STONE_IN_CRATER, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_RESET_PUTAWAY_TIMER, + + // #### `result` + // ```c + // CHECK_QUEST_ITEM(QUEST_SONG_EPONA) + // ``` + // #### `args` + // - `bool` (unused) + VB_ROMANI_CONSIDER_EPONA_SONG_GIVEN, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnSuttari` + VB_SAKON_TAKE_DAMAGE, + + // #### `result` + // ```c + // gSaveContext.save.saveInfo.inventory.items[ITEM_OCARINA_OF_TIME] == ITEM_NONE + // ``` + // #### `args` + // - None + VB_SCOPENUTS_CONSIDER_FIRST_CYCLE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_SET_BLAST_MASK_COOLDOWN_TIMER, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_SET_CAMERA_AT_EYE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_SET_CAMERA_FOV, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*f32` (direction, which factors into speed) + VB_SET_CLIMB_SPEED, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnElforg` + VB_SET_DRAW_FOR_SAVED_STRAY_FAIRY, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Player` + // - `u32` (damage flags) + VB_SET_PLAYER_CYLINDER_OC_FLAGS, + + // #### `result` + // ```c + // (gSaveContext.save.entrance == ENTRANCE(EAST_CLOCK_TOWN, 2)) && + // GameInteractor_Should(VB_BE_ELIGIBLE_FOR_BOMBERS_NOTEBOOK, ...) + // ``` + // #### `args` + // - `*EnBomBowlMan` (unused) + VB_SETUP_EAST_CLOCK_TOWN_BOM_BOWL_MAN, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_SETUP_TRANSITION, + + // #### `result` + // ```c + // CHECK_BTN_ALL(sPlayerControlInput->cur.button, BTN_R) + // ``` + // #### `args` + // - None + VB_SHIELD_FROM_BUTTON_HOLD, + + // #### `result` + // ##### In `Player_ActionHandler_6`: + // ```c + // this->transformation != PLAYER_FORM_FIERCE_DEITY + // ``` + // ##### In `Player_UpdateInterface`: + // ```c + // (this->transformation == PLAYER_FORM_HUMAN) || (this->transformation == PLAYER_FORM_ZORA) + // ``` + // #### `args` + // - None + VB_SHOULD_PUTAWAY, + + // #### `result` + // ```c + // gSaveContext.showTitleCard + // ``` + // #### `args` + // - None + VB_SHOW_TITLE_CARD, + + // #### `result` + // ```c + // !(this->unk_1C1 & 2) && (this->unk_172[sp2C] > 10) && (D_80A22A10 == 0) && + // !func_80A216D4(this, play, 2.0f, &sp20) && !Player_InCsMode(play) + // ``` + // #### `args` + // - `*ObjSkateblock` (unused) + VB_SKATE_BLOCK_BEGIN_MOVE, + + // #### `result` + // ```c + // CUR_FORM_EQUIP(EQUIP_SLOT_B) == ITEM_SWORD_GILDED + // ``` + // #### `args` + // - None + VB_SMITHY_CHECK_FOR_GILDED_SWORD, + + // #### `result` + // ```c + // CUR_FORM_EQUIP(EQUIP_SLOT_B) == ITEM_SWORD_RAZOR + // ``` + // #### `args` + // - None + VB_SMITHY_CHECK_FOR_RAZOR_SWORD, + + // #### `result` + // ```c + // ((CUR_FORM_EQUIP(EQUIP_SLOT_B) != ITEM_SWORD_KOKIRI) && + // (CUR_FORM_EQUIP(EQUIP_SLOT_B) != ITEM_SWORD_RAZOR) && + // (CUR_FORM_EQUIP(EQUIP_SLOT_B) != ITEM_SWORD_GILDED)) + // ``` + // #### `args` + // - None + VB_SMITHY_CHECK_FOR_SWORD, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnKgy` + VB_SMITHY_START_UPGRADING_SWORD, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*ObjSnowball` or `*ObjSnowball2` + VB_SNOWBALL_DROP_COLLECTIBLE, + + // #### `result` + // ```c + // this->actor.home.rot.y == 5 + // ``` + // #### `args` + // - `*ObjSnowball` + // - `s16` (actor ID to spawn, unused) + // - `ObjSnowballActionFunc` + VB_SNOWBALL_SET_FLAG, + + // #### `result` + // ##### In `AudioOcarina_CheckSongsWithoutMusicStaff` + // ```c + // (u32)sOcarinaAvailableSongFlags & (1 << songIndex) + // ``` + // ##### In `Message_DrawMain` + // ```c + // (msgCtx->ocarinaStaff->state == OCARINA_SONG_SCARECROW_SPAWN) || + // (msgCtx->ocarinaStaff->state == OCARINA_SONG_INVERTED_TIME) || + // (msgCtx->ocarinaStaff->state == OCARINA_SONG_DOUBLE_TIME) || + // (msgCtx->ocarinaStaff->state == OCARINA_SONG_GORON_LULLABY_INTRO) || + // (msgCtx->ocarinaStaff->state != 0xFE && + // msgCtx->ocarinaStaff->state != 0xFF && + // CHECK_QUEST_ITEM(QUEST_SONG_SONATA + msgCtx->ocarinaStaff->state)) + // ``` + // #### `args` + // - `u8` (song index) + VB_SONG_AVAILABLE_TO_PLAY, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*s32` (which boss remains to return, see `func_808B849C`) + VB_SPAWN_BOSS_REMAINS, + + // #### `result` + // ```c + // this->actor.xzDistToPlayer < 350.0f + // ``` + // #### `args` + // - `*s16` (cutscene ID) + // - `*Actor` + VB_START_CUTSCENE, + + // #### `result` + // ```c + // this->actor.xzDistToPlayer < 350.0f + // ``` + // #### `args` + // - `*EnElfgrp` + VB_START_GREAT_FAIRY_CUTSCENE, + + // #### `result` + // ```c + // !(this->stateFlags1 & PLAYER_STATE1_8000000) && + // (Player_GetMeleeWeaponHeld(this) != PLAYER_MELEEWEAPON_NONE) && + // Player_CanUpdateItems(this) && + // (this->transformation != PLAYER_FORM_GORON) + // ``` + // #### `args` + // - None + VB_START_JUMPSLASH, + + // #### `result` + // ```c + // gSaveContext.save.saveInfo.inventory.items[SLOT_OCARINA] == ITEM_NONE + // ``` + // #### `args` + // - None + VB_STK_HAVE_OCARINA, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_STONE_HEISHI_SET_ACTION, + + // #### `result` + // ```c + // !CutsceneManager_IsNext(CS_ID_GLOBAL_TALK) + // ``` + // #### `args` + // - None + VB_TATL_CONVERSATION_AVAILABLE, + + // #### `result` + // ```c + // (player->tatlActor != NULL) && ((func_8092E1FC(this))) + // ``` + // #### `args` + // - `*ElfMsg` (unused) + VB_TATL_INTERRUPT_MSG, + + // #### `result` + // ```c + // (((((player->tatlActor != NULL) && + // (fabsf(player->actor.world.pos.x - this->actor.world.pos.x) < (100.0f * this->actor.scale.x))) && + // (this->actor.world.pos.y <= player->actor.world.pos.y)) + // && ((player->actor.world.pos.y - this->actor.world.pos.y) < (100.0f * this->actor.scale.y))) && + // (fabsf(player->actor.world.pos.z - this->actor.world.pos.z) < (100.0f * this->actor.scale.z))) + // ``` + // #### `args` + // - `*ElfMsg3` + VB_TATL_INTERRUPT_MSG3, + + // #### `result` + // ```c + // (player->tatlActor != NULL) && func_80AFD5E0(this) + // ``` + // #### `args` + // - `*ElfMsg4` + VB_TATL_INTERRUPT_MSG4, + + // #### `result` + // ```c + // ((this->actor.xzDistToPlayer < (100.0f * this->actor.scale.x)) && + // ((this->actor.playerHeightRel >= 0.0f) && (this->actor.playerHeightRel < (100.0f * this->actor.scale.y)))) + // ``` + // #### `args` + // - `*ElfMsg6` + VB_TATL_INTERRUPT_MSG6, + + // #### `result` + // ```c + // INV_CONTENT(ITEM_OCARINA_OF_TIME) != ITEM_OCARINA_OF_TIME + // ``` + // #### `args` + // - None + VB_TERMINA_FIELD_BE_EMPTY, + + // #### `result` + // ```c + // Rand_ZeroOne() < 0.5f + // ``` + // #### `args` + // - None + VB_THIEF_BIRD_STEAL, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnBal` + VB_TINGLE_GIVE_MAP_UNLOCK, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnBjt` (unused) + VB_TOILET_HAND_TAKE_ITEM, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnMThunder` + VB_TRANSFORM_THUNDER_MATRIX, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Camera` + VB_USE_CUSTOM_CAMERA, + + // #### `result` + // ```c + // Player_ItemToItemAction(this, item) == PLAYER_IA_MASK_ZORA + // ``` + // #### `args` + // - `*PlayerItemAction` + VB_USE_ITEM_CONSIDER_ITEM_ACTION, + + // #### `result` + // ```c + // this->transformation == PLAYER_FORM_HUMAN + // ``` + // #### `args` + // - `*PlayerItemAction` + VB_USE_ITEM_CONSIDER_LINK_HUMAN, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*PlayerMask` + VB_USE_ITEM_EQUIP_MASK, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + // - `*Vec3f` (body part positions) + // - `int` (body part count) + VB_USE_NULL_FOR_DRAW_DAMAGE_EFFECTS, + + // #### `result` + // ```c + // this->poppedBalloonCounter == 10 + // ``` + // #### `args` + // - `*EnMa4` + VB_WIN_ROMANI_PRACTICE, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `*f32` (speed) + VB_ZTARGET_SPEED_CHECK, +} GIVanillaBehavior; + +#endif diff --git a/mm/2s2h/Rando/ActorBehavior/EnBomBowlMan.cpp b/mm/2s2h/Rando/ActorBehavior/EnBomBowlMan.cpp index dd9ac4cbf6..6089174313 100644 --- a/mm/2s2h/Rando/ActorBehavior/EnBomBowlMan.cpp +++ b/mm/2s2h/Rando/ActorBehavior/EnBomBowlMan.cpp @@ -50,7 +50,7 @@ void Rando::ActorBehavior::InitEnBomBowlManBehavior() { }); // Override the original requirement, which is the absence of the Bombers' Notebook - COND_VB_SHOULD(VB_BE_ELIGBLE_FOR_BOMBERS_NOTEBOOK, IS_RANDO, { + COND_VB_SHOULD(VB_BE_ELIGIBLE_FOR_BOMBERS_NOTEBOOK, IS_RANDO, { *should = CHECK_WEEKEVENTREG(WEEKEVENTREG_73_80) && !RANDO_SAVE_CHECKS[RC_CLOCK_TOWN_BOMBERS_NOTEBOOK].obtained; }); }; diff --git a/mm/src/overlays/actors/ovl_Elf_Msg/z_elf_msg.c b/mm/src/overlays/actors/ovl_Elf_Msg/z_elf_msg.c index aab35c160b..ba54dc9772 100644 --- a/mm/src/overlays/actors/ovl_Elf_Msg/z_elf_msg.c +++ b/mm/src/overlays/actors/ovl_Elf_Msg/z_elf_msg.c @@ -108,7 +108,7 @@ void func_8092E284(ElfMsg* this, PlayState* play) { Player* player = GET_PLAYER(play); EnElf* tatl = (EnElf*)player->tatlActor; - if (GameInteractor_Should(VB_TATL_INTERUPT_MSG, (player->tatlActor != NULL) && ((func_8092E1FC(this))), this)) { + if (GameInteractor_Should(VB_TATL_INTERRUPT_MSG, (player->tatlActor != NULL) && ((func_8092E1FC(this))), this)) { player->tatlTextId = func_8092E1D0(this); CutsceneManager_Queue(CS_ID_GLOBAL_TALK); tatl->elfMsg = &this->actor; diff --git a/mm/src/overlays/actors/ovl_Elf_Msg3/z_elf_msg3.c b/mm/src/overlays/actors/ovl_Elf_Msg3/z_elf_msg3.c index f88fdd11a2..f5f3e43532 100644 --- a/mm/src/overlays/actors/ovl_Elf_Msg3/z_elf_msg3.c +++ b/mm/src/overlays/actors/ovl_Elf_Msg3/z_elf_msg3.c @@ -104,7 +104,7 @@ void func_80A2CF7C(ElfMsg3* this, PlayState* play) { EnElf* tatl = (EnElf*)player->tatlActor; if (GameInteractor_Should( - VB_TATL_INTERUPT_MSG3, + VB_TATL_INTERRUPT_MSG3, (((((player->tatlActor != NULL) && (fabsf(player->actor.world.pos.x - this->actor.world.pos.x) < (100.0f * this->actor.scale.x))) && (this->actor.world.pos.y <= player->actor.world.pos.y)) && diff --git a/mm/src/overlays/actors/ovl_Elf_Msg4/z_elf_msg4.c b/mm/src/overlays/actors/ovl_Elf_Msg4/z_elf_msg4.c index a06e7e6eac..70d912a416 100644 --- a/mm/src/overlays/actors/ovl_Elf_Msg4/z_elf_msg4.c +++ b/mm/src/overlays/actors/ovl_Elf_Msg4/z_elf_msg4.c @@ -111,7 +111,7 @@ void func_80AFD668(ElfMsg4* this, PlayState* play) { Player* player = GET_PLAYER(play); EnElf* tatl = (EnElf*)player->tatlActor; - if (GameInteractor_Should(VB_TATL_INTERUPT_MSG4, (player->tatlActor != NULL) && func_80AFD5E0(this), this)) { + if (GameInteractor_Should(VB_TATL_INTERRUPT_MSG4, (player->tatlActor != NULL) && func_80AFD5E0(this), this)) { player->tatlTextId = ElfMsg4_GetTextId(this); CutsceneManager_Queue(CS_ID_GLOBAL_TALK); tatl->elfMsg = this->elfMsg5; diff --git a/mm/src/overlays/actors/ovl_Elf_Msg6/z_elf_msg6.c b/mm/src/overlays/actors/ovl_Elf_Msg6/z_elf_msg6.c index 622a8307c0..b68d656fc7 100644 --- a/mm/src/overlays/actors/ovl_Elf_Msg6/z_elf_msg6.c +++ b/mm/src/overlays/actors/ovl_Elf_Msg6/z_elf_msg6.c @@ -236,7 +236,7 @@ void ElfMsg6_Destroy(Actor* thisx, PlayState* play) { bool func_80BA1C00(ElfMsg6* this) { return GameInteractor_Should( - VB_TATL_INTERUPT_MSG6, + VB_TATL_INTERRUPT_MSG6, ((this->actor.xzDistToPlayer < (100.0f * this->actor.scale.x)) && ((this->actor.playerHeightRel >= 0.0f) && (this->actor.playerHeightRel < (100.0f * this->actor.scale.y)))), this); diff --git a/mm/src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c b/mm/src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c index ea245bd210..1af618c1e5 100644 --- a/mm/src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c +++ b/mm/src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c @@ -159,7 +159,7 @@ void EnBomBowlMan_Init(Actor* thisx, PlayState* play) { // notebook requirement if (GameInteractor_Should(VB_SETUP_EAST_CLOCK_TOWN_BOM_BOWL_MAN, (gSaveContext.save.entrance == ENTRANCE(EAST_CLOCK_TOWN, 2)) && - GameInteractor_Should(VB_BE_ELIGBLE_FOR_BOMBERS_NOTEBOOK, + GameInteractor_Should(VB_BE_ELIGIBLE_FOR_BOMBERS_NOTEBOOK, CHECK_WEEKEVENTREG(WEEKEVENTREG_73_80) && !CHECK_QUEST_ITEM(QUEST_BOMBERS_NOTEBOOK), this), From a6815e3e96a5fd10926e5c251e6107b48b1a4214 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Sat, 13 Dec 2025 09:58:10 -0500 Subject: [PATCH 11/44] [Rando] Enemy Drops V2 (#1373) * Embed enemy drop checks into individual Regions Exclude tooltips from enemy drops in check tracker * Shuffle the rest of the enemies * Move Gold Skulltula condition * Clean up and fix enemy drop check minimum value * Name Real Bombchu drops for consistency with souls * Update documentation --- .../GameInteractor_VanillaBehavior.h | 11 +- mm/2s2h/Rando/ActorBehavior/EnemyDrops.cpp | 264 +++++++++++------- mm/2s2h/Rando/CheckTracker/CheckTracker.cpp | 9 +- mm/2s2h/Rando/Logic/Logic.cpp | 4 - mm/2s2h/Rando/Logic/Logic.h | 45 +-- .../Rando/Logic/Regions/BeneathTheWell.cpp | 23 +- mm/2s2h/Rando/Logic/Regions/Central.cpp | 1 + mm/2s2h/Rando/Logic/Regions/East.cpp | 43 ++- .../Rando/Logic/Regions/GreatBayTemple.cpp | 20 +- mm/2s2h/Rando/Logic/Regions/IkanaCastle.cpp | 26 +- mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp | 3 +- mm/2s2h/Rando/Logic/Regions/Miscellaneous.cpp | 147 +--------- mm/2s2h/Rando/Logic/Regions/Moon.cpp | 4 + mm/2s2h/Rando/Logic/Regions/North.cpp | 30 +- .../Rando/Logic/Regions/PiratesFortress.cpp | 19 +- .../Rando/Logic/Regions/SnowheadTemple.cpp | 15 + mm/2s2h/Rando/Logic/Regions/South.cpp | 20 ++ mm/2s2h/Rando/Logic/Regions/SpiderHouses.cpp | 8 +- .../Rando/Logic/Regions/StoneTowerTemple.cpp | 42 ++- mm/2s2h/Rando/Logic/Regions/TerminaField.cpp | 20 ++ mm/2s2h/Rando/Logic/Regions/West.cpp | 29 +- .../Rando/Logic/Regions/WoodfallTemple.cpp | 16 +- mm/2s2h/Rando/StaticData/Checks.cpp | 89 +++--- mm/2s2h/Rando/Types.h | 25 +- mm/src/code/z_en_item00.c | 4 +- 25 files changed, 572 insertions(+), 345 deletions(-) diff --git a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h index 58302e0f33..a49a5be20c 100644 --- a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h +++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h @@ -545,11 +545,20 @@ typedef enum { VB_ENABLE_OBJECT_DEPENDENCY, // #### `result` + // #### In `Item_DropCollectible`: // ```c // true // ``` // #### `args` - // - `*Vec3f` (spawn position) + // - `*Vec3f` spawnPos + // - `u32` params + // #### In `Item_DropCollectibleRandom`: + // ```c + // true + // ``` + // #### `args` + // - `*Vec3f` spawnPos + // - `u16` params VB_ENEMY_DROP_COLLECTIBLE, // #### `result` diff --git a/mm/2s2h/Rando/ActorBehavior/EnemyDrops.cpp b/mm/2s2h/Rando/ActorBehavior/EnemyDrops.cpp index ff540678b5..bf6f5367f2 100644 --- a/mm/2s2h/Rando/ActorBehavior/EnemyDrops.cpp +++ b/mm/2s2h/Rando/ActorBehavior/EnemyDrops.cpp @@ -10,71 +10,83 @@ extern "C" { #include "overlays/actors/ovl_En_Sw/z_en_sw.h" } -std::unordered_map actorIdToRandoCheckIdMap = { - { ACTOR_EN_AM, RC_ENEMY_DROP_ARMOS }, - { ACTOR_EN_VM, RC_ENEMY_DROP_BEAMOS }, - { ACTOR_BOSS_05, RC_ENEMY_DROP_BIO_DEKU_BABA }, - { ACTOR_EN_BB, RC_ENEMY_DROP_BLUE_BUBBLE }, - { ACTOR_EN_RAT, RC_ENEMY_DROP_BOMBCHU }, - { ACTOR_EN_SLIME, RC_ENEMY_DROP_CHU }, - { ACTOR_EN_FAMOS, RC_ENEMY_DROP_DEATH_ARMOS }, - { ACTOR_EN_DEKUBABA, RC_ENEMY_DROP_DEKU_BABA }, - { ACTOR_EN_DODONGO, RC_ENEMY_DROP_DODONGO }, - { ACTOR_EN_GRASSHOPPER, RC_ENEMY_DROP_DRAGONFLY }, - { ACTOR_EN_SNOWMAN, RC_ENEMY_DROP_EENO }, - { ACTOR_EN_TUBO_TRAP, RC_ENEMY_DROP_FLYING_POT }, - { ACTOR_EN_FLOORMAS, RC_ENEMY_DROP_FLOORMASTER }, - { ACTOR_EN_FZ, RC_ENEMY_DROP_FREEZARD }, - { ACTOR_EN_CROW, RC_ENEMY_DROP_GUAY }, - { ACTOR_EN_PP, RC_ENEMY_DROP_HIPLOOP }, - { ACTOR_EN_IK, RC_ENEMY_DROP_IRON_KNUCKLE }, - { ACTOR_EN_FIREFLY, RC_ENEMY_DROP_KEESE }, - { ACTOR_EN_NEO_REEBA, RC_ENEMY_DROP_LEEVER }, - { ACTOR_EN_RR, RC_ENEMY_DROP_LIKE_LIKE }, - { ACTOR_EN_DEKUNUTS, RC_ENEMY_DROP_MAD_SCRUB }, - { ACTOR_EN_KAREBABA, RC_ENEMY_DROP_MINI_BABA }, - { ACTOR_EN_BAGUO, RC_ENEMY_DROP_NEJIRON }, - { ACTOR_EN_OKUTA, RC_ENEMY_DROP_OCTOROK }, - { ACTOR_EN_PEEHAT, RC_ENEMY_DROP_PEAHAT }, - { ACTOR_EN_BBFALL, RC_ENEMY_DROP_RED_BUBBLE }, - { ACTOR_EN_RD, RC_ENEMY_DROP_REDEAD }, - { ACTOR_EN_SB, RC_ENEMY_DROP_SHELLBLADE }, - { ACTOR_EN_PR2, RC_ENEMY_DROP_SKULLFISH }, - { ACTOR_EN_ST, RC_ENEMY_DROP_SKULLTULA }, - { ACTOR_EN_SW, RC_ENEMY_DROP_SKULLWALLTULA }, - { ACTOR_EN_KAME, RC_ENEMY_DROP_SNAPPER }, - { ACTOR_EN_SKB, RC_ENEMY_DROP_STALCHILD }, - { ACTOR_EN_TITE, RC_ENEMY_DROP_TEKTITE }, - { ACTOR_EN_WF, RC_ENEMY_DROP_WOLFOS }, - { ACTOR_EN_WALLMAS, RC_ENEMY_DROP_WALLMASTER }, +typedef enum { + DROP_TYPE_NORMAL, + DROP_TYPE_KILL, +} EnemyDropType; + +// clang-format off +std::unordered_map> enemyDropProfiles = { + { ACTOR_EN_INVADEPOH, { RC_ENEMY_DROP_ALIEN, ACTORCAT_PROP, DROP_TYPE_NORMAL } }, + { ACTOR_EN_AM, { RC_ENEMY_DROP_ARMOS, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_BAT, { RC_ENEMY_DROP_BAD_BAT, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_VM, { RC_ENEMY_DROP_BEAMOS, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_BOSS_05, { RC_ENEMY_DROP_BIO_DEKU_BABA, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_BB, { RC_ENEMY_DROP_BLUE_BUBBLE, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + // Boes do call drop collectible code, but only the ones in an unused grotto reach that point. We could add an init + // hook to EnMkk to set unk_14C to a non-zero value, but the kill type works for now. + { ACTOR_EN_MKK, { RC_ENEMY_DROP_BOE, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_SLIME, { RC_ENEMY_DROP_CHUCHU, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + // Captain Keeta dies in a cutscene, so that drop is handled specially below. + { ACTOR_EN_FAMOS, { RC_ENEMY_DROP_DEATH_ARMOS, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_DRAGON, { RC_ENEMY_DROP_DEEP_PYTHON, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_DEKUBABA, { RC_ENEMY_DROP_DEKU_BABA, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_PR, { RC_ENEMY_DROP_DESBREKO, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_WDHAND, { RC_ENEMY_DROP_DEXIHAND, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_DINOFOS, { RC_ENEMY_DROP_DINOLFOS, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_DODONGO, { RC_ENEMY_DROP_DODONGO, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_GRASSHOPPER, { RC_ENEMY_DROP_DRAGONFLY, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_SNOWMAN, { RC_ENEMY_DROP_EENO, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_EGOL, { RC_ENEMY_DROP_EYEGORE, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_TUBO_TRAP, { RC_ENEMY_DROP_FLYING_POT, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_FLOORMAS, { RC_ENEMY_DROP_FLOORMASTER, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_FZ, { RC_ENEMY_DROP_FREEZARD, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_JSO, { RC_ENEMY_DROP_GARO, ACTORCAT_NPC, DROP_TYPE_KILL } }, + { ACTOR_EN_JSO2, { RC_ENEMY_DROP_GARO_MASTER, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_BIGSLIME, { RC_ENEMY_DROP_GEKKO, ACTORCAT_BOSS, DROP_TYPE_KILL } }, + { ACTOR_EN_PAMETFROG, { RC_ENEMY_DROP_GEKKO, ACTORCAT_BOSS, DROP_TYPE_KILL } }, + { ACTOR_EN_BEE, { RC_ENEMY_DROP_GIANT_BEE, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_CROW, { RC_ENEMY_DROP_GUAY, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_PP, { RC_ENEMY_DROP_HIPLOOP, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + // Igos du Ikana dies in a cutscene, so that drop is handled specially below. + { ACTOR_EN_IK, { RC_ENEMY_DROP_IRON_KNUCKLE, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_FIREFLY, { RC_ENEMY_DROP_KEESE, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_NEO_REEBA, { RC_ENEMY_DROP_LEEVER, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_RR, { RC_ENEMY_DROP_LIKE_LIKE, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_DEKUNUTS, { RC_ENEMY_DROP_MAD_SCRUB, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_KAREBABA, { RC_ENEMY_DROP_MINI_BABA, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_BAGUO, { RC_ENEMY_DROP_NEJIRON, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_OKUTA, { RC_ENEMY_DROP_OCTOROK, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_PEEHAT, { RC_ENEMY_DROP_PEAHAT, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_KAIZOKU, { RC_ENEMY_DROP_PIRATE, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + // Poes and Big Poes are excluded because they drop a bottleable item, which may make more sense for bottle shuffle. + { ACTOR_EN_PO_SISTERS, { RC_ENEMY_DROP_POE_SISTER, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_RAT, { RC_ENEMY_DROP_REAL_BOMBCHU, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_BBFALL, { RC_ENEMY_DROP_RED_BUBBLE, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + // Gibdos are excluded. Well Gibdos only get "killed" when receiving their requested item, which may not make sense + // for enemy drops. Patrolling Gibdos take forever to die, and one of them doesn't call Actor_Kill at all, but also + // does not handle normal drops. + { ACTOR_EN_RD, { RC_ENEMY_DROP_REDEAD, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_SB, { RC_ENEMY_DROP_SHELLBLADE, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_PR2, { RC_ENEMY_DROP_SKULLFISH, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_ST, { RC_ENEMY_DROP_SKULLTULA, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_SW, { RC_ENEMY_DROP_SKULLWALLTULA, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_BIGPAMET, { RC_ENEMY_DROP_SNAPPER, ACTORCAT_BOSS, DROP_TYPE_KILL } }, + { ACTOR_EN_KAME, { RC_ENEMY_DROP_SNAPPER, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_HINT_SKB, { RC_ENEMY_DROP_STALCHILD, ACTORCAT_NPC, DROP_TYPE_NORMAL } }, + // ACTOR_EN_RAIL_SKB is excluded. It neither calls a drop function nor dies when attacked. It respawns. It's + // logically gated no differently from the regular ACTOR_EN_SKB in the same region, so leave it be for now. + { ACTOR_EN_SKB, { RC_ENEMY_DROP_STALCHILD, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_THIEFBIRD, { RC_ENEMY_DROP_TAKKURI, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_TITE, { RC_ENEMY_DROP_TEKTITE, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_EN_WALLMAS, { RC_ENEMY_DROP_WALLMASTER, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, + { ACTOR_BOSS_04, { RC_ENEMY_DROP_WART, ACTORCAT_BOSS, DROP_TYPE_KILL } }, + { ACTOR_EN_WIZ, { RC_ENEMY_DROP_WIZROBE, ACTORCAT_ENEMY, DROP_TYPE_KILL } }, + { ACTOR_EN_WF, { RC_ENEMY_DROP_WOLFOS, ACTORCAT_ENEMY, DROP_TYPE_NORMAL } }, }; +// clang-format on -RandoCheckId GetRandoCheckByActorId(int16_t actorId) { - for (auto& map : actorIdToRandoCheckIdMap) { - if (map.first == actorId) { - return map.second; - } - } - - return RC_UNKNOWN; -} - -Actor* FindActor(Vec3f position, ActorType actorType) { - ActorListEntry actorList = gPlayState->actorCtx.actorLists[actorType]; - - Actor* currentActor = actorList.first; - for (size_t i = 0; i < actorList.length; i++) { - if (currentActor->world.pos.x == position.x && currentActor->world.pos.y == position.y && - currentActor->world.pos.z == position.z) { - return currentActor; - } else { - currentActor = currentActor->next; - } - } - return nullptr; -} - -void SpawnEnemyDrop(Vec3f position, Actor* actor, RandoCheckId randoCheckId) { +void SpawnDropItem(Vec3f position, RandoCheckId randoCheckId) { CustomItem::Spawn( position.x, position.y, position.z, 0, CustomItem::KILL_ON_TOUCH | CustomItem::TOSS_ON_SPAWN | CustomItem::ABLE_TO_ZORA_RANG, randoCheckId, @@ -90,62 +102,112 @@ void SpawnEnemyDrop(Vec3f position, Actor* actor, RandoCheckId randoCheckId) { }); } -void Rando::ActorBehavior::InitEnemyDropBehavior() { - COND_VB_SHOULD(VB_ENEMY_DROP_COLLECTIBLE, IS_RANDO, { - Vec3f position = va_arg(args, Vec3f); +bool ProcessDropProfile(Vec3f position, std::tuple dropProfile, + EnemyDropType dropType) { + EnemyDropType profileDropType = std::get(dropProfile); + RandoCheckId randoCheckId = std::get(dropProfile); + if (profileDropType != dropType || RANDO_SAVE_CHECKS[randoCheckId].cycleObtained) { + return false; + } + SpawnDropItem(position, randoCheckId); + return true; +} - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENEMY_DROPS] == RO_GENERIC_OFF) { - return; - } +int isDropActorAtPosition(PlayState* play, Actor* callingActor_, Actor* actor, void* verifyData) { + Vec3f* position = (Vec3f*)verifyData; + return actor->world.pos.x == position->x && actor->world.pos.y == position->y && actor->world.pos.z == position->z; +} - Actor* foundActor = FindActor(position, ACTORCAT_ENEMY); - if (foundActor == nullptr || foundActor->category != ACTORCAT_ENEMY) { - return; +bool SpawnNormalEnemyDrop(Vec3f position, u32 params) { + Actor* dropActor; + std::tuple dropProfile; + for (auto it = enemyDropProfiles.begin(); it != enemyDropProfiles.end(); it++) { + dropProfile = it->second; + dropActor = SubS_FindActorCustom(gPlayState, NULL, NULL, std::get(dropProfile), it->first, &position, + isDropActorAtPosition); + if (dropActor != nullptr) { + break; } + } + + if (dropActor == nullptr) { + return false; + } - int16_t actorId = foundActor->id; + // Only shuffle the Huge Rupee drop from Takkuri + if (dropActor->id == ACTOR_EN_THIEFBIRD && params != ITEM00_RUPEE_HUGE) { + return false; + } - // Skullwalltulas need special handling because most of them drop tokens - if (actorId == ACTOR_EN_SW) { - return; + // Do not shuffle the eaten Hero's Shield from a Like-Like + if (dropActor->id == ACTOR_EN_RR && params == ITEM00_SHIELD_HERO) { + return false; + } + + return ProcessDropProfile(position, dropProfile, DROP_TYPE_NORMAL); +} + +void Rando::ActorBehavior::InitEnemyDropBehavior() { + bool shouldRegister = IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENEMY_DROPS]; + + COND_VB_SHOULD(VB_ENEMY_DROP_COLLECTIBLE, shouldRegister, { + Vec3f position = va_arg(args, Vec3f); + u32 params = va_arg(args, u32); + if (SpawnNormalEnemyDrop(position, params)) { + *should = false; } + }); - RandoCheckId randoCheckId = GetRandoCheckByActorId(actorId); - if (randoCheckId == RC_UNKNOWN || RANDO_SAVE_CHECKS[randoCheckId].cycleObtained) { + COND_HOOK(OnActorKill, shouldRegister, [](Actor* actor) { + // Ignore Gold Skulltulas + if (actor->id == ACTOR_EN_SW && ENSW_GET_3(actor)) { return; } - SpawnEnemyDrop(position, foundActor, randoCheckId); - *should = false; - }); - - COND_ID_HOOK(OnActorKill, ACTOR_EN_SW, IS_RANDO, [](Actor* actor) { - if (!ENSW_GET_3(actor)) { - RandoCheckId randoCheckId = GetRandoCheckByActorId(ACTOR_EN_SW); - if (RANDO_SAVE_CHECKS[randoCheckId].cycleObtained) { - return; + if (actor->room == gPlayState->roomCtx.curRoom.num) { // Ignore room change actor kills + for (auto& map : enemyDropProfiles) { + if (map.first == actor->id) { + Vec3f position = actor->world.pos; + if (actor->id == ACTOR_EN_DRAGON) { + // The Deep Python's base is out of bounds. Mimic what it does when spawning the Seahorse. + position = actor->parent->world.pos; + position.x += Math_SinS(actor->world.rot.y + 0x8000) * (500.0f + BREG(38)); + position.y += -100.0f + BREG(33); + position.z += Math_CosS(actor->world.rot.y + 0x8000) * (500.0f + BREG(38)); + } + ProcessDropProfile(position, map.second, DROP_TYPE_KILL); + break; + } } - - SpawnEnemyDrop(actor->world.pos, actor, randoCheckId); } }); - COND_VB_SHOULD(VB_DRAW_SLIME_RANDO_ITEM, IS_RANDO, { - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENEMY_DROPS] == RO_GENERIC_OFF) { - return; + // Captain Keeta dies in a cutscene, so spawn that drop once the weekeventreg flag is set + COND_HOOK(OnFlagSet, shouldRegister, [](FlagType flagType, u32 flag) { + if (flagType == FLAG_WEEK_EVENT_REG && flag == WEEKEVENTREG_23_04) { + // Spawn near where Link ends up after the fight + SpawnDropItem({ -100.0f, 525.0f, -2330.0f }, RC_ENEMY_DROP_CAPTAIN_KEETA); } + }); - EnSlime* slime = va_arg(args, EnSlime*); - RandoCheckId randoCheckId = GetRandoCheckByActorId(ACTOR_EN_SLIME); - if (RANDO_SAVE_CHECKS[randoCheckId].cycleObtained) { - return; + // Igos dies in a cutscene, so spawn that drop once the scene clear flag is set + COND_HOOK(OnSceneFlagSet, shouldRegister, [](s16 sceneId, FlagType flagType, u32 flag) { + if (sceneId == SCENE_IKNINSIDE && flagType == FLAG_CYCL_SCENE_CLEARED_ROOM && flag == 1) { + // Spawn on the throne + SpawnDropItem({ 1408.25f, 76.0f, 2863.5f }, RC_ENEMY_DROP_IGOS_DU_IKANA); } - RandoItemId randoItemId = Rando::ConvertItem(RANDO_SAVE_CHECKS[randoCheckId].randoItemId); + }); - Matrix_RotateYS(slime->actor.shape.rot.y, MTXMODE_APPLY); - Matrix_Scale(0.25f, 0.25f, 0.25f, MTXMODE_APPLY); - Rando::DrawItem(randoItemId); + COND_VB_SHOULD(VB_DRAW_SLIME_RANDO_ITEM, shouldRegister, { + if (!RANDO_SAVE_CHECKS[RC_ENEMY_DROP_CHUCHU].cycleObtained) { + RandoItemId randoItemId = Rando::ConvertItem(RANDO_SAVE_CHECKS[RC_ENEMY_DROP_CHUCHU].randoItemId); - *should = false; + EnSlime* slime = va_arg(args, EnSlime*); + Matrix_RotateYS(slime->actor.shape.rot.y, MTXMODE_APPLY); + Matrix_Scale(0.25f, 0.25f, 0.25f, MTXMODE_APPLY); + Rando::DrawItem(randoItemId); + + *should = false; + } }); } diff --git a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp index 48dfddb947..2411b43d4e 100644 --- a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp +++ b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp @@ -432,7 +432,14 @@ void CheckTrackerDrawNonLogicalList() { std::string accessLogicString = accessLogicFuncs.find(randoCheckId) != accessLogicFuncs.end() ? accessLogicFuncs[randoCheckId] : ""; - if (accessLogicString != "") { + /* + * Enemy drop checks are multiple in number and may have unique conditions per location. This + * can result in arbitrary particular instances' conditions being displayed for the general + * check. Since the basic requirement of defeating the enemy is self-explanatory and the + * minimum, we'll omit the logic tooltip for them in particular. + */ + if (accessLogicString != "" && + !(randoCheckId >= RC_ENEMY_DROP_ALIEN && randoCheckId <= RC_ENEMY_DROP_WOLFOS)) { UIWidgets::Tooltip(accessLogicString.c_str()); } ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::IsItemHovered() diff --git a/mm/2s2h/Rando/Logic/Logic.cpp b/mm/2s2h/Rando/Logic/Logic.cpp index 11958e09b3..b763975571 100644 --- a/mm/2s2h/Rando/Logic/Logic.cpp +++ b/mm/2s2h/Rando/Logic/Logic.cpp @@ -72,10 +72,6 @@ static RegisterShipInitFunc initFunc([]() { EXIT(ENTRANCE(ZORA_CAPE, 6), ONE_WAY_EXIT, CAN_PLAY_SONG(SOARING) && CAN_OWL_WARP(OWL_WARP_ZORA_CAPE)), EXIT(ENTRANCE(IKANA_CANYON, 4), ONE_WAY_EXIT, CAN_PLAY_SONG(SOARING) && CAN_OWL_WARP(OWL_WARP_IKANA_CANYON)), EXIT(ENTRANCE(STONE_TOWER, 3), ONE_WAY_EXIT, CAN_PLAY_SONG(SOARING) && CAN_OWL_WARP(OWL_WARP_STONE_TOWER)), - EXIT(ENTRANCE(CUTSCENE, 0), ONE_WAY_EXIT, true), // Enemy Drop Region - }, - .connections = { - CONNECTION(RR_MISCELLANEOUS, true), // Enemy Drop Region }, }; }, {}); diff --git a/mm/2s2h/Rando/Logic/Logic.h b/mm/2s2h/Rando/Logic/Logic.h index 9f4d4b4974..1eb05f4341 100644 --- a/mm/2s2h/Rando/Logic/Logic.h +++ b/mm/2s2h/Rando/Logic/Logic.h @@ -120,17 +120,6 @@ extern std::map Regions; } \ } -inline bool CanReachRegions(std::vector regionList) { - std::set reachableRegions; - FindReachableRegions(GetRegionIdFromEntrance(gSaveContext.save.entrance), reachableRegions); - for (auto& target : regionList) { - if (reachableRegions.count(target) > 0) { - return true; - } - } - return false; -} - inline std::string LogicString(std::string condition) { if (condition == "true") return ""; @@ -222,18 +211,20 @@ inline bool CanKillEnemy(ActorId EnemyId) { (CAN_USE_SWORD || CAN_BE_DEKU || CAN_BE_GORON || CAN_BE_ZORA)); case ACTOR_EN_KAIZOKU: // Fighter Pirate return (CAN_USE_SWORD || CAN_BE_ZORA); - case ACTOR_EN_PAMETFROG: // Swamp Gekko + case ACTOR_EN_PAMETFROG: // Woodfall Temple Gekko (and Snapper) return (HAS_ITEM(ITEM_BOW) && (CAN_BE_DEKU || CAN_USE_EXPLOSIVE || CAN_BE_GORON)); - case ACTOR_EN_BIGSLIME: // Great Bay Gekko + case ACTOR_EN_BIGSLIME: // Great Bay Temple Gekko return (CAN_USE_MAGIC_ARROW(ICE)); case ACTOR_EN_SW: // Gold Skulltula & Skullwalltula return (CAN_USE_PROJECTILE || CAN_BE_DEKU || CAN_BE_GORON || CAN_USE_SWORD || CAN_USE_EXPLOSIVE); - case ACTOR_EN_DINOFOS: // Dinofos + case ACTOR_EN_DINOFOS: // Dinolfos return (CAN_USE_SWORD || CAN_BE_GORON || HAS_ITEM(ITEM_BOW) || (CAN_BE_DEKU && HAS_MAGIC)); case ACTOR_EN_WIZ: // Wizrobe return (HAS_ITEM(ITEM_BOW) || HAS_ITEM(ITEM_HOOKSHOT) || CAN_USE_SWORD || CAN_BE_GORON); case ACTOR_EN_WF: // Wolfos return (CAN_USE_SWORD || (CAN_BE_DEKU && HAS_MAGIC) || CAN_BE_GORON || CAN_BE_ZORA); + case ACTOR_EN_JSO: // Garo + return (HAS_ITEM(ITEM_MASK_GARO) && (HAS_ITEM(ITEM_BOW) || CAN_BE_GORON || CAN_USE_SWORD)); case ACTOR_EN_JSO2: // Garo Master return (HAS_ITEM(ITEM_BOW) || CAN_BE_GORON || CAN_USE_SWORD); case ACTOR_EN_IK: // Iron Knuckle @@ -248,7 +239,7 @@ inline bool CanKillEnemy(ActorId EnemyId) { return (CAN_BE_DEKU || CAN_USE_EXPLOSIVE || CAN_BE_GORON); case ACTOR_EN_ST: // Large Skulltula return (CAN_USE_SWORD || CAN_USE_PROJECTILE || CAN_BE_GORON || CAN_USE_EXPLOSIVE); - case ACTOR_EN_BAT: // Bat Bat + case ACTOR_EN_BAT: // Bad Bat return (CAN_USE_SWORD || HAS_ITEM(ITEM_HOOKSHOT) || HAS_ITEM(ITEM_BOW) || CAN_USE_EXPLOSIVE || CAN_BE_GORON || CAN_BE_ZORA); case ACTOR_EN_DEKUBABA: // Neck bending Deku Baba @@ -295,6 +286,9 @@ inline bool CanKillEnemy(ActorId EnemyId) { return (CAN_USE_SWORD || CAN_BE_GORON || CAN_BE_ZORA || CAN_BE_DEKU || HAS_ITEM(ITEM_DEKU_STICK)); case ACTOR_EN_RD: // Redead & Gibdos return (CAN_USE_SWORD || CAN_BE_DEKU || CAN_BE_GORON || CAN_BE_ZORA || HAS_ITEM(ITEM_DEKU_STICK)); + case ACTOR_EN_BSB: // Captain Keeta (May be possible without bow, but the window is tight. Requiring for now) + return (HAS_ITEM(ITEM_BOW) && + (CAN_USE_SWORD || HAS_ITEM(ITEM_DEKU_STICK) || CAN_USE_EXPLOSIVE || CAN_BE_GORON || CAN_BE_ZORA)); case ACTOR_EN_SKB: // Stalchild return (CAN_USE_SWORD || CAN_BE_GORON || CAN_BE_ZORA || (CAN_BE_DEKU && HAS_MAGIC) || HAS_ITEM(ITEM_BOW) || HAS_ITEM(ITEM_DEKU_STICK) || HAS_ITEM(ITEM_HOOKSHOT)); @@ -308,23 +302,38 @@ inline bool CanKillEnemy(ActorId EnemyId) { case ACTOR_EN_WDHAND: // Dexihand (Basic kill method, seems like a pain to require other things) return (CAN_BE_ZORA && HAS_MAGIC); case ACTOR_EN_KAME: // Snapper (non Gekko Miniboss) - return (CAN_BE_DEKU || CAN_USE_EXPLOSIVE || CAN_BE_GORON); + return (CAN_USE_EXPLOSIVE || CAN_BE_GORON); case ACTOR_EN_SB: // Shellblade return (CAN_BE_ZORA && HAS_MAGIC); case ACTOR_EN_OKUTA: // Octorok + case ACTOR_EN_EGOL: // Eyegore return (CAN_USE_PROJECTILE); case ACTOR_EN_BAGUO: // Nejiron return (CAN_USE_SWORD || CAN_BE_GORON || CAN_BE_ZORA || HAS_ITEM(ITEM_HOOKSHOT)); case ACTOR_EN_NEO_REEBA: // Leever return (CAN_USE_SWORD || CAN_BE_GORON || CAN_BE_ZORA || (CAN_BE_DEKU && HAS_MAGIC) || HAS_ITEM(ITEM_BOW) || HAS_ITEM(ITEM_DEKU_STICK)); - case ACTOR_EN_PP: + case ACTOR_EN_PP: // Hiploop return (CAN_USE_SWORD || CAN_BE_GORON || CAN_BE_ZORA || (CAN_BE_DEKU && HAS_MAGIC) || HAS_ITEM(ITEM_BOW) || HAS_ITEM(ITEM_DEKU_STICK) || HAS_ITEM(ITEM_HOOKSHOT)); - case ACTOR_EN_PR2: + case ACTOR_EN_PR: // Desbreko + case ACTOR_EN_PR2: // Skull fish return (CAN_BE_ZORA && HAS_MAGIC); case ACTOR_BOSS_05: // Bio Deku Baba return CAN_BE_ZORA && CAN_USE_ABILITY(SWIM); + case ACTOR_EN_BEE: // Giant Bee + return (CAN_USE_SWORD || CAN_BE_GORON || CAN_BE_ZORA || CAN_BE_DEKU || HAS_ITEM(ITEM_DEKU_STICK) || + CAN_USE_PROJECTILE || CAN_USE_EXPLOSIVE || HAS_ITEM(ITEM_DEKU_NUT)); + case ACTOR_EN_DRAGON: // Deep Python + return (CAN_BE_ZORA && HAS_MAGIC); + case ACTOR_EN_PO_SISTERS: + // The first three sisters can be damaged with almost anything, but Meg requires ranged attacks. Not using + // CAN_USE_EXPLOSIVE here, as the Blast Mask cannot reach, and the Powder Keg can only be used once. + return (CAN_USE_PROJECTILE || HAS_ITEM(ITEM_BOMB) || HAS_ITEM(ITEM_BOMBCHU)); + case ACTOR_EN_INVADEPOH: // Them + return HAS_ITEM(ITEM_BOW); + case ACTOR_EN_THIEFBIRD: // Takkuri + return (CAN_USE_PROJECTILE || CAN_BE_GORON || CAN_BE_ZORA || CAN_USE_EXPLOSIVE || CAN_USE_SWORD); default: // Incorrect actor ID inputed. assert(false); return false; diff --git a/mm/2s2h/Rando/Logic/Regions/BeneathTheWell.cpp b/mm/2s2h/Rando/Logic/Regions/BeneathTheWell.cpp index 5fc4076220..3cc0789c90 100644 --- a/mm/2s2h/Rando/Logic/Regions/BeneathTheWell.cpp +++ b/mm/2s2h/Rando/Logic/Regions/BeneathTheWell.cpp @@ -19,6 +19,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_BENEATH_THE_WELL_MIDDLE_POT_08, true), CHECK(RC_BENEATH_THE_WELL_MIDDLE_POT_09, true), CHECK(RC_BENEATH_THE_WELL_MIDDLE_POT_10, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), + CHECK(RC_ENEMY_DROP_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA)), }, .connections = { CONNECTION(RR_BENEATH_THE_WELL_FREEZARD_ROOM, true), @@ -60,6 +62,9 @@ static RegisterShipInitFunc initFunc([]() { } }; Regions[RR_BENEATH_THE_WELL_DEXIHAND_ROOM] = RandoRegion{ .name = "Dexihand Room", .sceneId = SCENE_REDEAD, + .checks = { + CHECK(RC_ENEMY_DROP_DEXIHAND, CanKillEnemy(ACTOR_EN_WDHAND)), + }, .connections = { CONNECTION(RR_BENEATH_THE_WELL_THREE_SPIKED_BARS, true), }, @@ -84,6 +89,10 @@ static RegisterShipInitFunc initFunc([]() { }, }; Regions[RR_BENEATH_THE_WELL_FOUR_SPIKED_BARS] = RandoRegion{ .name = "Four Spikes Room", .sceneId = SCENE_REDEAD, + .checks = { + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), + CHECK(RC_ENEMY_DROP_WALLMASTER, CanKillEnemy(ACTOR_EN_WALLMAS)), + }, .connections = { CONNECTION(RR_BENEATH_THE_WELL_BABA_AND_POTS_ROOM, true), CONNECTION(RR_BENEATH_THE_WELL_MIRROR_SHIELD_ROOM, HAS_BOTTLE && CAN_ACCESS(MILK_REFILL) && HAS_ITEM(ITEM_MASK_GIBDO)) @@ -93,10 +102,15 @@ static RegisterShipInitFunc initFunc([]() { } }; Regions[RR_BENEATH_THE_WELL_FREEZARD_ROOM] = RandoRegion{ .name = "Freezard Room", .sceneId = SCENE_REDEAD, + .checks = { + CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), + CHECK(RC_ENEMY_DROP_WALLMASTER, CanKillEnemy(ACTOR_EN_WALLMAS)), + CHECK(RC_ENEMY_DROP_FREEZARD, CanKillEnemy(ACTOR_EN_FZ)), + }, .connections = { CONNECTION(RR_BENEATH_THE_WELL_ENTRANCE, true), CONNECTION(RR_BENEATH_THE_WELL_RIGHT_FIRE_KEESE, HAS_ITEM(ITEM_MASK_GIBDO)), - CONNECTION(RR_BENEATH_THE_WELL_BABA_AND_POTS_ROOM, HAS_BOTTLE && CAN_ACCESS(FISH) && HAS_ITEM(ITEM_MASK_GIBDO)) + CONNECTION(RR_BENEATH_THE_WELL_BABA_AND_POTS_ROOM, HAS_BOTTLE && CAN_ACCESS(FISH) && HAS_ITEM(ITEM_MASK_GIBDO)), }, .events = { EVENT(RE_ACCESS_SPRING_WATER, true), @@ -105,6 +119,8 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_BENEATH_THE_WELL_LEFT_FIRE_KEESE] = RandoRegion{ .name = "Left Fire Keese Room", .sceneId = SCENE_REDEAD, .checks = { CHECK(RC_BENEATH_THE_WELL_KEESE_CHEST, HAS_ITEM(ITEM_LENS_OF_TRUTH) && HAS_MAGIC), + CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), + CHECK(RC_ENEMY_DROP_WALLMASTER, CanKillEnemy(ACTOR_EN_WALLMAS)), }, .connections = { CONNECTION(RR_BENEATH_THE_WELL_TWO_SPIKED_BARS, true), @@ -121,6 +137,9 @@ static RegisterShipInitFunc initFunc([]() { } }; Regions[RR_BENEATH_THE_WELL_RIGHT_FIRE_KEESE] = RandoRegion{ .name = "Right Fire Keese Room", .sceneId = SCENE_REDEAD, + .checks = { + CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), + }, .connections = { CONNECTION(RR_BENEATH_THE_WELL_FREEZARD_ROOM, true), CONNECTION(RR_BENEATH_THE_WELL_BIG_POE_ROOM, HAS_ITEM(ITEM_BOMB) && HAS_ITEM(ITEM_MASK_GIBDO)), @@ -133,6 +152,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_BENEATH_THE_WELL_SKULLTULA_ROOM] = RandoRegion{ .name = "Skulltula Room", .sceneId = SCENE_REDEAD, .checks = { CHECK(RC_BENEATH_THE_WELL_SKULLTULLA_CHEST, CAN_LIGHT_TORCH_NEAR_ANOTHER), + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), }, .connections = { CONNECTION(RR_BENEATH_THE_WELL_BABA_AND_POTS_ROOM, true), @@ -156,6 +176,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_BENEATH_THE_WELL_LEFT_SIDE_POT_03, CAN_LIGHT_TORCH_NEAR_ANOTHER), CHECK(RC_BENEATH_THE_WELL_LEFT_SIDE_POT_04, CAN_LIGHT_TORCH_NEAR_ANOTHER), CHECK(RC_BENEATH_THE_WELL_LEFT_SIDE_POT_05, CAN_LIGHT_TORCH_NEAR_ANOTHER), + CHECK(RC_ENEMY_DROP_WALLMASTER, CanKillEnemy(ACTOR_EN_WALLMAS)), }, .connections = { CONNECTION(RR_BENEATH_THE_WELL_THREE_SPIKED_BARS, true), diff --git a/mm/2s2h/Rando/Logic/Regions/Central.cpp b/mm/2s2h/Rando/Logic/Regions/Central.cpp index d0a84d5650..c04d41eeee 100644 --- a/mm/2s2h/Rando/Logic/Regions/Central.cpp +++ b/mm/2s2h/Rando/Logic/Regions/Central.cpp @@ -25,6 +25,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ASTRAL_OBSERVATORY_PASSAGE_POT_02, true), CHECK(RC_ASTRAL_OBSERVATORY_PASSAGE_POT_03, true), CHECK(RC_ASTRAL_OBSERVATORY_PASSAGE_POT_04, true), + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), }, .exits = { // TO FROM EXIT(ENTRANCE(EAST_CLOCK_TOWN, 2), ENTRANCE(ASTRAL_OBSERVATORY, 0), true), diff --git a/mm/2s2h/Rando/Logic/Regions/East.cpp b/mm/2s2h/Rando/Logic/Regions/East.cpp index 465b0d0da6..e96dede927 100644 --- a/mm/2s2h/Rando/Logic/Regions/East.cpp +++ b/mm/2s2h/Rando/Logic/Regions/East.cpp @@ -20,6 +20,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_BENEATH_THE_GRAVEYARD_DAMPE_POT_08, true), CHECK(RC_BENEATH_THE_GRAVEYARD_DAMPE_POT_09, true), CHECK(RC_BENEATH_THE_GRAVEYARD_DAMPE_POT_10, true), + CHECK(RC_ENEMY_DROP_WALLMASTER, CanKillEnemy(ACTOR_EN_WALLMAS)), }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_GRAVEYARD, 4), ONE_WAY_EXIT, true), @@ -31,6 +32,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_BENEATH_THE_GRAVEYARD_NIGHT_1_BOSS] = RandoRegion{ .name = "Night 1 Boss", .sceneId = SCENE_HAKASHITA, .checks = { CHECK(RC_BENEATH_THE_GRAVEYARD_SONG_OF_STORMS, CanKillEnemy(ACTOR_EN_IK)), + CHECK(RC_ENEMY_DROP_IRON_KNUCKLE, CanKillEnemy(ACTOR_EN_IK)), }, .connections = { CONNECTION(RR_BENEATH_THE_GRAVEYARD_NIGHT_1_GRAVE, CanKillEnemy(ACTOR_EN_IK)), @@ -44,6 +46,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_BENEATH_THE_GRAVEYARD_NIGHT_1_BATS_POT_01, true), CHECK(RC_BENEATH_THE_GRAVEYARD_NIGHT_1_BATS_POT_02, true), CHECK(RC_BENEATH_THE_GRAVEYARD_NIGHT_1_BATS_POT_03, true), + CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT)), }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_GRAVEYARD, 3), ENTRANCE(BENEATH_THE_GRAVERYARD, 1), true), @@ -55,6 +58,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_BENEATH_THE_GRAVEYARD_NIGHT_2_BOSS] = RandoRegion{ .name = "Night 2 Boss", .sceneId = SCENE_HAKASHITA, .checks = { CHECK(RC_BENEATH_THE_GRAVEYARD_PIECE_OF_HEART, CanKillEnemy(ACTOR_EN_IK)), + CHECK(RC_ENEMY_DROP_IRON_KNUCKLE, CanKillEnemy(ACTOR_EN_IK)), }, .connections = { CONNECTION(RR_BENEATH_THE_GRAVEYARD_NIGHT_2_GRAVE_AFTER_PIT, true), @@ -66,6 +70,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_BENEATH_THE_GRAVEYARD_NIGHT_2_AFTER_PIT_POT_02, true), CHECK(RC_BENEATH_THE_GRAVEYARD_NIGHT_2_AFTER_PIT_POT_03, true), CHECK(RC_BENEATH_THE_GRAVEYARD_NIGHT_2_AFTER_PIT_POT_04, true), + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), }, .connections = { CONNECTION(RR_BENEATH_THE_GRAVEYARD_NIGHT_2_BOSS, CAN_USE_EXPLOSIVE), @@ -84,6 +89,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_BENEATH_THE_GRAVEYARD_NIGHT_2_EARLY_POT, true), CHECK(RC_BENEATH_THE_GRAVEYARD_NIGHT_2_BEFORE_PIT_POT_01, true), CHECK(RC_BENEATH_THE_GRAVEYARD_NIGHT_2_BEFORE_PIT_POT_02, true), + CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_GRAVEYARD, 2), ENTRANCE(BENEATH_THE_GRAVERYARD, 0), true), @@ -94,9 +100,8 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_GHOST_HUT] = RandoRegion{ .sceneId = SCENE_TOUGITES, .checks = { - // The first three sisters can be damaged with almost anything, but Meg requires ranged attacks. - // Not using CAN_USE_EXPLOSIVE here, as the Blast Mask cannot reach, and the Powder Keg can only be used once. - CHECK(RC_IKANA_CANYON_GHOST_HUT_PIECE_OF_HEART, CHECK_MAX_HP(4) && (CAN_USE_PROJECTILE || HAS_ITEM(ITEM_BOMB) || HAS_ITEM(ITEM_BOMBCHU))), + CHECK(RC_IKANA_CANYON_GHOST_HUT_PIECE_OF_HEART, CHECK_MAX_HP(4) && CanKillEnemy(ACTOR_EN_PO_SISTERS)), + CHECK(RC_ENEMY_DROP_POE_SISTER, CHECK_MAX_HP(4) && CanKillEnemy(ACTOR_EN_PO_SISTERS)), }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_CANYON, 1), ENTRANCE(GHOST_HUT, 0), true), @@ -127,6 +132,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_IKANA_CANYON_GROTTO_GRASS_12, true), CHECK(RC_IKANA_CANYON_GROTTO_GRASS_13, true), CHECK(RC_IKANA_CANYON_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_IKANA_CANYON_LOWER, true), // TODO: Grotto mapping @@ -137,6 +143,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_IKANA_CANYON_SCRUB_PIECE_OF_HEART, Flags_GetRandoInf(RANDO_INF_OBTAINED_DEED_OCEAN) && CAN_BE_ZORA && CAN_BE_DEKU), CHECK(RC_IKANA_CANYON_SCRUB_HUGE_RUPEE, Flags_GetRandoInf(RANDO_INF_OBTAINED_DEED_OCEAN) && CAN_BE_ZORA), CHECK(RC_IKANA_CANYON_SCRUB_POTION_REFILL, CUR_UPG_VALUE(UPG_WALLET) >= 1), + CHECK(RC_ENEMY_DROP_OCTOROK, CanKillEnemy(ACTOR_EN_OKUTA)), + CHECK(RC_ENEMY_DROP_GARO, CanKillEnemy(ACTOR_EN_JSO)), }, .exits = { // TO FROM EXIT(ENTRANCE(ROAD_TO_IKANA, 1), ENTRANCE(IKANA_CANYON, 0), true), @@ -162,6 +170,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_IKANA_CANYON_OWL_STATUE, CAN_USE_SWORD), CHECK(RC_IKANA_CANYON_TINGLE_MAP_01, CAN_USE_PROJECTILE && CAN_AFFORD(RC_IKANA_CANYON_TINGLE_MAP_01)), CHECK(RC_IKANA_CANYON_TINGLE_MAP_02, CAN_USE_PROJECTILE && CAN_AFFORD(RC_IKANA_CANYON_TINGLE_MAP_02)), + CHECK(RC_ENEMY_DROP_GUAY, CanKillEnemy(ACTOR_EN_CROW)), // Day only }, .exits = { // TO FROM EXIT(ENTRANCE(GHOST_HUT, 0), ENTRANCE(IKANA_CANYON, 1), true), @@ -201,6 +210,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_IKANA_GRAVEYARD_GROTTO_GRASS_12, true), CHECK(RC_IKANA_GRAVEYARD_GROTTO_GRASS_13, true), CHECK(RC_IKANA_GRAVEYARD_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_IKANA_GRAVEYARD_LOWER, true), // TODO: Grotto mapping @@ -217,6 +227,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_IKANA_GRAVEYARD_GRASS_07, true), CHECK(RC_IKANA_GRAVEYARD_GRASS_08, true), CHECK(RC_IKANA_GRAVEYARD_GRASS_09, true), + CHECK(RC_ENEMY_DROP_STALCHILD, CanKillEnemy(ACTOR_EN_SKB)), // Night only + CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT)), // Day only }, .exits = { // TO FROM EXIT(ENTRANCE(ROAD_TO_IKANA, 2), ENTRANCE(IKANA_GRAVEYARD, 0), true), @@ -234,7 +246,10 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_IKANA_GRAVEYARD_UPPER] = RandoRegion{ .name = "Upper", .sceneId = SCENE_BOTI, .checks = { - CHECK(RC_IKANA_GRAVEYARD_CAPTAIN_MASK, true) + CHECK(RC_IKANA_GRAVEYARD_CAPTAIN_MASK, CanKillEnemy(ACTOR_EN_SKB) && CanKillEnemy(ACTOR_EN_BSB)), + CHECK(RC_ENEMY_DROP_STALCHILD, CanKillEnemy(ACTOR_EN_SKB)), + CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT)), // Day only + CHECK(RC_ENEMY_DROP_CAPTAIN_KEETA, CanKillEnemy(ACTOR_EN_SKB) && CanKillEnemy(ACTOR_EN_BSB)), }, .connections = { CONNECTION(RR_IKANA_GRAVEYARD_LOWER, true) @@ -257,6 +272,9 @@ static RegisterShipInitFunc initFunc([]() { }, }; Regions[RR_ROAD_TO_IKANA_ABOVE_LEDGE] = RandoRegion{ .name = "Above Ledge", .sceneId = SCENE_IKANAMAE, + .checks = { + CHECK(RC_ENEMY_DROP_NEJIRON, CanKillEnemy(ACTOR_EN_BAGUO)), // Day only + }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_CANYON, 0), ENTRANCE(ROAD_TO_IKANA, 1), true), }, @@ -268,6 +286,8 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_ROAD_TO_IKANA_POT, CAN_HOOK_SCARECROW), CHECK(RC_ROAD_TO_IKANA_STONE_MASK, HAS_ITEM(ITEM_LENS_OF_TRUTH) && HAS_MAGIC && HAS_BOTTLE && (CAN_ACCESS(RED_POTION_REFILL) || CAN_ACCESS(BLUE_POTION_REFILL))), + CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB)), // Night only + CHECK(RC_ENEMY_DROP_REAL_BOMBCHU, CanKillEnemy(ACTOR_EN_RAT)), // Day only }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_GRAVEYARD, 0), ENTRANCE(ROAD_TO_IKANA, 2), true) @@ -280,6 +300,8 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_ROAD_TO_IKANA_FIELD_SIDE] = RandoRegion{ .name = "Field Side", .sceneId = SCENE_IKANAMAE, .checks = { CHECK(RC_ROAD_TO_IKANA_CHEST, HAS_ITEM(ITEM_HOOKSHOT)), + CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB)), // Night only + CHECK(RC_ENEMY_DROP_REAL_BOMBCHU, CanKillEnemy(ACTOR_EN_RAT)), // Day only }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 4), ENTRANCE(ROAD_TO_IKANA, 0), true), @@ -306,6 +328,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ROAD_TO_IKANA_GROTTO_GRASS_12, true), CHECK(RC_ROAD_TO_IKANA_GROTTO_GRASS_13, true), CHECK(RC_ROAD_TO_IKANA_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_ROAD_TO_IKANA_FIELD_SIDE, true), // TODO: Grotto mapping @@ -318,6 +341,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SAKON_HIDEOUT_SECOND_ROOM_POT_01, true), CHECK(RC_SAKON_HIDEOUT_SECOND_ROOM_POT_02, true), CHECK(RC_SAKON_HIDEOUT_THIRD_ROOM_POT, true), + CHECK(RC_ENEMY_DROP_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA)), + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_EN_WF)), }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_CANYON, 6), ENTRANCE(SAKONS_HIDEOUT, 0), true), @@ -369,6 +394,10 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SECRET_SHRINE_POT_07, (CAN_USE_PROJECTILE && CAN_USE_ABILITY(SWIM)) || CAN_BE_ZORA), CHECK(RC_SECRET_SHRINE_POT_08, (CAN_USE_PROJECTILE && CAN_USE_ABILITY(SWIM)) || CAN_BE_ZORA), CHECK(RC_SECRET_SHRINE_POT_09, (CAN_USE_PROJECTILE && CAN_USE_ABILITY(SWIM)) || CAN_BE_ZORA), + CHECK(RC_ENEMY_DROP_GARO_MASTER, CanKillEnemy(ACTOR_EN_JSO2)), + CHECK(RC_ENEMY_DROP_WIZROBE, CanKillEnemy(ACTOR_EN_WIZ)), + CHECK(RC_ENEMY_DROP_WART, CanKillEnemy(ACTOR_BOSS_04)), + CHECK(RC_ENEMY_DROP_DINOLFOS, CanKillEnemy(ACTOR_EN_DINOFOS)), }, .connections = { CONNECTION(RR_SECRET_SHRINE_ENTRANCE, true), @@ -385,6 +414,7 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_STONE_TOWER_CLIMB_POT_01, HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_STONE_TOWER_CLIMB_POT_02, HAS_ITEM(ITEM_HOOKSHOT)), + CHECK(RC_ENEMY_DROP_BEAMOS, CanKillEnemy(ACTOR_EN_VM)), }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_CANYON, 3), ENTRANCE(STONE_TOWER, 0), true) @@ -440,6 +470,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_LOWER_SCARECROW_POT_10, CAN_HOOK_SCARECROW), CHECK(RC_STONE_TOWER_LOWER_SCARECROW_POT_11, CAN_HOOK_SCARECROW), CHECK(RC_STONE_TOWER_LOWER_SCARECROW_POT_12, CAN_HOOK_SCARECROW), + CHECK(RC_ENEMY_DROP_BEAMOS, CanKillEnemy(ACTOR_EN_VM)), + CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), }, .connections = { CONNECTION(RR_STONE_TOWER_BOTTOM, HAS_ITEM(ITEM_HOOKSHOT) && CAN_PLAY_SONG(ELEGY) && CAN_BE_GORON && CAN_BE_ZORA), @@ -454,6 +486,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_OWL_STATUE_POT_02, true), CHECK(RC_STONE_TOWER_OWL_STATUE_POT_03, true), CHECK(RC_STONE_TOWER_OWL_STATUE_POT_04, true), + CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), }, .exits = { // TO FROM EXIT(ENTRANCE(STONE_TOWER_INVERTED, 0), ENTRANCE(STONE_TOWER, 1), CAN_PLAY_SONG(ELEGY) && HAS_ITEM(ITEM_BOW) && HAS_ITEM(ITEM_ARROW_LIGHT) && HAS_MAGIC), @@ -477,6 +510,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_HIGHER_SCARECROW_POT_07, CAN_HOOK_SCARECROW), CHECK(RC_STONE_TOWER_HIGHER_SCARECROW_POT_08, CAN_HOOK_SCARECROW), CHECK(RC_STONE_TOWER_HIGHER_SCARECROW_POT_09, CAN_HOOK_SCARECROW), + CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), + CHECK(RC_ENEMY_DROP_REDEAD, CanKillEnemy(ACTOR_EN_RD)), }, .connections = { CONNECTION(RR_STONE_TOWER_MIDDLE, HAS_ITEM(ITEM_HOOKSHOT)), diff --git a/mm/2s2h/Rando/Logic/Regions/GreatBayTemple.cpp b/mm/2s2h/Rando/Logic/Regions/GreatBayTemple.cpp index 685c3fad93..5ac3ed0c79 100644 --- a/mm/2s2h/Rando/Logic/Regions/GreatBayTemple.cpp +++ b/mm/2s2h/Rando/Logic/Regions/GreatBayTemple.cpp @@ -10,6 +10,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_GREAT_BAY_TEMPLE_BABA_CHEST_ROOM] = RandoRegion{ .sceneId = SCENE_SEA, .checks = { CHECK(RC_GREAT_BAY_TEMPLE_BABA_CHEST, CAN_BE_ZORA || CAN_USE_PROJECTILE || HAS_ITEM(ITEM_HOOKSHOT)), + CHECK(RC_ENEMY_DROP_BIO_DEKU_BABA, CanKillEnemy(ACTOR_BOSS_05)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_COMPASS_ROOM, true), @@ -30,6 +31,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_BEFORE_WART_POT_10, true), CHECK(RC_GREAT_BAY_TEMPLE_BEFORE_WART_POT_11, true), CHECK(RC_GREAT_BAY_TEMPLE_BEFORE_WART_POT_12, true), + CHECK(RC_ENEMY_DROP_CHUCHU, CanKillEnemy(ACTOR_EN_SLIME)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_RED_PIPE_BEFORE_WART, KEY_COUNT(GREAT_BAY_TEMPLE) >= 1), @@ -92,6 +94,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_SF_COMPASS_ROOM_TUNNEL_POT, CAN_BE_ZORA || (CAN_USE_PROJECTILE && HAS_ITEM(ITEM_MASK_GREAT_FAIRY))), CHECK(RC_GREAT_BAY_TEMPLE_COMPASS_ROOM_TUNNEL_FREESTANDING_RUPEE_01, CAN_BE_ZORA), CHECK(RC_GREAT_BAY_TEMPLE_COMPASS_ROOM_TUNNEL_FREESTANDING_RUPEE_02, CAN_BE_ZORA), + CHECK(RC_ENEMY_DROP_DEXIHAND, CanKillEnemy(ACTOR_EN_WDHAND)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_CENTRAL_ROOM, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), @@ -109,6 +112,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_COMPASS_ROOM_WATER_POT_01, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), CHECK(RC_GREAT_BAY_TEMPLE_COMPASS_ROOM_WATER_POT_02, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), CHECK(RC_GREAT_BAY_TEMPLE_COMPASS_ROOM_WATER_POT_03, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), + CHECK(RC_ENEMY_DROP_BIO_DEKU_BABA, CanKillEnemy(ACTOR_BOSS_05)), + CHECK(RC_ENEMY_DROP_DEXIHAND, CanKillEnemy(ACTOR_EN_WDHAND)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_BABA_CHEST_ROOM, true), @@ -155,6 +160,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_GEKKO_SMALL_CRATE_14, true), CHECK(RC_GREAT_BAY_TEMPLE_GEKKO_SMALL_CRATE_15, true), CHECK(RC_GREAT_BAY_TEMPLE_GEKKO_SMALL_CRATE_16, true), + CHECK(RC_ENEMY_DROP_GEKKO, CanKillEnemy(ACTOR_EN_BIGSLIME)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_COMPASS_ROOM_WITH_BOSS_KEY_CHEST, CanKillEnemy(ACTOR_EN_BIGSLIME)), @@ -172,6 +178,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_GREEN_PIPE_1_BARREL_01, HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_GREAT_BAY_TEMPLE_GREEN_PIPE_1_BARREL_02, HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_GREAT_BAY_TEMPLE_GREEN_PIPE_1_BARREL_03, HAS_ITEM(ITEM_HOOKSHOT)), + CHECK(RC_ENEMY_DROP_TEKTITE, CanKillEnemy(ACTOR_EN_TITE)), + CHECK(RC_ENEMY_DROP_DESBREKO, CanKillEnemy(ACTOR_EN_PR)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_CENTRAL_ROOM, CAN_USE_ABILITY(SWIM)), @@ -194,6 +202,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_GREEN_PIPE_2_POT_06, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), CHECK(RC_GREAT_BAY_TEMPLE_GREEN_PIPE_2_POT_07, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), CHECK(RC_GREAT_BAY_TEMPLE_GREEN_PIPE_2_POT_08, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), + CHECK(RC_ENEMY_DROP_DEXIHAND, CanKillEnemy(ACTOR_EN_WDHAND)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_COMPASS_ROOM, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), @@ -214,6 +223,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_GREEN_PIPE_3_LARGE_CRATE_05, true), CHECK(RC_GREAT_BAY_TEMPLE_GREEN_PIPE_3_LARGE_CRATE_06, true), CHECK(RC_GREAT_BAY_TEMPLE_GREEN_PIPE_3_LARGE_CRATE_07, true), + CHECK(RC_ENEMY_DROP_CHUCHU, CanKillEnemy(ACTOR_EN_SLIME)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_GREEN_PIPE_2, true), @@ -238,6 +248,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_MAP_ROOM_WATER_POT_07, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), CHECK(RC_GREAT_BAY_TEMPLE_MAP_ROOM_WATER_POT_08, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), CHECK(RC_GREAT_BAY_TEMPLE_SF_MAP_ROOM_POT, CAN_BE_ZORA || CAN_USE_MAGIC_ARROW(ICE)), + CHECK(RC_ENEMY_DROP_SKULLFISH, CanKillEnemy(ACTOR_EN_PR2)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_BABA_CHEST_ROOM, CAN_BE_ZORA), @@ -274,6 +285,9 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_RED_PIPE_BEFORE_WART_POT_02, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), CHECK(RC_GREAT_BAY_TEMPLE_RED_PIPE_BEFORE_WART_POT_03, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), CHECK(RC_GREAT_BAY_TEMPLE_RED_PIPE_BEFORE_WART_POT_04, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), + CHECK(RC_ENEMY_DROP_SHELLBLADE, CanKillEnemy(ACTOR_EN_SB)), + CHECK(RC_ENEMY_DROP_OCTOROK, CanKillEnemy(ACTOR_EN_OKUTA)), + CHECK(RC_ENEMY_DROP_SKULLFISH, CanKillEnemy(ACTOR_EN_PR2)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_CENTRAL_ROOM, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), @@ -305,7 +319,7 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_GREAT_BAY_TEMPLE_WART] = RandoRegion{ .sceneId = SCENE_SEA, .checks = { - CHECK(RC_GREAT_BAY_TEMPLE_ICE_ARROW_CHEST, true), + CHECK(RC_GREAT_BAY_TEMPLE_ICE_ARROW_CHEST, CanKillEnemy(ACTOR_BOSS_04)), CHECK(RC_GREAT_BAY_TEMPLE_WART_POT_01, true), CHECK(RC_GREAT_BAY_TEMPLE_WART_POT_02, true), CHECK(RC_GREAT_BAY_TEMPLE_WART_POT_03, true), @@ -314,9 +328,10 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_WART_POT_06, true), CHECK(RC_GREAT_BAY_TEMPLE_WART_POT_07, true), CHECK(RC_GREAT_BAY_TEMPLE_WART_POT_08, true), + CHECK(RC_ENEMY_DROP_WART, CanKillEnemy(ACTOR_BOSS_04)), }, .connections = { - CONNECTION(RR_GREAT_BAY_TEMPLE_BEFORE_WART, true), + CONNECTION(RR_GREAT_BAY_TEMPLE_BEFORE_WART, CanKillEnemy(ACTOR_BOSS_04)), }, }; Regions[RR_GREAT_BAY_TEMPLE_WATER_WHEEL_ROOM] = RandoRegion{ .sceneId = SCENE_SEA, @@ -328,6 +343,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_TEMPLE_WATER_WHEEL_FREESTANDING_RUPEE_03, true), CHECK(RC_GREAT_BAY_TEMPLE_WATER_WHEEL_FREESTANDING_RUPEE_04, true), CHECK(RC_GREAT_BAY_TEMPLE_WATER_WHEEL_FREESTANDING_RUPEE_05, true), + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), }, .connections = { CONNECTION(RR_GREAT_BAY_TEMPLE_CENTRAL_ROOM, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), diff --git a/mm/2s2h/Rando/Logic/Regions/IkanaCastle.cpp b/mm/2s2h/Rando/Logic/Regions/IkanaCastle.cpp index 5ed3c114ee..87cf46421a 100644 --- a/mm/2s2h/Rando/Logic/Regions/IkanaCastle.cpp +++ b/mm/2s2h/Rando/Logic/Regions/IkanaCastle.cpp @@ -19,6 +19,7 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_ANCIENT_CASTLE_OF_IKANA_LEFT_THIRD_ROOM_POT_01, true), CHECK(RC_ANCIENT_CASTLE_OF_IKANA_LEFT_THIRD_ROOM_POT_02, true), + CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB)), }, .connections = { CONNECTION(RR_IKANA_CASTLE_SKULLTULA_ROOM, CAN_USE_SWORD), @@ -37,7 +38,9 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_IKANA_CASTLE_COURTYARD] = RandoRegion{ .name = "Courtyard", .sceneId = SCENE_CASTLE, .checks = { - CHECK(RC_ANCIENT_CASTLE_OF_IKANA_EXTERIOR_POT, true) + CHECK(RC_ANCIENT_CASTLE_OF_IKANA_EXTERIOR_POT, true), + CHECK(RC_ENEMY_DROP_GUAY, CanKillEnemy(ACTOR_EN_CROW)), + CHECK(RC_ENEMY_DROP_GARO, CanKillEnemy(ACTOR_EN_JSO)), }, .exits = { // TO FROM EXIT(ENTRANCE(BENEATH_THE_WELL, 1), ENTRANCE(IKANA_CASTLE, 0), true), @@ -48,6 +51,9 @@ static RegisterShipInitFunc initFunc([]() { }, }; Regions[RR_IKANA_CASTLE_FLOORMASTER_ROOM] = RandoRegion{ .name = "Floormaster Room", .sceneId = SCENE_CASTLE, + .checks = { + CHECK(RC_ENEMY_DROP_FLOORMASTER, CanKillEnemy(ACTOR_EN_FLOORMAS)), + }, .connections = { CONNECTION(RR_IKANA_CASTLE_MAIN_ROOM, true), CONNECTION(RR_IKANA_CASTLE_FLOORMASTER_ROOM_REDEAD_AREA, CAN_USE_MAGIC_ARROW(LIGHT) || (RANDO_EVENTS[RE_IKANA_CASTLE_RIGHT_SUNLIGHT] && (GET_CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) >= EQUIP_VALUE_SHIELD_MIRROR))), @@ -57,12 +63,18 @@ static RegisterShipInitFunc initFunc([]() { } }; Regions[RR_IKANA_CASTLE_FLOORMASTER_ROOM_REDEAD_AREA] = RandoRegion{ .name = "Floormaster Room, Redead Area", .sceneId = SCENE_CASTLE, + .checks = { + CHECK(RC_ENEMY_DROP_REDEAD, CanKillEnemy(ACTOR_EN_RD)), + }, .connections = { CONNECTION(RR_IKANA_CASTLE_FLOORMASTER_ROOM, CAN_USE_MAGIC_ARROW(LIGHT)), CONNECTION(RR_IKANA_CASTLE_WIZZROBE_ROOM, true), }, }; Regions[RR_IKANA_CASTLE_FRONT_ENTRANCE] = RandoRegion{ .name = "Front Entrance", .sceneId = SCENE_CASTLE, + .checks = { + CHECK(RC_ENEMY_DROP_REDEAD, CanKillEnemy(ACTOR_EN_RD)), + }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_CANYON, 8), ENTRANCE(IKANA_CASTLE, 1), true), }, @@ -114,7 +126,8 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_IKANA_CASTLE_REDEAD_WALKWAY] = RandoRegion{ .name = "Redead Walkway", .sceneId = SCENE_CASTLE, .checks = { CHECK(RC_ANCIENT_CASTLE_OF_IKANA_RIGHT_POT_01, true), - CHECK(RC_ANCIENT_CASTLE_OF_IKANA_RIGHT_POT_02, true) + CHECK(RC_ANCIENT_CASTLE_OF_IKANA_RIGHT_POT_02, true), + CHECK(RC_ENEMY_DROP_REDEAD, CanKillEnemy(ACTOR_EN_RD)), }, .connections = { CONNECTION(RR_IKANA_CASTLE_WIZZROBE_ROOM, true), @@ -126,7 +139,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ANCIENT_CASTLE_OF_IKANA_LEFT_SECOND_ROOM_POT_01, CAN_BE_DEKU || HAS_ITEM(ITEM_BOW)), CHECK(RC_ANCIENT_CASTLE_OF_IKANA_LEFT_SECOND_ROOM_POT_02, CAN_BE_DEKU || HAS_ITEM(ITEM_BOW)), CHECK(RC_ANCIENT_CASTLE_OF_IKANA_LEFT_SECOND_ROOM_POT_03, CAN_BE_DEKU || HAS_ITEM(ITEM_BOW)), - CHECK(RC_ANCIENT_CASTLE_OF_IKANA_LEFT_SECOND_ROOM_POT_04, CAN_BE_DEKU || HAS_ITEM(ITEM_BOW)) + CHECK(RC_ANCIENT_CASTLE_OF_IKANA_LEFT_SECOND_ROOM_POT_04, CAN_BE_DEKU || HAS_ITEM(ITEM_BOW)), + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), }, .connections = { CONNECTION(RR_IKANA_CASTLE_CEILING_ROOM, CAN_USE_SWORD || CAN_USE_PROJECTILE), @@ -144,12 +158,16 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ANCIENT_CASTLE_OF_IKANA_BOSS_POT_06, true), CHECK(RC_ANCIENT_CASTLE_OF_IKANA_BOSS_POT_07, true), CHECK(RC_ANCIENT_CASTLE_OF_IKANA_BOSS_POT_08, true), + CHECK(RC_ENEMY_DROP_IGOS_DU_IKANA, CanKillEnemy(ACTOR_EN_KNIGHT)), }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_CASTLE, 6), ENTRANCE(IGOS_DU_IKANAS_LAIR, 0), true) } }; - Regions[RR_IKANA_CASTLE_WIZZROBE_ROOM] = RandoRegion{ .name = "Wizzrobe Room", .sceneId = SCENE_CASTLE, + Regions[RR_IKANA_CASTLE_WIZZROBE_ROOM] = RandoRegion{ .name = "Wizrobe Room", .sceneId = SCENE_CASTLE, + .checks = { + CHECK(RC_ENEMY_DROP_WIZROBE, CanKillEnemy(ACTOR_EN_WIZ)), + }, .connections = { CONNECTION(RR_IKANA_CASTLE_FLOORMASTER_ROOM_REDEAD_AREA, CanKillEnemy(ACTOR_EN_WIZ)), CONNECTION(RR_IKANA_CASTLE_REDEAD_WALKWAY, CanKillEnemy(ACTOR_EN_WIZ)) diff --git a/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp b/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp index 4f32bd1192..8f0de44c5e 100644 --- a/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp +++ b/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp @@ -122,7 +122,7 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_ROMANI_RANCH] = RandoRegion{ .sceneId = SCENE_F01, .checks = { - CHECK(RC_ROMANI_RANCH_ALIENS, HAS_ITEM(ITEM_BOW) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), + CHECK(RC_ROMANI_RANCH_ALIENS, CanKillEnemy(ACTOR_EN_INVADEPOH) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), CHECK(RC_ROMANI_RANCH_EPONAS_SONG, CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), CHECK(RC_ROMANI_RANCH_FIELD_COW_ENTRANCE, CAN_PLAY_SONG(EPONA) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), CHECK(RC_ROMANI_RANCH_FIELD_COW_NEAR_HOUSE_BACK, CAN_PLAY_SONG(EPONA) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), @@ -186,6 +186,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ROMANI_RANCH_GRASS_55, true), CHECK(RC_ROMANI_RANCH_GRASS_56, true), CHECK(RC_ROMANI_RANCH_GRASS_57, true), + CHECK(RC_ENEMY_DROP_ALIEN, CanKillEnemy(ACTOR_EN_INVADEPOH)), // Night 1 only }, .exits = { // TO FROM EXIT(ENTRANCE(MILK_ROAD, 1), ENTRANCE(ROMANI_RANCH, 0), true), diff --git a/mm/2s2h/Rando/Logic/Regions/Miscellaneous.cpp b/mm/2s2h/Rando/Logic/Regions/Miscellaneous.cpp index c4c26e795d..648af2e5ce 100644 --- a/mm/2s2h/Rando/Logic/Regions/Miscellaneous.cpp +++ b/mm/2s2h/Rando/Logic/Regions/Miscellaneous.cpp @@ -8,151 +8,8 @@ using namespace Rando::Logic; // clang-format off static RegisterShipInitFunc initFunc([]() { Regions[RR_MISCELLANEOUS] = RandoRegion{ .name = "Various Regions", .sceneId = SCENE_SPOT00, - .checks = { - CHECK(RC_ENEMY_DROP_ARMOS, CanKillEnemy(ACTOR_EN_AM) && CanReachRegions({ RR_STONE_TOWER_TEMPLE_ARMOS_ROOM })), - CHECK(RC_ENEMY_DROP_BEAMOS, CanKillEnemy(ACTOR_EN_VM) && CanReachRegions({ RR_STONE_TOWER_BOTTOM, - RR_STONE_TOWER_MIDDLE, - RR_STONE_TOWER_TEMPLE_SHALLOW_POOL_ROOM })), - CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB) && CanReachRegions({ RR_TERMINA_FIELD, - RR_ROAD_TO_IKANA_FIELD_SIDE, - RR_ROAD_TO_IKANA_BELOW_LEDGE, - RR_IKANA_CASTLE_BUBBLE_ROOM, - RR_STONE_TOWER_TEMPLE_INVERTED_PATH_TO_GOMESS })), - CHECK(RC_ENEMY_DROP_BIO_DEKU_BABA, CanKillEnemy(ACTOR_BOSS_05) && CanReachRegions({ RR_TERMINA_FIELD_BIO_BABA_GROTTO, - RR_STONE_TOWER_TEMPLE_DEEP_POOL_ROOM, - RR_GREAT_BAY_TEMPLE_COMPASS_ROOM, - RR_GREAT_BAY_TEMPLE_BABA_CHEST_ROOM })), - CHECK(RC_ENEMY_DROP_BOMBCHU, CanKillEnemy(ACTOR_EN_RAT) && CanReachRegions({ RR_TERMINA_FIELD, - RR_ROAD_TO_IKANA_FIELD_SIDE, - RR_ROAD_TO_IKANA_BELOW_LEDGE })), - CHECK(RC_ENEMY_DROP_CHU, CanKillEnemy(ACTOR_EN_SLIME) && CanReachRegions({ RR_TERMINA_FIELD, - RR_ROAD_TO_SOUTHERN_SWAMP, - RR_GREAT_BAY_TEMPLE_BEFORE_WART, - RR_GREAT_BAY_TEMPLE_GREEN_PIPE_3, - RR_STONE_TOWER_TEMPLE_INVERTED_BLOCK_FLIP_ROOM })), - CHECK(RC_ENEMY_DROP_DEATH_ARMOS, CanKillEnemy(ACTOR_EN_FAMOS) && CanReachRegions({ RR_STONE_TOWER_TEMPLE_INVERTED_SIDE_OF_ENTRANCE, - RR_STONE_TOWER_TEMPLE_INVERTED_POE_ROOM })), - CHECK(RC_ENEMY_DROP_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA) && CanReachRegions({ RR_TERMINA_FIELD, - RR_ROAD_TO_SOUTHERN_SWAMP, - RR_SOUTHERN_SWAMP_NEAR_WOODS, - RR_WOODFALL_TEMPLE_MAIN_ROOM, - RR_WOODFALL_TEMPLE_MAZE_ROOM, - RR_BENEATH_THE_WELL_BABA_AND_POTS_ROOM, - RR_SAKON_HIDEOUT })), - CHECK(RC_ENEMY_DROP_DODONGO, CanKillEnemy(ACTOR_EN_DODONGO) && CanReachRegions({ RR_TERMINA_FIELD, - RR_TERMINA_FIELD_DODONGO_GROTTO })), - CHECK(RC_ENEMY_DROP_DRAGONFLY, CanKillEnemy(ACTOR_EN_GRASSHOPPER) && CanReachRegions({ RR_SOUTHERN_SWAMP_SOUTH_UPPER, - RR_WOODFALL, - RR_STONE_TOWER_TEMPLE_ENTRANCE, - RR_STONE_TOWER_TEMPLE_OUTSIDE_SWITCH_ROOM, - RR_WOODFALL_TEMPLE_PRE_BOSS_ROOM, - RR_WOODFALL_TEMPLE_COMPASS_ROOM, - RR_WOODFALL_TEMPLE_MAIN_ROOM_UPPER })), - CHECK(RC_ENEMY_DROP_EENO, CanKillEnemy(ACTOR_EN_SNOWMAN) && CanReachRegions({ RR_TERMINA_FIELD, - RR_SNOWHEAD_TEMPLE_SNOW_ROOM })), - // Flying pots have unreachable drop logic. Excluding for now, pending rework to expand enemy drops - // CHECK(RC_ENEMY_DROP_FLYING_POT, CanKillEnemy(ACTOR_EN_TUBO_TRAP) && CanReachRegions({ RR_STONE_TOWER_TEMPLE_INVERTED_UNDER_BRIDGE, RR_SNOWHEAD_TEMPLE_BLOCK_ROOM })), - CHECK(RC_ENEMY_DROP_FLOORMASTER, CanKillEnemy(ACTOR_EN_FLOORMAS) && CanReachRegions({ RR_IKANA_CASTLE_FLOORMASTER_ROOM })), - // Freezards change their category before dying. Excluding for now, pending rework to expand enemy drops - // CHECK(RC_ENEMY_DROP_FREEZARD, CanKillEnemy(ACTOR_EN_FZ) && CanReachRegions({ RR_SNOWHEAD_TEMPLE_BRIDGE_ROOM_AFTER, RR_SNOWHEAD_TEMPLE_MAP_ROOM_UPPER, RR_SNOWHEAD_TEMPLE_PILLARS_ROOM_UPPER, RR_BENEATH_THE_WELL_FREEZARD_ROOM })), - CHECK(RC_ENEMY_DROP_GUAY, CanKillEnemy(ACTOR_EN_CROW) && CanReachRegions({ RR_IKANA_CANYON_UPPER, - RR_IKANA_CASTLE_COURTYARD, - RR_STONE_TOWER_TEMPLE_OUTSIDE_SWITCH_ROOM }) || - (CanReachRegions({ RR_MOUNTAIN_VILLAGE, - RR_PATH_TO_GORON_VILLAGE }) && - RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]) || - (CanReachRegions({ RR_ZORA_CAPE_OUTSIDE_FAIRY_FOUNTAIN }))), - CHECK(RC_ENEMY_DROP_HIPLOOP, CanKillEnemy(ACTOR_EN_PP) && CanReachRegions({ RR_WOODFALL, - RR_STONE_TOWER_TEMPLE_INVERTED_WIND_ROOM, - RR_STONE_TOWER_TEMPLE_SPIKED_BAR_ROOM_UPPER })), - CHECK(RC_ENEMY_DROP_IRON_KNUCKLE, CanKillEnemy(ACTOR_EN_IK) && CanReachRegions({ RR_BENEATH_THE_GRAVEYARD_NIGHT_1_BOSS, - RR_BENEATH_THE_GRAVEYARD_NIGHT_2_BOSS, - RR_MOON_LINK_TRIAL })), - CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY) && CanReachRegions({ RR_STONE_TOWER_TOP, - RR_STONE_TOWER_UPPER, - RR_STONE_TOWER_MIDDLE, - RR_BENEATH_THE_WELL_LEFT_FIRE_KEESE, - RR_BENEATH_THE_WELL_RIGHT_FIRE_KEESE, - RR_BENEATH_THE_WELL_FREEZARD_ROOM, - RR_BENEATH_THE_GRAVEYARD_NIGHT_2_GRAVE_BEFORE_PIT, - RR_SNOWHEAD_NEAR_TEMPLE, - RR_PATH_TO_SNOWHEAD_LOWER, - RR_PATH_TO_SNOWHEAD_MIDDLE })), - CHECK(RC_ENEMY_DROP_LEEVER, CanKillEnemy(ACTOR_EN_NEO_REEBA) && CanReachRegions({ RR_TERMINA_FIELD, - RR_GREAT_BAY_COAST, - RR_ZORA_CAPE })), - CHECK(RC_ENEMY_DROP_LIKE_LIKE, CanKillEnemy(ACTOR_EN_RR) && CanReachRegions({ RR_ZORA_CAPE, - RR_GREAT_BAY_COAST })), - CHECK(RC_ENEMY_DROP_MAD_SCRUB, CanKillEnemy(ACTOR_EN_DEKUNUTS) && CanReachRegions({ RR_WOODFALL, - RR_DEKU_PALACE_INSIDE_UPPER })), - CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA) && CanReachRegions({ RR_SOUTHERN_SWAMP_NORTH, - RR_SOUTHERN_SWAMP_NEAR_WOODS, - RR_DEKU_PALACE_OUTSIDE, - RR_BENEATH_THE_WELL_BABA_AND_POTS_ROOM, - RR_GREAT_BAY_COAST_FISHERMAN_GROTTO, - RR_IKANA_CANYON_GROTTO, - RR_IKANA_GRAVEYARD_GROTTO, - RR_MOUNTAIN_VILLAGE_TUNNEL_GROTTO, - RR_PATH_TO_GORON_VILLAGE_RAMP_GROTTO, - RR_PATH_TO_SNOWHEAD_GROTTO, - RR_ROAD_TO_IKANA_GROTTO, - RR_ROAD_TO_SOUTHERN_SWAMP_GROTTO, - RR_SOUTHERN_SWAMP_GROTTO, - RR_TERMINA_FIELD_PILLAR_GROTTO, - RR_TERMINA_FIELD_TALL_GRASS_GROTTO, - RR_WOODS_OF_MYSTERY_GROTTO, - RR_ZORA_CAPE_GROTTO })), - CHECK(RC_ENEMY_DROP_NEJIRON, CanKillEnemy(ACTOR_EN_BAGUO) && CanReachRegions({ RR_ROAD_TO_IKANA_ABOVE_LEDGE, - RR_STONE_TOWER_TEMPLE_MIRROR_PILLAR_ROOM })), - CHECK(RC_ENEMY_DROP_OCTOROK, CanKillEnemy(ACTOR_EN_OKUTA) && CanReachRegions({ RR_SOUTHERN_SWAMP_NEAR_FLOWERS, - RR_GREAT_BAY_TEMPLE_RED_PIPE_BEFORE_WART, - RR_IKANA_CANYON_LOWER })), - CHECK(RC_ENEMY_DROP_PEAHAT, CanKillEnemy(ACTOR_EN_PEEHAT) && CanReachRegions({ RR_TERMINA_FIELD_PEAHAT_GROTTO })), - CHECK(RC_ENEMY_DROP_RED_BUBBLE, CanKillEnemy(ACTOR_EN_BBFALL) && CanReachRegions({ RR_SNOWHEAD_TEMPLE_CENTRAL_ROOM_BOTTOM })), - CHECK(RC_ENEMY_DROP_REDEAD, CanKillEnemy(ACTOR_EN_RD) && CanReachRegions({ RR_IKANA_CASTLE_FRONT_ENTRANCE, - RR_IKANA_CASTLE_REDEAD_WALKWAY, - RR_IKANA_CASTLE_FLOORMASTER_ROOM_REDEAD_AREA, - RR_STONE_TOWER_UPPER, })), - CHECK(RC_ENEMY_DROP_SHELLBLADE, CanKillEnemy(ACTOR_EN_SB) && CanReachRegions({ RR_PIRATES_FORTRESS_LEFT_CLAM_EGG_ROOM, - RR_PIRATES_FORTRESS_RIGHT_CLAM_EGG_ROOM, - RR_PIRATES_FORTRESS_CAPTAIN_ROOM, - RR_GREAT_BAY_TEMPLE_RED_PIPE_BEFORE_WART })), - CHECK(RC_ENEMY_DROP_SKULLFISH, CanKillEnemy(ACTOR_EN_PR2) && CanReachRegions({ RR_GREAT_BAY_TEMPLE_MAP_ROOM, - RR_GREAT_BAY_TEMPLE_RED_PIPE_BEFORE_WART })), - CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST) && (CanReachRegions({ RR_ASTRAL_OBSERVATORY_PASSAGE, - RR_TERMINA_FIELD_GOSSIP_STONE_GROTTO_1, - RR_WOODFALL_TEMPLE_ENTRANCE, - RR_WOODFALL_TEMPLE_MAZE_ROOM, - RR_LONE_PEAK_SHRINE, - RR_OCEAN_SPIDER_HOUSE_STORAGE_ROOM, - RR_OCEAN_SPIDER_HOUSE_MEETING_ROOM, - RR_GREAT_BAY_TEMPLE_WATER_WHEEL_ROOM, - RR_BENEATH_THE_WELL_SKULLTULA_ROOM, - RR_BENEATH_THE_WELL_FOUR_SPIKED_BARS, - RR_BENEATH_THE_GRAVEYARD_NIGHT_1_GRAVE, - RR_IKANA_CASTLE_SKULLTULA_ROOM }) || - CanReachRegions({ RR_WOODFALL_TEMPLE_PRE_BOSS_ROOM }) && CAN_BE_DEKU)), - CHECK(RC_ENEMY_DROP_SKULLWALLTULA, CanKillEnemy(ACTOR_EN_SW) && CanReachRegions({ RR_TERMINA_FIELD_GOSSIP_STONE_GROTTO_1 })), - CHECK(RC_ENEMY_DROP_SNAPPER, CanKillEnemy(ACTOR_EN_KAME) && CanReachRegions({ RR_WOODS_OF_MYSTERY, - RR_WOODFALL_TEMPLE_MAP_ROOM })), - CHECK(RC_ENEMY_DROP_STALCHILD, CanKillEnemy(ACTOR_EN_SKB) && CanReachRegions({ RR_IKANA_GRAVEYARD_LOWER, - RR_IKANA_GRAVEYARD_UPPER })), - CHECK(RC_ENEMY_DROP_TEKTITE, CanKillEnemy(ACTOR_EN_TITE) && CanReachRegions({ RR_GORON_VILLAGE, - RR_PATH_TO_MOUNTAIN_VILLAGE_LOWER, - RR_PATH_TO_GORON_VILLAGE, - RR_GREAT_BAY_TEMPLE_GREEN_PIPE_1 })), - CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_EN_WF) && CanReachRegions({ RR_ROAD_TO_SOUTHERN_SWAMP, - RR_PATH_TO_GORON_VILLAGE, - RR_SNOWHEAD_NEAR_TEMPLE, - RR_SNOWHEAD_TEMPLE_ENTRANCE_AFTER_BLOCK, - RR_SNOWHEAD_TEMPLE_COMPASS_ROOM, - RR_SAKON_HIDEOUT })), - CHECK(RC_ENEMY_DROP_WALLMASTER, CanKillEnemy(ACTOR_EN_WALLMAS) && CanReachRegions({ RR_BENEATH_THE_GRAVEYARD_DAMPE, - RR_BENEATH_THE_WELL_FREEZARD_ROOM, - RR_BENEATH_THE_WELL_LEFT_FIRE_KEESE, - RR_BENEATH_THE_WELL_FOUR_SPIKED_BARS, - RR_BENEATH_THE_WELL_TWO_SPIKED_BARS })), - }, + // This region currently directly houses nothing, but enemy drop checks reference the scene ID so that they can + // appear together in the check tracker. }; }, {}); // clang-format on \ No newline at end of file diff --git a/mm/2s2h/Rando/Logic/Regions/Moon.cpp b/mm/2s2h/Rando/Logic/Regions/Moon.cpp index d375554481..21d83996b6 100644 --- a/mm/2s2h/Rando/Logic/Regions/Moon.cpp +++ b/mm/2s2h/Rando/Logic/Regions/Moon.cpp @@ -53,6 +53,10 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_MOON_TRIAL_LINK_POT_07, true), CHECK(RC_MOON_TRIAL_LINK_POT_08, true), CHECK(RC_MOON_TRIAL_LINK_PIECE_OF_HEART, HAS_ITEM(ITEM_HOOKSHOT) && HAS_ITEM(ITEM_BOMBCHU) && HAS_ITEM(ITEM_BOW)), + CHECK(RC_ENEMY_DROP_IRON_KNUCKLE, CanKillEnemy(ACTOR_EN_IK)), + CHECK(RC_ENEMY_DROP_GARO_MASTER, CanKillEnemy(ACTOR_EN_JSO2)), + CHECK(RC_ENEMY_DROP_WIZROBE, CanKillEnemy(ACTOR_EN_WIZ)), + CHECK(RC_ENEMY_DROP_DINOLFOS, CanKillEnemy(ACTOR_EN_DINOFOS)), }, .exits = { // TO FROM EXIT(ENTRANCE(THE_MOON, 0), ENTRANCE(MOON_LINK_TRIAL, 0), true), diff --git a/mm/2s2h/Rando/Logic/Regions/North.cpp b/mm/2s2h/Rando/Logic/Regions/North.cpp index 6cc57fef7d..00e7636849 100644 --- a/mm/2s2h/Rando/Logic/Regions/North.cpp +++ b/mm/2s2h/Rando/Logic/Regions/North.cpp @@ -123,6 +123,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GORON_VILLAGE_SMALL_SNOWBALL_10, true), CHECK(RC_GORON_VILLAGE_SMALL_SNOWBALL_11, true), CHECK(RC_GORON_VILLAGE_SMALL_SNOWBALL_12, true), + CHECK(RC_ENEMY_DROP_TEKTITE, CanKillEnemy(ACTOR_EN_TITE)), }, .exits = { // TO FROM // During First Day a NPC Goron can open the door to the the Shrine @@ -171,6 +172,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_LONE_PEAK_SHRINE_GRASS_22, true), CHECK(RC_LONE_PEAK_SHRINE_GRASS_23, true), CHECK(RC_LONE_PEAK_SHRINE_GRASS_24, true), + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), }, .exits = { // TO FROM EXIT(ENTRANCE(GORON_VILLAGE_WINTER, 3), ENTRANCE(GROTTOS, 16), true) @@ -201,7 +203,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_MOUNTAIN_VILLAGE_TUNNEL_GROTTO_GRASS_11, true), CHECK(RC_MOUNTAIN_VILLAGE_TUNNEL_GROTTO_GRASS_12, true), CHECK(RC_MOUNTAIN_VILLAGE_TUNNEL_GROTTO_GRASS_13, true), - CHECK(RC_MOUNTAIN_VILLAGE_TUNNEL_GROTTO_GRASS_14, true), + CHECK(RC_MOUNTAIN_VILLAGE_TUNNEL_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_MOUNTAIN_VILLAGE, true), // TODO: Grotto mapping @@ -263,6 +266,11 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_09, true), CHECK(RC_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_10, HAS_ITEM(ITEM_LENS_OF_TRUTH) && HAS_MAGIC), CHECK(RC_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_11, HAS_ITEM(ITEM_LENS_OF_TRUTH) && HAS_MAGIC), + CHECK(RC_ENEMY_DROP_GUAY, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && CanKillEnemy(ACTOR_EN_CROW)), + CHECK(RC_ENEMY_DROP_GIANT_BEE, CanKillEnemy(ACTOR_EN_BEE) && RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]), + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK) && RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]), + CHECK(RC_ENEMY_DROP_TEKTITE, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_TITE)), // Day 1 and 3 only + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_WF)), // Day 2 only }, .exits = { // TO FROM EXIT(ENTRANCE(MOUNTAIN_SMITHY, 0), ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 1), true), @@ -307,6 +315,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TWIN_ISLANDS_RAMP_GROTTO_GRASS_12, true), CHECK(RC_TWIN_ISLANDS_RAMP_GROTTO_GRASS_13, true), CHECK(RC_TWIN_ISLANDS_RAMP_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_PATH_TO_GORON_VILLAGE, true), // TODO: Grotto mapping @@ -355,6 +364,10 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TWIN_ISLANDS_SMALL_SNOWBALL_04, CAN_BE_GORON || HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_TWIN_ISLANDS_SMALL_SNOWBALL_05, CAN_BE_GORON || HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_TWIN_ISLANDS_SMALL_SNOWBALL_06, CAN_BE_GORON || HAS_ITEM(ITEM_HOOKSHOT)), + CHECK(RC_ENEMY_DROP_GUAY, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && CanKillEnemy(ACTOR_EN_CROW)), + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_EN_WF)), + CHECK(RC_ENEMY_DROP_TEKTITE, CanKillEnemy(ACTOR_EN_TITE)), + CHECK(RC_ENEMY_DROP_SNAPPER, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_KAME)), }, .exits = { // TO FROM EXIT(ENTRANCE(GROTTOS, 5), ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 0), RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] || CAN_USE_MAGIC_ARROW(FIRE)), // TODO: Grotto mapping Hot spring @@ -380,6 +393,10 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_01, true), CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_02, true), CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_03, true), + CHECK(RC_ENEMY_DROP_TEKTITE, CanKillEnemy(ACTOR_EN_TITE)), + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), // Night only + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_WF)), // Day 2 only + CHECK(RC_ENEMY_DROP_SNAPPER, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_KAME)), // Day 3 only }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 3), ENTRANCE(PATH_TO_MOUNTAIN_VILLAGE, 0), true), @@ -398,6 +415,9 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_10, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_11, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_04, true), + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), // Night only + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_WF)), // Day 2 only + CHECK(RC_ENEMY_DROP_SNAPPER, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_KAME)), // Day 3 only }, .exits = { // TO FROM EXIT(ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 6), ENTRANCE(PATH_TO_MOUNTAIN_VILLAGE, 1), true), @@ -423,12 +443,16 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_PATH_TO_SNOWHEAD_GROTTO_GRASS_12, true), CHECK(RC_PATH_TO_SNOWHEAD_GROTTO_GRASS_13, true), CHECK(RC_PATH_TO_SNOWHEAD_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_PATH_TO_SNOWHEAD_UPPER, true), // TODO: Grotto mapping }, }; Regions[RR_PATH_TO_SNOWHEAD_LOWER] = RandoRegion{ .sceneId = SCENE_14YUKIDAMANOMITI, + .checks = { + CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), + }, .exits = { // TO FROM EXIT(ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 4), ENTRANCE(PATH_TO_SNOWHEAD, 0), true), }, @@ -441,6 +465,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_PATH_TO_SNOWHEAD_PIECE_OF_HEART, HAS_ITEM(ITEM_LENS_OF_TRUTH) && HAS_MAGIC && CAN_HOOK_SCARECROW), CHECK(RC_PATH_TO_SNOWHEAD_LARGE_SNOWBALL_01, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_PATH_TO_SNOWHEAD_LARGE_SNOWBALL_02, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), + CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), }, .connections = { CONNECTION(RR_PATH_TO_SNOWHEAD_LOWER, CAN_BE_GORON && HAS_MAGIC), @@ -497,6 +522,9 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SNOWHEAD_LARGE_SNOWBALL_04, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_SNOWHEAD_LARGE_SNOWBALL_05, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_SNOWHEAD_LARGE_SNOWBALL_06, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), + CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_EN_WF)), + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), // Night only }, .connections = { CONNECTION(RR_SNOWHEAD_NEAR_PATH, true), diff --git a/mm/2s2h/Rando/Logic/Regions/PiratesFortress.cpp b/mm/2s2h/Rando/Logic/Regions/PiratesFortress.cpp index 82d738220d..866f96a356 100644 --- a/mm/2s2h/Rando/Logic/Regions/PiratesFortress.cpp +++ b/mm/2s2h/Rando/Logic/Regions/PiratesFortress.cpp @@ -25,7 +25,8 @@ static RegisterShipInitFunc initFunc([]() { // TODO: Zora Egg Here CHECK(RC_PIRATE_FORTRESS_CAPTAIN_ROOM_BARREL_01, RANDO_EVENTS[RE_PIRATE_FORTRESS_BEEHIVE_HIT]), CHECK(RC_PIRATE_FORTRESS_CAPTAIN_ROOM_BARREL_02, RANDO_EVENTS[RE_PIRATE_FORTRESS_BEEHIVE_HIT]), - CHECK(RC_PIRATE_FORTRESS_INTERIOR_HOOKSHOT_CHEST, RANDO_EVENTS[RE_PIRATE_FORTRESS_BEEHIVE_HIT]) + CHECK(RC_PIRATE_FORTRESS_INTERIOR_HOOKSHOT_CHEST, RANDO_EVENTS[RE_PIRATE_FORTRESS_BEEHIVE_HIT]), + CHECK(RC_ENEMY_DROP_SHELLBLADE, CanKillEnemy(ACTOR_EN_SB)), }, .exits = { // TO FROM EXIT(ENTRANCE(PIRATES_FORTRESS, 1), ENTRANCE(PIRATES_FORTRESS_INTERIOR, 0), true), @@ -54,6 +55,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_PIRATE_FORTRESS_INTERIOR_CHEST_AQUARIUM_POT_01, true), CHECK(RC_PIRATE_FORTRESS_INTERIOR_CHEST_AQUARIUM_POT_02, true), CHECK(RC_PIRATE_FORTRESS_INTERIOR_CHEST_AQUARIUM_POT_03, true), + CHECK(RC_ENEMY_DROP_DESBREKO, CanKillEnemy(ACTOR_EN_PR)), }, .exits = { // TO FROM EXIT(ENTRANCE(PIRATES_FORTRESS, 8), ENTRANCE(PIRATES_FORTRESS_INTERIOR, 7), true) @@ -66,6 +68,9 @@ static RegisterShipInitFunc initFunc([]() { }, }; Regions[RR_PIRATES_FORTRESS_INSIDE_GREEN_GUARD] = RandoRegion{ .name = "Green Guard Room", .sceneId = SCENE_PIRATE, + .checks = { + CHECK(RC_ENEMY_DROP_PIRATE, CanKillEnemy(ACTOR_EN_KAIZOKU)), + }, .exits = { // TO FROM EXIT(ENTRANCE(PIRATES_FORTRESS, 5), ENTRANCE(PIRATES_FORTRESS_INTERIOR, 4), true) }, @@ -97,12 +102,18 @@ static RegisterShipInitFunc initFunc([]() { }, }; Regions[RR_PIRATES_FORTRESS_INSIDE_ORANGE_GUARD] = RandoRegion{ .name = "Orange Guard Room", .sceneId = SCENE_PIRATE, + .checks = { + CHECK(RC_ENEMY_DROP_PIRATE, CanKillEnemy(ACTOR_EN_KAIZOKU)), + }, .connections = { CONNECTION(RR_PIRATES_FORTRESS_INSIDE_LINE_GUARD, CanKillEnemy(ACTOR_EN_KAIZOKU)), CONNECTION(RR_PIRATES_FORTRESS_INSIDE_CHEST_EGG_ROOM, CanKillEnemy(ACTOR_EN_KAIZOKU)), } }; Regions[RR_PIRATES_FORTRESS_INSIDE_PURPLE_GUARD] = RandoRegion{ .name = "Purple Guard Room", .sceneId = SCENE_PIRATE, + .checks = { + CHECK(RC_ENEMY_DROP_PIRATE, CanKillEnemy(ACTOR_EN_KAIZOKU)), + }, .connections = { CONNECTION(RR_PIRATES_FORTRESS_INSIDE_3_GUARD_ROOM, CanKillEnemy(ACTOR_EN_KAIZOKU)), CONNECTION(RR_PIRATES_FORTRESS_LEFT_CLAM_EGG_ROOM, CanKillEnemy(ACTOR_EN_KAIZOKU)), @@ -111,8 +122,9 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_PIRATES_FORTRESS_LEFT_CLAM_EGG_ROOM] = RandoRegion{ .name = "Left Clam Room", .sceneId = SCENE_PIRATE, .checks = { CHECK(RC_PIRATE_FORTRESS_INTERIOR_GUARDED_BARREL, true), - CHECK(RC_PIRATE_FORTRESS_INTERIOR_GUARDED_POT_01, true), - CHECK(RC_PIRATE_FORTRESS_INTERIOR_GUARDED_POT_02, true), + CHECK(RC_PIRATE_FORTRESS_INTERIOR_GUARDED_POT_01, true), + CHECK(RC_PIRATE_FORTRESS_INTERIOR_GUARDED_POT_02, true), + CHECK(RC_ENEMY_DROP_SHELLBLADE, CanKillEnemy(ACTOR_EN_SB)), }, .exits = { // TO FROM EXIT(ENTRANCE(PIRATES_FORTRESS, 4), ENTRANCE(PIRATES_FORTRESS_INTERIOR, 3), true), @@ -275,6 +287,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_PIRATE_FORTRESS_INTERIOR_BARREL_MAZE_POT_01, true), CHECK(RC_PIRATE_FORTRESS_INTERIOR_BARREL_MAZE_POT_02, true), CHECK(RC_PIRATE_FORTRESS_INTERIOR_BARREL_MAZE_POT_03, true), + CHECK(RC_ENEMY_DROP_SHELLBLADE, CanKillEnemy(ACTOR_EN_SB)), }, .exits = { // TO FROM EXIT(ENTRANCE(PIRATES_FORTRESS, 6), ENTRANCE(PIRATES_FORTRESS_INTERIOR, 5), true) diff --git a/mm/2s2h/Rando/Logic/Regions/SnowheadTemple.cpp b/mm/2s2h/Rando/Logic/Regions/SnowheadTemple.cpp index e548f56234..922d8a1da7 100644 --- a/mm/2s2h/Rando/Logic/Regions/SnowheadTemple.cpp +++ b/mm/2s2h/Rando/Logic/Regions/SnowheadTemple.cpp @@ -13,6 +13,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SNOWHEAD_TEMPLE_BLOCK_ROOM_LEDGE_CHEST, HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_SNOWHEAD_TEMPLE_BLOCK_ROOM_POT_01, true), CHECK(RC_SNOWHEAD_TEMPLE_BLOCK_ROOM_POT_02, true), + CHECK(RC_ENEMY_DROP_FLYING_POT, CanKillEnemy(ACTOR_EN_TUBO_TRAP)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_CENTRAL_ROOM_FIRST_FLOOR, true), @@ -57,6 +58,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SNOWHEAD_TEMPLE_BRIDGE_ROOM_AFTER_POT_02, CAN_BE_ZORA || CAN_BE_GORON), CHECK(RC_SNOWHEAD_TEMPLE_SF_BRIDGE_PILLAR, CAN_USE_PROJECTILE && HAS_ITEM(ITEM_MASK_GREAT_FAIRY)), // Accessible from both sides CHECK(RC_SNOWHEAD_TEMPLE_SF_BRIDGE_UNDER_PLATFORM, CAN_USE_PROJECTILE && HAS_ITEM(ITEM_MASK_GREAT_FAIRY)), // Accessible from both sides + CHECK(RC_ENEMY_DROP_FREEZARD, CanKillEnemy(ACTOR_EN_FZ)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_BRIDGE_ROOM_BEFORE, true), @@ -96,6 +98,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SNOWHEAD_TEMPLE_CENTRAL_ROOM_BOTTOM_CHEST, CAN_BE_GORON), CHECK(RC_SNOWHEAD_TEMPLE_CENTRAL_ROOM_BOTTOM_POT_01, true), CHECK(RC_SNOWHEAD_TEMPLE_CENTRAL_ROOM_BOTTOM_POT_02, true), + CHECK(RC_ENEMY_DROP_RED_BUBBLE, CanKillEnemy(ACTOR_EN_BBFALL)) }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_CENTRAL_ROOM_FIRST_FLOOR, true), @@ -182,6 +185,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SNOWHEAD_TEMPLE_COMPASS_ROOM_POT_04, true), CHECK(RC_SNOWHEAD_TEMPLE_COMPASS_ROOM_POT_05, true), CHECK(RC_SNOWHEAD_TEMPLE_SF_COMPASS_ROOM_CRATE, (CAN_USE_EXPLOSIVE && HAS_ITEM(ITEM_MASK_GREAT_FAIRY))), // TODO : Zora Mask can be used from the upper ledge to reach this after breaking the crate. Implement as a trick? + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_EN_WF)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_ENTRANCE_AFTER_BLOCK, KEY_COUNT(SNOWHEAD_TEMPLE) >= 1), @@ -193,6 +197,7 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_SNOWHEAD_TEMPLE_SF_DINOLFOS_01, CanKillEnemy(ACTOR_EN_DINOFOS)), CHECK(RC_SNOWHEAD_TEMPLE_SF_DINOLFOS_02, CanKillEnemy(ACTOR_EN_DINOFOS)), + CHECK(RC_ENEMY_DROP_DINOLFOS, CanKillEnemy(ACTOR_EN_DINOFOS)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_SNOW_ROOM, true), @@ -206,6 +211,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SNOWHEAD_TEMPLE_DUAL_SWITCHES_POT_01, true), CHECK(RC_SNOWHEAD_TEMPLE_DUAL_SWITCHES_POT_02, true), CHECK(RC_SNOWHEAD_TEMPLE_SF_DUAL_SWITCHES, ((HAS_ITEM(ITEM_LENS_OF_TRUTH) && HAS_MAGIC && HAS_ITEM(ITEM_MASK_GREAT_FAIRY)) && ((HAS_ITEM(ITEM_BOW) || HAS_ITEM(ITEM_HOOKSHOT)) || CAN_BE_DEKU))), + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_CENTRAL_ROOM_SECOND_FLOOR, CAN_BE_GORON || CAN_USE_MAGIC_ARROW(FIRE)), @@ -216,6 +222,7 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_SNOWHEAD_TEMPLE_ENTRANCE_POT_01, CAN_BE_GORON), CHECK(RC_SNOWHEAD_TEMPLE_ENTRANCE_POT_02, CAN_BE_GORON), + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_EN_WF)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_CENTRAL_ROOM_FIRST_FLOOR, CAN_USE_MAGIC_ARROW(FIRE)), @@ -225,6 +232,9 @@ static RegisterShipInitFunc initFunc([]() { }, }; Regions[RR_SNOWHEAD_TEMPLE_ENTRANCE_BEFORE_BLOCK] = RandoRegion{ .sceneId = SCENE_HAKUGIN, + .checks = { + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), + }, .exits = { // TO FROM EXIT(ENTRANCE(SNOWHEAD, 1), ENTRANCE(SNOWHEAD_TEMPLE, 0), true), }, @@ -254,6 +264,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_SNOWHEAD_TEMPLE_LOWER_WIZZROBE_ROOM] = RandoRegion{ .sceneId = SCENE_HAKUGIN, .checks = { CHECK(RC_SNOWHEAD_TEMPLE_FIRE_ARROW_CHEST, CanKillEnemy(ACTOR_EN_WIZ)), + CHECK(RC_ENEMY_DROP_WIZROBE, CanKillEnemy(ACTOR_EN_WIZ)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_CENTRAL_ROOM_SECOND_FLOOR, CanKillEnemy(ACTOR_EN_WIZ)), @@ -277,6 +288,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_SNOWHEAD_TEMPLE_MAP_ROOM_UPPER] = RandoRegion{ .sceneId = SCENE_HAKUGIN, .checks = { CHECK(RC_SNOWHEAD_TEMPLE_MAP_ALCOVE_CHEST, (HAS_ITEM(ITEM_LENS_OF_TRUTH) && HAS_MAGIC && HAS_ITEM(ITEM_BOW) && HAS_ITEM(ITEM_ARROW_FIRE))), + CHECK(RC_ENEMY_DROP_FREEZARD, CanKillEnemy(ACTOR_EN_FZ)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_MAP_ROOM_LOWER, true), @@ -307,6 +319,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SNOWHEAD_TEMPLE_PILLARS_ROOM_UPPER_POT_04, (CAN_BE_DEKU || CAN_USE_MAGIC_ARROW(FIRE))), CHECK(RC_SNOWHEAD_TEMPLE_PILLARS_ROOM_UPPER_POT_05, (CAN_BE_DEKU || CAN_USE_MAGIC_ARROW(FIRE))), CHECK(RC_SNOWHEAD_TEMPLE_PILLARS_ROOM_UPPER_POT_06, (CAN_BE_DEKU || CAN_USE_MAGIC_ARROW(FIRE))), + CHECK(RC_ENEMY_DROP_FREEZARD, CanKillEnemy(ACTOR_EN_FZ)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_CENTRAL_ROOM_FIRST_FLOOR_SWITCH_ROOM, CAN_USE_MAGIC_ARROW(FIRE)), @@ -325,6 +338,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SNOWHEAD_TEMPLE_SNOW_ROOM_SMALL_SNOWBALL_06, true), CHECK(RC_SNOWHEAD_TEMPLE_SNOW_ROOM_SMALL_SNOWBALL_07, true), CHECK(RC_SNOWHEAD_TEMPLE_SNOW_ROOM_SMALL_SNOWBALL_08, true), + CHECK(RC_ENEMY_DROP_EENO, CanKillEnemy(ACTOR_EN_SNOWMAN)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_CENTRAL_ROOM_THIRD_FLOOR, KEY_COUNT(SNOWHEAD_TEMPLE) >= 3), @@ -339,6 +353,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SNOWHEAD_TEMPLE_WIZZROBE_POT_03, true), CHECK(RC_SNOWHEAD_TEMPLE_WIZZROBE_POT_04, true), CHECK(RC_SNOWHEAD_TEMPLE_WIZZROBE_POT_05, true), + CHECK(RC_ENEMY_DROP_WIZROBE, CanKillEnemy(ACTOR_EN_WIZ)), }, .connections = { CONNECTION(RR_SNOWHEAD_TEMPLE_CENTRAL_ROOM_THIRD_FLOOR, CanKillEnemy(ACTOR_EN_WIZ)), diff --git a/mm/2s2h/Rando/Logic/Regions/South.cpp b/mm/2s2h/Rando/Logic/Regions/South.cpp index 378177bb55..2b29d7aac3 100644 --- a/mm/2s2h/Rando/Logic/Regions/South.cpp +++ b/mm/2s2h/Rando/Logic/Regions/South.cpp @@ -116,6 +116,7 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_DEKU_PALACE_POT_01, CAN_BE_DEKU), CHECK(RC_DEKU_PALACE_POT_02, CAN_BE_DEKU), + CHECK(RC_ENEMY_DROP_MAD_SCRUB, CanKillEnemy(ACTOR_EN_DEKUNUTS)), }, .exits = { // TO FROM EXIT(ENTRANCE(DEKU_KINGS_CHAMBER, 1), ENTRANCE(DEKU_PALACE, 3), CAN_BE_DEKU), // Cell @@ -125,6 +126,9 @@ static RegisterShipInitFunc initFunc([]() { }, }; Regions[RR_DEKU_PALACE_OUTSIDE] = RandoRegion{ .name = "Outside", .sceneId = SCENE_22DEKUCITY, + .checks = { + CHECK(RC_ENEMY_DROP_MINI_BABA, (CAN_BE_DEKU || (RANDO_EVENTS[RE_CLEARED_WOODFALL_TEMPLE] && CAN_TRAVERSE_WAIST_DEEP_WATER)) && CanKillEnemy(ACTOR_EN_KAREBABA)), + }, .exits = { // TO FROM EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 3), ENTRANCE(DEKU_PALACE, 0), true), EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 4), ENTRANCE(DEKU_PALACE, 5), CAN_BE_DEKU), // Treetop @@ -217,6 +221,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ROAD_TO_SOUTHERN_SWAMP_GROTTO_GRASS_12, true), CHECK(RC_ROAD_TO_SOUTHERN_SWAMP_GROTTO_GRASS_13, true), CHECK(RC_ROAD_TO_SOUTHERN_SWAMP_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_ROAD_TO_SOUTHERN_SWAMP, true), // TODO: Grotto mapping @@ -247,6 +252,10 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ROAD_TO_SOUTHERN_SWAMP_GRASS_18, true), CHECK(RC_ROAD_TO_SOUTHERN_SWAMP_GRASS_19, true), CHECK(RC_ROAD_TO_SOUTHERN_SWAMP_GRASS_20, true), + CHECK(RC_ENEMY_DROP_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA)), + CHECK(RC_ENEMY_DROP_CHUCHU, CanKillEnemy(ACTOR_EN_SLIME)), // Day only + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_EN_WF)), // Night only + CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT)), }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 1), ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 0), true), @@ -278,6 +287,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SOUTHERN_SWAMP_GROTTO_GRASS_12, true), CHECK(RC_SOUTHERN_SWAMP_GROTTO_GRASS_13, true), CHECK(RC_SOUTHERN_SWAMP_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_SOUTHERN_SWAMP_SOUTH, true), // TODO: Grotto mapping @@ -313,6 +323,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SOUTHERN_SWAMP_POISON_GRASS_10, true), CHECK(RC_SOUTHERN_SWAMP_POISON_GRASS_11, true), CHECK(RC_SOUTHERN_SWAMP_POISON_GRASS_12, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .exits = { // TO FROM EXIT(ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 1), ENTRANCE(SOUTHERN_SWAMP_POISONED, 0), true), @@ -338,6 +349,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SOUTHERN_SWAMP_FROG, HAS_ITEM(ITEM_MASK_DON_GERO)), CHECK(RC_SOUTHERN_SWAMP_FREESTANDING_RUPEE_01, CAN_BE_DEKU || CAN_BE_ZORA), CHECK(RC_SOUTHERN_SWAMP_FREESTANDING_RUPEE_02, CAN_BE_DEKU || CAN_BE_ZORA), + CHECK(RC_ENEMY_DROP_OCTOROK, CanKillEnemy(ACTOR_EN_OKUTA) && CAN_TRAVERSE_WAIST_DEEP_WATER), }, .connections = { CONNECTION(RR_SOUTHERN_SWAMP_SOUTH, CanGetPastBigOctoWithoutBoat()), @@ -396,6 +408,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SOUTHERN_SWAMP_POISON_GRASS_28, true), CHECK(RC_SOUTHERN_SWAMP_POISON_GRASS_29, true), CHECK(RC_SOUTHERN_SWAMP_POISON_GRASS_30, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), + CHECK(RC_ENEMY_DROP_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA)), }, .exits = { // TO FROM EXIT(ENTRANCE(MAGIC_HAGS_POTION_SHOP, 0), ENTRANCE(SOUTHERN_SWAMP_POISONED, 5), true), @@ -431,6 +445,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_SOUTHERN_SWAMP_SOUTH_UPPER] = RandoRegion{ .name = "Upper South Section", .sceneId = SCENE_20SICHITAI, .checks = { CHECK(RC_SOUTHERN_SWAMP_SONG_OF_SOARING, CAN_BE_DEKU), + CHECK(RC_ENEMY_DROP_DRAGONFLY, CanKillEnemy(ACTOR_EN_GRASSHOPPER)), }, .exits = { // TO FROM EXIT(ENTRANCE(WOODFALL, 0), ENTRANCE(SOUTHERN_SWAMP_POISONED, 2), CAN_BE_DEKU), @@ -485,6 +500,9 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_WOODFALL_GRASS_04, true), CHECK(RC_WOODFALL_GRASS_05, true), CHECK(RC_WOODFALL_GRASS_06, true), + CHECK(RC_ENEMY_DROP_DRAGONFLY, CanKillEnemy(ACTOR_EN_GRASSHOPPER)), + CHECK(RC_ENEMY_DROP_HIPLOOP, CanKillEnemy(ACTOR_EN_PP)), + CHECK(RC_ENEMY_DROP_MAD_SCRUB, CanKillEnemy(ACTOR_EN_DEKUNUTS)), }, .exits = { // TO FROM EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 2), ENTRANCE(WOODFALL, 0), true), @@ -527,6 +545,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_WOODS_OF_MYSTERY_GROTTO_GRASS_12, true), CHECK(RC_WOODS_OF_MYSTERY_GROTTO_GRASS_13, true), CHECK(RC_WOODS_OF_MYSTERY_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_WOODS_OF_MYSTERY, true), // TODO: Grotto mapping @@ -557,6 +576,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_WOODS_OF_MYSTERY_GRASS_21, true), CHECK(RC_WOODS_OF_MYSTERY_GRASS_22, true), CHECK(RC_WOODS_OF_MYSTERY_GRASS_23, true), + CHECK(RC_ENEMY_DROP_SNAPPER, CAN_BE_DEKU || CanKillEnemy(ACTOR_EN_KAME)), }, .exits = { // TO FROM EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 7), ENTRANCE(WOODS_OF_MYSTERY, 0), true), diff --git a/mm/2s2h/Rando/Logic/Regions/SpiderHouses.cpp b/mm/2s2h/Rando/Logic/Regions/SpiderHouses.cpp index ebaaff6daf..d72701ea6f 100644 --- a/mm/2s2h/Rando/Logic/Regions/SpiderHouses.cpp +++ b/mm/2s2h/Rando/Logic/Regions/SpiderHouses.cpp @@ -23,6 +23,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_OCEAN_SPIDER_HOUSE_MAIN_ROOM_POT_02, true), CHECK(RC_OCEAN_SPIDER_HOUSE_MAIN_ROOM_WEB_POT, true), CHECK(RC_OCEAN_SPIDER_HOUSE_MAIN_ROOM_BOE_POT, true), + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), // In pot }, .connections = { CONNECTION(RR_OCEAN_SPIDER_HOUSE_ENTRANCE_LOWER, HAS_ITEM(ITEM_HOOKSHOT)), @@ -82,6 +83,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_OCEAN_SPIDER_HOUSE_CHEST_PIECE_OF_HEART, HAS_ITEM(ITEM_BOW) && HAS_ITEM(ITEM_MASK_CAPTAIN)), CHECK(RC_OCEAN_SPIDER_HOUSE_COLORED_SKULLS_POT_01, true), CHECK(RC_OCEAN_SPIDER_HOUSE_COLORED_SKULLS_POT_02, true), + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), }, .connections = { CONNECTION(RR_OCEAN_SPIDER_HOUSE_CENTRAL_ROOM, true) @@ -101,6 +103,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_OCEAN_SPIDER_HOUSE_STORAGE_TOP_POT_01, HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_OCEAN_SPIDER_HOUSE_STORAGE_TOP_POT_02, HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_OCEAN_SPIDER_HOUSE_STORAGE_TOP_POT_03, HAS_ITEM(ITEM_HOOKSHOT)), + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), }, .connections = { CONNECTION(RR_OCEAN_SPIDER_HOUSE_CENTRAL_ROOM, true) @@ -121,6 +125,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SWAMP_SPIDER_HOUSE_JAR_ROOM_POT_03, true), CHECK(RC_SWAMP_SPIDER_HOUSE_JAR_ROOM_POT_04, true), CHECK(RC_SWAMP_SPIDER_HOUSE_JAR_ROOM_POT_05, true), + CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE), // In beehive }, .connections = { CONNECTION(RR_SWAMP_SPIDER_HOUSE_CENTER_ROOM_LOWER, true), @@ -253,7 +258,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SWAMP_SKULLTULA_TREE_ROOM_HIVE, CAN_USE_PROJECTILE), CHECK(RC_SWAMP_SKULLTULA_TREE_ROOM_TREE_01, CanKillEnemy(ACTOR_EN_SW)), CHECK(RC_SWAMP_SKULLTULA_TREE_ROOM_TREE_02, CanKillEnemy(ACTOR_EN_SW)), - CHECK(RC_SWAMP_SKULLTULA_TREE_ROOM_TREE_03, CanKillEnemy(ACTOR_EN_SW)) + CHECK(RC_SWAMP_SKULLTULA_TREE_ROOM_TREE_03, CanKillEnemy(ACTOR_EN_SW)), + CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE), // In beehive }, .connections = { CONNECTION(RR_SWAMP_SPIDER_HOUSE_GOLD_ROOM_UPPER, true) diff --git a/mm/2s2h/Rando/Logic/Regions/StoneTowerTemple.cpp b/mm/2s2h/Rando/Logic/Regions/StoneTowerTemple.cpp index 758a0b1fad..fb6bee2a89 100644 --- a/mm/2s2h/Rando/Logic/Regions/StoneTowerTemple.cpp +++ b/mm/2s2h/Rando/Logic/Regions/StoneTowerTemple.cpp @@ -16,6 +16,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_TEMPLE_ENTRANCE_SWITCH_CHEST, RANDO_EVENTS[RE_STONE_TOWER_TEMPLE_ENTRANCE_SWITCH_CHEST]), CHECK(RC_STONE_TOWER_TEMPLE_ENTRANCE_POT_01, true), CHECK(RC_STONE_TOWER_TEMPLE_ENTRANCE_POT_02, true), + CHECK(RC_ENEMY_DROP_DRAGONFLY, CanKillEnemy(ACTOR_EN_GRASSHOPPER)), }, .exits = { // TO FROM EXIT(ENTRANCE(STONE_TOWER, 2), ENTRANCE(STONE_TOWER_TEMPLE, 0), true), @@ -45,6 +46,10 @@ static RegisterShipInitFunc initFunc([]() { }, }; Regions[RR_STONE_TOWER_TEMPLE_OUTSIDE_SWITCH_ROOM] = RandoRegion{ .name = "Outside of Switch Room", .sceneId = SCENE_INISIE_N, + .checks = { + CHECK(RC_ENEMY_DROP_DRAGONFLY, CanKillEnemy(ACTOR_EN_GRASSHOPPER)), + CHECK(RC_ENEMY_DROP_GUAY, CanKillEnemy(ACTOR_EN_CROW)), + }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_SWITCH_ROOM, false), CONNECTION(RR_STONE_TOWER_TEMPLE_ARMOS_ROOM, true), @@ -63,7 +68,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_TEMPLE_LAVA_ROOM_AFTER_BLOCK_POT_01, CAN_BE_GORON && ((CAN_USE_EXPLOSIVE && (GET_CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) >= EQUIP_VALUE_SHIELD_MIRROR)) || CAN_USE_MAGIC_ARROW(LIGHT))), CHECK(RC_STONE_TOWER_TEMPLE_LAVA_ROOM_AFTER_BLOCK_POT_02, CAN_BE_GORON && ((CAN_USE_EXPLOSIVE && (GET_CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) >= EQUIP_VALUE_SHIELD_MIRROR)) || CAN_USE_MAGIC_ARROW(LIGHT))), CHECK(RC_STONE_TOWER_TEMPLE_LAVA_ROOM_AFTER_BLOCK_POT_03, CAN_BE_GORON && ((CAN_USE_EXPLOSIVE && (GET_CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) >= EQUIP_VALUE_SHIELD_MIRROR)) || CAN_USE_MAGIC_ARROW(LIGHT))), - CHECK(RC_STONE_TOWER_TEMPLE_LAVA_ROOM_AFTER_BLOCK_POT_04, CAN_BE_GORON && ((CAN_USE_EXPLOSIVE && (GET_CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) >= EQUIP_VALUE_SHIELD_MIRROR)) || CAN_USE_MAGIC_ARROW(LIGHT))) + CHECK(RC_STONE_TOWER_TEMPLE_LAVA_ROOM_AFTER_BLOCK_POT_04, CAN_BE_GORON && ((CAN_USE_EXPLOSIVE && (GET_CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) >= EQUIP_VALUE_SHIELD_MIRROR)) || CAN_USE_MAGIC_ARROW(LIGHT))), + CHECK(RC_ENEMY_DROP_ARMOS, CanKillEnemy(ACTOR_EN_AM)) }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_OUTSIDE_SWITCH_ROOM, true) @@ -79,6 +85,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_TEMPLE_CENTER_SMALL_CRATE_01, true), CHECK(RC_STONE_TOWER_TEMPLE_CENTER_SMALL_CRATE_02, true), CHECK(RC_STONE_TOWER_TEMPLE_CENTER_SMALL_CRATE_03, true), + CHECK(RC_ENEMY_DROP_BEAMOS, CanKillEnemy(ACTOR_EN_VM)), + CHECK(RC_ENEMY_DROP_DEXIHAND, CanKillEnemy(ACTOR_EN_WDHAND)), }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_OUTSIDE_SWITCH_ROOM, KEY_COUNT(STONE_TOWER_TEMPLE) >= 1), @@ -96,7 +104,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_TEMPLE_WATER_ROOM_UNDERWATER_LOWER_POT_03, CAN_BE_ZORA), CHECK(RC_STONE_TOWER_TEMPLE_WATER_ROOM_UNDERWATER_UPPER_POT_01, CAN_BE_ZORA), CHECK(RC_STONE_TOWER_TEMPLE_WATER_ROOM_UNDERWATER_UPPER_POT_02, CAN_BE_ZORA), - CHECK(RC_STONE_TOWER_TEMPLE_WATER_SUN_SWITCH_CHEST, CAN_BE_ZORA) + CHECK(RC_STONE_TOWER_TEMPLE_WATER_SUN_SWITCH_CHEST, CAN_BE_ZORA), + CHECK(RC_ENEMY_DROP_BIO_DEKU_BABA, CanKillEnemy(ACTOR_BOSS_05)), }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_SHALLOW_POOL_ROOM, CAN_BE_ZORA), @@ -112,6 +121,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_TEMPLE_MIRROR_ROOM_POT_02, true), CHECK(RC_STONE_TOWER_TEMPLE_MIRRORS_ROOM_LARGE_CRATE_01, (CAN_BE_GORON && (GET_CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) >= EQUIP_VALUE_SHIELD_MIRROR)) || CAN_USE_MAGIC_ARROW(LIGHT)), CHECK(RC_STONE_TOWER_TEMPLE_MIRRORS_ROOM_LARGE_CRATE_02, (CAN_BE_GORON && (GET_CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) >= EQUIP_VALUE_SHIELD_MIRROR)) || CAN_USE_MAGIC_ARROW(LIGHT)), + CHECK(RC_ENEMY_DROP_NEJIRON, CanKillEnemy(ACTOR_EN_BAGUO)), + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_DEEP_POOL_ROOM, KEY_COUNT(STONE_TOWER_TEMPLE) >= 2), @@ -143,6 +154,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_STONE_TOWER_TEMPLE_GARO_MASTER_ROOM] = RandoRegion{ .name = "Garo Master Room", .sceneId = SCENE_INISIE_N, .checks = { CHECK(RC_STONE_TOWER_TEMPLE_LIGHT_ARROW_CHEST, CanKillEnemy(ACTOR_EN_JSO2)), + CHECK(RC_ENEMY_DROP_GARO_MASTER, CanKillEnemy(ACTOR_EN_JSO2)), }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_LAVA_WIND_ROOM, CanKillEnemy(ACTOR_EN_JSO2)), @@ -175,6 +187,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_STONE_TOWER_TEMPLE_SPIKED_BAR_ROOM_UPPER] = RandoRegion{ .name = "Spiked Bar Room Upper", .sceneId = SCENE_INISIE_N, .checks = { CHECK(RC_STONE_TOWER_TEMPLE_BEFORE_WATER_BRIDGE_CHEST, CAN_USE_EXPLOSIVE), + CHECK(RC_ENEMY_DROP_HIPLOOP, CanKillEnemy(ACTOR_EN_PP)), }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_GARO_MASTER_ROOM, HAS_ITEM(ITEM_HOOKSHOT)), @@ -184,11 +197,12 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_STONE_TOWER_TEMPLE_BRIDGE] = RandoRegion{ .sceneId = SCENE_INISIE_N, .checks = { - CHECK(RC_STONE_TOWER_TEMPLE_WATER_BRIDGE_CHEST, CAN_USE_PROJECTILE), + CHECK(RC_STONE_TOWER_TEMPLE_WATER_BRIDGE_CHEST, CanKillEnemy(ACTOR_EN_EGOL)), + CHECK(RC_ENEMY_DROP_EYEGORE, CanKillEnemy(ACTOR_EN_EGOL)), }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_SPIKED_BAR_ROOM_UPPER, true), - CONNECTION(RR_STONE_TOWER_TEMPLE_ENTRANCE, CAN_USE_PROJECTILE) + CONNECTION(RR_STONE_TOWER_TEMPLE_ENTRANCE, CanKillEnemy(ACTOR_EN_EGOL)) } }; @@ -226,6 +240,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_WIND_ROOM_FREESTANDING_RUPEE_03, CAN_USE_MAGIC_ARROW(LIGHT)), CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_WIND_ROOM_FREESTANDING_RUPEE_04, CAN_USE_MAGIC_ARROW(LIGHT)), CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_WIND_ROOM_FREESTANDING_RUPEE_05, CAN_USE_MAGIC_ARROW(LIGHT)), + CHECK(RC_ENEMY_DROP_HIPLOOP, CanKillEnemy(ACTOR_EN_PP)), }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_ENTRANCE, true), @@ -239,12 +254,15 @@ static RegisterShipInitFunc initFunc([]() { } }; Regions[RR_STONE_TOWER_TEMPLE_INVERTED_BLOCK_FLIP_ROOM] = RandoRegion{ .name = "Flipped Block Room", .sceneId = SCENE_INISIE_R, + .checks = { + CHECK(RC_ENEMY_DROP_CHUCHU, CanKillEnemy(ACTOR_EN_SLIME)), + }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_LAVA_FLIP_ROOM, CAN_USE_MAGIC_ARROW(LIGHT)), CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_WIZZROBE_ROOM, CAN_USE_MAGIC_ARROW(LIGHT)) } }; - Regions[RR_STONE_TOWER_TEMPLE_INVERTED_WIZZROBE_ROOM] = RandoRegion{ .name = "Wizzrobe Room", .sceneId = SCENE_INISIE_R, + Regions[RR_STONE_TOWER_TEMPLE_INVERTED_WIZZROBE_ROOM] = RandoRegion{ .name = "Wizrobe Room", .sceneId = SCENE_INISIE_R, .checks = { CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_WIZZROBE_CHEST, CanKillEnemy(ACTOR_EN_WIZ) && HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_WIZZROBE_POT_01, HAS_ITEM(ITEM_HOOKSHOT)), @@ -265,6 +283,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_POE_MAZE_SIDE_POT_01, CAN_BE_DEKU), CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_POE_MAZE_SIDE_POT_02, CAN_BE_DEKU), CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_DEATH_ARMOS_CHEST, CAN_BE_DEKU && CAN_PLAY_SONG(ELEGY)), + CHECK(RC_ENEMY_DROP_DEATH_ARMOS, CanKillEnemy(ACTOR_EN_FAMOS)), }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_WIZZROBE_ROOM, true), @@ -272,6 +291,10 @@ static RegisterShipInitFunc initFunc([]() { } }; Regions[RR_STONE_TOWER_TEMPLE_INVERTED_UNDER_BRIDGE] = RandoRegion{ .name = "Under Bridge", .sceneId = SCENE_INISIE_R, + .checks = { + CHECK(RC_ENEMY_DROP_FLYING_POT, CanKillEnemy(ACTOR_EN_TUBO_TRAP)), + // There is a Dexihand here, but there's no reasonable way to kill it and collect its drop. + }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_POE_ROOM, true), CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_PATH_TO_GOMESS, CAN_BE_DEKU && (HAS_ITEM(ITEM_HOOKSHOT) || HAS_ITEM(ITEM_BOW) || CAN_BE_ZORA)), // Deku bubbles can work with TIGHT aim, prob better to not consider that in logic @@ -286,6 +309,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_PATH_TO_GOMESS_SMALL_CRATE_04, true), CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_PATH_TO_GOMESS_SMALL_CRATE_05, true), CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_PATH_TO_GOMESS_SMALL_CRATE_06, true), + CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB)), }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_UNDER_BRIDGE, true), @@ -305,6 +329,9 @@ static RegisterShipInitFunc initFunc([]() { } }; Regions[RR_STONE_TOWER_TEMPLE_INVERTED_SIDE_OF_ENTRANCE] = RandoRegion{ .name = "Side of Entrance", .sceneId = SCENE_INISIE_R, + .checks = { + CHECK(RC_ENEMY_DROP_DEATH_ARMOS, CanKillEnemy(ACTOR_EN_FAMOS)), + }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_UNDER_BRIDGE, true), CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_ENTRANCE, true), @@ -323,11 +350,12 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_STONE_TOWER_TEMPLE_INVERTED_BRIDGE] = RandoRegion{ .name = "Bridge", .sceneId = SCENE_INISIE_R, .checks = { - CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_GIANT_MASK, CAN_USE_PROJECTILE), + CHECK(RC_STONE_TOWER_TEMPLE_INVERTED_GIANT_MASK, CanKillEnemy(ACTOR_EN_EGOL)), + CHECK(RC_ENEMY_DROP_EYEGORE, CanKillEnemy(ACTOR_EN_EGOL)), }, .connections = { CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_ENTRANCE_TOP, KEY_COUNT(STONE_TOWER_TEMPLE) >= 4), - CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_SPIKED_BAR_ROOM_UPPER, CAN_USE_PROJECTILE) + CONNECTION(RR_STONE_TOWER_TEMPLE_INVERTED_SPIKED_BAR_ROOM_UPPER, CanKillEnemy(ACTOR_EN_EGOL)) }, }; Regions[RR_STONE_TOWER_TEMPLE_INVERTED_SPIKED_BAR_ROOM_UPPER] = RandoRegion{ .name = "Spiked Bar Room Upper", .sceneId = SCENE_INISIE_R, diff --git a/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp b/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp index 0eeb63bf6e..4af56e091e 100644 --- a/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp +++ b/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp @@ -28,6 +28,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TERMINA_FIELD_BIO_BABA_GROTTO, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), CHECK(RC_TERMINA_FIELD_BIO_BABA_GROTTO_GRASS_01, true), CHECK(RC_TERMINA_FIELD_BIO_BABA_GROTTO_GRASS_02, true), + CHECK(RC_ENEMY_DROP_BIO_DEKU_BABA, CanKillEnemy(ACTOR_BOSS_05)), }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 11), true), // TODO: Grotto mapping @@ -109,6 +110,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TERMINA_FIELD_COW_GROTTO_GRASS_70, true), CHECK(RC_TERMINA_FIELD_COW_GROTTO_GRASS_71, true), CHECK(RC_TERMINA_FIELD_COW_GROTTO_GRASS_72, true), + CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE), // In a beehive }, .connections = { CONNECTION(RR_TERMINA_FIELD, true), // TODO: Grotto mapping @@ -117,12 +119,17 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_TERMINA_FIELD_DODONGO_GROTTO] = RandoRegion{ .name = "Termina Field Dodongo", .sceneId = SCENE_KAKUSIANA, .checks = { CHECK(RC_TERMINA_FIELD_DODONGO_GROTTO_CHEST, CAN_USE_SWORD || CAN_BE_ZORA || CAN_BE_GORON), + CHECK(RC_ENEMY_DROP_DODONGO, CanKillEnemy(ACTOR_EN_DODONGO)), }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 7), true), // TODO: Grotto mapping }, }; Regions[RR_TERMINA_FIELD_GOSSIP_STONE_GROTTO_1] = RandoRegion{ .name = "Termina Field Gossip Stone #1", .sceneId = SCENE_KAKUSIANA, + .checks = { + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), + CHECK(RC_ENEMY_DROP_SKULLWALLTULA, CanKillEnemy(ACTOR_EN_SW)), + }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 1), true), // TODO: Grotto mapping }, @@ -146,6 +153,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_GRASS_03, true), CHECK(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_GRASS_04, true), CHECK(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_GRASS_05, true), + CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE), // In a beehive }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 0), true), // TODO: Grotto mapping @@ -184,6 +192,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TERMINA_FIELD_PEAHAT_GROTTO_GRASS_10, true), CHECK(RC_TERMINA_FIELD_PEAHAT_GROTTO_GRASS_11, true), CHECK(RC_TERMINA_FIELD_PEAHAT_GROTTO_GRASS_12, true), + CHECK(RC_ENEMY_DROP_PEAHAT, CanKillEnemy(ACTOR_EN_PEEHAT)), }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 13), true), // TODO: Grotto mapping @@ -206,6 +215,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TERMINA_FIELD_PILLAR_GROTTO_GRASS_12, true), CHECK(RC_TERMINA_FIELD_PILLAR_GROTTO_GRASS_13, true), CHECK(RC_TERMINA_FIELD_PILLAR_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_TERMINA_FIELD, true), // TODO: Grotto mapping @@ -238,6 +248,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TERMINA_FIELD_TALL_GRASS_GROTTO_GRASS_12, true), CHECK(RC_TERMINA_FIELD_TALL_GRASS_GROTTO_GRASS_13, true), CHECK(RC_TERMINA_FIELD_TALL_GRASS_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_TERMINA_FIELD, true), // TODO: Grotto mapping @@ -487,6 +498,15 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TERMINA_FIELD_GRASS_214, true), CHECK(RC_TERMINA_FIELD_GRASS_215, true), CHECK(RC_TERMINA_FIELD_GRASS_216, true), + CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB)), // Night only + CHECK(RC_ENEMY_DROP_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA)), + CHECK(RC_ENEMY_DROP_CHUCHU, CanKillEnemy(ACTOR_EN_SLIME)), // Day only + CHECK(RC_ENEMY_DROP_REAL_BOMBCHU, CanKillEnemy(ACTOR_EN_RAT)), // Day only + CHECK(RC_ENEMY_DROP_LEEVER, CanKillEnemy(ACTOR_EN_NEO_REEBA)), + CHECK(RC_ENEMY_DROP_DODONGO, CanKillEnemy(ACTOR_EN_DODONGO)), // Day only + CHECK(RC_ENEMY_DROP_EENO, CanKillEnemy(ACTOR_EN_SNOWMAN)), // Night only + CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT)), + CHECK(RC_ENEMY_DROP_TAKKURI, CanKillEnemy(ACTOR_EN_THIEFBIRD)), }, .exits = { // TO FROM EXIT(ENTRANCE(GROTTOS, 0), ENTRANCE(TERMINA_FIELD, 0), CAN_USE_EXPLOSIVE || CAN_BE_GORON), // TODO: Grotto mapping Gossip Stone #3 diff --git a/mm/2s2h/Rando/Logic/Regions/West.cpp b/mm/2s2h/Rando/Logic/Regions/West.cpp index 355ff7e70b..1732996c28 100644 --- a/mm/2s2h/Rando/Logic/Regions/West.cpp +++ b/mm/2s2h/Rando/Logic/Regions/West.cpp @@ -92,6 +92,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_GRASS_70, true), CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_GRASS_71, true), CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_GRASS_72, true), + CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE), // In a beehive }, .connections = { CONNECTION(RR_GREAT_BAY_COAST_CLIFFSIDE, true), // TODO: Grotto mapping @@ -114,6 +115,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_COAST_FISHERMAN_GROTTO_GRASS_12, true), CHECK(RC_GREAT_BAY_COAST_FISHERMAN_GROTTO_GRASS_13, true), CHECK(RC_GREAT_BAY_COAST_FISHERMAN_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_GREAT_BAY_COAST, true), // TODO: Grotto mapping @@ -130,6 +132,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GREAT_BAY_COAST_GRASS_03, true), CHECK(RC_GREAT_BAY_COAST_GRASS_04, true), CHECK(RC_GREAT_BAY_COAST_GRASS_05, true), + CHECK(RC_ENEMY_DROP_LEEVER, CanKillEnemy(ACTOR_EN_NEO_REEBA)), + CHECK(RC_ENEMY_DROP_LIKE_LIKE, CanKillEnemy(ACTOR_EN_RR)), }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 2), ENTRANCE(GREAT_BAY_COAST, 0), true), @@ -226,19 +230,20 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_PINNACLE_ROCK_INNER] = RandoRegion{ .name = "Inner", .sceneId = SCENE_SINKAI, .checks = { CHECK(RC_PINNACLE_ROCK_CHEST_01, CAN_BE_ZORA), - CHECK(RC_PINNACLE_ROCK_CHEST_02, CAN_BE_ZORA && HAS_MAGIC), - CHECK(RC_PINNACLE_ROCK_POT_01, CAN_BE_ZORA && HAS_MAGIC), - CHECK(RC_PINNACLE_ROCK_POT_02, CAN_BE_ZORA && HAS_MAGIC), - CHECK(RC_PINNACLE_ROCK_POT_03, CAN_BE_ZORA && HAS_MAGIC), - CHECK(RC_PINNACLE_ROCK_POT_04, CAN_BE_ZORA && HAS_MAGIC), - CHECK(RC_PINNACLE_ROCK_POT_05, CAN_BE_ZORA && HAS_MAGIC), + CHECK(RC_PINNACLE_ROCK_CHEST_02, CanKillEnemy(ACTOR_EN_DRAGON)), + CHECK(RC_PINNACLE_ROCK_POT_01, CanKillEnemy(ACTOR_EN_DRAGON)), + CHECK(RC_PINNACLE_ROCK_POT_02, CanKillEnemy(ACTOR_EN_DRAGON)), + CHECK(RC_PINNACLE_ROCK_POT_03, CanKillEnemy(ACTOR_EN_DRAGON)), + CHECK(RC_PINNACLE_ROCK_POT_04, CanKillEnemy(ACTOR_EN_DRAGON)), + CHECK(RC_PINNACLE_ROCK_POT_05, CanKillEnemy(ACTOR_EN_DRAGON)), CHECK(RC_PINNACLE_ROCK_POT_06, CAN_BE_ZORA), CHECK(RC_PINNACLE_ROCK_POT_07, CAN_BE_ZORA), CHECK(RC_PINNACLE_ROCK_POT_08, CAN_BE_ZORA), CHECK(RC_PINNACLE_ROCK_POT_09, CAN_BE_ZORA), CHECK(RC_PINNACLE_ROCK_POT_10, CAN_BE_ZORA), CHECK(RC_PINNACLE_ROCK_POT_11, CAN_BE_ZORA), - CHECK(RC_PINNACLE_ROCK_REUNITE_SEAHORSE, CAN_BE_ZORA && HAS_MAGIC && RANDO_EVENTS[RE_ACCESS_SEAHORSE]), + CHECK(RC_PINNACLE_ROCK_REUNITE_SEAHORSE, CanKillEnemy(ACTOR_EN_DRAGON) && RANDO_EVENTS[RE_ACCESS_SEAHORSE]), + CHECK(RC_ENEMY_DROP_DEEP_PYTHON, CanKillEnemy(ACTOR_EN_DRAGON)), }, .connections = { CONNECTION(RR_PINNACLE_ROCK_ENTRANCE, CAN_USE_ABILITY(SWIM)) @@ -295,12 +300,16 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ZORA_CAPE_GROTTO_GRASS_12, true), CHECK(RC_ZORA_CAPE_GROTTO_GRASS_13, true), CHECK(RC_ZORA_CAPE_GROTTO_GRASS_14, true), + CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { CONNECTION(RR_ZORA_CAPE, true), // TODO: Grotto mapping }, }; Regions[RR_ZORA_CAPE_OUTSIDE_FAIRY_FOUNTAIN] = RandoRegion{ .sceneId = SCENE_31MISAKI, + .checks = { + CHECK(RC_ENEMY_DROP_GUAY, CanKillEnemy(ACTOR_EN_CROW)), + }, .exits = { // TO FROM EXIT(ENTRANCE(FAIRY_FOUNTAIN, 3), ENTRANCE(ZORA_CAPE, 5), CAN_USE_EXPLOSIVE), }, @@ -312,10 +321,12 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_ZORA_CAPE_LEDGE_CHEST_01, HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_ZORA_CAPE_LEDGE_CHEST_02, HAS_ITEM(ITEM_HOOKSHOT)), - CHECK(RC_ZORA_CAPE_UNDERWATER_CHEST, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), - CHECK(RC_ZORA_CAPE_WATERFALL_PIECE_OF_HEART, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), + CHECK(RC_ZORA_CAPE_UNDERWATER_CHEST, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), + CHECK(RC_ZORA_CAPE_WATERFALL_PIECE_OF_HEART, CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)), CHECK(RC_ZORA_CAPE_NEAR_BEAVERS_POT_01, true), CHECK(RC_ZORA_CAPE_NEAR_BEAVERS_POT_02, true), + CHECK(RC_ENEMY_DROP_LEEVER, CanKillEnemy(ACTOR_EN_NEO_REEBA)), + CHECK(RC_ENEMY_DROP_LIKE_LIKE, CanKillEnemy(ACTOR_EN_RR)), }, .exits = { // TO FROM EXIT(ENTRANCE(GREAT_BAY_COAST, 1), ENTRANCE(ZORA_CAPE, 0), true), diff --git a/mm/2s2h/Rando/Logic/Regions/WoodfallTemple.cpp b/mm/2s2h/Rando/Logic/Regions/WoodfallTemple.cpp index fc38379552..e77788279e 100644 --- a/mm/2s2h/Rando/Logic/Regions/WoodfallTemple.cpp +++ b/mm/2s2h/Rando/Logic/Regions/WoodfallTemple.cpp @@ -15,6 +15,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_WOODFALL_TEMPLE_MINIBOSS_ROOM_POT_02, CanKillEnemy(ACTOR_EN_PAMETFROG)), CHECK(RC_WOODFALL_TEMPLE_MINIBOSS_ROOM_POT_03, CanKillEnemy(ACTOR_EN_PAMETFROG)), CHECK(RC_WOODFALL_TEMPLE_MINIBOSS_ROOM_POT_04, CanKillEnemy(ACTOR_EN_PAMETFROG)), + CHECK(RC_ENEMY_DROP_SNAPPER, CanKillEnemy(ACTOR_EN_PAMETFROG)), + CHECK(RC_ENEMY_DROP_GEKKO, CanKillEnemy(ACTOR_EN_PAMETFROG)), }, .connections = { CONNECTION(RR_WOODFALL_TEMPLE_WATER_ROOM_UPPER, true), @@ -39,6 +41,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_WOODFALL_TEMPLE_BOW_ROOM] = RandoRegion{ .name = "Bow Room", .sceneId = SCENE_MITURIN, .checks = { CHECK(RC_WOODFALL_TEMPLE_BOW_CHEST, CanKillEnemy(ACTOR_EN_DINOFOS)), + CHECK(RC_ENEMY_DROP_DINOLFOS, CanKillEnemy(ACTOR_EN_DINOFOS)), }, .connections = { CONNECTION(RR_WOODFALL_TEMPLE_WATER_ROOM_UPPER, true), @@ -47,6 +50,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_WOODFALL_TEMPLE_COMPASS_ROOM] = RandoRegion{ .name = "Compass Room", .sceneId = SCENE_MITURIN, .checks = { CHECK(RC_WOODFALL_TEMPLE_COMPASS_CHEST, CanKillEnemy(ACTOR_EN_GRASSHOPPER)), + CHECK(RC_ENEMY_DROP_DRAGONFLY, CanKillEnemy(ACTOR_EN_GRASSHOPPER)), }, .connections = { CONNECTION(RR_WOODFALL_TEMPLE_MAZE_ROOM, true), @@ -55,6 +59,7 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_WOODFALL_TEMPLE_DARK_ROOM] = RandoRegion{ .name = "Dark Room", .sceneId = SCENE_MITURIN, .checks = { CHECK(RC_WOODFALL_TEMPLE_DARK_CHEST, CanKillEnemy(ACTOR_EN_MKK)), + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), }, .connections = { CONNECTION(RR_WOODFALL_TEMPLE_MAIN_ROOM_UPPER, CAN_BE_DEKU && CAN_LIGHT_TORCH_NEAR_ANOTHER), @@ -77,6 +82,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_WOODFALL_TEMPLE_ENTRANCE_CHEST, CAN_BE_DEKU || HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_WOODFALL_TEMPLE_SF_ENTRANCE, CAN_USE_PROJECTILE), CHECK(RC_WOODFALL_TEMPLE_ENTRANCE_POT, CAN_BE_DEKU), + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), }, .exits = { // TO FROM EXIT(ENTRANCE(WOODFALL, 1), ENTRANCE(WOODFALL_TEMPLE, 0), true), @@ -114,6 +121,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_WOODFALL_TEMPLE_SF_MAIN_POT, true), CHECK(RC_WOODFALL_TEMPLE_SF_MAIN_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA)), CHECK(RC_WOODFALL_TEMPLE_SF_MAIN_BUBBLE, (HAS_ITEM(ITEM_BOW) || HAS_ITEM(ITEM_HOOKSHOT))), + CHECK(RC_ENEMY_DROP_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA)), }, .connections = { CONNECTION(RR_WOODFALL_TEMPLE_ENTRANCE, true), @@ -124,7 +132,8 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_WOODFALL_TEMPLE_MAP_ROOM] = RandoRegion{ .name = "Map Room", .sceneId = SCENE_MITURIN, .checks = { - CHECK(RC_WOODFALL_TEMPLE_MAP_CHEST, CanKillEnemy(ACTOR_EN_BIGPAMET)), + CHECK(RC_WOODFALL_TEMPLE_MAP_CHEST, CAN_BE_DEKU || CanKillEnemy(ACTOR_EN_KAME)), + CHECK(RC_ENEMY_DROP_SNAPPER, CAN_BE_DEKU || CanKillEnemy(ACTOR_EN_KAME)), }, .connections = { CONNECTION(RR_WOODFALL_TEMPLE_WATER_ROOM, true), @@ -138,8 +147,9 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_WOODFALL_TEMPLE_SF_MAZE_BEEHIVE, CAN_USE_PROJECTILE || CAN_USE_EXPLOSIVE), // TODO: Maybe add a health check here later CHECK(RC_WOODFALL_TEMPLE_SF_MAZE_BUBBLE, true), - // CanKillEnemy(ACTOR_EN_ST)? - DONE CHECK(RC_WOODFALL_TEMPLE_SF_MAZE_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), + CHECK(RC_ENEMY_DROP_SKULLTULA, CanKillEnemy(ACTOR_EN_ST)), + CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE), // In a beehive }, .connections = { CONNECTION(RR_WOODFALL_TEMPLE_MAIN_ROOM, KEY_COUNT(WOODFALL_TEMPLE) >= 1), @@ -161,6 +171,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_WOODFALL_TEMPLE_PRE_BOSS_FREESTANDING_RUPEE_04, CAN_BE_DEKU || CAN_BE_ZORA), CHECK(RC_WOODFALL_TEMPLE_PRE_BOSS_FREESTANDING_RUPEE_05, CAN_BE_DEKU && HAS_ITEM(ITEM_BOW) || CAN_BE_ZORA || CAN_USE_EXPLOSIVE), CHECK(RC_WOODFALL_TEMPLE_PRE_BOSS_FREESTANDING_RUPEE_06, CAN_BE_DEKU && (CAN_BE_ZORA || CAN_USE_EXPLOSIVE)), + CHECK(RC_ENEMY_DROP_SKULLTULA, CAN_BE_DEKU && CanKillEnemy(ACTOR_EN_ST)), + CHECK(RC_ENEMY_DROP_DRAGONFLY, CanKillEnemy(ACTOR_EN_GRASSHOPPER)), }, .exits = { // TO FROM EXIT(ENTRANCE(ODOLWAS_LAIR, 0), ONE_WAY_EXIT, CAN_BE_DEKU && HAS_ITEM(ITEM_BOW) && CHECK_DUNGEON_ITEM(DUNGEON_BOSS_KEY, DUNGEON_SCENE_INDEX_WOODFALL_TEMPLE)), diff --git a/mm/2s2h/Rando/StaticData/Checks.cpp b/mm/2s2h/Rando/StaticData/Checks.cpp index 74cc8ab249..b258c6cfa3 100644 --- a/mm/2s2h/Rando/StaticData/Checks.cpp +++ b/mm/2s2h/Rando/StaticData/Checks.cpp @@ -2233,42 +2233,61 @@ std::map Checks = { RC(RC_ZORA_CAPE_GROTTO_GRASS_14, RCTYPE_GRASS, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_JUNK), // Enemy Drops - RC(RC_ENEMY_DROP_ARMOS, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_BEAMOS, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_ALIEN, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_ARROWS_30), + RC(RC_ENEMY_DROP_ARMOS, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_BAD_BAT, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_BEAMOS, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), RC(RC_ENEMY_DROP_BIO_DEKU_BABA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_BLUE_BUBBLE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_BOMBCHU, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_CHU, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_DEATH_ARMOS, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_DEKU_BABA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_DODONGO, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_DRAGONFLY, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_EENO, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - // RC(RC_ENEMY_DROP_FLYING_POT, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_FLOORMASTER, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - // RC(RC_ENEMY_DROP_FREEZARD, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_GUAY, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_HIPLOOP, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_IRON_KNUCKLE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_KEESE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_LEEVER, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_LIKE_LIKE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_MAD_SCRUB, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_MINI_BABA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_NEJIRON, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_OCTOROK, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_PEAHAT, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_RED_BUBBLE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_REDEAD, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_SHELLBLADE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_SKULLFISH, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_SKULLTULA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_SKULLWALLTULA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_SNAPPER, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_STALCHILD, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_TEKTITE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_WOLFOS, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), - RC(RC_ENEMY_DROP_WALLMASTER, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_BLUE_BUBBLE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_BOE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_REAL_BOMBCHU, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_CAPTAIN_KEETA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_CHUCHU, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_DEATH_ARMOS, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_DEEP_PYTHON, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_DEKU_BABA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_DESBREKO, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_DEXIHAND, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_DINOLFOS, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_DODONGO, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_DRAGONFLY, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_EENO, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_EYEGORE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_FLYING_POT, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_FLOORMASTER, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_FREEZARD, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_GARO, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_GARO_MASTER, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_GEKKO, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_GIANT_BEE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_GUAY, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_HIPLOOP, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_IGOS_DU_IKANA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_IRON_KNUCKLE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_KEESE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_LEEVER, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_LIKE_LIKE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_MAD_SCRUB, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_MINI_BABA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_DEKU_NUT), + RC(RC_ENEMY_DROP_NEJIRON, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_OCTOROK, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_PEAHAT, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_PIRATE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_POE_SISTER, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_RED_BUBBLE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_REDEAD, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_SHELLBLADE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_SKULLFISH, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_SKULLTULA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_SKULLWALLTULA, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_SNAPPER, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_STALCHILD, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_TAKKURI, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_RUPEE_HUGE), + RC(RC_ENEMY_DROP_TEKTITE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_WALLMASTER, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), + RC(RC_ENEMY_DROP_WART, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_WIZROBE, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_NONE), + RC(RC_ENEMY_DROP_WOLFOS, RCTYPE_ENEMY_DROP, SCENE_SPOT00, FLAG_NONE, 0x0, RI_JUNK), }; // clang-format on diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h index d9bb50242c..7e330300f8 100644 --- a/mm/2s2h/Rando/Types.h +++ b/mm/2s2h/Rando/Types.h @@ -2237,22 +2237,35 @@ typedef enum { RC_ZORA_CAPE_GROTTO_GRASS_13, RC_ZORA_CAPE_GROTTO_GRASS_14, + RC_ENEMY_DROP_ALIEN, RC_ENEMY_DROP_ARMOS, + RC_ENEMY_DROP_BAD_BAT, RC_ENEMY_DROP_BEAMOS, RC_ENEMY_DROP_BIO_DEKU_BABA, RC_ENEMY_DROP_BLUE_BUBBLE, - RC_ENEMY_DROP_BOMBCHU, - RC_ENEMY_DROP_CHU, + RC_ENEMY_DROP_BOE, + RC_ENEMY_DROP_CAPTAIN_KEETA, + RC_ENEMY_DROP_CHUCHU, RC_ENEMY_DROP_DEATH_ARMOS, + RC_ENEMY_DROP_DEEP_PYTHON, RC_ENEMY_DROP_DEKU_BABA, + RC_ENEMY_DROP_DESBREKO, + RC_ENEMY_DROP_DEXIHAND, + RC_ENEMY_DROP_DINOLFOS, RC_ENEMY_DROP_DODONGO, RC_ENEMY_DROP_DRAGONFLY, RC_ENEMY_DROP_EENO, + RC_ENEMY_DROP_EYEGORE, RC_ENEMY_DROP_FLYING_POT, RC_ENEMY_DROP_FLOORMASTER, RC_ENEMY_DROP_FREEZARD, + RC_ENEMY_DROP_GARO, + RC_ENEMY_DROP_GARO_MASTER, + RC_ENEMY_DROP_GEKKO, + RC_ENEMY_DROP_GIANT_BEE, RC_ENEMY_DROP_GUAY, RC_ENEMY_DROP_HIPLOOP, + RC_ENEMY_DROP_IGOS_DU_IKANA, RC_ENEMY_DROP_IRON_KNUCKLE, RC_ENEMY_DROP_KEESE, RC_ENEMY_DROP_LEEVER, @@ -2262,6 +2275,9 @@ typedef enum { RC_ENEMY_DROP_NEJIRON, RC_ENEMY_DROP_OCTOROK, RC_ENEMY_DROP_PEAHAT, + RC_ENEMY_DROP_PIRATE, + RC_ENEMY_DROP_POE_SISTER, + RC_ENEMY_DROP_REAL_BOMBCHU, RC_ENEMY_DROP_RED_BUBBLE, RC_ENEMY_DROP_REDEAD, RC_ENEMY_DROP_SHELLBLADE, @@ -2270,9 +2286,12 @@ typedef enum { RC_ENEMY_DROP_SKULLWALLTULA, RC_ENEMY_DROP_SNAPPER, RC_ENEMY_DROP_STALCHILD, + RC_ENEMY_DROP_TAKKURI, RC_ENEMY_DROP_TEKTITE, - RC_ENEMY_DROP_WOLFOS, RC_ENEMY_DROP_WALLMASTER, + RC_ENEMY_DROP_WART, + RC_ENEMY_DROP_WIZROBE, + RC_ENEMY_DROP_WOLFOS, RC_MAX, } RandoCheckId; diff --git a/mm/src/code/z_en_item00.c b/mm/src/code/z_en_item00.c index 3af9d8133b..b027971d1d 100644 --- a/mm/src/code/z_en_item00.c +++ b/mm/src/code/z_en_item00.c @@ -929,7 +929,7 @@ Actor* Item_DropCollectible(PlayState* play, Vec3f* spawnPos, u32 params) { s32 paramFF = params & 0xFF; s32 i; - if ((GameInteractor_Should(VB_ENEMY_DROP_COLLECTIBLE, true, *spawnPos))) { + if ((GameInteractor_Should(VB_ENEMY_DROP_COLLECTIBLE, true, *spawnPos, params))) { params &= 0x7FFF; newParamFF = params & 0xFF; @@ -1337,7 +1337,7 @@ u8 sDropTableAmounts[DROP_TABLE_SIZE * DROP_TABLE_NUMBER] = { }; void Item_DropCollectibleRandom(PlayState* play, Actor* fromActor, Vec3f* spawnPos, s16 params) { - if (!(GameInteractor_Should(VB_ENEMY_DROP_COLLECTIBLE, true, *spawnPos))) { + if (!(GameInteractor_Should(VB_ENEMY_DROP_COLLECTIBLE, true, *spawnPos, params))) { return; } From d80c046d0d8aeab06b0519cad2e9f690142cf765 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Sat, 13 Dec 2025 09:58:17 -0500 Subject: [PATCH 12/44] Add build folder to `.gitignore` (#1393) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 69dc3c2eca..180a9fe42b 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,5 @@ _packages/ /mm/src/boot/build.c /mm/windows/properties.h /clang-format.exe + +build/ From 4e22b5cde95f6b81e2d1db9c2c1d3b622bbb16b3 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Sat, 13 Dec 2025 09:58:29 -0500 Subject: [PATCH 13/44] Swordsman pots need human sword (#1394) --- mm/2s2h/Rando/Logic/Regions/Central.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mm/2s2h/Rando/Logic/Regions/Central.cpp b/mm/2s2h/Rando/Logic/Regions/Central.cpp index d0a84d5650..84d309f9c6 100644 --- a/mm/2s2h/Rando/Logic/Regions/Central.cpp +++ b/mm/2s2h/Rando/Logic/Regions/Central.cpp @@ -299,11 +299,11 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_SWORDSMAN_SCHOOL] = RandoRegion{ .sceneId = SCENE_DOUJOU, .checks = { CHECK(RC_SWORDSMAN_SCHOOL_PIECE_OF_HEART, CAN_USE_HUMAN_SWORD), - CHECK(RC_SWORDSMAN_SCHOOL_POT_01, CAN_USE_SWORD), - CHECK(RC_SWORDSMAN_SCHOOL_POT_02, CAN_USE_SWORD), - CHECK(RC_SWORDSMAN_SCHOOL_POT_03, CAN_USE_SWORD), - CHECK(RC_SWORDSMAN_SCHOOL_POT_04, CAN_USE_SWORD), - CHECK(RC_SWORDSMAN_SCHOOL_POT_05, CAN_USE_SWORD), + CHECK(RC_SWORDSMAN_SCHOOL_POT_01, CAN_USE_HUMAN_SWORD), + CHECK(RC_SWORDSMAN_SCHOOL_POT_02, CAN_USE_HUMAN_SWORD), + CHECK(RC_SWORDSMAN_SCHOOL_POT_03, CAN_USE_HUMAN_SWORD), + CHECK(RC_SWORDSMAN_SCHOOL_POT_04, CAN_USE_HUMAN_SWORD), + CHECK(RC_SWORDSMAN_SCHOOL_POT_05, CAN_USE_HUMAN_SWORD), }, .exits = { // TO FROM EXIT(ENTRANCE(WEST_CLOCK_TOWN, 3), ENTRANCE(SWORDMANS_SCHOOL, 0), true), From a2e8b62def688528635da207f99b390b367c7ba8 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:21:09 -0500 Subject: [PATCH 14/44] Fix cow softlock when cows are not shuffled (#1396) * Fix cow softlock when cows are not shuffled * Make the cow hooks themselves conditional --- mm/2s2h/Rando/ActorBehavior/EnCow.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mm/2s2h/Rando/ActorBehavior/EnCow.cpp b/mm/2s2h/Rando/ActorBehavior/EnCow.cpp index cbd93de7f4..59613416d1 100644 --- a/mm/2s2h/Rando/ActorBehavior/EnCow.cpp +++ b/mm/2s2h/Rando/ActorBehavior/EnCow.cpp @@ -58,7 +58,9 @@ RandoCheckId IdentifyCow(Actor* actor) { } void Rando::ActorBehavior::InitEnCowBehavior() { - COND_VB_SHOULD(VB_GIVE_ITEM_FROM_COW, IS_RANDO, { + bool shouldRegister = IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_COWS]; + + COND_VB_SHOULD(VB_GIVE_ITEM_FROM_COW, shouldRegister, { // Original Should is the Range check, if it fails just Return. Actor* actor = va_arg(args, Actor*); if (!((actor->xzDistToPlayer < 90.0f) && @@ -66,13 +68,15 @@ void Rando::ActorBehavior::InitEnCowBehavior() { *should = false; return; } + + ((EnCow*)actor)->flags |= EN_COW_FLAG_WONT_GIVE_MILK; + RandoCheckId randoCheckId = IdentifyCow(actor); if (randoCheckId == RC_UNKNOWN) { *should = true; return; } - ((EnCow*)actor)->flags |= EN_COW_FLAG_WONT_GIVE_MILK; RandoSaveCheck& randoSaveCheck = RANDO_SAVE_CHECKS[randoCheckId]; if (!randoSaveCheck.shuffled || randoSaveCheck.cycleObtained) { @@ -83,7 +87,7 @@ void Rando::ActorBehavior::InitEnCowBehavior() { *should = false; }); - COND_VB_SHOULD(VB_COW_CONSIDER_EPONAS_SONG_PLAYED, IS_RANDO, { + COND_VB_SHOULD(VB_COW_CONSIDER_EPONAS_SONG_PLAYED, shouldRegister, { EnCow* actor = va_arg(args, EnCow*); if (actor->flags & EN_COW_FLAG_WONT_GIVE_MILK) { gPlayState->msgCtx.songPlayed = 0; @@ -91,7 +95,7 @@ void Rando::ActorBehavior::InitEnCowBehavior() { *should = gPlayState->msgCtx.songPlayed == OCARINA_SONG_EPONAS; }); - COND_ID_HOOK(ShouldActorInit, ACTOR_EN_COW, IS_RANDO, [](Actor* refActor, bool* should) { + COND_ID_HOOK(ShouldActorInit, ACTOR_EN_COW, shouldRegister, [](Actor* refActor, bool* should) { RandoCheckId randoCheckId = IdentifyCow(refActor); switch (randoCheckId) { case RC_ROMANI_RANCH_BARN_COW_LEFT: From cfbfdc5d1bfbfbee7f839866e366ac996855e4c5 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:21:18 -0500 Subject: [PATCH 15/44] Fix OOB in AudioSeq_ResetReverb (#1397) --- mm/src/audio/sequence.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mm/src/audio/sequence.c b/mm/src/audio/sequence.c index a2b70389e6..0b192b67b0 100644 --- a/mm/src/audio/sequence.c +++ b/mm/src/audio/sequence.c @@ -895,10 +895,15 @@ u8 AudioSeq_ResetReverb(void) { } else { while (fadeReverb != 0) { if (fadeReverb & 1) { - AUDIOCMD_GLOBAL_SET_REVERB_DATA( - reverbIndex, REVERB_DATA_TYPE_SETTINGS, - (uintptr_t)(gReverbSettingsTable[(u8)(sResetAudioHeapSeqCmd & 0xFF)] + reverbIndex)); - AudioThread_ScheduleProcessCmds(); + //! @bug Triggering the ending cutscene during final hours can give an index of 12 here, which is + // OOB. This does not normally happen in the game, but Triforce Hunt makes this a possibility. + // Follow a condition similar to what is found in Audio_ResetForAudioHeapStep2. + if (((u8)(sResetAudioHeapSeqCmd & 0xFF)) < ARRAY_COUNT(gReverbSettingsTable)) { + AUDIOCMD_GLOBAL_SET_REVERB_DATA( + reverbIndex, REVERB_DATA_TYPE_SETTINGS, + (uintptr_t)(gReverbSettingsTable[(u8)(sResetAudioHeapSeqCmd & 0xFF)] + reverbIndex)); + AudioThread_ScheduleProcessCmds(); + } } reverbIndex++; fadeReverb = fadeReverb >> 1; From 6a52fc06aa34d40d0f7b5f5f95fb0c3af8c37db1 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:21:23 -0500 Subject: [PATCH 16/44] [Enhancement] Persist entrance and respawn information in saves (#890) * Add enhancement to remember location on save/load * Fix Inverted STT goof, apply hook suggestion * Fix pause saves after leaving grottos * Address feedback, use ShipInit --- mm/2s2h/BenGui/BenMenu.cpp | 7 +- mm/2s2h/BenJsonConversions.hpp | 42 ++++++ .../Saving/SavingEnhancements.cpp | 135 +++++++++++++++--- mm/2s2h/SaveManager/Migrations/7.cpp | 23 +++ mm/include/z64save.h | 1 + 5 files changed, 184 insertions(+), 24 deletions(-) diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index b4f8d1039b..9361ca0360 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -1047,14 +1047,17 @@ void BenMenu::AddEnhancements() { "Re-introduce the pause menu save system. Pressing B in the pause menu will give you the " "option to create a persistent Owl Save from your current location.\n\nWhen loading back " "into the game, you will be placed either at the entrance of the dungeon you saved in, or " - "in South Clock Town.")); + "in South Clock Town, unless Remember Save Location is enabled.")); + AddWidget(path, "Remember Save Location", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Saving.RememberSaveLocation") + .Options(CheckboxOptions().Tooltip("When loading a save, places Link at the last entrance he went through.")); AddWidget(path, "Autosave", WIDGET_CVAR_CHECKBOX) .CVar("gEnhancements.Saving.Autosave") .Callback([](WidgetInfo& info) { RegisterAutosave(); }) .Options(CheckboxOptions().Tooltip( "Automatically create a persistent Owl Save on the chosen interval.\n\nWhen loading " "back into the game, you will be placed either at the entrance of the dungeon you " - "saved in, or in South Clock Town.")); + "saved in, or in South Clock Town, unless Remember Save Location is enabled.")); AddWidget(path, "Autosave Interval: %d minutes", WIDGET_CVAR_SLIDER_INT) .CVar("gEnhancements.Saving.AutosaveInterval") .PreFunc([](WidgetInfo& info) { diff --git a/mm/2s2h/BenJsonConversions.hpp b/mm/2s2h/BenJsonConversions.hpp index 0f34d71f83..1ba1f3beea 100644 --- a/mm/2s2h/BenJsonConversions.hpp +++ b/mm/2s2h/BenJsonConversions.hpp @@ -76,6 +76,46 @@ void from_json(const json& j, RandoSaveInfo& rando) { j.at("foundTriforcePieces").get_to(rando.foundTriforcePieces); } +void to_json(json& j, const Vec3f& vec) { + j = json{ + { "x", vec.x }, + { "y", vec.y }, + { "z", vec.z }, + }; +} + +void from_json(const json& j, Vec3f& vec) { + j.at("x").get_to(vec.x); + j.at("y").get_to(vec.y); + j.at("z").get_to(vec.z); +} + +void to_json(json& j, const RespawnData& respawnData) { + j = json{ + { "pos", respawnData.pos }, + { "yaw", respawnData.yaw }, + { "playerParams", respawnData.playerParams }, + { "entrance", respawnData.entrance }, + { "roomIndex", respawnData.roomIndex }, + { "data", respawnData.data }, + { "tempSwitchFlags", respawnData.tempSwitchFlags }, + { "unk_18", respawnData.unk_18 }, + { "tempCollectFlags", respawnData.tempCollectFlags }, + }; +} + +void from_json(const json& j, RespawnData& respawnData) { + j.at("pos").get_to(respawnData.pos); + j.at("yaw").get_to(respawnData.yaw); + j.at("playerParams").get_to(respawnData.playerParams); + j.at("entrance").get_to(respawnData.entrance); + j.at("roomIndex").get_to(respawnData.roomIndex); + j.at("data").get_to(respawnData.data); + j.at("tempSwitchFlags").get_to(respawnData.tempSwitchFlags); + j.at("unk_18").get_to(respawnData.unk_18); + j.at("tempCollectFlags").get_to(respawnData.tempCollectFlags); +} + void to_json(json& j, const ShipSaveInfo& shipSaveInfo) { uint8_t commitHash[8]; memcpy(commitHash, shipSaveInfo.commitHash, sizeof(commitHash)); @@ -87,6 +127,7 @@ void to_json(json& j, const ShipSaveInfo& shipSaveInfo) { { "fileCreatedAt", shipSaveInfo.fileCreatedAt }, { "fileCompletedAt", shipSaveInfo.fileCompletedAt }, { "filePlaytime", shipSaveInfo.filePlaytime }, + { "respawn", shipSaveInfo.respawn }, { "commitHash", commitHash }, }; @@ -102,6 +143,7 @@ void from_json(const json& j, ShipSaveInfo& shipSaveInfo) { j.at("fileCreatedAt").get_to(shipSaveInfo.fileCreatedAt); j.at("fileCompletedAt").get_to(shipSaveInfo.fileCompletedAt); j.at("filePlaytime").get_to(shipSaveInfo.filePlaytime); + j.at("respawn").get_to(shipSaveInfo.respawn); j.at("commitHash").get_to(shipSaveInfo.commitHash); if (shipSaveInfo.saveType == SAVETYPE_RANDO) { diff --git a/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp b/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp index be5a5cb679..01d9e07da2 100644 --- a/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp +++ b/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp @@ -1,44 +1,60 @@ #include #include "BenPort.h" #include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" extern "C" { #include #include } +#define CVAR_REMEMBER_SAVE_LOCATION_NAME "gEnhancements.Saving.RememberSaveLocation" +#define CVAR_REMEMBER_SAVE_LOCATION CVarGetInteger(CVAR_REMEMBER_SAVE_LOCATION_NAME, 0) + static uint32_t autosaveInterval = 0; static uint32_t iconTimer = 0; static uint64_t currentTimestamp = 0; static uint64_t lastSaveTimestamp = GetUnixTimestamp(); +static int lastEntrance = -1; +static int entranceToSave = -1; static HOOK_ID autosaveGameStateUpdateHookId = 0; static HOOK_ID autosaveGameStateDrawFinishHookId = 0; +static HOOK_ID skipEntranceCutsceneHookId = 0; +static HOOK_ID gameplayStartHookId = 0; // Used for saving through Autosaves and Pause Menu saves. extern "C" int SavingEnhancements_GetSaveEntrance() { - switch (gPlayState->sceneId) { - // Woodfall Temple + Odolwa - case SCENE_MITURIN: - case SCENE_MITURIN_BS: - return ENTRANCE(WOODFALL_TEMPLE, 0); - // Snowhead Temple + Goht - case SCENE_HAKUGIN: - case SCENE_HAKUGIN_BS: - return ENTRANCE(SNOWHEAD_TEMPLE, 0); - // Great Bay Temple + Gyorg - case SCENE_SEA: - case SCENE_SEA_BS: - return ENTRANCE(GREAT_BAY_TEMPLE, 0); - // Stone Tower Temple - case SCENE_INISIE_N: - return ENTRANCE(STONE_TOWER_TEMPLE, 0); - // Stone Tower Temple (inverted) + Twinmold - case SCENE_INISIE_R: - case SCENE_INISIE_BS: - return ENTRANCE(STONE_TOWER_TEMPLE_INVERTED, 0); - default: - return ENTRANCE(SOUTH_CLOCK_TOWN, 0); + if (CVAR_REMEMBER_SAVE_LOCATION) { + // Maintain respawn information, used for grottos + for (int i = 0; i < RESPAWN_MODE_MAX; i++) { + gSaveContext.save.shipSaveInfo.respawn[i] = gSaveContext.respawn[i]; + } + return entranceToSave; + } else { + switch (gPlayState->sceneId) { + // Woodfall Temple + Odolwa + case SCENE_MITURIN: + case SCENE_MITURIN_BS: + return ENTRANCE(WOODFALL_TEMPLE, 0); + // Snowhead Temple + Goht + case SCENE_HAKUGIN: + case SCENE_HAKUGIN_BS: + return ENTRANCE(SNOWHEAD_TEMPLE, 0); + // Great Bay Temple + Gyorg + case SCENE_SEA: + case SCENE_SEA_BS: + return ENTRANCE(GREAT_BAY_TEMPLE, 0); + // Stone Tower Temple + case SCENE_INISIE_N: + return ENTRANCE(STONE_TOWER_TEMPLE, 0); + // Stone Tower Temple (inverted) + Twinmold + case SCENE_INISIE_R: + case SCENE_INISIE_BS: + return ENTRANCE(STONE_TOWER_TEMPLE_INVERTED, 0); + default: + return ENTRANCE(SOUTH_CLOCK_TOWN, 0); + } } } @@ -158,6 +174,59 @@ void HandleAutoSave() { } } +/* + * This respawn data is used for multiple things. Beyond the obvious usage for handling player respawns, this structure + * also maintains state information when entering shared grottos. This code executes from OnSaveLoad, which runs after + * save data is populated. This must run after that, otherwise the RESPAWN_MODE_DOWN entrance would get set to + * ENTR_LOAD_OPENING, which in turn would lead to a crash if the save is within a grotto and the player dies before + * leaving. + */ +void loadRespawnData(s16 fileNum) { + for (int i = 0; i < RESPAWN_MODE_MAX; i++) { + gSaveContext.respawn[i] = gSaveContext.save.shipSaveInfo.respawn[i]; + } +} + +/* + * Upon loading a save, skip any cutscenes that would play if the save is from a cutscene entrance (e.g. owl warps, Link + * bowing at Mikau's grave, etc.). An OnPassPlayerInputs hook is used to detect when gameplay actually starts (any + * entrance cutscenes are done), at which point the cutscene skip hook is unregistered. This handles any potential cases + * where multiple cutscenes play in succession. + */ +static void UnregisterEntranceCutsceneSkip() { + if (skipEntranceCutsceneHookId) { + GameInteractor::Instance->UnregisterGameHookForID( + skipEntranceCutsceneHookId); + skipEntranceCutsceneHookId = 0; + } + + if (gameplayStartHookId) { + GameInteractor::Instance->UnregisterGameHook(gameplayStartHookId); + gameplayStartHookId = 0; + } +} + +void skipEntranceCutsceneOnLoad(s16 fileNum) { + // Clean up any existing hooks first + UnregisterEntranceCutsceneSkip(); + // Register hook to skip entrance cutscenes - may skip multiple if they chain + skipEntranceCutsceneHookId = REGISTER_VB_SHOULD(VB_START_CUTSCENE, { + // Only skip normal cutscenes + if (gSaveContext.gameMode == GAMEMODE_NORMAL && gPlayState != nullptr && gPlayState->sceneId != SCENE_SPOT00) { + *should = false; + } + }); + + // Register hook to detect when gameplay starts (all cutscenes done) + // OnPassPlayerInputs only fires during normal gameplay, not during cutscenes + gameplayStartHookId = + GameInteractor::Instance->RegisterGameHook([](Input* input) { + // Gameplay has started; any entrance cutscenes are done + // Now unregister both hooks so normal cutscenes can play + UnregisterEntranceCutsceneSkip(); + }); +} + void RegisterSavingEnhancements() { REGISTER_VB_SHOULD(VB_DELETE_OWL_SAVE, { if (CVarGetInteger("gEnhancements.Saving.PersistentOwlSaves", 0) || @@ -171,6 +240,7 @@ void RegisterSavingEnhancements() { gSaveContext.save.shipSaveInfo.fileCreatedAt = GetUnixTimestamp(); } gSaveContext.shipSaveContext.lastTimeLog = GetUnixTimestamp(); + lastEntrance = entranceToSave = gSaveContext.save.shipSaveInfo.pauseSaveEntrance; }); // Owl statue prompt @@ -191,6 +261,8 @@ void RegisterSavingEnhancements() { }); GameInteractor::Instance->RegisterGameHook([]() { DeleteOwlSave(); }); + + GameInteractor::Instance->RegisterGameHook(loadRespawnData); } void RegisterAutosave() { @@ -225,3 +297,22 @@ void RegisterAutosave() { }); } } + +void RegisterRememberSaveLocation() { + COND_VB_SHOULD(VB_PLAY_TRANSITION_CS, CVAR_REMEMBER_SAVE_LOCATION, { + /* + * Update the entrance to save, unless we're leaving a grotto. Grottos exit to entrance 0 of the destination + * scene and adjust the position manually. In effect, there is no real entrance to target for loading purposes, + * so we just load into the last grotto instead under those circumstances. + */ + if (lastEntrance != -1 && !(Entrance_GetSceneIdAbsolute(gSaveContext.save.entrance) != SCENE_KAKUSIANA && + Entrance_GetSceneIdAbsolute(lastEntrance) == SCENE_KAKUSIANA)) { + entranceToSave = gSaveContext.save.entrance; + } + lastEntrance = gSaveContext.save.entrance; + }); + + COND_HOOK(OnSaveLoad, CVAR_REMEMBER_SAVE_LOCATION, skipEntranceCutsceneOnLoad); +} + +static RegisterShipInitFunc initFunc(RegisterRememberSaveLocation, { CVAR_REMEMBER_SAVE_LOCATION_NAME }); diff --git a/mm/2s2h/SaveManager/Migrations/7.cpp b/mm/2s2h/SaveManager/Migrations/7.cpp index b6cffbb84f..ec620c8a41 100644 --- a/mm/2s2h/SaveManager/Migrations/7.cpp +++ b/mm/2s2h/SaveManager/Migrations/7.cpp @@ -1,8 +1,31 @@ #include "2s2h/SaveManager/SaveManager.h" #include "z64.h" +// Add respawn and playtime info to saves void SaveManager_Migration_7(nlohmann::json& j) { if (!j["save"]["shipSaveInfo"].contains("filePlaytime")) { j["save"]["shipSaveInfo"]["filePlaytime"] = 0; } + if (!j["save"]["shipSaveInfo"].contains("respawn")) { + j["save"]["shipSaveInfo"]["respawn"] = {}; + } + + for (int i = 0; i < RESPAWN_MODE_MAX; i++) { + j["save"]["shipSaveInfo"]["respawn"][i] = { + { "pos", + { + { "x", 0.0 }, + { "y", 0.0 }, + { "z", 0.0 }, + } }, + { "yaw", 0 }, + { "playerParams", 0 }, + { "entrance", 0 }, + { "roomIndex", 0 }, + { "data", 0 }, + { "tempSwitchFlags", 0 }, + { "unk_18", 0 }, + { "tempCollectFlags", 0 }, + }; + } } diff --git a/mm/include/z64save.h b/mm/include/z64save.h index d4bee40bf3..29a2c899a3 100644 --- a/mm/include/z64save.h +++ b/mm/include/z64save.h @@ -399,6 +399,7 @@ typedef struct ShipSaveInfo { uint64_t fileCreatedAt; uint64_t fileCompletedAt; uint64_t filePlaytime; + RespawnData respawn[RESPAWN_MODE_MAX]; char commitHash[8]; RandoSaveInfo rando; } ShipSaveInfo; From e56fe24b23fd793cff92e0089b0d1a45ebc43035 Mon Sep 17 00:00:00 2001 From: Glought Date: Thu, 18 Dec 2025 06:21:32 -0800 Subject: [PATCH 17/44] [Controls] Port Modifier Buttons and Speed Modifiers from SoH. (#1365) * [Controls] Port Modifier Buttons from SoH. * Adds "M1" and "M2" buttons extra buttons like the y and x buttons on the GameCube Controller, or a keyboard key can be assigned. Port Speed Modifiers from SoH. When Walk Speed Modifier is enabled Link's walk speed will be increased depending on the "Walk Modifier 1 or Modifier 2" setting. "Walk Modifier 1" is "M1". "Walk Modifier 2" is "M2". Default:100% Max:1500% Min:0% When Swim Speed Modifier is enabled Link's swim speed will be increased depending on the "Swim Modifier 1 or Modifier 2" setting. "Swim Modifier 1 is M1. "Swim Modifier 2 is M2. Default:100% Max:875% Min:0% "Toggle modifier instead of holding" setting will keep the Modifier active till the modifier button is pressed again. Modifier button toggles are reset when 2S2H is reset. * Ran Clang Format * Renamed "initWalkSpeedFunc" to "initFunc" in "LinkSpeedModifier.cpp" * Made changes based on mckinlee's suggestions. * Made it so only one Modifier button is toggled at a time. For example if you press "M1" it will untoggle "M2" * Removed "mModifierButtonsBitmasks" in "BenInputEditorWindow.cpp" wasn't needed. Fixed issue with "COND_VB_SHOULD(VB_SPEED_MODIFIER_SWIM," forgot to set "*should" Ran Clang Format. * Revert BTN_CUSTOM_MODIFIER1 back to 0x0040 Revert BTN_CUSTOM_MODIFIER2 back to 0x0080 * Moved "VB_SPEED_MODIFIER_WALK" and "VB_SPEED_MODIFIER_WALK" to "GameInteractor_VanillaBehavior.h" * Made changes suggested by Eblo. --- mm/2s2h/BenGui/BenInputEditorWindow.cpp | 68 ++++++++++++++ mm/2s2h/BenGui/BenInputEditorWindow.h | 2 + .../Enhancements/Player/LinkSpeedModifier.cpp | 94 +++++++++++++++++++ .../GameInteractor_VanillaBehavior.h | 19 ++++ .../actors/ovl_player_actor/z_player.c | 25 +++-- 5 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 mm/2s2h/Enhancements/Player/LinkSpeedModifier.cpp diff --git a/mm/2s2h/BenGui/BenInputEditorWindow.cpp b/mm/2s2h/BenGui/BenInputEditorWindow.cpp index f4f37d4804..422c858fb0 100644 --- a/mm/2s2h/BenGui/BenInputEditorWindow.cpp +++ b/mm/2s2h/BenGui/BenInputEditorWindow.cpp @@ -4,12 +4,17 @@ #include #include #include +#include "2s2h/BenPort.h" +#include "2s2h/BenGui/UIWidgets.hpp" +#include "2s2h/BenGui/BenGui.hpp" #ifndef __WIIU__ #include #endif #define SCALE_IMGUI_SIZE(value) ((value / 13.0f) * ImGui::GetFontSize()) +using namespace UIWidgets; + BenInputEditorWindow::~BenInputEditorWindow() { } @@ -1196,6 +1201,65 @@ void BenInputEditorWindow::DrawGyroSection(uint8_t port) { } } +void BenInputEditorWindow::DrawModifierButtonsSection(uint8_t port) { + DrawButtonLine("M1", port, BTN_CUSTOM_MODIFIER1); + DrawButtonLine("M2", port, BTN_CUSTOM_MODIFIER2); + + ImGui::BeginDisabled(CVarGetInteger("gSettings.DisableChanges", 0)); + CVarCheckbox("Enable Speed Modifiers", "gSettings.SpeedModifier.Enable", + CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Hold the assigned button to change the maximum walking or swimming speed.")); + if (CVarGetInteger("gSettings.SpeedModifier.Enable", 0)) { + UIWidgets::Spacer(5); + Ship::GuiWindow::BeginGroupPanel("Speed Modifier", ImGui::GetContentRegionAvail()); + CVarCheckbox("Toggle modifier instead of holding", "gSettings.SpeedModifier.Toggle", + CheckboxOptions().Color(THEME_COLOR)); + Ship::GuiWindow::BeginGroupPanel("Walk Modifier", ImGui::GetContentRegionAvail()); + CVarCheckbox("Enable Walk Speed Modifier", "gSettings.SpeedModifier.WalkEnable", + CheckboxOptions().Color(THEME_COLOR)); + CVarSliderFloat("Walk Modifier 1: %.0f %%", "gSettings.SpeedModifier.WalkMapping1", + FloatSliderOptions() + .Color(THEME_COLOR) + .IsPercentage() + .Min(0.0f) + .Max(15.0f) + .DefaultValue(1.0f) + .ShowAdjustmentButtons(true)); + CVarSliderFloat("Walk Modifier 2: %.0f %%", "gSettings.SpeedModifier.WalkMapping2", + FloatSliderOptions() + .Color(THEME_COLOR) + .IsPercentage() + .Min(0.0f) + .Max(15.0f) + .DefaultValue(1.0f) + .ShowAdjustmentButtons(true)); + Ship::GuiWindow::EndGroupPanel(0); + Ship::GuiWindow::BeginGroupPanel("Swim Modifier", ImGui::GetContentRegionAvail()); + CVarCheckbox("Enable Swim Speed Modifier", "gSettings.SpeedModifier.SwimEnable", + CheckboxOptions().Color(THEME_COLOR)); + CVarSliderFloat("Swim Modifier 1: %.0f %%", "gSettings.SpeedModifier.SwimMapping1", + FloatSliderOptions() + .Color(THEME_COLOR) + .IsPercentage() + .Min(0.0f) + .Max(8.75f) + .DefaultValue(1.0f) + .ShowAdjustmentButtons(true)); + CVarSliderFloat("Swim Modifier 2: %.0f %%", "gSettings.SpeedModifier.SwimMapping2", + FloatSliderOptions() + .Color(THEME_COLOR) + .IsPercentage() + .Min(0.0f) + .Max(8.75f) + .DefaultValue(1.0f) + .ShowAdjustmentButtons(true)); + Ship::GuiWindow::EndGroupPanel(0); + Ship::GuiWindow::EndGroupPanel(0); + } + ImGui::EndDisabled(); +} + const ImGuiTableFlags PANEL_TABLE_FLAGS = ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV; const ImGuiTableColumnFlags PANEL_TABLE_COLUMN_FLAGS = ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_NoSort; @@ -1396,6 +1460,10 @@ void BenInputEditorWindow::DrawPortTabContents(uint8_t portIndex) { DrawLEDSection(portIndex); } + if (ImGui::CollapsingHeader("Modifier Buttons")) { + DrawModifierButtonsSection(portIndex); + } + ImGui::PopStyleColor(); ImGui::PopStyleColor(); ImGui::PopStyleColor(); diff --git a/mm/2s2h/BenGui/BenInputEditorWindow.h b/mm/2s2h/BenGui/BenInputEditorWindow.h index ad7c838c54..e002feed4e 100644 --- a/mm/2s2h/BenGui/BenInputEditorWindow.h +++ b/mm/2s2h/BenGui/BenInputEditorWindow.h @@ -67,6 +67,8 @@ class BenInputEditorWindow : public Ship::GuiWindow { void DrawRemoveGyroMappingButton(uint8_t port, std::string id); void DrawAddGyroMappingButton(uint8_t port); + void DrawModifierButtonsSection(uint8_t port); + // Used together for an incomplete linked hash map implementation in order to // map button masks to their names and original mapping on N64 std::list> buttons; diff --git a/mm/2s2h/Enhancements/Player/LinkSpeedModifier.cpp b/mm/2s2h/Enhancements/Player/LinkSpeedModifier.cpp new file mode 100644 index 0000000000..142c43c244 --- /dev/null +++ b/mm/2s2h/Enhancements/Player/LinkSpeedModifier.cpp @@ -0,0 +1,94 @@ +#include +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" +#include "2s2h/BenPort.h" + +extern "C" { +#include "variables.h" +extern Input* sPlayerControlInput; +} + +#define CVAR_SPEED_MODIFIER_NAME "gSettings.SpeedModifier.Enable" +#define CVAR_SPEED_MODIFIER_TOGGLE "gSettings.SpeedModifier.Toggle" +#define CVAR_WALK_MODIFIER_NAME "gSettings.SpeedModifier.WalkEnable" +#define CVAR_SWIM_MODIFIER_NAME "gSettings.SpeedModifier.SwimEnable" +#define CVAR_SPEED CVarGetInteger(CVAR_SPEED_MODIFIER_NAME, 0) +#define CVAR_SPEED_TOGGLE CVarGetInteger(CVAR_SPEED_MODIFIER_TOGGLE, 0) +#define CVAR_WALK CVarGetInteger(CVAR_WALK_MODIFIER_NAME, 0) +#define CVAR_SWIM CVarGetInteger(CVAR_SWIM_MODIFIER_NAME, 0) + +bool speedToggle1; +bool speedToggle2; + +void RegisterLinkSpeedModifier() { + + COND_VB_SHOULD(VB_SPEED_MODIFIER_WALK, CVAR_WALK && CVAR_SPEED, { + f32* speedTarget = va_arg(args, f32*); + + if (CVAR_SPEED_TOGGLE) { + if (speedToggle1) { + *speedTarget *= CVarGetFloat("gSettings.SpeedModifier.WalkMapping1", 1.0f); + } else if (speedToggle2) { + *speedTarget *= CVarGetFloat("gSettings.SpeedModifier.WalkMapping2", 1.0f); + } + } else { + if (CHECK_BTN_ALL(sPlayerControlInput->cur.button, BTN_CUSTOM_MODIFIER1)) { + *speedTarget *= CVarGetFloat("gSettings.SpeedModifier.WalkMapping1", 1.0f); + } else if (CHECK_BTN_ALL(sPlayerControlInput->cur.button, BTN_CUSTOM_MODIFIER2)) { + *speedTarget *= CVarGetFloat("gSettings.SpeedModifier.WalkMapping2", 1.0f); + } + } + }); + + COND_VB_SHOULD(VB_SPEED_MODIFIER_SWIM, CVAR_SWIM && CVAR_SPEED, { + *should = false; + f32* incrStep = va_arg(args, f32*); + f32* maxSpeed = va_arg(args, f32*); + f32* speed = va_arg(args, f32*); + f32* speedTarget = va_arg(args, f32*); + f32 swimMod = 1.0f; + + if (CVAR_SPEED_TOGGLE) { + if (speedToggle1) { + swimMod *= CVarGetFloat("gSettings.SpeedModifier.SwimMapping1", 1.0f); + } else if (speedToggle2) { + swimMod *= CVarGetFloat("gSettings.SpeedModifier.SwimMapping2", 1.0f); + } + + // sControlInput is NULL to prevent inputs while surfacing after obtaining an underwater item so we want + // to ignore it for that case + } else if (sPlayerControlInput != NULL) { + if (CHECK_BTN_ALL(sPlayerControlInput->cur.button, BTN_CUSTOM_MODIFIER1)) { + swimMod *= CVarGetFloat("gSettings.SpeedModifier.SwimMapping1", 1.0f); + } else if (CHECK_BTN_ALL(sPlayerControlInput->cur.button, BTN_CUSTOM_MODIFIER2)) { + swimMod *= CVarGetFloat("gSettings.SpeedModifier.SwimMapping2", 1.0f); + } + } + + *maxSpeed *= swimMod; + + Math_AsymStepToF(speed, *speedTarget * 0.8f * swimMod, *incrStep, (fabsf(*speed) * 0.02f) + 0.05f); + }); + + COND_HOOK(OnPassPlayerInputs, CVAR_SPEED && (CVAR_WALK || CVAR_SWIM), [](Input* input) { + if (CVAR_SPEED_TOGGLE) { + + if (CHECK_BTN_ALL(input->press.button, BTN_CUSTOM_MODIFIER1)) { + speedToggle1 = !speedToggle1; + speedToggle2 = false; + } + if (CHECK_BTN_ALL(input->press.button, BTN_CUSTOM_MODIFIER2)) { + speedToggle2 = !speedToggle2; + speedToggle1 = false; + } + } + }); + + COND_HOOK(OnConsoleLogoUpdate, CVAR_SPEED && (CVAR_WALK || CVAR_SWIM), []() { + speedToggle1 = false; + speedToggle2 = false; + }); +} + +static RegisterShipInitFunc initFunc(RegisterLinkSpeedModifier, { CVAR_SPEED_MODIFIER_NAME, CVAR_SPEED_MODIFIER_TOGGLE, + CVAR_WALK_MODIFIER_NAME, CVAR_SWIM_MODIFIER_NAME }); \ No newline at end of file diff --git a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h index a49a5be20c..b7eaa84047 100644 --- a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h +++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h @@ -1802,6 +1802,25 @@ typedef enum { // - `*s32` (which boss remains to return, see `func_808B849C`) VB_SPAWN_BOSS_REMAINS, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - '*f32' (incrStep) + // - '*f32' (maxSpeed) + // - '*f32' (speed) + // - '*f32' (speedTarget) + VB_SPEED_MODIFIER_SWIM, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_SPEED_MODIFIER_WALK, + // #### `result` // ```c // this->actor.xzDistToPlayer < 350.0f diff --git a/mm/src/overlays/actors/ovl_player_actor/z_player.c b/mm/src/overlays/actors/ovl_player_actor/z_player.c index 236028dd00..542af15de9 100644 --- a/mm/src/overlays/actors/ovl_player_actor/z_player.c +++ b/mm/src/overlays/actors/ovl_player_actor/z_player.c @@ -13674,18 +13674,20 @@ void func_8084748C(Player* this, f32* speed, f32 speedTarget, s16 yawTarget) { f32 incrStep = this->skelAnime.curFrame - 10.0f; f32 maxSpeed = (R_RUN_SPEED_LIMIT / 100.0f) * 0.8f; - if (*speed > maxSpeed) { - *speed = maxSpeed; - } + if (GameInteractor_Should(VB_SPEED_MODIFIER_SWIM, true, &incrStep, &maxSpeed, speed, &speedTarget)) { - if ((0.0f < incrStep) && (incrStep < 16.0f)) { - incrStep = fabsf(incrStep) * 0.5f; - } else { - speedTarget = 0.0f; - incrStep = 0.0f; - } + if (*speed > maxSpeed) { + *speed = maxSpeed; + } - Math_AsymStepToF(speed, speedTarget * 0.8f, incrStep, (fabsf(*speed) * 0.02f) + 0.05f); + if ((0.0f < incrStep) && (incrStep < 16.0f)) { + incrStep = fabsf(incrStep) * 0.5f; + } else { + speedTarget = 0.0f; + incrStep = 0.0f; + } + Math_AsymStepToF(speed, speedTarget * 0.8f, incrStep, (fabsf(*speed) * 0.02f) + 0.05f); + } Math_ScaledStepToS(&this->yaw, yawTarget, 0x640); // 1 ESS turn, also one frame of first-person rotation } @@ -15149,6 +15151,9 @@ void Player_Action_13(Player* this, PlayState* play) { } if (!func_8083A4A4(this, &speedTarget, &yawTarget, R_DECELERATE_RATE / 100.0f)) { + + GameInteractor_Should(VB_SPEED_MODIFIER_WALK, true, &speedTarget); + func_8083CB58(this, speedTarget, yawTarget); func_8083C8E8(this, play); if ((this->speedXZ == 0.0f) && (speedTarget == 0.0f)) { From 711144364b1b41c4cff17de565f5047da5a26f4b Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Thu, 18 Dec 2025 08:21:43 -0600 Subject: [PATCH 18/44] Prevent notification for junk items, and remove queue delay (#1395) --- mm/2s2h/GameInteractor/GameInteractor.cpp | 41 ++++++++++++----------- mm/2s2h/Rando/MiscBehavior/CheckQueue.cpp | 12 ++++--- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/mm/2s2h/GameInteractor/GameInteractor.cpp b/mm/2s2h/GameInteractor/GameInteractor.cpp index 7c1d5cc1f9..7acfa594be 100644 --- a/mm/2s2h/GameInteractor/GameInteractor.cpp +++ b/mm/2s2h/GameInteractor/GameInteractor.cpp @@ -466,39 +466,40 @@ void ProcessEvents(Actor* actor) { if (auto e = std::get_if(&nextEvent)) { EnItem00* enItem00; + + s16 flags = CustomItem::HIDE_TILL_OVERHEAD | CustomItem::KEEP_ON_PLAYER; + // If the player is climbing or in the air, deliver the item without a cutscene but freeze the player if (!e->showGetItemCutscene || (player->stateFlags1 & (PLAYER_STATE1_CHARGING_SPIN_ATTACK | PLAYER_STATE1_2000 | PLAYER_STATE1_4000 | PLAYER_STATE1_40000 | PLAYER_STATE1_80000 | PLAYER_STATE1_100000 | PLAYER_STATE1_200000 | PLAYER_STATE1_8000000)) || (Player_GetExplosiveHeld(player) > PLAYER_EXPLOSIVE_NONE)) { - enItem00 = CustomItem::Spawn( - player->actor.world.pos.x, player->actor.world.pos.y, player->actor.world.pos.z, 0, - CustomItem::GIVE_OVERHEAD | CustomItem::HIDE_TILL_OVERHEAD | CustomItem::KEEP_ON_PLAYER, e->param, - [](Actor* actor, PlayState* play) { - Player* player = GET_PLAYER(gPlayState); - const auto& nextEvent = GameInteractor::Instance->currentEvent; - if (auto e = std::get_if(&nextEvent)) { - e->giveItem(actor, play); - if (e->showGetItemCutscene) { - player->actor.freezeTimer = 30; - } - } - }, - e->drawItem); + + flags |= CustomItem::GIVE_OVERHEAD; } else { - enItem00 = CustomItem::Spawn( - player->actor.world.pos.x, player->actor.world.pos.y, player->actor.world.pos.z, 0, - CustomItem::GIVE_ITEM_CUTSCENE | CustomItem::HIDE_TILL_OVERHEAD | CustomItem::KEEP_ON_PLAYER, e->param, - e->giveItem, e->drawItem); + flags |= CustomItem::GIVE_ITEM_CUTSCENE; } + + enItem00 = CustomItem::Spawn( + player->actor.world.pos.x, player->actor.world.pos.y, player->actor.world.pos.z, 0, flags, e->param, + [](Actor* actor, PlayState* play) { + Player* player = GET_PLAYER(gPlayState); + const auto& nextEvent = GameInteractor::Instance->currentEvent; + if (auto e = std::get_if(&nextEvent)) { + e->giveItem(actor, play); + if (e->showGetItemCutscene && !(CUSTOM_ITEM_FLAGS & CustomItem::GIVE_ITEM_CUTSCENE)) { + player->actor.freezeTimer = 30; + } + GameInteractor::Instance->currentEvent = GIEventNone{}; + } + }, + e->drawItem); enItem00->actor.destroy = [](Actor* actor, PlayState* play) { if (!(CUSTOM_ITEM_FLAGS & CustomItem::CALLED_ACTION)) { // Event was not handled, requeue it GameInteractor::Instance->events.push_back(GameInteractor::Instance->currentEvent); } - - GameInteractor::Instance->currentEvent = GIEventNone{}; }; } else if (auto e = std::get_if(&nextEvent)) { gPlayState->nextEntrance = e->entrance; diff --git a/mm/2s2h/Rando/MiscBehavior/CheckQueue.cpp b/mm/2s2h/Rando/MiscBehavior/CheckQueue.cpp index 9f372fe4fe..de35190761 100644 --- a/mm/2s2h/Rando/MiscBehavior/CheckQueue.cpp +++ b/mm/2s2h/Rando/MiscBehavior/CheckQueue.cpp @@ -82,11 +82,13 @@ void Rando::MiscBehavior::CheckQueue() { } else if (Rando::StaticData::ShouldShowGetItemCutscene(randoItemId)) { CustomMessage::StartTextbox(entry.msg + "\x1C\x02\x10", entry); } else { - Notification::Emit({ - .itemIcon = Rando::StaticData::GetIconTexturePath(randoItemId), - .message = prefix, - .suffix = message, - }); + if (Rando::StaticData::Items[randoItemId].randoItemType != RITYPE_JUNK) { + Notification::Emit({ + .itemIcon = Rando::StaticData::GetIconTexturePath(randoItemId), + .message = prefix, + .suffix = message, + }); + } } Rando::GiveItem(randoItemId); randoSaveCheck.cycleObtained = true; From bc2b3b6c5ab1ed5e30b7544a6178763c765414ac Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Thu, 18 Dec 2025 09:21:51 -0500 Subject: [PATCH 19/44] Add hook file for Pause Save (#1399) * Add hook file for Pause Save * Correct CVar default * Add missing include --- mm/2s2h/Enhancements/Saving/PauseSave.cpp | 17 +++++++++++ .../GameInteractor_VanillaBehavior.h | 8 +++++ .../ovl_kaleido_scope/z_kaleido_scope_NES.c | 30 ++++++++----------- 3 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 mm/2s2h/Enhancements/Saving/PauseSave.cpp diff --git a/mm/2s2h/Enhancements/Saving/PauseSave.cpp b/mm/2s2h/Enhancements/Saving/PauseSave.cpp new file mode 100644 index 0000000000..7478792ca7 --- /dev/null +++ b/mm/2s2h/Enhancements/Saving/PauseSave.cpp @@ -0,0 +1,17 @@ +#include +#include "2s2h/ShipInit.hpp" +#include "SavingEnhancements.h" + +extern "C" PlayState* gPlayState; + +#define CVAR_NAME "gEnhancements.Saving.PauseSave" +#define CVAR CVarGetInteger(CVAR_NAME, false) + +void RegisterPauseSave() { + COND_VB_SHOULD(VB_SAVE_ON_B_BUTTON_IN_PAUSE_MENU, CVAR, { + Input* input = CONTROLLER1(&gPlayState->state); + *should = SavingEnhancements_CanSave() && CHECK_BTN_ALL(input->press.button, BTN_B); + }); +} + +static RegisterShipInitFunc initFunc(RegisterPauseSave, { CVAR_NAME }); diff --git a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h index b7eaa84047..725b840536 100644 --- a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h +++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h @@ -1611,6 +1611,14 @@ typedef enum { // - `*EnSuttari` VB_SAKON_TAKE_DAMAGE, + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - None + VB_SAVE_ON_B_BUTTON_IN_PAUSE_MENU, + // #### `result` // ```c // gSaveContext.save.saveInfo.inventory.items[ITEM_OCARINA_OF_TIME] == ITEM_NONE diff --git a/mm/src/overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope_NES.c b/mm/src/overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope_NES.c index 036c255dbf..4209fc257b 100644 --- a/mm/src/overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope_NES.c +++ b/mm/src/overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope_NES.c @@ -3438,12 +3438,10 @@ void KaleidoScope_Update(PlayState* play) { if (!pauseCtx->itemDescriptionOn && (CHECK_BTN_ALL(input->press.button, BTN_START) || CHECK_BTN_ALL(input->press.button, BTN_B))) { Interface_SetAButtonDoAction(play, DO_ACTION_NONE); - if (CVarGetInteger("gEnhancements.Saving.PauseSave", 0) && SavingEnhancements_CanSave()) { - if (CHECK_BTN_ALL(input->press.button, BTN_B)) { - pauseCtx->state = PAUSE_STATE_SAVEPROMPT; - Audio_PlaySfx_MessageDecide(); - break; - } + if (GameInteractor_Should(VB_SAVE_ON_B_BUTTON_IN_PAUSE_MENU, false)) { + pauseCtx->state = PAUSE_STATE_SAVEPROMPT; + Audio_PlaySfx_MessageDecide(); + break; } pauseCtx->state = PAUSE_STATE_UNPAUSE_SETUP; sPauseMenuVerticalOffset = -6240.0f; @@ -3479,12 +3477,10 @@ void KaleidoScope_Update(PlayState* play) { // Abort having the player play the song and close the pause menu AudioOcarina_SetInstrument(OCARINA_INSTRUMENT_OFF); Interface_SetAButtonDoAction(play, DO_ACTION_NONE); - if (CVarGetInteger("gEnhancements.Saving.PauseSave", 0) && SavingEnhancements_CanSave()) { - if (CHECK_BTN_ALL(input->press.button, BTN_B)) { - pauseCtx->state = PAUSE_STATE_SAVEPROMPT; - Audio_PlaySfx_MessageDecide(); - break; - } + if (GameInteractor_Should(VB_SAVE_ON_B_BUTTON_IN_PAUSE_MENU, false)) { + pauseCtx->state = PAUSE_STATE_SAVEPROMPT; + Audio_PlaySfx_MessageDecide(); + break; } pauseCtx->state = PAUSE_STATE_UNPAUSE_SETUP; sPauseMenuVerticalOffset = -6240.0f; @@ -3522,12 +3518,10 @@ void KaleidoScope_Update(PlayState* play) { if (CHECK_BTN_ALL(input->press.button, BTN_START) || CHECK_BTN_ALL(input->press.button, BTN_B)) { AudioOcarina_SetInstrument(OCARINA_INSTRUMENT_OFF); Interface_SetAButtonDoAction(play, DO_ACTION_NONE); - if (CVarGetInteger("gEnhancements.Saving.PauseSave", 0) && SavingEnhancements_CanSave()) { - if (CHECK_BTN_ALL(input->press.button, BTN_B)) { - pauseCtx->state = PAUSE_STATE_SAVEPROMPT; - Audio_PlaySfx_MessageDecide(); - break; - } + if (GameInteractor_Should(VB_SAVE_ON_B_BUTTON_IN_PAUSE_MENU, false)) { + pauseCtx->state = PAUSE_STATE_SAVEPROMPT; + Audio_PlaySfx_MessageDecide(); + break; } pauseCtx->state = PAUSE_STATE_UNPAUSE_SETUP; sPauseMenuVerticalOffset = -6240.0f; From 53b70bfd82114bd18198759b01144c5a7abd656a Mon Sep 17 00:00:00 2001 From: mckinlee Date: Thu, 18 Dec 2025 09:22:06 -0500 Subject: [PATCH 20/44] Fix Combobox Search Filter (#1402) Added static map to maintain filter state for each combobox instance. --- mm/2s2h/BenGui/UIWidgets.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mm/2s2h/BenGui/UIWidgets.hpp b/mm/2s2h/BenGui/UIWidgets.hpp index fc5428d15b..6ee6680268 100644 --- a/mm/2s2h/BenGui/UIWidgets.hpp +++ b/mm/2s2h/BenGui/UIWidgets.hpp @@ -1068,8 +1068,10 @@ bool ComboboxWithSearch(const char* label, T* value, const std::unordered_mapat(*value), options.flags)) { - // Local filter, no persistence - ImGuiTextFilter filter; + // Use static map to maintain filter state per combobox instance + static std::unordered_map filters; + ImGuiID filterId = ImGui::GetID("##search"); + ImGuiTextFilter& filter = filters[filterId]; // Focus search input when dropdown first opens if (ImGui::IsWindowAppearing()) { From a46f7b377664a616a4d1fb9fa9bfd98676443245 Mon Sep 17 00:00:00 2001 From: ErawanJohnson <1121xxx@gmail.com> Date: Tue, 30 Dec 2025 06:20:06 -0800 Subject: [PATCH 21/44] [Rando] first pass of masks required to fight majora (#1279) * first pass of masks required to fight majora * adding in moon access options * Update mm/2s2h/Rando/Logic/Regions/Moon.cpp Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> * streamlined the menu to always show moon and masks count. Also ran the clang formatter * Update mm/2s2h/Rando/Logic/Regions/Moon.cpp Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> * Update mm/2s2h/Rando/StaticData/Options.cpp Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> * Update mm/2s2h/Rando/Types.h Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> * Update mm/2s2h/Rando/Types.h Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> * Update mm/2s2h/Rando/Types.h Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> * Update mm/2s2h/Rando/Menu.cpp Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> --------- Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> --- mm/2s2h/Rando/ActorBehavior/EnJs.cpp | 3 ++- mm/2s2h/Rando/Logic/Logic.h | 3 ++- mm/2s2h/Rando/Logic/Regions/Moon.cpp | 3 ++- mm/2s2h/Rando/Menu.cpp | 5 +++++ mm/2s2h/Rando/StaticData/Options.cpp | 2 ++ mm/2s2h/Rando/Types.h | 3 +++ 6 files changed, 16 insertions(+), 3 deletions(-) diff --git a/mm/2s2h/Rando/ActorBehavior/EnJs.cpp b/mm/2s2h/Rando/ActorBehavior/EnJs.cpp index b61c959a00..f67ae02955 100644 --- a/mm/2s2h/Rando/ActorBehavior/EnJs.cpp +++ b/mm/2s2h/Rando/ActorBehavior/EnJs.cpp @@ -116,7 +116,8 @@ void OverrideMainJsText(u16* textId, bool* loadFromMessageTable) { override = true; } - if (Rando::Logic::RemainsCount() < RANDO_SAVE_OPTIONS[RO_ACCESS_MAJORA_REMAINS_COUNT]) { + if (Rando::Logic::MoonMaskCount() < RANDO_SAVE_OPTIONS[RO_ACCESS_MAJORA_MASKS_COUNT] || + Rando::Logic::RemainsCount() < RANDO_SAVE_OPTIONS[RO_ACCESS_MAJORA_REMAINS_COUNT]) { entry.msg = "You are not strong enough to play with me..."; entry.nextMessageID = 0x21FD; override = true; diff --git a/mm/2s2h/Rando/Logic/Logic.h b/mm/2s2h/Rando/Logic/Logic.h index 1eb05f4341..3665e82a59 100644 --- a/mm/2s2h/Rando/Logic/Logic.h +++ b/mm/2s2h/Rando/Logic/Logic.h @@ -182,7 +182,8 @@ inline uint32_t RemainsCount() { } inline bool MeetsMoonRequirements() { - return RemainsCount() >= RANDO_SAVE_OPTIONS[RO_ACCESS_MOON_REMAINS_COUNT]; + return RemainsCount() >= RANDO_SAVE_OPTIONS[RO_ACCESS_MOON_REMAINS_COUNT] && + MoonMaskCount() >= RANDO_SAVE_OPTIONS[RO_ACCESS_MOON_MASKS_COUNT]; } inline bool CanKillEnemy(ActorId EnemyId) { diff --git a/mm/2s2h/Rando/Logic/Regions/Moon.cpp b/mm/2s2h/Rando/Logic/Regions/Moon.cpp index 21d83996b6..43c4a34f45 100644 --- a/mm/2s2h/Rando/Logic/Regions/Moon.cpp +++ b/mm/2s2h/Rando/Logic/Regions/Moon.cpp @@ -112,7 +112,8 @@ static RegisterShipInitFunc initFunc([]() { (RANDO_SAVE_OPTIONS[RO_ACCESS_TRIALS] == RO_ACCESS_TRIALS_FORMS) || (RANDO_SAVE_OPTIONS[RO_ACCESS_TRIALS] == RO_ACCESS_TRIALS_OPEN) ), - EXIT(ENTRANCE(MAJORAS_LAIR, 0), ONE_WAY_EXIT, RemainsCount() >= RANDO_SAVE_OPTIONS[RO_ACCESS_MAJORA_REMAINS_COUNT]), + EXIT(ENTRANCE(MAJORAS_LAIR, 0), ONE_WAY_EXIT, (RemainsCount() >= RANDO_SAVE_OPTIONS[RO_ACCESS_MAJORA_REMAINS_COUNT]) && (MoonMaskCount() >= RANDO_SAVE_OPTIONS[RO_ACCESS_MAJORA_MASKS_COUNT]) + ), }, .oneWayEntrances = { ENTRANCE(THE_MOON, 0), // From rooftop and trials diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index a57ae24469..cbf0452ccc 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -197,9 +197,14 @@ static void DrawLogicConditionsTab() { UIWidgets::CVarSliderInt("Majora Access Remains Required", Rando::StaticData::Options[RO_ACCESS_MAJORA_REMAINS_COUNT].cvar, IntSliderOptions().Min(0).Max(4).DefaultValue(0)); + UIWidgets::CVarSliderInt("Majora Access Masks Required", + Rando::StaticData::Options[RO_ACCESS_MAJORA_MASKS_COUNT].cvar, + IntSliderOptions().Min(0).Max(20).DefaultValue(0)); UIWidgets::CVarSliderInt("Moon Access Remains Required", Rando::StaticData::Options[RO_ACCESS_MOON_REMAINS_COUNT].cvar, IntSliderOptions().Min(0).Max(4).DefaultValue(4)); + UIWidgets::CVarSliderInt("Moon Access Masks Required", Rando::StaticData::Options[RO_ACCESS_MOON_MASKS_COUNT].cvar, + IntSliderOptions().Min(0).Max(20).DefaultValue(0)); UIWidgets::CVarCombobox("Trials Access", Rando::StaticData::Options[RO_ACCESS_TRIALS].cvar, &accessTrialsOptions); ImGui::EndChild(); ImGui::BeginChild("randoLogicTricks", ImVec2(0, 0)); diff --git a/mm/2s2h/Rando/StaticData/Options.cpp b/mm/2s2h/Rando/StaticData/Options.cpp index 0b8f6f0629..eb05930b0f 100644 --- a/mm/2s2h/Rando/StaticData/Options.cpp +++ b/mm/2s2h/Rando/StaticData/Options.cpp @@ -18,7 +18,9 @@ namespace StaticData { // clang-format off std::map Options = { RO(RO_ACCESS_DUNGEONS, RO_ACCESS_DUNGEONS_FORM_AND_SONG), + RO(RO_ACCESS_MAJORA_MASKS_COUNT, 0), RO(RO_ACCESS_MAJORA_REMAINS_COUNT, 0), + RO(RO_ACCESS_MOON_MASKS_COUNT, 0), RO(RO_ACCESS_MOON_REMAINS_COUNT, 4), RO(RO_ACCESS_TRIALS, RO_ACCESS_TRIALS_20_MASKS), RO(RO_HINTS_BOSS_REMAINS, RO_GENERIC_OFF), diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h index 7e330300f8..6c640e59e5 100644 --- a/mm/2s2h/Rando/Types.h +++ b/mm/2s2h/Rando/Types.h @@ -2804,7 +2804,10 @@ typedef enum { typedef enum { RO_ACCESS_DUNGEONS, + RO_ACCESS_MAJORA_MASKS_COUNT, RO_ACCESS_MAJORA_REMAINS_COUNT, + RO_ACCESS_MAJORA_REMAINS, + RO_ACCESS_MOON_MASKS_COUNT, RO_ACCESS_MOON_REMAINS_COUNT, RO_ACCESS_TRIALS, RO_HINTS_BOSS_REMAINS, From e7f1fc011e70a9a9abfe1686d809f955c6cc6ef5 Mon Sep 17 00:00:00 2001 From: mckinlee Date: Tue, 30 Dec 2025 09:20:19 -0500 Subject: [PATCH 22/44] fix crash (#1410) --- .../ItemTracker/ItemTrackerSettings.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp index 2c457d240e..b20b576982 100644 --- a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp +++ b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp @@ -345,8 +345,8 @@ void DrawPreviewPane() { } windowListIndex++; } - ImGui::EndChild(); } + ImGui::EndChild(); } void DrawTrackerWindowOptions(int32_t windowIndex, TrackerItemListObject& windowObject) { @@ -606,9 +606,8 @@ void DrawTrackerCustomizationOptions() { DrawItemList(randoListOrder[rkey], std::get<2>(list)); ImGui::PopID(); } - - ImGui::EndChild(); } + ImGui::EndChild(); } void ItemTrackerSettingsWindow::DrawElement() { @@ -634,28 +633,28 @@ void ItemTrackerSettingsWindow::DrawElement() { if (ImGui::BeginTabItem("Customization")) { if (ImGui::BeginChild("CustomizationChild")) { DrawTrackerCustomizationOptions(); - ImGui::EndChild(); } + ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Options")) { if (ImGui::BeginChild("OptionsChild")) { DrawTrackerOptions(); - ImGui::EndChild(); } + ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Save/Load")) { if (ImGui::BeginChild("SaveChild")) { DrawTrackerSaveLoadOptions(); - ImGui::EndChild(); } + ImGui::EndChild(); ImGui::EndTabItem(); } ImGui::EndTabBar(); } - ImGui::EndChild(); } + ImGui::EndChild(); ImGui::TableNextColumn(); if (ImGui::BeginChild("WindowChild")) { @@ -666,15 +665,15 @@ void ItemTrackerSettingsWindow::DrawElement() { } ImGui::EndTabBar(); } - ImGui::EndChild(); } + ImGui::EndChild(); ImGui::EndTable(); } UIWidgets::PopStyleTabs(); ImGui::PopStyleColor(3); - ImGui::EndChild(); } + ImGui::EndChild(); } void ItemTrackerSettingsWindow::InitElement() { From ffe0a173fb8d9fb0226a80a04cb2dc5a2303a989 Mon Sep 17 00:00:00 2001 From: sitton76 <58642183+sitton76@users.noreply.github.com> Date: Tue, 30 Dec 2025 08:20:28 -0600 Subject: [PATCH 23/44] Snowhead corrections attempt 2 (#1415) * Migrated changes made in other PR * CAN_BE_ZORA, not (CAN_BE_ZORA && CAN_USE_SWORD) --- mm/2s2h/Rando/Logic/Regions/SnowheadTemple.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mm/2s2h/Rando/Logic/Regions/SnowheadTemple.cpp b/mm/2s2h/Rando/Logic/Regions/SnowheadTemple.cpp index e548f56234..438f0f997d 100644 --- a/mm/2s2h/Rando/Logic/Regions/SnowheadTemple.cpp +++ b/mm/2s2h/Rando/Logic/Regions/SnowheadTemple.cpp @@ -51,10 +51,10 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_SNOWHEAD_TEMPLE_BRIDGE_ROOM_AFTER] = RandoRegion{ .sceneId = SCENE_HAKUGIN, .checks = { - CHECK(RC_SNOWHEAD_TEMPLE_BRIDGE_ROOM_CHEST, ((CAN_BE_ZORA && CAN_USE_SWORD) || (CAN_BE_GORON && CAN_USE_SWORD) || CAN_USE_MAGIC_ARROW(FIRE) || HAS_ITEM(ITEM_HOOKSHOT))), - CHECK(RC_SNOWHEAD_TEMPLE_BRIDGE_ROOM_LARGE_CRATE, CAN_BE_GORON || CAN_BE_ZORA), - CHECK(RC_SNOWHEAD_TEMPLE_BRIDGE_ROOM_AFTER_POT_01, CAN_BE_ZORA || CAN_BE_GORON), - CHECK(RC_SNOWHEAD_TEMPLE_BRIDGE_ROOM_AFTER_POT_02, CAN_BE_ZORA || CAN_BE_GORON), + CHECK(RC_SNOWHEAD_TEMPLE_BRIDGE_ROOM_CHEST, (CAN_BE_ZORA || CAN_USE_MAGIC_ARROW(FIRE) || HAS_ITEM(ITEM_HOOKSHOT))), + CHECK(RC_SNOWHEAD_TEMPLE_BRIDGE_ROOM_LARGE_CRATE, true), + CHECK(RC_SNOWHEAD_TEMPLE_BRIDGE_ROOM_AFTER_POT_01, true), + CHECK(RC_SNOWHEAD_TEMPLE_BRIDGE_ROOM_AFTER_POT_02, true), CHECK(RC_SNOWHEAD_TEMPLE_SF_BRIDGE_PILLAR, CAN_USE_PROJECTILE && HAS_ITEM(ITEM_MASK_GREAT_FAIRY)), // Accessible from both sides CHECK(RC_SNOWHEAD_TEMPLE_SF_BRIDGE_UNDER_PLATFORM, CAN_USE_PROJECTILE && HAS_ITEM(ITEM_MASK_GREAT_FAIRY)), // Accessible from both sides }, From f117e7384700ad217bfd13db77234b05daf041f4 Mon Sep 17 00:00:00 2001 From: Caladius Date: Tue, 30 Dec 2025 09:20:36 -0500 Subject: [PATCH 24/44] [Difficulty Option] Skip Younger Beaver Brother Races (#1409) * Skip racing the younger beaver brother. * Update mm/2s2h/Enhancements/Minigames/BeaverRace.cpp Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> * Update mm/2s2h/Enhancements/Minigames/BeaverRace.cpp Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> --------- Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> --- mm/2s2h/BenGui/BenMenu.cpp | 3 ++ mm/2s2h/Enhancements/Minigames/BeaverRace.cpp | 42 ++++++++++++++++--- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 9361ca0360..66a5dd2a29 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -1589,6 +1589,9 @@ void BenMenu::AddEnhancements() { .Min(1) .Max(20) .DefaultValue(20)); + AddWidget(path, "Skip Little Beaver Brother Races", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Minigames.SkipLittleBeaver") + .Options(CheckboxOptions().Tooltip("Only Race the Older Beaver.")); AddWidget(path, "Goron Race", WIDGET_CVAR_COMBOBOX) .CVar("gEnhancements.DifficultyOptions.GoronRace") .Options(ComboboxOptions() diff --git a/mm/2s2h/Enhancements/Minigames/BeaverRace.cpp b/mm/2s2h/Enhancements/Minigames/BeaverRace.cpp index 24c03b85f2..429a36bcdc 100644 --- a/mm/2s2h/Enhancements/Minigames/BeaverRace.cpp +++ b/mm/2s2h/Enhancements/Minigames/BeaverRace.cpp @@ -1,6 +1,7 @@ #include #include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/ShipInit.hpp" +#include "CustomMessage/CustomMessage.h" extern "C" { #include "overlays/actors/ovl_En_Az/z_en_az.h" @@ -9,18 +10,21 @@ void func_80A979DC(EnAz* thisx, PlayState* play); void func_80A97F9C(EnAz* thisx, PlayState* play); } -#define CVAR_NAME "gEnhancements.Minigames.BeaverRaceRingsCollected" -#define CVAR CVarGetInteger(CVAR_NAME, 20) +#define CVAR_RINGS_NAME "gEnhancements.Minigames.BeaverRaceRingsCollected" +#define CVAR_RINGS CVarGetInteger(CVAR_RINGS_NAME, 20) + +#define CVAR_SPEEDUP_NAME "gEnhancements.Minigames.SkipLittleBeaver" +#define CVAR_SPEEDUP CVarGetInteger(CVAR_SPEEDUP_NAME, 0) static bool minigameScoreSet = false; // Flag to track if the score has been set -void RegisterBeaverRace() { - COND_ID_HOOK(ShouldActorUpdate, ACTOR_EN_AZ, CVAR < 20, [](Actor* actor, bool* should) { +void RegisterBeaverRaceRings() { + COND_ID_HOOK(ShouldActorUpdate, ACTOR_EN_AZ, CVAR_RINGS < 20, [](Actor* actor, bool* should) { EnAz* enAz = (EnAz*)actor; Player* player = GET_PLAYER(gPlayState); if (!minigameScoreSet) { - gSaveContext.minigameScore = CVAR; + gSaveContext.minigameScore = CVAR_RINGS; minigameScoreSet = true; // Set the flag after assignment } @@ -60,4 +64,30 @@ void RegisterBeaverRace() { }); } -static RegisterShipInitFunc initFunc(RegisterBeaverRace, { CVAR_NAME }); +void RegisterBeaverRaceSpeedup() { + COND_ID_HOOK(ShouldActorUpdate, ACTOR_EN_AZ, CVAR_SPEEDUP, [](Actor* actor, bool* should) { + if (!CHECK_WEEKEVENTREG(WEEKEVENTREG_24_04)) { + SET_WEEKEVENTREG(WEEKEVENTREG_24_04); + } + }); + + COND_ID_HOOK(OnOpenText, 0x10D6, CVAR_SPEEDUP, [](u16* textId, bool* loadFromMessageTable) { + auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + entry.msg = "My older brother will show you\x11the way, so follow him and\x11" + "don't get separated!"; + + CustomMessage::LoadCustomMessageIntoFont(entry); + *loadFromMessageTable = false; + }); + + COND_ID_HOOK(OnOpenText, 0x10FA, CVAR_SPEEDUP, [](u16* textId, bool* loadFromMessageTable) { + auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + entry.msg = "The time limit is %r1:50%w, let's race."; + + CustomMessage::LoadCustomMessageIntoFont(entry); + *loadFromMessageTable = false; + }); +} + +static RegisterShipInitFunc initRingsFunc(RegisterBeaverRaceRings, { CVAR_RINGS_NAME }); +static RegisterShipInitFunc initSpeedupFunc(RegisterBeaverRaceSpeedup, { CVAR_SPEEDUP_NAME }); From 14902f06c46b51e76aed63214fcc481dba5fd953 Mon Sep 17 00:00:00 2001 From: mckinlee Date: Tue, 30 Dec 2025 09:20:44 -0500 Subject: [PATCH 25/44] [Enhancement] Ammo Buyback (#1407) * initial implementation * feedback * feedback 2 --- mm/2s2h/BenGui/BenMenu.cpp | 16 + mm/2s2h/Enhancements/Enhancements.h | 6 + mm/2s2h/Enhancements/Items/AmmoBuyback.cpp | 408 ++++++++++++++++++ .../GameInteractor_VanillaBehavior.h | 16 + mm/src/code/z_message.c | 40 +- mm/src/code/z_message_nes.c | 22 +- 6 files changed, 493 insertions(+), 15 deletions(-) create mode 100644 mm/2s2h/Enhancements/Items/AmmoBuyback.cpp diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 66a5dd2a29..04d14a58cd 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -57,6 +57,12 @@ static const std::vector cremiaRewardOptions = { "Rupee", // CREMIA_REWARD_ALWAYS_RUPEE }; +static const std::vector ammoBuybackOptions = { + "Vanilla", // AMMO_BUYBACK_VANILLA + "Full Price", // AMMO_BUYBACK_FULL_PRICE + "Half Price", // AMMO_BUYBACK_HALF_PRICE +}; + static const std::vector gibdoTradeSequenceOptions = { "Vanilla", // GIBDO_TRADE_SEQUENCE_VANILLA "MM3D", // GIBDO_TRADE_SEQUENCE_MM3D @@ -1020,6 +1026,16 @@ void BenMenu::AddEnhancements() { "-Hug: Get the hugging cutscene\n" "-Rupee: Get the rupee reward") .ComboVec(&cremiaRewardOptions)); + AddWidget(path, "Ammo Buyback Options", WIDGET_CVAR_COMBOBOX) + .CVar("gEnhancements.Items.AmmoBuyback") + .Options(ComboboxOptions() + .Tooltip("Choose whether to allow selling ammo items (Arrows, Bombs, Bombchus, Deku Sticks, Deku " + "Nuts, Magic Beans, Powder Keg) " + "to the Curiosity Shop owner for Rupees.\n" + "-Vanilla: Ammo items cannot be sold\n" + "-Full Price: Sell at full value\n" + "-Half Price: Sell at half value (rounded up)") + .ComboVec(&ammoBuybackOptions)); AddWidget(path, "Accessibility", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Disable Screen Flash for Enemy Kills", WIDGET_CVAR_CHECKBOX) .CVar("gEnhancements.A11y.NoScreenFlashForEnemyKill") diff --git a/mm/2s2h/Enhancements/Enhancements.h b/mm/2s2h/Enhancements/Enhancements.h index 8b120dadf8..13d8a58841 100644 --- a/mm/2s2h/Enhancements/Enhancements.h +++ b/mm/2s2h/Enhancements/Enhancements.h @@ -27,6 +27,12 @@ enum CremiaRewardsOptions { CREMIA_REWARD_ALWAYS_RUPEE, }; +enum AmmoBuybackOptions { + AMMO_BUYBACK_VANILLA, + AMMO_BUYBACK_FULL_PRICE, + AMMO_BUYBACK_HALF_PRICE, +}; + enum GibdoTradeSequenceOptions { GIBDO_TRADE_SEQUENCE_VANILLA, GIBDO_TRADE_SEQUENCE_MM3D, diff --git a/mm/2s2h/Enhancements/Items/AmmoBuyback.cpp b/mm/2s2h/Enhancements/Items/AmmoBuyback.cpp new file mode 100644 index 0000000000..ded163021f --- /dev/null +++ b/mm/2s2h/Enhancements/Items/AmmoBuyback.cpp @@ -0,0 +1,408 @@ +#include +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" +#include "2s2h/CustomMessage/CustomMessage.h" +#include "2s2h/Enhancements/Enhancements.h" +#include "message_data_fmt_nes.h" + +extern "C" { +#include "overlays/actors/ovl_En_Fsn/z_en_fsn.h" + +void EnFsn_MakeOffer(EnFsn* thisx, PlayState* play); +void EnFsn_GiveItem(EnFsn* thisx, PlayState* play); +void EnFsn_StartBuying(EnFsn* thisx, PlayState* play); +void EnFsn_ResumeInteraction(EnFsn* enFsn, PlayState* play); +void Player_StartTalking(PlayState* play, Actor* actor); +void Player_StopCutscene(Player* player); +} + +#define CVAR_NAME "gEnhancements.Items.AmmoBuyback" +#define CVAR CVarGetInteger(CVAR_NAME, AMMO_BUYBACK_VANILLA) + +#define TEXT_ID_FSN_REJECTION 0x29CF // "Sorry, but I can't sell that here." +#define TEXT_ID_FSN_OFFER 0x29EF // Price offer dialogue +#define MAX_AMMO_SELL_QUANTITY 99 +#define STICK_THRESHOLD 30 +#define REPEAT_DELAY_INITIAL 10 +#define REPEAT_DELAY_CONTINUOUS 2 + +static struct { + EnFsn* actor = nullptr; + ItemId itemId = ITEM_NONE; + s16 quantity = 0; + s16 price = 0; + s16 lastRupeesSelected = 0; + bool isInputActive = false; +} sAmmoSale; + +static void Reset() { + sAmmoSale = {}; +} + +static s16 GetPricePerUnit(ItemId itemId) { + switch (itemId) { + case ITEM_BOW: + return 1; + case ITEM_BOMB: + return 3; + case ITEM_BOMBCHU: + return 4; + case ITEM_DEKU_STICK: + return 10; + case ITEM_DEKU_NUT: + return 3; + case ITEM_MAGIC_BEANS: + return 10; + case ITEM_POWDER_KEG: + return 50; + default: + return 0; + } +} + +static const char* GetNameForDialogue(ItemId itemId) { + switch (itemId) { + case ITEM_BOW: + return "%rArrows%w"; + case ITEM_BOMB: + return "%rBombs%w"; + case ITEM_BOMBCHU: + return "%rBombchus%w"; + case ITEM_DEKU_STICK: + return "%rDeku Sticks%w"; + case ITEM_DEKU_NUT: + return "%rDeku Nuts%w"; + case ITEM_MAGIC_BEANS: + return "%rMagic Beans%w"; + case ITEM_POWDER_KEG: + return "%rPowder Kegs%w"; + default: + return "%ritems%w"; + } +} + +static s16 GetSalePrice(ItemId itemId, s16 count) { + s16 totalPrice = count * GetPricePerUnit(itemId); + if (CVAR == AMMO_BUYBACK_HALF_PRICE) { + totalPrice = (totalPrice + 1) / 2; + } + return totalPrice; +} + +static bool IsItemAvailable(ItemId itemId) { + if (itemId == ITEM_POWDER_KEG) { + return INV_CONTENT(ITEM_POWDER_KEG) == ITEM_POWDER_KEG && AMMO(ITEM_POWDER_KEG) > 0; + } + if (itemId == ITEM_MAGIC_BEANS) { + return INV_CONTENT(ITEM_MAGIC_BEANS) == ITEM_MAGIC_BEANS && AMMO(ITEM_MAGIC_BEANS) > 0; + } + return Item_CheckObtainability(itemId) != ITEM_NONE; +} + +static void UpdateDigits(PlayState* play, s16 value) { + if (play->msgCtx.unk120C0 == 0) + return; + + char digits[3]; + // Layout: "XX0" where 3rd digit is hidden. + // Forces left alignment and matches internal *10 calc. + digits[0] = ((value / 10) % 10) + '0'; + digits[1] = (value % 10) + '0'; + digits[2] = '0'; + + // Writes digits and extra space to decodedBuffer + play->msgCtx.decodedBuffer.schar[play->msgCtx.unk120C0] = digits[0]; + play->msgCtx.decodedBuffer.schar[play->msgCtx.unk120C0 + 1] = digits[1]; + play->msgCtx.decodedBuffer.schar[play->msgCtx.unk120C0 + 2] = digits[2]; + play->msgCtx.decodedBuffer.schar[play->msgCtx.unk120C0 + 3] = ' '; + + // Render: [Tens] [Units] [Space] + Font_LoadCharNES(play, digits[0], play->msgCtx.unk120C4); + Font_LoadCharNES(play, digits[1], play->msgCtx.unk120C4 + (1 << 7)); + Font_LoadCharNES(play, ' ', play->msgCtx.unk120C4 + (2 << 7)); +} + +static bool UpdateInputState(bool isActive, bool* isHeld, s16* timer) { + if (!isActive) { + *isHeld = false; + *timer = 0; + return false; + } + if (!*isHeld) { + *isHeld = true; + *timer = REPEAT_DELAY_INITIAL; + return true; + } + if (*timer > 0) { + (*timer)--; + return false; + } + *timer = REPEAT_DELAY_CONTINUOUS; + return true; +} + +static void HandleStart(EnFsn* enFsn, PlayState* play) { + if (enFsn->price != 0 || enFsn->actor.textId != TEXT_ID_FSN_REJECTION) + return; + + Player* player = GET_PLAYER(play); + ItemId buttonItem = (ItemId)(IS_HELD_DPAD(player->heldItemButton) + ? DPAD_GET_CUR_FORM_BTN_ITEM(HELD_ITEM_TO_DPAD(player->heldItemButton)) + : GET_CUR_FORM_BTN_ITEM(player->heldItemButton)); + + if (GetPricePerUnit(buttonItem) > 0 && IsItemAvailable(buttonItem) && AMMO(buttonItem) > 0) { + sAmmoSale.actor = enFsn; + sAmmoSale.itemId = buttonItem; + sAmmoSale.lastRupeesSelected = 10; // Default for input mode + + // If player only has 1, skip inputs + s16 maxAllowed = MIN(AMMO(buttonItem), MAX_AMMO_SELL_QUANTITY); + + if (maxAllowed == 1) { + sAmmoSale.quantity = 1; + sAmmoSale.price = GetSalePrice(buttonItem, 1); + sAmmoSale.isInputActive = false; + enFsn->price = sAmmoSale.price; + } else { + sAmmoSale.quantity = 1; + sAmmoSale.price = 0; + sAmmoSale.isInputActive = true; + play->msgCtx.rupeesSelected = 10; + } + + enFsn->actionFunc = EnFsn_MakeOffer; + player->actor.textId = TEXT_ID_FSN_OFFER; + } +} + +static void HandleInput(EnFsn* enFsn, PlayState* play) { + static bool sStickHeldX = false, sStickHeldY = false; + static s16 sRepeatTimerX = 0, sRepeatTimerY = 0; + + MessageContext* msgCtx = &play->msgCtx; + Input* input = &play->state.input[0]; + + // Prevent input processing if the message system hasn't loaded our text yet + if (msgCtx->currentTextId != TEXT_ID_FSN_OFFER || msgCtx->unk120C0 == 0 || msgCtx->rupeesSelected < 10 || + msgCtx->msgMode == MSGMODE_NONE || msgCtx->msgMode == MSGMODE_TEXT_START || + msgCtx->msgMode == MSGMODE_TEXT_BOX_GROWING || msgCtx->msgMode == MSGMODE_TEXT_STARTING) { + return; + } + + s16 currentQuantity = (s16)(msgCtx->rupeesSelected / 10); + s16 newQuantity = currentQuantity; + s16 maxAllowed = MIN(AMMO(sAmmoSale.itemId), MAX_AMMO_SELL_QUANTITY); + + // Enforce cursor on visible digits (0=Tens, 1=Units) + if (msgCtx->unk120C2 > 1) + msgCtx->unk120C2 = 1; + + // Vertical Input (Quantity) + bool inputUp = CHECK_BTN_ALL(input->cur.button, BTN_DUP) || input->rel.stick_y >= STICK_THRESHOLD; + bool inputDown = CHECK_BTN_ALL(input->cur.button, BTN_DDOWN) || input->rel.stick_y <= -STICK_THRESHOLD; + s16 delta = (msgCtx->unk120C2 == 0) ? 10 : 1; + + if (inputUp) { + if (UpdateInputState(true, &sStickHeldY, &sRepeatTimerY)) + newQuantity += delta; + } else if (inputDown) { + if (UpdateInputState(true, &sStickHeldY, &sRepeatTimerY)) + newQuantity -= delta; + } else { + UpdateInputState(false, &sStickHeldY, &sRepeatTimerY); + } + + // Horizontal Input (Cursor) + bool inputRight = CHECK_BTN_ALL(input->cur.button, BTN_DRIGHT) || input->rel.stick_x >= STICK_THRESHOLD; + bool inputLeft = CHECK_BTN_ALL(input->cur.button, BTN_DLEFT) || input->rel.stick_x <= -STICK_THRESHOLD; + + if (inputRight) { + if (UpdateInputState(true, &sStickHeldX, &sRepeatTimerX)) { + msgCtx->unk120C2 = (msgCtx->unk120C2 >= 1) ? 1 : msgCtx->unk120C2 + 1; + Audio_PlaySfx(NA_SE_SY_CURSOR); + } + } else if (inputLeft) { + if (UpdateInputState(true, &sStickHeldX, &sRepeatTimerX)) { + msgCtx->unk120C2 = (msgCtx->unk120C2 <= 0) ? 0 : msgCtx->unk120C2 - 1; + Audio_PlaySfx(NA_SE_SY_CURSOR); + } + } else { + UpdateInputState(false, &sStickHeldX, &sRepeatTimerX); + } + + // Shortcuts + if (CHECK_BTN_ALL(input->press.button, BTN_Z)) + newQuantity = maxAllowed; + if (CHECK_BTN_ALL(input->press.button, BTN_R)) + newQuantity = 1; + + // Cleanup & Update + input->press.button &= ~(BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT); + input->cur.button &= ~(BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT); + input->rel.stick_x = 0; + input->rel.stick_y = 0; + + newQuantity = CLAMP(newQuantity, 1, maxAllowed); + + if (newQuantity != currentQuantity) { + msgCtx->rupeesSelected = newQuantity * 10; + UpdateDigits(play, newQuantity); + } + + if (newQuantity != (sAmmoSale.lastRupeesSelected / 10)) { + Audio_PlaySfx(NA_SE_SY_FSEL_CURSOR); + sAmmoSale.lastRupeesSelected = newQuantity * 10; + } + + if (Message_ShouldAdvance(play)) { + sAmmoSale.quantity = newQuantity; + sAmmoSale.price = GetSalePrice(sAmmoSale.itemId, newQuantity); + + enFsn->price = sAmmoSale.price; + sAmmoSale.isInputActive = false; + Message_StartTextbox(play, TEXT_ID_FSN_OFFER, &enFsn->actor); + } +} + +static void Resolve(EnFsn* enFsn, bool* shouldGiveItem) { + if (enFsn->actionFunc == EnFsn_GiveItem) { + *shouldGiveItem = false; + + Inventory_ChangeAmmo(sAmmoSale.itemId, -sAmmoSale.quantity); + Rupees_ChangeBy(sAmmoSale.price); + + enFsn->actor.parent = nullptr; + enFsn->actor.flags |= ACTOR_FLAG_TALK; + enFsn->actor.textId = 0; + enFsn->actionFunc = EnFsn_ResumeInteraction; + + Player* player = GET_PLAYER(gPlayState); + player->talkActor = &enFsn->actor; + player->talkActorDistance = enFsn->actor.xzDistToPlayer; + player->exchangeItemAction = PLAYER_IA_MINUS1; + player->getItemDrawIdPlusOne = 0; + player->getItemId = GI_NONE; + player->interactRangeActor = nullptr; + + Player_StopCutscene(player); + Player_StartTalking(gPlayState, &enFsn->actor); + + Reset(); + } +} + +static void HandleOfferResponse(EnFsn* enFsn, PlayState* play) { + if (sAmmoSale.isInputActive) + return; + + // Guard against these text states to prevent premature Reset + if (play->msgCtx.msgMode == MSGMODE_NONE || play->msgCtx.msgMode == MSGMODE_TEXT_START || + play->msgCtx.msgMode == MSGMODE_TEXT_BOX_GROWING || play->msgCtx.msgMode == MSGMODE_TEXT_STARTING) { + return; + } + + u8 talkState = Message_GetState(&play->msgCtx); + + // If choice was made (not 0/Yes), or text ended, reset + if ((talkState == TEXT_STATE_CHOICE && Message_ShouldAdvance(play) && play->msgCtx.choiceIndex != 0) || + talkState == TEXT_STATE_NONE) { + Reset(); + } +} + +static void DrawAmmoSelectionDigits() { + if (!sAmmoSale.isInputActive) + return; + + PlayState* play = gPlayState; + if (play->msgCtx.rupeesSelected < 10) { + play->msgCtx.rupeesSelected = 10; + sAmmoSale.lastRupeesSelected = 10; + } + UpdateDigits(play, play->msgCtx.rupeesSelected / 10); +} + +static void GenerateBuybackDialogue(u16* textId, bool* loadFromMessageTable) { + if (!sAmmoSale.actor) + return; + + if (sAmmoSale.isInputActive) { + // Custom Input Textbox + CustomMessage::Entry entry; + entry.textboxType = 6; + entry.textboxYPos = 1; + entry.msg = (char)MESSAGE_QUICKTEXT_ENABLE; + entry.msg += std::string("How many ") + GetNameForDialogue(sAmmoSale.itemId) + "? " + (char)MESSAGE_NEWLINE + + (char)MESSAGE_INPUT_BANK + (char)MESSAGE_NEWLINE + "Set the amount with " + + (char)MESSAGE_CONTROL_PAD + " and" + (char)MESSAGE_NEWLINE + "press " + (char)MESSAGE_BTN_A + + " to decide." + (char)MESSAGE_END; + + CustomMessage::LoadCustomMessageIntoFont(entry); + *loadFromMessageTable = false; + } else { + // Offer Textbox with Price Injection + auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + entry.autoFormat = false; + CustomMessage::Replace(&entry.msg, std::string(1, (char)MESSAGE_HELD_ITEM_PRICE), + std::to_string(sAmmoSale.price) + " Rupees"); + CustomMessage::LoadCustomMessageIntoFont(entry); + *loadFromMessageTable = false; + } +} + +static void UpdateBuybackInteraction(Actor* actor) { + EnFsn* enFsn = (EnFsn*)actor; + PlayState* play = gPlayState; + + if (enFsn->actionFunc == EnFsn_StartBuying) { + HandleStart(enFsn, play); + return; + } + if (sAmmoSale.actor != enFsn) + return; + if (sAmmoSale.isInputActive) + HandleInput(enFsn, play); + if (enFsn->actionFunc == EnFsn_MakeOffer) + HandleOfferResponse(enFsn, play); +} + +static void OnFsnDestroy(Actor* actor) { + if (sAmmoSale.actor == (EnFsn*)actor) + Reset(); +} + +static void BlockAmmoBuybackInput() { + if (!gPlayState || (GameState*)gPlayState != gGameState) + return; + + if (sAmmoSale.isInputActive) { + Input* input = &gPlayState->state.input[0]; + input->press.button &= ~(BTN_B | BTN_CUP); + input->cur.button &= ~(BTN_B | BTN_CUP); + } +} + +static void RegisterAmmoBuyback() { + COND_HOOK(OnInterfaceDrawStart, CVAR != AMMO_BUYBACK_VANILLA, DrawAmmoSelectionDigits); + COND_ID_HOOK(OnOpenText, TEXT_ID_FSN_OFFER, CVAR != AMMO_BUYBACK_VANILLA, GenerateBuybackDialogue); + COND_ID_HOOK(OnActorUpdate, ACTOR_EN_FSN, CVAR != AMMO_BUYBACK_VANILLA, UpdateBuybackInteraction); + COND_ID_HOOK(OnActorDestroy, ACTOR_EN_FSN, CVAR != AMMO_BUYBACK_VANILLA, OnFsnDestroy); + COND_HOOK(OnGameStateMainStart, CVAR != AMMO_BUYBACK_VANILLA, BlockAmmoBuybackInput); + + COND_VB_SHOULD(VB_MSG_LOAD_RUPEES_TEXT, CVAR != AMMO_BUYBACK_VANILLA, { + if (sAmmoSale.isInputActive) + *should = false; + }); + + COND_VB_SHOULD(VB_MSG_PLAY_INPUT_COUNT_SOUND, sAmmoSale.isInputActive, { *should = false; }); + + COND_VB_SHOULD(VB_GIVE_ITEM_FROM_OFFER, CVAR != AMMO_BUYBACK_VANILLA, { + GetItemId* item = va_arg(args, GetItemId*); + Actor* actor = va_arg(args, Actor*); + if (sAmmoSale.actor != nullptr && sAmmoSale.actor == (EnFsn*)actor) { + Resolve((EnFsn*)actor, should); + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterAmmoBuyback, { CVAR_NAME }); diff --git a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h index 725b840536..a4bbe1793a 100644 --- a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h +++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h @@ -1189,6 +1189,22 @@ typedef enum { // - `*EnMnk` VB_MONKEY_WAIT_TO_TALK_AFTER_APPROACH, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_MSG_LOAD_RUPEES_TEXT, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_MSG_PLAY_INPUT_COUNT_SOUND, + // #### `result` // ```c // true diff --git a/mm/src/code/z_message.c b/mm/src/code/z_message.c index 4ce98b5bd8..0be1e96e8c 100644 --- a/mm/src/code/z_message.c +++ b/mm/src/code/z_message.c @@ -688,7 +688,9 @@ void func_80148D64(PlayState* play) { } Font_LoadCharNES(play, msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2], msgCtx->unk120C4 + (msgCtx->unk120C2 << 7)); - Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + if (GameInteractor_Should(VB_MSG_PLAY_INPUT_COUNT_SOUND, true)) { + Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + } } else if (msgCtx->stickAdjY >= 30) { msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2]++; if (msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2] > '9') { @@ -696,7 +698,9 @@ void func_80148D64(PlayState* play) { } Font_LoadCharNES(play, msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2], msgCtx->unk120C4 + (msgCtx->unk120C2 << 7)); - Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + if (GameInteractor_Should(VB_MSG_PLAY_INPUT_COUNT_SOUND, true)) { + Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + } } else if ((msgCtx->stickAdjX >= 30) && !sAnalogStickHeld) { sAnalogStickHeld = true; msgCtx->unk120C2++; @@ -732,7 +736,9 @@ void func_80149048(PlayState* play) { } Font_LoadCharNES(play, msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2], msgCtx->unk120C4 + (msgCtx->unk120C2 << 7)); - Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + if (GameInteractor_Should(VB_MSG_PLAY_INPUT_COUNT_SOUND, true)) { + Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + } } else if (msgCtx->stickAdjY >= 30) { msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2]++; if (msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2] > '9') { @@ -740,7 +746,9 @@ void func_80149048(PlayState* play) { } Font_LoadCharNES(play, msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2], msgCtx->unk120C4 + (msgCtx->unk120C2 << 7)); - Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + if (GameInteractor_Should(VB_MSG_PLAY_INPUT_COUNT_SOUND, true)) { + Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + } } msgCtx->rupeesSelected = (msgCtx->decodedBuffer.schar[msgCtx->unk120C0] - '0') * 10; @@ -758,7 +766,9 @@ void func_801491DC(PlayState* play) { msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2] = msgCtx->unk12054[msgCtx->unk120C2] + '0'; Font_LoadCharNES(play, msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2], msgCtx->unk120C4 + (msgCtx->unk120C2 << 7)); - Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + if (GameInteractor_Should(VB_MSG_PLAY_INPUT_COUNT_SOUND, true)) { + Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + } } else if (msgCtx->stickAdjY >= 30) { msgCtx->unk12054[msgCtx->unk120C2]++; if (msgCtx->unk12054[msgCtx->unk120C2] > 5) { @@ -767,7 +777,9 @@ void func_801491DC(PlayState* play) { msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2] = msgCtx->unk12054[msgCtx->unk120C2] + '0'; Font_LoadCharNES(play, msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2], msgCtx->unk120C4 + (msgCtx->unk120C2 << 7)); - Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + if (GameInteractor_Should(VB_MSG_PLAY_INPUT_COUNT_SOUND, true)) { + Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + } } else if ((msgCtx->stickAdjX >= 30) && !sAnalogStickHeld) { sAnalogStickHeld = true; msgCtx->unk120C2++; @@ -801,7 +813,9 @@ void func_80149454(PlayState* play) { msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2] = msgCtx->unk12054[msgCtx->unk120C2] + '0'; Font_LoadCharNES(play, msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2], msgCtx->unk120C4 + (msgCtx->unk120C2 << 7)); - Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + if (GameInteractor_Should(VB_MSG_PLAY_INPUT_COUNT_SOUND, true)) { + Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + } } else if (msgCtx->stickAdjY >= 30) { msgCtx->unk12054[msgCtx->unk120C2]++; if (msgCtx->unk12054[msgCtx->unk120C2] > 9) { @@ -810,7 +824,9 @@ void func_80149454(PlayState* play) { msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2] = msgCtx->unk12054[msgCtx->unk120C2] + '0'; Font_LoadCharNES(play, msgCtx->decodedBuffer.schar[msgCtx->unk120C0 + msgCtx->unk120C2], msgCtx->unk120C4 + (msgCtx->unk120C2 << 7)); - Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + if (GameInteractor_Should(VB_MSG_PLAY_INPUT_COUNT_SOUND, true)) { + Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + } } else if ((msgCtx->stickAdjX >= 30) && !sAnalogStickHeld) { sAnalogStickHeld = true; msgCtx->unk120C2++; @@ -844,7 +860,9 @@ void func_801496C8(PlayState* play) { msgCtx->decodedBuffer.wchar[msgCtx->unk120C0 + msgCtx->unk120C2] = msgCtx->unk12054[msgCtx->unk120C2] + 0x824F; Font_LoadChar(play, msgCtx->decodedBuffer.wchar[msgCtx->unk120C0 + msgCtx->unk120C2], msgCtx->unk120C4 + (msgCtx->unk120C2 << 7)); - Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + if (GameInteractor_Should(VB_MSG_PLAY_INPUT_COUNT_SOUND, true)) { + Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + } } else if (msgCtx->stickAdjY >= 30) { msgCtx->unk12054[msgCtx->unk120C2]++; if (msgCtx->unk12054[msgCtx->unk120C2] >= 4) { @@ -853,7 +871,9 @@ void func_801496C8(PlayState* play) { msgCtx->decodedBuffer.wchar[msgCtx->unk120C0 + msgCtx->unk120C2] = msgCtx->unk12054[msgCtx->unk120C2] + 0x824F; Font_LoadChar(play, msgCtx->decodedBuffer.wchar[msgCtx->unk120C0 + msgCtx->unk120C2], msgCtx->unk120C4 + (msgCtx->unk120C2 << 7)); - Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + if (GameInteractor_Should(VB_MSG_PLAY_INPUT_COUNT_SOUND, true)) { + Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); + } } else if ((msgCtx->stickAdjX >= 30) && !sAnalogStickHeld) { sAnalogStickHeld = true; msgCtx->unk120C2++; diff --git a/mm/src/code/z_message_nes.c b/mm/src/code/z_message_nes.c index 3cdd9a5fbb..79f6e74772 100644 --- a/mm/src/code/z_message_nes.c +++ b/mm/src/code/z_message_nes.c @@ -4,6 +4,7 @@ #include "assets/interface/message_texture_static/message_texture_static.h" #include #include +#include "2s2h/GameInteractor/GameInteractor.h" f32 sNESFontWidths[160] = { 8.0f, 8.0f, 6.0f, 9.0f, 9.0f, 14.0f, 12.0f, 3.0f, 7.0f, 7.0f, 7.0f, 9.0f, 4.0f, 6.0f, 4.0f, 9.0f, @@ -1291,7 +1292,9 @@ void Message_DecodeNES(PlayState* play) { Message_LoadCharNES(play, digits[i] + '0', &charTexIndex, &spA4, decodedBufPos); decodedBufPos++; } - Message_LoadLocalizedRupeesNES(play, &decodedBufPos, &charTexIndex, &spA4); + if (GameInteractor_Should(VB_MSG_LOAD_RUPEES_TEXT, true)) { + Message_LoadLocalizedRupeesNES(play, &decodedBufPos, &charTexIndex, &spA4); + } } else if (curChar == MESSAGE_RUPEES_SELECTED) { digits[0] = digits[1] = 0; digits[2] = msgCtx->rupeesSelected; @@ -1316,7 +1319,9 @@ void Message_DecodeNES(PlayState* play) { decodedBufPos++; } } - Message_LoadRupeesNES(play, &decodedBufPos, &charTexIndex, &spA4, msgCtx->rupeesSelected); + if (GameInteractor_Should(VB_MSG_LOAD_RUPEES_TEXT, true)) { + Message_LoadRupeesNES(play, &decodedBufPos, &charTexIndex, &spA4, msgCtx->rupeesSelected); + } } else if (curChar == MESSAGE_RUPEES_TOTAL) { digits[0] = digits[1] = digits[2] = 0; digits[3] = msgCtx->rupeesTotal; @@ -1344,7 +1349,9 @@ void Message_DecodeNES(PlayState* play) { decodedBufPos++; } } - Message_LoadRupeesNES(play, &decodedBufPos, &charTexIndex, &spA4, msgCtx->rupeesTotal); + if (GameInteractor_Should(VB_MSG_LOAD_RUPEES_TEXT, true)) { + Message_LoadRupeesNES(play, &decodedBufPos, &charTexIndex, &spA4, msgCtx->rupeesTotal); + } } else if (curChar == MESSAGE_TIME_UNTIL_MOON_CRASH) { Message_LoadTimeNES(play, curChar, &charTexIndex, &spA4, &decodedBufPos); } else if (curChar == MESSAGE_STRAY_FAIRIES) { @@ -1499,7 +1506,10 @@ void Message_DecodeNES(PlayState* play) { Message_LoadCharNES(play, digits[i] + '0', &charTexIndex, &spA4, decodedBufPos); decodedBufPos++; } - Message_LoadPluralRupeesNES(play, &decodedBufPos, &charTexIndex, &spA4); + if (GameInteractor_Should(VB_MSG_LOAD_RUPEES_TEXT, true)) { + Message_LoadPluralRupeesNES(play, &decodedBufPos, &charTexIndex, &spA4); + } + } else if (curChar == MESSAGE_INPUT_BOMBER_CODE) { decodedBufPos++; msgCtx->unk120BE = spC6; @@ -1674,7 +1684,9 @@ void Message_DecodeNES(PlayState* play) { spA4 += 16.0f * msgCtx->textCharScale; } } - Message_LoadPluralRupeesNES(play, &decodedBufPos, &charTexIndex, &spA4); + if (GameInteractor_Should(VB_MSG_LOAD_RUPEES_TEXT, true)) { + Message_LoadPluralRupeesNES(play, &decodedBufPos, &charTexIndex, &spA4); + } } else if (curChar == MESSAGE_BOMBER_CODE) { for (i = 0; i < 5; i++) { //! @bug OoB read & write for i == 4, digits array is only 4 elements From 2df11444a25a6199b570e36032b6a1c2fe552dc4 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:22:51 -0500 Subject: [PATCH 26/44] Only apply fast push to skateblock, not pull (#1418) --- mm/2s2h/Enhancements/Player/FasterPushAndPull.cpp | 8 +++++++- .../overlays/actors/ovl_Obj_Skateblock/z_obj_skateblock.c | 7 +++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/mm/2s2h/Enhancements/Player/FasterPushAndPull.cpp b/mm/2s2h/Enhancements/Player/FasterPushAndPull.cpp index 84bc6c4ffa..323bc4bb30 100644 --- a/mm/2s2h/Enhancements/Player/FasterPushAndPull.cpp +++ b/mm/2s2h/Enhancements/Player/FasterPushAndPull.cpp @@ -6,6 +6,7 @@ extern "C" { #include "overlays/actors/ovl_Bg_Dblue_Movebg/z_bg_dblue_movebg.h" #include "overlays/actors/ovl_Bg_Ikana_Block/z_bg_ikana_block.h" #include "overlays/actors/ovl_Obj_Oshihiki/z_obj_oshihiki.h" +#include "overlays/actors/ovl_Obj_Skateblock/z_obj_skateblock.h" } #define CVAR_NAME "gEnhancements.Player.FasterPushAndPull" @@ -34,7 +35,12 @@ void RegisterFasterPushAndPull() { *should = false; }); - COND_VB_SHOULD(VB_SKATE_BLOCK_BEGIN_MOVE, CVAR, { *should = true; }); + COND_VB_SHOULD(VB_SKATE_BLOCK_BEGIN_MOVE, CVAR, { + // These blocks can only be pushed, not pulled + ObjSkateblock* objSkateblock = va_arg(args, ObjSkateblock*); + s32 directionIndex = va_arg(args, s32); + *should = objSkateblock->unk_172[directionIndex] > 0; + }); COND_VB_SHOULD(VB_BLOCK_BEGIN_MOVE, CVAR, { *should = true; }); diff --git a/mm/src/overlays/actors/ovl_Obj_Skateblock/z_obj_skateblock.c b/mm/src/overlays/actors/ovl_Obj_Skateblock/z_obj_skateblock.c index a37a4c3ba5..0c7c558b45 100644 --- a/mm/src/overlays/actors/ovl_Obj_Skateblock/z_obj_skateblock.c +++ b/mm/src/overlays/actors/ovl_Obj_Skateblock/z_obj_skateblock.c @@ -529,10 +529,9 @@ void func_80A22334(ObjSkateblock* this, PlayState* play) { if (sp2C == -1) { sp30 = false; - } else if (GameInteractor_Should(VB_SKATE_BLOCK_BEGIN_MOVE, - !(this->unk_1C1 & 2) && (this->unk_172[sp2C] > 10) && (D_80A22A10 == 0) && - !func_80A216D4(this, play, 2.0f, &sp20) && !Player_InCsMode(play), - this)) { + } else if (!(this->unk_1C1 & 2) && + GameInteractor_Should(VB_SKATE_BLOCK_BEGIN_MOVE, (this->unk_172[sp2C] > 10), this, sp2C) && + (D_80A22A10 == 0) && !func_80A216D4(this, play, 2.0f, &sp20) && !Player_InCsMode(play)) { func_80A21C88(this, sp2C); func_80A2244C(this); sp30 = false; From bbc2ed1abd9920597ea88865c821f7228911b5a5 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:23:02 -0500 Subject: [PATCH 27/44] Fix mismatched Deku Search Balls labels (#1416) --- mm/2s2h/BenGui/BenMenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 600a0d9553..8d73cb22d4 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -113,8 +113,8 @@ static const std::vector notificationPosition = { }; static const std::vector dekuGuardSearchBallsOptions = { - "Never", // DEKU_GUARD_SEARCH_BALLS_NEVER "Night Only", // DEKU_GUARD_SEARCH_BALLS_NIGHT_ONLY + "Never", // DEKU_GUARD_SEARCH_BALLS_NEVER "Always", // DEKU_GUARD_SEARCH_BALLS_ALWAYS }; From dd5c0f152a788780ef4dac22dd9c902c2d15f0be Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:52:08 -0500 Subject: [PATCH 28/44] Fix softlock from repeating Kafei's Mask check (#1432) --- mm/2s2h/Rando/ActorBehavior/EnAl.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mm/2s2h/Rando/ActorBehavior/EnAl.cpp b/mm/2s2h/Rando/ActorBehavior/EnAl.cpp index e322b99738..726b07b2f2 100644 --- a/mm/2s2h/Rando/ActorBehavior/EnAl.cpp +++ b/mm/2s2h/Rando/ActorBehavior/EnAl.cpp @@ -15,6 +15,12 @@ void Rando::ActorBehavior::InitEnAlBehavior() { COND_ID_HOOK(OnActorInit, ACTOR_EN_AL, IS_RANDO, [](Actor* actor) { skipCmds.clear(); }); + // "I'm counting on you" + COND_ID_HOOK(OnOpenText, 0x2AA2, IS_RANDO, [](u16* textId, bool* loadFromMessageTable) { + Message_BombersNotebookQueueEvent(gPlayState, BOMBERS_NOTEBOOK_EVENT_MET_MADAME_AROMA); + Message_BombersNotebookQueueEvent(gPlayState, BOMBERS_NOTEBOOK_EVENT_RECEIVED_KAFEIS_MASK); + }); + COND_VB_SHOULD(VB_EXEC_MSG_EVENT, IS_RANDO, { u32 cmdId = va_arg(args, u32); Actor* actor = va_arg(args, Actor*); @@ -28,14 +34,13 @@ void Rando::ActorBehavior::InitEnAlBehavior() { GetItemId getItemId = (GetItemId)SCRIPT_PACK_16(cmd->itemIdH, cmd->itemIdL); skipCmds.clear(); if (getItemId == GI_MASK_KAFEIS_MASK) { // Mayor's Residence - // Prevents the player from moving freely in case a notebook event message pops afterward - Player_SetupWaitForPutAway(gPlayState, player, Player_SetupTalk); + // There is no usable flag for this check, so grant it manually + RANDO_SAVE_CHECKS[RC_MAYORS_OFFICE_KAFEIS_MASK].eligible = true; } else { // Express Mail reward /* * We do something a little tricky here. We manually open a textbox with the message that normally * plays after the player receives the reward (0x2B20), then also skip the MsgScript commands to - * open that textbox and wait on it. The Player_SetupWaitForPutAway call above does not work for - * this scenario, as it will softlock. More naive attempts at handling this actor case resulted in + * open that textbox and wait on it. More naive attempts at handling this actor case resulted in * softlocks, not appropriately locking textboxes, duplicate textboxes, or Bombers' Notebook * messages being eaten. The method below handles the intended behavior, both with or without * notebook messages, even if it is a little counterintuitive. From 722306e606bb5eee0c0a4db17cfb387d6ed0c02d Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:52:18 -0500 Subject: [PATCH 29/44] Exclude Triforce from plentiful items, add error handling (#1433) * Exclude Triforce from plentiful, add error handling * Add max check because unsigned --- mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index 6edec7fb1c..3114798e93 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -280,6 +280,11 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { std::vector plentifulItems; std::vector potentialPlentifulItems; for (size_t i = 0; i < itemPool.size(); i++) { + // The user can specify exactly how many pieces they want to shuffle, so skip those + if (Rando::StaticData::Items[itemPool[i]].randoItemId == RI_TRIFORCE_PIECE) { + continue; + } + switch (Rando::StaticData::Items[itemPool[i]].randoItemType) { case RITYPE_BOSS_KEY: case RITYPE_SMALL_KEY: @@ -409,6 +414,17 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_MAX] = piecesShuffled; RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_REQUIRED] = (piecesShuffled * currentRatio) + 1; } + + if (RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_MAX] < 1 || + RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_MAX] > 1000 || + RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_REQUIRED] < 1 || + RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_REQUIRED] > 1000) { + SPDLOG_ERROR("Error with adjusting Triforce Piece placement. Resulting shuffle requires {} " + "pieces and out of a total of {}", + RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_REQUIRED], + RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_MAX]); + throw std::runtime_error("Invalid Triforce Piece count"); + } } // Grant the starting items From 9db0b4578cad463f6f41a1777a449b5e81e15d97 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Sun, 4 Jan 2026 22:04:57 -0600 Subject: [PATCH 30/44] Random things I've been meaning to do (#1435) - Update curated preset - Fix column alignment on overlay menu - Generate spoiler file by default - Add timesplit file to gitignore --- .gitignore | 1 + mm/2s2h/BenGui/BenMenu.cpp | 1 + mm/2s2h/PresetManager/PresetManager.cpp | 25 ++++++++++++++++----- mm/2s2h/Rando/Menu.cpp | 3 ++- mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 2 +- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 180a9fe42b..f0c53b52bf 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ shipofharkinian.json imgui.ini saves/* randomizer/* +2S2HTimeSplitData.json mm/libultraship/extern/Debug/ImGui.lib diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index f104e64fb9..ac26bd66f3 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -557,6 +557,7 @@ void BenMenu::AddSettings() { .Options(ButtonOptions().Tooltip("Enables the separate Bindings Window.").Size(Sizes::Inline)); path.sidebarName = "Overlay"; + path.column = SECTION_COLUMN_1; AddSidebarEntry("Settings", "Overlay", 2); AddWidget(path, "Notifications", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Position", WIDGET_CVAR_COMBOBOX) diff --git a/mm/2s2h/PresetManager/PresetManager.cpp b/mm/2s2h/PresetManager/PresetManager.cpp index 63462833bb..1ae653b0ca 100644 --- a/mm/2s2h/PresetManager/PresetManager.cpp +++ b/mm/2s2h/PresetManager/PresetManager.cpp @@ -48,6 +48,11 @@ nlohmann::json defaultsPresetJ = R"( nlohmann::json curatedPresetJ = R"( { "CVars": { + "gAudioEditor": { + "ChildGoronCry": 1, + "LowHpAlarm": 1, + "MuteCarpenterSfx": 1 + }, "gCheats": { "EasyFrameAdvance": 1 }, @@ -61,6 +66,7 @@ nlohmann::json curatedPresetJ = R"( "gEnhancements": { "Cutscenes": { "HideTitleCards": 1, + "SkipEnemyCutscenes": 1, "SkipEntranceCutscenes": 1, "SkipFirstCycle": 1, "SkipGetItemCutscenes": 2, @@ -76,13 +82,16 @@ nlohmann::json curatedPresetJ = R"( "DoNotResetRazorSword": 1, "DoNotResetRupees": 1, "DoNotResetTimeSpeed": 1, - "KeepExpressMail": 1 + "KeepExpressMail": 1, + "OceansideWalletAnyDay": 1, + "StopOceansideSpiderHouseSquatter": 1 }, "Dialogue": { "FastBankSelection": 1, "FastText": 1 }, "DifficultyOptions": { + "GoronRace": 1, "LowerBankRewardThresholds": 1 }, "Dpad": { @@ -128,8 +137,10 @@ nlohmann::json curatedPresetJ = R"( "HoneyAndDarlingDay2": 4, "HoneyAndDarlingDay3": 8, "PowderKegCertification": 1, + "RomaniTargetPractice": 5, "SkipBalladOfWindfish": 1, "SkipHorseRace": 1, + "SkipLittleBeaver": 1, "SwampArcheryScore": 1580, "SwordsmanSchoolScore": 6, "TownArcheryScore": 25 @@ -157,11 +168,13 @@ nlohmann::json curatedPresetJ = R"( "InstantRecall": 1 }, "Restorations": { + "BonkCollision": 1, "ConstantFlipsHops": 1, "OoTFasterSwim": 1, "PowerCrouchStab": 1, "SideRoll": 1, - "TatlISG": 1 + "TatlISG": 1, + "WoodfallMountainAppearance": 1 }, "Saving": { "Autosave": 1, @@ -174,12 +187,15 @@ nlohmann::json curatedPresetJ = R"( "EnableSunsSong": 1, "FasterSongPlayback": 1, "PauseOwlWarp": 1, - "SkipSoTCutscenes": 1 + "SkipSoTCutscenes": 1, + "SkipSoaringCutscene": 1 }, "Timesavers": { "DampeDiggingSkip": 1, + "FastChests": 1, "GalleryTwofer": 1, "MarineLabHP": 1, + "SkipBalladOfWindfish": 1, "SwampBoatSpeed": 1 } }, @@ -246,9 +262,6 @@ nlohmann::json curatedPresetJ = R"( "Timers": { "Mode": 3 } - }, - "gModes": { - "PlayAsKafei": 1 } }, "type": "2S2H_PRESET", diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index cbf0452ccc..c3ef31c616 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -148,7 +148,8 @@ static void DrawGeneralTab() { } UIWidgets::PopStyleSlider(); - UIWidgets::CVarCheckbox("Generate Spoiler File", "gRando.GenerateSpoiler"); + UIWidgets::CVarCheckbox("Generate Spoiler File", "gRando.GenerateSpoiler", + CheckboxOptions().DefaultValue(true)); } ImGui::SeparatorText("Enhancements"); UIWidgets::CVarCheckbox("Container Style Matches Contents", "gRando.CSMC"); diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index ee22858a87..182aea74dd 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -447,7 +447,7 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { std::to_string(RANDO_SAVE_OPTIONS[RO_LOGIC])); } - if (CVarGetInteger("gRando.GenerateSpoiler", 0)) { + if (CVarGetInteger("gRando.GenerateSpoiler", 1)) { nlohmann::json spoiler = Rando::Spoiler::GenerateFromSaveContext(); spoiler["inputSeed"] = inputSeed; From 4a4d46e356ac7d01188ee2406f0dd1468eb89971 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Sun, 4 Jan 2026 22:05:14 -0600 Subject: [PATCH 31/44] Separate pool generation and pool metrics to UI (#1431) --- mm/2s2h/Rando/Logic/GeneratePools.cpp | 259 +++++++++++++++ mm/2s2h/Rando/Logic/GlitchlessLogic.cpp | 12 +- mm/2s2h/Rando/Logic/Logic.h | 1 + mm/2s2h/Rando/Logic/NearlyNoLogic.cpp | 17 +- mm/2s2h/Rando/Logic/NoLogic.cpp | 20 -- mm/2s2h/Rando/Menu.cpp | 171 ++++++++-- mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 350 +++----------------- mm/2s2h/Rando/Spoiler/Apply.cpp | 42 +-- 8 files changed, 454 insertions(+), 418 deletions(-) create mode 100644 mm/2s2h/Rando/Logic/GeneratePools.cpp diff --git a/mm/2s2h/Rando/Logic/GeneratePools.cpp b/mm/2s2h/Rando/Logic/GeneratePools.cpp new file mode 100644 index 0000000000..0fd55094d1 --- /dev/null +++ b/mm/2s2h/Rando/Logic/GeneratePools.cpp @@ -0,0 +1,259 @@ +#include "Logic.h" +#include +#include + +extern "C" { +#include "variables.h" +#include "ShipUtils.h" +} + +namespace Rando { + +namespace Logic { + +void GeneratePools(RandoSaveInfo& saveInfo, std::vector& checkPool, std::vector& itemPool) { + std::vector startingItems = convertStartingItemsToRandoItemId(saveInfo.randoStartingItems, ","); + + if (saveInfo.randoSaveOptions[RO_STARTING_MAPS_AND_COMPASSES]) { + std::vector MapsAndCompasses = { + RI_GREAT_BAY_COMPASS, RI_GREAT_BAY_MAP, RI_SNOWHEAD_COMPASS, RI_SNOWHEAD_MAP, + RI_STONE_TOWER_COMPASS, RI_STONE_TOWER_MAP, RI_TINGLE_MAP_CLOCK_TOWN, RI_TINGLE_MAP_GREAT_BAY, + RI_TINGLE_MAP_ROMANI_RANCH, RI_TINGLE_MAP_SNOWHEAD, RI_TINGLE_MAP_STONE_TOWER, RI_TINGLE_MAP_WOODFALL, + RI_WOODFALL_COMPASS, RI_WOODFALL_MAP, + }; + + for (RandoItemId itemId : MapsAndCompasses) { + startingItems.push_back(itemId); + } + } + + std::vector excludedChecks; + std::string excludedChecksList = CVarGetString("gRando.ExcludedChecks", ""); + std::string word; + std::istringstream stream(excludedChecksList); + while (std::getline(stream, word, ',')) { + excludedChecks.push_back((RandoCheckId)std::stoi(word)); + } + + // First loop through all regions and add checks/items to the pool + for (auto& [randoRegionId, randoRegion] : Rando::Logic::Regions) { + for (auto& [randoCheckId, _] : randoRegion.checks) { + auto& randoStaticCheck = Rando::StaticData::Checks[randoCheckId]; + + // Initialize the check with it's vanilla item + if (randoStaticCheck.randoCheckId != RC_UNKNOWN) { + saveInfo.randoSaveChecks[randoCheckId].randoItemId = randoStaticCheck.randoItemId; + } + + // Skip checks that are already in the pool + if (std::find(checkPool.begin(), checkPool.end(), randoCheckId) != checkPool.end()) { + continue; + } + + // TODO: We may never shuffle these 2 pots, leaving this decision for later + if (randoStaticCheck.sceneId == SCENE_LAST_BS) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_SKULL_TOKEN && + saveInfo.randoSaveOptions[RO_SHUFFLE_GOLD_SKULLTULAS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_OWL && + saveInfo.randoSaveOptions[RO_SHUFFLE_OWL_STATUES] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_POT && + saveInfo.randoSaveOptions[RO_SHUFFLE_POT_DROPS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_CRATE && + saveInfo.randoSaveOptions[RO_SHUFFLE_CRATE_DROPS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_BARREL && + saveInfo.randoSaveOptions[RO_SHUFFLE_BARREL_DROPS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_GRASS && + saveInfo.randoSaveOptions[RO_SHUFFLE_GRASS_DROPS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_FREESTANDING && + saveInfo.randoSaveOptions[RO_SHUFFLE_FREESTANDING_ITEMS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_SNOWBALL && + saveInfo.randoSaveOptions[RO_SHUFFLE_SNOWBALL_DROPS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_FROG && + saveInfo.randoSaveOptions[RO_SHUFFLE_FROGS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_REMAINS && + saveInfo.randoSaveOptions[RO_SHUFFLE_BOSS_REMAINS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_COW && + saveInfo.randoSaveOptions[RO_SHUFFLE_COWS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_ENEMY_DROP && + saveInfo.randoSaveOptions[RO_SHUFFLE_ENEMY_DROPS] == RO_GENERIC_NO) { + continue; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_TINGLE_SHOP && + saveInfo.randoSaveOptions[RO_SHUFFLE_TINGLE_SHOPS] == RO_GENERIC_NO) { + continue; + } else { + int price = Ship_Random(0, 200); + saveInfo.randoSaveChecks[randoCheckId].price = price; + } + + if (randoStaticCheck.randoCheckType == RCTYPE_SHOP) { + // We always want shuffle RC_CURIOSITY_SHOP_SPECIAL_ITEM & + // RC_BOMB_SHOP_ITEM_04_OR_CURIOSITY_SHOP_ITEM + if (saveInfo.randoSaveOptions[RO_SHUFFLE_SHOPS] == RO_GENERIC_NO && + randoCheckId != RC_CURIOSITY_SHOP_SPECIAL_ITEM && + randoCheckId != RC_BOMB_SHOP_ITEM_04_OR_CURIOSITY_SHOP_ITEM) { + continue; + } else { + // We may come up with a better solution for this in the future, but for now we choose a + // random price ahead of time, logic will account for whatever price we choose + int price = Ship_Random(0, 200); + saveInfo.randoSaveChecks[randoCheckId].price = price; + } + } + + // When a check is skipped, we still want to add it's vanilla item to the pool, but we don't add the check. + // Mark it as skipped and set it to junk. These leaves an inbalance in the pools that will get sorted + // automatically if there is enough space. + if (saveInfo.randoSaveOptions[RO_LOGIC] != RO_LOGIC_VANILLA) { + auto it = std::find(excludedChecks.begin(), excludedChecks.end(), randoCheckId); + if (it != excludedChecks.end()) { + itemPool.push_back(randoStaticCheck.randoItemId); + + saveInfo.randoSaveChecks[randoCheckId].shuffled = true; + saveInfo.randoSaveChecks[randoCheckId].randoItemId = RI_JUNK; + saveInfo.randoSaveChecks[randoCheckId].skipped = true; + continue; + } + } + + checkPool.emplace_back(randoCheckId); + itemPool.push_back(randoStaticCheck.randoItemId); + } + } + + // Add sword and shield to the pool because they don't have a vanilla location, if you are starting with + // them they will be removed from the pool in the next step + itemPool.push_back(RI_PROGRESSIVE_SWORD); + itemPool.push_back(RI_SHIELD_HERO); + + // Add other items that don't have a vanilla location like Sun's Song or Song of Double Time + + // Boss Souls + if (saveInfo.randoSaveOptions[RO_SHUFFLE_BOSS_SOULS] == RO_GENERIC_YES) { + for (int i = RI_SOUL_GOHT; i <= RI_SOUL_TWINMOLD; i++) { + if (i == RI_SOUL_MAJORA && saveInfo.randoSaveOptions[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) { + continue; + } + itemPool.push_back((RandoItemId)i); + } + } + + // Abilities + if (saveInfo.randoSaveOptions[RO_SHUFFLE_SWIM] == RO_GENERIC_YES) { + itemPool.push_back(RI_ABILITY_SWIM); + } + + // Shuffle Triforce Pieces into the Pool + if (saveInfo.randoSaveOptions[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) { + int piecesToShuffle = saveInfo.randoSaveOptions[RO_TRIFORCE_PIECES_MAX]; + while (piecesToShuffle) { + itemPool.push_back(RI_TRIFORCE_PIECE); + piecesToShuffle--; + } + } + + // Remove starting items from the pool (but only one per entry in startingItems) + for (RandoItemId startingItem : startingItems) { + auto it = std::find(itemPool.begin(), itemPool.end(), startingItem); + if (it != itemPool.end()) { + itemPool.erase(it); + } + } + + // Plentiful + if (saveInfo.randoSaveOptions[RO_PLENTIFUL_ITEMS] == RO_GENERIC_YES) { + int replaceableItems = 0; + std::vector plentifulItems; + std::vector potentialPlentifulItems; + for (size_t i = 0; i < itemPool.size(); i++) { + // The user can specify exactly how many pieces they want to shuffle, so skip those + if (itemPool[i] == RI_TRIFORCE_PIECE) { + continue; + } + + switch (Rando::StaticData::Items[itemPool[i]].randoItemType) { + case RITYPE_BOSS_KEY: + case RITYPE_SMALL_KEY: + case RITYPE_MASK: + case RITYPE_MAJOR: + plentifulItems.push_back(itemPool[i]); + break; + case RITYPE_LESSER: + case RITYPE_SKULLTULA_TOKEN: + case RITYPE_STRAY_FAIRY: + if (Ship_Random(0, 2) == 1) { + potentialPlentifulItems.push_back(itemPool[i]); + } + break; + case RITYPE_HEALTH: + case RITYPE_JUNK: + default: + replaceableItems++; + break; + } + } + + if (replaceableItems > plentifulItems.size()) { + for (RandoItemId plentifulItem : plentifulItems) { + itemPool.push_back(plentifulItem); + } + } + + // Only add potentialPlentifulItems if we think we have enough room (this might not be perfect) + if ((replaceableItems - plentifulItems.size() - 10) > potentialPlentifulItems.size()) { + for (RandoItemId plentifulItem : potentialPlentifulItems) { + itemPool.push_back(plentifulItem); + } + } + } + + // Traps + if (saveInfo.randoSaveOptions[RO_SHUFFLE_TRAPS] == RO_GENERIC_YES) { + int trapsToShuffle = saveInfo.randoSaveOptions[RO_TRAP_AMOUNT]; + while (trapsToShuffle) { + itemPool.push_back(RI_TRAP); + trapsToShuffle--; + } + } +} + +} // namespace Logic + +} // namespace Rando diff --git a/mm/2s2h/Rando/Logic/GlitchlessLogic.cpp b/mm/2s2h/Rando/Logic/GlitchlessLogic.cpp index 940fe3ad6a..1bd2fd745d 100644 --- a/mm/2s2h/Rando/Logic/GlitchlessLogic.cpp +++ b/mm/2s2h/Rando/Logic/GlitchlessLogic.cpp @@ -98,17 +98,7 @@ void ApplyGlitchlessLogicToSaveContext(std::vector& checkPool, std RandoItemId randoItemId; - if (RANDO_SAVE_CHECKS[randoCheckId].skipped) { - uint32_t index = 0; - for (auto& item : itemPool) { - if (Rando::StaticData::Items[item].randoItemType == RITYPE_JUNK) { - randoItemId = item; - itemPool.erase(itemPool.begin() + index); - break; - } - index++; - } - } else if (isShuffled) { + if (isShuffled) { randoItemId = itemPool.back(); itemPool.pop_back(); diff --git a/mm/2s2h/Rando/Logic/Logic.h b/mm/2s2h/Rando/Logic/Logic.h index 3665e82a59..ccee2a3a0c 100644 --- a/mm/2s2h/Rando/Logic/Logic.h +++ b/mm/2s2h/Rando/Logic/Logic.h @@ -19,6 +19,7 @@ namespace Logic { void FindReachableRegions(RandoRegionId currentRegion, std::set& reachableRegions); RandoRegionId GetRegionIdFromEntrance(s32 entrance); +void GeneratePools(RandoSaveInfo& saveInfo, std::vector& checkPool, std::vector& itemPool); void ApplyGlitchlessLogicToSaveContext(std::vector& checkPool, std::vector& itemPool); void ApplyNearlyNoLogicToSaveContext(std::vector& checkPool, std::vector& itemPool); void ApplyNoLogicToSaveContext(std::vector& checkPool, std::vector& itemPool); diff --git a/mm/2s2h/Rando/Logic/NearlyNoLogic.cpp b/mm/2s2h/Rando/Logic/NearlyNoLogic.cpp index fd3b7df212..56c9e73969 100644 --- a/mm/2s2h/Rando/Logic/NearlyNoLogic.cpp +++ b/mm/2s2h/Rando/Logic/NearlyNoLogic.cpp @@ -47,21 +47,8 @@ void ApplyNearlyNoLogicToSaveContext(std::vector& checkPool, std:: continue; } - RandoItemId randoItemId = RI_NONE; - if (RANDO_SAVE_CHECKS[randoCheckId].skipped) { - uint32_t index = 0; - for (auto& item : itemPool) { - if (Rando::StaticData::Items[item].randoItemType == RITYPE_JUNK) { - randoItemId = item; - itemPool.erase(itemPool.begin() + index); - break; - } - index++; - } - } else { - randoItemId = itemPool.back(); - itemPool.pop_back(); - } + RandoItemId randoItemId = itemPool.back(); + itemPool.pop_back(); RANDO_SAVE_CHECKS[randoCheckId].shuffled = true; RANDO_SAVE_CHECKS[randoCheckId].randoItemId = randoItemId; diff --git a/mm/2s2h/Rando/Logic/NoLogic.cpp b/mm/2s2h/Rando/Logic/NoLogic.cpp index e4604fcff1..8dba3dddf2 100644 --- a/mm/2s2h/Rando/Logic/NoLogic.cpp +++ b/mm/2s2h/Rando/Logic/NoLogic.cpp @@ -10,21 +10,6 @@ namespace Rando { namespace Logic { void ApplyNoLogicToSaveContext(std::vector& checkPool, std::vector& itemPool) { - std::vector junkPool; - for (auto& randoCheckId : checkPool) { - if (RANDO_SAVE_CHECKS[randoCheckId].skipped) { - uint32_t index = 0; - for (auto& item : itemPool) { - if (Rando::StaticData::Items[item].randoItemType == RITYPE_JUNK) { - junkPool.push_back(item); - itemPool.erase(itemPool.begin() + index); - break; - } - index++; - } - } - } - for (size_t i = 0; i < itemPool.size(); i++) { std::swap(itemPool[i], itemPool[Ship_Random(0, itemPool.size() - 1)]); } @@ -35,11 +20,6 @@ void ApplyNoLogicToSaveContext(std::vector& checkPool, std::vector } RANDO_SAVE_CHECKS[randoCheckId].shuffled = true; - if (RANDO_SAVE_CHECKS[randoCheckId].skipped == true) { - RANDO_SAVE_CHECKS[randoCheckId].randoItemId = junkPool.back(); - junkPool.pop_back(); - continue; - } RANDO_SAVE_CHECKS[randoCheckId].randoItemId = itemPool.back(); itemPool.pop_back(); } diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index c3ef31c616..9bdbc7eb48 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -4,6 +4,9 @@ #include "Rando/CheckTracker/CheckTracker.h" #include "build.h" #include "2s2h/BenGui/BenMenu.h" +#include "2s2h/BenGui/BenGui.hpp" +#include "2s2h/Rando/Logic/Logic.h" +#include "2s2h/ShipInit.hpp" extern "C" { #include "overlays/actors/ovl_En_Sth/z_en_sth.h" @@ -113,6 +116,81 @@ void LoadExcludedChecks() { SortExcludedChecks(); } +static int checksInPool = 0; +static int itemsInPool = 0; +static int junkInPool = 0; +static bool ableToBalance = true; +void RefreshMetrics() { + RandoSaveInfo randoSaveInfo; + std::vector checkPool; + std::vector itemPool; + + // Load options into CVars + for (auto& [randoOptionId, randoStaticOption] : Rando::StaticData::Options) { + randoSaveInfo.randoSaveOptions[randoOptionId] = + (uint32_t)CVarGetInteger(randoStaticOption.cvar, randoStaticOption.defaultValue); + } + std::string startingItemsString = CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT); + strncpy(randoSaveInfo.randoStartingItems, startingItemsString.c_str(), startingItemsString.size() + 1); + + Rando::Logic::GeneratePools(randoSaveInfo, checkPool, itemPool); + + checksInPool = checkPool.size(); + itemsInPool = itemPool.size(); + junkInPool = 0; + for (auto& item : itemPool) { + if (Rando::StaticData::Items[item].randoItemType == RITYPE_JUNK) { + junkInPool++; + } + } + ableToBalance = checksInPool >= (itemsInPool - junkInPool); +} + +static RegisterShipInitFunc refreshMetricsInit(RefreshMetrics, { + // I Don't love this, but it works... + "gRando.Options.RO_ACCESS_DUNGEONS", + "gRando.Options.RO_ACCESS_MAJORA_MASKS_COUNT", + "gRando.Options.RO_ACCESS_MAJORA_REMAINS_COUNT", + "gRando.Options.RO_ACCESS_MOON_MASKS_COUNT", + "gRando.Options.RO_ACCESS_MOON_REMAINS_COUNT", + "gRando.Options.RO_ACCESS_TRIALS", + "gRando.Options.RO_HINTS_BOSS_REMAINS", + "gRando.Options.RO_HINTS_GOSSIP_STONES", + "gRando.Options.RO_HINTS_HOOKSHOT", + "gRando.Options.RO_HINTS_OATH_TO_ORDER", + "gRando.Options.RO_HINTS_PURCHASEABLE", + "gRando.Options.RO_HINTS_SPIDER_HOUSES", + "gRando.Options.RO_TRAP_AMOUNT", + "gRando.Options.RO_LOGIC", + "gRando.Options.RO_MINIMUM_SKULLTULA_TOKENS", + "gRando.Options.RO_MINIMUM_STRAY_FAIRIES", + "gRando.Options.RO_PLENTIFUL_ITEMS", + "gRando.Options.RO_SHUFFLE_BARREL_DROPS", + "gRando.Options.RO_SHUFFLE_BOSS_REMAINS", + "gRando.Options.RO_SHUFFLE_BOSS_SOULS", + "gRando.Options.RO_SHUFFLE_COWS", + "gRando.Options.RO_SHUFFLE_CRATE_DROPS", + "gRando.Options.RO_SHUFFLE_ENEMY_DROPS", + "gRando.Options.RO_SHUFFLE_FREESTANDING_ITEMS", + "gRando.Options.RO_SHUFFLE_FROGS", + "gRando.Options.RO_SHUFFLE_GOLD_SKULLTULAS", + "gRando.Options.RO_SHUFFLE_GRASS_DROPS", + "gRando.Options.RO_SHUFFLE_TRAPS", + "gRando.Options.RO_SHUFFLE_OWL_STATUES", + "gRando.Options.RO_SHUFFLE_POT_DROPS", + "gRando.Options.RO_SHUFFLE_SHOPS", + "gRando.Options.RO_SHUFFLE_SNOWBALL_DROPS", + "gRando.Options.RO_SHUFFLE_SWIM", + "gRando.Options.RO_SHUFFLE_TINGLE_SHOPS", + "gRando.Options.RO_SHUFFLE_TRIFORCE_PIECES", + "gRando.Options.RO_STARTING_CONSUMABLES", + "gRando.Options.RO_STARTING_HEALTH", + "gRando.Options.RO_STARTING_MAPS_AND_COMPASSES", + "gRando.Options.RO_STARTING_RUPEES", + "gRando.Options.RO_TRIFORCE_PIECES_MAX", + "gRando.Options.RO_TRIFORCE_PIECES_REQUIRED", + }); + static void DrawGeneralTab() { ImGui::BeginChild("randoSettings"); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 0.5f)); @@ -151,6 +229,32 @@ static void DrawGeneralTab() { UIWidgets::CVarCheckbox("Generate Spoiler File", "gRando.GenerateSpoiler", CheckboxOptions().DefaultValue(true)); } + + float mainWidth = 300.0f; // Arbitrary width for progress bars + float itemProgress = mainWidth * (static_cast(itemsInPool) / static_cast(checksInPool)); + float junkProgress = static_cast(junkInPool) / static_cast(itemsInPool); + + ImGui::SeparatorText("Current Settings Metrics"); + ImGui::Text("Checks in pool: %d", checksInPool); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, UIWidgets::ColorValues.at(THEME_COLOR)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkGray)); + ImGui::ProgressBar(1.0f, ImVec2(mainWidth, 0.0f), ""); + ImGui::Text("Items in Pool: %d", itemsInPool); + ImGui::SameLine(); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.5f), "(%d Junk Items)", junkInPool); + + ImGui::ProgressBar(1.0f - junkProgress, ImVec2(itemProgress, 0.0f), ""); + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(); + ImGui::Text("Able to Balance:"); + ImGui::SameLine(); + if (ableToBalance) { + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "Yes"); + } else { + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "No"); + } + ImGui::SeparatorText("Enhancements"); UIWidgets::CVarCheckbox("Container Style Matches Contents", "gRando.CSMC"); UIWidgets::Tooltip("This will make the contents of a container match the container itself. This currently only " @@ -166,9 +270,8 @@ static void DrawGeneralTab() { } static void DrawLogicConditionsTab() { - f32 columnWidth = ImGui::GetContentRegionAvail().x / 3 - (ImGui::GetStyle().ItemSpacing.x * 2); - f32 halfHeight = ImGui::GetContentRegionAvail().y / 2 - (ImGui::GetStyle().ItemSpacing.y * 2); - ImGui::BeginChild("randoLogicColumn1", ImVec2(columnWidth, halfHeight)); + f32 columnWidth = ImGui::GetContentRegionAvail().x / 2 - (ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::BeginChild("randoLogicColumn1", ImVec2(columnWidth, 0)); if (UIWidgets::CVarCombobox("Logic", Rando::StaticData::Options[RO_LOGIC].cvar, &logicOptions)) { ClearIncompatibleSetting(); } @@ -185,7 +288,7 @@ static void DrawLogicConditionsTab() { "Not compatible with settings that add items to the pool, like Boss Souls or Plentiful Items."); ImGui::EndChild(); ImGui::SameLine(); - ImGui::BeginChild("randoLogicColumn2", ImVec2(columnWidth, halfHeight)); + ImGui::BeginChild("randoLogicColumn2", ImVec2(columnWidth, 0)); UIWidgets::CVarCombobox("Dungeon Access", Rando::StaticData::Options[RO_ACCESS_DUNGEONS].cvar, &accessDungeonOptions); @@ -208,14 +311,11 @@ static void DrawLogicConditionsTab() { IntSliderOptions().Min(0).Max(20).DefaultValue(0)); UIWidgets::CVarCombobox("Trials Access", Rando::StaticData::Options[RO_ACCESS_TRIALS].cvar, &accessTrialsOptions); ImGui::EndChild(); - ImGui::BeginChild("randoLogicTricks", ImVec2(0, 0)); - ImGui::SeparatorText("Tricks & Glitches"); - ImGui::EndChild(); } static void DrawShufflesTab() { - f32 columnWidth = ImGui::GetContentRegionAvail().x / 3 - (ImGui::GetStyle().ItemSpacing.x * 2); - f32 halfHeight = ImGui::GetContentRegionAvail().y / 2 - (ImGui::GetStyle().ItemSpacing.y * 2); + f32 columnWidth = ImGui::GetContentRegionAvail().x / 2 - (ImGui::GetStyle().ItemSpacing.x * 2); + f32 halfHeight = 0; ImGui::SeparatorText("Shuffle Options"); ImGui::BeginChild("randoShufflesColumn1", ImVec2(columnWidth, halfHeight)); CVarCheckbox("Shuffle Songs", "gPlaceholderBool", @@ -259,31 +359,6 @@ static void DrawShufflesTab() { ImGui::EndChild(); ImGui::SameLine(); ImGui::BeginChild("randoLocationsColumn3", ImVec2(columnWidth, halfHeight)); - CVarCheckbox("Triforce Hunt", Rando::StaticData::Options[RO_SHUFFLE_TRIFORCE_PIECES].cvar); - ImGui::BeginDisabled(!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRIFORCE_PIECES].cvar, RO_GENERIC_OFF)); - CVarSliderInt( - "Required Triforce Pieces", Rando::StaticData::Options[RO_TRIFORCE_PIECES_REQUIRED].cvar, - IntSliderOptions({}) - .Min(1) - .Max(CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, DEFAULT_TRIFORCE_PIECES_MAX)) - .DefaultValue(DEFAULT_TRIFORCE_PIECES_MAX)); - if (CVarSliderInt( - "Shuffled Triforce Pieces", Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, - IntSliderOptions({}) - .Min(1) - .Max(1000) - .DefaultValue(DEFAULT_TRIFORCE_PIECES_MAX) - .Tooltip("If the maximum amount of placeable pieces exceeds what will allow the seed to generate, the " - "amount will be adjusted automatically."))) { - if (CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_REQUIRED].cvar, DEFAULT_TRIFORCE_PIECES_MAX) > - CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, DEFAULT_TRIFORCE_PIECES_MAX)) { - CVarGetInteger( - Rando::StaticData::Options[RO_TRIFORCE_PIECES_REQUIRED].cvar, - CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, DEFAULT_TRIFORCE_PIECES_MAX)); - } - } - - ImGui::EndDisabled(); ImGui::EndChild(); } @@ -333,6 +408,32 @@ static void DrawItemsTab() { CheckboxOptions({ { .tooltip = "Shuffles the first drop from a non Boss Enemy." } })); CVarCheckbox("Enemy Souls", "gPlaceholderBool", CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } })); + + CVarCheckbox("Triforce Hunt", Rando::StaticData::Options[RO_SHUFFLE_TRIFORCE_PIECES].cvar); + ImGui::BeginDisabled(!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRIFORCE_PIECES].cvar, RO_GENERIC_OFF)); + CVarSliderInt( + "Required Triforce Pieces", Rando::StaticData::Options[RO_TRIFORCE_PIECES_REQUIRED].cvar, + IntSliderOptions({}) + .Min(1) + .Max(CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, DEFAULT_TRIFORCE_PIECES_MAX)) + .DefaultValue(DEFAULT_TRIFORCE_PIECES_MAX)); + if (CVarSliderInt( + "Shuffled Triforce Pieces", Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, + IntSliderOptions({}) + .Min(1) + .Max(1000) + .DefaultValue(DEFAULT_TRIFORCE_PIECES_MAX) + .Tooltip("If the maximum amount of placeable pieces exceeds what will allow the seed to generate, the " + "amount will be adjusted automatically."))) { + if (CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_REQUIRED].cvar, DEFAULT_TRIFORCE_PIECES_MAX) > + CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, DEFAULT_TRIFORCE_PIECES_MAX)) { + CVarSetInteger( + Rando::StaticData::Options[RO_TRIFORCE_PIECES_REQUIRED].cvar, + CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, DEFAULT_TRIFORCE_PIECES_MAX)); + } + } + + ImGui::EndDisabled(); ImGui::EndChild(); ImGui::SameLine(); ImGui::BeginChild("randoItemsColumn3", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y)); @@ -347,7 +448,7 @@ static void DrawItemsTab() { .Color(UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5))) .Format("Traps: %i") .Min(1) - .Max(10) + .Max(100) .DefaultValue(5)); ImGui::SeparatorText("Toggle Trap Types"); CVarCheckbox( diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index 182aea74dd..3cca006e76 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -12,6 +12,43 @@ extern "C" { #include "overlays/actors/ovl_En_Sth/z_en_sth.h" } +void GrantStarters() { + std::vector startingItems = convertStartingItemsToRandoItemId(RANDO_STARTING_ITEMS, ","); + + if (RANDO_SAVE_OPTIONS[RO_STARTING_MAPS_AND_COMPASSES]) { + std::vector MapsAndCompasses = { + RI_GREAT_BAY_COMPASS, RI_GREAT_BAY_MAP, RI_SNOWHEAD_COMPASS, RI_SNOWHEAD_MAP, + RI_STONE_TOWER_COMPASS, RI_STONE_TOWER_MAP, RI_TINGLE_MAP_CLOCK_TOWN, RI_TINGLE_MAP_GREAT_BAY, + RI_TINGLE_MAP_ROMANI_RANCH, RI_TINGLE_MAP_SNOWHEAD, RI_TINGLE_MAP_STONE_TOWER, RI_TINGLE_MAP_WOODFALL, + RI_WOODFALL_COMPASS, RI_WOODFALL_MAP, + }; + + for (RandoItemId itemId : MapsAndCompasses) { + startingItems.push_back(itemId); + } + } + + for (RandoItemId startingItem : startingItems) { + Rando::GiveItem(Rando::ConvertItem(startingItem)); + } + + if (RANDO_SAVE_OPTIONS[RO_STARTING_HEALTH] != 3) { + gSaveContext.save.saveInfo.playerData.healthCapacity = gSaveContext.save.saveInfo.playerData.health = + RANDO_SAVE_OPTIONS[RO_STARTING_HEALTH] * 0x10; + } + + if (RANDO_SAVE_OPTIONS[RO_STARTING_CONSUMABLES]) { + Rando::GiveItem(RI_DEKU_STICK); + Rando::GiveItem(RI_DEKU_NUT); + AMMO(ITEM_DEKU_STICK) = CUR_CAPACITY(UPG_DEKU_STICKS); + AMMO(ITEM_DEKU_NUT) = CUR_CAPACITY(UPG_DEKU_NUTS); + } + + if (RANDO_SAVE_OPTIONS[RO_STARTING_RUPEES]) { + gSaveContext.save.saveInfo.playerData.rupees = CUR_CAPACITY(UPG_WALLET); + } +} + // Very primitive randomizer implementation, when a save is created, if rando is enabled // we set the save type to rando and shuffle all checks and persist the results to the save void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { @@ -60,267 +97,18 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { (uint32_t)CVarGetInteger(randoStaticOption.cvar, randoStaticOption.defaultValue); } - std::vector startingItems = convertStartingItemsToRandoItemId( - CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT), ","); - - std::string startingItemSave = CreateStartingItemsToCvar(startingItems); - strncpy(RANDO_STARTING_ITEMS, startingItemSave.c_str(), startingItemSave.size() + 1); - - if (RANDO_SAVE_OPTIONS[RO_STARTING_HEALTH] != 3) { - gSaveContext.save.saveInfo.playerData.healthCapacity = - gSaveContext.save.saveInfo.playerData.health = RANDO_SAVE_OPTIONS[RO_STARTING_HEALTH] * 0x10; - } - - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_TRIFORCE_PIECES] != RO_GENERIC_OFF) { - RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_REQUIRED] = CVarGetInteger( - Rando::StaticData::Options[RO_TRIFORCE_PIECES_REQUIRED].cvar, DEFAULT_TRIFORCE_PIECES_MAX); - } - - if (RANDO_SAVE_OPTIONS[RO_STARTING_CONSUMABLES]) { - GiveItem(RI_DEKU_STICK); - GiveItem(RI_DEKU_NUT); - AMMO(ITEM_DEKU_STICK) = CUR_CAPACITY(UPG_DEKU_STICKS); - AMMO(ITEM_DEKU_NUT) = CUR_CAPACITY(UPG_DEKU_NUTS); - } - - if (RANDO_SAVE_OPTIONS[RO_STARTING_MAPS_AND_COMPASSES]) { - std::vector MapsAndCompasses = { - RI_GREAT_BAY_COMPASS, RI_GREAT_BAY_MAP, RI_SNOWHEAD_COMPASS, - RI_SNOWHEAD_MAP, RI_STONE_TOWER_COMPASS, RI_STONE_TOWER_MAP, - RI_TINGLE_MAP_CLOCK_TOWN, RI_TINGLE_MAP_GREAT_BAY, RI_TINGLE_MAP_ROMANI_RANCH, - RI_TINGLE_MAP_SNOWHEAD, RI_TINGLE_MAP_STONE_TOWER, RI_TINGLE_MAP_WOODFALL, - RI_WOODFALL_COMPASS, RI_WOODFALL_MAP, - }; - - for (RandoItemId itemId : MapsAndCompasses) { - startingItems.push_back(itemId); - } - } - // If Skulltula tokens are not shuffled, use the vanilla requirement if (!RANDO_SAVE_OPTIONS[RO_SHUFFLE_GOLD_SKULLTULAS]) { RANDO_SAVE_OPTIONS[RO_MINIMUM_SKULLTULA_TOKENS] = SPIDER_HOUSE_TOKENS_REQUIRED; } + // Persist StartingItems to the save + std::string startingItemsString = CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT); + strncpy(RANDO_STARTING_ITEMS, startingItemsString.c_str(), startingItemsString.size() + 1); + std::vector checkPool; std::vector itemPool; - - // Create Excluded Checks List to eliminate excluded checks from the pool - std::vector excludedChecks; - std::string excludedChecksList = CVarGetString("gRando.ExcludedChecks", ""); - std::string word; - std::istringstream stream(excludedChecksList); - while (std::getline(stream, word, ',')) { - excludedChecks.push_back((RandoCheckId)std::stoi(word)); - } - - // First loop through all regions and add checks/items to the pool - for (auto& [randoRegionId, randoRegion] : Rando::Logic::Regions) { - for (auto& [randoCheckId, _] : randoRegion.checks) { - auto& randoStaticCheck = Rando::StaticData::Checks[randoCheckId]; - - // Initialize the check with it's vanilla item - if (randoStaticCheck.randoCheckId != RC_UNKNOWN) { - RANDO_SAVE_CHECKS[randoCheckId].randoItemId = randoStaticCheck.randoItemId; - } - - // Skip checks that are already in the pool - if (std::find(checkPool.begin(), checkPool.end(), randoCheckId) != checkPool.end()) { - continue; - } - - // TODO: We may never shuffle these 2 pots, leaving this decision for later - if (randoStaticCheck.sceneId == SCENE_LAST_BS) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_SKULL_TOKEN && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_GOLD_SKULLTULAS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_OWL && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_OWL_STATUES] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_POT && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_POT_DROPS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_CRATE && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_CRATE_DROPS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_BARREL && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_BARREL_DROPS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_GRASS && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_GRASS_DROPS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_FREESTANDING && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_FREESTANDING_ITEMS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_SNOWBALL && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_SNOWBALL_DROPS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_FROG && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_FROGS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_REMAINS && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_BOSS_REMAINS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_COW && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_COWS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_ENEMY_DROP && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENEMY_DROPS] == RO_GENERIC_NO) { - continue; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_TINGLE_SHOP && - RANDO_SAVE_OPTIONS[RO_SHUFFLE_TINGLE_SHOPS] == RO_GENERIC_NO) { - continue; - } else { - int price = Ship_Random(0, 200); - RANDO_SAVE_CHECKS[randoCheckId].price = price; - } - - if (randoStaticCheck.randoCheckType == RCTYPE_SHOP) { - // We always want shuffle RC_CURIOSITY_SHOP_SPECIAL_ITEM & - // RC_BOMB_SHOP_ITEM_04_OR_CURIOSITY_SHOP_ITEM - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_SHOPS] == RO_GENERIC_NO && - randoCheckId != RC_CURIOSITY_SHOP_SPECIAL_ITEM && - randoCheckId != RC_BOMB_SHOP_ITEM_04_OR_CURIOSITY_SHOP_ITEM) { - continue; - } else { - // We may come up with a better solution for this in the future, but for now we choose a - // random price ahead of time, logic will account for whatever price we choose - int price = Ship_Random(0, 200); - RANDO_SAVE_CHECKS[randoCheckId].price = price; - } - } - - // Skip checks that have been excluded in the Locations menu and add their vanilla item to the - // pool except if Logic is set to Vanilla. - if (RANDO_SAVE_OPTIONS[RO_LOGIC] <= RO_LOGIC_NEARLY_NO_LOGIC) { - auto it = std::find(excludedChecks.begin(), excludedChecks.end(), randoCheckId); - if (it != excludedChecks.end()) { - RandoItemId vanillaItem = Rando::StaticData::Checks[randoCheckId].randoItemId; - itemPool.push_back(vanillaItem); - - RANDO_SAVE_CHECKS[randoCheckId].randoItemId = RI_JUNK; - RANDO_SAVE_CHECKS[randoCheckId].skipped = true; - - checkPool.emplace_back(randoCheckId); - continue; - } - } - - checkPool.emplace_back(randoCheckId); - itemPool.push_back(randoStaticCheck.randoItemId); - } - } - - // Add sword and shield to the pool because they don't have a vanilla location, if you are starting with - // them they will be removed from the pool in the next step - itemPool.push_back(RI_PROGRESSIVE_SWORD); - itemPool.push_back(RI_SHIELD_HERO); - - // Add other items that don't have a vanilla location like Sun's Song or Song of Double Time - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_BOSS_SOULS] == RO_GENERIC_YES) { - for (int i = RI_SOUL_GOHT; i <= RI_SOUL_TWINMOLD; i++) { - if (i == RI_SOUL_MAJORA && RANDO_SAVE_OPTIONS[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) { - continue; - } - itemPool.push_back((RandoItemId)i); - } - } - - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_SWIM] == RO_GENERIC_YES) { - itemPool.push_back(RI_ABILITY_SWIM); - } else { - Flags_SetRandoInf(RANDO_INF_OBTAINED_SWIM); - } - - // Remove starting items from the pool (but only one per entry in startingItems) - for (RandoItemId startingItem : startingItems) { - auto it = std::find(itemPool.begin(), itemPool.end(), startingItem); - if (it != itemPool.end()) { - itemPool.erase(it); - } - } - - // Shuffle Triforce Pieces into the Pool - int piecesShuffled = 0; - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) { - int piecesToShuffle = RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_MAX]; - for (auto& item : itemPool) { - if (piecesToShuffle == 0) { - break; - } - itemPool.push_back(RI_TRIFORCE_PIECE); - piecesToShuffle--; - piecesShuffled++; - } - } - - if (RANDO_SAVE_OPTIONS[RO_PLENTIFUL_ITEMS] == RO_GENERIC_YES) { - int replaceableItems = 0; - std::vector plentifulItems; - std::vector potentialPlentifulItems; - for (size_t i = 0; i < itemPool.size(); i++) { - switch (Rando::StaticData::Items[itemPool[i]].randoItemType) { - case RITYPE_BOSS_KEY: - case RITYPE_SMALL_KEY: - case RITYPE_MASK: - case RITYPE_MAJOR: - plentifulItems.push_back(itemPool[i]); - break; - case RITYPE_LESSER: - case RITYPE_SKULLTULA_TOKEN: - case RITYPE_STRAY_FAIRY: - if (Ship_Random(0, 2) == 1) { - potentialPlentifulItems.push_back(itemPool[i]); - } - break; - case RITYPE_HEALTH: - case RITYPE_JUNK: - default: - replaceableItems++; - break; - } - } - - if (replaceableItems > plentifulItems.size()) { - for (RandoItemId plentifulItem : plentifulItems) { - itemPool.push_back(plentifulItem); - } - } - - // Only add potentialPlentifulItems if we think we have enough room (this might not be perfect) - if ((replaceableItems - plentifulItems.size() - 10) > potentialPlentifulItems.size()) { - for (RandoItemId plentifulItem : potentialPlentifulItems) { - itemPool.push_back(plentifulItem); - } - } - } + Rando::Logic::GeneratePools(gSaveContext.save.shipSaveInfo.rando, checkPool, itemPool); if (checkPool.empty()) { throw std::runtime_error("No checks in logic"); @@ -329,20 +117,8 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { throw std::runtime_error("No items in logic"); } - // Handle Shuffling Traps - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_TRAPS] == RO_GENERIC_YES) { - for (int i = 0; i < RANDO_SAVE_OPTIONS[RO_TRAP_AMOUNT]; i++) { - for (int j = 0; j < itemPool.size(); j++) { - if (itemPool[j] == RI_JUNK) { - itemPool[j] = RI_TRAP; - break; - } - } - } - } - + // Balance pools int heartPiecesRemoved = 0; - // Add/Remove junk items to/from the pool to make the item pool size match the check pool size while (checkPool.size() != itemPool.size()) { if (checkPool.size() > itemPool.size()) { itemPool.push_back(RI_JUNK); @@ -383,48 +159,14 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { continue; } - // If Triforce Hunt is enabled, removed pieces as a last resort - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) { - bool removedTriforcePiece = false; - for (int i = 0; i < itemPool.size(); i++) { - if (Rando::StaticData::Items[itemPool[i]].randoItemId == RI_TRIFORCE_PIECE) { - itemPool.erase(itemPool.begin() + i); - removedTriforcePiece = true; - piecesShuffled--; - break; - } - } - - if (removedTriforcePiece) { - continue; - } - } - SPDLOG_ERROR("Could not match item pool size to check pool size {}/{}", itemPool.size(), checkPool.size()); throw std::runtime_error("Could not match item pool size to check pool size"); } } - // Update Required Triforce Pieces if piecesShuffled falls below max shuffled - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) { - if (piecesShuffled != RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_MAX]) { - float currentRatio = ((float)RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_REQUIRED] / - (float)RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_MAX]); - - RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_MAX] = piecesShuffled; - RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_REQUIRED] = (piecesShuffled * currentRatio) + 1; - } - } - - // Grant the starting items - for (RandoItemId startingItem : startingItems) { - GiveItem(ConvertItem(startingItem)); - } - - if (RANDO_SAVE_OPTIONS[RO_STARTING_RUPEES]) { - gSaveContext.save.saveInfo.playerData.rupees = CUR_CAPACITY(UPG_WALLET); - } + // Grant the starting stuff + GrantStarters(); if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_VANILLA) { GiveItem(RI_SWORD_KOKIRI); @@ -466,6 +208,8 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { nlohmann::json spoiler = Rando::Spoiler::LoadFromFile(fileName); Rando::Spoiler::ApplyToSaveContext(spoiler); + // Grant the starting stuff + GrantStarters(); Audio_PlaySfx(NA_SE_SY_ATTENTION_SOUND); } diff --git a/mm/2s2h/Rando/Spoiler/Apply.cpp b/mm/2s2h/Rando/Spoiler/Apply.cpp index 8e96c6862d..4f3d6baaed 100644 --- a/mm/2s2h/Rando/Spoiler/Apply.cpp +++ b/mm/2s2h/Rando/Spoiler/Apply.cpp @@ -3,6 +3,10 @@ #include #include "ShipUtils.h" +extern "C" { +#include "overlays/actors/ovl_En_Sth/z_en_sth.h" +} + namespace Rando { namespace Spoiler { @@ -14,42 +18,12 @@ void ApplyToSaveContext(nlohmann::json spoiler) { RANDO_SAVE_OPTIONS[randoOptionId] = spoiler["options"][randoStaticOption.name].get(); } - std::string startingItemsSave = spoiler["startingItems"].get(); - strncpy(RANDO_STARTING_ITEMS, startingItemsSave.c_str(), startingItemsSave.size() + 1); - - if (RANDO_SAVE_OPTIONS[RO_STARTING_HEALTH] != 3) { - gSaveContext.save.saveInfo.playerData.healthCapacity = gSaveContext.save.saveInfo.playerData.health = - RANDO_SAVE_OPTIONS[RO_STARTING_HEALTH] * 0x10; - } - - if (RANDO_SAVE_OPTIONS[RO_STARTING_CONSUMABLES]) { - GiveItem(RI_DEKU_STICK); - GiveItem(RI_DEKU_NUT); - AMMO(ITEM_DEKU_STICK) = CUR_CAPACITY(UPG_DEKU_STICKS); - AMMO(ITEM_DEKU_NUT) = CUR_CAPACITY(UPG_DEKU_NUTS); - } - - std::vector startingItems = convertStartingItemsToRandoItemId(RANDO_STARTING_ITEMS, ","); - - if (RANDO_SAVE_OPTIONS[RO_STARTING_MAPS_AND_COMPASSES]) { - std::vector MapsAndCompasses = { - RI_GREAT_BAY_COMPASS, RI_GREAT_BAY_MAP, RI_SNOWHEAD_COMPASS, RI_SNOWHEAD_MAP, - RI_STONE_TOWER_COMPASS, RI_STONE_TOWER_MAP, RI_TINGLE_MAP_CLOCK_TOWN, RI_TINGLE_MAP_GREAT_BAY, - RI_TINGLE_MAP_ROMANI_RANCH, RI_TINGLE_MAP_SNOWHEAD, RI_TINGLE_MAP_STONE_TOWER, RI_TINGLE_MAP_WOODFALL, - RI_WOODFALL_COMPASS, RI_WOODFALL_MAP, - }; - for (RandoItemId itemId : MapsAndCompasses) { - startingItems.push_back(itemId); - } - } - - for (RandoItemId startingItem : startingItems) { - GiveItem(ConvertItem(startingItem)); + if (!RANDO_SAVE_OPTIONS[RO_SHUFFLE_GOLD_SKULLTULAS]) { + RANDO_SAVE_OPTIONS[RO_MINIMUM_SKULLTULA_TOKENS] = SPIDER_HOUSE_TOKENS_REQUIRED; } - if (RANDO_SAVE_OPTIONS[RO_STARTING_RUPEES]) { - gSaveContext.save.saveInfo.playerData.rupees = CUR_CAPACITY(UPG_WALLET); - } + std::string startingItemsSave = spoiler["startingItems"].get(); + strncpy(RANDO_STARTING_ITEMS, startingItemsSave.c_str(), startingItemsSave.size() + 1); for (auto& [randoCheckId, randoStaticCheck] : Rando::StaticData::Checks) { if (randoStaticCheck.randoCheckId == RC_UNKNOWN) { From ce0966e631622566aff2d577df7d0c0be572dd39 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Sun, 4 Jan 2026 23:00:28 -0600 Subject: [PATCH 32/44] Add missing swim grant when not shuffled (#1436) --- mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index 3cca006e76..63ad54f952 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -28,6 +28,10 @@ void GrantStarters() { } } + if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_SWIM] != RO_GENERIC_YES) { + startingItems.push_back(RI_ABILITY_SWIM); + } + for (RandoItemId startingItem : startingItems) { Rando::GiveItem(Rando::ConvertItem(startingItem)); } From 090bfc0a030aab5f3ccbb94d512a70c6c972a4a8 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:35:30 -0500 Subject: [PATCH 33/44] Bump for Mion Charlie (#1434) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a120c9489..772b89fd15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,13 +5,13 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") set(GAME_STR "MM") -project(2s2h VERSION 3.0.1 LANGUAGES C CXX) +project(2s2h VERSION 3.0.2 LANGUAGES C CXX) include(CMake/2ship-cvars.cmake) include(CMake/lus-cvars.cmake) set(SPDLOG_LEVEL_TRACE 0) set(SPDLOG_LEVEL_OFF 6) set(SPDLOG_MIN_CUTOFF SPDLOG_LEVEL_TRACE CACHE STRING "cutoff at trace") -set(PROJECT_BUILD_NAME "Mion Bravo" CACHE STRING "" FORCE) +set(PROJECT_BUILD_NAME "Mion Charlie" CACHE STRING "" FORCE) set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "" FORCE) execute_process( From b3f359a2c14301d6d2b938c7bbb7b59b61588d9b Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:58:19 -0500 Subject: [PATCH 34/44] Merge develop-mion->develop, once more (#1438) * Fix softlock from repeating Kafei's Mask check (#1432) * Exclude Triforce from plentiful items, add error handling (#1433) * Exclude Triforce from plentiful, add error handling * Add max check because unsigned * Bump for Mion Charlie (#1434) --- CMakeLists.txt | 4 ++-- mm/2s2h/Rando/ActorBehavior/EnAl.cpp | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a120c9489..772b89fd15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,13 +5,13 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") set(GAME_STR "MM") -project(2s2h VERSION 3.0.1 LANGUAGES C CXX) +project(2s2h VERSION 3.0.2 LANGUAGES C CXX) include(CMake/2ship-cvars.cmake) include(CMake/lus-cvars.cmake) set(SPDLOG_LEVEL_TRACE 0) set(SPDLOG_LEVEL_OFF 6) set(SPDLOG_MIN_CUTOFF SPDLOG_LEVEL_TRACE CACHE STRING "cutoff at trace") -set(PROJECT_BUILD_NAME "Mion Bravo" CACHE STRING "" FORCE) +set(PROJECT_BUILD_NAME "Mion Charlie" CACHE STRING "" FORCE) set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "" FORCE) execute_process( diff --git a/mm/2s2h/Rando/ActorBehavior/EnAl.cpp b/mm/2s2h/Rando/ActorBehavior/EnAl.cpp index aac179c049..74bf83e46c 100644 --- a/mm/2s2h/Rando/ActorBehavior/EnAl.cpp +++ b/mm/2s2h/Rando/ActorBehavior/EnAl.cpp @@ -17,6 +17,11 @@ void Rando::ActorBehavior::InitEnAlBehavior() { COND_VB_SHOULD(VB_MADAME_AROMA_ASK_FOR_HELP, IS_RANDO, { *should = !RANDO_SAVE_CHECKS[RC_MAYORS_OFFICE_KAFEIS_MASK].cycleObtained; }); + // "I'm counting on you" + COND_ID_HOOK(OnOpenText, 0x2AA2, IS_RANDO, [](u16* textId, bool* loadFromMessageTable) { + Message_BombersNotebookQueueEvent(gPlayState, BOMBERS_NOTEBOOK_EVENT_MET_MADAME_AROMA); + Message_BombersNotebookQueueEvent(gPlayState, BOMBERS_NOTEBOOK_EVENT_RECEIVED_KAFEIS_MASK); + }); COND_VB_SHOULD(VB_EXEC_MSG_EVENT, IS_RANDO, { u32 cmdId = va_arg(args, u32); @@ -31,16 +36,13 @@ void Rando::ActorBehavior::InitEnAlBehavior() { GetItemId getItemId = (GetItemId)SCRIPT_PACK_16(cmd->itemIdH, cmd->itemIdL); skipCmds.clear(); if (getItemId == GI_MASK_KAFEIS_MASK) { // Mayor's Residence - // Prevents the player from moving freely in case a notebook event message pops afterward - Player_SetupWaitForPutAway(gPlayState, player, Player_SetupTalk); // There is no usable flag for this check, so grant it manually RANDO_SAVE_CHECKS[RC_MAYORS_OFFICE_KAFEIS_MASK].eligible = true; } else { // Express Mail reward /* * We do something a little tricky here. We manually open a textbox with the message that normally * plays after the player receives the reward (0x2B20), then also skip the MsgScript commands to - * open that textbox and wait on it. The Player_SetupWaitForPutAway call above does not work for - * this scenario, as it will softlock. More naive attempts at handling this actor case resulted in + * open that textbox and wait on it. More naive attempts at handling this actor case resulted in * softlocks, not appropriately locking textboxes, duplicate textboxes, or Bombers' Notebook * messages being eaten. The method below handles the intended behavior, both with or without * notebook messages, even if it is a little counterintuitive. From 2b139c215f478ef5deca007dfa6f9b6052db3d1d Mon Sep 17 00:00:00 2001 From: mckinlee Date: Mon, 5 Jan 2026 20:52:39 -0500 Subject: [PATCH 35/44] [Rando] Clock Shuffle + Logic (#1275) * Add Clocks as Items feature * refactor * some clean up * forgot to clangify * use proper randoinf flags and some fixes * proper skipsotcutscene fix * Animated Clock Item (Thank you Caladius!) * Close attempt at animating the clock, not perfect but a good start. * Git gud Clock * fix clock item get crash Co-Authored-By: Caladius * some enhancements * logic refactor, minor changes, and new enhancements * huge rework * clang * fix a oops; any day, not current day * housekeeping and starting items menu support * splits up some milkroad regions and fixes a audio bug * i suck at merging * feedback update * scarecrow dance fix * add clocks to new item tracker * day 2 bean patches * add time logic to enemy drops and simplify day 2 bean macro * rebrand to "Shuffle Time" * more eblo feedback * some more feedback * rename feedback * pauseSaveEntrance fix * fix take 2 * Time Shuffle cleanup Fix pause save Time Shuffle bug Fix event queue daytelop bug Finish merge commit header include Clean up with shouldRegister Co-authored-by: mckinlee --------- Co-authored-by: Caladius Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> --- mm/2s2h/DeveloperTools/SaveEditor.cpp | 67 ++ .../Saving/SavingEnhancements.cpp | 4 +- .../Songs/BetterSongOfDoubleTime.cpp | 18 +- .../Enhancements/Songs/SkipSoTCutscenes.cpp | 26 +- .../Trackers/ItemTracker/ItemTracker.cpp | 17 + .../ItemTracker/ItemTrackerSettings.cpp | 5 +- mm/2s2h/GameInteractor/GameInteractor.cpp | 4 +- .../GameInteractor_VanillaBehavior.h | 16 + mm/2s2h/Rando/CheckTracker/CheckTracker.cpp | 194 +---- mm/2s2h/Rando/ConvertItem.cpp | 36 + mm/2s2h/Rando/DrawFuncs.cpp | 84 +- mm/2s2h/Rando/DrawFuncs.h | 3 + mm/2s2h/Rando/DrawItem.cpp | 10 + mm/2s2h/Rando/GiveItem.cpp | 21 + mm/2s2h/Rando/Logic/GeneratePools.cpp | 4 + mm/2s2h/Rando/Logic/GlitchlessLogic.cpp | 48 +- mm/2s2h/Rando/Logic/Logic.cpp | 102 ++- mm/2s2h/Rando/Logic/Logic.h | 325 +++++++- mm/2s2h/Rando/Logic/Regions/Central.cpp | 247 ++++-- mm/2s2h/Rando/Logic/Regions/East.cpp | 26 +- mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp | 87 +- mm/2s2h/Rando/Logic/Regions/North.cpp | 30 +- mm/2s2h/Rando/Logic/Regions/South.cpp | 34 +- mm/2s2h/Rando/Logic/Regions/TerminaField.cpp | 14 +- mm/2s2h/Rando/Logic/Regions/West.cpp | 2 +- mm/2s2h/Rando/Logic/TimeLogic.cpp | 123 +++ mm/2s2h/Rando/Menu.cpp | 115 ++- mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp | 762 ++++++++++++++++++ mm/2s2h/Rando/MiscBehavior/ClockShuffle.h | 51 ++ mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 1 + mm/2s2h/Rando/MiscBehavior/Traps.cpp | 6 +- mm/2s2h/Rando/Rando.cpp | 2 + mm/2s2h/Rando/RemoveItem.cpp | 24 + mm/2s2h/Rando/StaticData/Items.cpp | 20 +- mm/2s2h/Rando/StaticData/Options.cpp | 3 + mm/2s2h/Rando/Types.h | 52 +- mm/2s2h/ShipUtils.cpp | 4 +- mm/2s2h/ShipUtils.h | 7 + mm/src/code/z_message.c | 8 +- mm/src/code/z_message_nes.c | 8 +- .../actors/ovl_En_Bji_01/z_en_bji_01.c | 6 +- .../actors/ovl_En_Kakasi/z_en_kakasi.c | 16 +- 42 files changed, 2269 insertions(+), 363 deletions(-) create mode 100644 mm/2s2h/Rando/Logic/TimeLogic.cpp create mode 100644 mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp create mode 100644 mm/2s2h/Rando/MiscBehavior/ClockShuffle.h diff --git a/mm/2s2h/DeveloperTools/SaveEditor.cpp b/mm/2s2h/DeveloperTools/SaveEditor.cpp index 8fbdcddca0..a4a111b34f 100644 --- a/mm/2s2h/DeveloperTools/SaveEditor.cpp +++ b/mm/2s2h/DeveloperTools/SaveEditor.cpp @@ -2,6 +2,7 @@ #include "2s2h/BenGui/UIWidgets.hpp" #include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/Rando/Rando.h" +#include "2s2h/Rando/MiscBehavior/ClockShuffle.h" #include "2s2h/CustomMessage/CustomMessage.h" #include "2s2h/CustomItem/CustomItem.h" #include "2s2h/BenGui/Notification.h" @@ -896,6 +897,72 @@ void DrawItemsAndMasksTab() { UIWidgets::Checkbox("Safe Mode", &safeMode); if (gSaveContext.save.shipSaveInfo.saveType == SAVETYPE_RANDO) { + if (RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE]) { + // Time Items Management Section + ImGui::SeparatorText("Time Items"); + + // Individual time items in 3x2 grid with static positioning + RandoItemId clockItems[] = { RI_TIME_DAY_1, RI_TIME_DAY_2, RI_TIME_DAY_3, + RI_TIME_NIGHT_1, RI_TIME_NIGHT_2, RI_TIME_NIGHT_3 }; + + const char* clockNames[] = { "Day 1", "Day 2", "Day 3", "Night 1", "Night 2", "Night 3" }; + + // Use table for static positioning - 3 columns, 2 rows + if (ImGui::BeginTable("ClockItemsTable", 3, ImGuiTableFlags_None)) { + ImGui::TableSetupColumn("Day 1", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Day 2", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Day 3", ImGuiTableColumnFlags_WidthStretch); + + // First row - Day items + ImGui::TableNextRow(); + for (int i = 0; i < 3; i++) { + ImGui::TableNextColumn(); + RandoItemId clockItem = clockItems[i]; + int halfIndex = Rando::ClockItems::GetHalfDayIndexFromClockItem(clockItem); + bool isOwned = Flags_GetRandoInf(static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + halfIndex)); + + std::string buttonText = + isOwned ? ("Remove " + std::string(clockNames[i])) : ("No Item##" + std::to_string(i)); + std::string tooltipText = ""; + if (!isOwned) { + tooltipText = "You don't own " + std::string(clockNames[i]); + } + UIWidgets::ButtonOptions buttonOpts; + buttonOpts.disabled = !isOwned; + buttonOpts.disabledTooltip = !isOwned ? tooltipText.c_str() : ""; + if (UIWidgets::Button(buttonText.c_str(), buttonOpts)) { + Rando::RemoveItem(clockItem); + } + } + + // Second row - Night items + ImGui::TableNextRow(); + for (int i = 3; i < 6; i++) { + ImGui::TableNextColumn(); + RandoItemId clockItem = clockItems[i]; + int halfIndex = Rando::ClockItems::GetHalfDayIndexFromClockItem(clockItem); + bool isOwned = Flags_GetRandoInf(static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + halfIndex)); + + std::string buttonText = + isOwned ? ("Remove " + std::string(clockNames[i])) : ("No Item##" + std::to_string(i)); + std::string tooltipText = ""; + if (!isOwned) { + tooltipText = "You don't own " + std::string(clockNames[i]); + } + UIWidgets::ButtonOptions buttonOpts; + buttonOpts.disabled = !isOwned; + buttonOpts.disabledTooltip = !isOwned ? tooltipText.c_str() : ""; + if (UIWidgets::Button(buttonText.c_str(), buttonOpts)) { + Rando::RemoveItem(clockItem); + } + } + + ImGui::EndTable(); + } + } + + // Queue Randomizer Item Gives section + ImGui::Spacing(); ImGui::SeparatorText("Queue Randomizer Item Gives"); static ImGuiTextFilter riFilter; diff --git a/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp b/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp index 01d9e07da2..e725da230c 100644 --- a/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp +++ b/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp @@ -30,7 +30,9 @@ extern "C" int SavingEnhancements_GetSaveEntrance() { for (int i = 0; i < RESPAWN_MODE_MAX; i++) { gSaveContext.save.shipSaveInfo.respawn[i] = gSaveContext.respawn[i]; } - return entranceToSave; + // Daytelop on new game, with Time Shuffle, makes it possible for entranceToSave to be -1. Given that the player + // must be at this entrance in that scenario, just use it as a fallback. + return entranceToSave < 0 ? ENTRANCE(SOUTH_CLOCK_TOWN, 0) : entranceToSave; } else { switch (gPlayState->sceneId) { // Woodfall Temple + Odolwa diff --git a/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp b/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp index 6061fa6c30..612b7082d5 100644 --- a/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp +++ b/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp @@ -3,6 +3,8 @@ #include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h" #include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/ShipInit.hpp" +#include "2s2h/Rando/MiscBehavior/ClockShuffle.h" +#include "2s2h/CustomMessage/CustomMessage.h" extern "C" { #include "variables.h" @@ -113,7 +115,21 @@ void OnPlayerUpdate(Actor* actor) { } // Pressing A should confirm the song - if (CHECK_BTN_ALL(input->press.button, BTN_A)) { + if (CHECK_BTN_ALL(input->press.button, BTN_A) && gPlayState->msgCtx.msgMode == MSGMODE_NONE) { + // Check if the selected time is owned in ClockShuffle mode + if (!Rando::ClockShuffle::IsTimeOwnedForClockShuffle(sSelectedDay, sSelectedTime)) { + // Play error sound + Audio_PlaySfx(NA_SE_SY_OCARINA_ERROR); + + // Get time description for the error message + std::string timeDescription = + Rando::ClockShuffle::GetTimeDescriptionForMessage(sSelectedDay, sSelectedTime); + + // Show message FIRST + CustomMessage::StartTextbox(timeDescription + " is beyond your reach!"); + return; + } + Audio_PlaySfx_MessageDecide(); gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_DOUBLE_SOT; sActivelyChangingTime = false; diff --git a/mm/2s2h/Enhancements/Songs/SkipSoTCutscenes.cpp b/mm/2s2h/Enhancements/Songs/SkipSoTCutscenes.cpp index bd5e283ed7..d06287d94f 100644 --- a/mm/2s2h/Enhancements/Songs/SkipSoTCutscenes.cpp +++ b/mm/2s2h/Enhancements/Songs/SkipSoTCutscenes.cpp @@ -1,6 +1,8 @@ #include #include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/ShipInit.hpp" +#include "2s2h/Rando/MiscBehavior/ClockShuffle.h" +#include "2s2h/Rando/Logic/Logic.h" extern "C" { #include "functions.h" @@ -27,8 +29,28 @@ void RegisterSkipSoTCutscenes() { // Normally set by EnTest6 gSaveContext.save.eventDayCount = 0; - gSaveContext.save.day = 0; - gSaveContext.save.time = CLOCK_TIME(6, 0) - 1; + + // Set time appropriately if clock shuffle is enabled + if (IS_RANDO && RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE]) { + const int earliestOwnedHalfDay = Rando::ClockItems::FindEarliestOwnedHalfDay(false); + if (earliestOwnedHalfDay != -1) { + bool isDayHalf = (earliestOwnedHalfDay % 2 == 0); + int targetDay = (earliestOwnedHalfDay / 2) + 1; + + if (isDayHalf) { + // Set to previous day at 5:59 AM to trigger day transition + // Vanilla will detect the transition and show DayTelop + gSaveContext.save.day = targetDay - 1; + gSaveContext.save.time = CLOCK_TIME(6, 0) - 1; + } else { + // Night halves: set time directly + Rando::ClockShuffle::SetTimeToHalfDayStart(earliestOwnedHalfDay); + } + } + } else { + gSaveContext.save.day = 0; + gSaveContext.save.time = CLOCK_TIME(6, 0) - 1; + } if (gSaveContext.save.entrance == ENTRANCE(CUTSCENE, 1)) { // Loads to flash back montage before going to Dawn of... in clock town diff --git a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp index e825bc62ad..1aa6d2c5fd 100644 --- a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp +++ b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp @@ -3,6 +3,7 @@ #include "2s2h/BenGui/UIWidgets.hpp" #include "Rando/Rando.h" +#include "Rando/MiscBehavior/ClockShuffle.h" #include "2s2h/ShipUtils.h" #include @@ -108,6 +109,22 @@ extern TrackerImageObject GetTextureObject(int16_t itemId, bool isRandoItem) { case RI_TINGLE_MAP_STONE_TOWER: itemObtained = CHECK_WEEKEVENTREG(WEEKEVENTREG_TINGLE_MAP_BOUGHT_STONE_TOWER); break; + case RI_TIME_DAY_1: + case RI_TIME_DAY_2: + case RI_TIME_DAY_3: + randoImageObject.textureColor = ImVec4(1.0f, 0.9f, 0.3f, 1.0f); // Yellow/gold for sun + itemObtained = Flags_GetRandoInf( + static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + + Rando::ClockItems::GetHalfDayIndexFromClockItem((RandoItemId)itemId))); + break; + case RI_TIME_NIGHT_1: + case RI_TIME_NIGHT_2: + case RI_TIME_NIGHT_3: + randoImageObject.textureColor = ImVec4(0.5f, 0.7f, 1.0f, 1.0f); // Light blue for moon + itemObtained = Flags_GetRandoInf( + static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + + Rando::ClockItems::GetHalfDayIndexFromClockItem((RandoItemId)itemId))); + break; case RI_TRIFORCE_PIECE: itemObtained = gSaveContext.save.shipSaveInfo.rando.foundTriforcePieces > 0; break; diff --git a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp index b20b576982..618744126e 100644 --- a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp +++ b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp @@ -27,7 +27,7 @@ std::vector listOrder = { }; std::vector randoListOrder = { - "Frogs", "Boss Souls", "Owl Statues", "Tingle Maps", "Misc", + "Frogs", "Boss Souls", "Owl Statues", "Tingle Maps", "Time", "Misc", }; std::map> defaultItemLists = { @@ -46,6 +46,7 @@ std::map> randoItemLists = { { "Boss Souls", { RI_SOUL_GOHT, RI_SOUL_TWINMOLD, 5 } }, { "Owl Statues", { RI_OWL_CLOCK_TOWN_SOUTH, RI_OWL_ZORA_CAPE, 5 } }, { "Tingle Maps", { RI_TINGLE_MAP_CLOCK_TOWN, RI_TINGLE_MAP_WOODFALL, 6 } }, + { "Time", { RI_TIME_DAY_1, RI_TIME_NIGHT_3, 6 } }, { "Misc", { RI_TRIFORCE_PIECE, RI_TRIFORCE_PIECE, 1 } }, }; @@ -249,7 +250,7 @@ void DrawItemList(std::string listName, int columns) { std::vector emptyList; if (listName == "Frogs" || listName == "Boss Souls" || listName == "Owl Statues" || - listName == "Tingle Maps" || listName == "Misc") { + listName == "Tingle Maps" || listName == "Time" || listName == "Misc") { for (int j = std::get<0>(randoItemLists.at(listName)); j <= std::get<1>(randoItemLists.at(listName)); j++) { ImGui::TableNextColumn(); diff --git a/mm/2s2h/GameInteractor/GameInteractor.cpp b/mm/2s2h/GameInteractor/GameInteractor.cpp index 7acfa594be..c50ee54e34 100644 --- a/mm/2s2h/GameInteractor/GameInteractor.cpp +++ b/mm/2s2h/GameInteractor/GameInteractor.cpp @@ -498,7 +498,9 @@ void ProcessEvents(Actor* actor) { enItem00->actor.destroy = [](Actor* actor, PlayState* play) { if (!(CUSTOM_ITEM_FLAGS & CustomItem::CALLED_ACTION)) { // Event was not handled, requeue it - GameInteractor::Instance->events.push_back(GameInteractor::Instance->currentEvent); + auto lostEvent = GameInteractor::Instance->currentEvent; + GameInteractor::Instance->currentEvent = GIEventNone{}; + GameInteractor::Instance->events.push_back(lostEvent); } }; } else if (auto e = std::get_if(&nextEvent)) { diff --git a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h index a4bbe1793a..235a2ea843 100644 --- a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h +++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h @@ -1635,6 +1635,14 @@ typedef enum { // - None VB_SAVE_ON_B_BUTTON_IN_PAUSE_MENU, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_SCARECROW_DANCE_SET_TIME, + // #### `result` // ```c // gSaveContext.save.saveInfo.inventory.items[ITEM_OCARINA_OF_TIME] == ITEM_NONE @@ -1950,6 +1958,14 @@ typedef enum { // - None VB_THIEF_BIRD_STEAL, + // #### `result` + // ```c + // TIME_UNTIL_MOON_CRASH + // ``` + // #### `args` + // - `*u32` (time variable) + VB_TIME_UNTIL_MOON_CRASH_CALCULATION, + // #### `result` // ```c // true diff --git a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp index 2411b43d4e..8a5c53a9fb 100644 --- a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp +++ b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp @@ -145,143 +145,6 @@ bool checkTrackerShouldShowRow(bool obtained, bool skipped) { return showCheck; } -void CheckTrackerDrawLogicalList() { - std::set reachableRegions = {}; - // Get connected entrances from starting & warp points - Rando::Logic::FindReachableRegions(RR_MAX, reachableRegions); - // Get connected regions from current entrance (TODO: Make this optional) - Rando::Logic::FindReachableRegions(Rando::Logic::GetRegionIdFromEntrance(gSaveContext.save.entrance), - reachableRegions); - - std::vector sortedRegionIds; - for (auto& regionId : reachableRegions) { - sortedRegionIds.push_back(regionId); - } - std::sort(sortedRegionIds.begin(), sortedRegionIds.end(), [](RandoRegionId a, RandoRegionId b) { - return betterSceneIndex[Rando::Logic::Regions[a].sceneId] < betterSceneIndex[Rando::Logic::Regions[b].sceneId]; - }); - - for (RandoRegionId regionId : sortedRegionIds) { - if (CVAR_SCROLL_TO_SCENE && sScrollToTargetEntrance != -1 && - Rando::Logic::GetRegionIdFromEntrance(sScrollToTargetEntrance) == regionId) { - ImGui::SetScrollHereY(0.0f); - sScrollToTargetScene = -1; - sScrollToTargetEntrance = -1; - } - auto& randoRegion = Rando::Logic::Regions[regionId]; - std::vector> availableChecks; - std::vector> availableEvents; - uint32_t obtainedCheckSum = 0; - - for (auto& [randoCheckId, accessLogicFunc] : randoRegion.checks) { - auto& randoStaticCheck = Rando::StaticData::Checks[randoCheckId]; - auto& randoSaveCheck = RANDO_SAVE_CHECKS[randoCheckId]; - if (randoSaveCheck.shuffled && accessLogicFunc.first()) { - if (randoSaveCheck.obtained) { - obtainedCheckSum++; - if (CVAR_HIDE_COLLECTED) { - continue; - } - } - - if (randoSaveCheck.skipped && CVAR_HIDE_SKIPPED) { - continue; - } - - if (!sCheckTrackerFilter.PassFilter(Rando::StaticData::CheckNames[randoCheckId].c_str())) { - continue; - } - - availableChecks.push_back({ randoCheckId, accessLogicFunc.second }); - } - } - - for (auto& event : randoRegion.events) { - if (!RANDO_EVENTS[event.first] && event.second()) { - RANDO_EVENTS[event.first]++; - } - } - - if (availableChecks.size() > 0 || availableEvents.size() > 0) { - std::string regionName = Ship_GetSceneName(randoRegion.sceneId); - if (randoRegion.name != "") { - regionName += " - "; - regionName += randoRegion.name; - } - - regionName += " (" + std::to_string(obtainedCheckSum) + "/" + std::to_string(availableChecks.size()) + ")"; - - ImGui::PushID(regionId); - ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0, 0, 0, 0)); - if (sExpandedHeadersState != sExpandedHeadersToggle) { - ImGui::SetNextItemOpen(sExpandedHeadersToggle); - } - if (ImGui::CollapsingHeader(regionName.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Indent(20.0f); - if (ImGui::BeginTable("Check Tracker", 2)) { - ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 16.0f); - ImGui::TableSetupColumn("Check"); - ImGui::TableNextColumn(); - for (auto& [name, accessLogicString] : availableEvents) { - ImGui::TableNextColumn(); - ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(UIWidgets::Colors::White)); - ImGui::Text("%s (Event)", name.c_str()); - if (accessLogicString != "") { - UIWidgets::Tooltip(accessLogicString.c_str()); - } - ImGui::PopStyleColor(); - ImGui::TableNextColumn(); - } - for (auto& [checkId, accessLogicString] : availableChecks) { - auto& randoStaticCheck = Rando::StaticData::Checks[checkId]; - auto& randoSaveCheck = RANDO_SAVE_CHECKS[checkId]; - ImGui::PushStyleColor(ImGuiCol_Text, randoSaveCheck.obtained - ? UIWidgets::ColorValues.at(UIWidgets::Colors::Green) - : randoSaveCheck.skipped - ? UIWidgets::ColorValues.at(UIWidgets::Colors::Indigo) - : UIWidgets::ColorValues.at(UIWidgets::Colors::White)); - if (checkTrackerShouldShowRow(randoSaveCheck.obtained, randoSaveCheck.skipped)) { - ImGui::BeginGroup(); - float cursorPosY = ImGui::GetCursorPosY(); - if (randoStaticCheck.randoCheckType == RCTYPE_OWL) { - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 3.5f); - } - DrawCheckTypeIcon(checkId); - ImGui::TableNextColumn(); - ImGui::SetCursorPosY(cursorPosY); - ImGui::Text("%s", Rando::StaticData::CheckNames[checkId].c_str()); - if (accessLogicString != "") { - UIWidgets::Tooltip(accessLogicString.c_str()); - } - if (randoSaveCheck.obtained) { - ImGui::SameLine(0, 25.0f); - ImGui::Text("(%s)", Rando::StaticData::Items[randoSaveCheck.randoItemId].name); - } else if (randoSaveCheck.skipped) { - ImGui::SameLine(0, 25.0f); - ImGui::Text("(Skipped)"); - } - ImGui::EndGroup(); - ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::IsItemHovered() - ? IM_COL32(255, 255, 0, 128) - : IM_COL32(255, 255, 255, 0)); - if (ImGui::IsItemClicked()) { - randoSaveCheck.skipped = !randoSaveCheck.skipped; - } - ImGui::TableNextColumn(); - } - ImGui::PopStyleColor(); - } - ImGui::EndTable(); - } - ImGui::Unindent(20.0f); - } - ImGui::PopStyleColor(); - ImGui::PopID(); - } - } -} - std::unordered_map checksInLogic; static u32 lastFrame = 0; @@ -293,18 +156,53 @@ void RefreshChecksInLogic() { lastFrame = gGameState->frames; checksInLogic.clear(); + // Clear all events so they're re-evaluated fresh each refresh + for (int i = 0; i < RE_MAX; i++) { + RANDO_EVENTS[i] = 0; + } + std::set reachableRegions = { RR_MAX, Rando::Logic::GetRegionIdFromEntrance(gSaveContext.save.entrance), }; - // Get connected entrances from starting & warp points - Rando::Logic::FindReachableRegions(RR_MAX, reachableRegions); - // Get connected regions from current entrance (TODO: Make this optional) - Rando::Logic::FindReachableRegions(Rando::Logic::GetRegionIdFromEntrance(gSaveContext.save.entrance), - reachableRegions); + // Initialize time states using shared function + std::unordered_map regionTimeStates = + Rando::Logic::InitializeRegionTimeStates(RR_MAX); + + // Iteratively explore until no new regions/events discovered + bool changed = true; + while (changed) { + changed = false; + auto prevSize = reachableRegions.size(); + + // Explore from all currently reachable regions + std::set regionsToExplore = reachableRegions; + for (RandoRegionId regionId : regionsToExplore) { + Rando::Logic::FindReachableRegions(regionId, reachableRegions, regionTimeStates); + } + + // Trigger events for newly discovered regions + for (RandoRegionId regionId : reachableRegions) { + auto& randoRegion = Rando::Logic::Regions[regionId]; + Rando::Logic::SetCurrentRegionTime(regionTimeStates, regionId); + + for (auto& event : randoRegion.events) { + if (!RANDO_EVENTS[event.first] && event.second()) { + RANDO_EVENTS[event.first]++; + changed = true; + } + } + } + + if (reachableRegions.size() != prevSize) { + changed = true; + } + } + + // Evaluate checks for all reachable regions for (RandoRegionId regionId : reachableRegions) { auto& randoRegion = Rando::Logic::Regions[regionId]; - std::vector> availableChecks; + Rando::Logic::SetCurrentRegionTime(regionTimeStates, regionId); for (auto& [randoCheckId, accessLogicFunc] : randoRegion.checks) { auto& randoStaticCheck = Rando::StaticData::Checks[randoCheckId]; @@ -313,12 +211,6 @@ void RefreshChecksInLogic() { checksInLogic.insert({ randoCheckId, true }); } } - - for (auto& event : randoRegion.events) { - if (!RANDO_EVENTS[event.first] && event.second()) { - RANDO_EVENTS[event.first]++; - } - } } } @@ -512,11 +404,7 @@ void CheckTrackerWindow::Draw() { ImGui::Text("Total: %s", totalChecksFound().c_str()); ImGui::BeginChild("Checks"); - // if (CVAR_SHOW_LOGIC) { - // CheckTrackerDrawLogicalList(); - // } else { CheckTrackerDrawNonLogicalList(); - // } sExpandedHeadersState = sExpandedHeadersToggle; ImGui::EndChild(); diff --git a/mm/2s2h/Rando/ConvertItem.cpp b/mm/2s2h/Rando/ConvertItem.cpp index 26ba83a947..46bc5f2140 100644 --- a/mm/2s2h/Rando/ConvertItem.cpp +++ b/mm/2s2h/Rando/ConvertItem.cpp @@ -1,4 +1,5 @@ #include "Rando/Rando.h" +#include "Rando/MiscBehavior/ClockShuffle.h" #include "2s2h/ShipUtils.h" #include @@ -427,6 +428,16 @@ bool Rando::IsItemObtainable(RandoItemId randoItemId, RandoCheckId randoCheckId) case RI_SOUL_ODOLWA: case RI_SOUL_TWINMOLD: return !Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_GOHT + (randoItemId - RI_SOUL_GOHT)); + case RI_TIME_DAY_1: + case RI_TIME_NIGHT_1: + case RI_TIME_DAY_2: + case RI_TIME_NIGHT_2: + case RI_TIME_DAY_3: + case RI_TIME_NIGHT_3: + return !Flags_GetRandoInf(RANDO_INF_OBTAINED_CLOCK_DAY_1 + + Rando::ClockItems::GetHalfDayIndexFromClockItem(randoItemId)); + case RI_TIME_PROGRESSIVE: + return true; // These items are technically fine to receive again because they don't do anything, but we'll convert them to // ensure it's clear to the player something didn't go wrong. We just simply check the inventory state // Masks @@ -471,6 +482,31 @@ bool Rando::IsItemObtainable(RandoItemId randoItemId, RandoCheckId randoCheckId) RandoItemId Rando::ConvertItem(RandoItemId randoItemId, RandoCheckId randoCheckId) { if (IsItemObtainable(randoItemId, randoCheckId)) { switch (randoItemId) { + case RI_TIME_PROGRESSIVE: { + // Choose the next clock according to mode and current owned half-days + int mode = RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE_PROGRESSIVE]; + + if (mode == RO_CLOCK_SHUFFLE_RANDOM) { + // Random mode should never have progressive items + return RI_JUNK; + } + + // Build list in target order + RandoItemId ascending[] = { RI_TIME_DAY_1, RI_TIME_NIGHT_1, RI_TIME_DAY_2, + RI_TIME_NIGHT_2, RI_TIME_DAY_3, RI_TIME_NIGHT_3 }; + RandoItemId descending[] = { RI_TIME_NIGHT_3, RI_TIME_DAY_3, RI_TIME_NIGHT_2, + RI_TIME_DAY_2, RI_TIME_NIGHT_1, RI_TIME_DAY_1 }; + RandoItemId* order = (mode == RO_CLOCK_SHUFFLE_DESCENDING) ? descending : ascending; + for (int i = 0; i < 6; ++i) { + int halfIndex = Rando::ClockItems::GetHalfDayIndexFromClockItem(order[i]); + if (halfIndex >= 0 && + !Flags_GetRandoInf(static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + halfIndex))) { + return order[i]; + } + } + // All owned; degrade to junk + return RI_JUNK; + } case RI_PROGRESSIVE_BOMB_BAG: if (CUR_UPG_VALUE(UPG_BOMB_BAG) == 0) { return RI_BOMB_BAG_20; diff --git a/mm/2s2h/Rando/DrawFuncs.cpp b/mm/2s2h/Rando/DrawFuncs.cpp index 5abf1c4bf5..8eab3dbf2e 100644 --- a/mm/2s2h/Rando/DrawFuncs.cpp +++ b/mm/2s2h/Rando/DrawFuncs.cpp @@ -5,6 +5,7 @@ extern "C" { #include #include "objects/gameplay_keep/gameplay_keep.h" +#include "objects/object_obj_tokeidai/object_obj_tokeidai.h" // Soul Effects #include "src/overlays/actors/ovl_Obj_Moon_Stone/z_obj_moon_stone.h" @@ -22,6 +23,11 @@ s32 EnMinifrog_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec // Other Actor Includes /* Minifrog */ #include "objects/object_fr/object_fr.h" +/* Clock */ #include "overlays/actors/ovl_Obj_Tokeidai/z_obj_tokeidai.h" + +// Clock +void ObjTokeidai_RotateOnMinuteChange(ObjTokeidai* thisx, s32 playSfx); +void ObjTokeidai_RotateOnHourChange(ObjTokeidai* thisx, PlayState* play); // clang-format on } @@ -261,4 +267,80 @@ extern void DrawMinifrog(RandoItemId randoItemId, Actor* actor) { EnMinifrog_OverrideLimbDraw, EnMinifrogPostLimbDraw, actor); CLOSE_DISPS(gPlayState->state.gfxCtx); -} \ No newline at end of file +} + +extern void DrawClock(RandoItemId randoItemId, Actor* actor) { + OPEN_DISPS(gPlayState->state.gfxCtx); + + ObjTokeidai* clockActor = (ObjTokeidai*)actor; + static u32 lastUpdate = 0; + static f32 yTranslation = 0; + static f32 xRotation = 0; + static int16_t minuteRingOrExteriorGearRotation = 0; + static f32 clockFaceZTranslation = 0; + static int16_t clockFaceRotation = 0; + static int16_t sunMoonPanelRotation = 0; + + switch (randoItemId) { + case RI_TIME_DAY_1: + case RI_TIME_DAY_2: + case RI_TIME_DAY_3: + clockFaceRotation = 0xC000; + sunMoonPanelRotation = 0; + break; + case RI_TIME_NIGHT_1: + case RI_TIME_NIGHT_2: + case RI_TIME_NIGHT_3: + clockFaceRotation = 0; + sunMoonPanelRotation = 0x8000; + break; + case RI_TIME_PROGRESSIVE: + clockFaceRotation = gSaveContext.save.isNight ? 0 : 0xC000; + sunMoonPanelRotation = gSaveContext.save.isNight ? 0x8000 : 0; + break; + } + + if (clockActor != nullptr && clockActor->actor.id == ACTOR_OBJ_TOKEIDAI) { + clockActor->clockTime = gSaveContext.save.time; + + if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { + lastUpdate = gPlayState->state.frames; + ObjTokeidai_RotateOnMinuteChange(clockActor, true); + ObjTokeidai_RotateOnHourChange(clockActor, gPlayState); + yTranslation = clockActor->yTranslation; + xRotation = clockActor->xRotation; + minuteRingOrExteriorGearRotation = clockActor->minuteRingOrExteriorGearRotation; + clockFaceZTranslation = clockActor->clockFaceZTranslation; + clockFaceRotation = clockActor->clockFaceRotation; + sunMoonPanelRotation = clockActor->sunMoonPanelRotation; + } + } + + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Translate(0.0f, yTranslation, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.015f, 0.015f, 0.015f, MTXMODE_APPLY); + Matrix_Translate(0.0f, 0.0f, -1791.0f, MTXMODE_APPLY); + Matrix_RotateXS(-xRotation, MTXMODE_APPLY); + Matrix_Translate(0.0f, 0.0f, 1791.0f, MTXMODE_APPLY); + + Matrix_Push(); + Matrix_RotateZS(-minuteRingOrExteriorGearRotation, MTXMODE_APPLY); + MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, gPlayState->state.gfxCtx); + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gClockTowerMinuteRingDL); + Matrix_Pop(); + + Matrix_Translate(0.0f, 0.0f, clockFaceZTranslation, MTXMODE_APPLY); + MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, gPlayState->state.gfxCtx); + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gClockTowerClockCenterAndHandDL); + + Matrix_RotateZS(-clockFaceRotation * 2, MTXMODE_APPLY); + MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, gPlayState->state.gfxCtx); + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gClockTowerClockFaceDL); + + Matrix_Translate(0.0f, -1112.0f, -19.6f, MTXMODE_APPLY); + Matrix_RotateYS(sunMoonPanelRotation, MTXMODE_APPLY); + MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, gPlayState->state.gfxCtx); + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gClockTowerSunAndMoonPanelDL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); +} diff --git a/mm/2s2h/Rando/DrawFuncs.h b/mm/2s2h/Rando/DrawFuncs.h index 345e63e5c3..818e73efb6 100644 --- a/mm/2s2h/Rando/DrawFuncs.h +++ b/mm/2s2h/Rando/DrawFuncs.h @@ -13,4 +13,7 @@ void DrawTwinmold(); // Other Actor Functions void DrawMinifrog(RandoItemId randoItemId, Actor* actor); +// Clock Function +void DrawClock(RandoItemId randoItemId, Actor* actor); + #endif diff --git a/mm/2s2h/Rando/DrawItem.cpp b/mm/2s2h/Rando/DrawItem.cpp index fa96c72dbd..29d9f87110 100644 --- a/mm/2s2h/Rando/DrawItem.cpp +++ b/mm/2s2h/Rando/DrawItem.cpp @@ -497,6 +497,15 @@ void Rando::DrawItem(RandoItemId randoItemId, Actor* actor) { case RI_OWL_ZORA_CAPE: DrawOwlStatue(); break; + case RI_TIME_DAY_1: + case RI_TIME_NIGHT_1: + case RI_TIME_DAY_2: + case RI_TIME_NIGHT_2: + case RI_TIME_DAY_3: + case RI_TIME_NIGHT_3: + case RI_TIME_PROGRESSIVE: + DrawClock(randoItemId, actor); + break; case RI_PROGRESSIVE_LULLABY: case RI_PROGRESSIVE_MAGIC: case RI_PROGRESSIVE_BOW: @@ -553,6 +562,7 @@ void Rando::DrawItem(RandoItemId randoItemId, Actor* actor) { case RI_PROGRESSIVE_MAGIC: case RI_SINGLE_MAGIC: case RI_DOUBLE_MAGIC: + case RI_TIME_PROGRESSIVE: DrawSparkles(randoItemId, actor); break; default: diff --git a/mm/2s2h/Rando/GiveItem.cpp b/mm/2s2h/Rando/GiveItem.cpp index 5d404db23d..6c6a234617 100644 --- a/mm/2s2h/Rando/GiveItem.cpp +++ b/mm/2s2h/Rando/GiveItem.cpp @@ -1,5 +1,6 @@ #include "Rando/Rando.h" #include "Rando/MiscBehavior/MiscBehavior.h" +#include "Rando/MiscBehavior/ClockShuffle.h" extern "C" { #include "variables.h" @@ -258,6 +259,26 @@ void Rando::GiveItem(RandoItemId randoItemId) { case RI_OWL_ZORA_CAPE: Sram_ActivateOwl(OWL_WARP_ZORA_CAPE); break; + case RI_TIME_DAY_1: + case RI_TIME_NIGHT_1: + case RI_TIME_DAY_2: + case RI_TIME_NIGHT_2: + case RI_TIME_DAY_3: + case RI_TIME_NIGHT_3: { + int index = Rando::ClockItems::GetHalfDayIndexFromClockItem(randoItemId); + if (index != Rando::ClockItems::INVALID) { + Flags_SetRandoInf(static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + index)); + } + break; + } + case RI_TIME_PROGRESSIVE: { + // Convert to actual half-day per mode + RandoItemId concrete = Rando::ConvertItem(RI_TIME_PROGRESSIVE); + if (concrete != RI_JUNK) { + Rando::GiveItem(concrete); + } + break; + } case RI_HEART_CONTAINER: case RI_HEART_PIECE: gSaveContext.healthAccumulator = gSaveContext.save.saveInfo.playerData.healthCapacity + 0x10; diff --git a/mm/2s2h/Rando/Logic/GeneratePools.cpp b/mm/2s2h/Rando/Logic/GeneratePools.cpp index 0fd55094d1..c3385ab338 100644 --- a/mm/2s2h/Rando/Logic/GeneratePools.cpp +++ b/mm/2s2h/Rando/Logic/GeneratePools.cpp @@ -1,4 +1,5 @@ #include "Logic.h" +#include "Rando/MiscBehavior/ClockShuffle.h" #include #include @@ -175,6 +176,9 @@ void GeneratePools(RandoSaveInfo& saveInfo, std::vector& checkPool } } + // Initialize shuffle time settings and item pool + ClockShuffle::InitializeFileClocks(itemPool); + // Abilities if (saveInfo.randoSaveOptions[RO_SHUFFLE_SWIM] == RO_GENERIC_YES) { itemPool.push_back(RI_ABILITY_SWIM); diff --git a/mm/2s2h/Rando/Logic/GlitchlessLogic.cpp b/mm/2s2h/Rando/Logic/GlitchlessLogic.cpp index 1bd2fd745d..f92c6528cd 100644 --- a/mm/2s2h/Rando/Logic/GlitchlessLogic.cpp +++ b/mm/2s2h/Rando/Logic/GlitchlessLogic.cpp @@ -25,6 +25,9 @@ void ApplyGlitchlessLogicToSaveContext(std::vector& checkPool, std std::map checksInLogic; std::set>*> eventsInLogic; + // Initialize time states using shared function + std::unordered_map regionTimeStates = InitializeRegionTimeStates(RR_MAX); + RandoCheckId checkWithJunk = RC_UNKNOWN; std::set nonJunkItemsThatWeHaveTried; std::vector checksWithJunk; @@ -68,7 +71,7 @@ void ApplyGlitchlessLogicToSaveContext(std::vector& checkPool, std // Crawl through all reachable regions and add any new reachable regions auto prevRegionsInLogicSize = regionsInLogic.size(); for (RandoRegionId regionId : regionsInLogic) { - FindReachableRegions(regionId, regionsInLogic); + FindReachableRegions(regionId, regionsInLogic, regionTimeStates); } if (regionsInLogic.size() != prevRegionsInLogicSize) { regionsInLogicChanged = true; @@ -77,18 +80,31 @@ void ApplyGlitchlessLogicToSaveContext(std::vector& checkPool, std for (RandoRegionId regionId : regionsInLogic) { auto& randoRegion = Regions[regionId]; + // Set current region time for check evaluation + SetCurrentRegionTime(regionTimeStates, regionId); + // Apply any new events for (auto& randoEvent : randoRegion.events) { - if (!eventsInLogic.contains(&randoEvent) && randoEvent.second()) { - RANDO_EVENTS[randoEvent.first]++; - eventsInLogic.insert(&randoEvent); - eventsInLogicChanged = true; + // When Clock Shuffle is active, always check events (don't skip based on eventsInLogic) + bool skipEventCheck = !SettingClocks() && eventsInLogic.contains(&randoEvent); + + if (!skipEventCheck && randoEvent.second()) { + // Only increment if not already triggered + if (!eventsInLogic.contains(&randoEvent)) { + RANDO_EVENTS[randoEvent.first]++; + eventsInLogic.insert(&randoEvent); + eventsInLogicChanged = true; + } } } // Apply any new checks for (auto& [randoCheckId, checkLogic] : randoRegion.checks) { if (checksInLogic.find(randoCheckId) == checksInLogic.end() && checkLogic.first()) { + // VALIDATION: Verify check is reachable with owned time + TimeLogic::ValidateRegionTimeOwnership(regionId, randoCheckId, + regionTimeStates[regionId].timeSlices, "Glitchless"); + auto it = std::find(checkPool.begin(), checkPool.end(), randoCheckId); bool isShuffled = it != checkPool.end(); checksInLogic.insert({ randoCheckId, isShuffled }); @@ -116,6 +132,28 @@ void ApplyGlitchlessLogicToSaveContext(std::vector& checkPool, std RANDO_SAVE_CHECKS[randoCheckId].randoItemId = randoItemId; RANDO_SAVE_CHECKS[randoCheckId].shuffled = isShuffled; GiveItem(ConvertItem(randoItemId)); + + // Update time states for all regions when time items are obtained + if (randoItemId >= RI_TIME_DAY_1 && randoItemId <= RI_TIME_PROGRESSIVE) { + uint64_t newTimeSlices = TimeLogic::GetOwnedTimeSlices(); + // Update RR_MAX time state first - this is the source for new region discoveries + if (regionTimeStates.find(RR_MAX) != regionTimeStates.end()) { + regionTimeStates[RR_MAX].timeSlices = newTimeSlices; + } + // Update existing region time states to reflect new owned time + for (auto& [regionId, timeState] : regionTimeStates) { + timeState.timeSlices = newTimeSlices; + // Expand time forward based on region's stay restrictions + if (timeState.canStayOverTime) { + timeState.timeSlices = TimeLogic::ExpandTimeForward(newTimeSlices, Regions[regionId]); + } + } + // Trigger region re-exploration to discover new regions accessible with expanded time + // Also trigger event re-evaluation since time-gated events may now be accessible + regionsInLogicChanged = true; + eventsInLogicChanged = true; + } + checksInLogicChanged = true; } } diff --git a/mm/2s2h/Rando/Logic/Logic.cpp b/mm/2s2h/Rando/Logic/Logic.cpp index b763975571..b72dde87a1 100644 --- a/mm/2s2h/Rando/Logic/Logic.cpp +++ b/mm/2s2h/Rando/Logic/Logic.cpp @@ -9,6 +9,9 @@ namespace Logic { std::map Regions = {}; +// Thread-local storage for current region time during check evaluation +thread_local uint64_t gCurrentRegionTime = 0; + RandoRegionId GetRegionIdFromEntrance(s32 entrance) { static std::map entranceToRegionId; if (entranceToRegionId.empty()) { @@ -32,23 +35,102 @@ RandoRegionId GetRegionIdFromEntrance(s32 entrance) { return RR_MAX; } -void FindReachableRegions(RandoRegionId currentRegion, std::set& reachableRegions) { - auto& randoRegion = Rando::Logic::Regions[currentRegion]; +// Helper: Convert runtime game time to TimeSlice enum for dynamic time checking +TimeSlice TimeSliceFromGameTime(s32 day, u16 time) { + // Handle edge cases: day 0 or invalid inputs + if (day < 1 || day > 3) { + return TIME_DAY1_AM_06_00; // Default fallback + } + + // Convert to time slice based on day/time ranges + // This is approximate - exact mapping would need game time constants + bool isNight = (time >= GAME_TIME_NIGHT_START || time < GAME_TIME_DAY_START); + int halfDayOffset = (day - 1) * 2 + (isNight ? 1 : 0); + + // Map to approximate time slice within the half-day + if (halfDayOffset >= 6) + return TIME_NIGHT3_AM_05_00; + + const auto& range = HALF_DAY_TIME_RANGES[halfDayOffset]; + return static_cast(range.startSlice); +} + +// Helper: Returns the initial time state for logic solving (start at Day 1, 6:00 AM) +RegionTimeState InitialTimeState() { + return { .timeSlices = (TIME_BIT_ONE << TIME_DAY1_AM_06_00), .canStayOverTime = false }; +} + +// Shared initialization function for region time states +std::unordered_map InitializeRegionTimeStates(RandoRegionId startRegion) { + std::unordered_map states; + + // Start with appropriate time based on Clock Shuffle + if (SettingClocks()) { + // Clock Shuffle: start with owned time slices only + states[startRegion] = { .timeSlices = TimeLogic::GetOwnedTimeSlices(), .canStayOverTime = false }; + } else { + // No Clock Shuffle: start at Day 1 6am + states[startRegion] = InitialTimeState(); + } + + return states; +} - for (auto& [connectedRegionId, condition] : randoRegion.connections) { - // Check if the region is accessible and hasn’t been visited yet +// Helper to ensure region time state exists +void EnsureRegionTimeState(std::unordered_map& regionTimeStates, + RandoRegionId regionId) { + if (regionTimeStates.find(regionId) == regionTimeStates.end()) { + auto& region = Regions[regionId]; + regionTimeStates[regionId] = { .timeSlices = TimeLogic::GetOwnedTimeSlices(), + .canStayOverTime = region.canStayOverTime }; + } +} + +// Time expansion during region traversal with stay restrictions +// Time expansion semantics: if canStayOverTime, sequentially test each future time slice +// Stop permanently if any timeStayRestrictions check fails +void FindReachableRegions(RandoRegionId currentRegion, std::set& reachableRegions, + std::unordered_map& regionTimeStates) { + // Ensure current region has time state + EnsureRegionTimeState(regionTimeStates, currentRegion); + + auto& sourceRegion = Regions[currentRegion]; + auto& sourceTimeState = regionTimeStates[currentRegion]; + + // Expand time if player can wait in this region + uint64_t currentTime = sourceTimeState.timeSlices; + if (sourceTimeState.canStayOverTime) { + currentTime = TimeLogic::ExpandTimeForward(currentTime, sourceRegion); + sourceTimeState.timeSlices = currentTime; + } + + // Set global time for check evaluation + gCurrentRegionTime = currentTime; + + // Explore connections + for (auto& [connectedRegionId, condition] : sourceRegion.connections) { if (reachableRegions.count(connectedRegionId) == 0 && condition.first()) { - reachableRegions.insert(connectedRegionId); // Mark region as visited - FindReachableRegions(connectedRegionId, reachableRegions); // Recursively visit neighbors + reachableRegions.insert(connectedRegionId); + + auto& targetRegion = Regions[connectedRegionId]; + regionTimeStates[connectedRegionId] = { .timeSlices = currentTime, + .canStayOverTime = targetRegion.canStayOverTime }; + + FindReachableRegions(connectedRegionId, reachableRegions, regionTimeStates); } } - for (auto& [exitId, regionExit] : randoRegion.exits) { + // Explore exits + for (auto& [exitId, regionExit] : sourceRegion.exits) { RandoRegionId connectedRegionId = GetRegionIdFromEntrance(exitId); - // Check if the region is accessible and hasn’t been visited yet if (reachableRegions.count(connectedRegionId) == 0 && regionExit.condition()) { - reachableRegions.insert(connectedRegionId); // Mark region as visited - FindReachableRegions(connectedRegionId, reachableRegions); // Recursively visit neighbors + reachableRegions.insert(connectedRegionId); + + auto& targetRegion = Regions[connectedRegionId]; + regionTimeStates[connectedRegionId] = { .timeSlices = currentTime, + .canStayOverTime = targetRegion.canStayOverTime }; + + FindReachableRegions(connectedRegionId, reachableRegions, regionTimeStates); } } } diff --git a/mm/2s2h/Rando/Logic/Logic.h b/mm/2s2h/Rando/Logic/Logic.h index ccee2a3a0c..7654fc8c85 100644 --- a/mm/2s2h/Rando/Logic/Logic.h +++ b/mm/2s2h/Rando/Logic/Logic.h @@ -17,7 +17,112 @@ namespace Rando { namespace Logic { -void FindReachableRegions(RandoRegionId currentRegion, std::set& reachableRegions); +// Time slice enum - 45 granular time points throughout MM's 3-day cycle +enum TimeSlice { + TIME_DAY1_AM_06_00 = 0, + TIME_DAY1_AM_07_00, + TIME_DAY1_AM_08_00, + TIME_DAY1_AM_10_00, + TIME_DAY1_PM_01_45, + TIME_DAY1_PM_03_00, + TIME_DAY1_PM_04_00, + TIME_NIGHT1_PM_06_00, + TIME_NIGHT1_PM_08_00, + TIME_NIGHT1_PM_09_00, + TIME_NIGHT1_PM_10_00, + TIME_NIGHT1_PM_11_00, + TIME_NIGHT1_AM_12_00, + TIME_NIGHT1_AM_02_30, + TIME_NIGHT1_AM_04_00, + TIME_NIGHT1_AM_05_00, + TIME_DAY2_AM_06_00, + TIME_DAY2_AM_07_00, + TIME_DAY2_AM_08_00, + TIME_DAY2_AM_10_00, + TIME_DAY2_AM_11_30, + TIME_DAY2_PM_02_00, + TIME_DAY2_PM_04_00, + TIME_NIGHT2_PM_06_00, + TIME_NIGHT2_PM_08_00, + TIME_NIGHT2_PM_09_00, + TIME_NIGHT2_PM_10_00, + TIME_NIGHT2_PM_11_00, + TIME_NIGHT2_AM_12_00, + TIME_NIGHT2_AM_04_00, + TIME_NIGHT2_AM_05_00, + TIME_DAY3_AM_06_00, + TIME_DAY3_AM_07_00, + TIME_DAY3_AM_08_00, + TIME_DAY3_AM_10_00, + TIME_DAY3_AM_11_30, + TIME_DAY3_PM_01_00, + TIME_NIGHT3_PM_06_00, + TIME_NIGHT3_PM_08_00, + TIME_NIGHT3_PM_09_00, + TIME_NIGHT3_PM_10_00, + TIME_NIGHT3_PM_11_00, + TIME_NIGHT3_AM_12_00, + TIME_NIGHT3_AM_04_00, + TIME_NIGHT3_AM_05_00 // = 44 +}; + +// Time slice count and bitmask constants +// Derived from enum - update if last enum value changes +constexpr int TIME_SLICE_COUNT = TIME_NIGHT3_AM_05_00 + 1; +constexpr uint64_t TIME_BIT_ONE = 1ULL; // Base value for bit shifting +constexpr uint64_t TIME_ALL_SLICES = 0x1FFFFFFFFFFF; // All 45 time bits set + +// Half-day period definitions for Clock Shuffle +struct HalfDayRange { + int startSlice; + int endSlice; +}; + +// Map half-day indices to their time slice ranges +// Index 0-5 correspond to HALF_DAY1_DAY through HALF_DAY3_NIGHT +constexpr HalfDayRange HALF_DAY_TIME_RANGES[6] = { + { 0, 6 }, // HALF_DAY1_DAY (TIME_DAY1_AM_06_00 to TIME_DAY1_PM_04_00) + { 7, 15 }, // HALF_DAY1_NIGHT (TIME_NIGHT1_PM_06_00 to TIME_NIGHT1_AM_05_00) + { 16, 22 }, // HALF_DAY2_DAY (TIME_DAY2_AM_06_00 to TIME_DAY2_PM_04_00) + { 23, 30 }, // HALF_DAY2_NIGHT (TIME_NIGHT2_PM_06_00 to TIME_NIGHT2_AM_05_00) + { 31, 36 }, // HALF_DAY3_DAY (TIME_DAY3_AM_06_00 to TIME_DAY3_PM_01_00) + { 37, 44 }, // HALF_DAY3_NIGHT (TIME_NIGHT3_PM_06_00 to TIME_NIGHT3_AM_05_00) +}; + +// Game time constants for day/night transitions +constexpr u16 GAME_TIME_DAY_START = 0x4000; // 6:00 AM +constexpr u16 GAME_TIME_NIGHT_START = 0xc000; // 6:00 PM + +// Time state for tracking time accessibility during logic solving +struct RegionTimeState { + uint64_t timeSlices; + bool canStayOverTime; +}; + +// Thread-local current region time for check evaluation +extern thread_local uint64_t gCurrentRegionTime; + +// Helper: Convert runtime game time to TimeSlice enum +TimeSlice TimeSliceFromGameTime(s32 day, u16 time); + +// Helper: Returns the initial time state for logic solving +RegionTimeState InitialTimeState(); + +// Shared initialization function for region time states +std::unordered_map InitializeRegionTimeStates(RandoRegionId startRegion); + +// Helper to ensure region time state exists +void EnsureRegionTimeState(std::unordered_map& regionTimeStates, + RandoRegionId regionId); + +// Helper to set current region time +inline void SetCurrentRegionTime(const std::unordered_map& regionTimeStates, + RandoRegionId regionId) { + gCurrentRegionTime = regionTimeStates.at(regionId).timeSlices; +} + +void FindReachableRegions(RandoRegionId currentRegion, std::set& reachableRegions, + std::unordered_map& regionTimeStates); RandoRegionId GetRegionIdFromEntrance(s32 entrance); void GeneratePools(RandoSaveInfo& saveInfo, std::vector& checkPool, std::vector& itemPool); void ApplyGlitchlessLogicToSaveContext(std::vector& checkPool, std::vector& itemPool); @@ -38,10 +143,31 @@ struct RandoRegion { std::map, std::string>> connections; std::vector>> events; std::set oneWayEntrances; + + // Time logic fields for Clock Shuffle and time-based region access + uint64_t timeSlices = 0; // Bitfield: accessible time slices (bits 0-44) - unused in current implementation + bool canStayOverTime = true; // Can player wait for time to pass? Set false for dungeons, shops with closing hours + std::unordered_map> + timeStayRestrictions; // Time slices where staying is restricted - use STAY() macro in region definitions }; extern std::map Regions; +// ============================================================================ +// TIME LOGIC NAMESPACE +// ============================================================================ +namespace TimeLogic { +// Core expansion function - sequential time expansion with stay restrictions +uint64_t ExpandTimeForward(uint64_t timeSlices, const RandoRegion& region); + +// Owned time calculation - aggregates all owned half-day time slices +uint64_t GetOwnedTimeSlices(); + +// Validation helper for clock ownership during logic generation +void ValidateRegionTimeOwnership(RandoRegionId regionId, RandoCheckId checkId, uint64_t regionTime, + const char* context); +} // namespace TimeLogic + // TODO: This may not stay here #define IS_DEKU (GET_PLAYER_FORM == PLAYER_FORM_DEKU) #define IS_ZORA (GET_PLAYER_FORM == PLAYER_FORM_ZORA) @@ -59,7 +185,9 @@ extern std::map Regions; #define CHECK_MAX_HP(TARGET_HP) ((TARGET_HP * 16) <= gSaveContext.save.saveInfo.playerData.healthCapacity) #define HAS_MAGIC (gSaveContext.save.saveInfo.playerData.isMagicAcquired) #define CAN_HOOK_SCARECROW (HAS_ITEM(ITEM_OCARINA_OF_TIME) && HAS_ITEM(ITEM_HOOKSHOT)) -#define CAN_USE_EXPLOSIVE ((HAS_ITEM(ITEM_BOMB) || HAS_ITEM(ITEM_BOMBCHU) || HAS_ITEM(ITEM_MASK_BLAST))) +#define CAN_USE_EXPLOSIVE \ + ((HAS_ITEM(ITEM_BOMB) || HAS_ITEM(ITEM_BOMBCHU) || HAS_ITEM(ITEM_MASK_BLAST) || \ + (HAS_ITEM(ITEM_POWDER_KEG) && CAN_BE_GORON))) #define CAN_USE_HUMAN_SWORD (GET_CUR_EQUIP_VALUE(EQUIP_TYPE_SWORD) >= EQUIP_VALUE_SWORD_KOKIRI) #define CAN_USE_SWORD (CAN_USE_HUMAN_SWORD || HAS_ITEM(ITEM_SWORD_GREAT_FAIRY) || CAN_BE_DEITY) // Be careful here, as some checks require you to play the song as a specific form @@ -83,6 +211,10 @@ extern std::map Regions; #define CAN_GROW_BEAN_PLANT \ (HAS_ITEM(ITEM_MAGIC_BEANS) && \ (CAN_PLAY_SONG(STORMS) || (HAS_BOTTLE && (CAN_ACCESS(SPRING_WATER) || CAN_ACCESS(HOT_SPRING_WATER))))) +// Bean patches that auto-water on Day 2 (Doggy Racetrack, Deku Palace Upper) +// Rain only occurs Day 2 from 7:00 AM to 5:30 PM (not Night 2) +// Requires magic beans and (Day 2 OR manual watering) +#define CAN_USE_DAY2_RAIN_BEAN (CAN_GROW_BEAN_PLANT || (HAS_ITEM(ITEM_MAGIC_BEANS) && CLOCK_DAY2())) #define CAN_USE_MAGIC_ARROW(arrowType) (HAS_ITEM(ITEM_BOW) && HAS_ITEM(ITEM_ARROW_##arrowType) && HAS_MAGIC) #define CAN_LIGHT_TORCH_NEAR_ANOTHER (HAS_ITEM(ITEM_DEKU_STICK) || CAN_USE_MAGIC_ARROW(FIRE)) #define KEY_COUNT(dungeon) (gSaveContext.save.shipSaveInfo.rando.foundDungeonKeys[DUNGEON_SCENE_INDEX_##dungeon]) @@ -120,6 +252,16 @@ extern std::map Regions; [] { return condition; }, LogicString(#condition) \ } \ } +// STAY macro for region time restrictions - defines when player can stay in a region over time +// Usage in region definitions: .timeStayRestrictions = { STAY(TIME_NIGHT1_PM_08_00, !HAS_ROOM_KEY) } +// If condition is false at the specified time, player is kicked out (expansion stops permanently) +// Examples: +// STAY(TIME_NIGHT1_PM_08_00, !Flags_GetRandoInf(RANDO_INF_OBTAINED_ROOM_KEY)) // Kicked out without room key +// STAY(TIME_NIGHT3_PM_10_00, false) // Always kicked out at this time (shop closes) +#define STAY(timeSlice, condition) \ + { \ + timeSlice, [] { return condition; } \ + } inline std::string LogicString(std::string condition) { if (condition == "true") @@ -187,6 +329,185 @@ inline bool MeetsMoonRequirements() { MoonMaskCount() >= RANDO_SAVE_OPTIONS[RO_ACCESS_MOON_MASKS_COUNT]; } +// ============================================================================ +// CLOCK OWNERSHIP HELPERS +// ============================================================================ + +inline uint32_t ClockCount() { + uint32_t count = 0; + for (int i = 0; i < 6; ++i) { + if (Flags_GetRandoInf(static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + i))) { + count++; + } + } + return count; +} + +inline bool SettingClocks() { + return RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE] != 0; +} + +// Centralized clock ownership check +inline bool OwnsClockHalfDay(int halfDayIndex) { + if (halfDayIndex < 0 || halfDayIndex >= 6) + return false; + RandoInf clockFlag = static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + halfDayIndex); + return Flags_GetRandoInf(clockFlag); +} + +// New consolidated helper that encapsulates ascending/descending/random logic +inline bool OwnsHalfDayForMode(int halfDayIndex) { + if (!SettingClocks() || halfDayIndex < 0 || halfDayIndex >= 6) { + return !SettingClocks(); // If not shuffling clocks, all time is available + } + + int clockMode = RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE_PROGRESSIVE]; + uint32_t totalClocks = ClockCount(); + + switch (clockMode) { + case RO_CLOCK_SHUFFLE_RANDOM: + // Random mode: check if this specific half-day is owned + return OwnsClockHalfDay(halfDayIndex); + + case RO_CLOCK_SHUFFLE_ASCENDING: + // Ascending: own first N half-days in sequence (0,1,2,3,4,5) + return totalClocks > halfDayIndex; + + case RO_CLOCK_SHUFFLE_DESCENDING: + // Descending: own last N half-days in reverse sequence (5,4,3,2,1,0) + return totalClocks > (5 - halfDayIndex); + + default: + return false; + } +} + +// ============================================================================ +// TIME OPERATOR FUNCTIONS +// ============================================================================ + +inline bool RawAt(TimeSlice slice) { + return (gCurrentRegionTime & (TIME_BIT_ONE << slice)) != 0; +} + +inline bool RawBefore(TimeSlice slice) { + if (slice == 0) + return false; + uint64_t mask = (TIME_BIT_ONE << slice) - 1; + return (gCurrentRegionTime & mask) != 0; +} + +inline bool RawAfter(TimeSlice slice) { + uint64_t mask = ~((TIME_BIT_ONE << slice) - 1) & TIME_ALL_SLICES; + return (gCurrentRegionTime & mask) != 0; +} + +inline bool RawBetween(TimeSlice start, TimeSlice end) { + uint64_t mask = ((TIME_BIT_ONE << end) - 1) & ~((TIME_BIT_ONE << start) - 1); + return (gCurrentRegionTime & mask) != 0; +} + +// Generate bitmask for a half-day period's time slices +inline constexpr uint64_t GetHalfDayTimeMask(int halfDayIndex) { + if (halfDayIndex < 0 || halfDayIndex >= 6) + return 0; + + uint64_t mask = 0; + const auto& range = HALF_DAY_TIME_RANGES[halfDayIndex]; + for (int slice = range.startSlice; slice <= range.endSlice; ++slice) { + mask |= (1ULL << slice); + } + return mask; +} + +// ============================================================================ +// CLOCK ITEM MACROS +// ============================================================================ + +// Simplified clock macros using consolidated helper +#define CLOCK_DAY1() OwnsHalfDayForMode(0) +#define CLOCK_NIGHT1() OwnsHalfDayForMode(1) +#define CLOCK_DAY2() OwnsHalfDayForMode(2) +#define CLOCK_NIGHT2() OwnsHalfDayForMode(3) +#define CLOCK_DAY3() OwnsHalfDayForMode(4) +#define CLOCK_NIGHT3() OwnsHalfDayForMode(5) + +// ============================================================================ +// CLOCK SHUFFLE VALIDATION FUNCTIONS +// ============================================================================ + +// Validation: Check if a time slice is in an owned half-day period +inline bool IsTimeSliceOwned(TimeSlice slice) { + if (!SettingClocks()) + return true; + + for (int i = 0; i < 6; ++i) { + const auto& range = HALF_DAY_TIME_RANGES[i]; + if (slice >= range.startSlice && slice <= range.endSlice) { + return OwnsClockHalfDay(i); + } + } + return false; +} + +// Validation: Check if any time in the timeslice mask is owned +inline bool HasAnyOwnedTime(uint64_t timeSlices) { + if (!SettingClocks()) + return true; + + for (int i = 0; i < 6; ++i) { + if (OwnsClockHalfDay(i)) { + uint64_t halfDayMask = GetHalfDayTimeMask(i); + if (timeSlices & halfDayMask) { + return true; + } + } + } + return false; +} + +// ============================================================================ +// COMPOSITE TIME CHECKS +// ============================================================================ + +#define IS_DAY1() (RawBefore(TIME_NIGHT1_PM_06_00) && CLOCK_DAY1()) +#define IS_NIGHT1() (RawBetween(TIME_NIGHT1_PM_06_00, TIME_DAY2_AM_06_00) && CLOCK_NIGHT1()) +#define IS_DAY2() (RawBetween(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_06_00) && CLOCK_DAY2()) +#define IS_NIGHT2() (RawBetween(TIME_NIGHT2_PM_06_00, TIME_DAY3_AM_06_00) && CLOCK_NIGHT2()) +#define IS_DAY3() (RawBetween(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_06_00) && CLOCK_DAY3()) +#define IS_NIGHT3() (RawAfter(TIME_NIGHT3_PM_06_00) && CLOCK_NIGHT3()) + +// Global clock filter for owned time periods +// Returns true if Clock Shuffle is disabled OR if player has access to any time period +inline bool ClockFilter() { + if (!SettingClocks()) + return true; + + return IS_DAY1() || IS_NIGHT1() || IS_DAY2() || IS_NIGHT2() || IS_DAY3() || IS_NIGHT3(); +} + +#define IS_DAY() (IS_DAY1() || IS_DAY2() || IS_DAY3()) +#define IS_NIGHT() (IS_NIGHT1() || IS_NIGHT2() || IS_NIGHT3()) +#define FIRST_DAY() (IS_DAY1() || IS_NIGHT1()) +#define SECOND_DAY() (IS_DAY2() || IS_NIGHT2()) +#define FINAL_DAY() (IS_DAY3() || IS_NIGHT3()) + +// ============================================================================ +// PUBLIC TIME API +// ============================================================================ + +#define AT(slice) (RawAt(slice) && ClockFilter()) + +#define BEFORE(slice) (RawBefore(slice) && ClockFilter()) + +#define AFTER(slice) (RawAfter(slice) && ClockFilter()) + +#define BETWEEN(s, e) (RawBetween(s, e) && ClockFilter()) + +#define MIDNIGHT() \ + (BETWEEN(TIME_NIGHT1_AM_12_00, TIME_DAY2_AM_06_00) || BETWEEN(TIME_NIGHT2_AM_12_00, TIME_DAY3_AM_06_00) || \ + AFTER(TIME_NIGHT3_AM_12_00)) + inline bool CanKillEnemy(ActorId EnemyId) { switch (EnemyId) { case ACTOR_BOSS_01: // Odolwa diff --git a/mm/2s2h/Rando/Logic/Regions/Central.cpp b/mm/2s2h/Rando/Logic/Regions/Central.cpp index ac83416f94..07c031ab41 100644 --- a/mm/2s2h/Rando/Logic/Regions/Central.cpp +++ b/mm/2s2h/Rando/Logic/Regions/Central.cpp @@ -90,23 +90,29 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_CLOCK_TOWN_EAST_SMALL_CRATE_01, true), CHECK(RC_CLOCK_TOWN_EAST_SMALL_CRATE_02, true), - CHECK(RC_CLOCK_TOWN_EAST_POSTMAN_HAT, Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_MAMA)), - CHECK(RC_CLOCK_TOWN_STRAY_FAIRY, CAN_BE_DEKU), + CHECK(RC_CLOCK_TOWN_EAST_POSTMAN_HAT, RANDO_EVENTS[RE_POSTMAN_FREEDOM] && BETWEEN(TIME_NIGHT3_PM_06_00, TIME_NIGHT3_AM_05_00)), + CHECK(RC_CLOCK_TOWN_STRAY_FAIRY, CAN_BE_DEKU && IS_NIGHT()), CHECK(RC_CLOCK_TOWN_EAST_UPPER_CHEST, true), + CHECK(RC_CLOCK_TOWN_BOMBERS_NOTEBOOK, RANDO_EVENTS[RE_BOMBER_CODE]), }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 7), ENTRANCE(EAST_CLOCK_TOWN, 0), true), EXIT(ENTRANCE(SOUTH_CLOCK_TOWN, 7), ENTRANCE(EAST_CLOCK_TOWN, 1), true), // To lower EXIT(ENTRANCE(ASTRAL_OBSERVATORY, 0), ENTRANCE(EAST_CLOCK_TOWN, 2), CAN_USE_PROJECTILE), EXIT(ENTRANCE(SOUTH_CLOCK_TOWN, 2), ENTRANCE(EAST_CLOCK_TOWN, 3), true), // To upper - EXIT(ENTRANCE(TREASURE_CHEST_SHOP, 0), ENTRANCE(EAST_CLOCK_TOWN, 4), true), + EXIT(ENTRANCE(TREASURE_CHEST_SHOP, 0), ENTRANCE(EAST_CLOCK_TOWN, 4), BEFORE(TIME_NIGHT1_PM_10_00) || BETWEEN(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_10_00) || BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_10_00)), EXIT(ENTRANCE(NORTH_CLOCK_TOWN, 1), ENTRANCE(EAST_CLOCK_TOWN, 5), true), - EXIT(ENTRANCE(HONEY_AND_DARLINGS_SHOP, 0), ENTRANCE(EAST_CLOCK_TOWN, 6), true), - EXIT(ENTRANCE(MAYORS_RESIDENCE, 0), ENTRANCE(EAST_CLOCK_TOWN, 7), true), - EXIT(ENTRANCE(TOWN_SHOOTING_GALLERY, 0), ENTRANCE(EAST_CLOCK_TOWN, 8), true), - EXIT(ENTRANCE(STOCK_POT_INN, 0), ENTRANCE(EAST_CLOCK_TOWN, 9), true), // To lobby + EXIT(ENTRANCE(HONEY_AND_DARLINGS_SHOP, 0), ENTRANCE(EAST_CLOCK_TOWN, 6), BEFORE(TIME_NIGHT1_PM_10_00) || BETWEEN(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_10_00) || BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_10_00)), + EXIT(ENTRANCE(MAYORS_RESIDENCE, 0), ENTRANCE(EAST_CLOCK_TOWN, 7), BETWEEN(TIME_DAY1_AM_10_00, TIME_NIGHT1_PM_08_00) || BETWEEN(TIME_DAY2_AM_10_00, TIME_NIGHT2_PM_08_00) || AFTER(TIME_DAY3_AM_10_00)), + EXIT(ENTRANCE(TOWN_SHOOTING_GALLERY, 0), ENTRANCE(EAST_CLOCK_TOWN, 8), BEFORE(TIME_NIGHT1_PM_10_00) || BETWEEN(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_10_00) || BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_10_00)), + EXIT(ENTRANCE(STOCK_POT_INN, 0), ENTRANCE(EAST_CLOCK_TOWN, 9), HAS_ITEM(ITEM_ROOM_KEY) || BETWEEN(TIME_DAY1_AM_08_00, TIME_NIGHT1_PM_08_00) || BETWEEN(TIME_DAY2_AM_08_00, TIME_NIGHT2_PM_08_00) || AFTER(TIME_DAY3_AM_08_00)), EXIT(ENTRANCE(STOCK_POT_INN, 1), ENTRANCE(EAST_CLOCK_TOWN, 10), CAN_BE_DEKU), // To upstairs - EXIT(ENTRANCE(MILK_BAR, 0), ENTRANCE(EAST_CLOCK_TOWN, 11), HAS_ITEM(ITEM_MASK_ROMANI)), + EXIT(ENTRANCE(MILK_BAR, 0), ENTRANCE(EAST_CLOCK_TOWN, 11), (BETWEEN(TIME_DAY1_AM_10_00, TIME_NIGHT1_PM_09_00) || + BETWEEN(TIME_DAY2_AM_10_00, TIME_NIGHT2_PM_09_00) || + BETWEEN(TIME_DAY3_AM_10_00, TIME_NIGHT3_PM_09_00)) || + (HAS_ITEM(ITEM_MASK_ROMANI) && (BETWEEN(TIME_NIGHT1_PM_10_00, TIME_NIGHT1_AM_05_00) || + BETWEEN(TIME_NIGHT2_PM_10_00, TIME_NIGHT2_AM_05_00) || + AFTER(TIME_NIGHT3_PM_10_00)))), }, }; Regions[RR_CLOCK_TOWN_GREAT_FAIRY_FOUNTAIN] = RandoRegion{ .name = "Clock Town", .sceneId = SCENE_YOUSEI_IZUMI, @@ -120,12 +126,12 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_CLOCK_TOWN_LAUNDRY] = RandoRegion{ .sceneId = SCENE_ALLEY, .checks = { - CHECK(RC_CLOCK_TOWN_STRAY_FAIRY, true), - CHECK(RC_CLOCK_TOWN_LAUNDRY_FREESTANDING_RUPEE_01, CAN_USE_ABILITY(SWIM) || CAN_BE_ZORA), - CHECK(RC_CLOCK_TOWN_LAUNDRY_FREESTANDING_RUPEE_02, CAN_USE_ABILITY(SWIM) || CAN_BE_ZORA), - CHECK(RC_CLOCK_TOWN_LAUNDRY_FREESTANDING_RUPEE_03, CAN_USE_ABILITY(SWIM) || CAN_BE_ZORA), + CHECK(RC_CLOCK_TOWN_STRAY_FAIRY, IS_DAY()), + CHECK(RC_CLOCK_TOWN_LAUNDRY_FREESTANDING_RUPEE_01, (CAN_USE_ABILITY(SWIM) || CAN_BE_ZORA) && IS_NIGHT2()), + CHECK(RC_CLOCK_TOWN_LAUNDRY_FREESTANDING_RUPEE_02, (CAN_USE_ABILITY(SWIM) || CAN_BE_ZORA) && IS_NIGHT2()), + CHECK(RC_CLOCK_TOWN_LAUNDRY_FREESTANDING_RUPEE_03, (CAN_USE_ABILITY(SWIM) || CAN_BE_ZORA) && IS_NIGHT2()), CHECK(RC_CLOCK_TOWN_LAUNDRY_FROG, HAS_ITEM(ITEM_MASK_DON_GERO)), - CHECK(RC_CLOCK_TOWN_LAUNDRY_GURU_GURU, true), + CHECK(RC_CLOCK_TOWN_LAUNDRY_GURU_GURU, IS_NIGHT1() || IS_NIGHT2()), CHECK(RC_CLOCK_TOWN_LAUNDRY_SMALL_CRATE, true), CHECK(RC_CLOCK_TOWN_LAUNDRY_POOL_GRASS_01, true), CHECK(RC_CLOCK_TOWN_LAUNDRY_POOL_GRASS_02, true), @@ -133,16 +139,19 @@ static RegisterShipInitFunc initFunc([]() { }, .exits = { // TO FROM EXIT(ENTRANCE(SOUTH_CLOCK_TOWN, 6), ENTRANCE(LAUNDRY_POOL, 0), true), - EXIT(ENTRANCE(CURIOSITY_SHOP, 1), ENTRANCE(LAUNDRY_POOL, 1), Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI)) + EXIT(ENTRANCE(CURIOSITY_SHOP, 1), ENTRANCE(LAUNDRY_POOL, 1), (Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI) && BETWEEN(TIME_DAY2_PM_02_00, TIME_NIGHT2_PM_10_00)) || (RANDO_EVENTS[RE_MEET_KAFEI] && BETWEEN(TIME_DAY3_PM_01_00, TIME_NIGHT3_PM_10_00))), + }, + .events = { + EVENT(RE_MEET_KAFEI, Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI) && BETWEEN(TIME_DAY2_PM_02_00, TIME_NIGHT2_PM_10_00)), }, }; Regions[RR_CLOCK_TOWN_NORTH] = RandoRegion{ .sceneId = SCENE_BACKTOWN, .checks = { - CHECK(RC_CLOCK_TOWN_NORTH_TINGLE_MAP_01, CAN_USE_PROJECTILE && CAN_AFFORD(RC_CLOCK_TOWN_NORTH_TINGLE_MAP_01)), - CHECK(RC_CLOCK_TOWN_NORTH_TINGLE_MAP_02, CAN_USE_PROJECTILE && CAN_AFFORD(RC_CLOCK_TOWN_NORTH_TINGLE_MAP_02)), + CHECK(RC_CLOCK_TOWN_NORTH_TINGLE_MAP_01, CAN_USE_PROJECTILE && CAN_AFFORD(RC_CLOCK_TOWN_NORTH_TINGLE_MAP_01) && IS_DAY()), + CHECK(RC_CLOCK_TOWN_NORTH_TINGLE_MAP_02, CAN_USE_PROJECTILE && CAN_AFFORD(RC_CLOCK_TOWN_NORTH_TINGLE_MAP_02) && IS_DAY()), CHECK(RC_CLOCK_TOWN_NORTH_TREE_PIECE_OF_HEART, true), - CHECK(RC_CLOCK_TOWN_NORTH_BOMB_LADY, CAN_USE_SWORD || CAN_BE_ZORA || CAN_BE_GORON), - CHECK(RC_CLOCK_TOWN_BOMBERS_NOTEBOOK, CAN_USE_PROJECTILE), // TODO: This will have to check for access with entrance rando + CHECK(RC_CLOCK_TOWN_NORTH_BOMB_LADY, RANDO_EVENTS[RE_SAVE_BOMB_SHOP_LADY]), + CHECK(RC_CLOCK_TOWN_BOMBERS_NOTEBOOK, RANDO_EVENTS[RE_BOMBER_CODE]), CHECK(RC_CLOCK_TOWN_POSTBOX, HAS_ITEM(ITEM_MASK_POSTMAN)), CHECK(RC_KEATON_QUIZ, HAS_ITEM(ITEM_MASK_KEATON)), }, @@ -154,11 +163,32 @@ static RegisterShipInitFunc initFunc([]() { EXIT(ENTRANCE(DEKU_SCRUB_PLAYGROUND, 0), ENTRANCE(NORTH_CLOCK_TOWN, 4), CAN_BE_DEKU), }, .events = { - EVENT(RE_ACCESS_PICTOGRAPH_TINGLE, HAS_ITEM(ITEM_PICTOGRAPH_BOX)), // Only in the day + EVENT(RE_ACCESS_PICTOGRAPH_TINGLE, HAS_ITEM(ITEM_PICTOGRAPH_BOX) && IS_DAY()), // Refer to z_en_suttari's damage table for more info. Damage effect 0xF stops him nonlethally, while 0xE kills. // FD sword beams can also kill him, but currently FD is not logically considered. - EVENT(RE_SAVE_BOMB_SHOP_LADY, CAN_USE_SWORD || CAN_BE_ZORA || CAN_BE_GORON), - EVENT(RE_KILL_SAKON, HAS_ITEM(ITEM_BOW) || HAS_ITEM(ITEM_HOOKSHOT) || CAN_BE_ZORA || CAN_USE_EXPLOSIVE), + EVENT(RE_SAVE_BOMB_SHOP_LADY, (CAN_USE_SWORD || CAN_BE_ZORA || CAN_BE_GORON) && AT(TIME_NIGHT1_AM_12_00)), + EVENT(RE_KILL_SAKON, (HAS_ITEM(ITEM_BOW) || HAS_ITEM(ITEM_HOOKSHOT) || CAN_BE_ZORA || CAN_USE_EXPLOSIVE) && AT(TIME_NIGHT1_AM_12_00)), + // Hide and seek events + EVENT(RE_HIDE_SEEK_DAY1, CAN_USE_PROJECTILE && FIRST_DAY()), + EVENT(RE_HIDE_SEEK_DAY2, CAN_USE_PROJECTILE && SECOND_DAY()), + EVENT(RE_HIDE_SEEK_DAY3, CAN_USE_PROJECTILE && FINAL_DAY()), + // North bomber events + EVENT(RE_BOMBERS_NORTH_DAY1, RANDO_EVENTS[RE_HIDE_SEEK_DAY1]), + EVENT(RE_BOMBERS_NORTH_DAY2, RANDO_EVENTS[RE_HIDE_SEEK_DAY2]), + EVENT(RE_BOMBERS_NORTH_DAY3, RANDO_EVENTS[RE_HIDE_SEEK_DAY3]), + // East bomber events + EVENT(RE_BOMBERS_EAST_DAY1, RANDO_EVENTS[RE_HIDE_SEEK_DAY1]), + EVENT(RE_BOMBERS_EAST_DAY2, RANDO_EVENTS[RE_HIDE_SEEK_DAY2]), + EVENT(RE_BOMBERS_EAST_DAY3, RANDO_EVENTS[RE_HIDE_SEEK_DAY3]), + // West bomber events + EVENT(RE_BOMBERS_WEST_DAY1, RANDO_EVENTS[RE_HIDE_SEEK_DAY1]), + EVENT(RE_BOMBERS_WEST_DAY2, RANDO_EVENTS[RE_HIDE_SEEK_DAY2]), + EVENT(RE_BOMBERS_WEST_DAY3, RANDO_EVENTS[RE_HIDE_SEEK_DAY3]), + // Bomber code event + EVENT(RE_BOMBER_CODE, + (RANDO_EVENTS[RE_BOMBERS_NORTH_DAY1] && RANDO_EVENTS[RE_BOMBERS_WEST_DAY1] && RANDO_EVENTS[RE_BOMBERS_EAST_DAY1]) || + (RANDO_EVENTS[RE_BOMBERS_NORTH_DAY2] && RANDO_EVENTS[RE_BOMBERS_WEST_DAY2] && RANDO_EVENTS[RE_BOMBERS_EAST_DAY2]) || + (RANDO_EVENTS[RE_BOMBERS_NORTH_DAY3] && RANDO_EVENTS[RE_BOMBERS_WEST_DAY3] && RANDO_EVENTS[RE_BOMBERS_EAST_DAY3])), }, }; Regions[RR_CLOCK_TOWN_SOUTH] = RandoRegion{ .sceneId = SCENE_CLOCKTOWER, @@ -166,7 +196,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_CLOCK_TOWN_POSTBOX, HAS_ITEM(ITEM_MASK_POSTMAN)), CHECK(RC_CLOCK_TOWN_SOUTH_PLATFORM_PIECE_OF_HEART, true), CHECK(RC_CLOCK_TOWN_SCRUB_DEED, Flags_GetRandoInf(RANDO_INF_OBTAINED_MOONS_TEAR)), - CHECK(RC_CLOCK_TOWN_SOUTH_CHEST_UPPER, (CAN_BE_DEKU && Flags_GetRandoInf(RANDO_INF_OBTAINED_MOONS_TEAR)) || HAS_ITEM(ITEM_HOOKSHOT)), + CHECK(RC_CLOCK_TOWN_SOUTH_CHEST_UPPER, ((CAN_BE_DEKU && Flags_GetRandoInf(RANDO_INF_OBTAINED_MOONS_TEAR)) || HAS_ITEM(ITEM_HOOKSHOT)) && FINAL_DAY()), CHECK(RC_CLOCK_TOWN_SOUTH_CHEST_LOWER, (CAN_BE_DEKU && Flags_GetRandoInf(RANDO_INF_OBTAINED_MOONS_TEAR)) || HAS_ITEM(ITEM_HOOKSHOT)), CHECK(RC_CLOCK_TOWN_SOUTH_OWL_STATUE, CAN_USE_SWORD), }, @@ -179,7 +209,7 @@ static RegisterShipInitFunc initFunc([]() { EXIT(ENTRANCE(WEST_CLOCK_TOWN, 1), ENTRANCE(SOUTH_CLOCK_TOWN, 5), true), // To lower EXIT(ENTRANCE(LAUNDRY_POOL, 0), ENTRANCE(SOUTH_CLOCK_TOWN, 6), true), EXIT(ENTRANCE(EAST_CLOCK_TOWN, 1), ENTRANCE(SOUTH_CLOCK_TOWN, 7), true), // To lower - EXIT(ENTRANCE(CLOCK_TOWER_ROOFTOP, 0), ONE_WAY_EXIT, true), + EXIT(ENTRANCE(CLOCK_TOWER_ROOFTOP, 0), ONE_WAY_EXIT, AFTER(TIME_NIGHT3_AM_12_00)), // Clock Tower Platform accessible only after midnight Night 3 }, .connections = { CONNECTION(RR_MAX, true), @@ -194,61 +224,78 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_CLOCK_TOWN_WEST_BANK_ADULTS_WALLET, true), CHECK(RC_CLOCK_TOWN_WEST_BANK_PIECE_OF_HEART, CUR_UPG_VALUE(UPG_WALLET) >= 1), CHECK(RC_CLOCK_TOWN_WEST_BANK_INTEREST, CUR_UPG_VALUE(UPG_WALLET) >= 1), - CHECK(RC_CLOCK_TOWN_WEST_SISTERS_PIECE_OF_HEART, HAS_ITEM(ITEM_MASK_KAMARO)), + CHECK(RC_CLOCK_TOWN_WEST_SISTERS_PIECE_OF_HEART, HAS_ITEM(ITEM_MASK_KAMARO) && (IS_NIGHT1() || IS_NIGHT2())), }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(WEST_CLOCK_TOWN, 0), true), EXIT(ENTRANCE(SOUTH_CLOCK_TOWN, 5), ENTRANCE(WEST_CLOCK_TOWN, 1), true), // To lower EXIT(ENTRANCE(SOUTH_CLOCK_TOWN, 3), ENTRANCE(WEST_CLOCK_TOWN, 2), true), // To upper - EXIT(ENTRANCE(SWORDMANS_SCHOOL, 0), ENTRANCE(WEST_CLOCK_TOWN, 3), true), - EXIT(ENTRANCE(CURIOSITY_SHOP, 0), ENTRANCE(WEST_CLOCK_TOWN, 4), true), + EXIT(ENTRANCE(SWORDMANS_SCHOOL, 0), ENTRANCE(WEST_CLOCK_TOWN, 3), FIRST_DAY() || SECOND_DAY() || BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_11_00) || AFTER(TIME_NIGHT3_AM_12_00)), + EXIT(ENTRANCE(CURIOSITY_SHOP, 0), ENTRANCE(WEST_CLOCK_TOWN, 4), BETWEEN(TIME_NIGHT1_PM_10_00, TIME_DAY2_AM_06_00) || BETWEEN(TIME_NIGHT2_PM_10_00, TIME_DAY3_AM_06_00) || AFTER(TIME_NIGHT3_PM_10_00)), EXIT(ENTRANCE(TRADING_POST, 0), ENTRANCE(WEST_CLOCK_TOWN, 5), true), EXIT(ENTRANCE(BOMB_SHOP, 0), ENTRANCE(WEST_CLOCK_TOWN, 6), true), - EXIT(ENTRANCE(POST_OFFICE, 0), ENTRANCE(WEST_CLOCK_TOWN, 7), true), - EXIT(ENTRANCE(LOTTERY_SHOP, 0), ENTRANCE(WEST_CLOCK_TOWN, 8), true), + EXIT(ENTRANCE(POST_OFFICE, 0), ENTRANCE(WEST_CLOCK_TOWN, 7), BETWEEN(TIME_DAY1_PM_03_00, TIME_NIGHT1_AM_12_00) || (Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI) && BETWEEN(TIME_NIGHT2_PM_06_00, TIME_NIGHT2_AM_12_00)) || IS_NIGHT3()), + EXIT(ENTRANCE(LOTTERY_SHOP, 0), ENTRANCE(WEST_CLOCK_TOWN, 8), IS_DAY() || (BEFORE(TIME_NIGHT1_PM_11_00) || BETWEEN(TIME_NIGHT2_PM_06_00, TIME_NIGHT2_PM_11_00) || BETWEEN(TIME_NIGHT3_PM_06_00, TIME_NIGHT3_PM_11_00))), }, }; Regions[RR_CURIOSITY_SHOP_BACK] = RandoRegion{ .name = "Back", .sceneId = SCENE_AYASHIISHOP, .checks = { - CHECK(RC_KAFEIS_HIDEOUT_KEATON_MASK, true), - CHECK(RC_KAFEIS_HIDEOUT_LETTER_TO_MAMA, true), - CHECK(RC_KAFEIS_HIDEOUT_PENDANT_OF_MEMORIES, Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI)), + CHECK(RC_KAFEIS_HIDEOUT_KEATON_MASK, BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_10_00)), + CHECK(RC_KAFEIS_HIDEOUT_LETTER_TO_MAMA, BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_10_00)), + CHECK(RC_KAFEIS_HIDEOUT_PENDANT_OF_MEMORIES, Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI) && BETWEEN(TIME_DAY2_PM_02_00, TIME_NIGHT2_PM_10_00)), }, .exits = { // TO FROM EXIT(ENTRANCE(LAUNDRY_POOL, 1), ENTRANCE(CURIOSITY_SHOP, 1), true) }, + .timeStayRestrictions = { + STAY(TIME_NIGHT2_PM_10_00, false), + STAY(TIME_NIGHT3_PM_10_00, false), + STAY(TIME_DAY2_AM_06_00, false), // Kick at start of day + STAY(TIME_DAY3_AM_06_00, false), + }, }; Regions[RR_CURIOSITY_SHOP_FRONT] = RandoRegion{ .name = "Front", .sceneId = SCENE_AYASHIISHOP, .checks = { - CHECK(RC_BOMB_SHOP_ITEM_04_OR_CURIOSITY_SHOP_ITEM, CAN_AFFORD(RC_BOMB_SHOP_ITEM_04_OR_CURIOSITY_SHOP_ITEM)), - CHECK(RC_CURIOSITY_SHOP_SPECIAL_ITEM, CAN_AFFORD(RC_CURIOSITY_SHOP_SPECIAL_ITEM) && (RANDO_EVENTS[RE_SAVE_BOMB_SHOP_LADY] || RANDO_EVENTS[RE_KILL_SAKON])), + CHECK(RC_BOMB_SHOP_ITEM_04_OR_CURIOSITY_SHOP_ITEM, CAN_AFFORD(RC_BOMB_SHOP_ITEM_04_OR_CURIOSITY_SHOP_ITEM) && IS_NIGHT3()), + CHECK(RC_CURIOSITY_SHOP_SPECIAL_ITEM, CAN_AFFORD(RC_CURIOSITY_SHOP_SPECIAL_ITEM) && (RANDO_EVENTS[RE_SAVE_BOMB_SHOP_LADY] || RANDO_EVENTS[RE_KILL_SAKON]) && IS_NIGHT3()), }, .exits = { // TO FROM - EXIT(ENTRANCE(WEST_CLOCK_TOWN, 4), ENTRANCE(CURIOSITY_SHOP, 0), true) + EXIT(ENTRANCE(WEST_CLOCK_TOWN, 4), ENTRANCE(CURIOSITY_SHOP, 0), true), }, }; Regions[RR_HONEY_AND_DARLING] = RandoRegion{ .sceneId = SCENE_BOWLING, .checks = { - CHECK(RC_CLOCK_TOWN_EAST_HONEY_DARLING_ALL_DAYS, HAS_ITEM(ITEM_BOW) && HAS_ITEM(ITEM_BOMBCHU) && HAS_ITEM(ITEM_BOMB)), - CHECK(RC_CLOCK_TOWN_EAST_HONEY_DARLING_ANY_DAY, HAS_ITEM(ITEM_BOW) || HAS_ITEM(ITEM_BOMBCHU) || HAS_ITEM(ITEM_BOMB)), + CHECK(RC_CLOCK_TOWN_EAST_HONEY_DARLING_ALL_DAYS, RANDO_EVENTS[RE_HONEY_DARLING_REWARD_DAY1] && RANDO_EVENTS[RE_HONEY_DARLING_REWARD_DAY2] && RANDO_EVENTS[RE_HONEY_DARLING_REWARD_DAY3]), + CHECK(RC_CLOCK_TOWN_EAST_HONEY_DARLING_ANY_DAY, (RANDO_EVENTS[RE_HONEY_DARLING_REWARD_DAY1] || RANDO_EVENTS[RE_HONEY_DARLING_REWARD_DAY2] || RANDO_EVENTS[RE_HONEY_DARLING_REWARD_DAY3])), }, .exits = { // TO FROM EXIT(ENTRANCE(EAST_CLOCK_TOWN, 6), ENTRANCE(HONEY_AND_DARLINGS_SHOP, 0), true), }, + .events = { + EVENT(RE_HONEY_DARLING_REWARD_DAY1, (HAS_ITEM(ITEM_BOMB) || HAS_ITEM(ITEM_BOMBCHU)) && BEFORE(TIME_NIGHT1_PM_10_00)), + EVENT(RE_HONEY_DARLING_REWARD_DAY2, HAS_ITEM(ITEM_BOMB) && BETWEEN(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_10_00)), + EVENT(RE_HONEY_DARLING_REWARD_DAY3, HAS_ITEM(ITEM_BOW) && IS_DAY3()), + }, }; Regions[RR_INN] = RandoRegion{ .sceneId = SCENE_YADOYA, .checks = { - CHECK(RC_STOCK_POT_INN_COUPLES_MASK, HAS_ITEM(ITEM_MASK_KAFEIS_MASK) && Flags_GetRandoInf(RANDO_INF_OBTAINED_PENDANT_OF_MEMORIES) && RANDO_EVENTS[RE_RETRIEVE_SUN_MASK]), - CHECK(RC_STOCK_POT_INN_GRANDMA_LONG_STORY, HAS_ITEM(ITEM_MASK_ALL_NIGHT)), - CHECK(RC_STOCK_POT_INN_GRANDMA_SHORT_STORY, HAS_ITEM(ITEM_MASK_ALL_NIGHT)), - CHECK(RC_STOCK_POT_INN_GUEST_ROOM_CHEST,Flags_GetRandoInf(RANDO_INF_OBTAINED_ROOM_KEY)), - CHECK(RC_STOCK_POT_INN_LETTER_TO_KAFEI, HAS_ITEM(ITEM_MASK_KAFEIS_MASK)), - CHECK(RC_STOCK_POT_INN_ROOM_KEY, true), - CHECK(RC_STOCK_POT_INN_STAFF_ROOM_CHEST, true), + CHECK(RC_STOCK_POT_INN_COUPLES_MASK, HAS_ITEM(ITEM_MASK_KAFEIS_MASK) && Flags_GetRandoInf(RANDO_INF_OBTAINED_PENDANT_OF_MEMORIES) && RANDO_EVENTS[RE_RETRIEVE_SUN_MASK] && AFTER(TIME_NIGHT3_AM_04_00)), + CHECK(RC_STOCK_POT_INN_GRANDMA_LONG_STORY, HAS_ITEM(ITEM_MASK_ALL_NIGHT) && + ((BEFORE(TIME_DAY1_PM_04_00) && CLOCK_NIGHT1()) || BETWEEN(TIME_DAY2_AM_06_00, TIME_DAY2_PM_04_00) || + (IS_DAY1() && (CLOCK_NIGHT1() || CLOCK_DAY2() || CLOCK_NIGHT2() || CLOCK_DAY3() || CLOCK_NIGHT3())) || + (IS_DAY2() && (CLOCK_NIGHT2() || CLOCK_DAY3() || CLOCK_NIGHT3())))), + CHECK(RC_STOCK_POT_INN_GRANDMA_SHORT_STORY, HAS_ITEM(ITEM_MASK_ALL_NIGHT) && + ((IS_DAY1() && (CLOCK_DAY2() || CLOCK_NIGHT2() || CLOCK_DAY3() || CLOCK_NIGHT3())) || + (IS_DAY2() && (CLOCK_DAY3() || CLOCK_NIGHT3())))), + CHECK(RC_STOCK_POT_INN_GUEST_ROOM_CHEST, Flags_GetRandoInf(RANDO_INF_OBTAINED_ROOM_KEY)), + CHECK(RC_STOCK_POT_INN_LETTER_TO_KAFEI, HAS_ITEM(ITEM_MASK_KAFEIS_MASK) && RANDO_EVENTS[RE_ANJU_MIDNIGHT_MEETING]), + CHECK(RC_STOCK_POT_INN_ROOM_KEY, BETWEEN(TIME_DAY1_PM_01_45, TIME_DAY1_PM_04_00)), + CHECK(RC_STOCK_POT_INN_STAFF_ROOM_CHEST, IS_NIGHT3()), CHECK(RC_STOCK_POT_INN_TOILET_HAND, - Flags_GetRandoInf(RANDO_INF_OBTAINED_DEED_LAND) || Flags_GetRandoInf(RANDO_INF_OBTAINED_DEED_SWAMP) || + (Flags_GetRandoInf(RANDO_INF_OBTAINED_DEED_LAND) || Flags_GetRandoInf(RANDO_INF_OBTAINED_DEED_SWAMP) || Flags_GetRandoInf(RANDO_INF_OBTAINED_DEED_MOUNTAIN) || Flags_GetRandoInf(RANDO_INF_OBTAINED_DEED_OCEAN) || - Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_MAMA) || Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI) + Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_MAMA) || Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI)) && + MIDNIGHT() ), }, .exits = { // TO FROM @@ -258,11 +305,23 @@ static RegisterShipInitFunc initFunc([]() { .events = { EVENT(RE_ACCESS_FISH, true), EVENT(RE_ACCESS_BUGS, true), + EVENT(RE_SETUP_MEET_ANJU, HAS_ITEM(ITEM_MASK_KAFEIS_MASK) && BETWEEN(TIME_DAY1_PM_01_45, TIME_NIGHT1_PM_09_00)), + EVENT(RE_ANJU_MIDNIGHT_MEETING, RANDO_EVENTS[RE_SETUP_MEET_ANJU] && BETWEEN(TIME_NIGHT1_AM_12_00, TIME_DAY2_AM_06_00) && (Flags_GetRandoInf(RANDO_INF_OBTAINED_ROOM_KEY) || CAN_BE_DEKU)), + EVENT(RE_DELIVER_PENDANT, Flags_GetRandoInf(RANDO_INF_OBTAINED_PENDANT_OF_MEMORIES) && (BETWEEN(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_09_00) || BETWEEN(TIME_DAY3_AM_06_00, TIME_DAY3_AM_11_30))), + }, + .timeStayRestrictions = { + STAY(TIME_NIGHT1_PM_08_00, Flags_GetRandoInf(RANDO_INF_OBTAINED_ROOM_KEY)), + STAY(TIME_DAY2_AM_06_00, Flags_GetRandoInf(RANDO_INF_OBTAINED_ROOM_KEY)), + STAY(TIME_NIGHT2_PM_08_00, Flags_GetRandoInf(RANDO_INF_OBTAINED_ROOM_KEY)), + STAY(TIME_DAY3_AM_06_00, Flags_GetRandoInf(RANDO_INF_OBTAINED_ROOM_KEY)), }, }; Regions[RR_LOTTERY_SHOP] = RandoRegion{ .sceneId = SCENE_TAKARAKUJI, .checks = { - CHECK(RC_CLOCK_TOWN_WEST_LOTTERY, true), + CHECK(RC_CLOCK_TOWN_WEST_LOTTERY, + (IS_DAY1() && CLOCK_NIGHT1()) || + (IS_DAY2() && CLOCK_NIGHT2()) || + (IS_DAY3() && CLOCK_NIGHT3())), }, .exits = { // TO FROM EXIT(ENTRANCE(WEST_CLOCK_TOWN, 8), ENTRANCE(LOTTERY_SHOP, 0), true), @@ -270,8 +329,8 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_MAYOR_RESIDENCE] = RandoRegion{ .sceneId = SCENE_SONCHONOIE, .checks = { - CHECK(RC_MAYORS_OFFICE_PIECE_OF_HEART, HAS_ITEM(ITEM_MASK_COUPLE)), - CHECK(RC_MAYORS_OFFICE_KAFEIS_MASK, true) + CHECK(RC_MAYORS_OFFICE_PIECE_OF_HEART, HAS_ITEM(ITEM_MASK_COUPLE) && (BETWEEN(TIME_DAY1_AM_10_00, TIME_NIGHT1_PM_08_00) || BETWEEN(TIME_DAY2_AM_10_00, TIME_NIGHT2_PM_08_00) || BETWEEN(TIME_DAY3_AM_10_00, TIME_NIGHT3_PM_06_00))), + CHECK(RC_MAYORS_OFFICE_KAFEIS_MASK, BETWEEN(TIME_DAY1_AM_10_00, TIME_NIGHT1_PM_08_00) || BETWEEN(TIME_DAY2_AM_10_00, TIME_NIGHT2_PM_08_00)) }, .exits = { // TO FROM EXIT(ENTRANCE(EAST_CLOCK_TOWN, 7), ENTRANCE(MAYORS_RESIDENCE, 0), true), @@ -279,63 +338,82 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_MILK_BAR] = RandoRegion{ .sceneId = SCENE_MILK_BAR, .checks = { - CHECK(RC_MILK_BAR_CIRCUS_LEADER_MASK, CAN_BE_DEKU && CAN_BE_GORON && CAN_BE_ZORA && HAS_ITEM(ITEM_OCARINA_OF_TIME)), - CHECK(RC_MILK_BAR_MADAME_AROMA, HAS_ITEM(ITEM_MASK_KAFEIS_MASK) && Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_MAMA)), - CHECK(RC_MILK_BAR_PURCHASE_CHATEAU, CAN_AFFORD(RC_MILK_BAR_PURCHASE_CHATEAU) && HAS_ITEM(ITEM_MASK_ROMANI)), - CHECK(RC_MILK_BAR_PURCHASE_MILK, CAN_AFFORD(RC_MILK_BAR_PURCHASE_MILK) && HAS_ITEM(ITEM_MASK_ROMANI)), + CHECK(RC_MILK_BAR_CIRCUS_LEADER_MASK, CAN_BE_DEKU && CAN_BE_GORON && CAN_BE_ZORA && HAS_ITEM(ITEM_OCARINA_OF_TIME) && (BETWEEN(TIME_NIGHT1_PM_10_00, TIME_NIGHT1_AM_05_00) || BETWEEN(TIME_NIGHT2_PM_10_00, TIME_NIGHT2_AM_05_00))), + CHECK(RC_MILK_BAR_MADAME_AROMA, HAS_ITEM(ITEM_MASK_KAFEIS_MASK) && Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_MAMA) && (BETWEEN(TIME_NIGHT3_PM_06_00, TIME_NIGHT3_PM_09_00) || AFTER(TIME_NIGHT3_PM_10_00))), + CHECK(RC_MILK_BAR_PURCHASE_CHATEAU, CAN_AFFORD(RC_MILK_BAR_PURCHASE_CHATEAU) && HAS_ITEM(ITEM_MASK_ROMANI) && (BETWEEN(TIME_NIGHT1_PM_10_00, TIME_DAY2_AM_06_00) || BETWEEN(TIME_NIGHT2_PM_10_00, TIME_DAY3_AM_06_00) || BETWEEN(TIME_NIGHT3_PM_06_00, TIME_NIGHT3_PM_09_00) || AFTER(TIME_NIGHT3_PM_10_00))), + CHECK(RC_MILK_BAR_PURCHASE_MILK, CAN_AFFORD(RC_MILK_BAR_PURCHASE_MILK) && HAS_ITEM(ITEM_MASK_ROMANI) && (BETWEEN(TIME_NIGHT1_PM_10_00, TIME_DAY2_AM_06_00) || BETWEEN(TIME_NIGHT2_PM_10_00, TIME_DAY3_AM_06_00) || BETWEEN(TIME_NIGHT3_PM_06_00, TIME_NIGHT3_PM_09_00) || AFTER(TIME_NIGHT3_PM_10_00))), }, .exits = { // TO FROM EXIT(ENTRANCE(EAST_CLOCK_TOWN, 11), ENTRANCE(MILK_BAR, 0), true), }, + .timeStayRestrictions = { + STAY(TIME_NIGHT1_PM_10_00, false), + STAY(TIME_NIGHT1_AM_05_00, false), + STAY(TIME_NIGHT2_PM_10_00, false), + STAY(TIME_NIGHT2_AM_05_00, false), + STAY(TIME_NIGHT3_PM_10_00, false), + STAY(TIME_NIGHT3_AM_05_00, false), + }, }; Regions[RR_POST_OFFICE] = RandoRegion{ .sceneId = SCENE_POSTHOUSE, .checks = { // TODO: Trick for doing without the Bunny Hood - CHECK(RC_CLOCK_TOWN_WEST_POSTMAN_MINIGAME, HAS_ITEM(ITEM_MASK_BUNNY)), + CHECK(RC_CLOCK_TOWN_WEST_POSTMAN_MINIGAME, HAS_ITEM(ITEM_MASK_BUNNY) && (BETWEEN(TIME_DAY1_PM_03_00, TIME_NIGHT1_AM_12_00) || (Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI) && BETWEEN(TIME_NIGHT2_PM_06_00, TIME_NIGHT2_AM_12_00)))), }, .exits = { // TO FROM EXIT(ENTRANCE(WEST_CLOCK_TOWN, 7), ENTRANCE(POST_OFFICE, 0), true), }, + .events = { + EVENT(RE_POSTMAN_FREEDOM, Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_MAMA) && IS_NIGHT3()), + }, }; Regions[RR_SWORDSMAN_SCHOOL] = RandoRegion{ .sceneId = SCENE_DOUJOU, .checks = { - CHECK(RC_SWORDSMAN_SCHOOL_PIECE_OF_HEART, CAN_USE_HUMAN_SWORD), - CHECK(RC_SWORDSMAN_SCHOOL_POT_01, CAN_USE_HUMAN_SWORD), - CHECK(RC_SWORDSMAN_SCHOOL_POT_02, CAN_USE_HUMAN_SWORD), - CHECK(RC_SWORDSMAN_SCHOOL_POT_03, CAN_USE_HUMAN_SWORD), - CHECK(RC_SWORDSMAN_SCHOOL_POT_04, CAN_USE_HUMAN_SWORD), - CHECK(RC_SWORDSMAN_SCHOOL_POT_05, CAN_USE_HUMAN_SWORD), + CHECK(RC_SWORDSMAN_SCHOOL_PIECE_OF_HEART, CAN_USE_HUMAN_SWORD && BEFORE(TIME_NIGHT3_PM_11_00)), + CHECK(RC_SWORDSMAN_SCHOOL_POT_01, CAN_USE_HUMAN_SWORD && AFTER(TIME_NIGHT3_AM_12_00)), + CHECK(RC_SWORDSMAN_SCHOOL_POT_02, CAN_USE_HUMAN_SWORD && AFTER(TIME_NIGHT3_AM_12_00)), + CHECK(RC_SWORDSMAN_SCHOOL_POT_03, CAN_USE_HUMAN_SWORD && AFTER(TIME_NIGHT3_AM_12_00)), + CHECK(RC_SWORDSMAN_SCHOOL_POT_04, CAN_USE_HUMAN_SWORD && AFTER(TIME_NIGHT3_AM_12_00)), + CHECK(RC_SWORDSMAN_SCHOOL_POT_05, CAN_USE_HUMAN_SWORD && AFTER(TIME_NIGHT3_AM_12_00)), }, .exits = { // TO FROM EXIT(ENTRANCE(WEST_CLOCK_TOWN, 3), ENTRANCE(SWORDMANS_SCHOOL, 0), true), }, + .timeStayRestrictions = { + STAY(TIME_NIGHT3_PM_11_00, false), + }, }; Regions[RR_TOWN_DEKU_PLAYGROUND] = RandoRegion{ .sceneId = SCENE_DEKUTES, .checks = { - CHECK(RC_DEKU_PLAYGROUND_ALL_DAYS, CAN_BE_DEKU), + CHECK(RC_DEKU_PLAYGROUND_ALL_DAYS, CAN_BE_DEKU && RANDO_EVENTS[RE_DEKU_PLAYGROUND_1] && RANDO_EVENTS[RE_DEKU_PLAYGROUND_2] && RANDO_EVENTS[RE_DEKU_PLAYGROUND_3]), CHECK(RC_DEKU_PLAYGROUND_ANY_DAY, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_01, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_02, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_03, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_04, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_05, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_06, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_01, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_02, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_03, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_04, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_05, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_06, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_01, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_02, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_03, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_04, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_05, CAN_BE_DEKU), - CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_06, CAN_BE_DEKU), + CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_01, RANDO_EVENTS[RE_DEKU_PLAYGROUND_1]), + CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_02, RANDO_EVENTS[RE_DEKU_PLAYGROUND_1]), + CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_03, RANDO_EVENTS[RE_DEKU_PLAYGROUND_1]), + CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_04, RANDO_EVENTS[RE_DEKU_PLAYGROUND_1]), + CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_05, RANDO_EVENTS[RE_DEKU_PLAYGROUND_1]), + CHECK(RC_DEKU_PLAYGROUND_DAY_1_RUPEE_06, RANDO_EVENTS[RE_DEKU_PLAYGROUND_1]), + CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_01, RANDO_EVENTS[RE_DEKU_PLAYGROUND_2]), + CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_02, RANDO_EVENTS[RE_DEKU_PLAYGROUND_2]), + CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_03, RANDO_EVENTS[RE_DEKU_PLAYGROUND_2]), + CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_04, RANDO_EVENTS[RE_DEKU_PLAYGROUND_2]), + CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_05, RANDO_EVENTS[RE_DEKU_PLAYGROUND_2]), + CHECK(RC_DEKU_PLAYGROUND_DAY_2_RUPEE_06, RANDO_EVENTS[RE_DEKU_PLAYGROUND_2]), + CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_01, RANDO_EVENTS[RE_DEKU_PLAYGROUND_3]), + CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_02, RANDO_EVENTS[RE_DEKU_PLAYGROUND_3]), + CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_03, RANDO_EVENTS[RE_DEKU_PLAYGROUND_3]), + CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_04, RANDO_EVENTS[RE_DEKU_PLAYGROUND_3]), + CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_05, RANDO_EVENTS[RE_DEKU_PLAYGROUND_3]), + CHECK(RC_DEKU_PLAYGROUND_DAY_3_RUPEE_06, RANDO_EVENTS[RE_DEKU_PLAYGROUND_3]), }, .exits = { // TO FROM EXIT(ENTRANCE(NORTH_CLOCK_TOWN, 4), ENTRANCE(DEKU_SCRUB_PLAYGROUND, 0), true), }, + .events = { + EVENT(RE_DEKU_PLAYGROUND_1, CAN_BE_DEKU && FIRST_DAY()), + EVENT(RE_DEKU_PLAYGROUND_2, CAN_BE_DEKU && SECOND_DAY()), + EVENT(RE_DEKU_PLAYGROUND_3, CAN_BE_DEKU && FINAL_DAY()), + }, }; Regions[RR_TOWN_SHOOTING_GALLERY] = RandoRegion{ .sceneId = SCENE_SYATEKI_MIZU, .checks = { @@ -345,6 +423,11 @@ static RegisterShipInitFunc initFunc([]() { .exits = { // TO FROM EXIT(ENTRANCE(EAST_CLOCK_TOWN, 8), ENTRANCE(TOWN_SHOOTING_GALLERY, 0), true), }, + .timeStayRestrictions = { + STAY(TIME_NIGHT1_PM_10_00, false), + STAY(TIME_NIGHT2_PM_10_00, false), + STAY(TIME_NIGHT3_PM_10_00, false), + }, }; Regions[RR_TRADING_POST] = RandoRegion{ .sceneId = SCENE_8ITEMSHOP, .checks = { @@ -362,6 +445,14 @@ static RegisterShipInitFunc initFunc([]() { EXIT(ENTRANCE(WEST_CLOCK_TOWN, 5), ENTRANCE(TRADING_POST, 0), true), }, + .timeStayRestrictions = { + // Logic break at night + STAY(TIME_NIGHT1_PM_09_00, false), + STAY(TIME_NIGHT1_PM_10_00, false), + STAY(TIME_NIGHT2_PM_09_00, false), + STAY(TIME_NIGHT2_PM_10_00, false), + STAY(TIME_NIGHT3_PM_09_00, false), + }, }; Regions[RR_TREASURE_SHOP] = RandoRegion{ .sceneId = SCENE_TAKARAYA, .checks = { diff --git a/mm/2s2h/Rando/Logic/Regions/East.cpp b/mm/2s2h/Rando/Logic/Regions/East.cpp index e96dede927..7227f7e5a2 100644 --- a/mm/2s2h/Rando/Logic/Regions/East.cpp +++ b/mm/2s2h/Rando/Logic/Regions/East.cpp @@ -153,7 +153,7 @@ static RegisterShipInitFunc initFunc([]() { Must NOT have helped the Bomb Shop old lady on this cycle(Kafei does not show up if you do, as Sakon never visits the shop to be followed.) Must have delivered the Letter to Kafei and met Kafei.(Sakon just does not show up otherwise, as odd as that may sound.) */ - EXIT(ENTRANCE(SAKONS_HIDEOUT, 0), ENTRANCE(IKANA_CANYON, 6), Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI)), + EXIT(ENTRANCE(SAKONS_HIDEOUT, 0), ENTRANCE(IKANA_CANYON, 6), RANDO_EVENTS[RE_MEET_KAFEI] && AT(TIME_NIGHT3_PM_06_00)), EXIT(ENTRANCE(SECRET_SHRINE, 0), ENTRANCE(IKANA_CANYON, 12), CAN_USE_ABILITY(SWIM)), EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 9), ONE_WAY_EXIT, CAN_USE_ABILITY(SWIM)), }, @@ -170,7 +170,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_IKANA_CANYON_OWL_STATUE, CAN_USE_SWORD), CHECK(RC_IKANA_CANYON_TINGLE_MAP_01, CAN_USE_PROJECTILE && CAN_AFFORD(RC_IKANA_CANYON_TINGLE_MAP_01)), CHECK(RC_IKANA_CANYON_TINGLE_MAP_02, CAN_USE_PROJECTILE && CAN_AFFORD(RC_IKANA_CANYON_TINGLE_MAP_02)), - CHECK(RC_ENEMY_DROP_GUAY, CanKillEnemy(ACTOR_EN_CROW)), // Day only + CHECK(RC_ENEMY_DROP_GUAY, CanKillEnemy(ACTOR_EN_CROW) && IS_DAY()), // Day only }, .exits = { // TO FROM EXIT(ENTRANCE(GHOST_HUT, 0), ENTRANCE(IKANA_CANYON, 1), true), @@ -227,14 +227,14 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_IKANA_GRAVEYARD_GRASS_07, true), CHECK(RC_IKANA_GRAVEYARD_GRASS_08, true), CHECK(RC_IKANA_GRAVEYARD_GRASS_09, true), - CHECK(RC_ENEMY_DROP_STALCHILD, CanKillEnemy(ACTOR_EN_SKB)), // Night only - CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT)), // Day only + CHECK(RC_ENEMY_DROP_STALCHILD, CanKillEnemy(ACTOR_EN_SKB) && IS_NIGHT()), // Night only + CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT) && IS_DAY()), // Day only }, .exits = { // TO FROM EXIT(ENTRANCE(ROAD_TO_IKANA, 2), ENTRANCE(IKANA_GRAVEYARD, 0), true), - EXIT(ENTRANCE(DAMPES_HOUSE, 0), ONE_WAY_EXIT, HAS_ITEM(ITEM_MASK_CAPTAIN)), // Day 3 hole - EXIT(ENTRANCE(BENEATH_THE_GRAVERYARD, 0), ENTRANCE(IKANA_GRAVEYARD, 2), HAS_ITEM(ITEM_MASK_CAPTAIN)), // Day 2 hole - EXIT(ENTRANCE(BENEATH_THE_GRAVERYARD, 1), ENTRANCE(IKANA_GRAVEYARD, 3), HAS_ITEM(ITEM_MASK_CAPTAIN)), // Day 1 hole + EXIT(ENTRANCE(DAMPES_HOUSE, 0), ONE_WAY_EXIT, HAS_ITEM(ITEM_MASK_CAPTAIN) && IS_NIGHT3()), // Day 3 hole + EXIT(ENTRANCE(BENEATH_THE_GRAVERYARD, 0), ENTRANCE(IKANA_GRAVEYARD, 2), HAS_ITEM(ITEM_MASK_CAPTAIN) && IS_NIGHT2()), // Day 2 hole + EXIT(ENTRANCE(BENEATH_THE_GRAVERYARD, 1), ENTRANCE(IKANA_GRAVEYARD, 3), HAS_ITEM(ITEM_MASK_CAPTAIN) && IS_NIGHT1()), // Day 1 hole }, .connections = { CONNECTION(RR_IKANA_GRAVEYARD_UPPER, CAN_PLAY_SONG(SONATA)), @@ -248,7 +248,7 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_IKANA_GRAVEYARD_CAPTAIN_MASK, CanKillEnemy(ACTOR_EN_SKB) && CanKillEnemy(ACTOR_EN_BSB)), CHECK(RC_ENEMY_DROP_STALCHILD, CanKillEnemy(ACTOR_EN_SKB)), - CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT)), // Day only + CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT) && IS_DAY()), // Day only CHECK(RC_ENEMY_DROP_CAPTAIN_KEETA, CanKillEnemy(ACTOR_EN_SKB) && CanKillEnemy(ACTOR_EN_BSB)), }, .connections = { @@ -273,7 +273,7 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_ROAD_TO_IKANA_ABOVE_LEDGE] = RandoRegion{ .name = "Above Ledge", .sceneId = SCENE_IKANAMAE, .checks = { - CHECK(RC_ENEMY_DROP_NEJIRON, CanKillEnemy(ACTOR_EN_BAGUO)), // Day only + CHECK(RC_ENEMY_DROP_NEJIRON, CanKillEnemy(ACTOR_EN_BAGUO) && IS_DAY()), // Day only }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_CANYON, 0), ENTRANCE(ROAD_TO_IKANA, 1), true), @@ -286,8 +286,8 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_ROAD_TO_IKANA_POT, CAN_HOOK_SCARECROW), CHECK(RC_ROAD_TO_IKANA_STONE_MASK, HAS_ITEM(ITEM_LENS_OF_TRUTH) && HAS_MAGIC && HAS_BOTTLE && (CAN_ACCESS(RED_POTION_REFILL) || CAN_ACCESS(BLUE_POTION_REFILL))), - CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB)), // Night only - CHECK(RC_ENEMY_DROP_REAL_BOMBCHU, CanKillEnemy(ACTOR_EN_RAT)), // Day only + CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB) && IS_NIGHT()), // Night only + CHECK(RC_ENEMY_DROP_REAL_BOMBCHU, CanKillEnemy(ACTOR_EN_RAT) && IS_DAY()), // Day only }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_GRAVEYARD, 0), ENTRANCE(ROAD_TO_IKANA, 2), true) @@ -300,8 +300,8 @@ static RegisterShipInitFunc initFunc([]() { Regions[RR_ROAD_TO_IKANA_FIELD_SIDE] = RandoRegion{ .name = "Field Side", .sceneId = SCENE_IKANAMAE, .checks = { CHECK(RC_ROAD_TO_IKANA_CHEST, HAS_ITEM(ITEM_HOOKSHOT)), - CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB)), // Night only - CHECK(RC_ENEMY_DROP_REAL_BOMBCHU, CanKillEnemy(ACTOR_EN_RAT)), // Day only + CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB) && IS_NIGHT()), // Night only + CHECK(RC_ENEMY_DROP_REAL_BOMBCHU, CanKillEnemy(ACTOR_EN_RAT) && IS_DAY()), // Day only }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 4), ENTRANCE(ROAD_TO_IKANA, 0), true), diff --git a/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp b/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp index 8f0de44c5e..03b3dc0e1a 100644 --- a/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp +++ b/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp @@ -17,13 +17,17 @@ static RegisterShipInitFunc initFunc([]() { .exits = { // TO FROM EXIT(ENTRANCE(ROMANI_RANCH, 4), ENTRANCE(CUCCO_SHACK, 0), true), }, + .timeStayRestrictions = { + STAY(TIME_NIGHT1_PM_08_00, false), + STAY(TIME_NIGHT2_PM_08_00, false), + STAY(TIME_NIGHT3_PM_08_00, false), + }, }; Regions[RR_DOGGY_RACETRACK] = RandoRegion{ .sceneId = SCENE_F01_B, .checks = { // TODO: Trick: Jumpslash to clip through (similar to Clock Town Straw). // Zora can just climb up, adding it to logic for now but if someone wants to make it a trick later feel free. - // TODO: This soil patch can be watered with the Day 2 rain. Clock shuffle will need to require Day 2 or another water source. - CHECK(RC_DOGGY_RACETRACK_CHEST, HAS_ITEM(ITEM_HOOKSHOT) || HAS_ITEM(ITEM_MAGIC_BEANS) || CAN_BE_ZORA), + CHECK(RC_DOGGY_RACETRACK_CHEST, HAS_ITEM(ITEM_HOOKSHOT) || CAN_USE_DAY2_RAIN_BEAN || CAN_BE_ZORA), CHECK(RC_DOGGY_RACETRACK_PIECE_OF_HEART, HAS_ITEM(ITEM_MASK_TRUTH)), CHECK(RC_DOGGY_RACETRACK_POT_01, true), CHECK(RC_DOGGY_RACETRACK_POT_02, true), @@ -33,21 +37,26 @@ static RegisterShipInitFunc initFunc([]() { .exits = { // TO FROM EXIT(ENTRANCE(ROMANI_RANCH, 5), ENTRANCE(DOGGY_RACETRACK, 0), true), }, + .timeStayRestrictions = { + STAY(TIME_NIGHT1_PM_08_00, false), + STAY(TIME_NIGHT2_PM_08_00, false), + STAY(TIME_NIGHT3_PM_08_00, false), + }, }; - Regions[RR_GORMAN_TRACK] = RandoRegion{ .sceneId = SCENE_KOEPONARACE, + Regions[RR_GORMAN_TRACK_FRONT] = RandoRegion{ .sceneId = SCENE_KOEPONARACE, .checks = { - CHECK(RC_GORMAN_MILK_PURCHASE, CAN_AFFORD(RC_GORMAN_MILK_PURCHASE)), - CHECK(RC_GORMAN_TRACK_GARO_MASK, CAN_PLAY_SONG(EPONA)), + CHECK(RC_GORMAN_MILK_PURCHASE, CAN_AFFORD(RC_GORMAN_MILK_PURCHASE) && IS_DAY()), + CHECK(RC_GORMAN_TRACK_GARO_MASK, CAN_PLAY_SONG(EPONA) && IS_DAY()), }, .exits = { // TO FROM EXIT(ENTRANCE(MILK_ROAD, 3), ENTRANCE(GORMAN_TRACK, 0), true), }, .connections = { // TODO: Also apparently can be reached using a trick with Goron mask and Bombs. Add trick later here - CONNECTION(RR_GORMAN_TRACK_INNER, RANDO_EVENTS[RE_COWS_FROM_ALIENS]), + CONNECTION(RR_GORMAN_TRACK, RANDO_EVENTS[RE_COWS_FROM_ALIENS] && IS_NIGHT2()), }, }; - Regions[RR_GORMAN_TRACK_INNER] = RandoRegion{ .sceneId = SCENE_KOEPONARACE, + Regions[RR_GORMAN_TRACK] = RandoRegion{ .sceneId = SCENE_KOEPONARACE, .checks = { // The grass is technically reachable while racing on Epona, but successfully picking up the drops can be // dubious. We can make this a trick in the future. For now, gate the entire region behind saving the ranch @@ -78,11 +87,17 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_GORMAN_TRACK_GRASS_23, true), CHECK(RC_GORMAN_TRACK_GRASS_24, true), }, + .connections = { + CONNECTION(RR_GORMAN_TRACK_FRONT, CAN_PLAY_SONG(EPONA) || (RANDO_EVENTS[RE_COWS_FROM_ALIENS] && IS_NIGHT2())), + CONNECTION(RR_GORMAN_TRACK_BACK, CAN_PLAY_SONG(EPONA) || (RANDO_EVENTS[RE_COWS_FROM_ALIENS] && IS_NIGHT2())), + }, + }; + Regions[RR_GORMAN_TRACK_BACK] = RandoRegion{ .sceneId = SCENE_KOEPONARACE, .exits = { // TO FROM - EXIT(ENTRANCE(MILK_ROAD, 2), ENTRANCE(GORMAN_TRACK, 3), RANDO_EVENTS[RE_COWS_FROM_ALIENS]), + EXIT(ENTRANCE(MILK_ROAD, 2), ENTRANCE(GORMAN_TRACK, 3), true), }, .connections = { - CONNECTION(RR_GORMAN_TRACK, RANDO_EVENTS[RE_COWS_FROM_ALIENS]), + CONNECTION(RR_GORMAN_TRACK, RANDO_EVENTS[RE_COWS_FROM_ALIENS] && IS_NIGHT2()), }, }; Regions[RR_MILK_ROAD] = RandoRegion{ .sceneId = SCENE_ROMANYMAE, @@ -94,41 +109,63 @@ static RegisterShipInitFunc initFunc([]() { }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 5), ENTRANCE(MILK_ROAD, 0), true), - EXIT(ENTRANCE(ROMANI_RANCH, 0), ENTRANCE(MILK_ROAD, 1), true), - EXIT(ENTRANCE(GORMAN_TRACK, 3), ENTRANCE(MILK_ROAD, 2), RANDO_EVENTS[RE_COWS_FROM_ALIENS]), + EXIT(ENTRANCE(ROMANI_RANCH, 0), ENTRANCE(MILK_ROAD, 1), AFTER(TIME_DAY3_AM_06_00) || RANDO_EVENTS[RE_DESTROY_MILK_ROAD_BOULDER]), EXIT(ENTRANCE(GORMAN_TRACK, 0), ENTRANCE(MILK_ROAD, 3), true), }, + .connections = { + // TODO: Trick to Goron bomb jump over the fence + CONNECTION(RR_MILK_ROAD_BEHIND_FENCE, (RANDO_EVENTS[RE_COWS_FROM_ALIENS] && IS_NIGHT2()) || FINAL_DAY()), + }, .events = { EVENT(RE_ACCESS_PICTOGRAPH_TINGLE, HAS_ITEM(ITEM_PICTOGRAPH_BOX)), + EVENT(RE_DESTROY_MILK_ROAD_BOULDER, CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), }, .oneWayEntrances = { ENTRANCE(MILK_ROAD, 4), // From Song of Soaring } }; + Regions[RR_MILK_ROAD_BEHIND_FENCE] = RandoRegion{ .sceneId = SCENE_ROMANYMAE, + .exits = { // TO FROM + EXIT(ENTRANCE(GORMAN_TRACK, 3), ENTRANCE(MILK_ROAD, 2), true), + }, + .connections = { + // TODO: Trick to Goron bomb jump over the fence + CONNECTION(RR_MILK_ROAD, (RANDO_EVENTS[RE_COWS_FROM_ALIENS] && IS_NIGHT2()) || FINAL_DAY()), + }, + }; Regions[RR_RANCH_BARN] = RandoRegion{ .sceneId = SCENE_OMOYA, .checks = { - CHECK(RC_ROMANI_RANCH_BARN_COW_LEFT, CAN_PLAY_SONG(EPONA) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), - CHECK(RC_ROMANI_RANCH_BARN_COW_MIDDLE, CAN_PLAY_SONG(EPONA) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), - CHECK(RC_ROMANI_RANCH_BARN_COW_RIGHT, CAN_PLAY_SONG(EPONA) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)) + CHECK(RC_ROMANI_RANCH_BARN_COW_LEFT, CAN_PLAY_SONG(EPONA) && (BETWEEN(TIME_NIGHT1_PM_06_00, TIME_NIGHT1_AM_02_30) || RANDO_EVENTS[RE_COWS_FROM_ALIENS])), + CHECK(RC_ROMANI_RANCH_BARN_COW_MIDDLE, CAN_PLAY_SONG(EPONA) && (BETWEEN(TIME_NIGHT1_PM_06_00, TIME_NIGHT1_AM_02_30) || RANDO_EVENTS[RE_COWS_FROM_ALIENS])), + CHECK(RC_ROMANI_RANCH_BARN_COW_RIGHT, CAN_PLAY_SONG(EPONA) && (BETWEEN(TIME_NIGHT1_PM_06_00, TIME_NIGHT1_AM_02_30) || RANDO_EVENTS[RE_COWS_FROM_ALIENS])) }, .exits = { // TO FROM EXIT(ENTRANCE(ROMANI_RANCH, 2), ENTRANCE(RANCH_HOUSE, 0), true), }, + .timeStayRestrictions = { + STAY(TIME_NIGHT1_AM_02_30, false), + STAY(TIME_NIGHT3_PM_08_00, false), + }, }; Regions[RR_RANCH_HOUSE] = RandoRegion{ .sceneId = SCENE_OMOYA, .exits = { // TO FROM EXIT(ENTRANCE(ROMANI_RANCH, 3), ENTRANCE(RANCH_HOUSE, 1), true), }, + .timeStayRestrictions = { + STAY(TIME_NIGHT1_PM_08_00, false), + STAY(TIME_NIGHT2_PM_08_00, false), + STAY(TIME_NIGHT3_PM_08_00, false), + }, }; Regions[RR_ROMANI_RANCH] = RandoRegion{ .sceneId = SCENE_F01, .checks = { CHECK(RC_ROMANI_RANCH_ALIENS, CanKillEnemy(ACTOR_EN_INVADEPOH) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), - CHECK(RC_ROMANI_RANCH_EPONAS_SONG, CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), - CHECK(RC_ROMANI_RANCH_FIELD_COW_ENTRANCE, CAN_PLAY_SONG(EPONA) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), - CHECK(RC_ROMANI_RANCH_FIELD_COW_NEAR_HOUSE_BACK, CAN_PLAY_SONG(EPONA) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), - CHECK(RC_ROMANI_RANCH_FIELD_COW_NEAR_HOUSE_FRONT, CAN_PLAY_SONG(EPONA) && CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG)), + CHECK(RC_ROMANI_RANCH_EPONAS_SONG, BEFORE(TIME_NIGHT1_PM_06_00)), + CHECK(RC_ROMANI_RANCH_FIELD_COW_ENTRANCE, CAN_PLAY_SONG(EPONA) && (BETWEEN(TIME_NIGHT1_PM_06_00, TIME_NIGHT1_AM_02_30) || RANDO_EVENTS[RE_COWS_FROM_ALIENS])), + CHECK(RC_ROMANI_RANCH_FIELD_COW_NEAR_HOUSE_BACK, CAN_PLAY_SONG(EPONA) && (BETWEEN(TIME_NIGHT1_PM_06_00, TIME_NIGHT1_AM_02_30) || RANDO_EVENTS[RE_COWS_FROM_ALIENS])), + CHECK(RC_ROMANI_RANCH_FIELD_COW_NEAR_HOUSE_FRONT, CAN_PLAY_SONG(EPONA) && (BETWEEN(TIME_NIGHT1_PM_06_00, TIME_NIGHT1_AM_02_30) || RANDO_EVENTS[RE_COWS_FROM_ALIENS])), CHECK(RC_ROMANI_RANCH_FIELD_LARGE_CRATE, true), - CHECK(RC_CREMIA_ESCORT, HAS_ITEM(ITEM_BOW) && RANDO_EVENTS[RE_COWS_FROM_ALIENS]), + CHECK(RC_CREMIA_ESCORT, HAS_ITEM(ITEM_BOW) && RANDO_EVENTS[RE_COWS_FROM_ALIENS] && AT(TIME_NIGHT2_PM_06_00)), CHECK(RC_ROMANI_RANCH_GRASS_01, true), CHECK(RC_ROMANI_RANCH_GRASS_02, true), CHECK(RC_ROMANI_RANCH_GRASS_03, true), @@ -186,17 +223,17 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ROMANI_RANCH_GRASS_55, true), CHECK(RC_ROMANI_RANCH_GRASS_56, true), CHECK(RC_ROMANI_RANCH_GRASS_57, true), - CHECK(RC_ENEMY_DROP_ALIEN, CanKillEnemy(ACTOR_EN_INVADEPOH)), // Night 1 only + CHECK(RC_ENEMY_DROP_ALIEN, CanKillEnemy(ACTOR_EN_INVADEPOH) && IS_NIGHT1()), // Night 1 only }, .exits = { // TO FROM EXIT(ENTRANCE(MILK_ROAD, 1), ENTRANCE(ROMANI_RANCH, 0), true), - EXIT(ENTRANCE(RANCH_HOUSE, 0), ENTRANCE(ROMANI_RANCH, 2), true), // Barn - EXIT(ENTRANCE(RANCH_HOUSE, 1), ENTRANCE(ROMANI_RANCH, 3), true), // House - EXIT(ENTRANCE(CUCCO_SHACK, 0), ENTRANCE(ROMANI_RANCH, 4), true), - EXIT(ENTRANCE(DOGGY_RACETRACK, 0), ENTRANCE(ROMANI_RANCH, 5), true), + EXIT(ENTRANCE(RANCH_HOUSE, 0), ENTRANCE(ROMANI_RANCH, 2), BETWEEN(TIME_DAY1_AM_06_00, TIME_NIGHT1_AM_02_30) || SECOND_DAY() || BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_08_00)), // Barn + EXIT(ENTRANCE(RANCH_HOUSE, 1), ENTRANCE(ROMANI_RANCH, 3), BETWEEN(TIME_DAY1_AM_06_00, TIME_NIGHT1_PM_08_00) || BETWEEN(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_08_00) || BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_08_00)), // House + EXIT(ENTRANCE(CUCCO_SHACK, 0), ENTRANCE(ROMANI_RANCH, 4), BETWEEN(TIME_DAY1_AM_06_00, TIME_NIGHT1_PM_08_00) || BETWEEN(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_08_00) || BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_08_00)), + EXIT(ENTRANCE(DOGGY_RACETRACK, 0), ENTRANCE(ROMANI_RANCH, 5), BETWEEN(TIME_DAY1_AM_06_00, TIME_NIGHT1_PM_08_00) || BETWEEN(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_08_00) || BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_08_00)), }, .events = { - EVENT(RE_COWS_FROM_ALIENS, CAN_BE_GORON && HAS_ITEM(ITEM_POWDER_KEG) && HAS_ITEM(ITEM_BOW)), + EVENT(RE_COWS_FROM_ALIENS, (HAS_ITEM(ITEM_POWDER_KEG) && CAN_BE_GORON) && HAS_ITEM(ITEM_BOW) && AT(TIME_NIGHT1_AM_02_30)), }, }; }, {}); diff --git a/mm/2s2h/Rando/Logic/Regions/North.cpp b/mm/2s2h/Rando/Logic/Regions/North.cpp index 00e7636849..b4d8eea8aa 100644 --- a/mm/2s2h/Rando/Logic/Regions/North.cpp +++ b/mm/2s2h/Rando/Logic/Regions/North.cpp @@ -128,7 +128,7 @@ static RegisterShipInitFunc initFunc([]() { .exits = { // TO FROM // During First Day a NPC Goron can open the door to the the Shrine EXIT(ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 1), ENTRANCE(GORON_VILLAGE_WINTER, 0), true), - EXIT(ENTRANCE(GORON_SHRINE, 0), ENTRANCE(GORON_VILLAGE_WINTER, 2), true), + EXIT(ENTRANCE(GORON_SHRINE, 0), ENTRANCE(GORON_VILLAGE_WINTER, 2), FIRST_DAY() || CAN_BE_GORON), }, .connections = { CONNECTION(RR_LONE_PEAK_SHRINE_ENTRANCE, true) @@ -252,7 +252,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_MOUNTAIN_VILLAGE_SPRING_GRASS_30, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]), CHECK(RC_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_01, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_02, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), - CHECK(RC_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_03, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), + CHECK(RC_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_03, (FIRST_DAY() || SECOND_DAY()) && CanKillEnemy(ACTOR_OBJ_SNOWBALL)), // Goron Elder inside on Final Day CHECK(RC_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_04, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_05, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_01, true), @@ -269,8 +269,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ENEMY_DROP_GUAY, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && CanKillEnemy(ACTOR_EN_CROW)), CHECK(RC_ENEMY_DROP_GIANT_BEE, CanKillEnemy(ACTOR_EN_BEE) && RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]), CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK) && RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]), - CHECK(RC_ENEMY_DROP_TEKTITE, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_TITE)), // Day 1 and 3 only - CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_WF)), // Day 2 only + CHECK(RC_ENEMY_DROP_TEKTITE, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_TITE) && (FIRST_DAY() || FINAL_DAY())), // Day 1 and 3 only + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_WF) && SECOND_DAY()), // Day 2 only }, .exits = { // TO FROM EXIT(ENTRANCE(MOUNTAIN_SMITHY, 0), ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 1), true), @@ -345,19 +345,19 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TWIN_ISLANDS_SPRING_GRASS_10, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]), CHECK(RC_TWIN_ISLANDS_SPRING_GRASS_11, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]), CHECK(RC_TWIN_ISLANDS_SPRING_GRASS_12, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]), - CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_01, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), + CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_01, (FIRST_DAY() || FINAL_DAY()) && CanKillEnemy(ACTOR_OBJ_SNOWBALL)), // Goron inside on Second Day CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_02, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_03, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_04, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_05, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_06, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_07, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), - CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_08, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), + CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_08, (SECOND_DAY() || FINAL_DAY()) && CanKillEnemy(ACTOR_OBJ_SNOWBALL)), // Goron inside on First Day CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_09, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_10, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_11, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), - CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_12, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), - CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_13, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), + CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_12, (FIRST_DAY() || FINAL_DAY()) && CanKillEnemy(ACTOR_OBJ_SNOWBALL)), // Does not exist on Second Day + CHECK(RC_TWIN_ISLANDS_LARGE_SNOWBALL_13, (FIRST_DAY() || FINAL_DAY()) && CanKillEnemy(ACTOR_OBJ_SNOWBALL)), // Goron inside on Second Day CHECK(RC_TWIN_ISLANDS_SMALL_SNOWBALL_01, true), CHECK(RC_TWIN_ISLANDS_SMALL_SNOWBALL_02, true), CHECK(RC_TWIN_ISLANDS_SMALL_SNOWBALL_03, true), @@ -394,9 +394,9 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_02, true), CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_03, true), CHECK(RC_ENEMY_DROP_TEKTITE, CanKillEnemy(ACTOR_EN_TITE)), - CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), // Night only - CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_WF)), // Day 2 only - CHECK(RC_ENEMY_DROP_SNAPPER, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_KAME)), // Day 3 only + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK) && IS_NIGHT()), // Night only + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_WF) && SECOND_DAY()), // Day 2 only + CHECK(RC_ENEMY_DROP_SNAPPER, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_KAME) && FINAL_DAY()), // Day 3 only }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 3), ENTRANCE(PATH_TO_MOUNTAIN_VILLAGE, 0), true), @@ -415,9 +415,9 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_10, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_11, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_PATH_TO_MOUNTAIN_VILLAGE_SMALL_SNOWBALL_04, true), - CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), // Night only - CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_WF)), // Day 2 only - CHECK(RC_ENEMY_DROP_SNAPPER, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_KAME)), // Day 3 only + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK) && IS_NIGHT()), // Night only + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_WF) && SECOND_DAY()), // Day 2 only + CHECK(RC_ENEMY_DROP_SNAPPER, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_KAME) && FINAL_DAY()), // Day 3 only }, .exits = { // TO FROM EXIT(ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 6), ENTRANCE(PATH_TO_MOUNTAIN_VILLAGE, 1), true), @@ -524,7 +524,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_SNOWHEAD_LARGE_SNOWBALL_06, CanKillEnemy(ACTOR_OBJ_SNOWBALL)), CHECK(RC_ENEMY_DROP_KEESE, CanKillEnemy(ACTOR_EN_FIREFLY)), CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_EN_WF)), - CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK)), // Night only + CHECK(RC_ENEMY_DROP_BOE, CanKillEnemy(ACTOR_EN_MKK) && IS_NIGHT()), // Night only }, .connections = { CONNECTION(RR_SNOWHEAD_NEAR_PATH, true), diff --git a/mm/2s2h/Rando/Logic/Regions/South.cpp b/mm/2s2h/Rando/Logic/Regions/South.cpp index 2b29d7aac3..37cd95453e 100644 --- a/mm/2s2h/Rando/Logic/Regions/South.cpp +++ b/mm/2s2h/Rando/Logic/Regions/South.cpp @@ -136,8 +136,7 @@ static RegisterShipInitFunc initFunc([]() { }, .connections = { CONNECTION(RR_DEKU_PALACE_INSIDE_LOWER, CAN_BE_DEKU), - // TODO: This soil patch can be watered with the Day 2 rain. Clock shuffle will need to require Day 2 or another water source. - CONNECTION(RR_DEKU_PALACE_INSIDE_UPPER, (CAN_BE_DEKU || (RANDO_EVENTS[RE_CLEARED_WOODFALL_TEMPLE] && CAN_TRAVERSE_WAIST_DEEP_WATER)) && HAS_ITEM(ITEM_MAGIC_BEANS)), + CONNECTION(RR_DEKU_PALACE_INSIDE_UPPER, (CAN_BE_DEKU || (RANDO_EVENTS[RE_CLEARED_WOODFALL_TEMPLE] && CAN_TRAVERSE_WAIST_DEEP_WATER)) && CAN_USE_DAY2_RAIN_BEAN), }, }; Regions[RR_DEKU_SHRINE_ENTRANCE] = RandoRegion{ .name = "Entrance", .sceneId = SCENE_DANPEI, @@ -192,9 +191,9 @@ static RegisterShipInitFunc initFunc([]() { .checks = { CHECK(RC_HAGS_POTION_SHOP_FREESTANDING_RUPEE, true), // TODO: Add CAN_ACCESS(MUSHROOM) once that is shuffled. - CHECK(RC_HAGS_POTION_SHOP_ITEM_01, CAN_AFFORD(RC_HAGS_POTION_SHOP_ITEM_01) && HAS_ITEM(ITEM_MASK_SCENTS) && HAS_BOTTLE), - CHECK(RC_HAGS_POTION_SHOP_ITEM_02, CAN_AFFORD(RC_HAGS_POTION_SHOP_ITEM_02)), - CHECK(RC_HAGS_POTION_SHOP_ITEM_03, CAN_AFFORD(RC_HAGS_POTION_SHOP_ITEM_03)), + CHECK(RC_HAGS_POTION_SHOP_ITEM_01, (FIRST_DAY() || RANDO_EVENTS[RE_SAVED_KOUME]) && CAN_AFFORD(RC_HAGS_POTION_SHOP_ITEM_01) && HAS_ITEM(ITEM_MASK_SCENTS) && HAS_BOTTLE), + CHECK(RC_HAGS_POTION_SHOP_ITEM_02, (FIRST_DAY() || RANDO_EVENTS[RE_SAVED_KOUME]) && CAN_AFFORD(RC_HAGS_POTION_SHOP_ITEM_02)), + CHECK(RC_HAGS_POTION_SHOP_ITEM_03, (FIRST_DAY() || RANDO_EVENTS[RE_SAVED_KOUME]) && CAN_AFFORD(RC_HAGS_POTION_SHOP_ITEM_03)), CHECK(RC_HAGS_POTION_SHOP_KOTAKE, true), }, .exits = { // TO FROM @@ -253,14 +252,14 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ROAD_TO_SOUTHERN_SWAMP_GRASS_19, true), CHECK(RC_ROAD_TO_SOUTHERN_SWAMP_GRASS_20, true), CHECK(RC_ENEMY_DROP_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA)), - CHECK(RC_ENEMY_DROP_CHUCHU, CanKillEnemy(ACTOR_EN_SLIME)), // Day only - CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_EN_WF)), // Night only + CHECK(RC_ENEMY_DROP_CHUCHU, CanKillEnemy(ACTOR_EN_SLIME) && IS_DAY()), // Day only + CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_EN_WF) && IS_NIGHT()), // Night only CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT)), }, .exits = { // TO FROM EXIT(ENTRANCE(TERMINA_FIELD, 1), ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 0), true), EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 0), ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 1), true), - EXIT(ENTRANCE(SWAMP_SHOOTING_GALLERY, 0), ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 2), true), + EXIT(ENTRANCE(SWAMP_SHOOTING_GALLERY, 0), ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 2), BEFORE(TIME_NIGHT1_PM_10_00) || BETWEEN(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_10_00) || BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_10_00)), }, .connections = { CONNECTION(RR_ROAD_TO_SOUTHERN_SWAMP_GROTTO, true), // TODO: Grotto mapping @@ -466,6 +465,11 @@ static RegisterShipInitFunc initFunc([]() { .exits = { // TO FROM EXIT(ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 2), ENTRANCE(SWAMP_SHOOTING_GALLERY, 0), true), }, + .timeStayRestrictions = { + STAY(TIME_NIGHT1_PM_10_00, false), + STAY(TIME_NIGHT2_PM_10_00, false), + STAY(TIME_NIGHT3_PM_10_00, false), + }, }; Regions[RR_TOURIST_INFORMATION] = RandoRegion{ .sceneId = SCENE_MAP_SHOP, .checks = { @@ -548,7 +552,7 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)), }, .connections = { - CONNECTION(RR_WOODS_OF_MYSTERY, true), // TODO: Grotto mapping + CONNECTION(RR_WOODS_OF_MYSTERY, SECOND_DAY()), // TODO: Grotto mapping }, }; Regions[RR_WOODS_OF_MYSTERY] = RandoRegion{ .sceneId = SCENE_26SARUNOMORI, @@ -556,8 +560,8 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_WOODS_OF_MYSTERY_GRASS_01, true), CHECK(RC_WOODS_OF_MYSTERY_GRASS_02, true), CHECK(RC_WOODS_OF_MYSTERY_GRASS_03, true), - CHECK(RC_WOODS_OF_MYSTERY_GRASS_04, true), - CHECK(RC_WOODS_OF_MYSTERY_GRASS_05, true), + CHECK(RC_WOODS_OF_MYSTERY_GRASS_04, FIRST_DAY()), + CHECK(RC_WOODS_OF_MYSTERY_GRASS_05, FIRST_DAY()), CHECK(RC_WOODS_OF_MYSTERY_GRASS_06, true), CHECK(RC_WOODS_OF_MYSTERY_GRASS_07, true), CHECK(RC_WOODS_OF_MYSTERY_GRASS_08, true), @@ -573,16 +577,16 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_WOODS_OF_MYSTERY_GRASS_18, true), CHECK(RC_WOODS_OF_MYSTERY_GRASS_19, true), CHECK(RC_WOODS_OF_MYSTERY_GRASS_20, true), - CHECK(RC_WOODS_OF_MYSTERY_GRASS_21, true), - CHECK(RC_WOODS_OF_MYSTERY_GRASS_22, true), - CHECK(RC_WOODS_OF_MYSTERY_GRASS_23, true), + CHECK(RC_WOODS_OF_MYSTERY_GRASS_21, SECOND_DAY()), + CHECK(RC_WOODS_OF_MYSTERY_GRASS_22, FINAL_DAY()), + CHECK(RC_WOODS_OF_MYSTERY_GRASS_23, FINAL_DAY()), CHECK(RC_ENEMY_DROP_SNAPPER, CAN_BE_DEKU || CanKillEnemy(ACTOR_EN_KAME)), }, .exits = { // TO FROM EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 7), ENTRANCE(WOODS_OF_MYSTERY, 0), true), }, .connections = { - CONNECTION(RR_WOODS_OF_MYSTERY_GROTTO, true), // TODO: Grotto mapping + CONNECTION(RR_WOODS_OF_MYSTERY_GROTTO, SECOND_DAY()), // TODO: Grotto mapping }, .events = { EVENT(RE_SAVED_KOUME, HAS_BOTTLE && (CAN_ACCESS(RED_POTION_REFILL) || CAN_ACCESS(BLUE_POTION_REFILL))), diff --git a/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp b/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp index 4af56e091e..3ff118ef56 100644 --- a/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp +++ b/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp @@ -179,7 +179,7 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_TERMINA_FIELD_PEAHAT_GROTTO] = RandoRegion{ .name = "Termina Field Peahat", .sceneId = SCENE_KAKUSIANA, .checks = { - CHECK(RC_TERMINA_FIELD_PEAHAT_GROTTO_CHEST, CAN_USE_SWORD || CAN_BE_ZORA || CAN_BE_GORON), + CHECK(RC_TERMINA_FIELD_PEAHAT_GROTTO_CHEST, (CAN_USE_SWORD || CAN_BE_ZORA || CAN_BE_GORON) && IS_DAY()), CHECK(RC_TERMINA_FIELD_PEAHAT_GROTTO_GRASS_01, true), CHECK(RC_TERMINA_FIELD_PEAHAT_GROTTO_GRASS_02, true), CHECK(RC_TERMINA_FIELD_PEAHAT_GROTTO_GRASS_03, true), @@ -256,7 +256,7 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_TERMINA_FIELD] = RandoRegion{ .sceneId = SCENE_00KEIKOKU, .checks = { - CHECK(RC_TERMINA_FIELD_KAMARO_MASK, CAN_PLAY_SONG(HEALING)), + CHECK(RC_TERMINA_FIELD_KAMARO_MASK, CAN_PLAY_SONG(HEALING) && MIDNIGHT()), CHECK(RC_TERMINA_FIELD_POT, CAN_GROW_BEAN_PLANT), CHECK(RC_TERMINA_FIELD_TALL_GRASS_CHEST, true), CHECK(RC_TERMINA_FIELD_TREE_STUMP_CHEST, CAN_GROW_BEAN_PLANT || HAS_ITEM(ITEM_HOOKSHOT)), @@ -498,13 +498,13 @@ static RegisterShipInitFunc initFunc([]() { CHECK(RC_TERMINA_FIELD_GRASS_214, true), CHECK(RC_TERMINA_FIELD_GRASS_215, true), CHECK(RC_TERMINA_FIELD_GRASS_216, true), - CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB)), // Night only + CHECK(RC_ENEMY_DROP_BLUE_BUBBLE, CanKillEnemy(ACTOR_EN_BB) && IS_NIGHT()), // Night only CHECK(RC_ENEMY_DROP_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA)), - CHECK(RC_ENEMY_DROP_CHUCHU, CanKillEnemy(ACTOR_EN_SLIME)), // Day only - CHECK(RC_ENEMY_DROP_REAL_BOMBCHU, CanKillEnemy(ACTOR_EN_RAT)), // Day only + CHECK(RC_ENEMY_DROP_CHUCHU, CanKillEnemy(ACTOR_EN_SLIME) && IS_DAY()), // Day only + CHECK(RC_ENEMY_DROP_REAL_BOMBCHU, CanKillEnemy(ACTOR_EN_RAT) && IS_DAY()), // Day only CHECK(RC_ENEMY_DROP_LEEVER, CanKillEnemy(ACTOR_EN_NEO_REEBA)), - CHECK(RC_ENEMY_DROP_DODONGO, CanKillEnemy(ACTOR_EN_DODONGO)), // Day only - CHECK(RC_ENEMY_DROP_EENO, CanKillEnemy(ACTOR_EN_SNOWMAN)), // Night only + CHECK(RC_ENEMY_DROP_DODONGO, CanKillEnemy(ACTOR_EN_DODONGO) && IS_DAY()), // Day only + CHECK(RC_ENEMY_DROP_EENO, CanKillEnemy(ACTOR_EN_SNOWMAN) && IS_NIGHT()), // Night only CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT)), CHECK(RC_ENEMY_DROP_TAKKURI, CanKillEnemy(ACTOR_EN_THIEFBIRD)), }, diff --git a/mm/2s2h/Rando/Logic/Regions/West.cpp b/mm/2s2h/Rando/Logic/Regions/West.cpp index 1732996c28..97474397fd 100644 --- a/mm/2s2h/Rando/Logic/Regions/West.cpp +++ b/mm/2s2h/Rando/Logic/Regions/West.cpp @@ -123,7 +123,7 @@ static RegisterShipInitFunc initFunc([]() { }; Regions[RR_GREAT_BAY_COAST] = RandoRegion{ .sceneId = SCENE_30GYOSON, .checks = { - CHECK(RC_GREAT_BAY_COAST_FISHERMAN_MINIGAME, RANDO_EVENTS[RE_CLEARED_GREAT_BAY_TEMPLE] && (HAS_ITEM(ITEM_HOOKSHOT) || CAN_USE_MAGIC_ARROW(ICE))), + CHECK(RC_GREAT_BAY_COAST_FISHERMAN_MINIGAME, RANDO_EVENTS[RE_CLEARED_GREAT_BAY_TEMPLE] && (HAS_ITEM(ITEM_HOOKSHOT) || CAN_USE_MAGIC_ARROW(ICE)) && (BETWEEN(TIME_DAY1_AM_07_00, TIME_NIGHT1_AM_04_00) || BETWEEN(TIME_DAY2_AM_07_00, TIME_NIGHT2_AM_04_00) || BETWEEN(TIME_DAY3_AM_07_00, TIME_NIGHT3_AM_04_00))), CHECK(RC_GREAT_BAY_COAST_MIKAU, CAN_USE_ABILITY(SWIM) && CAN_PLAY_SONG(HEALING)), CHECK(RC_GREAT_BAY_COAST_POT_03, true), CHECK(RC_GREAT_BAY_COAST_POT_04, true), diff --git a/mm/2s2h/Rando/Logic/TimeLogic.cpp b/mm/2s2h/Rando/Logic/TimeLogic.cpp new file mode 100644 index 0000000000..5514f5895b --- /dev/null +++ b/mm/2s2h/Rando/Logic/TimeLogic.cpp @@ -0,0 +1,123 @@ +#include "Logic.h" +#include + +extern "C" { +#include "ShipUtils.h" +} + +namespace Rando { +namespace Logic { +namespace TimeLogic { + +// Core expansion function - expands accessible time forward with stay restrictions +// Implements sequential expansion: if a stay restriction fails, expansion stops permanently +// For unrestricted regions without Clock Shuffle: fast bitwise fill across all time slices (O(1)) +// For Clock Shuffle or restricted regions: sequential expansion respecting boundaries +uint64_t ExpandTimeForward(uint64_t timeSlices, const RandoRegion& region) { + // Fast path: unrestricted time expansion using bitwise fill (only when Clock Shuffle is off) + if (region.timeStayRestrictions.empty() && !SettingClocks()) { + uint64_t expanded = timeSlices; + + // Non-Clock Shuffle: expand across ALL time slices using bitwise fill + expanded |= (expanded << 1); + expanded |= (expanded << 2); + expanded |= (expanded << 4); + expanded |= (expanded << 8); + expanded |= (expanded << 16); + expanded |= (expanded << 32); + expanded &= TIME_ALL_SLICES; + + return expanded; + } + + // Slow path: restricted time expansion with sequential checking + // In Clock Shuffle, filter input to only owned time + uint64_t filteredTimeSlices = timeSlices; + if (SettingClocks()) { + filteredTimeSlices &= GetOwnedTimeSlices(); + } + + uint64_t expanded = filteredTimeSlices; + bool canWait = false; + + for (int i = 0; i < TIME_SLICE_COUNT; ++i) { + uint64_t mask = (TIME_BIT_ONE << i); + + if (filteredTimeSlices & mask) { + // We can be at this time + canWait = true; + expanded |= mask; + } else if (canWait) { + // During Clock Shuffle, check if this time slice is owned + if (SettingClocks() && !IsTimeSliceOwned(static_cast(i))) { + canWait = false; // Can't expand into unowned time period + continue; + } + + // Check if we can wait to this time + auto it = region.timeStayRestrictions.find(static_cast(i)); + if (it != region.timeStayRestrictions.end()) { + // CLOCK SHUFFLE: Ignore item-gated restrictions during logic generation + // Player will obtain items eventually, so treat as permissive + if (SettingClocks()) { + expanded |= mask; // Allow expansion - player will get items eventually + } else if (it->second()) { + expanded |= mask; // Condition passed, add time + } else { + canWait = false; // Kicked out, STOP expansion + } + } else { + // No restriction = default true, can stay + expanded |= mask; + } + } + } + + // VALIDATION: In Clock Shuffle, expanded time must not exceed owned time + if (SettingClocks()) { + uint64_t ownedTimeSlices = GetOwnedTimeSlices(); + bool expandedBeyondOwned = (expanded & ~ownedTimeSlices) != 0; + assert(!expandedBeyondOwned && "Time expansion exceeded owned half-day boundaries!"); + } + + return expanded; +} + +// Owned time calculation - aggregates all owned half-day time slices +uint64_t GetOwnedTimeSlices() { + if (!RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE]) { + return TIME_ALL_SLICES; + } + + uint64_t timeSlices = 0; + for (int halfDayIndex = 0; halfDayIndex < 6; ++halfDayIndex) { + if (OwnsClockHalfDay(halfDayIndex)) { + timeSlices |= GetHalfDayTimeMask(halfDayIndex); + } + } + + // If no clocks are owned, ensure we at least have access to the start of the game (Day 1 6 AM) + return timeSlices ? timeSlices : (TIME_BIT_ONE << TIME_DAY1_AM_06_00); +} + +// Validation helper for clock ownership during logic generation +void ValidateRegionTimeOwnership(RandoRegionId regionId, RandoCheckId checkId, uint64_t regionTime, + const char* context) { + if (!SettingClocks()) + return; + + if (!HasAnyOwnedTime(regionTime)) { + auto& region = Regions[regionId]; + SPDLOG_ERROR("CLOCK SHUFFLE VALIDATION FAILED ({})!", context); + SPDLOG_ERROR("Check: {}", Rando::StaticData::Checks[checkId].name); + SPDLOG_ERROR("Region: {} - {}", Ship_GetSceneName(region.sceneId), region.name); + SPDLOG_ERROR("Region time mask: 0x{:X}", regionTime); + SPDLOG_ERROR("Owned clocks: D1={} N1={} D2={} N2={} D3={} N3={}", OwnsClockHalfDay(0), OwnsClockHalfDay(1), + OwnsClockHalfDay(2), OwnsClockHalfDay(3), OwnsClockHalfDay(4), OwnsClockHalfDay(5)); + assert(false && "Check placed in unowned time period during Clock Shuffle!"); + } +} + +} // namespace TimeLogic +} // namespace Logic +} // namespace Rando diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index 9bdbc7eb48..f141b1443c 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -2,6 +2,7 @@ #include "Rando/Spoiler/Spoiler.h" #include "2s2h/BenGui/UIWidgets.hpp" #include "Rando/CheckTracker/CheckTracker.h" +#include "Rando/MiscBehavior/ClockShuffle.h" #include "build.h" #include "2s2h/BenGui/BenMenu.h" #include "2s2h/BenGui/BenGui.hpp" @@ -39,6 +40,7 @@ std::vector incompatibleWithVanilla = { RO_SHUFFLE_BOSS_SOULS, RO_SHUFFLE_SWIM, RO_PLENTIFUL_ITEMS, + RO_CLOCK_SHUFFLE, }; std::vector checkExclusionList; @@ -57,6 +59,36 @@ extern "C" { #include "archives/icon_item_24_static/icon_item_24_static_yar.h" } +// Clock UI rendering constants +static const ImVec4 CLOCK_DAY_TINT = ImVec4(1.0f, 0.85f, 0.3f, 1.0f); +static const ImVec4 CLOCK_NIGHT_TINT = ImVec4(0.3f, 0.5f, 1.0f, 1.0f); +static const float DISABLED_ITEM_ALPHA = 0.3f; +static const char* CLOCK_PROGRESSIVE_TOOLTIP = + "\n\nTime items are not compatible with Progressive Time modes.\nSwitch to Random mode to use starting time."; + +// Apply clock-specific rendering (tint colors and tooltips) based on progressive mode +static void ApplyClockItemRendering(RandoItemId item, ImVec4& tintColor, std::string& tooltipText, + bool isProgressiveMode) { + using namespace Rando::ClockItems; + + if (!IsClockItem(item)) { + return; // Not a clock item, no special handling needed + } + + // Apply day/night color tint + if (IsDayClock(item)) { + tintColor = CLOCK_DAY_TINT; + } else { + tintColor = CLOCK_NIGHT_TINT; + } + + // Grey out and add tooltip if progressive mode is active + if (isProgressiveMode) { + tintColor.w *= DISABLED_ITEM_ALPHA; + tooltipText += CLOCK_PROGRESSIVE_TOOLTIP; + } +} + void ClearIncompatibleSetting() { int32_t currentLogicSetting = CVarGetInteger(Rando::StaticData::Options[RO_LOGIC].cvar, Rando::StaticData::Options[RO_LOGIC].defaultValue); @@ -66,6 +98,7 @@ void ClearIncompatibleSetting() { CVarClear(Rando::StaticData::Options[RO_PLENTIFUL_ITEMS].cvar); CVarClear(Rando::StaticData::Options[RO_SHUFFLE_BOSS_SOULS].cvar); CVarClear(Rando::StaticData::Options[RO_SHUFFLE_SWIM].cvar); + CVarClear(Rando::StaticData::Options[RO_CLOCK_SHUFFLE].cvar); break; default: break; @@ -154,6 +187,7 @@ static RegisterShipInitFunc refreshMetricsInit(RefreshMetrics, { "gRando.Options.RO_ACCESS_MOON_MASKS_COUNT", "gRando.Options.RO_ACCESS_MOON_REMAINS_COUNT", "gRando.Options.RO_ACCESS_TRIALS", + "gRando.Options.RO_CLOCK_SHUFFLE", "gRando.Options.RO_HINTS_BOSS_REMAINS", "gRando.Options.RO_HINTS_GOSSIP_STONES", "gRando.Options.RO_HINTS_HOOKSHOT", @@ -408,6 +442,51 @@ static void DrawItemsTab() { CheckboxOptions({ { .tooltip = "Shuffles the first drop from a non Boss Enemy." } })); CVarCheckbox("Enemy Souls", "gPlaceholderBool", CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } })); + CVarCheckbox("Shuffle Time", Rando::StaticData::Options[RO_CLOCK_SHUFFLE].cvar, + CheckboxOptions({ { .tooltip = "Breaks the 3-day cycle into 6 separate half-days (Day 1 Day/Night, " + "Day 2 Day/Night, Day 3 Day/Night) that must be unlocked as items. " + "Players can only access time periods they've obtained. Attempting to " + "access unowned time redirects to the next owned half-day.", + .disabled = IncompatibleWithLogicSetting(RO_CLOCK_SHUFFLE), + .disabledTooltip = "Incompatible with current Logic Setting" } })); + // Only show time progression options when shuffle time is enabled + if (CVarGetInteger(Rando::StaticData::Options[RO_CLOCK_SHUFFLE].cvar, 0)) { + static std::unordered_map clockModeOptions = { + { RO_CLOCK_SHUFFLE_RANDOM, "Random" }, + { RO_CLOCK_SHUFFLE_ASCENDING, "Progressive: Ascending" }, + { RO_CLOCK_SHUFFLE_DESCENDING, "Progressive: Descending" }, + }; + { + int32_t value = + CVarGetInteger(Rando::StaticData::Options[RO_CLOCK_SHUFFLE_PROGRESSIVE].cvar, RO_CLOCK_SHUFFLE_RANDOM); + if (UIWidgets::Combobox("Time Progression Mode", &value, &clockModeOptions)) { + CVarSetInteger(Rando::StaticData::Options[RO_CLOCK_SHUFFLE_PROGRESSIVE].cvar, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + UIWidgets::Tooltip("Random: All 6 half-days shuffled randomly. Player starts with one random half-day.\n\n" + "Progressive Ascending: Unlocks half-days in order (D1, N1, D2, N2, D3, N3).\n\n" + "Progressive Descending: Unlocks half-days in reverse order (N3, D3, N2, D2, N1, D1)."); + } + // Terminal time slider (Final Hours start time) + { + int32_t terminalMinutes = CVarGetInteger(Rando::StaticData::Options[RO_CLOCK_TERMINAL_TIME].cvar, 0); + int hours = terminalMinutes / 60; + int minutes = terminalMinutes % 60; + + ImGui::Spacing(); + ImGui::Text("Final Hours Start Time: %02d:%02d", hours, minutes); + ImGui::Spacing(); + UIWidgets::CVarSliderInt("Final Hours Start Time", Rando::StaticData::Options[RO_CLOCK_TERMINAL_TIME].cvar, + UIWidgets::IntSliderOptions().Min(0).Max(359).DefaultValue(0).LabelPosition( + UIWidgets::LabelPosition::None)); + ImGui::Spacing(); + + UIWidgets::Tooltip("Controls when the final hours countdown begins (00:00 to 05:59). " + "When you run out of owned half-days, this allows the player control over how much " + "time is left before the moon crash.\n\n" + "This setting is baked into the seed and cannot be changed after generation."); + } + } CVarCheckbox("Triforce Hunt", Rando::StaticData::Options[RO_SHUFFLE_TRIFORCE_PIECES].cvar); ImGui::BeginDisabled(!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRIFORCE_PIECES].cvar, RO_GENERIC_OFF)); @@ -550,10 +629,15 @@ static void DrawStartingItemsTab() { const char* texturePath = Rando::StaticData::GetIconTexturePath(startingItem); ImTextureID textureId = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(texturePath); - if (ImGui::ImageButton( - std::to_string(listIndex).c_str(), textureId, imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), - Ship_GetItemColorTint(startingItem == RI_PROGRESSIVE_LULLABY ? ITEM_SONG_LULLABY - : randoStaticItem.itemId))) { + ImVec4 tintColor = + Ship_GetItemColorTint(startingItem == RI_PROGRESSIVE_LULLABY ? ITEM_SONG_LULLABY : randoStaticItem.itemId); + std::string tooltipText = randoStaticItem.name; + bool isProgressiveMode = CVarGetInteger(Rando::StaticData::Options[RO_CLOCK_SHUFFLE_PROGRESSIVE].cvar, + RO_CLOCK_SHUFFLE_RANDOM) != RO_CLOCK_SHUFFLE_RANDOM; + ApplyClockItemRendering(startingItem, tintColor, tooltipText, isProgressiveMode); + + if (ImGui::ImageButton(std::to_string(listIndex).c_str(), textureId, imageSize, ImVec2(0, 0), ImVec2(1, 1), + ImVec4(0, 0, 0, 0), tintColor)) { for (size_t i = 0; i < setStartingItemsList.size(); i++) { if (setStartingItemsList[i] == startingItem) { setStartingItemsList.erase(setStartingItemsList.begin() + i); @@ -563,7 +647,7 @@ static void DrawStartingItemsTab() { CVarSetString("gRando.StartingItems", CreateStartingItemsToCvar(setStartingItemsList).c_str()); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - UIWidgets::Tooltip(randoStaticItem.name); + UIWidgets::Tooltip(tooltipText.c_str()); listIndex++; if ((listIndex + 1) % 15 != 0) { @@ -583,6 +667,8 @@ static void DrawStartingItemsTab() { tableColumns = 5; if (category.first == STARTING_ITEMS_MASK) { tableColumns++; + } else if (category.first == STARTING_ITEMS_MISC) { + tableColumns = 6; // Need 6 columns for the 6 time items on their own row } ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); if (ImGui::BeginChild(std::to_string(category.first).c_str(), ImVec2(0, 0), @@ -609,15 +695,22 @@ static void DrawStartingItemsTab() { ImTextureID textureId = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(texturePath); - if (item == RI_SONG_TIME) { + // Force new row for Song of Time, first frog, and first time item + if (item == RI_SONG_TIME || item == RI_FROG_BLUE || item == RI_TIME_DAY_1) { ImGui::TableNextRow(); } ImGui::TableNextColumn(); + + ImVec4 tintColor = Ship_GetItemColorTint(item == RI_PROGRESSIVE_LULLABY ? ITEM_SONG_LULLABY + : randoStaticItem.itemId); + std::string tooltipText = randoStaticItem.name; + bool isProgressiveMode = + CVarGetInteger(Rando::StaticData::Options[RO_CLOCK_SHUFFLE_PROGRESSIVE].cvar, + RO_CLOCK_SHUFFLE_RANDOM) != RO_CLOCK_SHUFFLE_RANDOM; + ApplyClockItemRendering(item, tintColor, tooltipText, isProgressiveMode); + if (ImGui::ImageButton(std::to_string(item).c_str(), textureId, imageSize, ImVec2(0, 0), - ImVec2(1, 1), ImVec4(0, 0, 0, 0), - Ship_GetItemColorTint(item == RI_PROGRESSIVE_LULLABY - ? ITEM_SONG_LULLABY - : randoStaticItem.itemId))) { + ImVec2(1, 1), ImVec4(0, 0, 0, 0), tintColor)) { std::string currentStartingItems = CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT); if (currentStartingItems.length() != 0) { @@ -627,7 +720,7 @@ static void DrawStartingItemsTab() { CVarSetString("gRando.StartingItems", currentStartingItems.c_str()); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - UIWidgets::Tooltip(randoStaticItem.name); + UIWidgets::Tooltip(tooltipText.c_str()); } ImGui::EndTable(); } diff --git a/mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp b/mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp new file mode 100644 index 0000000000..d5c8f82f57 --- /dev/null +++ b/mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp @@ -0,0 +1,762 @@ +#include "ClockShuffle.h" +#include "Rando/Rando.h" +#include "Rando/Logic/Logic.h" +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/CustomMessage/CustomMessage.h" +#include +#include "2s2h/ShipUtils.h" + +extern "C" { +#include "z64game.h" +#include "overlays/gamestates/ovl_daytelop/z_daytelop.h" +#include "overlays/actors/ovl_En_Test4/z_en_test4.h" +#include "functions.h" +} + +using namespace Rando::Logic; + +namespace Rando { + +// ============================================================================ +// CLOCK ITEM MANAGEMENT +// ============================================================================ + +namespace ClockItems { + +// Internal half-day indices + +// Convert a rando item ID to its corresponding half-day index +int GetHalfDayIndexFromClockItem(RandoItemId clockItemId) { + switch (clockItemId) { + case RI_TIME_DAY_1: + return HALF_DAY1_DAY; + case RI_TIME_NIGHT_1: + return HALF_DAY1_NIGHT; + case RI_TIME_DAY_2: + return HALF_DAY2_DAY; + case RI_TIME_NIGHT_2: + return HALF_DAY2_NIGHT; + case RI_TIME_DAY_3: + return HALF_DAY3_DAY; + case RI_TIME_NIGHT_3: + return HALF_DAY3_NIGHT; + default: + return INVALID; + } +} + +// Convert a half-day index back to its rando item ID +RandoItemId GetClockItemFromHalfDayIndex(int halfDayIndex) { + if (halfDayIndex < 0 || halfDayIndex >= HALF_COUNT) { + return RI_UNKNOWN; + } + + // Map each half-day index to its corresponding rando item + static const RandoItemId clockItemMap[] = { + RI_TIME_DAY_1, // HALF_DAY1_DAY (index 0) + RI_TIME_NIGHT_1, // HALF_DAY1_NIGHT (index 1) + RI_TIME_DAY_2, // HALF_DAY2_DAY (index 2) + RI_TIME_NIGHT_2, // HALF_DAY2_NIGHT (index 3) + RI_TIME_DAY_3, // HALF_DAY3_DAY (index 4) + RI_TIME_NIGHT_3, // HALF_DAY3_NIGHT (index 5) + }; + + return clockItemMap[halfDayIndex]; +} + +u8 GetAllOwnedHalfDaysMask() { + u8 ownedMask = 0; + for (int i = 0; i < HALF_COUNT; ++i) { + if (OwnsClockHalfDay(i)) { + ownedMask |= (1 << i); + } + } + return ownedMask; +} + +int FindEarliestOwnedHalfDay(bool searchFromEnd) { + if (searchFromEnd) { + for (int i = HALF_COUNT - 1; i >= 0; --i) { + if (OwnsClockHalfDay(i)) + return i; + } + } else { + for (int i = 0; i < HALF_COUNT; ++i) { + if (OwnsClockHalfDay(i)) + return i; + } + } + return INVALID; +} + +int FindNextOwnedHalfDayAfter(int startHalfDay, u8 ownedMask) { + if (startHalfDay < 0 || startHalfDay >= HALF_COUNT) { + return TERMINAL_STATE; // Invalid input, go to terminal state + } + + // Search for the next owned half-day after the start point + for (int halfDayIndex = startHalfDay + 1; halfDayIndex < HALF_COUNT; ++halfDayIndex) { + if (ownedMask & (1 << halfDayIndex)) { + return halfDayIndex; + } + } + + return TERMINAL_STATE; // No owned half-days found after start point +} + +// Check if a rando item is a clock item +bool IsClockItem(RandoItemId itemId) { + return (itemId >= RI_TIME_DAY_1 && itemId <= RI_TIME_NIGHT_3) || itemId == RI_TIME_PROGRESSIVE; +} + +// Check if a clock item is a day clock (vs night clock) +bool IsDayClock(RandoItemId itemId) { + return itemId == RI_TIME_DAY_1 || itemId == RI_TIME_DAY_2 || itemId == RI_TIME_DAY_3; +} + +} // namespace ClockItems + +namespace ClockShuffle { + +// ============================================================================ +// INTERNAL TYPES AND DATA +// ============================================================================ + +// Configuration for each half-day's timing +struct HalfDayTimeConfig { + u8 dayNumber; // Which day (1, 2, or 3) + u16 startTime; // When this half-day begins (6:00 AM or 6:00 PM) + u16 endTime; // When this half-day ends (5:59 AM or 5:59 PM) +}; + +// ============================================================================ +// TIME CONFIGURATION DATA +// ============================================================================ + +constexpr u16 DAWN_TIME = CLOCK_TIME(6, 0); // 6:00 AM - start of day +constexpr u16 DUSK_TIME = CLOCK_TIME(18, 0); // 6:00 PM - start of night +constexpr u16 DAWN_END_TIME = CLOCK_TIME(5, 59); // 5:59 AM - end of night +constexpr u16 DUSK_END_TIME = CLOCK_TIME(17, 59); // 5:59 PM - end of day +constexpr u16 DAY_0_0559_TIME = CLOCK_TIME(6, 0) - 1; // Day 0, 5:59 AM - Cycle Reset Time +// Vanilla uses CLOCK_TIME(6, 0) - 1 = 16383, NOT CLOCK_TIME(5, 59) = 16338 for cycle resets +// This 45-unit difference has to be accounted for. + +// ============================================================================ +// TERMINAL TIME HELPER FUNCTIONS +// ============================================================================ + +// Convert slider minutes (0-359) to CLOCK_TIME format +u16 MinutesToClockTime(int minutes) { + return CLOCK_TIME(0, minutes); // 00:00 + minutes +} + +// Get the configured terminal state time from the saved rando option +u16 GetConfiguredTerminalTime() { + int terminalMinutes = RANDO_SAVE_OPTIONS[RO_CLOCK_TERMINAL_TIME]; + return MinutesToClockTime(terminalMinutes); +} + +// Configuration for each half-day's timing and behavior +constexpr HalfDayTimeConfig HALF_DAY_CONFIGS[] = { + /* HALF_DAY1_DAY */ { 1, DAWN_TIME, DUSK_END_TIME }, // Day 1: 6:00 AM - 5:59 PM + /* HALF_DAY1_NIGHT */ { 1, DUSK_TIME, DAWN_END_TIME }, // Day 1: 6:00 PM - 5:59 AM + /* HALF_DAY2_DAY */ { 2, DAWN_TIME, DUSK_END_TIME }, // Day 2: 6:00 AM - 5:59 PM + /* HALF_DAY2_NIGHT */ { 2, DUSK_TIME, DAWN_END_TIME }, // Day 2: 6:00 PM - 5:59 AM + /* HALF_DAY3_DAY */ { 3, DAWN_TIME, DUSK_END_TIME }, // Day 3: 6:00 AM - 5:59 PM + /* HALF_DAY3_NIGHT */ { 3, DUSK_TIME, DAWN_END_TIME }, // Day 3: 6:00 PM - 5:59 AM +}; + +// ============================================================================ +// TIME DETECTION AND CONFIGURATION +// ============================================================================ + +const HalfDayTimeConfig* GetHalfDayTimeConfig(int halfDayIndex) { + if (halfDayIndex < 0 || halfDayIndex >= ClockItems::HALF_COUNT) { + return nullptr; + } + + return &HALF_DAY_CONFIGS[halfDayIndex]; +} + +bool IsCurrentlyNightTime(u16 gameTime) { + return (gameTime >= DUSK_TIME) || (gameTime < DAWN_TIME); +} + +// Check if time value represents night (inline for performance) +inline bool IsNightTime(u16 time) { + return (time < GAME_TIME_DAY_START) || (time >= GAME_TIME_NIGHT_START); +} + +// Check if a given day/time is in the configured terminal state range +bool IsInTerminalRange(s32 day, u16 time) { + if (day != 3) { + return false; + } + + u16 terminalTime = GetConfiguredTerminalTime(); + + // Terminal range spans from terminal time until 6:00 AM (may wrap around midnight) + if (terminalTime >= DUSK_TIME) { + // Terminal starts in evening (18:00-23:59), range wraps through midnight to dawn + return (time >= terminalTime) || (time < DAWN_TIME); + } else { + // Terminal starts after midnight (0:00-5:59), range goes until dawn + return (time >= terminalTime && time < DAWN_TIME); + } +} + +// Calculate half-day index from day/time (extracted for reuse) +int GetHalfDayIndexFromTime(s32 day, u16 time) { + if (day < 1) + return 0; + int halfDay = (day - 1) * 2; + if (IsNightTime(time)) + halfDay++; + return halfDay; +} + +int GetCurrentHalfDayIndex() { + const u16 currentTime = gSaveContext.save.time; + const s32 currentDay = gSaveContext.save.day; + + // Handle moon crash/game over sequence: Day 4 or higher means the moon has fallen and the game is ending. + if (currentDay >= 4) { + return ClockItems::TERMINAL_STATE; // Use TERMINAL_STATE as a sentinel for post-crash state. + } + + // The game uses Day 0 as a transition before placing the player at the correct half-day. + if (currentDay == 0) { + return ClockItems::TERMINAL_STATE; // Signal: handle Day 0 as a reset/redirect. + } + + // This is what ClockShuffle treats as a "buffer" period before the moon crash. + // The buffer length is now configurable via RO_CLOCK_TERMINAL_TIME. + if (IsInTerminalRange(currentDay, currentTime)) { + return ClockItems::TERMINAL_STATE; + } + + const bool isNight = IsCurrentlyNightTime(currentTime); + + // Figure out which half-day we're in: + // - Each day has two halves: day (even indices) and night (odd indices) + // - Day 1's day is index 0, night is 1; Day 2's day is 2, night is 3, etc. + // - So: (currentDay - 1) * 2 gives us the starting index for that day (0 for Day 1, 2 for Day 2, 4 for Day 3) + // - If it's night, add 1; if it's day, add 0. + // Example: Day 2 night → (2-1)*2 + 1 = 2 + 1 = 3 + return (currentDay - 1) * 2 + (isNight ? 1 : 0); +} + +// ============================================================================ +// TIME MANIPULATION FUNCTIONS +// ============================================================================ + +// Apply a new time to the game, updating all related state +void SetGameTime(u8 day, u16 time) { + gSaveContext.save.day = day; + gSaveContext.save.time = time; + gSaveContext.save.isNight = IsCurrentlyNightTime(time); + gSaveContext.save.eventDayCount = day; +} + +// Set time to half-day start with proper music handling +void SetTimeToHalfDayStart(int halfDayIndex) { + // Don't try to set time for terminal state + if (halfDayIndex == ClockItems::TERMINAL_STATE) { + return; + } + + // Get the configuration for this half-day + const HalfDayTimeConfig* config = GetHalfDayTimeConfig(halfDayIndex); + if (!config) { + return; + } + + // Set time to the start of this half-day + SetGameTime(config->dayNumber, config->startTime); + + // Reset gSceneSeqState to prevent morning sequence from playing for night halves + // This is needed because DayTelop or previous transitions may have set it to SCENESEQ_MORNING + if (IsCurrentlyNightTime(config->startTime)) { + gSceneSeqState = SCENESEQ_DEFAULT; + } +} + +// Check if a scene needs to be reloaded for time skips (whitelist approach) +bool SceneNeedsReloadForTimeSkip(s16 sceneId) { + // Use a whitelist approach - only reload scenes that actually need it + // These scenes are extremely time-sensitive and require reload + switch (sceneId) { + case SCENE_BOWLING: // Honey & Darling - minigame mode changes based on day/time + return true; + case SCENE_CLOCKTOWER: // South Clock Town - Clock Tower platform appears at midnight Night 3 + return true; + default: + return false; // Most scenes handle time changes without reload + } +} + +// Force a scene transition to reload the current area +void ForceSceneReload() { + Player* player = GET_PLAYER(gPlayState); + + // Set up the transition parameters + gPlayState->nextEntrance = gSaveContext.save.entrance; + gPlayState->transitionTrigger = TRANS_TRIGGER_START; + gPlayState->transitionType = TRANS_TYPE_FADE_BLACK_FAST; + + // Set up respawn data to return to the same location + Play_SetRespawnData(gPlayState, RESPAWN_MODE_RETURN, gSaveContext.save.entrance, gPlayState->roomCtx.curRoom.num, + PLAYER_PARAMS(0xFF, PLAYER_START_MODE_B), &player->actor.world.pos, player->actor.world.rot.y); + + // Configure the transition + gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK; + gSaveContext.respawnFlag = 2; +} + +// ============================================================================ +// PROACTIVE TIME CHECKING +// ============================================================================ +// This is separated into two functions for clarity: +// - ShouldSkipTime: Pure decision logic to determine if a skip is needed +// - CheckAndSkipUnownedTime: Application logic that performs the time skip +// +// Key implementation patterns: +// - Lookahead calculation (current time + 1 minute) +// - Inline night check (time < GAME_TIME_DAY_START || time >= GAME_TIME_NIGHT_START) +// - Day transition detection via prevTime +// - Direct time/day modification followed by day-- trick for dawn +// - prevTime = newTime - 1 minute +// +// Additional features: +// - Terminal state: If no owned clocks, jump to configured terminal time (not Day 4) +// - RandoInf flags for tracking owned half-days +// - GameInteractor hooks for seamless integration + +// Decision function: Determines if we should skip time and calculates target half-day +// Returns true if skip is needed, false otherwise +bool ShouldSkipTime(s32 day, u16 time, int* outNextHalfDay) { + // Early exits + if (gPlayState->envCtx.sceneTimeSpeed == 0 || Play_InCsMode(gPlayState) || day >= 4) { + return false; + } + + // Check terminal range + if (IsInTerminalRange(day, time)) { + return false; + } + + // Calculate and check current half-day ownership + int currentHalfDay = GetHalfDayIndexFromTime(day, time); + if (currentHalfDay >= 0 && currentHalfDay < ClockItems::HALF_COUNT && OwnsClockHalfDay(currentHalfDay)) { + return false; + } + + // Find next owned half-day + int nextHalfDay = ClockItems::TERMINAL_STATE; + for (int i = currentHalfDay + 1; i < ClockItems::HALF_COUNT; ++i) { + if (OwnsClockHalfDay(i)) { + nextHalfDay = i; + break; + } + } + + *outNextHalfDay = nextHalfDay; + return true; +} + +// Apply time skip to target half-day (extracted helper) +void ApplyTimeSkip(int nextHalfDay, EnTest4* enTest4) { + s32 day = (nextHalfDay / 2) + 1; + u16 time = (nextHalfDay & 1) ? GAME_TIME_NIGHT_START : GAME_TIME_DAY_START; + + // Terminal state override + if (nextHalfDay == ClockItems::TERMINAL_STATE) { + day = 3; + time = GetConfiguredTerminalTime(); + } + + // Apply time change + gSaveContext.save.day = day; + gSaveContext.save.time = time; + + // Update actor state + enTest4->daytimeIndex = IsCurrentlyNightTime(time) ? 1 : 0; + + // Handle scene reload for time-sensitive scenes + if (SceneNeedsReloadForTimeSkip(gPlayState->sceneId)) { + ForceSceneReload(); + enTest4->prevTime = time - CLOCK_TIME(0, 1); + return; + } + + // Terminal state handling + if (nextHalfDay == ClockItems::TERMINAL_STATE) { + enTest4->prevTime = time - CLOCK_TIME(0, 1); + return; + } + + // Day transition handling + if (time == GAME_TIME_DAY_START) { + gSaveContext.save.day--; + } else { + Interface_NewDay(gPlayState, gSaveContext.save.day); + Environment_NewDay(&gPlayState->envCtx); + } + + enTest4->prevTime = time - CLOCK_TIME(0, 1); +} + +// Application function: Applies the time skip +// Check if time is about to cross into an unowned half-day and skip forward if needed +void CheckAndSkipUnownedTime(Actor* timeActor) { + // Get EnTest4 actor for state access + EnTest4* enTest4 = (EnTest4*)timeActor; + + // Skip if eventInf bit 0x0f is set (dog race) + if (gSaveContext.eventInf[0] & 0x0F) { + return; + } + + // Calculate lookahead day/time + s32 day = gSaveContext.save.day; + u16 time = gSaveContext.save.time + CLOCK_TIME(0, 1); + + // Check for day transition using actor's prevTime + // Night check: time < GAME_TIME_DAY_START || time >= GAME_TIME_NIGHT_START + bool prevWasNight = IsNightTime(enTest4->prevTime); + bool nextIsNight = IsNightTime(time); + if (prevWasNight && !nextIsNight) { + day++; + } + + if (day >= 4) { + return; // Don't process moon crash + } + + // Check if we should skip time (decision logic) + int nextHalfDay; + if (!ShouldSkipTime(day, time, &nextHalfDay)) { + return; // No skip needed + } + + // Apply the time skip using extracted helper + ApplyTimeSkip(nextHalfDay, enTest4); +} + +// ============================================================================ +// PUBLIC API +// ============================================================================ + +// Check if a specific day/time is owned by the player in ClockShuffle mode +bool IsTimeOwnedForClockShuffle(s32 day, u16 time) { + if (!RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE]) { + return true; + } + + if (day < 1 || day > 3) + return true; + if (IsInTerminalRange(day, time)) + return true; + + int halfDayIndex = GetHalfDayIndexFromTime(day, time); + return OwnsClockHalfDay(halfDayIndex); +} + +// Get a formatted description of a time period for error messages +std::string GetTimeDescriptionForMessage(s32 day, u16 time) { + // Handle terminal state + if (IsInTerminalRange(day, time)) { + return "%rFinal Hours%w"; + } + + // Determine if this is night time + bool isNight = IsCurrentlyNightTime(time); + + std::string description; + if (isNight) { + description = "%rNight of the "; + } else { + description = "%rDawn of the "; + } + + // Add day ordinal + if (day == 1) { + description += "First"; + } else if (day == 2) { + description += "Second"; + } else if (day == 3) { + description += "Third"; + } else { + description += "Unknown"; + } + + description += " Day%w"; + return description; +} + +// Helper for initial file load time correction +void CorrectInitialTime() { + const int earliestOwnedHalfDay = ClockItems::FindEarliestOwnedHalfDay(false); + if (earliestOwnedHalfDay == ClockItems::INVALID) + return; + + bool isDayHalf = (earliestOwnedHalfDay % 2 == 0); + int targetDay = (earliestOwnedHalfDay / 2) + 1; + + if (isDayHalf) { + SetGameTime(targetDay - 1, CLOCK_TIME(6, 0) - 1); + } else { + SetTimeToHalfDayStart(earliestOwnedHalfDay); + } +} + +void ProcessClockShuffleMessage(u16* textId, bool* loadFromMessageTable, bool isSongOfTime) { + auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + + // Determine target half-day based on which song is used + int targetHalfDay; + if (isSongOfTime) { + // Song of Time: go to earliest owned half-day + targetHalfDay = ClockItems::FindEarliestOwnedHalfDay(false); + } else { + // Song of Double Time: go to next owned half-day after current + int currentHalfDay = GetCurrentHalfDayIndex(); + u8 ownedHalfDaysMask = ClockItems::GetAllOwnedHalfDaysMask(); + targetHalfDay = ClockItems::FindNextOwnedHalfDayAfter(currentHalfDay, ownedHalfDaysMask); + } + + std::string destinationText; + if (targetHalfDay == ClockItems::TERMINAL_STATE) { + destinationText = "%rFinal Hours%w"; + } else { + // Convert half-day index to readable text + int targetDay = (targetHalfDay / 2) + 1; + bool isNight = (targetHalfDay % 2 == 1); + + if (isNight) { + destinationText = "%rNight of "; + if (targetDay == 1) + destinationText += "First"; + else if (targetDay == 2) + destinationText += "Second"; + else if (targetDay == 3) + destinationText += "Third"; + destinationText += " Day%w"; + } else { + destinationText = "%rDawn of the "; + if (targetDay == 1) + destinationText += "First"; + else if (targetDay == 2) + destinationText += "Second"; + else if (targetDay == 3) + destinationText += "Third"; + destinationText += " Day%w"; + } + } + + // Use different message format for each song + if (isSongOfTime) { + entry.msg = "Save and return to " + destinationText + "?\n%gYes\nNo\xC2"; + } else { // Song of Double Time + entry.msg = "Time moves strangely...\nProceed to " + destinationText + "?\n%gYes\nNo\xC2"; + } + + CustomMessage::LoadCustomMessageIntoFont(entry); + *loadFromMessageTable = false; +} + +void OnFileLoad() { + bool shouldRegister = IS_RANDO && RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE]; + + // Correct Day 0 time on file load BEFORE scene initialization + // OnSaveLoad fires before Play_Init, ensuring time is correct before Environment_PlaySceneSequence processes audio + // This prevents bird chirps from playing when correcting to night half-days on initial spawn + if (shouldRegister) { + // Check if this is initial spawn (day=0) and needs correction + if (!gSaveContext.save.isOwlSave && (gSaveContext.save.day == 0 && gSaveContext.save.time == DAY_0_0559_TIME)) { + CorrectInitialTime(); + } + } + + // Hook EnTest4 BEFORE vanilla update to proactively check for time skips + // This is critical: we must modify time BEFORE vanilla processes it! + COND_ID_HOOK(ShouldActorUpdate, ACTOR_EN_TEST4, shouldRegister, [](Actor* actor, bool* should) { + CheckAndSkipUnownedTime(actor); + *should = true; // Always let vanilla continue with our modified time + }); + + // Hook Song of Time and Song of Double Time message IDs + COND_ID_HOOK(OnOpenText, 0x1B8A, shouldRegister, [](u16* textId, bool* loadFromMessageTable) { + ProcessClockShuffleMessage(textId, loadFromMessageTable, true); + }); + + auto onDoubleTime = [](u16* textId, bool* loadFromMessageTable) { + ProcessClockShuffleMessage(textId, loadFromMessageTable, false); + }; + + COND_ID_HOOK(OnOpenText, 0x1B91, shouldRegister, onDoubleTime); + COND_ID_HOOK(OnOpenText, 0x1B90, shouldRegister, onDoubleTime); + COND_ID_HOOK(OnOpenText, 0x1B8F, shouldRegister, onDoubleTime); + COND_ID_HOOK(OnOpenText, 0x1B92, shouldRegister, onDoubleTime); + COND_ID_HOOK(OnOpenText, 0x1B8E, shouldRegister, onDoubleTime); + + COND_VB_SHOULD(VB_TIME_UNTIL_MOON_CRASH_CALCULATION, shouldRegister, { + *should = false; // Skip vanilla calculation + + // Get the time variable that was passed + u32* timeVar = va_arg(args, u32*); + + // Calculate owned time remaining + u8 ownedHalfDaysMask = ClockItems::GetAllOwnedHalfDaysMask(); + u32 totalHours = 0; + + for (int halfDayIndex = 0; halfDayIndex < ClockItems::HALF_COUNT; ++halfDayIndex) { + if (ownedHalfDaysMask & (1 << halfDayIndex)) { + totalHours += 12; // Each half-day is 12 hours + } + } + + // Add final hours based on configured terminal time if we're in terminal state + // OR if we don't own Night 3 (which means we'll end up in terminal state) + bool shouldIncludeTerminalHours = + (GetCurrentHalfDayIndex() == ClockItems::TERMINAL_STATE) || !OwnsClockHalfDay(ClockItems::HALF_DAY3_NIGHT); + + if (shouldIncludeTerminalHours) { + // Calculate remaining hours from configured terminal time to 6:00 AM + u16 terminalTime = GetConfiguredTerminalTime(); + + // Calculate time difference (terminal time to dawn) + u32 terminalZeroed = ZERO_DAY_START(terminalTime); + u32 timeDiff = (DAY_LENGTH - terminalZeroed) % DAY_LENGTH; + + // Convert to hours (round up to ensure we don't under-calculate) + u32 terminalHours = (timeDiff + CLOCK_TIME_HOUR - 1) / CLOCK_TIME_HOUR; + totalHours += terminalHours; + } + + u32 ownedTime = totalHours * CLOCK_TIME_HOUR; + + // Compensate for the -1 minute offset that vanilla uses + // Vanilla uses CLOCK_TIME(6, 0) - 1, Clock Shuffle uses CLOCK_TIME(6, 0) + // So we need to add 1 minute to match vanilla behavior + ownedTime += CLOCK_TIME_MINUTE; + + // Set the time variable to our calculated value + *timeVar = ownedTime; + }); + + // Hook scarecrow dance time skip to redirect to next owned half-day + COND_VB_SHOULD(VB_SCARECROW_DANCE_SET_TIME, shouldRegister, { + *should = false; // Skip vanilla behavior + + // Calculate next owned half-day after current + int currentHalfDay = GetCurrentHalfDayIndex(); + u8 ownedHalfDaysMask = ClockItems::GetAllOwnedHalfDaysMask(); + int nextHalfDay = ClockItems::FindNextOwnedHalfDayAfter(currentHalfDay, ownedHalfDaysMask); + + if (nextHalfDay == ClockItems::TERMINAL_STATE) { + // Jump to terminal time + gSaveContext.save.day = 3; + gSaveContext.save.time = GetConfiguredTerminalTime(); + gSaveContext.respawnFlag = -8; // No daytelop for terminal + } else { + // Get target half-day configuration + const HalfDayTimeConfig* config = GetHalfDayTimeConfig(nextHalfDay); + bool isNightHalf = (nextHalfDay % 2 == 1); + s32 targetDay = config->dayNumber; + u16 targetTime = config->startTime; + + if (isNightHalf) { + // Advancing to night - use respawnFlag -8 (no daytelop) + gSaveContext.save.day = targetDay; + gSaveContext.save.time = targetTime; + gSaveContext.respawnFlag = -8; + } else { + // Advancing to dawn - use respawnFlag -4 (triggers daytelop) + // CRITICAL: Subtract 1 from day because daytelop will increment it + gSaveContext.save.day = targetDay - 1; + gSaveContext.save.time = targetTime; + gSaveContext.respawnFlag = -4; + SET_EVENTINF(EVENTINF_TRIGGER_DAYTELOP); + } + } + }); +} + +// Initialize clock settings and item pool for file creation +void InitializeFileClocks(std::vector& itemPool) { + if (!RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE]) { + return; // Skip if clocks not enabled + } + + int clockMode = RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE_PROGRESSIVE]; + + // Check if player has selected any starting time items + std::vector startingItems = + convertStartingItemsToRandoItemId(CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT), ","); + std::vector startingClockHalves; + + for (RandoItemId item : startingItems) { + if (ClockItems::IsClockItem(item)) { + int halfDayIndex = ClockItems::GetHalfDayIndexFromClockItem(item); + if (halfDayIndex != ClockItems::INVALID) { + startingClockHalves.push_back(halfDayIndex); + } + } + } + + // If player selected starting clocks, use those instead of random/progressive logic + if (!startingClockHalves.empty()) { + // Grant all selected starting time + for (int halfDayIndex : startingClockHalves) { + Flags_SetRandoInf(static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + halfDayIndex)); + } + + // Add remaining (non-starting) time items to pool + // Progressive mode items are added in the else block of this conditional + if (clockMode == RO_CLOCK_SHUFFLE_RANDOM) { + for (int i = 0; i < 6; ++i) { + // Skip if this clock was a starting item + if (std::find(startingClockHalves.begin(), startingClockHalves.end(), i) != startingClockHalves.end()) { + continue; + } + + RandoItemId clockItem = ClockItems::GetClockItemFromHalfDayIndex(i); + if (clockItem != RI_UNKNOWN) + itemPool.push_back(clockItem); + } + } + } else { + // No starting time selected - use default logic + int initialClockHalf; + + if (clockMode == RO_CLOCK_SHUFFLE_RANDOM) { + // Grant one random half-day + initialClockHalf = Ship_Random(0, 6); // 0..5 map to D1..N3 + } else { + // Progressive modes: grant first half-day in sequence + initialClockHalf = (clockMode == RO_CLOCK_SHUFFLE_ASCENDING) ? 0 : 5; + } + + // Own the selected half + Flags_SetRandoInf(static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + initialClockHalf)); + + if (clockMode == RO_CLOCK_SHUFFLE_RANDOM) { + // Add remaining 5 individual time items to pool + for (int i = 0; i < 6; ++i) { + if (i == initialClockHalf) + continue; + RandoItemId clockItem = ClockItems::GetClockItemFromHalfDayIndex(i); + if (clockItem != RI_UNKNOWN) + itemPool.push_back(clockItem); + } + } else { + // Add 5 progressive time items to pool (6 total - 1 granted = 5 remaining) + for (int i = 0; i < 5; ++i) + itemPool.push_back(RI_TIME_PROGRESSIVE); + } + } +} + +} // namespace ClockShuffle +} // namespace Rando diff --git a/mm/2s2h/Rando/MiscBehavior/ClockShuffle.h b/mm/2s2h/Rando/MiscBehavior/ClockShuffle.h new file mode 100644 index 0000000000..ea363b4bdb --- /dev/null +++ b/mm/2s2h/Rando/MiscBehavior/ClockShuffle.h @@ -0,0 +1,51 @@ +#ifndef RANDO_CLOCK_SHUFFLE_H +#define RANDO_CLOCK_SHUFFLE_H + +#include "Rando/Types.h" +#include +#include + +extern "C" { +#include "variables.h" +#include "functions.h" +} + +namespace Rando { +namespace ClockItems { + +// Internal half-day indices +enum ClockHalfIndex : int { + INVALID = -1, // Invalid/not found/uninitialized half-day index + HALF_DAY1_DAY = 0, // Day 1, 6:00 AM - 5:59 PM + HALF_DAY1_NIGHT = 1, // Day 1, 6:00 PM - 5:59 AM + HALF_DAY2_DAY = 2, // Day 2, 6:00 AM - 5:59 PM + HALF_DAY2_NIGHT = 3, // Day 2, 6:00 PM - 5:59 AM + HALF_DAY3_DAY = 4, // Day 3, 6:00 AM - 5:59 PM + HALF_DAY3_NIGHT = 5, // Day 3, 6:00 PM - 5:59 AM + TERMINAL_STATE = 6, // Terminal state (fallback for invalid/end states) + HALF_COUNT = 6, // Total number of regular half-days (0-5) +}; + +RandoItemId GetClockItemFromHalfDayIndex(int halfDayIndex); +int GetHalfDayIndexFromClockItem(RandoItemId clockItemId); +int FindEarliestOwnedHalfDay(bool searchFromEnd = false); +u8 GetAllOwnedHalfDaysMask(); +bool IsClockItem(RandoItemId itemId); +bool IsDayClock(RandoItemId itemId); + +} // namespace ClockItems + +namespace ClockShuffle { + +void InitializeFileClocks(std::vector& itemPool); +void OnFileLoad(); +void SetTimeToHalfDayStart(int halfDayIndex); + +bool IsTimeOwnedForClockShuffle(s32 day, u16 time); +int GetHalfDayIndexFromTime(s32 day, u16 time); +std::string GetTimeDescriptionForMessage(s32 day, u16 time); + +} // namespace ClockShuffle +} // namespace Rando + +#endif // RANDO_CLOCK_SHUFFLE_H diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index 63ad54f952..18b9e7cdbd 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -3,6 +3,7 @@ #include "Rando/Logic/Logic.h" #include "2s2h/ShipUtils.h" #include +#include "ClockShuffle.h" #include extern "C" { diff --git a/mm/2s2h/Rando/MiscBehavior/Traps.cpp b/mm/2s2h/Rando/MiscBehavior/Traps.cpp index 6a7528dc27..27ade031fb 100644 --- a/mm/2s2h/Rando/MiscBehavior/Traps.cpp +++ b/mm/2s2h/Rando/MiscBehavior/Traps.cpp @@ -3,6 +3,7 @@ #include "MiscBehavior.h" #include "Rando/ActorBehavior/ActorBehavior.h" #include "2s2h/DeveloperTools/SaveEditor.h" +#include "2s2h/ShipUtils.h" extern "C" { #include "variables.h" @@ -13,11 +14,6 @@ void func_80833B18(PlayState* play, Player* thisx, s32 arg2, f32 speed, f32 velo void EnTimeTag_KickOut_Transition(EnTimeTag* enTimeTag, PlayState* play); } -#define MORNING_TIME 0x4000 -#define DAY_LENGTH 0x10000 -// Adjust so that 6 A.M. is 0 and 5:59 A.M. is 0xFFFF -#define ZERO_DAY_START(time) (((u16)(time - MORNING_TIME) % DAY_LENGTH)) - extern void UpdateGameTime(u16 gameTime); int roll = TRAP_FREEZE; diff --git a/mm/2s2h/Rando/Rando.cpp b/mm/2s2h/Rando/Rando.cpp index 4140756e49..09cd90cbc8 100644 --- a/mm/2s2h/Rando/Rando.cpp +++ b/mm/2s2h/Rando/Rando.cpp @@ -2,6 +2,7 @@ #include "2s2h/GameInteractor/GameInteractor.h" #include "Rando/ActorBehavior/ActorBehavior.h" #include "Rando/MiscBehavior/MiscBehavior.h" +#include "Rando/MiscBehavior/ClockShuffle.h" #include "Rando/Spoiler/Spoiler.h" #include "Rando/CheckTracker/CheckTracker.h" #include "2s2h/ShipInit.hpp" @@ -13,6 +14,7 @@ void OnSaveLoadHandler(s16 fileNum) { Rando::MiscBehavior::OnFileLoad(); Rando::ActorBehavior::OnFileLoad(); Rando::CheckTracker::OnFileLoad(); + Rando::ClockShuffle::OnFileLoad(); // Re-initalizes enhancements that are effected by the save being rando or not ShipInit::Init("IS_RANDO"); diff --git a/mm/2s2h/Rando/RemoveItem.cpp b/mm/2s2h/Rando/RemoveItem.cpp index 6a3c79341b..3ecfe33318 100644 --- a/mm/2s2h/Rando/RemoveItem.cpp +++ b/mm/2s2h/Rando/RemoveItem.cpp @@ -1,4 +1,5 @@ #include "Rando/Rando.h" +#include "Rando/MiscBehavior/ClockShuffle.h" extern "C" { #include "variables.h" @@ -282,6 +283,29 @@ void Rando::RemoveItem(RandoItemId randoItemId) { case RI_TINGLE_MAP_STONE_TOWER: CLEAR_WEEKEVENTREG(WEEKEVENTREG_TINGLE_MAP_BOUGHT_STONE_TOWER); break; + case RI_TIME_DAY_1: + case RI_TIME_NIGHT_1: + case RI_TIME_DAY_2: + case RI_TIME_NIGHT_2: + case RI_TIME_DAY_3: + case RI_TIME_NIGHT_3: { + int index = Rando::ClockItems::GetHalfDayIndexFromClockItem(randoItemId); + if (index != Rando::ClockItems::INVALID) { + Flags_ClearRandoInf(static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + index)); + } + break; + } + case RI_TIME_PROGRESSIVE: { + // Remove most recently earned half-day per current mode + const bool descending = (RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE_PROGRESSIVE] == RO_CLOCK_SHUFFLE_DESCENDING); + // For ascending mode, remove the latest (search from end) + // For descending mode, remove the earliest (search from front) + int toRemove = Rando::ClockItems::FindEarliestOwnedHalfDay(!descending); + if (toRemove >= 0) { + Flags_ClearRandoInf(static_cast(RANDO_INF_OBTAINED_CLOCK_DAY_1 + toRemove)); + } + break; + } case RI_HEART_CONTAINER: gSaveContext.save.saveInfo.playerData.healthCapacity -= 0x10; gSaveContext.save.saveInfo.playerData.health = diff --git a/mm/2s2h/Rando/StaticData/Items.cpp b/mm/2s2h/Rando/StaticData/Items.cpp index 8afe1c06d1..4676d34351 100644 --- a/mm/2s2h/Rando/StaticData/Items.cpp +++ b/mm/2s2h/Rando/StaticData/Items.cpp @@ -181,6 +181,13 @@ std::map Items = { RI(RI_STONE_TOWER_MAP, "the", "Stone Tower Map", RITYPE_LESSER, ITEM_DUNGEON_MAP, GI_MAP, GID_DUNGEON_MAP), RI(RI_STONE_TOWER_SMALL_KEY, "a", "Stone Tower Small Key", RITYPE_SMALL_KEY, ITEM_KEY_SMALL, GI_KEY_SMALL, GID_KEY_SMALL), RI(RI_STONE_TOWER_STRAY_FAIRY, "a", "Stone Tower Stray Fairy", RITYPE_STRAY_FAIRY, ITEM_STRAY_FAIRIES, GI_STRAY_FAIRY, GID_NONE), + RI(RI_TIME_DAY_1, "", "Time (Day 1)", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_TIME_DAY_2, "", "Time (Day 2)", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_TIME_DAY_3, "", "Time (Day 3)", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_TIME_NIGHT_1, "", "Time (Night 1)", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_TIME_NIGHT_2, "", "Time (Night 2)", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_TIME_NIGHT_3, "", "Time (Night 3)", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_TIME_PROGRESSIVE, "", "Progressive Time", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), RI(RI_SWORD_GILDED, "the", "Gilded Sword", RITYPE_LESSER, ITEM_SWORD_GILDED, GI_SWORD_GILDED, GID_SWORD_GILDED), RI(RI_SWORD_KOKIRI, "the", "Kokiri Sword", RITYPE_MAJOR, ITEM_SWORD_KOKIRI, GI_SWORD_KOKIRI, GID_SWORD_KOKIRI), RI(RI_SWORD_RAZOR, "the", "Razor Sword", RITYPE_LESSER, ITEM_SWORD_RAZOR, GI_SWORD_RAZOR, GID_SWORD_RAZOR), @@ -228,7 +235,8 @@ std::unordered_map> StartingItems } }, { STARTING_ITEMS_MISC, { RI_SOUL_GOHT, RI_SOUL_GYORG, RI_SOUL_MAJORA, RI_SOUL_ODOLWA, RI_SOUL_TWINMOLD, - RI_FROG_BLUE, RI_FROG_CYAN, RI_FROG_PINK, RI_FROG_WHITE + RI_FROG_BLUE, RI_FROG_CYAN, RI_FROG_PINK, RI_FROG_WHITE, + RI_TIME_DAY_1, RI_TIME_DAY_2, RI_TIME_DAY_3, RI_TIME_NIGHT_1, RI_TIME_NIGHT_2, RI_TIME_NIGHT_3 } }, }; // clang-format on @@ -356,6 +364,16 @@ const char* GetIconTexturePath(RandoItemId randoItemId) { return (const char*)gItemIconTingleMapTex; case RI_TRIFORCE_PIECE: return (const char*)gTriforcePieceTex; + case RI_TIME_DAY_1: + case RI_TIME_DAY_2: + case RI_TIME_DAY_3: + return (const char*)gThreeDayClockSunHourTex; + case RI_TIME_NIGHT_1: + case RI_TIME_NIGHT_2: + case RI_TIME_NIGHT_3: + return (const char*)gThreeDayClockMoonHourTex; + case RI_TIME_PROGRESSIVE: + return (const char*)gThreeDayClockSunHourTex; default: break; } diff --git a/mm/2s2h/Rando/StaticData/Options.cpp b/mm/2s2h/Rando/StaticData/Options.cpp index eb05930b0f..ed15501661 100644 --- a/mm/2s2h/Rando/StaticData/Options.cpp +++ b/mm/2s2h/Rando/StaticData/Options.cpp @@ -58,6 +58,9 @@ std::map Options = { RO(RO_STARTING_RUPEES, RO_GENERIC_OFF), RO(RO_TRIFORCE_PIECES_MAX, DEFAULT_TRIFORCE_PIECES_MAX), RO(RO_TRIFORCE_PIECES_REQUIRED, DEFAULT_TRIFORCE_PIECES_MAX), + RO(RO_CLOCK_SHUFFLE, RO_GENERIC_OFF), + RO(RO_CLOCK_SHUFFLE_PROGRESSIVE, RO_CLOCK_SHUFFLE_RANDOM), + RO(RO_CLOCK_TERMINAL_TIME, 0), // Default: 00:00 (midnight) }; // clang-format on diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h index 6c640e59e5..a6f6f4719a 100644 --- a/mm/2s2h/Rando/Types.h +++ b/mm/2s2h/Rando/Types.h @@ -2470,6 +2470,13 @@ typedef enum { RI_SWORD_GILDED, RI_SWORD_KOKIRI, RI_SWORD_RAZOR, + RI_TIME_DAY_1, + RI_TIME_DAY_2, + RI_TIME_DAY_3, + RI_TIME_NIGHT_1, + RI_TIME_NIGHT_2, + RI_TIME_NIGHT_3, + RI_TIME_PROGRESSIVE, RI_TINGLE_MAP_CLOCK_TOWN, RI_TINGLE_MAP_GREAT_BAY, RI_TINGLE_MAP_ROMANI_RANCH, @@ -2539,7 +2546,8 @@ typedef enum { RR_FISHERMANS_HUT, RR_GHOST_HUT, RR_GORMAN_TRACK, - RR_GORMAN_TRACK_INNER, + RR_GORMAN_TRACK_BACK, + RR_GORMAN_TRACK_FRONT, RR_GORON_GRAVEYARD, RR_GORON_RACETRACK, RR_GORON_SHOP, @@ -2603,6 +2611,7 @@ typedef enum { RR_MISCELLANEOUS, RR_MILK_BAR, RR_MILK_ROAD, + RR_MILK_ROAD_BEHIND_FENCE, RR_MOON_DEKU_TRIAL, RR_MOON_GORON_TRIAL, RR_MOON_LINK_TRIAL, @@ -2845,6 +2854,9 @@ typedef enum { RO_STARTING_RUPEES, RO_TRIFORCE_PIECES_MAX, RO_TRIFORCE_PIECES_REQUIRED, + RO_CLOCK_SHUFFLE, + RO_CLOCK_SHUFFLE_PROGRESSIVE, + RO_CLOCK_TERMINAL_TIME, RO_MAX, } RandoOptionId; @@ -2880,6 +2892,12 @@ typedef enum { RO_ACCESS_TRIALS_OPEN, } RandoOptionAccessTrials; +typedef enum { + RO_CLOCK_SHUFFLE_RANDOM, + RO_CLOCK_SHUFFLE_ASCENDING, + RO_CLOCK_SHUFFLE_DESCENDING, +} RandoClockShuffleOptions; + typedef enum { RANDO_INF_PURCHASED_BEANS_FROM_SOUTHERN_SWAMP_SCRUB, RANDO_INF_PURCHASED_BOMB_BAG_FROM_GORON_VILLAGE_SCRUB, @@ -2900,6 +2918,12 @@ typedef enum { RANDO_INF_OBTAINED_SOUL_OF_ODOLWA, RANDO_INF_OBTAINED_SOUL_OF_TWINMOLD, RANDO_INF_OBTAINED_SWIM, + RANDO_INF_OBTAINED_CLOCK_DAY_1, + RANDO_INF_OBTAINED_CLOCK_NIGHT_1, + RANDO_INF_OBTAINED_CLOCK_DAY_2, + RANDO_INF_OBTAINED_CLOCK_NIGHT_2, + RANDO_INF_OBTAINED_CLOCK_DAY_3, + RANDO_INF_OBTAINED_CLOCK_NIGHT_3, RANDO_INF_MAX, } RandoInf; @@ -2959,6 +2983,32 @@ typedef enum { RE_ACCESS_PICTOGRAPH_TINGLE, RE_ACCESS_PICTOGRAPH_DEKU_KING, RE_ACCESS_PICTOGRAPH_SWAMP_GENERIC, + RE_MEET_KAFEI, + RE_SETUP_MEET_ANJU, + RE_ANJU_MIDNIGHT_MEETING, + RE_DELIVER_PENDANT, + RE_POSTMAN_FREEDOM, + RE_HONEY_DARLING_REWARD_DAY1, + RE_HONEY_DARLING_REWARD_DAY2, + RE_HONEY_DARLING_REWARD_DAY3, + RE_DEKU_PLAYGROUND_1, + RE_DEKU_PLAYGROUND_2, + RE_DEKU_PLAYGROUND_3, + RE_HIDE_SEEK_DAY1, + RE_HIDE_SEEK_DAY2, + RE_HIDE_SEEK_DAY3, + RE_BOMBERS_NORTH_DAY1, + RE_BOMBERS_NORTH_DAY2, + RE_BOMBERS_NORTH_DAY3, + RE_BOMBERS_WEST_DAY1, + RE_BOMBERS_WEST_DAY2, + RE_BOMBERS_WEST_DAY3, + RE_BOMBERS_EAST_DAY1, + RE_BOMBERS_EAST_DAY2, + RE_BOMBERS_EAST_DAY3, + RE_BOMBER_CODE, + RE_DESTROY_MILK_ROAD_BOULDER, + RE_MAX, } RandoEvent; diff --git a/mm/2s2h/ShipUtils.cpp b/mm/2s2h/ShipUtils.cpp index bb0b7a66dc..fb1c4ea39c 100644 --- a/mm/2s2h/ShipUtils.cpp +++ b/mm/2s2h/ShipUtils.cpp @@ -61,7 +61,7 @@ extern u16 sOwlWarpEntrancesForMods[OWL_WARP_MAX - 1] = { }; // These textures are not in existing lists that we iterate over. -std::array miscellaneousTextures = { +std::array miscellaneousTextures = { gArcheryScoreIconTex, gBarrelTrackerIcon, gChestTrackerIcon, @@ -86,6 +86,8 @@ std::array miscellaneousTextures = { gPauseUnusedCursorTex, gWorldMapOwlFaceTex, gItemIconTingleMapTex, + gThreeDayClockSunHourTex, + gThreeDayClockMoonHourTex, }; std::array digitList = { gCounterDigit0Tex, gCounterDigit1Tex, gCounterDigit2Tex, gCounterDigit3Tex, diff --git a/mm/2s2h/ShipUtils.h b/mm/2s2h/ShipUtils.h index 4845d15fc5..eaa66841af 100644 --- a/mm/2s2h/ShipUtils.h +++ b/mm/2s2h/ShipUtils.h @@ -3,6 +3,13 @@ #include "PR/ultratypes.h" +#include "macros.h" // For CLOCK_TIME and DAY_LENGTH + +// Time utilities for 2s2h enhancements +#define MORNING_TIME 0x4000 // 6:00 AM - start of day +// Normalize time so that 6:00 AM is 0 and 5:59 AM is 0xFFFF (wraps around) +#define ZERO_DAY_START(time) (((u16)((time)-MORNING_TIME) % DAY_LENGTH)) + #include "gbi.h" #ifdef __cplusplus diff --git a/mm/src/code/z_message.c b/mm/src/code/z_message.c index 0be1e96e8c..af383621c4 100644 --- a/mm/src/code/z_message.c +++ b/mm/src/code/z_message.c @@ -2203,7 +2203,9 @@ void Message_LoadTime(PlayState* play, u16 curChar, s32* offset, f32* arg3, s16* f32 timeInMinutes; if (curChar == 0x20F) { - dayTime = TIME_UNTIL_MOON_CRASH; + if (GameInteractor_Should(VB_TIME_UNTIL_MOON_CRASH_CALCULATION, true, &dayTime)) { + dayTime = TIME_UNTIL_MOON_CRASH; + } } else { dayTime = TIME_UNTIL_NEW_DAY; } @@ -2952,7 +2954,9 @@ void Message_Decode(PlayState* play) { decodedBufPos++; msgCtx->decodedBuffer.wchar[decodedBufPos] = 0x2000; } else if (curChar == 0x237) { - timeToMoonCrash = TIME_UNTIL_MOON_CRASH; + if (GameInteractor_Should(VB_TIME_UNTIL_MOON_CRASH_CALCULATION, true, &timeToMoonCrash)) { + timeToMoonCrash = TIME_UNTIL_MOON_CRASH; + } digits[0] = 0; digits[1] = TIME_TO_HOURS_F_ALT(timeToMoonCrash); diff --git a/mm/src/code/z_message_nes.c b/mm/src/code/z_message_nes.c index 79f6e74772..ec8b8d0b74 100644 --- a/mm/src/code/z_message_nes.c +++ b/mm/src/code/z_message_nes.c @@ -204,7 +204,9 @@ void Message_LoadTimeNES(PlayState* play, u8 curChar, s32* offset, f32* arg3, s1 s16 i; if (curChar == MESSAGE_TIME_UNTIL_MOON_CRASH) { - timeLeft = TIME_UNTIL_MOON_CRASH; + if (GameInteractor_Should(VB_TIME_UNTIL_MOON_CRASH_CALCULATION, true, &timeLeft)) { + timeLeft = TIME_UNTIL_MOON_CRASH; + } } else { timeLeft = TIME_UNTIL_NEW_DAY; } @@ -1714,7 +1716,9 @@ void Message_DecodeNES(PlayState* play) { msgCtx->decodedBuffer.schar[decodedBufPos] = 0; } else if (curChar == MESSAGE_HOURS_UNTIL_MOON_CRASH) { - timeToMoonCrash = TIME_UNTIL_MOON_CRASH; + if (GameInteractor_Should(VB_TIME_UNTIL_MOON_CRASH_CALCULATION, true, &timeToMoonCrash)) { + timeToMoonCrash = TIME_UNTIL_MOON_CRASH; + } digits[0] = 0; digits[1] = TIME_TO_HOURS_F_ALT(timeToMoonCrash); diff --git a/mm/src/overlays/actors/ovl_En_Bji_01/z_en_bji_01.c b/mm/src/overlays/actors/ovl_En_Bji_01/z_en_bji_01.c index 0fa2ab27a3..b65a73da1f 100644 --- a/mm/src/overlays/actors/ovl_En_Bji_01/z_en_bji_01.c +++ b/mm/src/overlays/actors/ovl_En_Bji_01/z_en_bji_01.c @@ -5,6 +5,7 @@ */ #include "z_en_bji_01.h" +#include "2s2h/GameInteractor/GameInteractor.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -189,7 +190,10 @@ void func_809CD028(EnBji01* this, PlayState* play) { break; case 3: - timeUntilMoonCrash = TIME_UNTIL_MOON_CRASH; + if (GameInteractor_Should(VB_TIME_UNTIL_MOON_CRASH_CALCULATION, true, + &timeUntilMoonCrash)) { + timeUntilMoonCrash = TIME_UNTIL_MOON_CRASH; + } if (timeUntilMoonCrash < CLOCK_TIME_F(1, 0)) { this->textId = 0x5E8; } else { diff --git a/mm/src/overlays/actors/ovl_En_Kakasi/z_en_kakasi.c b/mm/src/overlays/actors/ovl_En_Kakasi/z_en_kakasi.c index 5a8425a7ee..1927f87add 100644 --- a/mm/src/overlays/actors/ovl_En_Kakasi/z_en_kakasi.c +++ b/mm/src/overlays/actors/ovl_En_Kakasi/z_en_kakasi.c @@ -956,13 +956,15 @@ void EnKakasi_DancingNightAway(EnKakasi* this, PlayState* play) { PLAYER_PARAMS(0xFF, PLAYER_START_MODE_B), &player->unk_3C0, player->unk_3CC); func_80169EFC(play); - if ((CURRENT_TIME > CLOCK_TIME(18, 0)) || (CURRENT_TIME < CLOCK_TIME(6, 0))) { - gSaveContext.save.time = CLOCK_TIME(6, 0); - gSaveContext.respawnFlag = -4; - SET_EVENTINF(EVENTINF_TRIGGER_DAYTELOP); - } else { - gSaveContext.save.time = CLOCK_TIME(18, 0); - gSaveContext.respawnFlag = -8; + if (GameInteractor_Should(VB_SCARECROW_DANCE_SET_TIME, true)) { + if ((CURRENT_TIME > CLOCK_TIME(18, 0)) || (CURRENT_TIME < CLOCK_TIME(6, 0))) { + gSaveContext.save.time = CLOCK_TIME(6, 0); + gSaveContext.respawnFlag = -4; + SET_EVENTINF(EVENTINF_TRIGGER_DAYTELOP); + } else { + gSaveContext.save.time = CLOCK_TIME(18, 0); + gSaveContext.respawnFlag = -8; + } } SET_WEEKEVENTREG(WEEKEVENTREG_83_01); this->unk190 = 0; From 890ae91c332a0863268ed323d6b7e6a24e7d54e0 Mon Sep 17 00:00:00 2001 From: Jordyn Hardyman <42100286+Jameriquiah@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:53:18 -0500 Subject: [PATCH 36/44] Fierce Deity Flipbook Support (#1412) * Fierce Deity Flipbook Support * implemented suggestions z_player_lib.c is no longer touched * Crash fix + Deku and Goron * clang-format failed fix --- mm/2s2h/BenPort.cpp | 2 + .../GfxPatcher/PlayerCustomFlipbooks.cpp | 133 ++++++++++++++++++ .../GfxPatcher/PlayerCustomFlipbooks.h | 3 + 3 files changed, 138 insertions(+) create mode 100644 mm/2s2h/Enhancements/GfxPatcher/PlayerCustomFlipbooks.cpp create mode 100644 mm/2s2h/Enhancements/GfxPatcher/PlayerCustomFlipbooks.h diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index d913c05920..89ee51a4df 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -53,6 +53,7 @@ CrowdControl* CrowdControl::Instance; #include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/Enhancements/Enhancements.h" #include "2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h" +#include "2s2h/Enhancements/GfxPatcher/PlayerCustomFlipbooks.h" #include "2s2h/DeveloperTools/DebugConsole.h" #include "2s2h/Rando/Rando.h" #include "2s2h/Rando/Spoiler/Spoiler.h" @@ -719,6 +720,7 @@ extern "C" void InitOTR() { OTRMessage_Init(); OTRAudio_Init(); OTRExtScanner(); + PlayerCustomFlipbooks_Patch(); // Just came up with arbitrary numbers that seemed to work, this is // usually set once(?) in currently stubbed out areas of code. diff --git a/mm/2s2h/Enhancements/GfxPatcher/PlayerCustomFlipbooks.cpp b/mm/2s2h/Enhancements/GfxPatcher/PlayerCustomFlipbooks.cpp new file mode 100644 index 0000000000..71717d07d4 --- /dev/null +++ b/mm/2s2h/Enhancements/GfxPatcher/PlayerCustomFlipbooks.cpp @@ -0,0 +1,133 @@ +#include "PlayerCustomFlipbooks.h" +#include "2s2h/BenPort.h" + +extern "C" { +#include "z64player.h" +extern TexturePtr sPlayerEyesTextures[PLAYER_FORM_MAX][PLAYER_EYES_MAX]; +extern TexturePtr sPlayerMouthTextures[PLAYER_FORM_MAX][PLAYER_MOUTH_MAX]; +uint8_t ResourceMgr_FileExists(const char* resName); +} + +static const char* sFDEyesTextures[PLAYER_EYES_MAX] = { + "__OTR__objects/object_link_boy/gLinkFierceDeityEyesOpenTex", + "__OTR__objects/object_link_boy/gLinkFierceDeityEyesHalfTex", + "__OTR__objects/object_link_boy/gLinkFierceDeityEyesClosedTex", + "__OTR__objects/object_link_boy/gLinkFierceDeityEyesRightTex", + "__OTR__objects/object_link_boy/gLinkFierceDeityEyesLeftTex", + "__OTR__objects/object_link_boy/gLinkFierceDeityEyesUpTex", + "__OTR__objects/object_link_boy/gLinkFierceDeityEyesDownTex", + "__OTR__objects/object_link_boy/gLinkFierceDeityEyesWincingTex", +}; + +static const char* sFDMouthTextures[PLAYER_MOUTH_MAX] = { + "__OTR__objects/object_link_boy/gLinkFierceDeityMouthClosedTex", + "__OTR__objects/object_link_boy/gLinkFierceDeityMouthHalfTex", + "__OTR__objects/object_link_boy/gLinkFierceDeityMouthOpenTex", + "__OTR__objects/object_link_boy/gLinkFierceDeityMouthSmileTex", +}; + +static const char* sDekuEyesTextures[PLAYER_EYES_MAX] = { + "__OTR__objects/object_link_nuts/gLinkDekuEyesOpenTex", "__OTR__objects/object_link_nuts/gLinkDekuEyesHalfTex", + "__OTR__objects/object_link_nuts/gLinkDekuEyesClosedTex", "__OTR__objects/object_link_nuts/gLinkDekuEyesRightTex", + "__OTR__objects/object_link_nuts/gLinkDekuEyesLeftTex", "__OTR__objects/object_link_nuts/gLinkDekuEyesUpTex", + "__OTR__objects/object_link_nuts/gLinkDekuEyesDownTex", "__OTR__objects/object_link_nuts/gLinkDekuEyesWincingTex", +}; + +static const char* sDekuMouthTextures[PLAYER_MOUTH_MAX] = { + "__OTR__objects/object_link_nuts/gLinkDekuMouthClosedTex", + "__OTR__objects/object_link_nuts/gLinkDekuMouthHalfTex", + "__OTR__objects/object_link_nuts/gLinkDekuMouthOpenTex", + "__OTR__objects/object_link_nuts/gLinkDekuMouthSmileTex", +}; + +static const char* sGoronMouthTextures[PLAYER_MOUTH_MAX] = { + "__OTR__objects/object_link_goron/gLinkGoronMouthClosedTex", + "__OTR__objects/object_link_goron/gLinkGoronMouthHalfTex", + "__OTR__objects/object_link_goron/gLinkGoronMouthOpenTex", + "__OTR__objects/object_link_goron/gLinkGoronMouthSmileTex", +}; + +static s32 sFacePatchState = 0; + +static void PlayerCustomFlipbooks_PatchOnce(void) { + if (sFacePatchState != 0) { + return; + } + + bool EyesPatch = true; + bool MouthPatch = true; + bool DekuEyesPatch = true; + bool DekuMouthPatch = true; + bool GoronMouthPatch = true; + + for (s32 i = 0; i < PLAYER_EYES_MAX; i++) { + if (!ResourceMgr_FileExists(sFDEyesTextures[i])) { + EyesPatch = false; + break; + } + } + + for (s32 i = 0; i < PLAYER_MOUTH_MAX; i++) { + if (!ResourceMgr_FileExists(sFDMouthTextures[i])) { + MouthPatch = false; + break; + } + } + + for (s32 i = 0; i < PLAYER_EYES_MAX; i++) { + if (!ResourceMgr_FileExists(sDekuEyesTextures[i])) { + DekuEyesPatch = false; + break; + } + } + + for (s32 i = 0; i < PLAYER_MOUTH_MAX; i++) { + if (!ResourceMgr_FileExists(sDekuMouthTextures[i])) { + DekuMouthPatch = false; + break; + } + } + + for (s32 i = 0; i < PLAYER_MOUTH_MAX; i++) { + if (!ResourceMgr_FileExists(sGoronMouthTextures[i])) { + GoronMouthPatch = false; + break; + } + } + + if (EyesPatch) { + for (s32 i = 0; i < PLAYER_EYES_MAX; i++) { + sPlayerEyesTextures[PLAYER_FORM_FIERCE_DEITY][i] = (TexturePtr)sFDEyesTextures[i]; + } + } + + if (MouthPatch) { + for (s32 i = 0; i < PLAYER_MOUTH_MAX; i++) { + sPlayerMouthTextures[PLAYER_FORM_FIERCE_DEITY][i] = (TexturePtr)sFDMouthTextures[i]; + } + } + + if (DekuEyesPatch) { + for (s32 i = 0; i < PLAYER_EYES_MAX; i++) { + sPlayerEyesTextures[PLAYER_FORM_DEKU][i] = (TexturePtr)sDekuEyesTextures[i]; + } + } + + if (DekuMouthPatch) { + for (s32 i = 0; i < PLAYER_MOUTH_MAX; i++) { + sPlayerMouthTextures[PLAYER_FORM_DEKU][i] = (TexturePtr)sDekuMouthTextures[i]; + } + } + + if (GoronMouthPatch) { + for (s32 i = 0; i < PLAYER_MOUTH_MAX; i++) { + sPlayerMouthTextures[PLAYER_FORM_GORON][i] = (TexturePtr)sGoronMouthTextures[i]; + } + } + + sFacePatchState = 1; +} + +void PlayerCustomFlipbooks_Patch(void) { + PlayerCustomFlipbooks_PatchOnce(); +} diff --git a/mm/2s2h/Enhancements/GfxPatcher/PlayerCustomFlipbooks.h b/mm/2s2h/Enhancements/GfxPatcher/PlayerCustomFlipbooks.h new file mode 100644 index 0000000000..dc0fc549da --- /dev/null +++ b/mm/2s2h/Enhancements/GfxPatcher/PlayerCustomFlipbooks.h @@ -0,0 +1,3 @@ +#pragma once + +void PlayerCustomFlipbooks_Patch(void); From 74890e1a74bc15ba960631e4a6dfb102c66b6018 Mon Sep 17 00:00:00 2001 From: mckinlee Date: Mon, 5 Jan 2026 20:53:27 -0500 Subject: [PATCH 37/44] [Enhancement] Auto Bank Deposit (#1419) * initial implementation * oops * feedback --- mm/2s2h/BenGui/BenMenu.cpp | 6 + .../Timesavers/AutoBankDeposit.cpp | 146 ++++++++++++++++++ .../GameInteractor_VanillaBehavior.h | 8 + mm/src/code/z_parameter.c | 6 +- 4 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 mm/2s2h/Enhancements/Timesavers/AutoBankDeposit.cpp diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index ac26bd66f3..fe8da6805c 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -1391,6 +1391,12 @@ void BenMenu::AddEnhancements() { .CVar("gEnhancements.Timesavers.SkipBalladOfWindfish") .Options(CheckboxOptions().Tooltip( "Play the complete Ballad after playing in one form if you have all three transformation masks.")); + AddWidget(path, "Auto Bank Deposit", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Timesavers.AutoBankDeposit") + .Options(CheckboxOptions().Tooltip( + "Automatically deposits excess Rupees into your bank account when your wallet is full. " + "Deposits stop when the bank reaches maximum capacity. " + "Bank rewards are granted automatically. Notifications display deposit amount and new balance.")); // Fixes path = { "Enhancements", "Fixes", SECTION_COLUMN_1 }; diff --git a/mm/2s2h/Enhancements/Timesavers/AutoBankDeposit.cpp b/mm/2s2h/Enhancements/Timesavers/AutoBankDeposit.cpp new file mode 100644 index 0000000000..713e3af181 --- /dev/null +++ b/mm/2s2h/Enhancements/Timesavers/AutoBankDeposit.cpp @@ -0,0 +1,146 @@ +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" +#include "2s2h/BenGui/Notification.h" +#include "2s2h/Rando/Rando.h" +#include "2s2h/CustomMessage/CustomMessage.h" +#include "2s2h/CustomItem/CustomItem.h" + +#define CVAR_NAME "gEnhancements.Timesavers.AutoBankDeposit" +#define CVAR CVarGetInteger(CVAR_NAME, 0) + +#define BANK_MAX_CAPACITY 5000 + +static void EmitDepositNotification(s16 depositAmount, s16 newBalance) { + Notification::Options notif = {}; + notif.prefix = "Deposit Amount:"; + notif.prefixColor = ImVec4(0.4f, 0.7f, 1.0f, 1.0f); + notif.message = std::to_string(depositAmount); + notif.messageColor = ImVec4(0.9f, 0.9f, 0.9f, 1.0f); + notif.suffix = "New Balance: " + std::to_string(newBalance); + notif.suffixColor = ImVec4(0.3f, 1.0f, 0.3f, 1.0f); + notif.remainingTime = 6.0f; + Notification::Emit(notif); +} + +static void GrantBankFirstReward() { + SET_WEEKEVENTREG(WEEKEVENTREG_RECEIVED_BANK_WALLET_UPGRADE); + + if (IS_RANDO) { + RANDO_SAVE_CHECKS[RC_CLOCK_TOWN_WEST_BANK_ADULTS_WALLET].eligible = true; + } else { + u32 walletLevel = CUR_UPG_VALUE(UPG_WALLET); + s16 itemDrawId = (walletLevel == 0) ? (s16)GID_WALLET_ADULT : (s16)GID_WALLET_GIANT; + + GameInteractor::Instance->events.emplace_back(GIEventGiveItem{ + .showGetItemCutscene = true, .param = itemDrawId, .giveItem = [](Actor* actor, PlayState* play) { + u32 walletLevel = CUR_UPG_VALUE(UPG_WALLET); + ItemId wallet = (walletLevel == 0) ? ITEM_WALLET_ADULT : ITEM_WALLET_GIANT; + const char* walletName = (walletLevel == 0) ? "Adult's Wallet" : "Giant's Wallet"; + + if (CUSTOM_ITEM_FLAGS & CustomItem::GIVE_ITEM_CUTSCENE) { + CustomMessage::SetActiveCustomMessage(std::string("You got ") + walletName + "!", + { .textboxType = 2 }); + } else { + CustomMessage::StartTextbox(std::string("You got ") + walletName + "!\x1C\x02\x10", + { .textboxType = 2 }); + } + + Item_Give(play, wallet); + } }); + } +} + +static void GrantBankInterestReward() { + SET_WEEKEVENTREG(WEEKEVENTREG_59_80); + + if (IS_RANDO) { + RANDO_SAVE_CHECKS[RC_CLOCK_TOWN_WEST_BANK_INTEREST].eligible = true; + } else { + GameInteractor::Instance->events.emplace_back(GIEventGiveItem{ + .showGetItemCutscene = true, .param = GID_RUPEE_BLUE, .giveItem = [](Actor* actor, PlayState* play) { + if (CUSTOM_ITEM_FLAGS & CustomItem::GIVE_ITEM_CUTSCENE) { + CustomMessage::SetActiveCustomMessage("You got a Blue Rupee!", { .textboxType = 2 }); + } else { + CustomMessage::StartTextbox("You got a Blue Rupee!\x1C\x02\x10", { .textboxType = 2 }); + } + + Item_Give(play, ITEM_RUPEE_BLUE); + } }); + } +} + +static void GrantBankFinalReward() { + SET_WEEKEVENTREG(WEEKEVENTREG_RECEIVED_BANK_HEART_PIECE); + + if (IS_RANDO) { + RANDO_SAVE_CHECKS[RC_CLOCK_TOWN_WEST_BANK_PIECE_OF_HEART].eligible = true; + } else { + GameInteractor::Instance->events.emplace_back(GIEventGiveItem{ + .showGetItemCutscene = true, .param = GID_HEART_PIECE, .giveItem = [](Actor* actor, PlayState* play) { + if (CUSTOM_ITEM_FLAGS & CustomItem::GIVE_ITEM_CUTSCENE) { + CustomMessage::SetActiveCustomMessage("You got a Piece of Heart!", { .textboxType = 2 }); + } else { + CustomMessage::StartTextbox("You got a Piece of Heart!\x1C\x02\x10", { .textboxType = 2 }); + } + + Item_Give(play, ITEM_HEART_PIECE); + } }); + } +} + +static void GrantBankerReward(s16 balanceBeforeDeposit, s16 balanceAfterDeposit) { + bool useCustomThresholds = CVarGetInteger("gEnhancements.DifficultyOptions.LowerBankRewardThresholds", 0); + + s16 walletThreshold = useCustomThresholds ? 100 : 200; + s16 interestThreshold = useCustomThresholds ? 500 : 1000; + s16 heartPieceThreshold = useCustomThresholds ? 1000 : 5000; + + if (balanceBeforeDeposit < walletThreshold && balanceAfterDeposit >= walletThreshold && + !CHECK_WEEKEVENTREG(WEEKEVENTREG_RECEIVED_BANK_WALLET_UPGRADE)) { + GrantBankFirstReward(); + } + + if (balanceBeforeDeposit < interestThreshold && balanceAfterDeposit >= interestThreshold && + !CHECK_WEEKEVENTREG(WEEKEVENTREG_59_80)) { + GrantBankInterestReward(); + } + + if (balanceBeforeDeposit < heartPieceThreshold && balanceAfterDeposit >= heartPieceThreshold && + !CHECK_WEEKEVENTREG(WEEKEVENTREG_RECEIVED_BANK_HEART_PIECE)) { + GrantBankFinalReward(); + } +} + +static void HandleWalletOverflow() { + s16 currentBankBalance = HS_GET_BANK_RUPEES(); + + if (currentBankBalance >= BANK_MAX_CAPACITY) { + return; + } + + s16 spaceInBank = BANK_MAX_CAPACITY - currentBankBalance; + s16 depositAmount = MIN(gSaveContext.rupeeAccumulator, spaceInBank); + + if (depositAmount > 0) { + s16 balanceBeforeDeposit = currentBankBalance; + s16 balanceAfterDeposit = currentBankBalance + depositAmount; + + HS_SET_BANK_RUPEES(balanceAfterDeposit); + gSaveContext.rupeeAccumulator -= depositAmount; + + EmitDepositNotification(depositAmount, balanceAfterDeposit); + GrantBankerReward(balanceBeforeDeposit, balanceAfterDeposit); + } +} + +static void RegisterAutoBankDeposit() { + COND_VB_SHOULD(VB_DISCARD_EXCESS_RUPEES, CVAR, { + HandleWalletOverflow(); + + if (gSaveContext.rupeeAccumulator == 0) { + *should = true; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterAutoBankDeposit, { CVAR_NAME }); diff --git a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h index 235a2ea843..9add2c8ac4 100644 --- a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h +++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h @@ -401,6 +401,14 @@ typedef enum { // - None VB_DISABLE_LETTERBOX, + // #### `result` + // ```c + // gSaveContext.save.saveInfo.playerData.rupees >= CUR_CAPACITY(UPG_WALLET) + // ``` + // #### `args` + // - None + VB_DISCARD_EXCESS_RUPEES, + // #### `result` // ```c // true diff --git a/mm/src/code/z_parameter.c b/mm/src/code/z_parameter.c index 9cea7ffcec..7950c516de 100644 --- a/mm/src/code/z_parameter.c +++ b/mm/src/code/z_parameter.c @@ -9663,8 +9663,10 @@ void Interface_Update(PlayState* play) { Audio_PlaySfx(NA_SE_SY_RUPY_COUNT); } else { // Max rupees - gSaveContext.save.saveInfo.playerData.rupees = CUR_CAPACITY(UPG_WALLET); - gSaveContext.rupeeAccumulator = 0; + if (!GameInteractor_Should(VB_DISCARD_EXCESS_RUPEES, false)) { + gSaveContext.save.saveInfo.playerData.rupees = CUR_CAPACITY(UPG_WALLET); + gSaveContext.rupeeAccumulator = 0; + } } } else if (gSaveContext.save.saveInfo.playerData.rupees != 0) { if (gSaveContext.rupeeAccumulator <= -50) { From 8eab6b612d00d6cb69c103dc660f3d330a8a5fd0 Mon Sep 17 00:00:00 2001 From: mckinlee Date: Mon, 5 Jan 2026 20:53:38 -0500 Subject: [PATCH 38/44] [Enhancement] GFS Attack With B (#1420) * alternative solution? * feedback --- mm/2s2h/BenGui/BenMenu.cpp | 5 ++++ .../Equipment/GreatFairySwordBButton.cpp | 26 +++++++++++++++++++ .../GameInteractor_VanillaBehavior.h | 10 +++++++ mm/src/code/z_player_lib.c | 3 +++ 4 files changed, 44 insertions(+) create mode 100644 mm/2s2h/Enhancements/Equipment/GreatFairySwordBButton.cpp diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index fe8da6805c..105d819c9d 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -994,6 +994,11 @@ void BenMenu::AddEnhancements() { .CVar("gEnhancements.Equipment.InvertShieldY") .Options(CheckboxOptions().Tooltip( "Invert the Y axis while holding the shield so that it moves up with the left stick.")); + AddWidget(path, "Great Fairy Sword B-Button Attack", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Equipment.GreatFairySwordBButton") + .Options(CheckboxOptions().Tooltip( + "When the Great Fairy's Sword is held, pressing B attacks with it instead of drawing " + "your equipped sword. The sword can still be put away with A as normal.")); path.column = SECTION_COLUMN_2; AddWidget(path, "Modes", WIDGET_SEPARATOR_TEXT); diff --git a/mm/2s2h/Enhancements/Equipment/GreatFairySwordBButton.cpp b/mm/2s2h/Enhancements/Equipment/GreatFairySwordBButton.cpp new file mode 100644 index 0000000000..e0aedf291b --- /dev/null +++ b/mm/2s2h/Enhancements/Equipment/GreatFairySwordBButton.cpp @@ -0,0 +1,26 @@ +#include +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" + +extern "C" { +#include "variables.h" +extern Input* sPlayerControlInput; +} + +#define CVAR_NAME "gEnhancements.Equipment.GreatFairySwordBButton" +#define CVAR CVarGetInteger(CVAR_NAME, 0) + +void RegisterGreatFairySwordBButton() { + COND_VB_SHOULD(VB_GET_ITEM_ON_BUTTON, CVAR, { + Player* player = GET_PLAYER(gPlayState); + EquipSlot slot = (EquipSlot)va_arg(args, int); + ItemId* item = va_arg(args, ItemId*); + + if (slot == EQUIP_SLOT_B && player->transformation == PLAYER_FORM_HUMAN && + player->heldItemId == ITEM_SWORD_GREAT_FAIRY) { + *item = ITEM_SWORD_GREAT_FAIRY; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterGreatFairySwordBButton, { CVAR_NAME }); diff --git a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h index 9add2c8ac4..19852707b7 100644 --- a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h +++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h @@ -637,6 +637,16 @@ typedef enum { // - `PlayerItemAction` VB_GET_ITEM_ACTION_FROM_MASK, + // #### `result` + // #### In `Player_GetItemOnButton`: + // ```c + // item + // ``` + // #### `args` + // - `EquipSlot` + // - `*ItemId` + VB_GET_ITEM_ON_BUTTON, + // #### `result` // ```c // false diff --git a/mm/src/code/z_player_lib.c b/mm/src/code/z_player_lib.c index f51d17cfd4..11efb78e78 100644 --- a/mm/src/code/z_player_lib.c +++ b/mm/src/code/z_player_lib.c @@ -4,6 +4,7 @@ */ #include "global.h" +#include "2s2h/GameInteractor/GameInteractor.h" #include "objects/gameplay_keep/gameplay_keep.h" @@ -790,6 +791,8 @@ ItemId Player_GetItemOnButton(PlayState* play, Player* player, EquipSlot slot) { return ITEM_F2; } + GameInteractor_Should(VB_GET_ITEM_ON_BUTTON, item, slot, &item); + return item; } From b2fb5b230563488f030664989fa7b0b0160705c7 Mon Sep 17 00:00:00 2001 From: mckinlee Date: Mon, 5 Jan 2026 20:53:46 -0500 Subject: [PATCH 39/44] just be a regular maze (#1421) --- mm/2s2h/BenGui/BenMenu.cpp | 4 ++ .../Minigames/TreasureChestShopFullMaze.cpp | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 mm/2s2h/Enhancements/Minigames/TreasureChestShopFullMaze.cpp diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 105d819c9d..5c11a1cbdc 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -1653,6 +1653,10 @@ void BenMenu::AddEnhancements() { AddWidget(path, "Invincible", WIDGET_CVAR_CHECKBOX) .CVar("gEnhancements.Minigames.BoatArcheryInvincible") .Options(CheckboxOptions().Tooltip("Koume's health does not decrease when hit.")); + AddWidget(path, "Treasure Chest Shop Show Full Maze", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Minigames.TreasureChestShopShowFullMaze") + .Options(CheckboxOptions().Tooltip("Shows the entire maze layout in the Treasure Chest Shop minigame " + "instead of only revealing tiles near Link.")); path.column = SECTION_COLUMN_3; AddWidget(path, "Other", WIDGET_SEPARATOR_TEXT); diff --git a/mm/2s2h/Enhancements/Minigames/TreasureChestShopFullMaze.cpp b/mm/2s2h/Enhancements/Minigames/TreasureChestShopFullMaze.cpp new file mode 100644 index 0000000000..485f377059 --- /dev/null +++ b/mm/2s2h/Enhancements/Minigames/TreasureChestShopFullMaze.cpp @@ -0,0 +1,40 @@ +#include +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" + +// Re-definitions to avoid modifying source headers +#define TAKARAYA_WALL_ROWS 11 +#define TAKARAYA_WALL_COLUMNS 8 + +extern "C" { +extern f32 sTakarayaWallHeights[TAKARAYA_WALL_ROWS][TAKARAYA_WALL_COLUMNS]; +extern u8 sTakarayaWallStates[TAKARAYA_WALL_ROWS][TAKARAYA_WALL_COLUMNS]; +} + +// Re-definition to avoid modifying source headers +typedef enum { TAKARAYA_WALL_INACTIVE, TAKARAYA_WALL_RISING, TAKARAYA_WALL_FALLING } TakarayaWallCellState; + +#define CVAR_NAME "gEnhancements.Minigames.TreasureChestShopShowFullMaze" +#define CVAR CVarGetInteger(CVAR_NAME, 0) + +static void RegisterTreasureChestShopFullMaze() { + COND_ID_HOOK(OnActorUpdate, ACTOR_OBJ_TAKARAYA_WALL, CVAR, [](Actor* actor) { + if (gSaveContext.timerStates[TIMER_ID_MINIGAME_2] == TIMER_STATE_OFF) { + return; + } + + for (int i = 0; i < TAKARAYA_WALL_ROWS; i++) { + for (int j = 0; j < TAKARAYA_WALL_COLUMNS; j++) { + if (sTakarayaWallHeights[i][j] >= 0.0f) { + if (Math_StepToF(&sTakarayaWallHeights[i][j], 120.0f, 15.0f)) { + sTakarayaWallStates[i][j] = TAKARAYA_WALL_INACTIVE; + } else { + sTakarayaWallStates[i][j] = TAKARAYA_WALL_RISING; + } + } + } + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterTreasureChestShopFullMaze, { CVAR_NAME }); From de61cd8a9116f77096a481e3a354494aaa8e6790 Mon Sep 17 00:00:00 2001 From: Caladius Date: Tue, 6 Jan 2026 09:28:56 -0500 Subject: [PATCH 40/44] [Rando] Enemy Soul Shuffle (#1284) * Add Aliens and initial code moved from old build. * Update Enemy Souls to latest Develop and fix issues. * Change Stalchild Animation to Idle * Fix Stalchild Eyes * Adds Hiploop to keep in line with Enemy Drops * correction * Update Matrix Calls * Remove comment * Remove Garo for now * Add Octorok Soul req for Upper Canyon Access * Add soul requirement for Red Switch * Grant Soul INF when the option is off * Adds Nejiron Soul * Add Flying Pot Soul * ew (#14) * cleanup * Add Bubble Soul * Adds Freezard * Change Soul behaivor to Invincible * header cleanup and arg consolidation * Fixes OnFileCreate to actually shuffle Enemy Souls. took this op. to fix up Boss Souls shuffling as it was needed to cleanly shuffle Enemy Souls. * Naming Updates * Name Updates pt2 * Add Tracker Icons and adds a Default to avoid erroring * Fix Iron Knuckle Helmet * revert item icon default and add soul flame to tatl target for enemies that are invincible. * Move VBs to GameInteractor_VanillaBehavior.h * Use VB_PERFORM_AC_COLLISION for enemy souls * Add enemy souls for closer parity with drops Stub out enemy draw funcs Use macros to streamline existing skeleton inits Logically account for enemy souls in CanKillEnemy Fix logical quirk with Ikana ice arrows * Draw everything * Remove unused INIT arg from SETUP_FLEX_SKEL * Add Igos, Keeta, Gomess Make soul RIs contiguous, separate boss and enemy Ignore target color for EnInvadepoh Remove Real Bombchus and Flying Pots for now Require Gekko and Snapper souls for beating both * Add Enemy Souls to item tracker * clang format --------- Co-authored-by: mckinlee Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com> --- .../Trackers/ItemTracker/ItemTracker.cpp | 60 +- .../ItemTracker/ItemTrackerSettings.cpp | 9 +- .../GameInteractor_VanillaBehavior.h | 8 + mm/2s2h/Rando/ActorBehavior/Souls.cpp | 152 ++- mm/2s2h/Rando/ActorBehavior/Souls.h | 10 + mm/2s2h/Rando/ConvertItem.cpp | 60 +- mm/2s2h/Rando/DrawFuncs.cpp | 1135 +++++++++++++++-- mm/2s2h/Rando/DrawFuncs.h | 53 + mm/2s2h/Rando/DrawItem.cpp | 118 +- mm/2s2h/Rando/GiveItem.cpp | 64 +- mm/2s2h/Rando/Logic/GeneratePools.cpp | 11 +- mm/2s2h/Rando/Logic/Logic.h | 16 +- mm/2s2h/Rando/Logic/Regions/East.cpp | 1 + .../Rando/Logic/Regions/GreatBayTemple.cpp | 2 +- mm/2s2h/Rando/Menu.cpp | 12 +- mm/2s2h/Rando/RemoveItem.cpp | 60 +- mm/2s2h/Rando/StaticData/Items.cpp | 116 +- mm/2s2h/Rando/StaticData/Options.cpp | 1 + mm/2s2h/Rando/Types.h | 116 +- mm/src/code/z_actor.c | 7 +- 20 files changed, 1829 insertions(+), 182 deletions(-) create mode 100644 mm/2s2h/Rando/ActorBehavior/Souls.h diff --git a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp index 1aa6d2c5fd..15a2d8ec09 100644 --- a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp +++ b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp @@ -3,6 +3,7 @@ #include "2s2h/BenGui/UIWidgets.hpp" #include "Rando/Rando.h" +#include "Rando/ActorBehavior/Souls.h" #include "Rando/MiscBehavior/ClockShuffle.h" #include "2s2h/ShipUtils.h" @@ -84,12 +85,59 @@ extern TrackerImageObject GetTextureObject(int16_t itemId, bool isRandoItem) { case RI_OWL_ZORA_CAPE: itemObtained = GET_OWL_STATUE_ACTIVATED(OWL_WARP_ZORA_CAPE); break; - case RI_SOUL_GOHT: - case RI_SOUL_GYORG: - case RI_SOUL_MAJORA: - case RI_SOUL_ODOLWA: - case RI_SOUL_TWINMOLD: - itemObtained = Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_GOHT + (itemId - RI_SOUL_GOHT)); + case RI_SOUL_BOSS_GOHT: + case RI_SOUL_BOSS_GYORG: + case RI_SOUL_BOSS_MAJORA: + case RI_SOUL_BOSS_ODOLWA: + case RI_SOUL_BOSS_TWINMOLD: + case RI_SOUL_ENEMY_ALIEN: + case RI_SOUL_ENEMY_ARMOS: + case RI_SOUL_ENEMY_BAD_BAT: + case RI_SOUL_ENEMY_BEAMOS: + case RI_SOUL_ENEMY_BOE: + case RI_SOUL_ENEMY_BUBBLE: + case RI_SOUL_ENEMY_CAPTAIN_KEETA: + case RI_SOUL_ENEMY_CHUCHU: + case RI_SOUL_ENEMY_DEATH_ARMOS: + case RI_SOUL_ENEMY_DEEP_PYTHON: + case RI_SOUL_ENEMY_DEKU_BABA: + case RI_SOUL_ENEMY_DEXIHAND: + case RI_SOUL_ENEMY_DINOLFOS: + case RI_SOUL_ENEMY_DODONGO: + case RI_SOUL_ENEMY_DRAGONFLY: + case RI_SOUL_ENEMY_EENO: + case RI_SOUL_ENEMY_EYEGORE: + case RI_SOUL_ENEMY_FREEZARD: + case RI_SOUL_ENEMY_GARO: + case RI_SOUL_ENEMY_GEKKO: + case RI_SOUL_ENEMY_GIANT_BEE: + case RI_SOUL_ENEMY_GOMESS: + case RI_SOUL_ENEMY_GUAY: + case RI_SOUL_ENEMY_HIPLOOP: + case RI_SOUL_ENEMY_IGOS_DU_IKANA: + case RI_SOUL_ENEMY_IRON_KNUCKLE: + case RI_SOUL_ENEMY_KEESE: + case RI_SOUL_ENEMY_LEEVER: + case RI_SOUL_ENEMY_LIKE_LIKE: + case RI_SOUL_ENEMY_MAD_SCRUB: + case RI_SOUL_ENEMY_NEJIRON: + case RI_SOUL_ENEMY_OCTOROK: + case RI_SOUL_ENEMY_PEAHAT: + case RI_SOUL_ENEMY_PIRATE: + case RI_SOUL_ENEMY_POE: + case RI_SOUL_ENEMY_REDEAD: + case RI_SOUL_ENEMY_SHELLBLADE: + case RI_SOUL_ENEMY_SKULLFISH: + case RI_SOUL_ENEMY_SKULLTULA: + case RI_SOUL_ENEMY_SNAPPER: + case RI_SOUL_ENEMY_STALCHILD: + case RI_SOUL_ENEMY_TAKKURI: + case RI_SOUL_ENEMY_TEKTITE: + case RI_SOUL_ENEMY_WALLMASTER: + case RI_SOUL_ENEMY_WART: + case RI_SOUL_ENEMY_WIZROBE: + case RI_SOUL_ENEMY_WOLFOS: + itemObtained = Flags_GetRandoInf(SOUL_RI_TO_RANDO_INF(itemId)); break; case RI_TINGLE_MAP_CLOCK_TOWN: itemObtained = CHECK_WEEKEVENTREG(WEEKEVENTREG_TINGLE_MAP_BOUGHT_CLOCK_TOWN); diff --git a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp index 618744126e..dec6e019dc 100644 --- a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp +++ b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp @@ -27,7 +27,7 @@ std::vector listOrder = { }; std::vector randoListOrder = { - "Frogs", "Boss Souls", "Owl Statues", "Tingle Maps", "Time", "Misc", + "Frogs", "Boss Souls", "Enemy Souls", "Owl Statues", "Time", "Tingle Maps", "Misc", }; std::map> defaultItemLists = { @@ -43,7 +43,8 @@ std::map> defaultItemLists = std::map> randoItemLists = { { "Frogs", { RI_FROG_BLUE, RI_FROG_WHITE, 4 } }, - { "Boss Souls", { RI_SOUL_GOHT, RI_SOUL_TWINMOLD, 5 } }, + { "Boss Souls", { RI_SOUL_BOSS_GOHT, RI_SOUL_BOSS_TWINMOLD, 5 } }, + { "Enemy Souls", { RI_SOUL_ENEMY_ALIEN, RI_SOUL_ENEMY_WOLFOS, 6 } }, { "Owl Statues", { RI_OWL_CLOCK_TOWN_SOUTH, RI_OWL_ZORA_CAPE, 5 } }, { "Tingle Maps", { RI_TINGLE_MAP_CLOCK_TOWN, RI_TINGLE_MAP_WOODFALL, 6 } }, { "Time", { RI_TIME_DAY_1, RI_TIME_NIGHT_3, 6 } }, @@ -249,8 +250,8 @@ void DrawItemList(std::string listName, int columns) { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(5, 5)); std::vector emptyList; - if (listName == "Frogs" || listName == "Boss Souls" || listName == "Owl Statues" || - listName == "Tingle Maps" || listName == "Time" || listName == "Misc") { + if (listName == "Frogs" || listName == "Boss Souls" || listName == "Enemy Souls" || + listName == "Owl Statues" || listName == "Tingle Maps" || listName == "Time" || listName == "Misc") { for (int j = std::get<0>(randoItemLists.at(listName)); j <= std::get<1>(randoItemLists.at(listName)); j++) { ImGui::TableNextColumn(); diff --git a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h index 19852707b7..0d84558fa3 100644 --- a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h +++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h @@ -441,6 +441,14 @@ typedef enum { // - `*DmHina` VB_DRAW_BOSS_REMAINS, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + VB_DRAW_LOCK_ON_ARROW, + // #### `result` // ```c // true diff --git a/mm/2s2h/Rando/ActorBehavior/Souls.cpp b/mm/2s2h/Rando/ActorBehavior/Souls.cpp index bea7c3d6f3..f1b58b94b2 100644 --- a/mm/2s2h/Rando/ActorBehavior/Souls.cpp +++ b/mm/2s2h/Rando/ActorBehavior/Souls.cpp @@ -1,5 +1,8 @@ #include "ActorBehavior.h" +#include "Souls.h" #include +#include "Rando/DrawFuncs.h" +#include "Rando/Logic/Logic.h" extern "C" { #include "variables.h" @@ -21,6 +24,89 @@ bool shouldMajoraRegister() { return registerStatus; } +// clang-format off +std::unordered_map enemySoulMap = { + // Real Bombchu and Flying Pot souls are excluded, as those actors are programmed to die to any collision, not just + // the damaging type. We don't have a good answer for what behavior those should follow if the hit something. + { ACTOR_EN_INVADEPOH, RI_SOUL_ENEMY_ALIEN }, + { ACTOR_EN_AM, RI_SOUL_ENEMY_ARMOS }, + { ACTOR_EN_BAT, RI_SOUL_ENEMY_BAD_BAT }, + { ACTOR_EN_VM, RI_SOUL_ENEMY_BEAMOS }, + { ACTOR_EN_BB, RI_SOUL_ENEMY_BUBBLE }, + { ACTOR_EN_BBFALL, RI_SOUL_ENEMY_BUBBLE }, + { ACTOR_EN_MKK, RI_SOUL_ENEMY_BOE }, + { ACTOR_EN_BSB, RI_SOUL_ENEMY_CAPTAIN_KEETA }, + { ACTOR_EN_SLIME, RI_SOUL_ENEMY_CHUCHU }, + { ACTOR_EN_FAMOS, RI_SOUL_ENEMY_DEATH_ARMOS }, + { ACTOR_EN_DRAGON, RI_SOUL_ENEMY_DEEP_PYTHON }, + { ACTOR_EN_DEKUBABA, RI_SOUL_ENEMY_DEKU_BABA }, + { ACTOR_EN_KAREBABA, RI_SOUL_ENEMY_DEKU_BABA }, + { ACTOR_BOSS_05, RI_SOUL_ENEMY_DEKU_BABA }, + { ACTOR_EN_WDHAND, RI_SOUL_ENEMY_DEXIHAND }, + { ACTOR_EN_DINOFOS, RI_SOUL_ENEMY_DINOLFOS }, + { ACTOR_EN_DODONGO, RI_SOUL_ENEMY_DODONGO }, + { ACTOR_EN_GRASSHOPPER, RI_SOUL_ENEMY_DRAGONFLY }, + { ACTOR_EN_SNOWMAN, RI_SOUL_ENEMY_EENO }, + { ACTOR_EN_EGOL, RI_SOUL_ENEMY_EYEGORE }, + { ACTOR_EN_FZ, RI_SOUL_ENEMY_FREEZARD }, + { ACTOR_EN_JSO, RI_SOUL_ENEMY_GARO }, + { ACTOR_EN_JSO2, RI_SOUL_ENEMY_GARO }, + { ACTOR_EN_BIGSLIME, RI_SOUL_ENEMY_GEKKO }, + { ACTOR_EN_PAMETFROG, RI_SOUL_ENEMY_GEKKO }, + { ACTOR_EN_BEE, RI_SOUL_ENEMY_GIANT_BEE }, + { ACTOR_EN_DEATH, RI_SOUL_ENEMY_GOMESS }, + { ACTOR_EN_MINIDEATH, RI_SOUL_ENEMY_GOMESS }, + { ACTOR_EN_CROW, RI_SOUL_ENEMY_GUAY }, + { ACTOR_EN_RUPPECROW, RI_SOUL_ENEMY_GUAY }, + { ACTOR_EN_PP, RI_SOUL_ENEMY_HIPLOOP }, + { ACTOR_EN_KNIGHT, RI_SOUL_ENEMY_IGOS_DU_IKANA }, + { ACTOR_EN_IK, RI_SOUL_ENEMY_IRON_KNUCKLE }, + { ACTOR_EN_FIREFLY, RI_SOUL_ENEMY_KEESE }, + { ACTOR_EN_NEO_REEBA, RI_SOUL_ENEMY_LEEVER }, + { ACTOR_EN_RR, RI_SOUL_ENEMY_LIKE_LIKE }, + { ACTOR_EN_DEKUNUTS, RI_SOUL_ENEMY_MAD_SCRUB }, + { ACTOR_EN_BAGUO, RI_SOUL_ENEMY_NEJIRON }, + { ACTOR_EN_OKUTA, RI_SOUL_ENEMY_OCTOROK }, + { ACTOR_EN_PEEHAT, RI_SOUL_ENEMY_PEAHAT }, + { ACTOR_EN_KAIZOKU, RI_SOUL_ENEMY_PIRATE }, + { ACTOR_EN_BIGPO, RI_SOUL_ENEMY_POE }, + { ACTOR_EN_PO_SISTERS, RI_SOUL_ENEMY_POE }, + { ACTOR_EN_POH, RI_SOUL_ENEMY_POE }, + { ACTOR_EN_RD, RI_SOUL_ENEMY_REDEAD }, + { ACTOR_EN_SB, RI_SOUL_ENEMY_SHELLBLADE }, + { ACTOR_EN_PR, RI_SOUL_ENEMY_SKULLFISH }, + { ACTOR_EN_PR2, RI_SOUL_ENEMY_SKULLFISH }, + { ACTOR_EN_PRZ, RI_SOUL_ENEMY_SKULLFISH }, + { ACTOR_EN_ST, RI_SOUL_ENEMY_SKULLTULA }, + { ACTOR_EN_SW, RI_SOUL_ENEMY_SKULLTULA }, + { ACTOR_EN_BIGPAMET, RI_SOUL_ENEMY_SNAPPER }, + { ACTOR_EN_KAME, RI_SOUL_ENEMY_SNAPPER }, + { ACTOR_EN_HINT_SKB, RI_SOUL_ENEMY_STALCHILD }, + { ACTOR_EN_RAIL_SKB, RI_SOUL_ENEMY_STALCHILD }, + { ACTOR_EN_SKB, RI_SOUL_ENEMY_STALCHILD }, + { ACTOR_EN_THIEFBIRD, RI_SOUL_ENEMY_TAKKURI }, + { ACTOR_EN_TITE, RI_SOUL_ENEMY_TEKTITE }, + { ACTOR_EN_FLOORMAS, RI_SOUL_ENEMY_WALLMASTER }, + { ACTOR_EN_WALLMAS, RI_SOUL_ENEMY_WALLMASTER }, + { ACTOR_BOSS_04, RI_SOUL_ENEMY_WART }, + { ACTOR_EN_TANRON2, RI_SOUL_ENEMY_WART }, + { ACTOR_EN_WIZ, RI_SOUL_ENEMY_WIZROBE }, + { ACTOR_EN_WF, RI_SOUL_ENEMY_WOLFOS }, +}; +// clang-format on + +bool HaveEnemySoul(ActorId enemyId) { + auto findSoulFlag = enemySoulMap.find(enemyId); + if (findSoulFlag != enemySoulMap.end()) { + RandoItemId randoItemId = findSoulFlag->second; + if (randoItemId != RI_UNKNOWN) { + return Flags_GetRandoInf(SOUL_RI_TO_RANDO_INF(randoItemId)); + } + } + // Enemy soul does not exist, so act as if it is obtained + return true; +} + void ShouldActorUpdate(Actor* actor, bool* should, RandoInf randoInf) { if (!Flags_GetRandoInf(randoInf)) { *should = false; @@ -37,48 +123,59 @@ void ShouldActorDraw(Actor* actor, bool* should, RandoInf randoInf) { } void Rando::ActorBehavior::InitSoulsBehavior() { - bool shouldRegister = IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_BOSS_SOULS] == RO_GENERIC_YES; + bool shouldBossRegister = IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_BOSS_SOULS] == RO_GENERIC_YES; + bool shouldEnemyInjure = IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENEMY_SOULS] == RO_GENERIC_YES; - COND_ID_HOOK(ShouldActorDraw, ACTOR_BOSS_HAKUGIN, shouldRegister, [](Actor* actor, bool* should) { - if (!Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_GOHT)) { + COND_VB_SHOULD(VB_PERFORM_AC_COLLISION, shouldEnemyInjure, { + Collider* at = va_arg(args, Collider*); + Collider* ac = va_arg(args, Collider*); + *should = HaveEnemySoul((ActorId)ac->actor->id); + }); + + // ShouldActorDraw & ShouldActorUpdate for Boss Souls + COND_ID_HOOK(ShouldActorDraw, ACTOR_BOSS_HAKUGIN, shouldBossRegister, [](Actor* actor, bool* should) { + if (!Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_BOSS_GOHT)) { BossHakugin_DrawIce((BossHakugin*)actor, gPlayState); *should = false; } }); - COND_VB_SHOULD(VB_GOHT_UNFREEZE, shouldRegister, { - if (!Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_GOHT)) { + COND_VB_SHOULD(VB_GOHT_UNFREEZE, shouldBossRegister, { + if (!Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_BOSS_GOHT)) { *should = false; } }); - COND_ID_HOOK(ShouldActorDraw, ACTOR_BOSS_03, shouldRegister, - [](Actor* actor, bool* should) { ShouldActorDraw(actor, should, RANDO_INF_OBTAINED_SOUL_OF_GYORG); }); + COND_ID_HOOK(ShouldActorDraw, ACTOR_BOSS_03, shouldBossRegister, [](Actor* actor, bool* should) { + ShouldActorDraw(actor, should, RANDO_INF_OBTAINED_SOUL_OF_BOSS_GYORG); + }); - COND_ID_HOOK(ShouldActorDraw, ACTOR_BOSS_07, shouldMajoraRegister(), - [](Actor* actor, bool* should) { ShouldActorDraw(actor, should, RANDO_INF_OBTAINED_SOUL_OF_MAJORA); }); + COND_ID_HOOK(ShouldActorDraw, ACTOR_BOSS_07, shouldMajoraRegister(), [](Actor* actor, bool* should) { + ShouldActorDraw(actor, should, RANDO_INF_OBTAINED_SOUL_OF_BOSS_MAJORA); + }); - COND_ID_HOOK(ShouldActorDraw, ACTOR_BOSS_01, shouldRegister, - [](Actor* actor, bool* should) { ShouldActorDraw(actor, should, RANDO_INF_OBTAINED_SOUL_OF_ODOLWA); }); + COND_ID_HOOK(ShouldActorDraw, ACTOR_BOSS_01, shouldBossRegister, [](Actor* actor, bool* should) { + ShouldActorDraw(actor, should, RANDO_INF_OBTAINED_SOUL_OF_BOSS_ODOLWA); + }); - COND_ID_HOOK(ShouldActorDraw, ACTOR_BOSS_02, shouldRegister, [](Actor* actor, bool* should) { - ShouldActorDraw(actor, should, RANDO_INF_OBTAINED_SOUL_OF_TWINMOLD); + COND_ID_HOOK(ShouldActorDraw, ACTOR_BOSS_02, shouldBossRegister, [](Actor* actor, bool* should) { + ShouldActorDraw(actor, should, RANDO_INF_OBTAINED_SOUL_OF_BOSS_TWINMOLD); }); - COND_ID_HOOK(ShouldActorUpdate, ACTOR_BOSS_03, shouldRegister, [](Actor* actor, bool* should) { - ShouldActorUpdate(actor, should, RANDO_INF_OBTAINED_SOUL_OF_GYORG); + COND_ID_HOOK(ShouldActorUpdate, ACTOR_BOSS_03, shouldBossRegister, [](Actor* actor, bool* should) { + ShouldActorUpdate(actor, should, RANDO_INF_OBTAINED_SOUL_OF_BOSS_GYORG); }); COND_ID_HOOK(ShouldActorUpdate, ACTOR_BOSS_07, shouldMajoraRegister(), [](Actor* actor, bool* should) { - ShouldActorUpdate(actor, should, RANDO_INF_OBTAINED_SOUL_OF_MAJORA); + ShouldActorUpdate(actor, should, RANDO_INF_OBTAINED_SOUL_OF_BOSS_MAJORA); }); - COND_ID_HOOK(ShouldActorUpdate, ACTOR_BOSS_01, shouldRegister, [](Actor* actor, bool* should) { - ShouldActorUpdate(actor, should, RANDO_INF_OBTAINED_SOUL_OF_ODOLWA); + COND_ID_HOOK(ShouldActorUpdate, ACTOR_BOSS_01, shouldBossRegister, [](Actor* actor, bool* should) { + ShouldActorUpdate(actor, should, RANDO_INF_OBTAINED_SOUL_OF_BOSS_ODOLWA); }); - COND_ID_HOOK(ShouldActorUpdate, ACTOR_BOSS_02, shouldRegister, [](Actor* actor, bool* should) { - ShouldActorUpdate(actor, should, RANDO_INF_OBTAINED_SOUL_OF_TWINMOLD); + COND_ID_HOOK(ShouldActorUpdate, ACTOR_BOSS_02, shouldBossRegister, [](Actor* actor, bool* should) { + ShouldActorUpdate(actor, should, RANDO_INF_OBTAINED_SOUL_OF_BOSS_TWINMOLD); }); /* @@ -86,11 +183,22 @@ void Rando::ActorBehavior::InitSoulsBehavior() { * be used, while the Twinmold actor itself handles the transformation. Boss Souls prevent Twinmold from updating * unless its soul has been obtained, which results in a softlock. In this case, disable the item. */ - COND_VB_SHOULD(VB_ITEM_BE_RESTRICTED, shouldRegister, { + COND_VB_SHOULD(VB_ITEM_BE_RESTRICTED, shouldBossRegister, { ItemId itemId = *va_arg(args, ItemId*); if (itemId == ITEM_MASK_GIANT && gPlayState->sceneId == SCENE_INISIE_BS && - !Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_TWINMOLD)) { + !Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_BOSS_TWINMOLD)) { *should = true; } }); + + COND_VB_SHOULD(VB_DRAW_LOCK_ON_ARROW, shouldEnemyInjure, { + Actor* refActor = va_arg(args, Actor*); + ActorId actorId = (ActorId)refActor->id; + // ACTOR_EN_INVADEPOH represents multiple actors, including Romani and the dog. The aliens cannot be targeted + // anyway, so just don't draw this arrow if the actor is ACTOR_EN_INVADEPOH. + if (actorId != ACTOR_EN_INVADEPOH && !HaveEnemySoul(actorId)) { + DrawEnLight({ 155, 0, 0 }, { 1.0f, 1.0f, 1.0f }); + *should = false; + } + }); } diff --git a/mm/2s2h/Rando/ActorBehavior/Souls.h b/mm/2s2h/Rando/ActorBehavior/Souls.h new file mode 100644 index 0000000000..d2809d5559 --- /dev/null +++ b/mm/2s2h/Rando/ActorBehavior/Souls.h @@ -0,0 +1,10 @@ +#ifndef SOULS_H +#define SOULS_H + +#include "Rando/Rando.h" + +#define SOUL_RI_TO_RANDO_INF(randoItemId) ((randoItemId - RI_SOUL_BOSS_GOHT) + RANDO_INF_OBTAINED_SOUL_OF_BOSS_GOHT) + +extern bool HaveEnemySoul(ActorId enemyId); + +#endif // SOULS_H \ No newline at end of file diff --git a/mm/2s2h/Rando/ConvertItem.cpp b/mm/2s2h/Rando/ConvertItem.cpp index 46bc5f2140..f6f6404bd4 100644 --- a/mm/2s2h/Rando/ConvertItem.cpp +++ b/mm/2s2h/Rando/ConvertItem.cpp @@ -1,4 +1,5 @@ #include "Rando/Rando.h" +#include "Rando/ActorBehavior/Souls.h" #include "Rando/MiscBehavior/ClockShuffle.h" #include "2s2h/ShipUtils.h" #include @@ -422,12 +423,59 @@ bool Rando::IsItemObtainable(RandoItemId randoItemId, RandoCheckId randoCheckId) return !CHECK_WEEKEVENTREG(WEEKEVENTREG_TINGLE_MAP_BOUGHT_SNOWHEAD); case RI_TINGLE_MAP_STONE_TOWER: return !CHECK_WEEKEVENTREG(WEEKEVENTREG_TINGLE_MAP_BOUGHT_STONE_TOWER); - case RI_SOUL_GOHT: - case RI_SOUL_GYORG: - case RI_SOUL_MAJORA: - case RI_SOUL_ODOLWA: - case RI_SOUL_TWINMOLD: - return !Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_GOHT + (randoItemId - RI_SOUL_GOHT)); + case RI_SOUL_BOSS_GOHT: + case RI_SOUL_BOSS_GYORG: + case RI_SOUL_BOSS_MAJORA: + case RI_SOUL_BOSS_ODOLWA: + case RI_SOUL_BOSS_TWINMOLD: + case RI_SOUL_ENEMY_ALIEN: + case RI_SOUL_ENEMY_ARMOS: + case RI_SOUL_ENEMY_BAD_BAT: + case RI_SOUL_ENEMY_BEAMOS: + case RI_SOUL_ENEMY_BOE: + case RI_SOUL_ENEMY_BUBBLE: + case RI_SOUL_ENEMY_CAPTAIN_KEETA: + case RI_SOUL_ENEMY_CHUCHU: + case RI_SOUL_ENEMY_DEATH_ARMOS: + case RI_SOUL_ENEMY_DEEP_PYTHON: + case RI_SOUL_ENEMY_DEKU_BABA: + case RI_SOUL_ENEMY_DEXIHAND: + case RI_SOUL_ENEMY_DINOLFOS: + case RI_SOUL_ENEMY_DODONGO: + case RI_SOUL_ENEMY_DRAGONFLY: + case RI_SOUL_ENEMY_EENO: + case RI_SOUL_ENEMY_EYEGORE: + case RI_SOUL_ENEMY_FREEZARD: + case RI_SOUL_ENEMY_GARO: + case RI_SOUL_ENEMY_GEKKO: + case RI_SOUL_ENEMY_GIANT_BEE: + case RI_SOUL_ENEMY_GOMESS: + case RI_SOUL_ENEMY_GUAY: + case RI_SOUL_ENEMY_HIPLOOP: + case RI_SOUL_ENEMY_IGOS_DU_IKANA: + case RI_SOUL_ENEMY_IRON_KNUCKLE: + case RI_SOUL_ENEMY_KEESE: + case RI_SOUL_ENEMY_LEEVER: + case RI_SOUL_ENEMY_LIKE_LIKE: + case RI_SOUL_ENEMY_MAD_SCRUB: + case RI_SOUL_ENEMY_NEJIRON: + case RI_SOUL_ENEMY_OCTOROK: + case RI_SOUL_ENEMY_PEAHAT: + case RI_SOUL_ENEMY_PIRATE: + case RI_SOUL_ENEMY_POE: + case RI_SOUL_ENEMY_REDEAD: + case RI_SOUL_ENEMY_SHELLBLADE: + case RI_SOUL_ENEMY_SKULLFISH: + case RI_SOUL_ENEMY_SKULLTULA: + case RI_SOUL_ENEMY_SNAPPER: + case RI_SOUL_ENEMY_STALCHILD: + case RI_SOUL_ENEMY_TAKKURI: + case RI_SOUL_ENEMY_TEKTITE: + case RI_SOUL_ENEMY_WALLMASTER: + case RI_SOUL_ENEMY_WART: + case RI_SOUL_ENEMY_WIZROBE: + case RI_SOUL_ENEMY_WOLFOS: + return !Flags_GetRandoInf(SOUL_RI_TO_RANDO_INF(randoItemId)); case RI_TIME_DAY_1: case RI_TIME_NIGHT_1: case RI_TIME_DAY_2: diff --git a/mm/2s2h/Rando/DrawFuncs.cpp b/mm/2s2h/Rando/DrawFuncs.cpp index 8eab3dbf2e..4188caef07 100644 --- a/mm/2s2h/Rando/DrawFuncs.cpp +++ b/mm/2s2h/Rando/DrawFuncs.cpp @@ -12,6 +12,8 @@ extern "C" { #include "assets/objects/object_gi_reserve00/object_gi_reserve00.h" s32 EnMinifrog_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, Actor* enMini); +s32 EnRd_ShouldNotDance(PlayState* play); +Gfx* EnKnight_BuildEmptyDL(GraphicsContext* gfxCtx); // clang-format off // Boss Includes @@ -21,6 +23,58 @@ s32 EnMinifrog_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec /* Twinmold */ #include "objects/object_boss02/object_boss02.h" /* Majora */ #include "objects/object_boss07/object_boss07.h" +// Enemy Includes +/* Alien */ #include "assets/objects/object_uch/object_uch.h" +/* Armos */ #include "assets/objects/object_am/object_am.h" +/* Bad Bat */ #include "assets/objects/object_bat/object_bat.h" +/* Beamos */ #include "assets/objects/object_vm/object_vm.h" +/* Boe */ #include "assets/objects/object_mkk/object_mkk.h" +/* Captain Keeta */ #include "assets/objects/object_bsb/object_bsb.h" +/* Chuchu */ #include "assets/objects/object_slime/object_slime.h" +/* Bubble */ #include "assets/objects/object_bb/object_bb.h" +/* Death Armos */ #include "assets/objects/object_famos/object_famos.h" +/* Deep Python */ #include "assets/objects/object_utubo/object_utubo.h" +/* Deku Baba */ #include "assets/objects/object_dekubaba/object_dekubaba.h" +/* Dexihand */ #include "assets/objects/object_wdhand/object_wdhand.h" +/* Dinolfos */ #include "assets/objects/object_dinofos/object_dinofos.h" +/* Dodongo */ #include "assets/objects/object_dodongo/object_dodongo.h" +/* Dragonfly */ #include "assets/objects/object_grasshopper/object_grasshopper.h" +/* Eeno */ #include "assets/objects/object_snowman/object_snowman.h" +/* Eyegore */ #include "assets/objects/object_eg/object_eg.h" +/* Flying Pot */ #include "assets/objects/gameplay_dangeon_keep/gameplay_dangeon_keep.h" +/* Freezard */ #include "assets/objects/object_fz/object_fz.h" +/* Garo */ #include "assets/objects/object_jso/object_jso.h" +/* Gekko */ #include "overlays/actors/ovl_En_Pametfrog/z_en_pametfrog.h" +/* Giant Bee */ #include "assets/objects/object_bee/object_bee.h" +/* Gomess */ #include "assets/objects/object_death/object_death.h" +/* Guay */ #include "assets/objects/object_crow/object_crow.h" +/* Hiploop */ #include "assets/objects/object_pp/object_pp.h" +/* Igos du Ikana */ #include "assets/objects/object_knight/object_knight.h" +/* Iron Knuckle */ #include "assets/objects/object_ik/object_ik.h" +/* Keese */ #include "assets/objects/object_firefly/object_firefly.h" +/* Leever */ #include "assets/objects/object_rb/object_rb.h" +/* Like Like */ #include "assets/objects/object_rr/object_rr.h" +/* Mad Scrub */ #include "assets/objects/object_dekunuts/object_dekunuts.h" +/* Nejiron */ #include "assets/objects/object_gmo/object_gmo.h" +/* Octorok */ #include "assets/objects/object_okuta/object_okuta.h" +/* Peehat */ #include "assets/objects/object_ph/object_ph.h" +/* Pirate */ #include "assets/objects/object_kz/object_kz.h" +/* Poe */ #include "assets/objects/object_po/object_po.h" +/* Poe */ #include "assets/objects/object_bigpo/object_bigpo.h" +/* Real Bombchu */ #include "assets/objects/object_rat/object_rat.h" +/* Redead */ #include "assets/objects/object_rd/object_rd.h" +/* Shellblade */ #include "assets/objects/object_sb/object_sb.h" +/* Skullfish */ #include "assets/objects/object_pr/object_pr.h" +/* Skulltula */ #include "assets/objects/object_st/object_st.h" +/* Snapper */ #include "assets/objects/object_tl/object_tl.h" +/* Stalchild */ #include "assets/objects/object_skb/object_skb.h" +/* Takkuri */ #include "assets/objects/object_thiefbird/object_thiefbird.h" +/* Tektite */ #include "assets/objects/object_tite/object_tite.h" +/* Wallmaster */ #include "assets/objects/object_wallmaster/object_wallmaster.h" +/* Wart */ #include "assets/objects/object_boss04/object_boss04.h" +/* Wizrobe */ #include "assets/objects/object_wiz/object_wiz.h" +/* Wolfos */ #include "assets/objects/object_wf/object_wf.h" + // Other Actor Includes /* Minifrog */ #include "objects/object_fr/object_fr.h" /* Clock */ #include "overlays/actors/ovl_Obj_Tokeidai/z_obj_tokeidai.h" @@ -31,8 +85,33 @@ void ObjTokeidai_RotateOnHourChange(ObjTokeidai* thisx, PlayState* play); // clang-format on } +#define SETUP_DRAW(LIMB_MAX) \ + static bool initialized = false; \ + static SkelAnime skelAnime; \ + static Vec3s jointTable[LIMB_MAX]; \ + static Vec3s morphTable[LIMB_MAX]; \ + static u32 lastUpdate = 0; \ + OPEN_DISPS(gPlayState->state.gfxCtx); + +#define SETUP_DRAW_TYPE(LIMB_MAX, SKEL_HEADER, ANIM_HEADER, INIT_TYPE, HEADER_TYPE) \ + if (!initialized) { \ + initialized = true; \ + INIT_TYPE(gPlayState, &skelAnime, (HEADER_TYPE*)&SKEL_HEADER, (AnimationHeader*)&ANIM_HEADER, jointTable, \ + morphTable, LIMB_MAX); \ + } \ + if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { \ + lastUpdate = gPlayState->state.frames; \ + SkelAnime_Update(&skelAnime); \ + } + +#define SETUP_SKEL(LIMB_MAX, SKEL_HEADER, ANIM_HEADER) \ + SETUP_DRAW_TYPE(LIMB_MAX, SKEL_HEADER, ANIM_HEADER, SkelAnime_Init, SkeletonHeader) + +#define SETUP_FLEX_SKEL(LIMB_MAX, SKEL_HEADER, ANIM_HEADER) \ + SETUP_DRAW_TYPE(LIMB_MAX, SKEL_HEADER, ANIM_HEADER, SkelAnime_InitFlex, FlexSkeletonHeader) + // Soul Effects -void DrawEnLight(Color_RGB8 flameColor, Vec3f flameSize) { +extern void DrawEnLight(Color_RGB8 flameColor, Vec3f flameSize) { Gfx* sp68; static s8 unk_144 = (s8)(Rand_ZeroOne() * 255.0f); static u32 lastUpdate = 0; @@ -74,111 +153,1015 @@ void EnMinifrogPostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* } } -// Boss Souls -extern void DrawGoht() { - OPEN_DISPS(gPlayState->state.gfxCtx); +void DrawEnFirefly_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* firefly) { + static Color_RGBA8 auraPrimColor[2] = { { 255, 255, 100, 255 }, { 100, 200, 255, 255 } }; + static Color_RGBA8 auraEnvColor[2] = { { 255, 50, 0, 0 }, { 0, 0, 255, 0 } }; + static uint32_t dustUpdate = 0; + static bool auraColor = false; + static Vec3f auraVelocity = { 0, 0.5f, 0 }; + static Vec3f auraAccel = { 0, 0.5f, 0 }; + static Vec3f auraPos; + if (firefly != NULL) { + auraPos = firefly->world.pos; + } - Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); - Matrix_Translate(0.0f, -20.0f, 0.0f, MTXMODE_APPLY); - Matrix_Scale(0.005f, 0.005f, 0.005f, MTXMODE_APPLY); + Matrix_MultZero(&auraPos); + auraPos.x += Rand_ZeroOne() * 0.5f; + auraPos.y += Rand_ZeroOne() * 0.5f; + auraPos.z += Rand_ZeroOne() * 0.5f; - static bool initialized = false; - static SkelAnime skelAnime; - static Vec3s jointTable[33]; - static Vec3s otherTable[33]; - static u32 lastUpdate = 0; - if (!initialized) { - initialized = true; - SkelAnime_InitFlex(gPlayState, &skelAnime, (FlexSkeletonHeader*)&gGohtSkel, (AnimationHeader*)&gGohtRunAnim, - jointTable, otherTable, 33); + if (gPlayState != NULL && dustUpdate != gPlayState->state.frames) { + if (dustUpdate == gPlayState->state.frames - 20) { + dustUpdate = gPlayState->state.frames; + auraColor = !auraColor; + } } - if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { - lastUpdate = gPlayState->state.frames; - SkelAnime_Update(&skelAnime); + + if (limbIndex == FIRE_KEESE_LIMB_HEAD) { + Gfx* gfx = play->state.gfxCtx->polyXlu.p; + Scene_SetRenderModeXlu(play, 1, 2); + MATRIX_FINALIZE_AND_LOAD(gfx++, play->state.gfxCtx); + gSPDisplayList(gfx++, (Gfx*)&gKeeseRedEyesDL); } - gSPSegment(POLY_OPA_DISP++, 0x08, (uintptr_t)gGohtMetalPlateWithCirclePatternTex); + if (limbIndex == FIRE_KEESE_LIMB_LEFT_WING_END || limbIndex == FIRE_KEESE_LIMB_RIGHT_WING_END_ROOT) { + EffectSsDust_Spawn(gPlayState, 2, &auraPos, &auraVelocity, &auraAccel, &auraPrimColor[auraColor], + &auraEnvColor[auraColor], 100, -40, 3, 0); + } +} + +void DrawEnRealBombchu_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* rat) { + if (limbIndex == REAL_BOMBCHU_LIMB_TAIL_END) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_ReplaceRotation(&play->billboardMtxF); + MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, play->state.gfxCtx); + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)&gBombCapDL); + Matrix_RotateZYX(0x4000, 0, 0, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 80, 255); + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 10, 0, 40, 255); + MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, play->state.gfxCtx); + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)&gBombBodyDL); + CLOSE_DISPS(play->state.gfxCtx); + } +} + +s32 DrawEnSkb_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, Actor* thisx) { + s16 sins; + if (limbIndex == STALCHILD_LIMB_HEAD) { + OPEN_DISPS(play->state.gfxCtx); + + sins = fabsf(Math_SinS(play->gameplayFrames * 6000) * 95.0f) + 160.0f; + + gDPPipeSync(POLY_OPA_DISP++); + gDPSetEnvColor(POLY_OPA_DISP++, sins, sins, sins, 255); + + CLOSE_DISPS(play->state.gfxCtx); + } + return false; +} + +void DrawEnIk_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) { + if (limbIndex == IRON_KNUCKLE_LIMB_HELMET_ARMOR) { + OPEN_DISPS(play->state.gfxCtx); + + Gfx* xlu = POLY_XLU_DISP; + + MATRIX_FINALIZE_AND_LOAD(&xlu[0], play->state.gfxCtx); + gSPDisplayList(&xlu[1], (Gfx*)gIronKnuckleHelmetMarkingDL); + POLY_XLU_DISP = &xlu[2]; + + CLOSE_DISPS(play->state.gfxCtx); + } +} + +void EnKaizoku_TransformLimbDraw(PlayState* play, s32 limbIndex, Actor* thisx) { + // Even if this does nothing, it must exist, as TransformLimbDrawOpa is not null checked before invocation. +} + +// Enemy Soul Draw Functions +extern void DrawAlien() { + SETUP_DRAW(ALIEN_LIMB_MAX); + static uintptr_t eyeTexture = (uintptr_t)Lib_SegmentedToVirtual((TexturePtr)gAlienEyeTex); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.007f, 0.007f, 0.007f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(ALIEN_LIMB_MAX, gAlienSkel, gAlienFloatAnim); + + gSPSegment(POLY_OPA_DISP++, 0x08, eyeTexture); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 255, 255, 255); SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); CLOSE_DISPS(gPlayState->state.gfxCtx); DrawEnLight({ 10, 138, 46 }, { 30.0f, 30.0f, 30.0f }); } -extern void DrawGyorg() { - OPEN_DISPS(gPlayState->state.gfxCtx); - +extern void DrawArmos() { + SETUP_DRAW(OBJECT_AM_LIMB_MAX); Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); - Matrix_Translate(0.0f, -20.0f, 0.0f, MTXMODE_APPLY); - Matrix_Scale(0.05f, 0.05f, 0.05f, MTXMODE_APPLY); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -3100, 0, MTXMODE_APPLY); + SETUP_SKEL(OBJECT_AM_LIMB_MAX, object_am_Skel_005948, gArmosHopAnim); - static bool initialized = false; - static SkelAnime skelAnime; - static Vec3s jointTable[15]; - static Vec3s otherTable[15]; + gDPSetEnvColor(POLY_OPA_DISP++, 255, 255, 255, 255); + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawBat() { static u32 lastUpdate = 0; - if (!initialized) { - initialized = true; - SkelAnime_InitFlex(gPlayState, &skelAnime, (FlexSkeletonHeader*)&gGyorgSkel, - (AnimationHeader*)&gGyorgGentleSwimmingAnim, jointTable, otherTable, 15); - } + static u32 wingAnim = 0; + + OPEN_DISPS(gPlayState->state.gfxCtx); + Matrix_Scale(0.02f, 0.02f, 0.02f, MTXMODE_APPLY); + + static Gfx* sWingsDLs[] = { + (Gfx*)&gBadBatWingsFrame0DL, (Gfx*)&gBadBatWingsFrame1DL, (Gfx*)&gBadBatWingsFrame2DL, + (Gfx*)&gBadBatWingsFrame3DL, (Gfx*)&gBadBatWingsFrame4DL, (Gfx*)&gBadBatWingsFrame5DL, + (Gfx*)&gBadBatWingsFrame6DL, (Gfx*)&gBadBatWingsFrame7DL, (Gfx*)&gBadBatWingsFrame8DL, + }; + + Gfx* gfx = POLY_OPA_DISP; + if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { lastUpdate = gPlayState->state.frames; - SkelAnime_Update(&skelAnime); + if (wingAnim == 8) { + wingAnim = 0; + } else { + wingAnim++; + } + } + + gSPDisplayList(&gfx[0], gSetupDLs[SETUPDL_25]); + MATRIX_FINALIZE_AND_LOAD(&gfx[1], gPlayState->state.gfxCtx); + gSPDisplayList(&gfx[2], (Gfx*)&gBadBatSetupDL); + gSPDisplayList(&gfx[3], (Gfx*)&gBadBatBodyDL); + gSPDisplayList(&gfx[4], sWingsDLs[wingAnim]); + + POLY_OPA_DISP = &gfx[5]; + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 6.0f, 6.0f, 6.0f }); +} + +extern void DrawBeamos() { + SETUP_DRAW(BEAMOS_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -3200, 0, MTXMODE_APPLY); + SETUP_SKEL(BEAMOS_LIMB_MAX, gBeamosSkel, gBeamosAnim); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawBoe() { + static Color_RGBA8 D_80A4F7C4 = { 0, 0, 0, 255 }; + + OPEN_DISPS(gPlayState->state.gfxCtx); + + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -1200, 0, MTXMODE_APPLY); + + gSPDisplayList(POLY_OPA_DISP++, gSetupDLs[SETUPDL_25]); + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0xFF, 0, 0, 0, 255); + gSPSegment(POLY_OPA_DISP++, 0x08, (uintptr_t)D_801AEFA0); + MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, gPlayState->state.gfxCtx); + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gBlackBoeEndDL); + + gSPDisplayList(POLY_XLU_DISP++, gSetupDLs[SETUPDL_25]); + gDPSetEnvColor(POLY_XLU_DISP++, 255, 255, 255, 255); + gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gBlackBoeBodyMaterialDL); + Matrix_ReplaceRotation(&gPlayState->billboardMtxF); + MATRIX_FINALIZE_AND_LOAD(POLY_XLU_DISP++, gPlayState->state.gfxCtx); + gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gBlackBoeBodyModelDL); + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0xFF, 245, 97, 0, 255); + gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gBlackBoeEyesDL); + Matrix_Scale(0.009f, 0.009f, 0.009f, MTXMODE_APPLY); + MATRIX_FINALIZE_AND_LOAD(POLY_XLU_DISP++, gPlayState->state.gfxCtx); + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0xFF, 245, 214, 0, 255); + gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gBlackBoeEyesDL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 1000.0f, 1000.0f, 1000.0f }); +} + +extern void DrawBubble() { + SETUP_DRAW(BUBBLE_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.02f, 0.02f, 0.02f, MTXMODE_APPLY); + SETUP_SKEL(BUBBLE_LIMB_MAX, gBubbleSkel, gBubbleFlyingAnim); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawCaptainKeeta() { + SETUP_DRAW(OBJECT_BSB_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -3500.0f, 0, MTXMODE_APPLY); + SETUP_SKEL(OBJECT_BSB_LIMB_MAX, object_bsb_Skel_00C3E0, object_bsb_Anim_004894); + + gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 255); + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 255, 192, 0 }, { 5.0f, 10.0f, 5.0f }); +} + +extern void DrawChuchu() { + static int16_t timer = 25; + f32 timerFactor = sqrtf(timer) * 0.2f; + static AnimatedMaterial* sSlimeTexAnim = (AnimatedMaterial*)Lib_SegmentedToVirtual((void*)gChuchuSlimeFlowTexAnim); + + OPEN_DISPS(gPlayState->state.gfxCtx); + Matrix_Scale( + 0.01f, + ((((coss(RAD_TO_BINANG(timer * (2.0f * M_PI / 5.0f))) * SHT_MINV) * (0.07f * timerFactor)) + 1.0f) * 0.01f), + 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -2700.0f, 0, MTXMODE_APPLY); + + Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); + AnimatedMat_Draw(gPlayState, sSlimeTexAnim); + gDPSetPrimColor(POLY_XLU_DISP++, 0, 100, 255, 255, 200, 255); + gDPSetEnvColor(POLY_XLU_DISP++, 255, 180, 0, 255); + + if (timer == 0) { + timer = 25; } + + MATRIX_FINALIZE_AND_LOAD(POLY_XLU_DISP++, gPlayState->state.gfxCtx); + Scene_SetRenderModeXlu(gPlayState, 1, 2); + gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gChuchuBodyDL); + gSPSegment(POLY_XLU_DISP++, 9, (uintptr_t)gChuchuEyeOpenTex); + gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gChuchuEyesDL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); + timer--; +} + +extern void DrawDeathArmos() { + SETUP_DRAW(FAMOS_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + AnimatedMat_Draw(gPlayState, (AnimatedMaterial*)gFamosNormalGlowingEmblemTexAnim); + Matrix_Scale(0.008f, 0.008f, 0.008f, MTXMODE_APPLY); + Matrix_Translate(0, -4100, 0, MTXMODE_APPLY); + SETUP_SKEL(FAMOS_LIMB_MAX, gFamosSkel, gFamosIdleAnim); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawDeepPython() { + SETUP_DRAW(DEEP_PYTHON_LIMB_MAX); + Matrix_Scale(0.02f, 0.02f, 0.02f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(DEEP_PYTHON_LIMB_MAX, gDeepPythonSkel, gDeepPythonUnusedSideSwayAnim); + + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); CLOSE_DISPS(gPlayState->state.gfxCtx); - DrawEnLight({ 19, 99, 165 }, { 3.0f, 3.0f, 3.0f }); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); } -extern void DrawOdolwa() { +extern void DrawDekuBaba() { + SETUP_DRAW(DEKUBABA_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.02f, 0.02f, 0.02f, MTXMODE_APPLY); + SETUP_SKEL(DEKUBABA_LIMB_MAX, gDekuBabaSkel, gDekuBabaFastChompAnim); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 6.0f, 6.0f, 6.0f }); +} + +extern void DrawDexihand() { + SETUP_DRAW(DEXIHAND_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.02f, 0.02f, 0.02f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(DEXIHAND_LIMB_MAX, gDexihandSkel, gDexihandIdleAnim); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 70 }, { 6.0f, 6.0f, 6.0f }); +} + +extern void DrawDinolfos() { + static uintptr_t eyeTexture = (uintptr_t)Lib_SegmentedToVirtual((TexturePtr)gDinolfosEyeOpenTex); + SETUP_DRAW(DINOLFOS_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.014f, 0.014f, 0.014f, MTXMODE_APPLY); + Matrix_Translate(0, -2200.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(DINOLFOS_LIMB_MAX, gDinolfosSkel, gDinolfosIdleAnim); + + Scene_SetRenderModeXlu(gPlayState, 0, 1); + gSPSegment(POLY_OPA_DISP++, 0x08, eyeTexture); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 255, 255, 255); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawDodongo() { + SETUP_DRAW(OBJECT_DODONGO_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.015f, 0.015f, 0.015f, MTXMODE_APPLY); + Matrix_Translate(0, -1500.0f, 0, MTXMODE_APPLY); + SETUP_SKEL(OBJECT_DODONGO_LIMB_MAX, object_dodongo_Skel_008318, object_dodongo_Anim_004C20); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawDragonfly() { + SETUP_DRAW(DRAGONFLY_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -700.0f, 0, MTXMODE_APPLY); + SETUP_SKEL(DRAGONFLY_LIMB_MAX, gDragonflySkel, gDragonflyFlyAnim); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawEeno() { + SETUP_DRAW(EENO_LIMB_MAX); + Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -3000.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(EENO_LIMB_MAX, gEenoSkel, gEenoIdleAnim); + + Scene_SetRenderModeXlu(gPlayState, 0, 1); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 35 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawEyegore() { + static AnimatedMaterial* sEyegoreEyeLaserTexAnim = + (AnimatedMaterial*)Lib_SegmentedToVirtual((void*)gEyegoreEyeLaserTexAnim); + SETUP_DRAW(EYEGORE_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.006f, 0.006f, 0.006f, MTXMODE_APPLY); + Matrix_Translate(0, -4000.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(EYEGORE_LIMB_MAX, gEyegoreSkel, gEyegoreUnusedWalkAnim); + + AnimatedMat_Draw(gPlayState, sEyegoreEyeLaserTexAnim); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + POLY_OPA_DISP = Play_SetFog(gPlayState, POLY_OPA_DISP); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 192, 192, 64 }, { 20.0f, 20.0f, 20.0f }); +} + +extern void DrawFlyingPot() { + OPEN_DISPS(gPlayState->state.gfxCtx); + Matrix_Scale(0.3f, 0.3f, 0.3f, MTXMODE_APPLY); + Matrix_Translate(0, -100.0f, 0, MTXMODE_APPLY); + + Gfx_DrawDListOpa(gPlayState, (Gfx*)gameplay_dangeon_keep_DL_017EA0); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 0.4f, 0.4f, 0.4f }); +} + +extern void DrawFreezard() { OPEN_DISPS(gPlayState->state.gfxCtx); + Matrix_Scale(0.006f, 0.006f, 0.006f, MTXMODE_APPLY); + Matrix_Translate(0, -4100.0f, 0, MTXMODE_APPLY); + Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); + + gSPSegment(POLY_XLU_DISP++, 0x08, + (uintptr_t)Gfx_TwoTexScroll(gPlayState->state.gfxCtx, 0, 0, gPlayState->state.frames % 128, 0x20, 0x20, + 1, 0, (gPlayState->state.frames * 2) % 128, 0x20, 0x20)); + MATRIX_FINALIZE_AND_LOAD(POLY_XLU_DISP++, gPlayState->state.gfxCtx); + gDPSetCombineLERP(POLY_XLU_DISP++, TEXEL1, PRIMITIVE, PRIM_LOD_FRAC, TEXEL0, TEXEL1, TEXEL0, PRIMITIVE, TEXEL0, + PRIMITIVE, ENVIRONMENT, COMBINED, ENVIRONMENT, COMBINED, 0, ENVIRONMENT, 0); + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0x80, 155, 255, 255, 255); + gDPSetEnvColor(POLY_XLU_DISP++, 200, 200, 200, 255); + gSPDisplayList(POLY_XLU_DISP++, (Gfx*)object_fz_DL_001130); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 20.0f, 20.0f, 20.0f }); +} + +extern void DrawGaro() { + SETUP_DRAW(GARO_LIMB_MAX); + Matrix_Scale(0.03f, 0.03f, 0.03f, MTXMODE_APPLY); + Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + SETUP_FLEX_SKEL(GARO_LIMB_MAX, gGaroSkel, gGaroIdleAnim); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 150, 255, 150 }, { 8.0f, 8.0f, 8.0f }); +} + +extern void DrawGekko() { + SETUP_DRAW(GEKKO_LIMB_MAX); + Matrix_Scale(0.006f, 0.006f, 0.006f, MTXMODE_APPLY); + Matrix_Translate(0, -4100.0f, 0, MTXMODE_APPLY); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + SETUP_FLEX_SKEL(GEKKO_LIMB_MAX, gGekkoSkel, gGekkoBoxingStanceAnim); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 150, 100, 255 }, { 20.0f, 20.0f, 20.0f }); +} + +extern void DrawGiantBee() { + SETUP_DRAW(OBJECT_BEE_LIMB_MAX); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); - Matrix_Translate(0.0f, -20.0f, 0.0f, MTXMODE_APPLY); + SETUP_SKEL(OBJECT_BEE_LIMB_MAX, gBeeSkel, gBeeFlyingAnim); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawGomess() { + static AnimatedMaterial* bodyMatAnim = (AnimatedMaterial*)Lib_SegmentedToVirtual((void*)&gGomessBodyMatAnim); + static AnimatedMaterial* coreMatAnim = (AnimatedMaterial*)Lib_SegmentedToVirtual((void*)&gGomessCoreMatAnim); + SETUP_DRAW(GOMESS_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); Matrix_Scale(0.005f, 0.005f, 0.005f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(GOMESS_LIMB_MAX, gGomessSkel, gGomessFloatAnim); + + AnimatedMat_DrawStepOpa(gPlayState, bodyMatAnim, 23); + AnimatedMat_DrawOpa(gPlayState, coreMatAnim); + Scene_SetRenderModeXlu(gPlayState, 0, 1); + gDPSetEnvColor(POLY_OPA_DISP++, 30, 30, 0, 255); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 0, 0 }, { 15.0f, 15.0f, 15.0f }); +} + +extern void DrawGuay() { + SETUP_DRAW(OBJECT_CROW_LIMB_MAX); + Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.02f, 0.02f, 0.02f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(OBJECT_CROW_LIMB_MAX, gGuaySkel, gGuayFlyAnim); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 6.0f, 6.0f, 6.0f }); +} + +extern void DrawHiploop() { + SETUP_DRAW(HIPLOOP_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.02f, 0.02f, 0.02f, MTXMODE_APPLY); + Matrix_Translate(0, -1400.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(HIPLOOP_LIMB_MAX, gHiploopSkel, gHiploopChargeAnim); + + Scene_SetRenderModeXlu(gPlayState, 0, 1); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawIgosDuIkana() { + SETUP_DRAW(IGOS_LIMB_MAX); + gSPSegment(POLY_OPA_DISP++, 0x0A, (uintptr_t)EnKnight_BuildEmptyDL(gPlayState->state.gfxCtx)); + gSPSegment(POLY_XLU_DISP++, 0x0A, (uintptr_t)EnKnight_BuildEmptyDL(gPlayState->state.gfxCtx)); + gSPSegment(POLY_OPA_DISP++, 0x09, (uintptr_t)EnKnight_BuildEmptyDL(gPlayState->state.gfxCtx)); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -2000.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(IGOS_LIMB_MAX, gIgosSkel, gKnightIdleAnim); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + POLY_OPA_DISP = Play_SetFog(gPlayState, POLY_OPA_DISP); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 0, 0, 0 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawIronKnuckle() { + SETUP_DRAW(IRON_KNUCKLE_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -2900.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(IRON_KNUCKLE_LIMB_MAX, gIronKnuckleSkel, gIronKnuckleWalkAnim); + Gfx* gfx = POLY_XLU_DISP; + gSPDisplayList(&gfx[0], gSetupDLs[SETUPDL_25]); + POLY_XLU_DISP = &gfx[1]; + gfx = POLY_OPA_DISP; + gSPDisplayList(&gfx[0], gSetupDLs[SETUPDL_25]); + gSPSegment(&gfx[1], 0x08, (uintptr_t)gIronKnuckleBlackArmorMaterialDL); + gSPSegment(&gfx[2], 0x09, (uintptr_t)gIronKnuckleBrownArmorMaterialDL); + gSPSegment(&gfx[3], 0x0A, (uintptr_t)gIronKnuckleBrownArmorMaterialDL); + POLY_OPA_DISP = &gfx[4]; + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, + DrawEnIk_PostLimbDraw, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 12.0f, 12.0f, 12.0f }); +} + +extern void DrawKeese() { + SETUP_DRAW(FIRE_KEESE_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -700.0f, 0, MTXMODE_APPLY); + SETUP_SKEL(FIRE_KEESE_LIMB_MAX, gFireKeeseSkel, gFireKeeseFlyAnim); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, DrawEnFirefly_PostLimbDraw, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawLeever() { + SETUP_DRAW(LEEVER_LIMB_MAX); + // The Leever animation already spins in the same direction as the Get Item animation, which looks really fast. + // Reverse the animation so that it spins more slowly. + skelAnime.playSpeed = -1.0f; + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.05f, 0.05f, 0.05f, MTXMODE_APPLY); + Matrix_Translate(0, -700.0f, 0, MTXMODE_APPLY); + SETUP_SKEL(LEEVER_LIMB_MAX, gLeeverSkel, gLeeverSpinAnim); + + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0x01, 255, 255, 255, 255); + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 3.0f, 3.0f, 3.0f }); +} + +extern void DrawLikeLike() { static bool initialized = false; - static SkelAnime skelAnime; - static Vec3s jointTable[52]; - static Vec3s otherTable[52]; static u32 lastUpdate = 0; + static s16 textureScroll = 0; + static struct { + f32 unk_08; + f32 unk_10; + f32 unk_00; + Vec3s unk_1A; + } segments[5]; + if (!initialized) { initialized = true; - SkelAnime_InitFlex(gPlayState, &skelAnime, (FlexSkeletonHeader*)&gOdolwaSkel, - (AnimationHeader*)&gOdolwaReadyAnim, jointTable, otherTable, 52); + for (int i = 0; i < 5; i++) { + segments[i].unk_08 = 0.8f; + segments[i].unk_10 = 0.0f; + segments[i].unk_00 = 0.0f; + segments[i].unk_1A.x = 0; + segments[i].unk_1A.y = 0; + segments[i].unk_1A.z = 0; + } } + if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { lastUpdate = gPlayState->state.frames; - SkelAnime_Update(&skelAnime); + textureScroll++; + + f32 phase = gPlayState->state.frames * (2500.0f * (2.0f * M_PI / 65536.0f)); + + for (int j = 0; j < 5; j++) { + f32 segmentPhase = phase + (j * 0x4000) * (2.0f * M_PI / 65536.0f); + segments[j].unk_10 = cosf(segmentPhase) * 0.15f; + segments[j].unk_00 = 0.0f; + } + + for (int j = 1; j < 5; j++) { + segments[j].unk_1A.x = (s16)(cosf(phase + (j * 0x3000) * (2.0f * M_PI / 65536.0f)) * 2048.0f); + segments[j].unk_1A.z = (s16)(sinf(phase + (j * 0x1000) * (2.0f * M_PI / 65536.0f)) * 2048.0f); + } } - SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + Mtx* mtx = (Mtx*)GRAPH_ALLOC(gPlayState->state.gfxCtx, 4 * sizeof(Mtx)); + s32 i; + f32 temp_f20; + + OPEN_DISPS(gPlayState->state.gfxCtx); + + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -3000.0f, 0, MTXMODE_APPLY); + Matrix_Push(); + + gSPSegment(POLY_OPA_DISP++, 0x0C, (uintptr_t)mtx); + gSPSegment(POLY_OPA_DISP++, 0x08, + (uintptr_t)Gfx_TwoTexScroll(gPlayState->state.gfxCtx, 0, 0, 0, 0x20, 0x10, 1, (textureScroll * 0) & 0x3F, + (textureScroll * -6) & 0x7F, 0x20, 0x10)); + + Matrix_Push(); + Matrix_Scale((1.0f + segments[0].unk_10) * segments[0].unk_08, 1.0f, + (1.0f + segments[0].unk_10) * segments[0].unk_08, MTXMODE_APPLY); + + MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, gPlayState->state.gfxCtx); + + Matrix_Pop(); + + for (i = 1; i < 5; i++) { + temp_f20 = segments[i].unk_08 * (segments[i].unk_10 + 1.0f); + + Matrix_Translate(0.0f, segments[i].unk_00 + 1000.0f, 0.0f, MTXMODE_APPLY); + Matrix_RotateZYX(segments[i].unk_1A.x, segments[i].unk_1A.y, segments[i].unk_1A.z, MTXMODE_APPLY); + Matrix_Push(); + Matrix_Scale(temp_f20, 1.0f, temp_f20, MTXMODE_APPLY); + Matrix_ToMtx(mtx); + Matrix_Pop(); + mtx++; + } + + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gLikeLikeDL); CLOSE_DISPS(gPlayState->state.gfxCtx); - DrawEnLight({ 145, 20, 133 }, { 25.0f, 25.0f, 25.0f }); + Matrix_Pop(); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); } -extern void DrawTwinmold() { - OPEN_DISPS(gPlayState->state.gfxCtx); +extern void DrawMadScrub() { + SETUP_DRAW(DEKU_SCRUB_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -2300, 0, MTXMODE_APPLY); + SETUP_SKEL(DEKU_SCRUB_LIMB_MAX, gDekuScrubSkel, gDekuScrubLookAroundAnim); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} +extern void DrawNejiron() { + SETUP_DRAW(NEJIRON_LIMB_MAX); Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); - Matrix_Scale(0.06f, 0.06f, 0.06f, MTXMODE_APPLY); + Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); + Matrix_Scale(0.015f, 0.015f, 0.015f, MTXMODE_APPLY); + SETUP_SKEL(NEJIRON_LIMB_MAX, gNejironSkel, gNejironIdleAnim); + + gSPSegment(POLY_OPA_DISP++, 8, (uintptr_t)gNejironEyeOpenTex); + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 13.0f, 13.0f, 13.0f }); +} + +extern void DrawOctorok() { + SETUP_DRAW(OCTOROK_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.007f, 0.007f, 0.007f, MTXMODE_APPLY); + Matrix_Translate(0, -700.0f, 0, MTXMODE_APPLY); + SETUP_SKEL(OCTOROK_LIMB_MAX, gOctorokSkel, gOctorokFloatAnim); + + Gfx* gfxPtr = POLY_OPA_DISP; + gSPDisplayList(&gfxPtr[0], gSetupDLs[SETUPDL_25]); + gSPSegment(&gfxPtr[1], 0x08, (uintptr_t)D_801AEFA0); + POLY_OPA_DISP = &gfxPtr[2]; + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0x01, 255, 255, 255, 255); + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawPeahat() { + SETUP_DRAW(OBJECT_PH_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + SETUP_SKEL(OBJECT_PH_LIMB_MAX, object_ph_Skel_001C80, object_ph_Anim_0009C4); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawPirate() { + static uintptr_t eyeTexture = (uintptr_t)Lib_SegmentedToVirtual((TexturePtr)gFighterPirateEyeOpenTex); + SETUP_DRAW(KAIZOKU_LIMB_MAX); + Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -2000.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(KAIZOKU_LIMB_MAX, gFighterPirateSkel, gFighterPirateFightingIdleAnim); + + gSPSegment(POLY_OPA_DISP++, 0x08, eyeTexture); + SkelAnime_DrawTransformFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, + NULL, EnKaizoku_TransformLimbDraw, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawPoe() { + SETUP_DRAW(POE_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.0075f, 0.0075f, 0.0075f, MTXMODE_APPLY); + Matrix_Translate(0, -5000.0f, 0, MTXMODE_APPLY); + SETUP_SKEL(POE_LIMB_MAX, gPoeSkel, gPoeFloatAnim); + + gSPSegment(POLY_OPA_DISP++, 0x08, (uintptr_t)D_801AEFA0); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 255, 255, 255); + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawRealBombchu() { + SETUP_DRAW(REAL_BOMBCHU_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Gfx_SetupDL60_XluNoCD(gPlayState->state.gfxCtx); + Matrix_Scale(0.02f, 0.02f, 0.02f, MTXMODE_APPLY); + Matrix_Translate(0, -1500.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(REAL_BOMBCHU_LIMB_MAX, gRealBombchuSkel, gRealBombchuRunAnim); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, + DrawEnRealBombchu_PostLimbDraw, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 6.0f, 6.0f, 6.0f }); +} + +extern void DrawRedead() { + static u32 animUpdate = 0; + static uint32_t rdAnimID = 0; + static AnimationHeader* currentAnim = (AnimationHeader*)gGibdoRedeadIdleAnim; + static std::vector rdAnims = { + (AnimationHeader*)gGibdoRedeadSquattingDanceAnim, + (AnimationHeader*)gGibdoRedeadClappingDanceAnim, + (AnimationHeader*)gGibdoRedeadPirouetteAnim, + }; + SETUP_DRAW(REDEAD_LIMB_MAX); + + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Gfx_SetupDL60_XluNoCD(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -2900.0f, 0, MTXMODE_APPLY); - static bool initialized = false; - static SkelAnime skelAnime; - static Vec3s jointTable[13]; - static Vec3s otherTable[13]; - static u32 lastUpdate = 0; if (!initialized) { initialized = true; - SkelAnime_InitFlex(gPlayState, &skelAnime, (FlexSkeletonHeader*)&gTwinmoldHeadSkel, - (AnimationHeader*)&gTwinmoldHeadFlyAnim, jointTable, otherTable, 13); + SkelAnime_InitFlex(gPlayState, &skelAnime, (FlexSkeletonHeader*)&gRedeadSkel, + (AnimationHeader*)gGibdoRedeadPirouetteAnim, jointTable, morphTable, REDEAD_LIMB_MAX); } + if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { + if (EnRd_ShouldNotDance(gPlayState)) { + currentAnim = (AnimationHeader*)gGibdoRedeadIdleAnim; + Animation_MorphToLoop(&skelAnime, currentAnim, -6.0f); + } else if (animUpdate != gPlayState->state.frames && animUpdate <= gPlayState->state.frames - 35) { + animUpdate = gPlayState->state.frames; + currentAnim = rdAnims[rdAnimID]; + if (rdAnimID >= rdAnims.size() - 1) { + rdAnimID = 0; + } else { + rdAnimID++; + } + Animation_MorphToLoop(&skelAnime, currentAnim, -6.0f); + } lastUpdate = gPlayState->state.frames; SkelAnime_Update(&skelAnime); } + gSPSegment(POLY_OPA_DISP++, 0x08, (uintptr_t)D_801AEFA0); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawShellBlade() { + SETUP_DRAW(OBJECT_SB_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.007f, 0.007f, 0.007f, MTXMODE_APPLY); + Matrix_Translate(0, -3500.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(OBJECT_SB_LIMB_MAX, object_sb_Skel_002BF0, object_sb_Anim_000194); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawSkullfish() { + SETUP_DRAW(OBJECT_PR_2_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.02f, 0.02f, 0.02f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(OBJECT_PR_2_LIMB_MAX, object_pr_Skel_004188, object_pr_Anim_004340); + + gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 255); + Scene_SetRenderModeXlu(gPlayState, 0, 1); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 5.0f, 5.0f, 5.0f }); +} + +extern void DrawSkulltula() { + SETUP_DRAW(OBJECT_ST_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.03f, 0.03f, 0.03f, MTXMODE_APPLY); + SETUP_SKEL(OBJECT_ST_LIMB_MAX, object_st_Skel_005298, object_st_Anim_000304); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 5.0f, 5.0f, 5.0f }); +} + +extern void DrawSnapper() { + SETUP_DRAW(SNAPPER_LIMB_MAX); + static uintptr_t eyeTexture = (uintptr_t)Lib_SegmentedToVirtual((TexturePtr)gSnapperEyeOpenTex); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -3100.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(SNAPPER_LIMB_MAX, gSnapperSkel, gSnapperIdleAnim); + + gSPSegment(POLY_OPA_DISP++, 0x08, eyeTexture); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawStalchild() { + SETUP_DRAW(STALCHILD_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -3200, 0, MTXMODE_APPLY); + SETUP_SKEL(STALCHILD_LIMB_MAX, gStalchildSkel, gStalchildIdleAnim); + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, DrawEnSkb_OverrideLimbDraw, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawTakkuri() { + SETUP_DRAW(TAKKURI_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(TAKKURI_LIMB_MAX, gTakkuriSkel, gTakkuriFlyAnim); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawTektite() { + static TexturePtr D_80896B24[2][3] = { + { (TexturePtr*)&object_tite_Tex_001300, (TexturePtr*)&object_tite_Tex_001700, + (TexturePtr*)&object_tite_Tex_001900 }, + { (TexturePtr*)&object_tite_Tex_001B00, (TexturePtr*)&object_tite_Tex_001F00, + (TexturePtr*)&object_tite_Tex_002100 }, + }; + SETUP_DRAW(OBJECT_TITE_LIMB_MAX); + + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -2900.0f, 0, MTXMODE_APPLY); + SETUP_SKEL(OBJECT_TITE_LIMB_MAX, object_tite_Skel_003A20, object_tite_Anim_0012E4); + + Gfx* gfx = POLY_OPA_DISP; + + gSPDisplayList(&gfx[0], gSetupDLs[SETUPDL_25]); + + gSPSegment(&gfx[1], 0x08, (uintptr_t)D_80896B24[0][0]); + gSPSegment(&gfx[2], 0x09, (uintptr_t)D_80896B24[0][1]); + gSPSegment(&gfx[3], 0x0A, (uintptr_t)D_80896B24[0][2]); + + POLY_OPA_DISP = &gfx[4]; + + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawWallmaster() { + SETUP_DRAW(WALLMASTER_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -3500.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(WALLMASTER_LIMB_MAX, gWallmasterSkel, gWallmasterIdleAnim); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawWart() { + SETUP_DRAW(WART_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.02f, 0.02f, 0.02f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(WART_LIMB_MAX, gWartSkel, gWartIdleAnim); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +extern void DrawWizrobe() { + static uintptr_t eyeTexture = (uintptr_t)Lib_SegmentedToVirtual((TexturePtr)gWizrobeEyeTex); + SETUP_DRAW(WIZROBE_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Translate(0.0f, -20.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.006f, 0.006f, 0.006f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(WIZROBE_LIMB_MAX, gWizrobeSkel, gWizrobeIdleAnim); + + Scene_SetRenderModeXlu(gPlayState, 0, 1); + gSPSegment(POLY_OPA_DISP++, 0x08, eyeTexture); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 255, 255, 255); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 15.0f, 15.0f, 15.0f }); +} + +extern void DrawWolfos() { + SETUP_DRAW(WOLFOS_NORMAL_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.01f, 0.01f, 0.01f, MTXMODE_APPLY); + Matrix_Translate(0, -3000.0f, 0, MTXMODE_APPLY); + SETUP_FLEX_SKEL(WOLFOS_NORMAL_LIMB_MAX, gWolfosNormalSkel, gWolfosWaitAnim); + + gSPSegment(POLY_OPA_DISP++, 0x08, (uintptr_t)&gWolfosNormalEyeOpenTex); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +// Boss Souls +extern void DrawGoht() { + SETUP_DRAW(GOHT_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Translate(0.0f, -20.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.005f, 0.005f, 0.005f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(GOHT_LIMB_MAX, gGohtSkel, gGohtRunAnim); + + gSPSegment(POLY_OPA_DISP++, 0x08, (uintptr_t)gGohtMetalPlateWithCirclePatternTex); + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 10, 138, 46 }, { 30.0f, 30.0f, 30.0f }); +} + +extern void DrawGyorg() { + SETUP_DRAW(GYORG_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Translate(0.0f, -20.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.05f, 0.05f, 0.05f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(GYORG_LIMB_MAX, gGyorgSkel, gGyorgGentleSwimmingAnim); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 19, 99, 165 }, { 3.0f, 3.0f, 3.0f }); +} + +extern void DrawOdolwa() { + SETUP_DRAW(ODOLWA_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); + Matrix_Translate(0.0f, -20.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.005f, 0.005f, 0.005f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(ODOLWA_LIMB_MAX, gOdolwaSkel, gOdolwaReadyAnim); + + SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 145, 20, 133 }, { 25.0f, 25.0f, 25.0f }); +} + +extern void DrawTwinmold() { + SETUP_DRAW(TWINMOLD_HEAD_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + Matrix_Scale(0.06f, 0.06f, 0.06f, MTXMODE_APPLY); + SETUP_FLEX_SKEL(TWINMOLD_HEAD_LIMB_MAX, gTwinmoldHeadSkel, gTwinmoldHeadFlyAnim); + Mtx* mtxHead = (Mtx*)GRAPH_ALLOC(gPlayState->state.gfxCtx, 23 * sizeof(Mtx)); gSPSegment(POLY_OPA_DISP++, 0x0D, (uintptr_t)mtxHead); gSPSegment(POLY_OPA_DISP++, 0x08, (uintptr_t)gTwinmoldBlueSkinTex); @@ -189,27 +1172,12 @@ extern void DrawTwinmold() { } extern void DrawMajora() { - static bool initialized = false; - static SkelAnime skelAnime; - static Vec3s jointTable[MAJORAS_MASK_LIMB_MAX]; - static Vec3s morphTable[MAJORAS_MASK_LIMB_MAX]; - static u32 lastUpdate = 0; - - OPEN_DISPS(gPlayState->state.gfxCtx); + SETUP_DRAW(MAJORAS_MASK_LIMB_MAX); Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); Matrix_Scale(0.05f, 0.05f, 0.05f, MTXMODE_APPLY); - Matrix_Translate(0, 0, 0, MTXMODE_APPLY); - - if (!initialized) { - initialized = true; - SkelAnime_Init(gPlayState, &skelAnime, (SkeletonHeader*)&gMajorasMaskSkel, - (AnimationHeader*)&gMajorasMaskFloatingAnim, jointTable, morphTable, MAJORAS_MASK_LIMB_MAX); - } - if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { - lastUpdate = gPlayState->state.frames; - SkelAnime_Update(&skelAnime); - } + Matrix_ReplaceRotation(&gPlayState->billboardMtxF); + SETUP_SKEL(MAJORAS_MASK_LIMB_MAX, gMajorasMaskSkel, gMajorasMaskFloatingAnim); gSPSegment(POLY_OPA_DISP++, 8, (uintptr_t)gMajorasMaskWithNormalEyesTex); SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); @@ -221,27 +1189,12 @@ extern void DrawMajora() { // Other Actors extern void DrawMinifrog(RandoItemId randoItemId, Actor* actor) { - OPEN_DISPS(gPlayState->state.gfxCtx); - + SETUP_DRAW(FROG_LIMB_MAX); Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); Matrix_Translate(0.0f, -20.0f, 0.0f, MTXMODE_APPLY); Matrix_Scale(0.03f, 0.03f, 0.03f, MTXMODE_APPLY); - - static bool initialized = false; - static SkelAnime skelAnime; - static Vec3s jointTable[FROG_LIMB_MAX]; - static Vec3s otherTable[FROG_LIMB_MAX]; + SETUP_FLEX_SKEL(FROG_LIMB_MAX, gFrogSkel, gFrogIdleAnim); Color_RGBA8 envColor = { 200, 170, 0, 255 }; // FROG_YELLOW - static u32 lastUpdate = 0; - if (!initialized) { - initialized = true; - SkelAnime_InitFlex(gPlayState, &skelAnime, (FlexSkeletonHeader*)&gFrogSkel, (AnimationHeader*)&gFrogIdleAnim, - jointTable, otherTable, FROG_LIMB_MAX); - } - if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { - lastUpdate = gPlayState->state.frames; - SkelAnime_Update(&skelAnime); - } switch (randoItemId) { case RI_FROG_BLUE: diff --git a/mm/2s2h/Rando/DrawFuncs.h b/mm/2s2h/Rando/DrawFuncs.h index 818e73efb6..99623fdb9d 100644 --- a/mm/2s2h/Rando/DrawFuncs.h +++ b/mm/2s2h/Rando/DrawFuncs.h @@ -3,6 +3,8 @@ #include "Rando/Rando.h" +void DrawEnLight(Color_RGB8 flameColor, Vec3f flameSize); + // Boss Functions void DrawGoht(); void DrawGyorg(); @@ -10,6 +12,57 @@ void DrawMajora(); void DrawOdolwa(); void DrawTwinmold(); +// Enemy Functions +void DrawAlien(); +void DrawArmos(); +void DrawBat(); +void DrawBeamos(); +void DrawBoe(); +void DrawBubble(); +void DrawCaptainKeeta(); +void DrawChuchu(); +void DrawDeathArmos(); +void DrawDeepPython(); +void DrawDekuBaba(); +void DrawDexihand(); +void DrawDinolfos(); +void DrawDodongo(); +void DrawDragonfly(); +void DrawEeno(); +void DrawEyegore(); +void DrawFlyingPot(); +void DrawFreezard(); +void DrawGaro(); +void DrawGekko(); +void DrawGiantBee(); +void DrawGomess(); +void DrawGuay(); +void DrawHiploop(); +void DrawIgosDuIkana(); +void DrawIronKnuckle(); +void DrawKeese(); +void DrawLeever(); +void DrawLikeLike(); +void DrawMadScrub(); +void DrawNejiron(); +void DrawOctorok(); +void DrawPeahat(); +void DrawPirate(); +void DrawPoe(); +void DrawRealBombchu(); +void DrawRedead(); +void DrawShellBlade(); +void DrawSkullfish(); +void DrawSkulltula(); +void DrawSnapper(); +void DrawStalchild(); +void DrawTakkuri(); +void DrawTektite(); +void DrawWallmaster(); +void DrawWart(); +void DrawWizrobe(); +void DrawWolfos(); + // Other Actor Functions void DrawMinifrog(RandoItemId randoItemId, Actor* actor); diff --git a/mm/2s2h/Rando/DrawItem.cpp b/mm/2s2h/Rando/DrawItem.cpp index 29d9f87110..dd8eae0ace 100644 --- a/mm/2s2h/Rando/DrawItem.cpp +++ b/mm/2s2h/Rando/DrawItem.cpp @@ -401,6 +401,65 @@ void DrawAbilityItem(RandoItemId randoItemId, Actor* actor) { CLOSE_DISPS(gPlayState->state.gfxCtx); } +// clang-format off +std::unordered_map> soulDrawMap = { + { RI_SOUL_ENEMY_ALIEN, DrawAlien }, + { RI_SOUL_ENEMY_ARMOS, DrawArmos }, + { RI_SOUL_ENEMY_BAD_BAT, DrawBat }, + { RI_SOUL_ENEMY_BEAMOS, DrawBeamos }, + { RI_SOUL_ENEMY_BUBBLE, DrawBubble }, + { RI_SOUL_ENEMY_BOE, DrawBoe }, + { RI_SOUL_ENEMY_CHUCHU, DrawChuchu }, + { RI_SOUL_ENEMY_CAPTAIN_KEETA, DrawCaptainKeeta }, + { RI_SOUL_ENEMY_DEATH_ARMOS, DrawDeathArmos }, + { RI_SOUL_ENEMY_DEEP_PYTHON, DrawDeepPython }, + { RI_SOUL_ENEMY_DEKU_BABA, DrawDekuBaba }, + { RI_SOUL_ENEMY_DEXIHAND, DrawDexihand }, + { RI_SOUL_ENEMY_DINOLFOS, DrawDinolfos }, + { RI_SOUL_ENEMY_DODONGO, DrawDodongo }, + { RI_SOUL_ENEMY_DRAGONFLY, DrawDragonfly }, + { RI_SOUL_ENEMY_EENO, DrawEeno }, + { RI_SOUL_ENEMY_EYEGORE, DrawEyegore }, + { RI_SOUL_ENEMY_FREEZARD, DrawFreezard }, + { RI_SOUL_ENEMY_GARO, DrawGaro }, + { RI_SOUL_ENEMY_GEKKO, DrawGekko }, + { RI_SOUL_ENEMY_GIANT_BEE, DrawGiantBee }, + { RI_SOUL_ENEMY_GOMESS, DrawGomess }, + { RI_SOUL_ENEMY_GUAY, DrawGuay }, + { RI_SOUL_ENEMY_HIPLOOP, DrawHiploop }, + { RI_SOUL_ENEMY_IGOS_DU_IKANA, DrawIgosDuIkana }, + { RI_SOUL_ENEMY_IRON_KNUCKLE, DrawIronKnuckle }, + { RI_SOUL_ENEMY_KEESE, DrawKeese }, + { RI_SOUL_ENEMY_LEEVER, DrawLeever }, + { RI_SOUL_ENEMY_LIKE_LIKE, DrawLikeLike }, + { RI_SOUL_ENEMY_MAD_SCRUB, DrawMadScrub }, + { RI_SOUL_ENEMY_NEJIRON, DrawNejiron }, + { RI_SOUL_ENEMY_OCTOROK, DrawOctorok }, + { RI_SOUL_ENEMY_PEAHAT, DrawPeahat }, + { RI_SOUL_ENEMY_PIRATE, DrawPirate }, + { RI_SOUL_ENEMY_POE, DrawPoe }, + { RI_SOUL_ENEMY_REDEAD, DrawRedead }, + { RI_SOUL_ENEMY_SHELLBLADE, DrawShellBlade }, + { RI_SOUL_ENEMY_SKULLFISH, DrawSkullfish }, + { RI_SOUL_ENEMY_SKULLTULA, DrawSkulltula }, + { RI_SOUL_ENEMY_SNAPPER, DrawSnapper }, + { RI_SOUL_ENEMY_STALCHILD, DrawStalchild }, + { RI_SOUL_ENEMY_TAKKURI, DrawTakkuri }, + { RI_SOUL_ENEMY_TEKTITE, DrawTektite }, + { RI_SOUL_ENEMY_WALLMASTER, DrawWallmaster }, + { RI_SOUL_ENEMY_WART, DrawWart }, + { RI_SOUL_ENEMY_WIZROBE, DrawWizrobe }, + { RI_SOUL_ENEMY_WOLFOS, DrawWolfos }, +}; +// clang-format on + +void DrawSoul(RandoItemId randoItemId) { + auto it = soulDrawMap.find(randoItemId); + if (it != soulDrawMap.end()) { + it->second(); + } +} + void DrawSparkles(RandoItemId randoItemId, Actor* actor) { if (actor == NULL) { return; @@ -514,19 +573,68 @@ void Rando::DrawItem(RandoItemId randoItemId, Actor* actor) { case RI_PROGRESSIVE_WALLET: Rando::DrawItem(Rando::ConvertItem(randoItemId), actor); break; - case RI_SOUL_GOHT: + case RI_SOUL_ENEMY_ALIEN: + case RI_SOUL_ENEMY_ARMOS: + case RI_SOUL_ENEMY_BAD_BAT: + case RI_SOUL_ENEMY_BEAMOS: + case RI_SOUL_ENEMY_BOE: + case RI_SOUL_ENEMY_BUBBLE: + case RI_SOUL_ENEMY_CAPTAIN_KEETA: + case RI_SOUL_ENEMY_CHUCHU: + case RI_SOUL_ENEMY_DEATH_ARMOS: + case RI_SOUL_ENEMY_DEEP_PYTHON: + case RI_SOUL_ENEMY_DEKU_BABA: + case RI_SOUL_ENEMY_DEXIHAND: + case RI_SOUL_ENEMY_DINOLFOS: + case RI_SOUL_ENEMY_DODONGO: + case RI_SOUL_ENEMY_DRAGONFLY: + case RI_SOUL_ENEMY_EENO: + case RI_SOUL_ENEMY_EYEGORE: + case RI_SOUL_ENEMY_FREEZARD: + case RI_SOUL_ENEMY_GARO: + case RI_SOUL_ENEMY_GEKKO: + case RI_SOUL_ENEMY_GIANT_BEE: + case RI_SOUL_ENEMY_GOMESS: + case RI_SOUL_ENEMY_GUAY: + case RI_SOUL_ENEMY_HIPLOOP: + case RI_SOUL_ENEMY_IGOS_DU_IKANA: + case RI_SOUL_ENEMY_IRON_KNUCKLE: + case RI_SOUL_ENEMY_KEESE: + case RI_SOUL_ENEMY_LEEVER: + case RI_SOUL_ENEMY_LIKE_LIKE: + case RI_SOUL_ENEMY_MAD_SCRUB: + case RI_SOUL_ENEMY_NEJIRON: + case RI_SOUL_ENEMY_OCTOROK: + case RI_SOUL_ENEMY_PEAHAT: + case RI_SOUL_ENEMY_PIRATE: + case RI_SOUL_ENEMY_POE: + case RI_SOUL_ENEMY_REDEAD: + case RI_SOUL_ENEMY_SHELLBLADE: + case RI_SOUL_ENEMY_SKULLFISH: + case RI_SOUL_ENEMY_SKULLTULA: + case RI_SOUL_ENEMY_SNAPPER: + case RI_SOUL_ENEMY_STALCHILD: + case RI_SOUL_ENEMY_TAKKURI: + case RI_SOUL_ENEMY_TEKTITE: + case RI_SOUL_ENEMY_WALLMASTER: + case RI_SOUL_ENEMY_WART: + case RI_SOUL_ENEMY_WIZROBE: + case RI_SOUL_ENEMY_WOLFOS: + DrawSoul(randoItemId); + break; + case RI_SOUL_BOSS_GOHT: DrawGoht(); break; - case RI_SOUL_GYORG: + case RI_SOUL_BOSS_GYORG: DrawGyorg(); break; - case RI_SOUL_MAJORA: + case RI_SOUL_BOSS_MAJORA: DrawMajora(); break; - case RI_SOUL_ODOLWA: + case RI_SOUL_BOSS_ODOLWA: DrawOdolwa(); break; - case RI_SOUL_TWINMOLD: + case RI_SOUL_BOSS_TWINMOLD: DrawTwinmold(); break; case RI_FROG_BLUE: diff --git a/mm/2s2h/Rando/GiveItem.cpp b/mm/2s2h/Rando/GiveItem.cpp index 6c6a234617..8778fd5a00 100644 --- a/mm/2s2h/Rando/GiveItem.cpp +++ b/mm/2s2h/Rando/GiveItem.cpp @@ -1,4 +1,5 @@ #include "Rando/Rando.h" +#include "Rando/ActorBehavior/Souls.h" #include "Rando/MiscBehavior/MiscBehavior.h" #include "Rando/MiscBehavior/ClockShuffle.h" @@ -111,8 +112,8 @@ void Rando::GiveItem(RandoItemId randoItemId) { if (gSaveContext.save.shipSaveInfo.rando.foundTriforcePieces == RANDO_SAVE_OPTIONS[RO_TRIFORCE_PIECES_REQUIRED]) { // Blocks the ability to beat the game through killing Majora until all Triforce Pieces are found. - if (!Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_MAJORA)) { - Rando::GiveItem(RI_SOUL_MAJORA); + if (!Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_BOSS_MAJORA)) { + Rando::GiveItem(RI_SOUL_BOSS_MAJORA); } GameInteractor_ExecuteOnGameCompletion(); GameInteractor::Instance->events.emplace_back( @@ -289,12 +290,59 @@ void Rando::GiveItem(RandoItemId randoItemId) { // ITEM_POTION_RED will put a Red Potion bottle on the first bottle slot Item_Give(gPlayState, ITEM_LONGSHOT); break; - case RI_SOUL_GOHT: - case RI_SOUL_GYORG: - case RI_SOUL_MAJORA: - case RI_SOUL_ODOLWA: - case RI_SOUL_TWINMOLD: - Flags_SetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_GOHT + (randoItemId - RI_SOUL_GOHT)); + case RI_SOUL_BOSS_GOHT: + case RI_SOUL_BOSS_GYORG: + case RI_SOUL_BOSS_MAJORA: + case RI_SOUL_BOSS_ODOLWA: + case RI_SOUL_BOSS_TWINMOLD: + case RI_SOUL_ENEMY_ALIEN: + case RI_SOUL_ENEMY_ARMOS: + case RI_SOUL_ENEMY_BAD_BAT: + case RI_SOUL_ENEMY_BEAMOS: + case RI_SOUL_ENEMY_BOE: + case RI_SOUL_ENEMY_BUBBLE: + case RI_SOUL_ENEMY_CAPTAIN_KEETA: + case RI_SOUL_ENEMY_CHUCHU: + case RI_SOUL_ENEMY_DEATH_ARMOS: + case RI_SOUL_ENEMY_DEEP_PYTHON: + case RI_SOUL_ENEMY_DEKU_BABA: + case RI_SOUL_ENEMY_DEXIHAND: + case RI_SOUL_ENEMY_DINOLFOS: + case RI_SOUL_ENEMY_DODONGO: + case RI_SOUL_ENEMY_DRAGONFLY: + case RI_SOUL_ENEMY_EENO: + case RI_SOUL_ENEMY_EYEGORE: + case RI_SOUL_ENEMY_FREEZARD: + case RI_SOUL_ENEMY_GARO: + case RI_SOUL_ENEMY_GEKKO: + case RI_SOUL_ENEMY_GIANT_BEE: + case RI_SOUL_ENEMY_GOMESS: + case RI_SOUL_ENEMY_GUAY: + case RI_SOUL_ENEMY_HIPLOOP: + case RI_SOUL_ENEMY_IGOS_DU_IKANA: + case RI_SOUL_ENEMY_IRON_KNUCKLE: + case RI_SOUL_ENEMY_KEESE: + case RI_SOUL_ENEMY_LEEVER: + case RI_SOUL_ENEMY_LIKE_LIKE: + case RI_SOUL_ENEMY_MAD_SCRUB: + case RI_SOUL_ENEMY_NEJIRON: + case RI_SOUL_ENEMY_OCTOROK: + case RI_SOUL_ENEMY_PEAHAT: + case RI_SOUL_ENEMY_PIRATE: + case RI_SOUL_ENEMY_POE: + case RI_SOUL_ENEMY_REDEAD: + case RI_SOUL_ENEMY_SHELLBLADE: + case RI_SOUL_ENEMY_SKULLFISH: + case RI_SOUL_ENEMY_SKULLTULA: + case RI_SOUL_ENEMY_SNAPPER: + case RI_SOUL_ENEMY_STALCHILD: + case RI_SOUL_ENEMY_TAKKURI: + case RI_SOUL_ENEMY_TEKTITE: + case RI_SOUL_ENEMY_WALLMASTER: + case RI_SOUL_ENEMY_WART: + case RI_SOUL_ENEMY_WIZROBE: + case RI_SOUL_ENEMY_WOLFOS: + Flags_SetRandoInf(SOUL_RI_TO_RANDO_INF(randoItemId)); break; case RI_FROG_BLUE: SET_WEEKEVENTREG(WEEKEVENTREG_33_01); diff --git a/mm/2s2h/Rando/Logic/GeneratePools.cpp b/mm/2s2h/Rando/Logic/GeneratePools.cpp index c3385ab338..99b1aac709 100644 --- a/mm/2s2h/Rando/Logic/GeneratePools.cpp +++ b/mm/2s2h/Rando/Logic/GeneratePools.cpp @@ -168,14 +168,21 @@ void GeneratePools(RandoSaveInfo& saveInfo, std::vector& checkPool // Boss Souls if (saveInfo.randoSaveOptions[RO_SHUFFLE_BOSS_SOULS] == RO_GENERIC_YES) { - for (int i = RI_SOUL_GOHT; i <= RI_SOUL_TWINMOLD; i++) { - if (i == RI_SOUL_MAJORA && saveInfo.randoSaveOptions[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) { + for (int i = RI_SOUL_BOSS_GOHT; i <= RI_SOUL_BOSS_TWINMOLD; i++) { + if (i == RI_SOUL_BOSS_MAJORA && saveInfo.randoSaveOptions[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) { continue; } itemPool.push_back((RandoItemId)i); } } + // Enemy Souls + if (saveInfo.randoSaveOptions[RO_SHUFFLE_ENEMY_SOULS] == RO_GENERIC_YES) { + for (int i = RI_SOUL_ENEMY_ALIEN; i <= RI_SOUL_ENEMY_WOLFOS; i++) { + itemPool.push_back((RandoItemId)i); + } + } + // Initialize shuffle time settings and item pool ClockShuffle::InitializeFileClocks(itemPool); diff --git a/mm/2s2h/Rando/Logic/Logic.h b/mm/2s2h/Rando/Logic/Logic.h index 7654fc8c85..f22e7bdd6a 100644 --- a/mm/2s2h/Rando/Logic/Logic.h +++ b/mm/2s2h/Rando/Logic/Logic.h @@ -2,6 +2,7 @@ #define RANDO_LOGIC_H #include "Rando/Rando.h" +#include "Rando/ActorBehavior/Souls.h" #include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/ShipUtils.h" @@ -509,24 +510,29 @@ inline bool ClockFilter() { AFTER(TIME_NIGHT3_AM_12_00)) inline bool CanKillEnemy(ActorId EnemyId) { + // If enemy souls are shuffled, and the relevant soul is not obtained, we cannot kill that enemy. + if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENEMY_SOULS] && !HaveEnemySoul(EnemyId)) { + return false; + } + switch (EnemyId) { case ACTOR_BOSS_01: // Odolwa return (CAN_USE_SWORD || CAN_BE_GORON || CAN_BE_ZORA || CAN_USE_EXPLOSIVE || CAN_USE_MAGIC_ARROW(FIRE) || CAN_USE_MAGIC_ARROW(LIGHT)) && - (Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_ODOLWA) || + (Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_BOSS_ODOLWA) || RANDO_SAVE_OPTIONS[RO_SHUFFLE_BOSS_SOULS] == RO_GENERIC_NO); case ACTOR_BOSS_02: // Twinmold return (HAS_ITEM(ITEM_BOW) || (HAS_ITEM(ITEM_MASK_GIANT) && HAS_MAGIC && CAN_USE_HUMAN_SWORD)) && - (Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_TWINMOLD) || + (Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_BOSS_TWINMOLD) || RANDO_SAVE_OPTIONS[RO_SHUFFLE_BOSS_SOULS] == RO_GENERIC_NO); case ACTOR_BOSS_03: // Gyorg return ((CAN_BE_DEITY && HAS_MAGIC) || (CAN_BE_ZORA && HAS_MAGIC)) && - (Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_GYORG) || + (Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_BOSS_GYORG) || RANDO_SAVE_OPTIONS[RO_SHUFFLE_BOSS_SOULS] == RO_GENERIC_NO); case ACTOR_BOSS_04: // Wart return (HAS_ITEM(ITEM_BOW) || HAS_ITEM(ITEM_HOOKSHOT) || CAN_BE_ZORA); case ACTOR_BOSS_HAKUGIN: // Goht - return (CAN_USE_MAGIC_ARROW(FIRE)) && (Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_GOHT) || + return (CAN_USE_MAGIC_ARROW(FIRE)) && (Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_BOSS_GOHT) || RANDO_SAVE_OPTIONS[RO_SHUFFLE_BOSS_SOULS] == RO_GENERIC_NO); case ACTOR_EN_KNIGHT: // Igos du Ikana/IdI Lackey return (CAN_USE_MAGIC_ARROW(FIRE) && @@ -535,7 +541,7 @@ inline bool CanKillEnemy(ActorId EnemyId) { case ACTOR_EN_KAIZOKU: // Fighter Pirate return (CAN_USE_SWORD || CAN_BE_ZORA); case ACTOR_EN_PAMETFROG: // Woodfall Temple Gekko (and Snapper) - return (HAS_ITEM(ITEM_BOW) && (CAN_BE_DEKU || CAN_USE_EXPLOSIVE || CAN_BE_GORON)); + return (HAS_ITEM(ITEM_BOW) && CanKillEnemy(ACTOR_EN_BIGPAMET)); case ACTOR_EN_BIGSLIME: // Great Bay Temple Gekko return (CAN_USE_MAGIC_ARROW(ICE)); case ACTOR_EN_SW: // Gold Skulltula & Skullwalltula diff --git a/mm/2s2h/Rando/Logic/Regions/East.cpp b/mm/2s2h/Rando/Logic/Regions/East.cpp index 7227f7e5a2..514ae967c5 100644 --- a/mm/2s2h/Rando/Logic/Regions/East.cpp +++ b/mm/2s2h/Rando/Logic/Regions/East.cpp @@ -158,6 +158,7 @@ static RegisterShipInitFunc initFunc([]() { EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 9), ONE_WAY_EXIT, CAN_USE_ABILITY(SWIM)), }, .connections = { + // Octorok soul not needed; the player can also create ice platforms on the water itself. CONNECTION(RR_IKANA_CANYON_UPPER, HAS_ITEM(ITEM_HOOKSHOT) && CAN_USE_MAGIC_ARROW(ICE)), CONNECTION(RR_IKANA_CANYON_GROTTO, CAN_USE_ABILITY(SWIM)), // TODO: Grotto mapping }, diff --git a/mm/2s2h/Rando/Logic/Regions/GreatBayTemple.cpp b/mm/2s2h/Rando/Logic/Regions/GreatBayTemple.cpp index 5ac3ed0c79..59bc244c31 100644 --- a/mm/2s2h/Rando/Logic/Regions/GreatBayTemple.cpp +++ b/mm/2s2h/Rando/Logic/Regions/GreatBayTemple.cpp @@ -294,7 +294,7 @@ static RegisterShipInitFunc initFunc([]() { CONNECTION(RR_GREAT_BAY_TEMPLE_BEFORE_WART, KEY_COUNT(GREAT_BAY_TEMPLE) >= 1), }, .events = { - EVENT(RE_GREAT_BAY_RED_SWITCH_1, CAN_USE_MAGIC_ARROW(ICE)), + EVENT(RE_GREAT_BAY_RED_SWITCH_1, CAN_USE_MAGIC_ARROW(ICE) && Flags_GetRandoInf(RANDO_INF_OBTAINED_SOUL_OF_ENEMY_OCTOROKS)), } }; Regions[RR_GREAT_BAY_TEMPLE_RED_PIPE_SWITCH_ROOM] = RandoRegion{ .sceneId = SCENE_SEA, diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index f141b1443c..7e14418a94 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -36,12 +36,15 @@ std::unordered_map accessTrialsOptions = { { RO_ACCESS_TRIALS_OPEN, "Open" }, }; +// clang-format off std::vector incompatibleWithVanilla = { RO_SHUFFLE_BOSS_SOULS, RO_SHUFFLE_SWIM, + RO_SHUFFLE_ENEMY_SOULS, RO_PLENTIFUL_ITEMS, RO_CLOCK_SHUFFLE, }; +// clang-format on std::vector checkExclusionList; bool isExcludedInitialized = false; @@ -205,6 +208,7 @@ static RegisterShipInitFunc refreshMetricsInit(RefreshMetrics, { "gRando.Options.RO_SHUFFLE_COWS", "gRando.Options.RO_SHUFFLE_CRATE_DROPS", "gRando.Options.RO_SHUFFLE_ENEMY_DROPS", + "gRando.Options.RO_SHUFFLE_ENEMY_SOULS", "gRando.Options.RO_SHUFFLE_FREESTANDING_ITEMS", "gRando.Options.RO_SHUFFLE_FROGS", "gRando.Options.RO_SHUFFLE_GOLD_SKULLTULAS", @@ -440,8 +444,12 @@ static void DrawItemsTab() { .disabledTooltip = "Incompatible with current Logic Setting" } })); CVarCheckbox("Enemy Drops", Rando::StaticData::Options[RO_SHUFFLE_ENEMY_DROPS].cvar, CheckboxOptions({ { .tooltip = "Shuffles the first drop from a non Boss Enemy." } })); - CVarCheckbox("Enemy Souls", "gPlaceholderBool", - CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } })); + CVarCheckbox( + "Enemy Souls", Rando::StaticData::Options[RO_SHUFFLE_ENEMY_SOULS].cvar, + CheckboxOptions({ { .tooltip = "Adds the \"souls\" of regular enemies to the item pool. Enemy Souls are items " + "that must be found in order for their corresponding enemy to spawn.", + .disabled = IncompatibleWithLogicSetting(RO_SHUFFLE_ENEMY_SOULS), + .disabledTooltip = "Incompatible with current Logic Setting" } })); CVarCheckbox("Shuffle Time", Rando::StaticData::Options[RO_CLOCK_SHUFFLE].cvar, CheckboxOptions({ { .tooltip = "Breaks the 3-day cycle into 6 separate half-days (Day 1 Day/Night, " "Day 2 Day/Night, Day 3 Day/Night) that must be unlocked as items. " diff --git a/mm/2s2h/Rando/RemoveItem.cpp b/mm/2s2h/Rando/RemoveItem.cpp index 3ecfe33318..f7bebe0a83 100644 --- a/mm/2s2h/Rando/RemoveItem.cpp +++ b/mm/2s2h/Rando/RemoveItem.cpp @@ -1,4 +1,5 @@ #include "Rando/Rando.h" +#include "Rando/ActorBehavior/Souls.h" #include "Rando/MiscBehavior/ClockShuffle.h" extern "C" { @@ -377,12 +378,59 @@ void Rando::RemoveItem(RandoItemId randoItemId) { case RI_REMAINS_TWINMOLD: REMOVE_QUEST_ITEM(QUEST_REMAINS_TWINMOLD); break; - case RI_SOUL_GOHT: - case RI_SOUL_GYORG: - case RI_SOUL_MAJORA: - case RI_SOUL_ODOLWA: - case RI_SOUL_TWINMOLD: - Flags_ClearRandoInf(RANDO_INF_OBTAINED_SOUL_OF_GOHT + (randoItemId - RI_SOUL_GOHT)); + case RI_SOUL_BOSS_GOHT: + case RI_SOUL_BOSS_GYORG: + case RI_SOUL_BOSS_MAJORA: + case RI_SOUL_BOSS_ODOLWA: + case RI_SOUL_BOSS_TWINMOLD: + case RI_SOUL_ENEMY_ALIEN: + case RI_SOUL_ENEMY_ARMOS: + case RI_SOUL_ENEMY_BAD_BAT: + case RI_SOUL_ENEMY_BEAMOS: + case RI_SOUL_ENEMY_BOE: + case RI_SOUL_ENEMY_BUBBLE: + case RI_SOUL_ENEMY_CAPTAIN_KEETA: + case RI_SOUL_ENEMY_CHUCHU: + case RI_SOUL_ENEMY_DEATH_ARMOS: + case RI_SOUL_ENEMY_DEEP_PYTHON: + case RI_SOUL_ENEMY_DEKU_BABA: + case RI_SOUL_ENEMY_DEXIHAND: + case RI_SOUL_ENEMY_DINOLFOS: + case RI_SOUL_ENEMY_DODONGO: + case RI_SOUL_ENEMY_DRAGONFLY: + case RI_SOUL_ENEMY_EENO: + case RI_SOUL_ENEMY_EYEGORE: + case RI_SOUL_ENEMY_FREEZARD: + case RI_SOUL_ENEMY_GARO: + case RI_SOUL_ENEMY_GEKKO: + case RI_SOUL_ENEMY_GIANT_BEE: + case RI_SOUL_ENEMY_GOMESS: + case RI_SOUL_ENEMY_GUAY: + case RI_SOUL_ENEMY_HIPLOOP: + case RI_SOUL_ENEMY_IGOS_DU_IKANA: + case RI_SOUL_ENEMY_IRON_KNUCKLE: + case RI_SOUL_ENEMY_KEESE: + case RI_SOUL_ENEMY_LEEVER: + case RI_SOUL_ENEMY_LIKE_LIKE: + case RI_SOUL_ENEMY_MAD_SCRUB: + case RI_SOUL_ENEMY_NEJIRON: + case RI_SOUL_ENEMY_OCTOROK: + case RI_SOUL_ENEMY_PEAHAT: + case RI_SOUL_ENEMY_PIRATE: + case RI_SOUL_ENEMY_POE: + case RI_SOUL_ENEMY_REDEAD: + case RI_SOUL_ENEMY_SHELLBLADE: + case RI_SOUL_ENEMY_SKULLFISH: + case RI_SOUL_ENEMY_SKULLTULA: + case RI_SOUL_ENEMY_SNAPPER: + case RI_SOUL_ENEMY_STALCHILD: + case RI_SOUL_ENEMY_TAKKURI: + case RI_SOUL_ENEMY_TEKTITE: + case RI_SOUL_ENEMY_WALLMASTER: + case RI_SOUL_ENEMY_WART: + case RI_SOUL_ENEMY_WIZROBE: + case RI_SOUL_ENEMY_WOLFOS: + Flags_ClearRandoInf(SOUL_RI_TO_RANDO_INF(randoItemId)); break; case RI_FROG_BLUE: CLEAR_WEEKEVENTREG(WEEKEVENTREG_33_01); diff --git a/mm/2s2h/Rando/StaticData/Items.cpp b/mm/2s2h/Rando/StaticData/Items.cpp index 4676d34351..50b156fc92 100644 --- a/mm/2s2h/Rando/StaticData/Items.cpp +++ b/mm/2s2h/Rando/StaticData/Items.cpp @@ -171,11 +171,58 @@ std::map Items = { RI(RI_SONG_STORMS, "the", "Song of Storms", RITYPE_MAJOR, ITEM_SONG_STORMS, GI_NONE, GID_NONE), RI(RI_SONG_SUN, "the", "Sun's Song", RITYPE_MAJOR, ITEM_SONG_SUN, GI_NONE, GID_NONE), RI(RI_SONG_TIME, "the", "Song of Time", RITYPE_MAJOR, ITEM_SONG_TIME, GI_NONE, GID_NONE), - RI(RI_SOUL_GOHT, "the", "Soul of Goht", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), - RI(RI_SOUL_GYORG, "the", "Soul of Gyorg", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), - RI(RI_SOUL_MAJORA, "the", "Soul of Majora", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), - RI(RI_SOUL_ODOLWA, "the", "Soul of Odolwa", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), - RI(RI_SOUL_TWINMOLD, "the", "Soul of Twinmold", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_BOSS_GOHT, "the", "Soul of Goht", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_BOSS_GYORG, "the", "Soul of Gyorg", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_BOSS_MAJORA, "the", "Soul of Majora", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_BOSS_ODOLWA, "the", "Soul of Odolwa", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_BOSS_TWINMOLD, "the", "Soul of Twinmold", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_ALIEN, "the", "Soul of Aliens", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_ARMOS, "the", "Soul of Armos", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_BAD_BAT, "the", "Soul of Bad Bats", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_BEAMOS, "the", "Soul of Beamos", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_BOE, "the", "Soul of Boes", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_BUBBLE, "the", "Soul of Bubbles", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_CHUCHU, "the", "Soul of Chuchus", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_CAPTAIN_KEETA, "the", "Soul of Captain Keeta", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_DEATH_ARMOS, "the", "Soul of Death Armos", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_DEEP_PYTHON, "the", "Soul of Deep Pythons", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_DEKU_BABA, "the", "Soul of Deku Babas", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_DEXIHAND, "the", "Soul of Dexihands", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_DINOLFOS, "the", "Soul of Dinolfos", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_DODONGO, "the", "Soul of Dodongos", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_DRAGONFLY, "the", "Soul of Dragonflies", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_EENO, "the", "Soul of Eenos", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_EYEGORE, "the", "Soul of Eyegores", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_FREEZARD, "the", "Soul of Freezards", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_GARO, "the", "Soul of Garos", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_GEKKO, "the", "Soul of Gekkos", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_GIANT_BEE, "the", "Soul of Giant Bees", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_GOMESS, "the", "Soul of Gomess", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_GUAY, "the", "Soul of Guays", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_HIPLOOP, "the", "Soul of Hiploops", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_IGOS_DU_IKANA, "the", "Soul of Igos du Ikana", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_IRON_KNUCKLE, "the", "Soul of Iron Knuckles", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_KEESE, "the", "Soul of Keese", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_LEEVER, "the", "Soul of Leevers", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_LIKE_LIKE, "the", "Soul of Like Likes", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_MAD_SCRUB, "the", "Soul of Mad Scrubs", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_NEJIRON, "the", "Soul of Nejirons", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_OCTOROK, "the", "Soul of Octoroks", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_PEAHAT, "the", "Soul of Peahats", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_PIRATE, "the", "Soul of Pirates", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_POE, "the", "Soul of Poes", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_REDEAD, "the", "Soul of Redeads", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_SHELLBLADE, "the", "Soul of Shellblades", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_SKULLFISH, "the", "Soul of Skullfish", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_SKULLTULA, "the", "Soul of Skulltulas", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_SNAPPER, "the", "Soul of Snappers", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_STALCHILD, "the", "Soul of Stalchildren", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_TAKKURI, "the", "Soul of Takkuri", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_TEKTITE, "the", "Soul of Tektites", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_WALLMASTER, "the", "Soul of Wallmasters", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_WART, "the", "Soul of Warts", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_WIZROBE, "the", "Soul of Wizrobes", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), + RI(RI_SOUL_ENEMY_WOLFOS, "the", "Soul of Wolfos", RITYPE_MAJOR, ITEM_NONE, GI_NONE, GID_NONE), RI(RI_STONE_TOWER_BOSS_KEY, "the", "Stone Tower Boss Key", RITYPE_BOSS_KEY, ITEM_KEY_BOSS, GI_KEY_BOSS, GID_KEY_BOSS), RI(RI_STONE_TOWER_COMPASS, "the", "Stone Tower Compass", RITYPE_LESSER, ITEM_COMPASS, GI_COMPASS, GID_COMPASS), RI(RI_STONE_TOWER_MAP, "the", "Stone Tower Map", RITYPE_LESSER, ITEM_DUNGEON_MAP, GI_MAP, GID_DUNGEON_MAP), @@ -234,7 +281,7 @@ std::unordered_map> StartingItems RI_LETTER_TO_KAFEI, RI_PENDANT_OF_MEMORIES } }, { STARTING_ITEMS_MISC, - { RI_SOUL_GOHT, RI_SOUL_GYORG, RI_SOUL_MAJORA, RI_SOUL_ODOLWA, RI_SOUL_TWINMOLD, + { RI_SOUL_BOSS_GOHT, RI_SOUL_BOSS_GYORG, RI_SOUL_BOSS_MAJORA, RI_SOUL_BOSS_ODOLWA, RI_SOUL_BOSS_TWINMOLD, RI_FROG_BLUE, RI_FROG_CYAN, RI_FROG_PINK, RI_FROG_WHITE, RI_TIME_DAY_1, RI_TIME_DAY_2, RI_TIME_DAY_3, RI_TIME_NIGHT_1, RI_TIME_NIGHT_2, RI_TIME_NIGHT_3 } }, @@ -333,11 +380,58 @@ const char* GetIconTexturePath(RandoItemId randoItemId) { return (const char*)gItemIcons[ITEM_SONG_LULLABY]; case RI_PROGRESSIVE_MAGIC: return (const char*)gItemIcons[ITEM_MAGIC_JAR_SMALL]; - case RI_SOUL_GOHT: - case RI_SOUL_GYORG: - case RI_SOUL_MAJORA: - case RI_SOUL_ODOLWA: - case RI_SOUL_TWINMOLD: + case RI_SOUL_BOSS_GOHT: + case RI_SOUL_BOSS_GYORG: + case RI_SOUL_BOSS_MAJORA: + case RI_SOUL_BOSS_ODOLWA: + case RI_SOUL_BOSS_TWINMOLD: + case RI_SOUL_ENEMY_ALIEN: + case RI_SOUL_ENEMY_ARMOS: + case RI_SOUL_ENEMY_BAD_BAT: + case RI_SOUL_ENEMY_BEAMOS: + case RI_SOUL_ENEMY_BOE: + case RI_SOUL_ENEMY_BUBBLE: + case RI_SOUL_ENEMY_CAPTAIN_KEETA: + case RI_SOUL_ENEMY_CHUCHU: + case RI_SOUL_ENEMY_DEATH_ARMOS: + case RI_SOUL_ENEMY_DEEP_PYTHON: + case RI_SOUL_ENEMY_DEKU_BABA: + case RI_SOUL_ENEMY_DEXIHAND: + case RI_SOUL_ENEMY_DINOLFOS: + case RI_SOUL_ENEMY_DODONGO: + case RI_SOUL_ENEMY_DRAGONFLY: + case RI_SOUL_ENEMY_EENO: + case RI_SOUL_ENEMY_EYEGORE: + case RI_SOUL_ENEMY_FREEZARD: + case RI_SOUL_ENEMY_GARO: + case RI_SOUL_ENEMY_GEKKO: + case RI_SOUL_ENEMY_GIANT_BEE: + case RI_SOUL_ENEMY_GOMESS: + case RI_SOUL_ENEMY_GUAY: + case RI_SOUL_ENEMY_HIPLOOP: + case RI_SOUL_ENEMY_IGOS_DU_IKANA: + case RI_SOUL_ENEMY_IRON_KNUCKLE: + case RI_SOUL_ENEMY_KEESE: + case RI_SOUL_ENEMY_LEEVER: + case RI_SOUL_ENEMY_LIKE_LIKE: + case RI_SOUL_ENEMY_MAD_SCRUB: + case RI_SOUL_ENEMY_NEJIRON: + case RI_SOUL_ENEMY_OCTOROK: + case RI_SOUL_ENEMY_PEAHAT: + case RI_SOUL_ENEMY_PIRATE: + case RI_SOUL_ENEMY_POE: + case RI_SOUL_ENEMY_REDEAD: + case RI_SOUL_ENEMY_SHELLBLADE: + case RI_SOUL_ENEMY_SKULLFISH: + case RI_SOUL_ENEMY_SKULLTULA: + case RI_SOUL_ENEMY_SNAPPER: + case RI_SOUL_ENEMY_STALCHILD: + case RI_SOUL_ENEMY_TAKKURI: + case RI_SOUL_ENEMY_TEKTITE: + case RI_SOUL_ENEMY_WALLMASTER: + case RI_SOUL_ENEMY_WART: + case RI_SOUL_ENEMY_WIZROBE: + case RI_SOUL_ENEMY_WOLFOS: return (const char*)gDungeonMapSkullTex; case RI_FROG_BLUE: case RI_FROG_CYAN: diff --git a/mm/2s2h/Rando/StaticData/Options.cpp b/mm/2s2h/Rando/StaticData/Options.cpp index ed15501661..1af0b8d508 100644 --- a/mm/2s2h/Rando/StaticData/Options.cpp +++ b/mm/2s2h/Rando/StaticData/Options.cpp @@ -40,6 +40,7 @@ std::map Options = { RO(RO_SHUFFLE_COWS, RO_GENERIC_OFF), RO(RO_SHUFFLE_CRATE_DROPS, RO_GENERIC_OFF), RO(RO_SHUFFLE_ENEMY_DROPS, RO_GENERIC_OFF), + RO(RO_SHUFFLE_ENEMY_SOULS, RO_GENERIC_OFF), RO(RO_SHUFFLE_FREESTANDING_ITEMS, RO_GENERIC_OFF), RO(RO_SHUFFLE_FROGS, RO_GENERIC_OFF), RO(RO_SHUFFLE_GOLD_SKULLTULAS, RO_GENERIC_OFF), diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h index a6f6f4719a..922dc68b27 100644 --- a/mm/2s2h/Rando/Types.h +++ b/mm/2s2h/Rando/Types.h @@ -2457,11 +2457,58 @@ typedef enum { RI_SONG_STORMS, RI_SONG_SUN, RI_SONG_TIME, - RI_SOUL_GOHT, - RI_SOUL_GYORG, - RI_SOUL_MAJORA, - RI_SOUL_ODOLWA, - RI_SOUL_TWINMOLD, + RI_SOUL_BOSS_GOHT, + RI_SOUL_BOSS_GYORG, + RI_SOUL_BOSS_MAJORA, + RI_SOUL_BOSS_ODOLWA, + RI_SOUL_BOSS_TWINMOLD, + RI_SOUL_ENEMY_ALIEN, + RI_SOUL_ENEMY_ARMOS, + RI_SOUL_ENEMY_BAD_BAT, + RI_SOUL_ENEMY_BEAMOS, + RI_SOUL_ENEMY_BOE, + RI_SOUL_ENEMY_BUBBLE, + RI_SOUL_ENEMY_CAPTAIN_KEETA, + RI_SOUL_ENEMY_CHUCHU, + RI_SOUL_ENEMY_DEATH_ARMOS, + RI_SOUL_ENEMY_DEEP_PYTHON, + RI_SOUL_ENEMY_DEKU_BABA, + RI_SOUL_ENEMY_DEXIHAND, + RI_SOUL_ENEMY_DINOLFOS, + RI_SOUL_ENEMY_DODONGO, + RI_SOUL_ENEMY_DRAGONFLY, + RI_SOUL_ENEMY_EENO, + RI_SOUL_ENEMY_EYEGORE, + RI_SOUL_ENEMY_FREEZARD, + RI_SOUL_ENEMY_GARO, + RI_SOUL_ENEMY_GEKKO, + RI_SOUL_ENEMY_GIANT_BEE, + RI_SOUL_ENEMY_GOMESS, + RI_SOUL_ENEMY_GUAY, + RI_SOUL_ENEMY_HIPLOOP, + RI_SOUL_ENEMY_IGOS_DU_IKANA, + RI_SOUL_ENEMY_IRON_KNUCKLE, + RI_SOUL_ENEMY_KEESE, + RI_SOUL_ENEMY_LEEVER, + RI_SOUL_ENEMY_LIKE_LIKE, + RI_SOUL_ENEMY_MAD_SCRUB, + RI_SOUL_ENEMY_NEJIRON, + RI_SOUL_ENEMY_OCTOROK, + RI_SOUL_ENEMY_PEAHAT, + RI_SOUL_ENEMY_PIRATE, + RI_SOUL_ENEMY_POE, + RI_SOUL_ENEMY_REDEAD, + RI_SOUL_ENEMY_SHELLBLADE, + RI_SOUL_ENEMY_SKULLFISH, + RI_SOUL_ENEMY_SKULLTULA, + RI_SOUL_ENEMY_SNAPPER, + RI_SOUL_ENEMY_STALCHILD, + RI_SOUL_ENEMY_TAKKURI, + RI_SOUL_ENEMY_TEKTITE, + RI_SOUL_ENEMY_WALLMASTER, + RI_SOUL_ENEMY_WART, + RI_SOUL_ENEMY_WIZROBE, + RI_SOUL_ENEMY_WOLFOS, RI_STONE_TOWER_BOSS_KEY, RI_STONE_TOWER_COMPASS, RI_STONE_TOWER_MAP, @@ -2836,6 +2883,7 @@ typedef enum { RO_SHUFFLE_COWS, RO_SHUFFLE_CRATE_DROPS, RO_SHUFFLE_ENEMY_DROPS, + RO_SHUFFLE_ENEMY_SOULS, RO_SHUFFLE_FREESTANDING_ITEMS, RO_SHUFFLE_FROGS, RO_SHUFFLE_GRASS_DROPS, @@ -2912,11 +2960,59 @@ typedef enum { RANDO_INF_OBTAINED_LETTER_TO_MAMA, RANDO_INF_OBTAINED_LETTER_TO_KAFEI, RANDO_INF_OBTAINED_PENDANT_OF_MEMORIES, - RANDO_INF_OBTAINED_SOUL_OF_GOHT, - RANDO_INF_OBTAINED_SOUL_OF_GYORG, - RANDO_INF_OBTAINED_SOUL_OF_MAJORA, - RANDO_INF_OBTAINED_SOUL_OF_ODOLWA, - RANDO_INF_OBTAINED_SOUL_OF_TWINMOLD, + RANDO_INF_OBTAINED_SOUL_OF_BOSS_GOHT, + RANDO_INF_OBTAINED_SOUL_OF_BOSS_GYORG, + RANDO_INF_OBTAINED_SOUL_OF_BOSS_MAJORA, + RANDO_INF_OBTAINED_SOUL_OF_BOSS_ODOLWA, + RANDO_INF_OBTAINED_SOUL_OF_BOSS_TWINMOLD, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_ALIENS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_ARMOS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_BAD_BATS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_BEAMOS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_BOES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_BUBBLES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_CAPTAIN_KEETA, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_CHUCHUS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_DEATH_ARMOS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_DEEP_PYTHONS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_DEKU_BABAS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_DEXIHANDS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_DINOLFOS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_DODONGOS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_DRAGONFLIES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_EENOS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_EYEGORES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_FREEZARDS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_GAROS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_GEKKOS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_GIANT_BEES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_GOMESS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_GUAYS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_HIPLOOPS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_IGOS_DU_IKANA, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_IRON_KNUCKLES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_KEESE, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_LEEVERS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_LIKE_LIKES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_MAD_SCRUBS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_NEJIRONS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_OCTOROKS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_ODOLWA, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_PEAHATS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_PIRATES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_POES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_REDEADS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_SHELLBLADES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_SKULLFISH, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_SKULLTULAS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_SNAPPERS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_STALCHILDREN, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_TAKKURI, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_TEKTITES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_WALLMASTERS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_WARTS, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_WIZROBES, + RANDO_INF_OBTAINED_SOUL_OF_ENEMY_WOLFOS, RANDO_INF_OBTAINED_SWIM, RANDO_INF_OBTAINED_CLOCK_DAY_1, RANDO_INF_OBTAINED_CLOCK_NIGHT_1, diff --git a/mm/src/code/z_actor.c b/mm/src/code/z_actor.c index 6fed8a8753..03148315c8 100644 --- a/mm/src/code/z_actor.c +++ b/mm/src/code/z_actor.c @@ -648,9 +648,10 @@ void Attention_Draw(Attention* attention, PlayState* play) { actor->focus.pos.z, MTXMODE_NEW); Matrix_RotateYS(play->gameplayFrames * 0xBB8, MTXMODE_APPLY); Matrix_Scale((iREG(27) + 35) / 1000.0f, (iREG(28) + 60) / 1000.0f, (iREG(29) + 50) / 1000.0f, MTXMODE_APPLY); - - gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, attentionColor->primary.r, attentionColor->primary.g, - attentionColor->primary.b, 255); + if (GameInteractor_Should(VB_DRAW_LOCK_ON_ARROW, true, actor)) { + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, attentionColor->primary.r, attentionColor->primary.g, + attentionColor->primary.b, 255); + } MATRIX_FINALIZE_AND_LOAD(POLY_XLU_DISP++, play->state.gfxCtx); gSPDisplayList(POLY_XLU_DISP++, gLockOnArrowDL); FrameInterpolation_RecordCloseChild(); From 7829d8bf009c4632339d92022c0520536ccfa4e2 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:07:57 -0500 Subject: [PATCH 41/44] Cleanup --- .../Trackers/ItemTracker/ItemTrackerSettings.cpp | 5 +++-- mm/2s2h/Rando/ConvertItem.cpp | 6 ++++++ mm/2s2h/Rando/RemoveItem.cpp | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp index 7d0eedd0b5..e02be5b4e0 100644 --- a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp +++ b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTrackerSettings.cpp @@ -251,8 +251,9 @@ void DrawItemList(std::string listName, int columns) { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(5, 5)); std::vector emptyList; - if (listName == "Frogs" || listName == "Ocarina Buttons" || listName == "Boss Souls" || listName == "Enemy Souls" || - listName == "Owl Statues" || listName == "Tingle Maps" || listName == "Time" || listName == "Misc") { + if (listName == "Frogs" || listName == "Ocarina Buttons" || listName == "Boss Souls" || + listName == "Enemy Souls" || listName == "Owl Statues" || listName == "Tingle Maps" || + listName == "Time" || listName == "Misc") { for (int j = std::get<0>(randoItemLists.at(listName)); j <= std::get<1>(randoItemLists.at(listName)); j++) { ImGui::TableNextColumn(); diff --git a/mm/2s2h/Rando/ConvertItem.cpp b/mm/2s2h/Rando/ConvertItem.cpp index f6f6404bd4..d4e60c243c 100644 --- a/mm/2s2h/Rando/ConvertItem.cpp +++ b/mm/2s2h/Rando/ConvertItem.cpp @@ -486,6 +486,12 @@ bool Rando::IsItemObtainable(RandoItemId randoItemId, RandoCheckId randoCheckId) Rando::ClockItems::GetHalfDayIndexFromClockItem(randoItemId)); case RI_TIME_PROGRESSIVE: return true; + case RI_OCARINA_BUTTON_A: + case RI_OCARINA_BUTTON_C_DOWN: + case RI_OCARINA_BUTTON_C_LEFT: + case RI_OCARINA_BUTTON_C_RIGHT: + case RI_OCARINA_BUTTON_C_UP: + return !Flags_GetRandoInf(RANDO_INF_OBTAINED_OCARINA_BUTTON_A + (randoItemId - RI_OCARINA_BUTTON_A)); // These items are technically fine to receive again because they don't do anything, but we'll convert them to // ensure it's clear to the player something didn't go wrong. We just simply check the inventory state // Masks diff --git a/mm/2s2h/Rando/RemoveItem.cpp b/mm/2s2h/Rando/RemoveItem.cpp index f7bebe0a83..12c0ae1c29 100644 --- a/mm/2s2h/Rando/RemoveItem.cpp +++ b/mm/2s2h/Rando/RemoveItem.cpp @@ -444,6 +444,13 @@ void Rando::RemoveItem(RandoItemId randoItemId) { case RI_FROG_WHITE: CLEAR_WEEKEVENTREG(WEEKEVENTREG_33_02); break; + case RI_OCARINA_BUTTON_A: + case RI_OCARINA_BUTTON_C_DOWN: + case RI_OCARINA_BUTTON_C_LEFT: + case RI_OCARINA_BUTTON_C_RIGHT: + case RI_OCARINA_BUTTON_C_UP: + Flags_ClearRandoInf(RANDO_INF_OBTAINED_OCARINA_BUTTON_A + (randoItemId - RI_OCARINA_BUTTON_A)); + break; // Ignore Ammo case RI_BOMBCHU: case RI_DEKU_STICK: From b8c4c670d4fd636b60c7e111aa72efc0db30b816 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:59:26 -0500 Subject: [PATCH 42/44] Clean up Fix bug with SkipScarecrowSong Fix Zora Ballad logic Grant Ocarina buttons when not shuffled Fix VB_PLAY_OCARINA_NOTE boolean Re-instantiate starting items RI adjustment Swap Ocarina right and left --- mm/2s2h/Enhancements/Songs/SkipScarecrowSong.cpp | 6 ++++-- mm/2s2h/Rando/ActorBehavior/Player.cpp | 7 ++----- mm/2s2h/Rando/DrawItem.cpp | 4 ++-- mm/2s2h/Rando/Logic/Logic.h | 1 + mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 6 ++++++ mm/2s2h/Rando/Rando.h | 6 ++++-- mm/2s2h/Rando/Types.h | 4 ++-- mm/src/audio/code_8019AF00.c | 2 +- 8 files changed, 22 insertions(+), 14 deletions(-) diff --git a/mm/2s2h/Enhancements/Songs/SkipScarecrowSong.cpp b/mm/2s2h/Enhancements/Songs/SkipScarecrowSong.cpp index 748bb80e65..c3902a2c9e 100644 --- a/mm/2s2h/Enhancements/Songs/SkipScarecrowSong.cpp +++ b/mm/2s2h/Enhancements/Songs/SkipScarecrowSong.cpp @@ -19,11 +19,13 @@ void RegisterSkipScarecrowSong() { * This is somewhat similar to the condition that the scarecrow normally checks, except it checks if the * instrument is being played at all instead of having played the Scarecrow's Song in particular, and it * bypasses the check that Link has taught Pierre a song this cycle. + * + * With Ocarina buttons shuffled, this enhancement will only apply if at least two buttons are obtained. This is + * consistent with the requirements to create the Scarecrow's Song, i.e. play at least two different notes. */ if ((enKakasi->picto.actor.xzDistToPlayer < enKakasi->songSummonDist) && ((BREG(1) != 0) || (gPlayState->msgCtx.ocarinaMode == OCARINA_MODE_ACTIVE))) { - // In Rando we may utilize Ocarina Buttons, ensure this is honored. - if (IS_RANDO && Rando::Logic::canPlaySong(OCARINA_SONG_SCARECROW_SPAWN)) { + if (IS_RANDO && !Rando::Logic::canPlaySong(OCARINA_SONG_SCARECROW_SPAWN)) { return; } diff --git a/mm/2s2h/Rando/ActorBehavior/Player.cpp b/mm/2s2h/Rando/ActorBehavior/Player.cpp index 937d8d3ad5..2e5cf5fb11 100644 --- a/mm/2s2h/Rando/ActorBehavior/Player.cpp +++ b/mm/2s2h/Rando/ActorBehavior/Player.cpp @@ -28,17 +28,14 @@ void RespawnOnWaterTouch(Player* player) { } void Rando::ActorBehavior::InitPlayerBehavior() { - bool shouldPlayerRegister = - IS_RANDO && (RANDO_SAVE_OPTIONS[RO_SHUFFLE_SWIM] || RANDO_SAVE_OPTIONS[RO_SHUFFLE_OCARINA_BUTTONS]); - - COND_ID_HOOK(OnActorUpdate, ACTOR_PLAYER, shouldPlayerRegister, [](Actor* actor) { + COND_ID_HOOK(OnActorUpdate, ACTOR_PLAYER, IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_SWIM], [](Actor* actor) { Player* player = GET_PLAYER(gPlayState); if (!Flags_GetRandoInf(RANDO_INF_OBTAINED_SWIM)) { RespawnOnWaterTouch(player); } }); - COND_VB_SHOULD(VB_PLAY_OCARINA_NOTE, IS_RANDO, { + COND_VB_SHOULD(VB_PLAY_OCARINA_NOTE, IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_OCARINA_BUTTONS], { u8* sCurOcarinaButtonIndex = va_arg(args, u8*); u8* sCurOcarinaPitch = va_arg(args, u8*); u8 currentOcarinaButton = *sCurOcarinaButtonIndex; diff --git a/mm/2s2h/Rando/DrawItem.cpp b/mm/2s2h/Rando/DrawItem.cpp index 2b39a824b1..87c18cdf9c 100644 --- a/mm/2s2h/Rando/DrawItem.cpp +++ b/mm/2s2h/Rando/DrawItem.cpp @@ -403,8 +403,8 @@ void DrawAbilityItem(RandoItemId randoItemId, Actor* actor) { void DrawOcarinaButtonItem(RandoItemId randoItemId, Actor* actor) { Gfx* ocarinaButtonModel[5] = { - (Gfx*)gOcarinaAButtonDL, (Gfx*)gOcarinaCDownButtonDL, (Gfx*)gOcarinaCLeftButtonDL, - (Gfx*)gOcarinaCRightButtonDL, (Gfx*)gOcarinaCUpButtonDL, + (Gfx*)gOcarinaAButtonDL, (Gfx*)gOcarinaCDownButtonDL, (Gfx*)gOcarinaCRightButtonDL, + (Gfx*)gOcarinaCLeftButtonDL, (Gfx*)gOcarinaCUpButtonDL, }; OPEN_DISPS(gPlayState->state.gfxCtx); diff --git a/mm/2s2h/Rando/Logic/Logic.h b/mm/2s2h/Rando/Logic/Logic.h index 13f1a007a9..7473ba9b63 100644 --- a/mm/2s2h/Rando/Logic/Logic.h +++ b/mm/2s2h/Rando/Logic/Logic.h @@ -305,6 +305,7 @@ inline bool canPlaySong(u8 songId) { case OCARINA_SONG_OATH: case OCARINA_SONG_WIND_FISH_ZORA: return (Flags_GetRandoInf(RANDO_INF_OBTAINED_OCARINA_BUTTON_C_RIGHT) && + Flags_GetRandoInf(RANDO_INF_OBTAINED_OCARINA_BUTTON_C_LEFT) && Flags_GetRandoInf(RANDO_INF_OBTAINED_OCARINA_BUTTON_C_DOWN) && Flags_GetRandoInf(RANDO_INF_OBTAINED_OCARINA_BUTTON_A) && Flags_GetRandoInf(RANDO_INF_OBTAINED_OCARINA_BUTTON_C_UP)); diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index 18b9e7cdbd..7aa103520e 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -33,6 +33,12 @@ void GrantStarters() { startingItems.push_back(RI_ABILITY_SWIM); } + if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_OCARINA_BUTTONS] != RO_GENERIC_YES) { + for (int i = RI_OCARINA_BUTTON_A; i <= RI_OCARINA_BUTTON_C_UP; i++) { + startingItems.push_back((RandoItemId)i); + } + } + for (RandoItemId startingItem : startingItems) { Rando::GiveItem(Rando::ConvertItem(startingItem)); } diff --git a/mm/2s2h/Rando/Rando.h b/mm/2s2h/Rando/Rando.h index 4f592b0c5f..7d824e5a9b 100644 --- a/mm/2s2h/Rando/Rando.h +++ b/mm/2s2h/Rando/Rando.h @@ -11,8 +11,10 @@ #define RANDO_EVENTS gSaveContext.save.shipSaveInfo.rando.randoEvents #define RANDO_STARTING_ITEMS gSaveContext.save.shipSaveInfo.rando.randoStartingItems -#define RANDO_STARTING_ITEMS_DEFAULT \ - "109,126,91,146" // This includes a Progressive Sword, Hero's Shield, Ocarina of Time, and Song of Time +#define RANDO_STARTING_ITEMS_DEFAULT \ + (std::to_string(RI_PROGRESSIVE_SWORD) + "," + std::to_string(RI_SHIELD_HERO) + "," + std::to_string(RI_OCARINA) + \ + "," + std::to_string(RI_SONG_TIME)) \ + .c_str() namespace Rando { diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h index 05e8d8cfe0..202a135746 100644 --- a/mm/2s2h/Rando/Types.h +++ b/mm/2s2h/Rando/Types.h @@ -2404,8 +2404,8 @@ typedef enum { RI_OCARINA, RI_OCARINA_BUTTON_A, RI_OCARINA_BUTTON_C_DOWN, - RI_OCARINA_BUTTON_C_LEFT, RI_OCARINA_BUTTON_C_RIGHT, + RI_OCARINA_BUTTON_C_LEFT, RI_OCARINA_BUTTON_C_UP, RI_OWL_CLOCK_TOWN_SOUTH, RI_OWL_GREAT_BAY_COAST, @@ -3028,8 +3028,8 @@ typedef enum { RANDO_INF_OBTAINED_CLOCK_NIGHT_3, RANDO_INF_OBTAINED_OCARINA_BUTTON_A, RANDO_INF_OBTAINED_OCARINA_BUTTON_C_DOWN, - RANDO_INF_OBTAINED_OCARINA_BUTTON_C_LEFT, RANDO_INF_OBTAINED_OCARINA_BUTTON_C_RIGHT, + RANDO_INF_OBTAINED_OCARINA_BUTTON_C_LEFT, RANDO_INF_OBTAINED_OCARINA_BUTTON_C_UP, RANDO_INF_MAX, } RandoInf; diff --git a/mm/src/audio/code_8019AF00.c b/mm/src/audio/code_8019AF00.c index 1406cb2fd3..030023d2ba 100644 --- a/mm/src/audio/code_8019AF00.c +++ b/mm/src/audio/code_8019AF00.c @@ -2666,7 +2666,7 @@ void AudioOcarina_PlayControllerInput(u8 isOcarinaSfxSuppressedWhenCancelled) { sCurOcarinaButtonIndex = OCARINA_BTN_C_UP; } - if (GameInteractor_Should(VB_PLAY_OCARINA_NOTE, &sCurOcarinaButtonIndex, &sCurOcarinaPitch)) {} + if (GameInteractor_Should(VB_PLAY_OCARINA_NOTE, true, &sCurOcarinaButtonIndex, &sCurOcarinaPitch)) {} // Pressing the R Button will raise the pitch by 1 semitone if ((sCurOcarinaPitch != OCARINA_PITCH_NONE) && CHECK_BTN_ANY(sOcarinaInputButtonCur, BTN_R) && From 07854304a4bc40c9273ec2bc4f16766d636f9b84 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Wed, 7 Jan 2026 07:55:22 -0500 Subject: [PATCH 43/44] [Enhancement] Masks equippable in water (#1430) * [Enhancement] Masks equippable in water * Format --- mm/2s2h/BenGui/BenMenu.cpp | 3 +++ .../Enhancements/Masks/EquipWhileSwimming.cpp | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 mm/2s2h/Enhancements/Masks/EquipWhileSwimming.cpp diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 5c11a1cbdc..5850badab8 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -1227,6 +1227,9 @@ void BenMenu::AddEnhancements() { AddSidebarEntry("Enhancements", "Items/Songs", 3); // Mask Enhancements AddWidget(path, "Masks", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Equippable While Swimming", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Masks.EquipWhileSwimming") + .Options(CheckboxOptions().Tooltip("Human Link can equip any non-transformation mask while swimming.")); AddWidget(path, "Blast Mask has Powder Keg Force", WIDGET_CVAR_CHECKBOX) .CVar("gEnhancements.Masks.BlastMaskKeg") .Options(CheckboxOptions().Tooltip("Blast Mask can also destroy objects only the Powder Keg can.")); diff --git a/mm/2s2h/Enhancements/Masks/EquipWhileSwimming.cpp b/mm/2s2h/Enhancements/Masks/EquipWhileSwimming.cpp new file mode 100644 index 0000000000..20ea8a5274 --- /dev/null +++ b/mm/2s2h/Enhancements/Masks/EquipWhileSwimming.cpp @@ -0,0 +1,25 @@ +#include +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" + +#define CVAR_NAME "gEnhancements.Masks.EquipWhileSwimming" +#define CVAR CVarGetInteger(CVAR_NAME, 0) + +static void RegisterEquipWhileSwimming() { + COND_VB_SHOULD(VB_USE_ITEM_CONSIDER_ITEM_ACTION, CVAR, { + PlayerItemAction itemAction = *va_arg(args, PlayerItemAction*); + if (itemAction >= PLAYER_IA_MASK_MIN && itemAction < PLAYER_IA_MASK_GIANT) { + *should = true; + } + }); + + COND_VB_SHOULD(VB_DISABLE_ITEM_UNDERWATER, CVAR, { + s32 item = va_arg(args, s32); + if (GET_PLAYER_FORM == PLAYER_FORM_HUMAN && item > ITEM_MASK_FIERCE_DEITY && item < ITEM_MASK_GIANT && + Player_GetEnvironmentalHazard(gPlayState) > PLAYER_ENV_HAZARD_UNDERWATER_FLOOR) { + *should = false; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterEquipWhileSwimming, { CVAR_NAME }); From 4b1b70242de537b0eb0f935b404d7c1a3b332df6 Mon Sep 17 00:00:00 2001 From: Eblo <7004497+Eblo@users.noreply.github.com> Date: Wed, 7 Jan 2026 07:55:30 -0500 Subject: [PATCH 44/44] Grant starting enemy souls if not shuffled (#1443) --- mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index 18b9e7cdbd..1d4fab153e 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -33,6 +33,12 @@ void GrantStarters() { startingItems.push_back(RI_ABILITY_SWIM); } + if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENEMY_SOULS] != RO_GENERIC_YES) { + for (int i = RI_SOUL_ENEMY_ALIEN; i <= RI_SOUL_ENEMY_WOLFOS; i++) { + startingItems.push_back((RandoItemId)i); + } + } + for (RandoItemId startingItem : startingItems) { Rando::GiveItem(Rando::ConvertItem(startingItem)); }