diff --git a/.gitignore b/.gitignore index 69dc3c2eca..f0c53b52bf 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ shipofharkinian.json imgui.ini saves/* randomizer/* +2S2HTimeSplitData.json mm/libultraship/extern/Debug/ImGui.lib @@ -68,3 +69,5 @@ _packages/ /mm/src/boot/build.c /mm/windows/properties.h /clang-format.exe + +build/ 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/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/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 610372d9dd..5850badab8 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 @@ -113,8 +119,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 }; @@ -551,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) @@ -987,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); @@ -1020,6 +1032,27 @@ 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") + .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) @@ -1036,14 +1069,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) { @@ -1153,9 +1189,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( @@ -1194,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.")); @@ -1265,6 +1301,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 }; @@ -1360,6 +1399,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 }; @@ -1575,10 +1620,9 @@ 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, "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() @@ -1588,6 +1632,34 @@ 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.")); + 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); @@ -1978,6 +2050,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/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); 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/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()) { 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/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/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/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/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/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 }); 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); }); } 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/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; } }); 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/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/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); 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/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 }); 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/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 }); 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 }); 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/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/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/Enhancements/Saving/SavingEnhancements.cpp b/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp index be5a5cb679..e725da230c 100644 --- a/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp +++ b/mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp @@ -1,44 +1,62 @@ #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]; + } + // 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 + 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 +176,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 +242,7 @@ void RegisterSavingEnhancements() { gSaveContext.save.shipSaveInfo.fileCreatedAt = GetUnixTimestamp(); } gSaveContext.shipSaveContext.lastTimeLog = GetUnixTimestamp(); + lastEntrance = entranceToSave = gSaveContext.save.shipSaveInfo.pauseSaveEntrance; }); // Owl statue prompt @@ -191,6 +263,8 @@ void RegisterSavingEnhancements() { }); GameInteractor::Instance->RegisterGameHook([]() { DeleteOwlSave(); }); + + GameInteractor::Instance->RegisterGameHook(loadRespawnData); } void RegisterAutosave() { @@ -225,3 +299,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/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/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/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/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/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/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/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/Enhancements/Trackers/ItemTracker/ItemTracker.cpp b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp index 987f282c9a..c8c4852f2f 100644 --- a/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp +++ b/mm/2s2h/Enhancements/Trackers/ItemTracker/ItemTracker.cpp @@ -3,6 +3,8 @@ #include "2s2h/BenGui/UIWidgets.hpp" #include "Rando/Rando.h" +#include "Rando/ActorBehavior/Souls.h" +#include "Rando/MiscBehavior/ClockShuffle.h" #include "2s2h/ShipUtils.h" #include @@ -83,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); @@ -108,6 +157,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 0c650ebf9f..e02be5b4e0 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", "Ocarina Buttons", "Boss Souls", "Owl Statues", "Tingle Maps", "Misc", + "Frogs", "Ocarina Buttons", "Boss Souls", "Enemy Souls", "Owl Statues", "Time", "Tingle Maps", "Misc", }; std::map> defaultItemLists = { @@ -44,9 +44,11 @@ std::map> defaultItemLists = std::map> randoItemLists = { { "Frogs", { RI_FROG_BLUE, RI_FROG_WHITE, 4 } }, { "Ocarina Buttons", { RI_OCARINA_BUTTON_A, RI_OCARINA_BUTTON_C_UP, 5 } }, - { "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 } }, { "Misc", { RI_TRIFORCE_PIECE, RI_TRIFORCE_PIECE, 1 } }, }; @@ -250,7 +252,8 @@ void DrawItemList(std::string listName, int columns) { std::vector emptyList; if (listName == "Frogs" || listName == "Ocarina Buttons" || listName == "Boss Souls" || - listName == "Owl Statues" || listName == "Tingle Maps" || listName == "Misc") { + 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(); @@ -346,8 +349,8 @@ void DrawPreviewPane() { } windowListIndex++; } - ImGui::EndChild(); } + ImGui::EndChild(); } void DrawTrackerWindowOptions(int32_t windowIndex, TrackerItemListObject& windowObject) { @@ -607,9 +610,8 @@ void DrawTrackerCustomizationOptions() { DrawItemList(randoListOrder[rkey], std::get<2>(list)); ImGui::PopID(); } - - ImGui::EndChild(); } + ImGui::EndChild(); } void ItemTrackerSettingsWindow::DrawElement() { @@ -635,28 +637,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")) { @@ -667,15 +669,15 @@ void ItemTrackerSettingsWindow::DrawElement() { } ImGui::EndTabBar(); } - ImGui::EndChild(); } + ImGui::EndChild(); ImGui::EndTable(); } UIWidgets::PopStyleTabs(); ImGui::PopStyleColor(3); - ImGui::EndChild(); } + ImGui::EndChild(); } void ItemTrackerSettingsWindow::InitElement() { diff --git a/mm/2s2h/GameInteractor/GameInteractor.cpp b/mm/2s2h/GameInteractor/GameInteractor.cpp index 7c1d5cc1f9..c50ee54e34 100644 --- a/mm/2s2h/GameInteractor/GameInteractor.cpp +++ b/mm/2s2h/GameInteractor/GameInteractor.cpp @@ -466,39 +466,42 @@ 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); + auto lostEvent = GameInteractor::Instance->currentEvent; + GameInteractor::Instance->currentEvent = GIEventNone{}; + GameInteractor::Instance->events.push_back(lostEvent); } - - GameInteractor::Instance->currentEvent = GIEventNone{}; }; } else if (auto e = std::get_if(&nextEvent)) { gPlayState->nextEntrance = e->entrance; diff --git a/mm/2s2h/GameInteractor/GameInteractor.h b/mm/2s2h/GameInteractor/GameInteractor.h index 111ddbf65d..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,243 +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_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_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, - VB_PLAY_OCARINA_NOTE, - VB_TOTO_START_SOUND_CHECK, -} 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..c88c54d8ca --- /dev/null +++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h @@ -0,0 +1,2095 @@ +#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 + // gSaveContext.save.saveInfo.playerData.rupees >= CUR_CAPACITY(UPG_WALLET) + // ``` + // #### `args` + // - None + VB_DISCARD_EXCESS_RUPEES, + + // #### `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` + VB_DRAW_LOCK_ON_ARROW, + + // #### `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` + // #### In `Item_DropCollectible`: + // ```c + // true + // ``` + // #### `args` + // - `*Vec3f` spawnPos + // - `u32` params + // #### In `Item_DropCollectibleRandom`: + // ```c + // true + // ``` + // #### `args` + // - `*Vec3f` spawnPos + // - `u16` params + 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` + // #### In `Player_GetItemOnButton`: + // ```c + // item + // ``` + // #### `args` + // - `EquipSlot` + // - `*ItemId` + VB_GET_ITEM_ON_BUTTON, + + // #### `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` + // - None + VB_MSG_LOAD_RUPEES_TEXT, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_MSG_PLAY_INPUT_COUNT_SOUND, + + // #### `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 + // true + // ``` + // #### `args` + // - `*u8` (current Ocarina button input index) + // - `*u8` (current Ocarina pitch) + VB_PLAY_OCARINA_NOTE, + + // #### `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 + // false + // ``` + // #### `args` + // - 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 + // ``` + // #### `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 + // 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 + // ``` + // #### `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 + // TIME_UNTIL_MOON_CRASH + // ``` + // #### `args` + // - `*u32` (time variable) + VB_TIME_UNTIL_MOON_CRASH_CALCULATION, + + // #### `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` + // - `*EnToto` + VB_TOTO_START_SOUND_CHECK, + + // #### `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/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/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. 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/2s2h/Rando/ActorBehavior/EnCow.cpp b/mm/2s2h/Rando/ActorBehavior/EnCow.cpp index 854b04f631..6703af28d0 100644 --- a/mm/2s2h/Rando/ActorBehavior/EnCow.cpp +++ b/mm/2s2h/Rando/ActorBehavior/EnCow.cpp @@ -72,7 +72,9 @@ 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) && @@ -80,13 +82,15 @@ void Rando::ActorBehavior::InitEnCowBehavior() { *should = false; return; } + + ((EnCow*)actor)->flags |= EN_COW_FLAG_WONT_GIVE_MILK; + RandoCheckId randoCheckId = GetObjectRandoCheckId(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) { @@ -97,7 +101,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; 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/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/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 }); 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/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/CheckTracker/CheckTracker.cpp b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp index 48dfddb947..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]++; - } - } } } @@ -432,7 +324,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() @@ -505,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..d4e60c243c 100644 --- a/mm/2s2h/Rando/ConvertItem.cpp +++ b/mm/2s2h/Rando/ConvertItem.cpp @@ -1,4 +1,6 @@ #include "Rando/Rando.h" +#include "Rando/ActorBehavior/Souls.h" +#include "Rando/MiscBehavior/ClockShuffle.h" #include "2s2h/ShipUtils.h" #include @@ -421,12 +423,75 @@ 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: + 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; + 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 @@ -471,6 +536,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..4188caef07 100644 --- a/mm/2s2h/Rando/DrawFuncs.cpp +++ b/mm/2s2h/Rando/DrawFuncs.cpp @@ -5,12 +5,15 @@ 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" #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 @@ -20,76 +23,1105 @@ 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" + +// Clock +void ObjTokeidai_RotateOnMinuteChange(ObjTokeidai* thisx, s32 playSfx); +void ObjTokeidai_RotateOnHourChange(ObjTokeidai* thisx, PlayState* play); // clang-format on } -// Soul Effects -void DrawEnLight(Color_RGB8 flameColor, Vec3f flameSize) { - Gfx* sp68; - static s8 unk_144 = (s8)(Rand_ZeroOne() * 255.0f); - static u32 lastUpdate = 0; +#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 +extern void DrawEnLight(Color_RGB8 flameColor, Vec3f flameSize) { + Gfx* sp68; + static s8 unk_144 = (s8)(Rand_ZeroOne() * 255.0f); + static u32 lastUpdate = 0; + + OPEN_DISPS(gPlayState->state.gfxCtx); + + Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); + Matrix_ReplaceRotation(&gPlayState->billboardMtxF); + + gSPSegment(POLY_XLU_DISP++, 0x08, + (uintptr_t)Gfx_TwoTexScroll(gPlayState->state.gfxCtx, 0, 0, 0, 0x10, 0x20, 1, (unk_144 * 2) & 0x3F, + (unk_144 * -6) & 0x7F, 0x10, 0x20)); + sp68 = (Gfx*)gameplay_keep_DL_01ACF0; + gDPSetPrimColor(POLY_XLU_DISP++, 0xC0, 0xC0, flameColor.r, flameColor.g, flameColor.b, 0); + gDPSetEnvColor(POLY_XLU_DISP++, flameColor.r, flameColor.g, flameColor.b, 0); + Matrix_Scale(flameSize.x, flameSize.y, flameSize.z, MTXMODE_APPLY); + + MATRIX_FINALIZE_AND_LOAD(POLY_XLU_DISP++, gPlayState->state.gfxCtx); + gSPDisplayList(POLY_XLU_DISP++, sp68); + + CLOSE_DISPS(gPlayState->state.gfxCtx); + + if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { + lastUpdate = gPlayState->state.frames; + unk_144++; + } +} + +// Limb Override Functions +void EnMinifrogPostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) { + if ((limbIndex == FROG_LIMB_RIGHT_EYE) || (limbIndex == FROG_LIMB_LEFT_EYE)) { + OPEN_DISPS(play->state.gfxCtx); + + Matrix_ReplaceRotation(&play->billboardMtxF); + MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, play->state.gfxCtx); + gSPDisplayList(POLY_OPA_DISP++, *dList); + + CLOSE_DISPS(play->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; + } + + Matrix_MultZero(&auraPos); + auraPos.x += Rand_ZeroOne() * 0.5f; + auraPos.y += Rand_ZeroOne() * 0.5f; + auraPos.z += Rand_ZeroOne() * 0.5f; + + if (gPlayState != NULL && dustUpdate != gPlayState->state.frames) { + if (dustUpdate == gPlayState->state.frames - 20) { + dustUpdate = gPlayState->state.frames; + auraColor = !auraColor; + } + } + + 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); + } + 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 DrawArmos() { + SETUP_DRAW(OBJECT_AM_LIMB_MAX); + Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); + 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); + + 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; + 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; + 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({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +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); + 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 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; + 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; + 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); + } + } + + 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); + Matrix_Pop(); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); +} + +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); - OPEN_DISPS(gPlayState->state.gfxCtx); + 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); Gfx_SetupDL25_Xlu(gPlayState->state.gfxCtx); - Matrix_ReplaceRotation(&gPlayState->billboardMtxF); + Matrix_Scale(0.015f, 0.015f, 0.015f, MTXMODE_APPLY); + SETUP_SKEL(NEJIRON_LIMB_MAX, gNejironSkel, gNejironIdleAnim); - gSPSegment(POLY_XLU_DISP++, 0x08, - (uintptr_t)Gfx_TwoTexScroll(gPlayState->state.gfxCtx, 0, 0, 0, 0x10, 0x20, 1, (unk_144 * 2) & 0x3F, - (unk_144 * -6) & 0x7F, 0x10, 0x20)); - sp68 = (Gfx*)gameplay_keep_DL_01ACF0; - gDPSetPrimColor(POLY_XLU_DISP++, 0xC0, 0xC0, flameColor.r, flameColor.g, flameColor.b, 0); - gDPSetEnvColor(POLY_XLU_DISP++, flameColor.r, flameColor.g, flameColor.b, 0); - Matrix_Scale(flameSize.x, flameSize.y, flameSize.z, MTXMODE_APPLY); + gSPSegment(POLY_OPA_DISP++, 8, (uintptr_t)gNejironEyeOpenTex); + SkelAnime_DrawOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, NULL, NULL, NULL); - MATRIX_FINALIZE_AND_LOAD(POLY_XLU_DISP++, gPlayState->state.gfxCtx); - gSPDisplayList(POLY_XLU_DISP++, sp68); + 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 }); +} - if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { - lastUpdate = gPlayState->state.frames; - unk_144++; - } +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 }); } -// Limb Override Functions -void EnMinifrogPostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) { - if ((limbIndex == FROG_LIMB_RIGHT_EYE) || (limbIndex == FROG_LIMB_LEFT_EYE)) { - OPEN_DISPS(play->state.gfxCtx); +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); - Matrix_ReplaceRotation(&play->billboardMtxF); - MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, play->state.gfxCtx); - gSPDisplayList(POLY_OPA_DISP++, *dList); + gSPSegment(POLY_OPA_DISP++, 0x08, eyeTexture); + SkelAnime_DrawTransformFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, + NULL, EnKaizoku_TransformLimbDraw, NULL); - CLOSE_DISPS(play->state.gfxCtx); - } + CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawEnLight({ 155, 155, 155 }, { 10.0f, 10.0f, 10.0f }); } -// Boss Souls -extern void DrawGoht() { - OPEN_DISPS(gPlayState->state.gfxCtx); +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); - Matrix_Translate(0.0f, -20.0f, 0.0f, MTXMODE_APPLY); - Matrix_Scale(0.005f, 0.005f, 0.005f, MTXMODE_APPLY); + 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[33]; - static Vec3s otherTable[33]; - static u32 lastUpdate = 0; if (!initialized) { initialized = true; - SkelAnime_InitFlex(gPlayState, &skelAnime, (FlexSkeletonHeader*)&gGohtSkel, (AnimationHeader*)&gGohtRunAnim, - jointTable, otherTable, 33); + 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); @@ -98,26 +1130,12 @@ extern void DrawGoht() { } extern void DrawGyorg() { - OPEN_DISPS(gPlayState->state.gfxCtx); - + 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); - static bool initialized = false; - static SkelAnime skelAnime; - static Vec3s jointTable[15]; - static Vec3s otherTable[15]; - static u32 lastUpdate = 0; - if (!initialized) { - initialized = true; - SkelAnime_InitFlex(gPlayState, &skelAnime, (FlexSkeletonHeader*)&gGyorgSkel, - (AnimationHeader*)&gGyorgGentleSwimmingAnim, jointTable, otherTable, 15); - } - if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { - lastUpdate = gPlayState->state.frames; - SkelAnime_Update(&skelAnime); - } SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); CLOSE_DISPS(gPlayState->state.gfxCtx); @@ -125,27 +1143,13 @@ extern void DrawGyorg() { } extern void DrawOdolwa() { - OPEN_DISPS(gPlayState->state.gfxCtx); - + 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); - static bool initialized = false; - static SkelAnime skelAnime; - static Vec3s jointTable[52]; - static Vec3s otherTable[52]; - static u32 lastUpdate = 0; - if (!initialized) { - initialized = true; - SkelAnime_InitFlex(gPlayState, &skelAnime, (FlexSkeletonHeader*)&gOdolwaSkel, - (AnimationHeader*)&gOdolwaReadyAnim, jointTable, otherTable, 52); - } - if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { - lastUpdate = gPlayState->state.frames; - SkelAnime_Update(&skelAnime); - } SkelAnime_DrawFlexOpa(gPlayState, skelAnime.skeleton, skelAnime.jointTable, skelAnime.dListCount, NULL, NULL, NULL); CLOSE_DISPS(gPlayState->state.gfxCtx); @@ -153,25 +1157,10 @@ extern void DrawOdolwa() { } extern void DrawTwinmold() { - OPEN_DISPS(gPlayState->state.gfxCtx); - + SETUP_DRAW(TWINMOLD_HEAD_LIMB_MAX); Gfx_SetupDL25_Opa(gPlayState->state.gfxCtx); Matrix_Scale(0.06f, 0.06f, 0.06f, 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); - } - if (gPlayState != NULL && lastUpdate != gPlayState->state.frames) { - lastUpdate = gPlayState->state.frames; - SkelAnime_Update(&skelAnime); - } + 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); @@ -183,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); @@ -215,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: @@ -261,4 +1220,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..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,7 +12,61 @@ 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); +// Clock Function +void DrawClock(RandoItemId randoItemId, Actor* actor); + #endif diff --git a/mm/2s2h/Rando/DrawItem.cpp b/mm/2s2h/Rando/DrawItem.cpp index 356603de04..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); @@ -416,6 +416,65 @@ void DrawOcarinaButtonItem(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; @@ -512,6 +571,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: @@ -520,19 +588,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: @@ -575,6 +692,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 241166f742..6a774a9870 100644 --- a/mm/2s2h/Rando/GiveItem.cpp +++ b/mm/2s2h/Rando/GiveItem.cpp @@ -1,5 +1,7 @@ #include "Rando/Rando.h" +#include "Rando/ActorBehavior/Souls.h" #include "Rando/MiscBehavior/MiscBehavior.h" +#include "Rando/MiscBehavior/ClockShuffle.h" extern "C" { #include "variables.h" @@ -110,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( @@ -258,6 +260,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; @@ -268,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 new file mode 100644 index 0000000000..e917c2ec70 --- /dev/null +++ b/mm/2s2h/Rando/Logic/GeneratePools.cpp @@ -0,0 +1,277 @@ +#include "Logic.h" +#include "Rando/MiscBehavior/ClockShuffle.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_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); + + // Abilities + if (saveInfo.randoSaveOptions[RO_SHUFFLE_SWIM] == RO_GENERIC_YES) { + itemPool.push_back(RI_ABILITY_SWIM); + } + + // Ocarina Buttons + if (saveInfo.randoSaveOptions[RO_SHUFFLE_OCARINA_BUTTONS] == RO_GENERIC_YES) { + for (int i = RI_OCARINA_BUTTON_A; i <= RI_OCARINA_BUTTON_C_UP; i++) { + itemPool.push_back((RandoItemId)i); + } + } + + // 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..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 }); @@ -98,17 +114,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(); @@ -126,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 11958e09b3..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; +} + +// 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; + } - for (auto& [connectedRegionId, condition] : randoRegion.connections) { - // Check if the region is accessible and hasn’t been visited yet + // 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); } } } @@ -72,10 +154,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 18e5d7d742..7473ba9b63 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" @@ -17,8 +18,114 @@ 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); void ApplyNearlyNoLogicToSaveContext(std::vector& checkPool, std::vector& itemPool); void ApplyNoLogicToSaveContext(std::vector& checkPool, std::vector& itemPool); @@ -37,10 +144,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 +187,9 @@ extern std::map Regions; #define HAS_MAGIC (gSaveContext.save.saveInfo.playerData.isMagicAcquired) #define CAN_HOOK_SCARECROW \ (HAS_ITEM(ITEM_OCARINA_OF_TIME) && HAS_ITEM(ITEM_HOOKSHOT) && canPlaySong(OCARINA_SONG_SCARECROW_SPAWN)) -#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 @@ -85,6 +215,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]) @@ -122,17 +256,16 @@ extern std::map Regions; [] { return condition; }, LogicString(#condition) \ } \ } - -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; - } +// 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; } \ } - return false; -} inline std::string LogicString(std::string condition) { if (condition == "true") @@ -172,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)); @@ -273,28 +407,213 @@ 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]; +} + +// ============================================================================ +// 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) { + // 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) && @@ -302,18 +621,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 - return (HAS_ITEM(ITEM_BOW) && (CAN_BE_DEKU || CAN_USE_EXPLOSIVE || CAN_BE_GORON)); - case ACTOR_EN_BIGSLIME: // Great Bay Gekko + case ACTOR_EN_PAMETFROG: // Woodfall Temple Gekko (and Snapper) + 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 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 @@ -328,7 +649,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 @@ -375,6 +696,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)); @@ -388,23 +712,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/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/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 902ef4c6cb..b93f68c8e0 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), @@ -89,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, @@ -119,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), @@ -132,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)), }, @@ -153,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, @@ -165,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), }, @@ -178,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), @@ -193,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 @@ -257,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), @@ -269,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), @@ -278,63 +338,87 @@ 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) && (canPlaySong(OCARINA_SONG_WIND_FISH_HUMAN) && canPlaySong(OCARINA_SONG_WIND_FISH_DEKU) && canPlaySong(OCARINA_SONG_WIND_FISH_GORON) && canPlaySong(OCARINA_SONG_WIND_FISH_ZORA))), - 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, 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)) + && (canPlaySong(OCARINA_SONG_WIND_FISH_HUMAN) && + (CAN_BE_DEKU && canPlaySong(OCARINA_SONG_WIND_FISH_DEKU)) && + (CAN_BE_GORON && canPlaySong(OCARINA_SONG_WIND_FISH_GORON)) && + (CAN_BE_ZORA && canPlaySong(OCARINA_SONG_WIND_FISH_ZORA)))), + 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_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_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 = { @@ -344,6 +428,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 = { @@ -361,6 +450,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 465b0d0da6..514ae967c5 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), @@ -145,11 +153,12 @@ 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)), }, .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 }, @@ -162,6 +171,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) && IS_DAY()), // Day only }, .exits = { // TO FROM EXIT(ENTRANCE(GHOST_HUT, 0), ENTRANCE(IKANA_CANYON, 1), true), @@ -201,6 +211,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,12 +228,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) && 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)), @@ -234,7 +247,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) && IS_DAY()), // 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 +273,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) && IS_DAY()), // Day only + }, .exits = { // TO FROM EXIT(ENTRANCE(IKANA_CANYON, 0), ENTRANCE(ROAD_TO_IKANA, 1), true), }, @@ -268,6 +287,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) && 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) @@ -280,6 +301,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) && 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), @@ -306,6 +329,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 +342,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 +395,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 +415,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 +471,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 +487,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 +511,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..59bc244c31 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,13 +285,16 @@ 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)), 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, @@ -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..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, HAS_ITEM(ITEM_BOW) && 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_ALIENS, CanKillEnemy(ACTOR_EN_INVADEPOH) && 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,16 +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) && 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/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..43c4a34f45 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), @@ -108,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/Logic/Regions/North.cpp b/mm/2s2h/Rando/Logic/Regions/North.cpp index 6cc57fef7d..b4d8eea8aa 100644 --- a/mm/2s2h/Rando/Logic/Regions/North.cpp +++ b/mm/2s2h/Rando/Logic/Regions/North.cpp @@ -123,11 +123,12 @@ 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 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) @@ -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 @@ -249,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), @@ -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) && (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), @@ -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 @@ -336,25 +345,29 @@ 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), 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) && 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), @@ -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) && 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), @@ -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) && IS_NIGHT()), // 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..64611300b7 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), @@ -51,12 +52,13 @@ 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 + 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..37cd95453e 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 @@ -132,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, @@ -188,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 @@ -217,6 +220,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,11 +251,15 @@ 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) && 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 @@ -278,6 +286,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 +322,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 +348,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 +407,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 +444,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), @@ -451,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 = { @@ -485,6 +504,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,9 +549,10 @@ 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 + CONNECTION(RR_WOODS_OF_MYSTERY, SECOND_DAY()), // TODO: Grotto mapping }, }; Regions[RR_WOODS_OF_MYSTERY] = RandoRegion{ .sceneId = SCENE_26SARUNOMORI, @@ -537,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), @@ -554,15 +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/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..3ff118ef56 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 @@ -171,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), @@ -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 @@ -245,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)), @@ -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) && IS_NIGHT()), // Night only + CHECK(RC_ENEMY_DROP_DEKU_BABA, CanKillEnemy(ACTOR_EN_DEKUBABA)), + 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) && 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)), }, .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 7697068ead..6261851125 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 @@ -121,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), @@ -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/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 9b20eb2dcc..d057b49800 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -2,8 +2,12 @@ #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" +#include "2s2h/Rando/Logic/Logic.h" +#include "2s2h/ShipInit.hpp" extern "C" { #include "overlays/actors/ovl_En_Sth/z_en_sth.h" @@ -32,12 +36,16 @@ 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_SHUFFLE_OCARINA_BUTTONS, RO_PLENTIFUL_ITEMS, + RO_CLOCK_SHUFFLE, }; +// clang-format on std::vector checkExclusionList; bool isExcludedInitialized = false; @@ -55,6 +63,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); @@ -64,6 +102,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; @@ -114,6 +153,84 @@ 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_CLOCK_SHUFFLE", + "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_ENEMY_SOULS", + "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_OCARINA_BUTTONS", + "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)); @@ -149,8 +266,35 @@ static void DrawGeneralTab() { } UIWidgets::PopStyleSlider(); - UIWidgets::CVarCheckbox("Generate Spoiler File", "gRando.GenerateSpoiler"); + 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 +310,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 +328,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); @@ -198,19 +341,21 @@ 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)); - 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", @@ -254,31 +399,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(); } @@ -332,8 +452,83 @@ 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. " + "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)); + 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)); @@ -348,7 +543,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( @@ -436,8 +631,8 @@ static void DrawStartingItemsTab() { ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f)); - std::vector setStartingItemsList = convertStartingItemsToRandoItemId( - CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT.c_str()), ","); + std::vector setStartingItemsList = + convertStartingItemsToRandoItemId(CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT), ","); uint32_t listIndex = 0; for (auto& startingItem : setStartingItemsList) { ImGui::PushID(listIndex); @@ -450,10 +645,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); @@ -463,7 +663,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) { @@ -483,6 +683,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), @@ -509,17 +711,24 @@ 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.c_str()); + CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT); if (currentStartingItems.length() != 0) { currentStartingItems += ","; } @@ -527,7 +736,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/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; 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 baa422ea2b..a0f9332d9c 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" { @@ -12,6 +13,59 @@ 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); + } + } + + if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_SWIM] != RO_GENERIC_YES) { + 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); + } + } + + 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)); + } + + 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,279 +114,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.c_str()), ","); - - 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); - } - - // Shuffle Ocarina Buttons or grant the Rando INF if not enabled - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_OCARINA_BUTTONS] == RO_GENERIC_YES) { - for (int16_t i = RI_OCARINA_BUTTON_A; i <= RI_OCARINA_BUTTON_C_UP; i++) { - itemPool.push_back((RandoItemId)i); - } - } else { - for (int i = RANDO_INF_OBTAINED_OCARINA_BUTTON_A; i <= RANDO_INF_OBTAINED_OCARINA_BUTTON_C_UP; - i++) { - Flags_SetRandoInf(i); - } - } - - // 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"); @@ -341,20 +134,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); @@ -395,48 +176,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); @@ -459,7 +206,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; @@ -478,6 +225,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/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/Rando.h b/mm/2s2h/Rando/Rando.h index ee23aa1f97..7d824e5a9b 100644 --- a/mm/2s2h/Rando/Rando.h +++ b/mm/2s2h/Rando/Rando.h @@ -13,7 +13,8 @@ #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)) + "," + std::to_string(RI_SONG_TIME)) \ + .c_str() namespace Rando { diff --git a/mm/2s2h/Rando/RemoveItem.cpp b/mm/2s2h/Rando/RemoveItem.cpp index 6a3c79341b..12c0ae1c29 100644 --- a/mm/2s2h/Rando/RemoveItem.cpp +++ b/mm/2s2h/Rando/RemoveItem.cpp @@ -1,4 +1,6 @@ #include "Rando/Rando.h" +#include "Rando/ActorBehavior/Souls.h" +#include "Rando/MiscBehavior/ClockShuffle.h" extern "C" { #include "variables.h" @@ -282,6 +284,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 = @@ -353,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); @@ -372,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: 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) { 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/StaticData/Items.cpp b/mm/2s2h/Rando/StaticData/Items.cpp index f9fe7f8b8f..9345290bae 100644 --- a/mm/2s2h/Rando/StaticData/Items.cpp +++ b/mm/2s2h/Rando/StaticData/Items.cpp @@ -176,16 +176,70 @@ 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), 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), @@ -232,8 +286,9 @@ 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_FROG_BLUE, RI_FROG_CYAN, RI_FROG_PINK, RI_FROG_WHITE + { 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 } }, }; // clang-format on @@ -330,11 +385,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: @@ -371,6 +473,16 @@ const char* GetIconTexturePath(RandoItemId randoItemId) { return (const char*)gOcarinaCRightTex; case RI_OCARINA_BUTTON_C_UP: return (const char*)gOcarinaCUpTex; + 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 ec5441bdba..deb1cfb71b 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), @@ -38,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), @@ -57,6 +60,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 3789bae30d..202a135746 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; @@ -2385,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, @@ -2443,11 +2462,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, @@ -2456,6 +2522,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, @@ -2525,7 +2598,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, @@ -2589,6 +2663,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, @@ -2790,7 +2865,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, @@ -2810,6 +2888,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, @@ -2829,6 +2908,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; @@ -2864,6 +2946,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, @@ -2878,16 +2966,70 @@ 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, + 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_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; @@ -2948,6 +3090,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/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/2s2h/ShipUtils.cpp b/mm/2s2h/ShipUtils.cpp index 7814d8fbe8..aac3b5fa57 100644 --- a/mm/2s2h/ShipUtils.cpp +++ b/mm/2s2h/ShipUtils.cpp @@ -43,8 +43,25 @@ 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 = { +std::array miscellaneousTextures = { gArcheryScoreIconTex, gBarrelTrackerIcon, gChestTrackerIcon, @@ -69,6 +86,8 @@ std::array miscellaneousTextures = { gPauseUnusedCursorTex, gWorldMapOwlFaceTex, gItemIconTingleMapTex, + gThreeDayClockSunHourTex, + gThreeDayClockMoonHourTex, gOcarinaATex, gOcarinaCDownTex, gOcarinaCLeftTex, diff --git a/mm/2s2h/ShipUtils.h b/mm/2s2h/ShipUtils.h index 11f11ea204..a1eaab4abe 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 @@ -17,6 +24,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); 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; diff --git a/mm/src/audio/code_8019AF00.c b/mm/src/audio/code_8019AF00.c index 3eb22220fc..030023d2ba 100644 --- a/mm/src/audio/code_8019AF00.c +++ b/mm/src/audio/code_8019AF00.c @@ -2666,8 +2666,7 @@ void AudioOcarina_PlayControllerInput(u8 isOcarinaSfxSuppressedWhenCancelled) { sCurOcarinaButtonIndex = OCARINA_BTN_C_UP; } - if (GameInteractor_Should(VB_PLAY_OCARINA_NOTE, sOcarinaInputButtonCur, &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) && 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; 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(); 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; } diff --git a/mm/src/code/z_message.c b/mm/src/code/z_message.c index 4ce98b5bd8..af383621c4 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++; @@ -2183,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; } @@ -2932,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 3cdd9a5fbb..ec8b8d0b74 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, @@ -203,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; } @@ -1291,7 +1294,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 +1321,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 +1351,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 +1508,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 +1686,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 @@ -1702,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/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) { 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; 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; } 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 dcab8ff386..673e0f4564 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_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_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_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), 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; +} 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; 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); 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; 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)) { 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;