diff --git a/libultraship/libultraship/CMakeLists.txt b/libultraship/libultraship/CMakeLists.txt index 8f365162d9..b599936601 100644 --- a/libultraship/libultraship/CMakeLists.txt +++ b/libultraship/libultraship/CMakeLists.txt @@ -87,6 +87,7 @@ set(Source_Files__Controller "KeyboardController.cpp" "KeyboardController.h" "UltraController.h" + "DummyController.cpp" "DummyController.h" ) diff --git a/libultraship/libultraship/Console.cpp b/libultraship/libultraship/Console.cpp index 846e6e0a0d..65ec3ea816 100644 --- a/libultraship/libultraship/Console.cpp +++ b/libultraship/libultraship/Console.cpp @@ -389,4 +389,20 @@ namespace Ship { Commands[command] = entry; } } + + std::string Console::GetCurrentChannel() { + return currentChannel; + } + + bool Console::IsOpened() { + return opened; + } + + void Console::Close() { + opened = false; + } + + void Console::Open() { + opened = true; + } } \ No newline at end of file diff --git a/libultraship/libultraship/Console.h b/libultraship/libultraship/Console.h index 4f846a2c50..91aebb907b 100644 --- a/libultraship/libultraship/Console.h +++ b/libultraship/libultraship/Console.h @@ -93,10 +93,9 @@ namespace Ship { void Append(const std::string& channel, spdlog::level::level_enum priority, const char* fmt, ...); bool HasCommand(const std::string& command); void AddCommand(const std::string& command, CommandEntry entry); - - std::string GetCurrentChannel() { return currentChannel; } - bool IsOpened() { return opened; } - void Close() { opened = false; } - void Open() { opened = true; } + std::string GetCurrentChannel(); + bool IsOpened(); + void Close(); + void Open(); }; } \ No newline at end of file diff --git a/libultraship/libultraship/ControlDeck.h b/libultraship/libultraship/ControlDeck.h index d925020c46..e0e85f27ae 100644 --- a/libultraship/libultraship/ControlDeck.h +++ b/libultraship/libultraship/ControlDeck.h @@ -21,7 +21,7 @@ namespace Ship { size_t GetNumVirtualDevices(); uint8_t* GetControllerBits(); private: - std::vector virtualDevices = {}; + std::vector virtualDevices = {}; std::vector> physicalDevices = {}; uint8_t* controllerBits = nullptr; }; diff --git a/libultraship/libultraship/Controller.cpp b/libultraship/libultraship/Controller.cpp index c1d32c2a9c..ef2ebe943d 100644 --- a/libultraship/libultraship/Controller.cpp +++ b/libultraship/libultraship/Controller.cpp @@ -112,4 +112,16 @@ namespace Ship { std::shared_ptr Controller::getProfile(int32_t virtualSlot) { return profiles[virtualSlot]; } + + std::shared_ptr Controller::GetAttachment() { + return Attachment; + } + + bool Controller::IsRumbling() { + return isRumbling; + } + + std::string Controller::GetGuid() { + return GUID; + } } diff --git a/libultraship/libultraship/Controller.h b/libultraship/libultraship/Controller.h index 76d1c46afb..5cd5b86544 100644 --- a/libultraship/libultraship/Controller.h +++ b/libultraship/libultraship/Controller.h @@ -40,7 +40,6 @@ namespace Ship { public: virtual ~Controller() = default; Controller(); - void Read(OSContPad* pad, int32_t virtualSlot); virtual void ReadFromSource(int32_t virtualSlot) = 0; virtual void WriteToSource(int32_t virtualSlot, ControllerCallback* controller) = 0; virtual bool Connected() const = 0; @@ -49,8 +48,12 @@ namespace Ship { virtual void CreateDefaultBinding(int32_t virtualSlot) = 0; virtual void ClearRawPress() = 0; virtual int32_t ReadRawPress() = 0; + virtual const std::string GetButtonName(int32_t virtualSlot, int32_t n64Button) = 0; + virtual const std::string GetControllerName() = 0; + void Read(OSContPad* pad, int32_t virtualSlot); void SetButtonMapping(int32_t virtualSlot, int32_t n64Button, int32_t dwScancode); - std::shared_ptr GetAttachment() { return Attachment; } + std::shared_ptr GetAttachment(); + std::shared_ptr getProfile(int32_t virtualSlot); int8_t& getLeftStickX(int32_t virtualSlot); int8_t& getLeftStickY(int32_t virtualSlot); int8_t& getRightStickX(int32_t virtualSlot); @@ -58,11 +61,8 @@ namespace Ship { int32_t& getPressedButtons(int32_t virtualSlot); float& getGyroX(int32_t virtualSlot); float& getGyroY(int32_t virtualSlot); - std::shared_ptr getProfile(int32_t virtualSlot); - bool IsRumbling() { return isRumbling; } - std::string GetGuid() { return GUID; } - virtual const std::string GetButtonName(int32_t virtualSlot, int32_t n64Button) = 0; - virtual const std::string GetControllerName() = 0; + bool IsRumbling(); + std::string GetGuid(); protected: std::shared_ptr Attachment; diff --git a/libultraship/libultraship/DummyController.cpp b/libultraship/libultraship/DummyController.cpp new file mode 100644 index 0000000000..fba77edb1d --- /dev/null +++ b/libultraship/libultraship/DummyController.cpp @@ -0,0 +1,61 @@ +#include "DummyController.h" + +namespace Ship { + DummyController::DummyController(const std::string& CUID, const std::string& KeyName, bool Connected) { + GUID = CUID; + isConnected = Connected; + ButtonName = KeyName; + } + + void DummyController::ReadFromSource(int32_t virtualSlot) { + + } + + const std::string DummyController::GetControllerName() { + return GUID; + } + + const std::string DummyController::GetButtonName(int32_t virtualSlot, int32_t n64Button) { + return ButtonName; + } + + void DummyController::WriteToSource(int32_t virtualSlot, ControllerCallback* controller){ + + } + + bool DummyController::Connected() const { + return isConnected; + } + + bool DummyController::CanRumble() const { + return false; + } + + bool DummyController::CanGyro() const { + return false; + } + + void DummyController::CreateDefaultBinding(int32_t slot) { + + } + + std::string DummyController::GetControllerType() { + return "Unk"; + } + + std::string DummyController::GetConfSection() { + return "Unk"; + } + + std::string DummyController::GetBindingConfSection() { + return "Unk"; + } + + void DummyController::ClearRawPress() { + + } + + int32_t DummyController::ReadRawPress() { + return -1; + } +} diff --git a/libultraship/libultraship/DummyController.h b/libultraship/libultraship/DummyController.h index 7e71b7033a..c66d83c3d8 100644 --- a/libultraship/libultraship/DummyController.h +++ b/libultraship/libultraship/DummyController.h @@ -7,31 +7,25 @@ namespace Ship { class DummyController final : public Controller { public: - DummyController(const std::string& CUID, const std::string& KeyName, bool Connected) { - GUID = CUID; - isConnected = Connected; - ButtonName = KeyName; - } - + DummyController(const std::string& CUID, const std::string& KeyName, bool Connected); std::map, int32_t> ReadButtonPress(); - void ReadFromSource(int32_t slot) override {} - const std::string GetControllerName() override { return GUID; } - const std::string GetButtonName(int slot, int n64Button) override { return ButtonName; } - void WriteToSource(int32_t slot, ControllerCallback* controller) override { } - bool Connected() const override { return isConnected; } - bool CanRumble() const override { return false; } - bool CanGyro() const override { return false; } - - void ClearRawPress() override {} - int32_t ReadRawPress() override { return -1; } - bool HasPadConf() const { return true; } - std::optional GetPadConfSection() { return "Unk"; } - void CreateDefaultBinding(int32_t slot) override {} + void ReadFromSource(int32_t virtualSlot) override; + const std::string GetControllerName() override; + const std::string GetButtonName(int32_t virtualSlot, int32_t n64Button) override; + void WriteToSource(int32_t slot, ControllerCallback* controller) override; + bool Connected() const override; + bool CanRumble() const override; + bool CanGyro() const override; + void ClearRawPress() override; + int32_t ReadRawPress() override; + bool HasPadConf() const; + std::optional GetPadConfSection(); + void CreateDefaultBinding(int32_t virtualSlot) override; protected: std::string ButtonName; bool isConnected = false; - std::string GetControllerType() { return "Unk"; } - std::string GetConfSection() { return "Unk"; } - std::string GetBindingConfSection() { return "Unk"; } + std::string GetControllerType(); + std::string GetConfSection(); + std::string GetBindingConfSection(); }; } \ No newline at end of file diff --git a/libultraship/libultraship/Hooks.cpp b/libultraship/libultraship/Hooks.cpp new file mode 100644 index 0000000000..577801743f --- /dev/null +++ b/libultraship/libultraship/Hooks.cpp @@ -0,0 +1,5 @@ +#include "Hooks.h" + +namespace Ship { + +} diff --git a/libultraship/libultraship/ImGuiImpl.cpp b/libultraship/libultraship/ImGuiImpl.cpp index d430bc7699..c74d126fb6 100644 --- a/libultraship/libultraship/ImGuiImpl.cpp +++ b/libultraship/libultraship/ImGuiImpl.cpp @@ -2011,7 +2011,9 @@ namespace SohImGui { ); PaddedEnhancementCheckbox("Key Colors Match Dungeon", "gRandoMatchKeyColors", true, false); Tooltip( - "Matches the color of small keys and boss keys to the dungeon they belong to. This helps identify keys from afar and adds a little bit of flair."); + "Matches the color of small keys and boss keys to the dungeon they belong to. " + "This helps identify keys from afar and adds a little bit of flair.\n\nThis only " + "applies to seeds with keys and boss keys shuffled to Any Dungeon, Overworld, or Anywhere."); PaddedEnhancementCheckbox("Quest Item Fanfares", "gRandoQuestItemFanfares", true, false); Tooltip( "Play unique fanfares when obtaining quest items " diff --git a/libultraship/libultraship/KeyboardController.cpp b/libultraship/libultraship/KeyboardController.cpp index ff0b72b70e..88fb0f8979 100644 --- a/libultraship/libultraship/KeyboardController.cpp +++ b/libultraship/libultraship/KeyboardController.cpp @@ -7,7 +7,7 @@ #endif #include "Hooks.h" - + #include "Window.h" namespace Ship { @@ -100,4 +100,28 @@ namespace Ship { const std::string KeyboardController::GetControllerName() { return "Keyboard"; } + + bool KeyboardController::Connected() const { + return true; + } + + bool KeyboardController::CanRumble() const { + return false; + } + + bool KeyboardController::CanGyro() const { + return false; + } + + void KeyboardController::ClearRawPress() { + lastKey = -1; + } + + void KeyboardController::SetLastScancode(int32_t key) { + lastScancode = key; + } + + int32_t KeyboardController::GetLastScancode() { + return lastScancode; + } } diff --git a/libultraship/libultraship/KeyboardController.h b/libultraship/libultraship/KeyboardController.h index 345e34718e..6e2a9f5f5a 100644 --- a/libultraship/libultraship/KeyboardController.h +++ b/libultraship/libultraship/KeyboardController.h @@ -9,26 +9,18 @@ namespace Ship { void ReadFromSource(int32_t virtualSlot) override; void WriteToSource(int32_t virtualSlot, ControllerCallback* controller) override; - bool Connected() const override { return true; } - bool CanRumble() const override { return false; } - bool CanGyro() const override { return false; } const std::string GetControllerName() override; const std::string GetButtonName(int32_t virtualSlot, int32_t n64Button) override; bool PressButton(int32_t dwScancode); bool ReleaseButton(int32_t dwScancode); - - void ClearRawPress() override { - lastKey = -1; - } - + bool Connected() const override; + bool CanRumble() const override; + bool CanGyro() const override; + void ClearRawPress() override; int32_t ReadRawPress() override; void ReleaseAllButtons(); - - void SetLastScancode(int32_t key) { - lastScancode = key; - } - - int32_t GetLastScancode() { return lastScancode; } + void SetLastScancode(int32_t key); + int32_t GetLastScancode(); void CreateDefaultBinding(int32_t virtualSlot) override; protected: diff --git a/libultraship/libultraship/ResourceMgr.cpp b/libultraship/libultraship/ResourceMgr.cpp index 4ca77708a3..0bf34ffe80 100644 --- a/libultraship/libultraship/ResourceMgr.cpp +++ b/libultraship/libultraship/ResourceMgr.cpp @@ -333,4 +333,17 @@ namespace Ship { const std::string* ResourceMgr::HashToString(uint64_t Hash) const { return OTR->HashToString(Hash); } + + std::shared_ptr ResourceMgr::GetArchive() { + return OTR; + } + + std::shared_ptr ResourceMgr::GetContext() { + return Context; + } + + std::shared_ptr ResourceMgr::LoadResource(const std::string& FilePath) { + return LoadResource(FilePath.c_str()); + } + } diff --git a/libultraship/libultraship/ResourceMgr.h b/libultraship/libultraship/ResourceMgr.h index e0b0c07cfb..f98b8cb8ba 100644 --- a/libultraship/libultraship/ResourceMgr.h +++ b/libultraship/libultraship/ResourceMgr.h @@ -23,20 +23,17 @@ namespace Ship { bool IsRunning(); bool DidLoadSuccessfully(); - std::shared_ptr GetArchive() { return OTR; } - std::shared_ptr GetContext() { return Context; } - + std::shared_ptr GetArchive(); + std::shared_ptr GetContext(); const std::string* HashToString(uint64_t Hash) const; - void InvalidateResourceCache(); - uint32_t GetGameVersion(); void SetGameVersion(uint32_t newGameVersion); std::shared_ptr LoadFileAsync(const std::string& FilePath); std::shared_ptr LoadFile(const std::string& FilePath); std::shared_ptr GetCachedFile(const char* FilePath) const; std::shared_ptr LoadResource(const char* FilePath); - std::shared_ptr LoadResource(const std::string& FilePath) { return LoadResource(FilePath.c_str()); } + std::shared_ptr LoadResource(const std::string& FilePath); std::variant, std::shared_ptr> LoadResourceAsync(const char* FilePath); std::shared_ptr>> CacheDirectory(const std::string& SearchMask); std::shared_ptr>> CacheDirectoryAsync(const std::string& SearchMask); diff --git a/libultraship/libultraship/SDLController.cpp b/libultraship/libultraship/SDLController.cpp index d0bb8e1f78..8811b94cb1 100644 --- a/libultraship/libultraship/SDLController.cpp +++ b/libultraship/libultraship/SDLController.cpp @@ -12,6 +12,10 @@ extern "C" uint8_t __osMaxControllers; namespace Ship { + SDLController::SDLController(int32_t physicalSlot) : Controller(), Cont(nullptr), physicalSlot(physicalSlot) { + + } + bool SDLController::Open() { const auto NewCont = SDL_GameControllerOpen(physicalSlot); @@ -450,4 +454,23 @@ namespace Ship { profile->GyroData[DRIFT_Y] = 0.0f; profile->GyroData[GYRO_SENSITIVITY] = 1.0f; } + + bool SDLController::Connected() const { + return Cont != nullptr; + } + + bool SDLController::CanGyro() const { + return supportsGyro; + } + + bool SDLController::CanRumble() const { +#if SDL_COMPILEDVERSION >= SDL_VERSIONNUM(2,0,18) + return SDL_GameControllerHasRumble(Cont); +#endif + return false; + } + + void SDLController::ClearRawPress() { + + } } diff --git a/libultraship/libultraship/SDLController.h b/libultraship/libultraship/SDLController.h index e9c696dac7..111070843a 100644 --- a/libultraship/libultraship/SDLController.h +++ b/libultraship/libultraship/SDLController.h @@ -5,6 +5,19 @@ namespace Ship { class SDLController : public Controller { public: + SDLController(int32_t physicalSlot); + void ReadFromSource(int32_t virtualSlot) override; + const std::string GetControllerName() override; + const std::string GetButtonName(int32_t virtualSlot, int32_t n64Button) override; + void WriteToSource(int32_t virtualSlot, ControllerCallback* controller) override; + bool Connected() const override; + bool CanGyro() const override; + bool CanRumble() const override; + bool Open(); + void ClearRawPress() override; + int32_t ReadRawPress() override; + + protected: inline static const char* AxisNames[] = { "Left Stick X", "Left Stick Y", @@ -15,25 +28,6 @@ namespace Ship { "Start Button" }; - SDLController(int32_t physicalSlot) : Controller(), Cont(nullptr), physicalSlot(physicalSlot) { } - void ReadFromSource(int32_t virtualSlot) override; - const std::string GetControllerName() override; - const std::string GetButtonName(int32_t virtualSlot, int32_t n64Button) override; - void WriteToSource(int32_t virtualSlot, ControllerCallback* controller) override; - bool Connected() const override { return Cont != nullptr; } - bool CanGyro() const override { return supportsGyro; } - bool CanRumble() const override { -#if SDL_COMPILEDVERSION >= SDL_VERSIONNUM(2,0,18) - return SDL_GameControllerHasRumble(Cont); -#endif - return false; - } - - bool Open(); - void ClearRawPress() override {} - int32_t ReadRawPress() override; - - protected: void CreateDefaultBinding(int32_t virtualSlot) override; private: diff --git a/libultraship/libultraship/Window.cpp b/libultraship/libultraship/Window.cpp index 666c1e0c00..b9e84fd60d 100644 --- a/libultraship/libultraship/Window.cpp +++ b/libultraship/libultraship/Window.cpp @@ -581,4 +581,52 @@ namespace Ship { saveFile.close(); } + + bool Window::IsFullscreen() { + return bIsFullscreen; + } + + uint32_t Window::GetMenuBar() { + return dwMenubar; + } + + void Window::SetMenuBar(uint32_t dwMenuBar) { + this->dwMenubar = dwMenuBar; + } + + std::string Window::GetName() { + return Name; + } + + std::shared_ptr Window::GetControlDeck() { + return ControllerApi; + } + + std::shared_ptr Window::GetAudioPlayer() { + return APlayer; + } + + std::shared_ptr Window::GetResourceManager() { + return ResMan; + } + + std::shared_ptr Window::GetConfig() { + return Config; + } + + std::shared_ptr Window::GetLogger() { + return Logger; + } + + const char* Window::GetKeyName(int32_t scancode) { + return WmApi->get_key_name(scancode); + } + + int32_t Window::GetLastScancode() { + return lastScancode; + } + + void Window::SetLastScancode(int32_t scanCode) { + lastScancode = scanCode; + } } diff --git a/libultraship/libultraship/Window.h b/libultraship/libultraship/Window.h index 6a0e688997..b70587bf58 100644 --- a/libultraship/libultraship/Window.h +++ b/libultraship/libultraship/Window.h @@ -36,20 +36,20 @@ namespace Ship { void ToggleFullscreen(); void SetFullscreen(bool bIsFullscreen); void ShowCursor(bool hide); - bool IsFullscreen() { return bIsFullscreen; } uint32_t GetCurrentWidth(); uint32_t GetCurrentHeight(); - uint32_t GetMenuBar() { return dwMenubar; } - void SetMenuBar(uint32_t dwMenuBar) { this->dwMenubar = dwMenuBar; } - std::string GetName() { return Name; } - std::shared_ptr GetControlDeck() { return ControllerApi; }; - std::shared_ptr GetAudioPlayer() { return APlayer; } - std::shared_ptr GetResourceManager() { return ResMan; } - std::shared_ptr GetConfig() { return Config; } - std::shared_ptr GetLogger() { return Logger; } - const char* GetKeyName(int32_t scancode) { return WmApi->get_key_name(scancode); } - int32_t GetLastScancode() { return lastScancode; } - void SetLastScancode(int32_t scanCode) { lastScancode = scanCode; } + bool IsFullscreen(); + uint32_t GetMenuBar(); + void SetMenuBar(uint32_t dwMenuBar); + std::string GetName(); + std::shared_ptr GetControlDeck(); + std::shared_ptr GetAudioPlayer(); + std::shared_ptr GetResourceManager(); + std::shared_ptr GetConfig(); + std::shared_ptr GetLogger(); + const char* GetKeyName(int32_t scancode); + int32_t GetLastScancode(); + void SetLastScancode(int32_t scanCode); protected: Window() = default; diff --git a/soh/include/macros.h b/soh/include/macros.h index 1822cb1d69..bcfb3fce5f 100644 --- a/soh/include/macros.h +++ b/soh/include/macros.h @@ -260,5 +260,6 @@ extern GraphicsContext* __gfxCtx; #define SEG_ADDR(seg, addr) (addr | (seg << 24) | 1) +#define NUM_TRIALS 6 #endif diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 14da6a2fce..6ad6eeb170 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -183,7 +183,7 @@ typedef struct { char ganonHintText[150]; char ganonText[250]; u8 seedIcons[5]; - u16 randomizerInf[2]; + u16 randomizerInf[4]; u8 temporaryWeapon; u16 adultTradeItems; } SaveContext; // size = 0x1428 diff --git a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h index 61b64dbcb0..1af49d6a62 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h @@ -17,7 +17,8 @@ typedef enum { TEXT_BLUE_RUPEE = 0xCC, TEXT_RED_RUPEE = 0xF0, TEXT_PURPLE_RUPEE = 0xF1, - TEXT_HUGE_RUPEE = 0xF2 + TEXT_HUGE_RUPEE = 0xF2, + TEXT_BEAN_SALESMAN = 0x405E } TextIDs; #ifdef __cplusplus diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.cpp b/soh/soh/Enhancements/randomizer/3drando/fill.cpp index 14bf594c2e..e23f9633d6 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.cpp @@ -1039,6 +1039,19 @@ int Fill() { //Fast fill for the rest of the pool std::vector remainingPool = FilterAndEraseFromPool(ItemPool, [](const auto i) { return true; }); FastFill(remainingPool, GetAllEmptyLocations(), false); + + //Add prices for scrubsanity, this is unique to SoH because we write/read scrub prices to/from the spoilerfile. + if (Scrubsanity.Is(SCRUBSANITY_AFFORDABLE)) { + for (size_t i = 0; i < ScrubLocations.size(); i++) { + Location(ScrubLocations[i])->SetScrubsanityPrice(10); + } + } else if (Scrubsanity.Is(SCRUBSANITY_RANDOM_PRICES)) { + for (size_t i = 0; i < ScrubLocations.size(); i++) { + int randomPrice = GetRandomScrubPrice(); + Location(ScrubLocations[i])->SetScrubsanityPrice(randomPrice); + } + } + GeneratePlaythrough(); //Successful placement, produced beatable result if(playthroughBeatable && !placementFailure) { diff --git a/soh/soh/Enhancements/randomizer/3drando/item_location.cpp b/soh/soh/Enhancements/randomizer/3drando/item_location.cpp index 08d191740d..0a4405c785 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_location.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_location.cpp @@ -1011,6 +1011,56 @@ std::vector> ShopLocationLists = { GC_ShopLocations, }; +//List of scrubs, used for pricing the scrubs +std::vector ScrubLocations = { + LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT, + LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT, + LW_DEKU_SCRUB_NEAR_BRIDGE, + LW_DEKU_SCRUB_GROTTO_REAR, + LW_DEKU_SCRUB_GROTTO_FRONT, + SFM_DEKU_SCRUB_GROTTO_REAR, + SFM_DEKU_SCRUB_GROTTO_FRONT, + HF_DEKU_SCRUB_GROTTO, + LH_DEKU_SCRUB_GROTTO_LEFT, + LH_DEKU_SCRUB_GROTTO_RIGHT, + LH_DEKU_SCRUB_GROTTO_CENTER, + GV_DEKU_SCRUB_GROTTO_REAR, + GV_DEKU_SCRUB_GROTTO_FRONT, + COLOSSUS_DEKU_SCRUB_GROTTO_REAR, + COLOSSUS_DEKU_SCRUB_GROTTO_FRONT, + GC_DEKU_SCRUB_GROTTO_LEFT, + GC_DEKU_SCRUB_GROTTO_RIGHT, + GC_DEKU_SCRUB_GROTTO_CENTER, + DMC_DEKU_SCRUB, + DMC_DEKU_SCRUB_GROTTO_LEFT, + DMC_DEKU_SCRUB_GROTTO_RIGHT, + DMC_DEKU_SCRUB_GROTTO_CENTER, + ZR_DEKU_SCRUB_GROTTO_REAR, + ZR_DEKU_SCRUB_GROTTO_FRONT, + LLR_DEKU_SCRUB_GROTTO_LEFT, + LLR_DEKU_SCRUB_GROTTO_RIGHT, + LLR_DEKU_SCRUB_GROTTO_CENTER, + DEKU_TREE_MQ_DEKU_SCRUB, + DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_LEFT, + DODONGOS_CAVERN_DEKU_SCRUB_SIDE_ROOM_NEAR_DODONGOS, + DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_RIGHT, + DODONGOS_CAVERN_DEKU_SCRUB_LOBBY, + DODONGOS_CAVERN_MQ_DEKU_SCRUB_LOBBY_REAR, + DODONGOS_CAVERN_MQ_DEKU_SCRUB_LOBBY_FRONT, + DODONGOS_CAVERN_MQ_DEKU_SCRUB_STAIRCASE, + DODONGOS_CAVERN_MQ_DEKU_SCRUB_SIDE_ROOM_NEAR_LOWER_LIZALFOS, + JABU_JABUS_BELLY_DEKU_SCRUB, + GANONS_CASTLE_DEKU_SCRUB_CENTER_LEFT, + GANONS_CASTLE_DEKU_SCRUB_CENTER_RIGHT, + GANONS_CASTLE_DEKU_SCRUB_RIGHT, + GANONS_CASTLE_DEKU_SCRUB_LEFT, + GANONS_CASTLE_MQ_DEKU_SCRUB_RIGHT, + GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER_LEFT, + GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER, + GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER_RIGHT, + GANONS_CASTLE_MQ_DEKU_SCRUB_LEFT, +}; + //List of gossip stone locations for hints std::vector gossipStoneLocations = { DMC_GOSSIP_STONE, diff --git a/soh/soh/Enhancements/randomizer/3drando/item_location.hpp b/soh/soh/Enhancements/randomizer/3drando/item_location.hpp index 823cd88ef5..efe1a62576 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_location.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_location.hpp @@ -257,8 +257,8 @@ public: } void SetPrice(uint16_t price_) { - //don't override price if the price was set for shopsanity - if (hasShopsanityPrice) { + //don't override price if the price was set for shopsanity/scrubsanity + if (hasShopsanityPrice || hasScrubsanityPrice) { return; } price = price_; @@ -269,10 +269,19 @@ public: hasShopsanityPrice = true; } + void SetScrubsanityPrice(uint16_t price_) { + price = price_; + hasScrubsanityPrice = true; + } + bool HasShopsanityPrice() const { return hasShopsanityPrice; } + bool HasScrubsanityPrice() const { + return hasScrubsanityPrice; + } + bool IsExcluded() const { return excludedOption.Value(); } @@ -426,6 +435,7 @@ public: isHintable = false; price = 0; hasShopsanityPrice = false; + hasScrubsanityPrice = false; hidden = false; } @@ -451,6 +461,7 @@ private: bool isHintable = false; uint32_t parentRegion = NONE; bool hasShopsanityPrice = false; + bool hasScrubsanityPrice = false; bool hidden = false; }; @@ -467,6 +478,8 @@ ItemLocation* Location(uint32_t locKey); extern std::vector> ShopLocationLists; +extern std::vector ScrubLocations; + extern std::vector gossipStoneLocations; extern std::vector dungeonRewardLocations; diff --git a/soh/soh/Enhancements/randomizer/3drando/settings.cpp b/soh/soh/Enhancements/randomizer/3drando/settings.cpp index 40278c6a8c..dd9f6d2de5 100644 --- a/soh/soh/Enhancements/randomizer/3drando/settings.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/settings.cpp @@ -2517,21 +2517,20 @@ namespace Settings { BridgeRewardCount.SetSelectedIndex(cvarSettings[RSK_RAINBOW_BRIDGE_REWARD_COUNT]); BridgeDungeonCount.SetSelectedIndex(cvarSettings[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT]); BridgeTokenCount.SetSelectedIndex(cvarSettings[RSK_RAINBOW_BRIDGE_TOKEN_COUNT]); - RandomGanonsTrials.SetSelectedIndex(cvarSettings[RSK_RANDOM_TRIALS]); - // RANDTODO: Switch this back once Ganon's Trials Count is properly implemented. - //GanonsTrialsCount.SetSelectedIndex(cvarSettings[RSK_TRIAL_COUNT]); - switch (cvarSettings[RSK_TRIAL_COUNT]) { - case 0: - GanonsTrialsCount.SetSelectedIndex(6); - break; - case 1: - GanonsTrialsCount.SetSelectedIndex(0); - break; + if (cvarSettings[RSK_RANDOM_TRIALS] == 2) { + RandomGanonsTrials.SetSelectedIndex(1); + } else { + RandomGanonsTrials.SetSelectedIndex(0); + } + if (cvarSettings[RSK_RANDOM_TRIALS] == 0) { + GanonsTrialsCount.SetSelectedIndex(0); + } else { + GanonsTrialsCount.SetSelectedIndex(cvarSettings[RSK_TRIAL_COUNT]); } - ShuffleRewards.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_DUNGEON_REWARDS]); ShuffleSongs.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_SONGS]); Tokensanity.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_TOKENS]); + Scrubsanity.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_SCRUBS]); ShuffleCows.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_COWS]); ShuffleKokiriSword.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_KOKIRI_SWORD]); ShuffleOcarinas.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_OCARINA]); @@ -2551,6 +2550,7 @@ namespace Settings { ShuffleFrogSongRupees.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_FROG_SONG_RUPEES]); ShuffleAdultTradeQuest.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_ADULT_TRADE]); + ShuffleMagicBeans.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_MAGIC_BEANS]); // the checkbox works because 0 is "Off" and 1 is "Fairy Ocarina" StartingOcarina.SetSelectedIndex(cvarSettings[RSK_STARTING_OCARINA]); diff --git a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp index 675f28cae5..63230c2d1d 100644 --- a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp @@ -501,25 +501,23 @@ static void WriteMasterQuestDungeons(tinyxml2::XMLDocument& spoilerLog) { } } -// Writes the required trails to the spoiler log, if there are any. -static void WriteRequiredTrials(tinyxml2::XMLDocument& spoilerLog) { - auto parentNode = spoilerLog.NewElement("required-trials"); - - for (const auto* trial : Trial::trialList) { - if (trial->IsSkipped()) { - continue; +// Writes the required trials to the spoiler log, if there are any. +static void WriteRequiredTrials() { + for (const auto& trial : Trial::trialList) { + if (trial->IsRequired()) { + std::string trialName; + switch (gSaveContext.language) { + case LANGUAGE_FRA: + trialName = trial->GetName().GetFrench(); + break; + case LANGUAGE_ENG: + default: + trialName = trial->GetName().GetEnglish(); + break; + } + jsonData["requiredTrials"].push_back(RemoveLineBreaks(trialName)); + } } - - auto node = parentNode->InsertNewChildElement("trial"); - // PURPLE TODO: LOCALIZATION - std::string name = trial->GetName().GetEnglish(); - name[0] = toupper(name[0]); // Capitalize T in "The" - node->SetAttribute("name", name.c_str()); - } - - if (!parentNode->NoChildren()) { - spoilerLog.RootElement()->InsertEndChild(parentNode); - } } // Writes the intended playthrough to the spoiler log, separated into spheres. @@ -673,15 +671,24 @@ static void WriteHints(int language) { static void WriteAllLocations(int language) { for (const uint32_t key : allLocations) { ItemLocation* location = Location(key); + std::string placedItemName; switch (language) { - case 0: - default: - jsonData["locations"][location->GetName()] = location->GetPlacedItemName().english; - break; - case 2: - jsonData["locations"][location->GetName()] = location->GetPlacedItemName().french; - break; + case 0: + default: + placedItemName = location->GetPlacedItemName().english; + break; + case 2: + placedItemName = location->GetPlacedItemName().french; + break; + } + + // Eventually check for other things here like fake name + if (location->HasScrubsanityPrice() || location->HasShopsanityPrice()) { + jsonData["locations"][location->GetName()]["item"] = placedItemName; + jsonData["locations"][location->GetName()]["price"] = location->GetPrice(); + } else { + jsonData["locations"][location->GetName()] = placedItemName; } } } @@ -714,7 +721,7 @@ const char* SpoilerLog_Write(int language) { // WriteEnabledGlitches(spoilerLog); //} //WriteMasterQuestDungeons(spoilerLog); - //WriteRequiredTrials(spoilerLog); + WriteRequiredTrials(); WritePlaythrough(); //WriteWayOfTheHeroLocation(spoilerLog); @@ -764,7 +771,7 @@ bool PlacementLog_Write() { WriteEnabledTricks(placementLog); WriteEnabledGlitches(placementLog); WriteMasterQuestDungeons(placementLog); - WriteRequiredTrials(placementLog); + //WriteRequiredTrials(placementLog); placementtxt = "\n" + placementtxt; diff --git a/soh/soh/Enhancements/randomizer/3drando/trial.cpp b/soh/soh/Enhancements/randomizer/3drando/trial.cpp index 1659673c97..e9ee31317b 100644 --- a/soh/soh/Enhancements/randomizer/3drando/trial.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/trial.cpp @@ -7,12 +7,12 @@ TrialInfo::TrialInfo(Text name_) TrialInfo::~TrialInfo() = default; - TrialInfo ForestTrial = TrialInfo(Text{"the Forest Trial", /*french*/"l'épreuve de la forêt", /*spanish*/"la prueba del bosque"}); - TrialInfo FireTrial = TrialInfo(Text{"the Fire Trial", /*french*/"l'épreuve du feu", /*spanish*/"la prueba del fuego"}); - TrialInfo WaterTrial = TrialInfo(Text{"the Water Trial", /*french*/"l'épreuve de l'eau", /*spanish*/"la prueba del agua"}); - TrialInfo SpiritTrial = TrialInfo(Text{"the Spirit Trial", /*french*/"l'épreuve de l'esprit", /*spanish*/"la prueba del espíritu"}); - TrialInfo ShadowTrial = TrialInfo(Text{"the Shadow Trial", /*french*/"l'épreuve de l'ombre", /*spanish*/"la prueba de las sombras"}); - TrialInfo LightTrial = TrialInfo(Text{"the Light Trial", /*french*/"l'épreuve de la lumière", /*spanish*/"la prueba de la luz"}); + TrialInfo ForestTrial = TrialInfo(Text{"the Forest Trial", /*french*/"l'épreuve de la Forêt", /*spanish*/"la prueba del bosque"}); + TrialInfo FireTrial = TrialInfo(Text{"the Fire Trial", /*french*/"l'épreuve du Feu", /*spanish*/"la prueba del fuego"}); + TrialInfo WaterTrial = TrialInfo(Text{"the Water Trial", /*french*/"l'épreuve de l'Eau", /*spanish*/"la prueba del agua"}); + TrialInfo SpiritTrial = TrialInfo(Text{"the Spirit Trial", /*french*/"l'épreuve de l'Esprit", /*spanish*/"la prueba del espíritu"}); + TrialInfo ShadowTrial = TrialInfo(Text{"the Shadow Trial", /*french*/"l'épreuve de l'Ombre", /*spanish*/"la prueba de las sombras"}); + TrialInfo LightTrial = TrialInfo(Text{"the Light Trial", /*french*/"l'épreuve de la Lumière", /*spanish*/"la prueba de la luz"}); const TrialArray trialList = { &ForestTrial, diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 0e931e6698..90e6c22520 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -32,7 +32,7 @@ u8 generated; const std::string Randomizer::getItemMessageTableID = "Randomizer"; const std::string Randomizer::hintMessageTableID = "RandomizerHints"; -const std::string Randomizer::scrubMessageTableID = "RandomizerScrubs"; +const std::string Randomizer::merchantMessageTableID = "RandomizerMerchants"; const std::string Randomizer::rupeeMessageTableID = "RandomizerRupees"; const std::string Randomizer::NaviRandoMessageTableID = "RandomizerNavi"; @@ -104,8 +104,24 @@ Sprite* Randomizer::GetSeedTexture(uint8_t index) { Randomizer::~Randomizer() { this->randoSettings.clear(); this->itemLocations.clear(); + this->randomizerMerchantPrices.clear(); } +std::unordered_map spoilerFileTrialToEnum = { + { "the Forest Trial", RAND_INF_TRIALS_DONE_FOREST_TRIAL }, + { "l'épreuve de la Forêt", RAND_INF_TRIALS_DONE_FOREST_TRIAL }, + { "the Fire Trial", RAND_INF_TRIALS_DONE_FIRE_TRIAL }, + { "l'épreuve du Feu", RAND_INF_TRIALS_DONE_FIRE_TRIAL }, + { "the Water Trial", RAND_INF_TRIALS_DONE_WATER_TRIAL }, + { "l'épreuve de l'Eau", RAND_INF_TRIALS_DONE_WATER_TRIAL }, + { "the Spirit Trial", RAND_INF_TRIALS_DONE_SPIRIT_TRIAL }, + { "l'épreuve de l'Esprit", RAND_INF_TRIALS_DONE_SPIRIT_TRIAL }, + { "the Shadow Trial", RAND_INF_TRIALS_DONE_SHADOW_TRIAL }, + { "l'épreuve de l'Ombre", RAND_INF_TRIALS_DONE_SHADOW_TRIAL }, + { "the Light Trial", RAND_INF_TRIALS_DONE_LIGHT_TRIAL }, + { "l'épreuve de la Lumière", RAND_INF_TRIALS_DONE_LIGHT_TRIAL } +}; + std::unordered_map getItemIdToItemId = { { GI_BOW, ITEM_BOW }, { GI_ARROW_FIRE, ITEM_ARROW_FIRE }, @@ -546,9 +562,11 @@ std::unordered_map SpoilerfileSettingNameToEn { "Open Settings:Random Ganon's Trials", RSK_RANDOM_TRIALS }, { "Open Settings:Trial Count", RSK_TRIAL_COUNT }, { "Shuffle Settings:Shuffle Gerudo Card", RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD }, + { "Shuffle Settings:Scrub Shuffle", RSK_SHUFFLE_SCRUBS }, { "Shuffle Settings:Shuffle Cows", RSK_SHUFFLE_COWS }, { "Shuffle Settings:Tokensanity", RSK_SHUFFLE_TOKENS }, { "Shuffle Settings:Shuffle Adult Trade", RSK_SHUFFLE_ADULT_TRADE }, + { "Shuffle Settings:Shuffle Magic Beans", RSK_SHUFFLE_MAGIC_BEANS}, { "Start with Deku Shield", RSK_STARTING_DEKU_SHIELD }, { "Start with Kokiri Sword", RSK_STARTING_KOKIRI_SWORD }, { "Start with Fairy Ocarina", RSK_STARTING_OCARINA }, @@ -671,6 +689,12 @@ void Randomizer::LoadItemLocations(const char* spoilerFileName, bool silent) { itemLocations[RC_UNKNOWN_CHECK] = RG_NONE; } +void Randomizer::LoadRequiredTrials(const char* spoilerFileName) { + if (strcmp(spoilerFileName, "") != 0) { + ParseRequiredTrialsFile(spoilerFileName); + } +} + void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) { std::ifstream spoilerFileStream(sanitize(spoilerFileName)); if (!spoilerFileStream) @@ -773,9 +797,21 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) { numericValueString = it.value(); gSaveContext.randoSettings[index].value = std::stoi(numericValueString); break; + case RSK_SHUFFLE_SCRUBS: + if(it.value() == "Off") { + gSaveContext.randoSettings[index].value = 0; + } else if(it.value() == "Affordable") { + gSaveContext.randoSettings[index].value = 1; + } else if(it.value() == "Expensive") { + gSaveContext.randoSettings[index].value = 2; + } else if(it.value() == "Random Prices") { + gSaveContext.randoSettings[index].value = 3; + } + break; case RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD: case RSK_SHUFFLE_COWS: case RSK_SHUFFLE_ADULT_TRADE: + case RSK_SHUFFLE_MAGIC_BEANS: case RSK_RANDOM_TRIALS: case RSK_STARTING_DEKU_SHIELD: case RSK_STARTING_KOKIRI_SWORD: @@ -841,6 +877,16 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) { gSaveContext.randoSettings[index].value = 3; } break; + case RSK_GERUDO_KEYS: + if (it.value() == "Vanilla") { + gSaveContext.randoSettings[index].value = 0; + } else if (it.value() == "Any Dungeon") { + gSaveContext.randoSettings[index].value = 1; + } else if (it.value() == "Overworld") { + gSaveContext.randoSettings[index].value = 2; + } else if (it.value() == "Anywhere") { + gSaveContext.randoSettings[index].value = 3; + } case RSK_KEYSANITY: if(it.value() == "Start With") { gSaveContext.randoSettings[index].value = 0; @@ -1078,6 +1124,25 @@ void Randomizer::ParseHintLocationsFile(const char* spoilerFileName) { } } +void Randomizer::ParseRequiredTrialsFile(const char* spoilerFileName) { + std::ifstream spoilerFileStream(sanitize(spoilerFileName)); + if (!spoilerFileStream) { + return; + } + + try { + json spoilerFileJson; + spoilerFileStream >> spoilerFileJson; + json trialsJson = spoilerFileJson["requiredTrials"]; + + for (auto it = trialsJson.begin(); it != trialsJson.end(); it++) { + this->trialsRequired[spoilerFileTrialToEnum[it.value()]] = true; + } + } catch (const std::exception& e) { + return; + } +} + void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent) { std::ifstream spoilerFileStream(sanitize(spoilerFileName)); if (!spoilerFileStream) @@ -1104,9 +1169,10 @@ void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent for (auto itemit = itemJson.begin(); itemit != itemJson.end(); ++itemit) { // todo handle prices if (itemit.key() == "item") { - gSaveContext.itemLocations[index].check = SpoilerfileCheckNameToEnum[it.key()]; gSaveContext.itemLocations[index].get = SpoilerfileGetNameToEnum[itemit.value()]; + } else if (itemit.key() == "price") { + randomizerMerchantPrices[gSaveContext.itemLocations[index].check] = itemit.value(); } } } else { @@ -1127,6 +1193,10 @@ void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent } } +bool Randomizer::IsTrialRequired(RandomizerInf trial) { + return this->trialsRequired.contains(trial); +} + s16 Randomizer::GetRandomizedItemId(GetItemID ogId, s16 actorId, s16 actorParams, s16 sceneNum) { s16 itemId = GetItemFromActor(actorId, actorParams, sceneNum, ogId); return itemId; @@ -1199,8 +1269,6 @@ s16 Randomizer::GetItemFromGet(RandomizerGet randoGet, GetItemID ogItemId) { case RG_MAGIC_BEAN: return GI_BEAN; - case RG_MAGIC_BEAN_PACK: - return GI_BEAN; //todo make it 10 of them case RG_WEIRD_EGG: return GI_WEIRD_EGG; @@ -1359,7 +1427,71 @@ s16 Randomizer::GetItemFromGet(RandomizerGet randoGet, GetItemID ogItemId) { return GI_BOTTLE; case RG_BOTTLE_WITH_MILK: return GI_MILK_BOTTLE; - + + case RG_DEKU_TREE_MAP: + case RG_DODONGOS_CAVERN_MAP: + case RG_JABU_JABUS_BELLY_MAP: + case RG_FOREST_TEMPLE_MAP: + case RG_FIRE_TEMPLE_MAP: + case RG_WATER_TEMPLE_MAP: + case RG_SPIRIT_TEMPLE_MAP: + case RG_SHADOW_TEMPLE_MAP: + case RG_BOTTOM_OF_THE_WELL_MAP: + case RG_ICE_CAVERN_MAP: + if (GetRandoSettingValue(RSK_STARTING_MAPS_COMPASSES) < 3) { + return GI_MAP; + } else { + return randoGet; + } + + case RG_DEKU_TREE_COMPASS: + case RG_DODONGOS_CAVERN_COMPASS: + case RG_JABU_JABUS_BELLY_COMPASS: + case RG_FOREST_TEMPLE_COMPASS: + case RG_FIRE_TEMPLE_COMPASS: + case RG_WATER_TEMPLE_COMPASS: + case RG_SPIRIT_TEMPLE_COMPASS: + case RG_SHADOW_TEMPLE_COMPASS: + case RG_BOTTOM_OF_THE_WELL_COMPASS: + case RG_ICE_CAVERN_COMPASS: + if (GetRandoSettingValue(RSK_STARTING_MAPS_COMPASSES) < 3) { + return GI_COMPASS; + } else { + return randoGet; + } + + case RG_FOREST_TEMPLE_BOSS_KEY: + case RG_FIRE_TEMPLE_BOSS_KEY: + case RG_WATER_TEMPLE_BOSS_KEY: + case RG_SPIRIT_TEMPLE_BOSS_KEY: + case RG_SHADOW_TEMPLE_BOSS_KEY: + case RG_GANONS_CASTLE_BOSS_KEY: + if (GetRandoSettingValue(RSK_BOSS_KEYSANITY) < 3) { + return GI_KEY_BOSS; + } else { + return randoGet; + } + + case RG_FOREST_TEMPLE_SMALL_KEY: + case RG_FIRE_TEMPLE_SMALL_KEY: + case RG_WATER_TEMPLE_SMALL_KEY: + case RG_SPIRIT_TEMPLE_SMALL_KEY: + case RG_SHADOW_TEMPLE_SMALL_KEY: + case RG_BOTTOM_OF_THE_WELL_SMALL_KEY: + case RG_GERUDO_TRAINING_GROUNDS_SMALL_KEY: + case RG_GANONS_CASTLE_SMALL_KEY: + if (GetRandoSettingValue(RSK_KEYSANITY) < 3) { + return GI_KEY_SMALL; + } else { + return randoGet; + } + case RG_GERUDO_FORTRESS_SMALL_KEY: + if (GetRandoSettingValue(RSK_GERUDO_KEYS) == 0) { + return GI_KEY_SMALL; + } else { + return randoGet; + } + // todo test this with keys in own dungeon case RG_TREASURE_GAME_SMALL_KEY: return GI_DOOR_KEY; @@ -1570,6 +1702,61 @@ bool Randomizer::IsItemVanilla(RandomizerGet randoGet) { case RG_BUY_RED_POTION_40: case RG_BUY_RED_POTION_50: return true; + case RG_FOREST_TEMPLE_SMALL_KEY: + case RG_FIRE_TEMPLE_SMALL_KEY: + case RG_WATER_TEMPLE_SMALL_KEY: + case RG_SPIRIT_TEMPLE_SMALL_KEY: + case RG_SHADOW_TEMPLE_SMALL_KEY: + case RG_BOTTOM_OF_THE_WELL_SMALL_KEY: + case RG_GERUDO_TRAINING_GROUNDS_SMALL_KEY: + case RG_GANONS_CASTLE_SMALL_KEY: + if (GetRandoSettingValue(RSK_KEYSANITY) > 2) { + return false; + } + return true; + case RG_GERUDO_FORTRESS_SMALL_KEY: + if (GetRandoSettingValue(RSK_GERUDO_KEYS) != 0) { + return false; + } + return true; + case RG_FOREST_TEMPLE_BOSS_KEY: + case RG_FIRE_TEMPLE_BOSS_KEY: + case RG_WATER_TEMPLE_BOSS_KEY: + case RG_SPIRIT_TEMPLE_BOSS_KEY: + case RG_SHADOW_TEMPLE_BOSS_KEY: + if (GetRandoSettingValue(RSK_BOSS_KEYSANITY) > 2) { + return false; + } + return true; + case RG_GANONS_CASTLE_BOSS_KEY: + if (GetRandoSettingValue(RSK_GANONS_BOSS_KEY) > 2) { + return false; + } + return true; + case RG_DEKU_TREE_COMPASS: + case RG_DODONGOS_CAVERN_COMPASS: + case RG_JABU_JABUS_BELLY_COMPASS: + case RG_FOREST_TEMPLE_COMPASS: + case RG_FIRE_TEMPLE_COMPASS: + case RG_WATER_TEMPLE_COMPASS: + case RG_SPIRIT_TEMPLE_COMPASS: + case RG_SHADOW_TEMPLE_COMPASS: + case RG_BOTTOM_OF_THE_WELL_COMPASS: + case RG_ICE_CAVERN_COMPASS: + case RG_DEKU_TREE_MAP: + case RG_DODONGOS_CAVERN_MAP: + case RG_JABU_JABUS_BELLY_MAP: + case RG_FOREST_TEMPLE_MAP: + case RG_FIRE_TEMPLE_MAP: + case RG_WATER_TEMPLE_MAP: + case RG_SPIRIT_TEMPLE_MAP: + case RG_SHADOW_TEMPLE_MAP: + case RG_BOTTOM_OF_THE_WELL_MAP: + case RG_ICE_CAVERN_MAP: + if (GetRandoSettingValue(RSK_STARTING_MAPS_COMPASSES) > 2) { + return false; + } + return true; default: return false; } @@ -1596,6 +1783,280 @@ std::string Randomizer::GetGanonHintText() const { return ganonHintText; } +ScrubIdentity Randomizer::IdentifyScrub(s32 sceneNum, s32 actorParams, s32 respawnData) { + struct ScrubIdentity scrubIdentity; + + scrubIdentity.randomizerCheck = RC_UNKNOWN_CHECK; + scrubIdentity.getItemId = GI_NONE; + scrubIdentity.itemPrice = -1; + scrubIdentity.isShuffled = GetRandoSettingValue(RSK_SHUFFLE_SCRUBS) > 0; + + // Based on z_en_dns.c 93-113 + switch (actorParams) { + case 0x00: + scrubIdentity.getItemId = GI_NUTS_5_2; + break; + case 0x01: + scrubIdentity.getItemId = GI_STICKS_1; + break; + case 0x02: + scrubIdentity.getItemId = GI_HEART_PIECE; + break; + case 0x03: + scrubIdentity.getItemId = GI_SEEDS_30; + break; + case 0x04: + scrubIdentity.getItemId = GI_SHIELD_DEKU; + break; + case 0x05: + scrubIdentity.getItemId = GI_BOMBS_5; + break; + case 0x06: + scrubIdentity.getItemId = GI_ARROWS_LARGE; + break; + case 0x07: + scrubIdentity.getItemId = GI_POTION_RED; + break; + case 0x08: + scrubIdentity.getItemId = GI_POTION_GREEN; + break; + case 0x09: + scrubIdentity.getItemId = GI_STICK_UPGRADE_20; + break; + case 0x0A: + scrubIdentity.getItemId = GI_NUT_UPGRADE_30; + break; + } + + // TODO: Handle MQ scrubs + switch (sceneNum) { + case SCENE_DDAN: // Dodongo's Cavern + switch (actorParams) { + case 0x00: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_LEFT; + scrubIdentity.randomizerCheck = RC_DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_LEFT; + break; + case 0x01: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_DODONGOS_CAVERN_DEKU_SCRUB_SIDE_ROOM_NEAR_DODONGOS; + scrubIdentity.randomizerCheck = RC_DODONGOS_CAVERN_DEKU_SCRUB_SIDE_ROOM_NEAR_DODONGOS; + break; + case 0x03: + case 0x06: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_RIGHT; + scrubIdentity.randomizerCheck = RC_DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_RIGHT; + break; + case 0x04: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_DODONGOS_CAVERN_DEKU_SCRUB_LOBBY; + scrubIdentity.randomizerCheck = RC_DODONGOS_CAVERN_DEKU_SCRUB_LOBBY; + break; + } + break; + case SCENE_BDAN: // Jabu Jabu's Belly + switch (actorParams) { + case 0x00: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_JABU_JABUS_BELLY_DEKU_SCRUB; + scrubIdentity.randomizerCheck = RC_JABU_JABUS_BELLY_DEKU_SCRUB; + break; + } + break; + case SCENE_GANONTIKA: // Ganon's Castle + switch (actorParams) { + case 0x05: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_GANONS_CASTLE_DEKU_SCRUB_CENTER_LEFT; + scrubIdentity.randomizerCheck = RC_GANONS_CASTLE_DEKU_SCRUB_CENTER_LEFT; + break; + case 0x03: + case 0x06: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_GANONS_CASTLE_DEKU_SCRUB_CENTER_RIGHT; + scrubIdentity.randomizerCheck = RC_GANONS_CASTLE_DEKU_SCRUB_CENTER_RIGHT; + break; + case 0x07: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_GANONS_CASTLE_DEKU_SCRUB_RIGHT; + scrubIdentity.randomizerCheck = RC_GANONS_CASTLE_DEKU_SCRUB_RIGHT; + break; + case 0x08: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_GANONS_CASTLE_DEKU_SCRUB_LEFT; + scrubIdentity.randomizerCheck = RC_GANONS_CASTLE_DEKU_SCRUB_LEFT; + break; + } + break; + case SCENE_KAKUSIANA: // Grotto + switch (respawnData) { + case 0xE6: // Hyrule Field Scrub Grotto + switch (actorParams) { + case 0x02: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_HF_DEKU_SCRUB_GROTTO; + scrubIdentity.randomizerCheck = RC_HF_DEKU_SCRUB_GROTTO; + scrubIdentity.isShuffled = true; + break; + } + break; + case 0xEB: // ZR Scrub Grotto + switch (actorParams) { + case 0x07: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_ZR_DEKU_SCRUB_GROTTO_REAR; + scrubIdentity.randomizerCheck = RC_ZR_DEKU_SCRUB_GROTTO_REAR; + break; + case 0x08: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_ZR_DEKU_SCRUB_GROTTO_FRONT; + scrubIdentity.randomizerCheck = RC_ZR_DEKU_SCRUB_GROTTO_FRONT; + break; + } + break; + case 0xEE: // Sacred Forest Meadow Scrub Grotto + switch (actorParams) { + case 0x07: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_SFM_DEKU_SCRUB_GROTTO_REAR; + scrubIdentity.randomizerCheck = RC_SFM_DEKU_SCRUB_GROTTO_REAR; + break; + case 0x08: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_SFM_DEKU_SCRUB_GROTTO_FRONT; + scrubIdentity.randomizerCheck = RC_SFM_DEKU_SCRUB_GROTTO_FRONT; + break; + } + break; + case 0xEF: // Lake Hylia Scrub Grotto + switch (actorParams) { + case 0x00: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LH_DEKU_SCRUB_GROTTO_LEFT; + scrubIdentity.randomizerCheck = RC_LH_DEKU_SCRUB_GROTTO_LEFT; + break; + case 0x05: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LH_DEKU_SCRUB_GROTTO_RIGHT; + scrubIdentity.randomizerCheck = RC_LH_DEKU_SCRUB_GROTTO_RIGHT; + break; + case 0x03: + case 0x06: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LH_DEKU_SCRUB_GROTTO_CENTER; + scrubIdentity.randomizerCheck = RC_LH_DEKU_SCRUB_GROTTO_CENTER; + break; + } + break; + case 0xF0: // Gerudo Valley Scrub Grotto + switch (actorParams) { + case 0x07: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_GV_DEKU_SCRUB_GROTTO_REAR; + scrubIdentity.randomizerCheck = RC_GV_DEKU_SCRUB_GROTTO_REAR; + break; + case 0x08: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_GV_DEKU_SCRUB_GROTTO_FRONT; + scrubIdentity.randomizerCheck = RC_GV_DEKU_SCRUB_GROTTO_FRONT; + break; + } + break; + case 0xF5: // Lost Woods Scrub Grotto + switch (actorParams) { + case 0x03: + case 0x06: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LW_DEKU_SCRUB_GROTTO_REAR; + scrubIdentity.randomizerCheck = RC_LW_DEKU_SCRUB_GROTTO_REAR; + break; + case 0x0A: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LW_DEKU_SCRUB_GROTTO_FRONT; + scrubIdentity.randomizerCheck = RC_LW_DEKU_SCRUB_GROTTO_FRONT; + scrubIdentity.isShuffled = true; + break; + } + break; + case 0xF9: // Death Mountain Crater Scrub Grotto + switch (actorParams) { + case 0x00: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_DMC_DEKU_SCRUB_GROTTO_LEFT; + scrubIdentity.randomizerCheck = RC_DMC_DEKU_SCRUB_GROTTO_LEFT; + break; + case 0x05: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_DMC_DEKU_SCRUB_GROTTO_RIGHT; + scrubIdentity.randomizerCheck = RC_DMC_DEKU_SCRUB_GROTTO_RIGHT; + break; + case 0x03: + case 0x06: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_DMC_DEKU_SCRUB_GROTTO_CENTER; + scrubIdentity.randomizerCheck = RC_DMC_DEKU_SCRUB_GROTTO_CENTER; + break; + } + break; + case 0xFB: // Gerudo City Scrub Grotto + switch (actorParams) { + case 0x00: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_GC_DEKU_SCRUB_GROTTO_LEFT; + scrubIdentity.randomizerCheck = RC_GC_DEKU_SCRUB_GROTTO_LEFT; + break; + case 0x05: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_GC_DEKU_SCRUB_GROTTO_RIGHT; + scrubIdentity.randomizerCheck = RC_GC_DEKU_SCRUB_GROTTO_RIGHT; + break; + case 0x03: + case 0x06: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_GC_DEKU_SCRUB_GROTTO_CENTER; + scrubIdentity.randomizerCheck = RC_GC_DEKU_SCRUB_GROTTO_CENTER; + break; + } + break; + case 0xFC: // Lon Lon Ranch Scrub Grotto + switch (actorParams) { + case 0x00: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LLR_DEKU_SCRUB_GROTTO_LEFT; + scrubIdentity.randomizerCheck = RC_LLR_DEKU_SCRUB_GROTTO_LEFT; + break; + case 0x05: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LLR_DEKU_SCRUB_GROTTO_RIGHT; + scrubIdentity.randomizerCheck = RC_LLR_DEKU_SCRUB_GROTTO_RIGHT; + break; + case 0x03: + case 0x06: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LLR_DEKU_SCRUB_GROTTO_CENTER; + scrubIdentity.randomizerCheck = RC_LLR_DEKU_SCRUB_GROTTO_CENTER; + break; + } + break; + case 0xFD: // Desert Colossus Scrub Grotto + switch (actorParams) { + case 0x07: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_COLOSSUS_DEKU_SCRUB_GROTTO_REAR; + scrubIdentity.randomizerCheck = RC_COLOSSUS_DEKU_SCRUB_GROTTO_REAR; + break; + case 0x08: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_COLOSSUS_DEKU_SCRUB_GROTTO_FRONT; + scrubIdentity.randomizerCheck = RC_COLOSSUS_DEKU_SCRUB_GROTTO_FRONT; + break; + } + break; + } + break; + case SCENE_SPOT10: // Lost woods + switch (actorParams) { + case 0x00: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT; + scrubIdentity.randomizerCheck = RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT; + break; + case 0x01: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT; + scrubIdentity.randomizerCheck = RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT; + break; + case 0x09: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_LW_DEKU_SCRUB_NEAR_BRIDGE; + scrubIdentity.randomizerCheck = RC_LW_DEKU_SCRUB_NEAR_BRIDGE; + scrubIdentity.isShuffled = true; + break; + } + break; + case SCENE_SPOT17: // Death Mountain Crater + switch (actorParams) { + case 0x05: + scrubIdentity.randomizerInf = RAND_INF_SCRUBS_PURCHASED_DMC_DEKU_SCRUB; + scrubIdentity.randomizerCheck = RC_DMC_DEKU_SCRUB; + break; + } + break; + } + + if (randomizerMerchantPrices.find(scrubIdentity.randomizerCheck) != randomizerMerchantPrices.end()) { + scrubIdentity.itemPrice = randomizerMerchantPrices[scrubIdentity.randomizerCheck]; + } + + return scrubIdentity; +} + u8 Randomizer::GetRandoSettingValue(RandomizerSettingKey randoSettingKey) { return this->randoSettings[randoSettingKey]; } @@ -2189,10 +2650,6 @@ RandomizerCheck Randomizer::GetCheckFromActor(s16 sceneNum, s16 actorId, s16 act break; case 62: switch (actorParams) { - case 2: - return RC_HF_DEKU_SCRUB_GROTTO; - case 10: - return RC_LW_DEKU_SCRUB_GROTTO_FRONT; case 22988: return RC_KF_STORMS_GROTTO_CHEST; case -22988: @@ -2457,8 +2914,6 @@ RandomizerCheck Randomizer::GetCheckFromActor(s16 sceneNum, s16 actorId, s16 act break; case 91: switch (actorParams) { - case 9: - return RC_LW_DEKU_SCRUB_NEAR_BRIDGE; case 14365: return RC_LW_GOSSIP_STONE; case 27905: @@ -2623,8 +3078,10 @@ void GenerateRandomizerImgui() { cvarSettings[RSK_SHUFFLE_DUNGEON_REWARDS] = CVar_GetS32("gRandomizeShuffleDungeonReward", 0); cvarSettings[RSK_SHUFFLE_SONGS] = CVar_GetS32("gRandomizeShuffleSongs", 0); cvarSettings[RSK_SHUFFLE_TOKENS] = CVar_GetS32("gRandomizeShuffleTokens", 0); + cvarSettings[RSK_SHUFFLE_SCRUBS] = CVar_GetS32("gRandomizeShuffleScrubs", 0); cvarSettings[RSK_SHUFFLE_COWS] = CVar_GetS32("gRandomizeShuffleCows", 0); cvarSettings[RSK_SHUFFLE_ADULT_TRADE] = CVar_GetS32("gRandomizeShuffleAdultTrade", 0); + cvarSettings[RSK_SHUFFLE_MAGIC_BEANS] = CVar_GetS32("gRandomizeShuffleBeans", 0); cvarSettings[RSK_BOMBCHUS_IN_LOGIC] = CVar_GetS32("gRandomizeBombchusInLogic", 0); cvarSettings[RSK_SKIP_CHILD_ZELDA] = CVar_GetS32("gRandomizeSkipChildZelda", 0); @@ -2706,7 +3163,7 @@ void DrawRandoEditor(bool& open) { const char* randoGerudoFortress[3] = { "Normal", "Fast", "Open" }; const char* randoRainbowBridge[7] = { "Vanilla", "Always open", "Stones", "Medallions", "Dungeon rewards", "Dungeons", "Tokens" }; - const char* randoGanonsTrial[2] = { "Off", "On" }; + const char* randoGanonsTrial[3] = { "Skip", "Set Number", "Random Number" }; // World Settings const char* randoStartingAge[3] = { "Child", "Adult", "Random" }; @@ -3080,21 +3537,18 @@ void DrawRandoEditor(bool& open) { PaddedSeparator(); // Random Ganon's Trials - /* - ImGui::Text("Random Ganon's Trials"); - InsertHelpHoverText("Sets a random number or required trials to enter\nGanon's Tower."); - SohImGui::EnhancementCombobox("gRandomizeGanonTrial", randoGanonsTrial, 2, 0); - if (CVar_GetS32("gRandomizeGanonTrial", 0) == 0) { - ImGui::PopItemWidth(); + ImGui::Text("Ganon's Trials"); + InsertHelpHoverText("Sets the number of Ganon's Trials required to dispel the barrier\n\n" + "Skip - No Trials are required and the barrier is already dispelled.\n\n" + "Set Number - Select a number of trials that will be required from the" + "slider below. Which specific trials you need to complete will be random.\n\n" + "Random Number - A Random number and set of trials will be required."); + SohImGui::EnhancementCombobox("gRandomizeGanonTrial", randoGanonsTrial, 3, 0); + if (CVar_GetS32("gRandomizeGanonTrial", 0) == 1) { SohImGui::EnhancementSliderInt("Ganon's Trial Count: %d", "##RandoTrialCount", - "gRandomizeGanonTrialCount", 0, 6, "", 6); + "gRandomizeGanonTrialCount", 1, 6, "", 6); InsertHelpHoverText("Set the number of trials required to enter Ganon's Tower."); - RANDTODO: Switch back to slider when pre-completing some of Ganon's Trials is properly implemnted. } - */ - SohImGui::EnhancementCheckbox("Skip Ganon's Trials", "gRandomizeGanonTrialCount"); - InsertHelpHoverText( - "Sets whether or not Ganon's Castle Trials are required to enter Ganon's Tower."); } // COLUMN 2 - Shuffle Settings @@ -3120,6 +3574,20 @@ void DrawRandoEditor(bool& open) { SohImGui::EnhancementCombobox("gRandomizeShuffleSongs", randoShuffleSongs, 3, 0); PaddedSeparator(); + // Shuffle Scrubs + ImGui::Text(Settings::Scrubsanity.GetName().c_str()); + InsertHelpHoverText( + "Off - Scrubs will not be shuffled. The 3 Scrubs that give one-time items in the vanilla game (PoH, Deku Nut capacity, and Deku Stick capacity) will have random items.\n" + "\n" + "Affordable - Scrubs will be shuffled and their item will cost 10 rupees.\n" + "\n" + "Expensive - Scrubs will be shuffled and their item will cost the vanilla price.\n" + "\n" + "Random - Scrubs will be shuffled and their item will cost will be between 0-95 rupees.\n" + ); + SohImGui::EnhancementCombobox("gRandomizeShuffleScrubs", randoShuffleScrubs, 4, 0); + PaddedSeparator(); + // Shuffle Tokens ImGui::Text(Settings::Tokensanity.GetName().c_str()); InsertHelpHoverText("Shuffles Golden Skulltula Tokens into the item pool. This means " @@ -3140,10 +3608,10 @@ void DrawRandoEditor(bool& open) { "expected to be collected after getting Sun's Song."); PaddedSeparator(); + // Shuffle Cows SohImGui::EnhancementCheckbox(Settings::ShuffleCows.GetName().c_str(), "gRandomizeShuffleCows"); - InsertHelpHoverText( - "Cows give a randomized item from the pool upon performing Epona's Song in front of them."); + InsertHelpHoverText("Cows give a randomized item from the pool upon performing Epona's Song in front of them."); PaddedSeparator(); // Shuffle Adult Trade Quest @@ -3159,6 +3627,11 @@ void DrawRandoEditor(bool& open) { "If disabled, only the Claim Check will be found in the pool."); PaddedSeparator(); + SohImGui::EnhancementCheckbox(Settings::ShuffleMagicBeans.GetName().c_str(), "gRandomizeShuffleBeans"); + InsertHelpHoverText("Enabling this adds a pack of 10 beans to the item pool and changes the Magic Bean Salesman to sell a" + "random item at a price of 60 rupees."); + PaddedSeparator(); + if (CVar_GetS32("gRandomizeStartingKokiriSword", 0) == 0) { // Shuffle Kokiri Sword SohImGui::EnhancementCheckbox(Settings::ShuffleKokiriSword.GetName().c_str(), @@ -3745,12 +4218,20 @@ void CreateGetItemMessages(std::vector messageEntries) { } } -void CreateScrubMessages() { +// Currently these are generated at runtime, one for each price between 0-95. We're soon going to migrate this +// to being generated at save load, with only messages specific to each scrub. +void CreateMerchantMessages() { CustomMessageManager* customMessageManager = CustomMessageManager::Instance; - customMessageManager->AddCustomMessageTable(Randomizer::scrubMessageTableID); - const std::vector prices = { 10, 40 }; - for (u8 price : prices) { - customMessageManager->CreateMessage(Randomizer::scrubMessageTableID, price, + customMessageManager->AddCustomMessageTable(Randomizer::merchantMessageTableID); + customMessageManager->CreateMessage(Randomizer::merchantMessageTableID, 0, + { TEXTBOX_TYPE_BLACK, TEXTBOX_POS_BOTTOM, + "\x12\x38\x82\All right! You win! In return for&sparing me, I will give you a&%gmysterious item%w!&Please, take it!\x07\x10\xA3", + "\x12\x38\x82\In Ordnung! Du gewinnst! Im Austausch&dafür, dass du mich verschont hast,&werde ich dir einen %gmysteriösen&Gegenstand%w geben! Bitte nimm ihn!\x07\x10\xA3", + "\x12\x38\x82\D'accord! Vous avez gagné! En échange&de m'épargner, je vous donnerai un &%gobjet mystérieux%w! S'il vous plaît,&prenez-le!\x07\x10\xA3", + }); + + for (u32 price = 5; price <= 95; price += 5) { + customMessageManager->CreateMessage(Randomizer::merchantMessageTableID, price, { TEXTBOX_TYPE_BLACK, TEXTBOX_POS_BOTTOM, "\x12\x38\x82\All right! You win! In return for&sparing me, I will sell you a&%gmysterious item%w!&%r" + std::to_string(price) + " Rupees%w it is!\x07\x10\xA3", @@ -3761,6 +4242,18 @@ void CreateScrubMessages() { std::to_string(price) + " Rubis%w!\x07\x10\xA3" }); } + customMessageManager->CreateMessage( + Randomizer::merchantMessageTableID, TEXT_BEAN_SALESMAN, + { + TEXTBOX_TYPE_BLACK, + TEXTBOX_POS_BOTTOM, + "I tried to be a %rmagic bean%w salesman,&but it turns out my marketing skills&weren't worth " + "beans!^Anyway, want to buy my&%gmysterious item%w for 60 Rupees?\x1B&%gYes&No%w", + "Möchten Sie einen geheimnisvollen&Gegenstand für 60 Rubine?\x1B&%gJa&Nein%w", + "J'ai essayé d'être un vendeur de&%rharicots magiques%w, mais j'étais&mauvais au niveau du marketing et&ça " + "me courait sur le haricot...^Enfin bref, ça te dirait de m'acheter un&" + "%gobjet mystérieux%w pour 60 Rubis?\x1B&%gOui&Non%w", + }); } void CreateRupeeMessages() { @@ -3905,75 +4398,122 @@ void Randomizer::CreateCustomMessages() { const std::vector getItemMessages = { GIMESSAGE(RG_ICE_TRAP, ITEM_NONE, "\x08\x06\x30You are a %bFOWL%w!\x0E\x20", "\x08\x06\x15 Du bist ein %bDUMMKOPF%w!\x0E\x20", "\x08\x06\x50%bIDIOT%w\x0E\x20"), - GIMESSAGE_NO_GERMAN(RG_BOTTLE_WITH_BLUE_FIRE, ITEM_BLUE_FIRE, - "You got a %rBottle with Blue &Fire%w! Use it to melt Red Ice!", - "Vous obtenez une %rBouteille avec&une Flamme Bleue%w! Utilisez-la&pour faire fondre la %rGlace&Rouge%w!"), + GIMESSAGE_NO_GERMAN( + RG_BOTTLE_WITH_BLUE_FIRE, ITEM_BLUE_FIRE, "You got a %rBottle with Blue &Fire%w! Use it to melt Red Ice!", + "Vous obtenez une %rBouteille avec&une Flamme Bleue%w! Utilisez-la&pour faire fondre la %rGlace&Rouge%w!"), GIMESSAGE_NO_GERMAN(RG_BOTTLE_WITH_BIG_POE, ITEM_BIG_POE, - "You got a %rBig Poe in a Bottle%w!&Sell it to the Ghost Shop!", - "Vous obtenez une %rBouteille avec&une Âme%w! Vendez-la au Marchand&d'Âme"), + "You got a %rBig Poe in a Bottle%w!&Sell it to the Ghost Shop!", + "Vous obtenez une %rBouteille avec&une Âme%w! Vendez-la au Marchand&d'Âme"), GIMESSAGE_NO_GERMAN(RG_BOTTLE_WITH_BLUE_POTION, ITEM_POTION_BLUE, - "You got a %rBottle of Blue Potion%w!&Drink it to replenish your&%ghealth%w and %bmagic%w!", - "Vous obtenez une %rBouteille avec&une Potion Bleue%w! Buvez-la pour&restaurer votre %rénergie vitale%w&ainsi que votre %gmagie%w!"), - GIMESSAGE_NO_GERMAN(RG_BOTTLE_WITH_FISH, ITEM_FISH, - "You got a %rFish in a Bottle%w!&It looks fresh and delicious!&They say Jabu-Jabu loves them!", - "Vous obtenez une %rBouteille avec&un Poisson%w! Il a l'air délicieux!&Il paraîtrait que %bJabu-Jabu %wen&serait friand!"), - GIMESSAGE_NO_GERMAN(RG_BOTTLE_WITH_BUGS, ITEM_BUG, - "You got a %rBug in a Bottle%w!&They love to burrow in&dirt holes!", - "Vous obtenez une %rBouteille avec&des Insectes%w! Ils adorent creuser&dans la terre meuble!"), - GIMESSAGE_NO_GERMAN(RG_BOTTLE_WITH_FAIRY, ITEM_FAIRY, - "You got a %rFairy in a Bottle%w!&Use it wisely!", - "Vous obtenez une %rBouteille avec&une Fée%w! Faites-en bon usage!"), - GIMESSAGE_NO_GERMAN(RG_BOTTLE_WITH_RED_POTION, ITEM_POTION_RED, - "You got a %rBottle of Red Potion%w!&Drink it to replenish your&%ghealth%w!", - "Vous obtenez une %rBouteille avec&une Potion Rouge%w! Buvez-la pour&restaurer votre %rénergie vitale%w!"), - GIMESSAGE_NO_GERMAN(RG_BOTTLE_WITH_GREEN_POTION, ITEM_POTION_GREEN, - "You got a %rBottle of Green Potion%w!&Drink it to replenish your&%bmagic%w!", - "Vous obtenez une %rBouteille avec&une Potion Verte%w! Buvez-la pour&restaurer votre %gmagie%w!"), - GIMESSAGE_NO_GERMAN(RG_BOTTLE_WITH_POE, ITEM_POE, - "You got a %rPoe in a Bottle%w!&That creepy Ghost Shop might&be interested in this...", - "Vous obtenez une %rBouteille avec&un Esprit%w! Ça intéresserait&peut-être le vendeur d'Âme "), + "You got a %rBottle of Blue Potion%w!&Drink it to replenish your&%ghealth%w and %bmagic%w!", + "Vous obtenez une %rBouteille avec&une Potion Bleue%w! Buvez-la pour&restaurer votre " + "%rénergie vitale%w&ainsi que votre %gmagie%w!"), + GIMESSAGE_NO_GERMAN( + RG_BOTTLE_WITH_FISH, ITEM_FISH, + "You got a %rFish in a Bottle%w!&It looks fresh and delicious!&They say Jabu-Jabu loves them!", + "Vous obtenez une %rBouteille avec&un Poisson%w! Il a l'air délicieux!&Il paraîtrait que %bJabu-Jabu " + "%wen&serait friand!"), + GIMESSAGE_NO_GERMAN( + RG_BOTTLE_WITH_BUGS, ITEM_BUG, "You got a %rBug in a Bottle%w!&They love to burrow in&dirt holes!", + "Vous obtenez une %rBouteille avec&des Insectes%w! Ils adorent creuser&dans la terre meuble!"), + GIMESSAGE_NO_GERMAN(RG_BOTTLE_WITH_FAIRY, ITEM_FAIRY, "You got a %rFairy in a Bottle%w!&Use it wisely!", + "Vous obtenez une %rBouteille avec&une Fée%w! Faites-en bon usage!"), + GIMESSAGE_NO_GERMAN( + RG_BOTTLE_WITH_RED_POTION, ITEM_POTION_RED, + "You got a %rBottle of Red Potion%w!&Drink it to replenish your&%ghealth%w!", + "Vous obtenez une %rBouteille avec&une Potion Rouge%w! Buvez-la pour&restaurer votre %rénergie vitale%w!"), + GIMESSAGE_NO_GERMAN( + RG_BOTTLE_WITH_GREEN_POTION, ITEM_POTION_GREEN, + "You got a %rBottle of Green Potion%w!&Drink it to replenish your&%bmagic%w!", + "Vous obtenez une %rBouteille avec&une Potion Verte%w! Buvez-la pour&restaurer votre %gmagie%w!"), + GIMESSAGE_NO_GERMAN( + RG_BOTTLE_WITH_POE, ITEM_POE, + "You got a %rPoe in a Bottle%w!&That creepy Ghost Shop might&be interested in this...", + "Vous obtenez une %rBouteille avec&un Esprit%w! Ça intéresserait&peut-être le vendeur d'Âme "), - GIMESSAGE_NO_GERMAN(RG_GERUDO_FORTRESS_SMALL_KEY, ITEM_KEY_SMALL, "You found a %yThieves Hideout &%wSmall Key!", "Vous obtenez une %rPetite Clé %w&du %yRepaire des Voleurs%w!"), - GIMESSAGE_NO_GERMAN(RG_FOREST_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %gForest Temple &%wSmall Key!", "Vous obtenez une %rPetite Clé %w&du %gTemple de la Forêt%w!"), - GIMESSAGE_NO_GERMAN(RG_FIRE_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %rFire Temple &%wSmall Key!", "Vous obtenez une %rPetite Clé %w&du %rTemple du Feu%w!"), - GIMESSAGE_NO_GERMAN(RG_WATER_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %bWater Temple &%wSmall Key!", "Vous obtenez une %rPetite Clé %w&du %bTemple de l'Eau%w!"), - GIMESSAGE_NO_GERMAN(RG_SPIRIT_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %ySpirit Temple &%wSmall Key!", "Vous obtenez une %rPetite Clé %w&du %yTemple de l'Esprit%w!"), - GIMESSAGE_NO_GERMAN(RG_SHADOW_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %pShadow Temple &%wSmall Key!", "Vous obtenez une %rPetite Clé %w&du %pTemple de l'Ombre%w!"), - GIMESSAGE_NO_GERMAN(RG_BOTTOM_OF_THE_WELL_SMALL_KEY, ITEM_KEY_SMALL, "You found a %pBottom of the &Well %wSmall Key!", "Vous obtenez une %rPetite Clé %w&du %Puits%w!"), - GIMESSAGE_NO_GERMAN(RG_GERUDO_TRAINING_GROUNDS_SMALL_KEY, ITEM_KEY_SMALL, "You found a %yGerudo Training &Grounds %wSmall Key!", "Vous obtenez une %rPetite Clé %w&du %yGymnase Gerudo%w!"), - GIMESSAGE_NO_GERMAN(RG_GANONS_CASTLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %rGanon's Castle &%wSmall Key!", "Vous obtenez une %rPetite Clé %w&du %Château de Ganon%w!"), + GIMESSAGE_NO_GERMAN(RG_GERUDO_FORTRESS_SMALL_KEY, ITEM_KEY_SMALL, "You found a %yThieves Hideout &%wSmall Key!", + "Vous obtenez une %rPetite Clé %w&du %yRepaire des Voleurs%w!"), + GIMESSAGE_NO_GERMAN(RG_FOREST_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %gForest Temple &%wSmall Key!", + "Vous obtenez une %rPetite Clé %w&du %gTemple de la Forêt%w!"), + GIMESSAGE_NO_GERMAN(RG_FIRE_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %rFire Temple &%wSmall Key!", + "Vous obtenez une %rPetite Clé %w&du %rTemple du Feu%w!"), + GIMESSAGE_NO_GERMAN(RG_WATER_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %bWater Temple &%wSmall Key!", + "Vous obtenez une %rPetite Clé %w&du %bTemple de l'Eau%w!"), + GIMESSAGE_NO_GERMAN(RG_SPIRIT_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %ySpirit Temple &%wSmall Key!", + "Vous obtenez une %rPetite Clé %w&du %yTemple de l'Esprit%w!"), + GIMESSAGE_NO_GERMAN(RG_SHADOW_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %pShadow Temple &%wSmall Key!", + "Vous obtenez une %rPetite Clé %w&du %pTemple de l'Ombre%w!"), + GIMESSAGE_NO_GERMAN(RG_BOTTOM_OF_THE_WELL_SMALL_KEY, ITEM_KEY_SMALL, + "You found a %pBottom of the &Well %wSmall Key!", + "Vous obtenez une %rPetite Clé %w&du %Puits%w!"), + GIMESSAGE_NO_GERMAN(RG_GERUDO_TRAINING_GROUNDS_SMALL_KEY, ITEM_KEY_SMALL, + "You found a %yGerudo Training &Grounds %wSmall Key!", + "Vous obtenez une %rPetite Clé %w&du %yGymnase Gerudo%w!"), + GIMESSAGE_NO_GERMAN(RG_GANONS_CASTLE_SMALL_KEY, ITEM_KEY_SMALL, "You found a %rGanon's Castle &%wSmall Key!", + "Vous obtenez une %rPetite Clé %w&du %Château de Ganon%w!"), - GIMESSAGE_NO_GERMAN(RG_FOREST_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %gForest Temple &%wBoss Key!", "Vous obtenez la %rClé d'or %wdu&%gTemple de la Forêt%w!"), - GIMESSAGE_NO_GERMAN(RG_FIRE_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %rFire Temple &%wBoss Key!", "Vous obtenez la %rClé d'or %wdu&%rTemple du Feu%w!"), - GIMESSAGE_NO_GERMAN(RG_WATER_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %bWater Temple &%wBoss Key!", "Vous obtenez la %rClé d'or %wdu&%bTemple de l'Eau%w!"), - GIMESSAGE_NO_GERMAN(RG_SPIRIT_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %ySpirit Temple &%wBoss Key!", "Vous obtenez la %rClé d'or %wdu&%yTemple de l'Esprit%w!"), - GIMESSAGE_NO_GERMAN(RG_SHADOW_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %pShadow Temple &%wBoss Key!", "Vous obtenez la %rClé d'or %wdu&%pTemple de l'Ombre%w!"), - GIMESSAGE_NO_GERMAN(RG_GANONS_CASTLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %rGanon's Castle &%wBoss Key!", "Vous obtenez la %rClé d'or %wdu&%rChâteau de Ganon%w!"), - - GIMESSAGE_NO_GERMAN(RG_DEKU_TREE_MAP, ITEM_DUNGEON_MAP, "You found the %gDeku Tree &%wMap!", "Vous obtenez la %rCarte %wde&l'%gArbre Mojo%w!"), - GIMESSAGE_NO_GERMAN(RG_DODONGOS_CAVERN_MAP, ITEM_DUNGEON_MAP, "You found the %rDodongo's Cavern &%wMap!", "Vous obtenez la %rCarte %wde la&%rCaverne Dodongo%w!"), - GIMESSAGE_NO_GERMAN(RG_JABU_JABUS_BELLY_MAP, ITEM_DUNGEON_MAP, "You found the %bJabu Jabu's Belly &%wMap!", "Vous obtenez la %rCarte %wdu &%bVentre de Jabu-Jabu%w!"), - GIMESSAGE_NO_GERMAN(RG_FOREST_TEMPLE_MAP, ITEM_DUNGEON_MAP, "You found the %gForest Temple &%wMap!", "Vous obtenez la %rCarte %wdu &%gTemple de la Forêt%w!"), - GIMESSAGE_NO_GERMAN(RG_FIRE_TEMPLE_MAP, ITEM_DUNGEON_MAP, "You found the %rFire Temple &%wMap!", "Vous obtenez la %rCarte %wdu &%rTemple du Feu%w!"), - GIMESSAGE_NO_GERMAN(RG_WATER_TEMPLE_MAP, ITEM_DUNGEON_MAP, "You found the %bWater Temple &%wMap!", "Vous obtenez la %rCarte %wdu &%bTemple de l'Eau%w!"), - GIMESSAGE_NO_GERMAN(RG_SPIRIT_TEMPLE_MAP, ITEM_DUNGEON_MAP, "You found the %ySpirit Temple &%wMap!", "Vous obtenez la %rCarte %wdu &%yTemple de l'Esprit%w!"), - GIMESSAGE_NO_GERMAN(RG_SHADOW_TEMPLE_MAP, ITEM_DUNGEON_MAP, "You found the %pShadow Temple &%wMap!", "Vous obtenez la %rCarte %wdu &%pTemple de l'Ombre%w!"), - GIMESSAGE_NO_GERMAN(RG_BOTTOM_OF_THE_WELL_MAP, ITEM_DUNGEON_MAP, "You found the %pBottom of the &Well %wMap!", "Vous obtenez la %rCarte %wdu &%pPuits%w!"), - GIMESSAGE_NO_GERMAN(RG_ICE_CAVERN_MAP, ITEM_DUNGEON_MAP, "You found the %cIce Cavern &%wMap!", "Vous obtenez la %rCarte %wde &la %cCaverne Polaire%w!"), + GIMESSAGE_NO_GERMAN(RG_FOREST_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %gForest Temple &%wBoss Key!", + "Vous obtenez la %rClé d'or %wdu&%gTemple de la Forêt%w!"), + GIMESSAGE_NO_GERMAN(RG_FIRE_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %rFire Temple &%wBoss Key!", + "Vous obtenez la %rClé d'or %wdu&%rTemple du Feu%w!"), + GIMESSAGE_NO_GERMAN(RG_WATER_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %bWater Temple &%wBoss Key!", + "Vous obtenez la %rClé d'or %wdu&%bTemple de l'Eau%w!"), + GIMESSAGE_NO_GERMAN(RG_SPIRIT_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %ySpirit Temple &%wBoss Key!", + "Vous obtenez la %rClé d'or %wdu&%yTemple de l'Esprit%w!"), + GIMESSAGE_NO_GERMAN(RG_SHADOW_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %pShadow Temple &%wBoss Key!", + "Vous obtenez la %rClé d'or %wdu&%pTemple de l'Ombre%w!"), + GIMESSAGE_NO_GERMAN(RG_GANONS_CASTLE_BOSS_KEY, ITEM_KEY_BOSS, "You found the %rGanon's Castle &%wBoss Key!", + "Vous obtenez la %rClé d'or %wdu&%rChâteau de Ganon%w!"), - GIMESSAGE_NO_GERMAN(RG_DEKU_TREE_COMPASS, ITEM_COMPASS, "You found the %gDeku Tree &%wCompass!", "Vous obtenez la %rBoussole %wde&l'%gArbre Mojo%w!"), - GIMESSAGE_NO_GERMAN(RG_DODONGOS_CAVERN_COMPASS, ITEM_COMPASS, "You found the %rDodongo's Cavern &%wCompass!", "Vous obtenez la %rBoussole %wde la&%rCaverne Dodongo%w!"), - GIMESSAGE_NO_GERMAN(RG_JABU_JABUS_BELLY_COMPASS, ITEM_COMPASS, "You found the %bJabu Jabu's Belly &%wCompass!", "Vous obtenez la %rBoussole %wdu &%bVentre de Jabu-Jabu%w!"), - GIMESSAGE_NO_GERMAN(RG_FOREST_TEMPLE_COMPASS, ITEM_COMPASS, "You found the %gForest Temple &%wCompass!", "Vous obtenez la %rBoussole %wdu &%gTemple de la Forêt%w!"), - GIMESSAGE_NO_GERMAN(RG_FIRE_TEMPLE_COMPASS, ITEM_COMPASS, "You found the %rFire Temple &%wCompass!", "Vous obtenez la %rBoussole %wdu &%rTemple du Feu%w!"), - GIMESSAGE_NO_GERMAN(RG_WATER_TEMPLE_COMPASS, ITEM_COMPASS, "You found the %bWater Temple &%wCompass!", "Vous obtenez la %rBoussole %wdu &%bTemple de l'Eau%w!"), - GIMESSAGE_NO_GERMAN(RG_SPIRIT_TEMPLE_COMPASS, ITEM_COMPASS, "You found the %ySpirit Temple &%wCompass!", "Vous obtenez la %rBoussole %wdu &%yTemple de l'Esprit%w!"), - GIMESSAGE_NO_GERMAN(RG_SHADOW_TEMPLE_COMPASS, ITEM_COMPASS, "You found the %pShadow Temple &%wCompass!", "Vous obtenez la %rBoussole %wdu &%pTemple de l'Ombre%w!"), - GIMESSAGE_NO_GERMAN(RG_BOTTOM_OF_THE_WELL_COMPASS, ITEM_COMPASS, "You found the %pBottom of the &Well %wCompass!", "Vous obtenez la %rBoussole %wdu &%pPuits%w!"), - GIMESSAGE_NO_GERMAN(RG_ICE_CAVERN_COMPASS, ITEM_COMPASS, "You found the %cIce Cavern &%wCompass!", "Vous obtenez la %rBoussole %wde &la %cCaverne Polaire%w!"), + GIMESSAGE_NO_GERMAN(RG_DEKU_TREE_MAP, ITEM_DUNGEON_MAP, "You found the %gDeku Tree &%wMap!", + "Vous obtenez la %rCarte %wde&l'%gArbre Mojo%w!"), + GIMESSAGE_NO_GERMAN(RG_DODONGOS_CAVERN_MAP, ITEM_DUNGEON_MAP, "You found the %rDodongo's Cavern &%wMap!", + "Vous obtenez la %rCarte %wde la&%rCaverne Dodongo%w!"), + GIMESSAGE_NO_GERMAN(RG_JABU_JABUS_BELLY_MAP, ITEM_DUNGEON_MAP, "You found the %bJabu Jabu's Belly &%wMap!", + "Vous obtenez la %rCarte %wdu &%bVentre de Jabu-Jabu%w!"), + GIMESSAGE_NO_GERMAN(RG_FOREST_TEMPLE_MAP, ITEM_DUNGEON_MAP, "You found the %gForest Temple &%wMap!", + "Vous obtenez la %rCarte %wdu &%gTemple de la Forêt%w!"), + GIMESSAGE_NO_GERMAN(RG_FIRE_TEMPLE_MAP, ITEM_DUNGEON_MAP, "You found the %rFire Temple &%wMap!", + "Vous obtenez la %rCarte %wdu &%rTemple du Feu%w!"), + GIMESSAGE_NO_GERMAN(RG_WATER_TEMPLE_MAP, ITEM_DUNGEON_MAP, "You found the %bWater Temple &%wMap!", + "Vous obtenez la %rCarte %wdu &%bTemple de l'Eau%w!"), + GIMESSAGE_NO_GERMAN(RG_SPIRIT_TEMPLE_MAP, ITEM_DUNGEON_MAP, "You found the %ySpirit Temple &%wMap!", + "Vous obtenez la %rCarte %wdu &%yTemple de l'Esprit%w!"), + GIMESSAGE_NO_GERMAN(RG_SHADOW_TEMPLE_MAP, ITEM_DUNGEON_MAP, "You found the %pShadow Temple &%wMap!", + "Vous obtenez la %rCarte %wdu &%pTemple de l'Ombre%w!"), + GIMESSAGE_NO_GERMAN(RG_BOTTOM_OF_THE_WELL_MAP, ITEM_DUNGEON_MAP, "You found the %pBottom of the &Well %wMap!", + "Vous obtenez la %rCarte %wdu &%pPuits%w!"), + GIMESSAGE_NO_GERMAN(RG_ICE_CAVERN_MAP, ITEM_DUNGEON_MAP, "You found the %cIce Cavern &%wMap!", + "Vous obtenez la %rCarte %wde &la %cCaverne Polaire%w!"), + + GIMESSAGE_NO_GERMAN(RG_DEKU_TREE_COMPASS, ITEM_COMPASS, "You found the %gDeku Tree &%wCompass!", + "Vous obtenez la %rBoussole %wde&l'%gArbre Mojo%w!"), + GIMESSAGE_NO_GERMAN(RG_DODONGOS_CAVERN_COMPASS, ITEM_COMPASS, "You found the %rDodongo's Cavern &%wCompass!", + "Vous obtenez la %rBoussole %wde la&%rCaverne Dodongo%w!"), + GIMESSAGE_NO_GERMAN(RG_JABU_JABUS_BELLY_COMPASS, ITEM_COMPASS, "You found the %bJabu Jabu's Belly &%wCompass!", + "Vous obtenez la %rBoussole %wdu &%bVentre de Jabu-Jabu%w!"), + GIMESSAGE_NO_GERMAN(RG_FOREST_TEMPLE_COMPASS, ITEM_COMPASS, "You found the %gForest Temple &%wCompass!", + "Vous obtenez la %rBoussole %wdu &%gTemple de la Forêt%w!"), + GIMESSAGE_NO_GERMAN(RG_FIRE_TEMPLE_COMPASS, ITEM_COMPASS, "You found the %rFire Temple &%wCompass!", + "Vous obtenez la %rBoussole %wdu &%rTemple du Feu%w!"), + GIMESSAGE_NO_GERMAN(RG_WATER_TEMPLE_COMPASS, ITEM_COMPASS, "You found the %bWater Temple &%wCompass!", + "Vous obtenez la %rBoussole %wdu &%bTemple de l'Eau%w!"), + GIMESSAGE_NO_GERMAN(RG_SPIRIT_TEMPLE_COMPASS, ITEM_COMPASS, "You found the %ySpirit Temple &%wCompass!", + "Vous obtenez la %rBoussole %wdu &%yTemple de l'Esprit%w!"), + GIMESSAGE_NO_GERMAN(RG_SHADOW_TEMPLE_COMPASS, ITEM_COMPASS, "You found the %pShadow Temple &%wCompass!", + "Vous obtenez la %rBoussole %wdu &%pTemple de l'Ombre%w!"), + GIMESSAGE_NO_GERMAN(RG_BOTTOM_OF_THE_WELL_COMPASS, ITEM_COMPASS, + "You found the %pBottom of the &Well %wCompass!", + "Vous obtenez la %rBoussole %wdu &%pPuits%w!"), + GIMESSAGE_NO_GERMAN(RG_ICE_CAVERN_COMPASS, ITEM_COMPASS, "You found the %cIce Cavern &%wCompass!", + "Vous obtenez la %rBoussole %wde &la %cCaverne Polaire%w!"), + GIMESSAGE(RG_MAGIC_BEAN_PACK, ITEM_BEAN, + "You got a %rPack of Magic Beans%w!&Find a suitable spot for a garden&and plant them. Then, wait for&something fun to happen!", + "Du hast eine %rPackung&Magic Beans%w! Finde&einen geeigneten Platz fur einen&Garten und pflanze sie. Dann^warte auf etwas Lustiges passiert!", + "Vous avez un %rPack de&haricots magiques%w ! Trouvez&un endroit convenable pour un&jardin et plantez-les.^Ensuite, attendez quelque&chose d'amusant doit arriver !") }; CreateGetItemMessages(getItemMessages); - CreateScrubMessages(); + CreateMerchantMessages(); CreateRupeeMessages(); CreateNaviRandoMessages(); } @@ -4070,6 +4610,7 @@ void InitRandoItemTable() { GET_ITEM(RG_SHADOW_TEMPLE_COMPASS, OBJECT_GI_COMPASS, GID_COMPASS, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, MOD_RANDOMIZER, RG_SHADOW_TEMPLE_COMPASS), GET_ITEM(RG_BOTTOM_OF_THE_WELL_COMPASS, OBJECT_GI_COMPASS, GID_COMPASS, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, MOD_RANDOMIZER, RG_BOTTOM_OF_THE_WELL_COMPASS), GET_ITEM(RG_ICE_CAVERN_COMPASS, OBJECT_GI_COMPASS, GID_COMPASS, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, MOD_RANDOMIZER, RG_ICE_CAVERN_COMPASS), + GET_ITEM(RG_MAGIC_BEAN_PACK, OBJECT_GI_BEAN, GID_BEAN, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, MOD_RANDOMIZER, RG_MAGIC_BEAN_PACK) }; ItemTableManager::Instance->AddItemTable(MOD_RANDOMIZER); for (int i = 0; i < ARRAY_COUNT(extendedVanillaGetItemTable); i++) { diff --git a/soh/soh/Enhancements/randomizer/randomizer.h b/soh/soh/Enhancements/randomizer/randomizer.h index 54b50bcba8..2c87a5b036 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.h +++ b/soh/soh/Enhancements/randomizer/randomizer.h @@ -14,15 +14,18 @@ class Randomizer { private: std::unordered_map itemLocations; std::unordered_map hintLocations; + std::unordered_map trialsRequired; std::string childAltarText; std::string adultAltarText; std::string ganonHintText; std::string ganonText; std::unordered_map randoSettings; + std::unordered_map randomizerMerchantPrices; s16 GetItemFromGet(RandomizerGet randoGet, GetItemID ogItemId); s16 GetItemFromActor(s16 actorId, s16 actorParams, s16 sceneNum, GetItemID ogItemId); void ParseRandomizerSettingsFile(const char* spoilerFileName); void ParseHintLocationsFile(const char* spoilerFileName); + void ParseRequiredTrialsFile(const char* spoilerFileName); void ParseItemLocationsFile(const char* spoilerFileName, bool silent); bool IsItemVanilla(RandomizerGet randoGet); @@ -33,7 +36,7 @@ class Randomizer { static const std::string getItemMessageTableID; static const std::string hintMessageTableID; - static const std::string scrubMessageTableID; + static const std::string merchantMessageTableID; static const std::string rupeeMessageTableID; static const std::string NaviRandoMessageTableID; @@ -43,13 +46,16 @@ class Randomizer { bool SpoilerFileExists(const char* spoilerFileName); void LoadRandomizerSettings(const char* spoilerFileName); void LoadHintLocations(const char* spoilerFileName); - void LoadItemLocations(const char* spoilerFileName, bool silent); + void LoadRequiredTrials(const char* spoilerFileName); + void LoadItemLocations(const char* spoilerFileName,bool silent); + bool IsTrialRequired(RandomizerInf trial); u8 GetRandoSettingValue(RandomizerSettingKey randoSettingKey); RandomizerCheck GetCheckFromActor(s16 sceneNum, s16 actorId, s16 actorParams); std::string GetChildAltarText() const; std::string GetAdultAltarText() const; std::string GetGanonText() const; std::string GetGanonHintText() const; + ScrubIdentity IdentifyScrub(s32 sceneNum, s32 actorParams, s32 respawnData); s16 GetRandomizedItemIdFromKnownCheck(RandomizerCheck randomizerCheck, GetItemID ogId); s16 GetRandomizedItemId(GetItemID ogId, s16 actorId, s16 actorParams, s16 sceneNum); static void CreateCustomMessages(); diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 419e25ba21..d30e0cee48 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -1,6 +1,8 @@ #pragma once #include +#include "z64item.h" +#include "randomizer_inf.h" // This should probably go in a less rando-specific location // but the best location will probably be in the modding engine @@ -987,6 +989,7 @@ typedef enum { RSK_SHUFFLE_DUNGEON_REWARDS, RSK_SHUFFLE_SONGS, RSK_SHUFFLE_TOKENS, + RSK_SHUFFLE_SCRUBS, RSK_SHUFFLE_COWS, RSK_SHUFFLE_WEIRD_EGG, RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD, @@ -1014,5 +1017,14 @@ typedef enum { RSK_ENABLE_GLITCH_CUTSCENES, RSK_SKULLS_SUNS_SONG, RSK_SHUFFLE_ADULT_TRADE, + RSK_SHUFFLE_MAGIC_BEANS, RSK_BOMBCHUS_IN_LOGIC } RandomizerSettingKey; + +typedef struct ScrubIdentity { + RandomizerInf randomizerInf; + RandomizerCheck randomizerCheck; + GetItemID getItemId; + int32_t itemPrice; + bool isShuffled; +} ScrubIdentity; diff --git a/soh/soh/Enhancements/randomizer/randomizer_inf.h b/soh/soh/Enhancements/randomizer/randomizer_inf.h index e5f41a5d3d..c55e2d2ec0 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_inf.h +++ b/soh/soh/Enhancements/randomizer/randomizer_inf.h @@ -29,6 +29,43 @@ typedef enum { RAND_INF_COWS_MILKED_JABU_JABUS_BELLY_MQ_COW, RAND_INF_COWS_MILKED_HF_COW_GROTTO_GOSSIP_STONE, + RAND_INF_SCRUBS_PURCHASED_DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_LEFT, + RAND_INF_SCRUBS_PURCHASED_DODONGOS_CAVERN_DEKU_SCRUB_SIDE_ROOM_NEAR_DODONGOS, + RAND_INF_SCRUBS_PURCHASED_DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_RIGHT, + RAND_INF_SCRUBS_PURCHASED_DODONGOS_CAVERN_DEKU_SCRUB_LOBBY, + RAND_INF_SCRUBS_PURCHASED_JABU_JABUS_BELLY_DEKU_SCRUB, + RAND_INF_SCRUBS_PURCHASED_GANONS_CASTLE_DEKU_SCRUB_CENTER_LEFT, + RAND_INF_SCRUBS_PURCHASED_GANONS_CASTLE_DEKU_SCRUB_CENTER_RIGHT, + RAND_INF_SCRUBS_PURCHASED_GANONS_CASTLE_DEKU_SCRUB_RIGHT, + RAND_INF_SCRUBS_PURCHASED_GANONS_CASTLE_DEKU_SCRUB_LEFT, + RAND_INF_SCRUBS_PURCHASED_HF_DEKU_SCRUB_GROTTO, + RAND_INF_SCRUBS_PURCHASED_ZR_DEKU_SCRUB_GROTTO_REAR, + RAND_INF_SCRUBS_PURCHASED_ZR_DEKU_SCRUB_GROTTO_FRONT, + RAND_INF_SCRUBS_PURCHASED_SFM_DEKU_SCRUB_GROTTO_REAR, + RAND_INF_SCRUBS_PURCHASED_SFM_DEKU_SCRUB_GROTTO_FRONT, + RAND_INF_SCRUBS_PURCHASED_LH_DEKU_SCRUB_GROTTO_LEFT, + RAND_INF_SCRUBS_PURCHASED_LH_DEKU_SCRUB_GROTTO_RIGHT, + RAND_INF_SCRUBS_PURCHASED_LH_DEKU_SCRUB_GROTTO_CENTER, + RAND_INF_SCRUBS_PURCHASED_GV_DEKU_SCRUB_GROTTO_REAR, + RAND_INF_SCRUBS_PURCHASED_GV_DEKU_SCRUB_GROTTO_FRONT, + RAND_INF_SCRUBS_PURCHASED_LW_DEKU_SCRUB_GROTTO_REAR, + RAND_INF_SCRUBS_PURCHASED_LW_DEKU_SCRUB_GROTTO_FRONT, + RAND_INF_SCRUBS_PURCHASED_DMC_DEKU_SCRUB_GROTTO_LEFT, + RAND_INF_SCRUBS_PURCHASED_DMC_DEKU_SCRUB_GROTTO_RIGHT, + RAND_INF_SCRUBS_PURCHASED_DMC_DEKU_SCRUB_GROTTO_CENTER, + RAND_INF_SCRUBS_PURCHASED_GC_DEKU_SCRUB_GROTTO_LEFT, + RAND_INF_SCRUBS_PURCHASED_GC_DEKU_SCRUB_GROTTO_RIGHT, + RAND_INF_SCRUBS_PURCHASED_GC_DEKU_SCRUB_GROTTO_CENTER, + RAND_INF_SCRUBS_PURCHASED_LLR_DEKU_SCRUB_GROTTO_LEFT, + RAND_INF_SCRUBS_PURCHASED_LLR_DEKU_SCRUB_GROTTO_RIGHT, + RAND_INF_SCRUBS_PURCHASED_LLR_DEKU_SCRUB_GROTTO_CENTER, + RAND_INF_SCRUBS_PURCHASED_COLOSSUS_DEKU_SCRUB_GROTTO_REAR, + RAND_INF_SCRUBS_PURCHASED_COLOSSUS_DEKU_SCRUB_GROTTO_FRONT, + RAND_INF_SCRUBS_PURCHASED_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT, + RAND_INF_SCRUBS_PURCHASED_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT, + RAND_INF_SCRUBS_PURCHASED_LW_DEKU_SCRUB_NEAR_BRIDGE, + RAND_INF_SCRUBS_PURCHASED_DMC_DEKU_SCRUB, + // If you add anything to this list, you need to update the size of randomizerInf in z64save.h to be ceil(RAND_INF_MAX / 16) RAND_INF_MAX, diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 5ae7e3f5df..efb0e44649 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1562,10 +1562,18 @@ extern "C" void Randomizer_LoadHintLocations(const char* spoilerFileName) { OTRGlobals::Instance->gRandomizer->LoadHintLocations(spoilerFileName); } +extern "C" void Randomizer_LoadRequiredTrials(const char* spoilerFileName) { + OTRGlobals::Instance->gRandomizer->LoadRequiredTrials(spoilerFileName); +} + extern "C" void Randomizer_LoadItemLocations(const char* spoilerFileName, bool silent) { OTRGlobals::Instance->gRandomizer->LoadItemLocations(spoilerFileName, silent); } +extern "C" bool Randomizer_IsTrialRequired(RandomizerInf trial) { + return OTRGlobals::Instance->gRandomizer->IsTrialRequired(trial); +} + extern "C" bool SpoilerFileExists(const char* spoilerFileName) { return OTRGlobals::Instance->gRandomizer->SpoilerFileExists(spoilerFileName); } @@ -1578,18 +1586,12 @@ extern "C" RandomizerCheck Randomizer_GetCheckFromActor(s16 sceneNum, s16 actorI return OTRGlobals::Instance->gRandomizer->GetCheckFromActor(sceneNum, actorId, actorParams); } -extern "C" CustomMessageEntry Randomizer_GetScrubMessage(u16 scrubTextId) { - int price = 0; - switch (scrubTextId) { - case TEXT_SCRUB_POH: - price = 10; - break; - case TEXT_SCRUB_STICK_UPGRADE: - case TEXT_SCRUB_NUT_UPGRADE: - price = 40; - break; - } - return CustomMessageManager::Instance->RetrieveMessage(Randomizer::scrubMessageTableID, price); +extern "C" ScrubIdentity Randomizer_IdentifyScrub(s32 sceneNum, s32 actorParams, s32 respawnData) { + return OTRGlobals::Instance->gRandomizer->IdentifyScrub(sceneNum, actorParams, respawnData); +} + +extern "C" CustomMessageEntry Randomizer_GetScrubMessage(s16 itemPrice) { + return CustomMessageManager::Instance->RetrieveMessage(Randomizer::merchantMessageTableID, itemPrice); } extern "C" CustomMessageEntry Randomizer_GetNaviMessage() { @@ -1650,15 +1652,14 @@ extern "C" GetItemEntry Randomizer_GetItemFromKnownCheck(RandomizerCheck randomi return ItemTable_RetrieveEntry(getItemModIndex, itemID); } -extern "C" bool Randomizer_ObtainedFreestandingIceTrap(RandomizerCheck randomizerCheck, GetItemID ogId, Actor* actor) { - return gSaveContext.n64ddFlag && (actor->parent != NULL) && - Randomizer_GetItemFromKnownCheck(randomizerCheck, ogId).getItemId == RG_ICE_TRAP; -} - extern "C" bool Randomizer_ItemIsIceTrap(RandomizerCheck randomizerCheck, GetItemID ogId) { return gSaveContext.n64ddFlag && Randomizer_GetItemFromKnownCheck(randomizerCheck, ogId).getItemId == RG_ICE_TRAP; } +extern "C" bool Randomizer_ObtainedFreestandingIceTrap(RandomizerCheck randomizerCheck, GetItemID ogId, Actor* actor) { + return Randomizer_ItemIsIceTrap(randomizerCheck, ogId) && actor->parent != NULL; +} + extern "C" CustomMessageEntry Randomizer_GetCustomGetItemMessage(Player* player) { s16 giid; if (player->getItemEntry.objectId != OBJECT_INVALID) { @@ -1718,8 +1719,8 @@ extern "C" int CustomMessage_RetrieveIfExists(GlobalContext* globalCtx) { } else { messageEntry = Randomizer_GetGanonHintText(); } - } else if (textId == TEXT_SCRUB_POH || textId == TEXT_SCRUB_STICK_UPGRADE || textId == TEXT_SCRUB_NUT_UPGRADE) { - messageEntry = Randomizer_GetScrubMessage(textId); + } else if (textId >= 0x9000 && textId <= 0x905F) { + messageEntry = Randomizer_GetScrubMessage((textId & ((1 << 8) - 1))); } else if (CVar_GetS32("gRandomizeRupeeNames", 0) && (textId == TEXT_BLUE_RUPEE || textId == TEXT_RED_RUPEE || textId == TEXT_PURPLE_RUPEE || textId == TEXT_HUGE_RUPEE)) { @@ -1727,6 +1728,8 @@ extern "C" int CustomMessage_RetrieveIfExists(GlobalContext* globalCtx) { // In rando, replace Navi's general overworld hints with rando-related gameplay tips } else if (CVar_GetS32("gRandoRelevantNavi", 1) && textId >= 0x0140 && textId <= 0x015F) { messageEntry = Randomizer_GetNaviMessage(); + } else if (Randomizer_GetSettingValue(RSK_SHUFFLE_MAGIC_BEANS) && textId == TEXT_BEAN_SALESMAN) { + messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::merchantMessageTableID, TEXT_BEAN_SALESMAN); } else if (Randomizer_GetSettingValue(RSK_BOMBCHUS_IN_LOGIC) && (textId == TEXT_BUY_BOMBCHU_10_DESC || textId == TEXT_BUY_BOMBCHU_10_PROMPT)) { messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, textId); diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index dc304358db..f81afdaeac 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -96,8 +96,11 @@ Sprite* GetSeedTexture(uint8_t index); void Randomizer_LoadSettings(const char* spoilerFileName); u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey); RandomizerCheck Randomizer_GetCheckFromActor(s16 actorId, s16 actorParams, s16 sceneNum); +ScrubIdentity Randomizer_IdentifyScrub(s32 sceneNum, s32 actorParams, s32 respawnData); void Randomizer_LoadHintLocations(const char* spoilerFileName); +void Randomizer_LoadRequiredTrials(const char* spoilerFileName); void Randomizer_LoadItemLocations(const char* spoilerFileName, bool silent); +bool Randomizer_IsTrialRequired(RandomizerInf trial); GetItemEntry Randomizer_GetRandomizedItem(GetItemID ogId, s16 actorId, s16 actorParams, s16 sceneNum); GetItemEntry Randomizer_GetItemFromKnownCheck(RandomizerCheck randomizerCheck, GetItemID ogId); bool Randomizer_ObtainedFreestandingIceTrap(RandomizerCheck randomizerCheck, GetItemID ogId, Actor* actor); diff --git a/soh/src/code/z_en_item00.c b/soh/src/code/z_en_item00.c index 89e1c9df7f..95375ea484 100644 --- a/soh/src/code/z_en_item00.c +++ b/soh/src/code/z_en_item00.c @@ -1290,6 +1290,7 @@ void EnItem00_CustomItemsParticles(Actor* Parent, GlobalContext* globalCtx, GetI switch (giEntry.itemId) { case RG_MAGIC_SINGLE: case RG_MAGIC_DOUBLE: + case RG_MAGIC_BEAN_PACK: color_slot = 0; break; case RG_DOUBLE_DEFENSE: diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 9d95bb0bc9..8d26f36586 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1642,12 +1642,16 @@ u8 Item_Give(GlobalContext* globalCtx, u8 item) { } else if ((item >= ITEM_SWORD_KOKIRI) && (item <= ITEM_SWORD_BGS)) { gSaveContext.inventory.equipment |= gBitFlags[item - ITEM_SWORD_KOKIRI] << gEquipShifts[EQUIP_SWORD]; + // Both Giant's Knife and Biggoron Sword have the same Item ID, so this part handles both of them if (item == ITEM_SWORD_BGS) { gSaveContext.swordHealth = 8; - if (ALL_EQUIP_VALUE(EQUIP_SWORD) == 0xF - ||(gSaveContext.n64ddFlag && ALL_EQUIP_VALUE(EQUIP_SWORD) == 0xE)) { // In rando, when buying Giant's Knife, also check - gSaveContext.inventory.equipment ^= 8 << gEquipShifts[EQUIP_SWORD]; // for 0xE in case we don't have Kokiri Sword + // In rando, when buying Giant's Knife, also check + // for 0xE in case we don't have Kokiri Sword + if (ALL_EQUIP_VALUE(EQUIP_SWORD) == 0xF || (gSaveContext.n64ddFlag && ALL_EQUIP_VALUE(EQUIP_SWORD) == 0xE)) { + + gSaveContext.inventory.equipment ^= 8 << gEquipShifts[EQUIP_SWORD]; + if (gSaveContext.equips.buttonItems[0] == ITEM_SWORD_KNIFE) { gSaveContext.equips.buttonItems[0] = ITEM_SWORD_BGS; Interface_LoadItemIcon1(globalCtx, 0); @@ -1686,6 +1690,28 @@ u8 Item_Give(GlobalContext* globalCtx, u8 item) { } return ITEM_NONE; } else if (item == ITEM_KEY_SMALL) { + // Small key exceptions for rando with keysanity off. + if (gSaveContext.n64ddFlag) { + if (globalCtx->sceneNum == 10) { // ganon's tower -> ganon's castle + if (gSaveContext.inventory.dungeonKeys[13] < 0) { + gSaveContext.inventory.dungeonKeys[13] = 1; + return ITEM_NONE; + } else { + gSaveContext.inventory.dungeonKeys[13]++; + return ITEM_NONE; + } + } + + if (globalCtx->sceneNum == 92) { // Desert Colossus -> Spirit Temple. + if (gSaveContext.inventory.dungeonKeys[6] < 0) { + gSaveContext.inventory.dungeonKeys[6] = 1; + return ITEM_NONE; + } else { + gSaveContext.inventory.dungeonKeys[6]++; + return ITEM_NONE; + } + } + } if (gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex] < 0) { gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex] = 1; return ITEM_NONE; @@ -2177,6 +2203,14 @@ u16 Randomizer_Item_Give(GlobalContext* globalCtx, GetItemEntry giEntry) { return RG_NONE; } + if (item == RG_MAGIC_BEAN_PACK) { + if (INV_CONTENT(ITEM_BEAN) == ITEM_NONE) { + INV_CONTENT(ITEM_BEAN) = ITEM_BEAN; + AMMO(ITEM_BEAN) = 10; + } + return RG_NONE; + } + if (item == RG_DOUBLE_DEFENSE) { gSaveContext.doubleDefense = true; gSaveContext.inventory.defenseHearts = 20; @@ -2223,9 +2257,9 @@ u16 Randomizer_Item_Give(GlobalContext* globalCtx, GetItemEntry giEntry) { } } } else if ((item >= RG_FOREST_TEMPLE_SMALL_KEY && item <= RG_GANONS_CASTLE_SMALL_KEY) || - (item >= RG_FOREST_TEMPLE_BOSS_KEY && item <= RG_GANONS_CASTLE_BOSS_KEY) || - (item >= RG_DEKU_TREE_MAP && item <= RG_ICE_CAVERN_MAP) || - (item >= RG_DEKU_TREE_COMPASS && item <= RG_ICE_CAVERN_COMPASS)) { + (item >= RG_FOREST_TEMPLE_BOSS_KEY && item <= RG_GANONS_CASTLE_BOSS_KEY) || + (item >= RG_DEKU_TREE_MAP && item <= RG_ICE_CAVERN_MAP) || + (item >= RG_DEKU_TREE_COMPASS && item <= RG_ICE_CAVERN_COMPASS)) { int mapIndex = gSaveContext.mapIndex; switch (item) { case RG_DEKU_TREE_MAP: @@ -2323,7 +2357,6 @@ u16 Randomizer_Item_Give(GlobalContext* globalCtx, GetItemEntry giEntry) { return temp; } - u8 Item_CheckObtainability(u8 item) { s16 i; s16 slot = SLOT(item); diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 0da3685a61..b1dd2bc7c6 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -255,10 +255,10 @@ void GivePlayerRandoRewardSariaGift(GlobalContext* globalCtx, RandomizerCheck ch if (gSaveContext.entranceIndex == 0x05E0) { GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(check, RG_ZELDAS_LULLABY); - if ((!Flags_GetEventChkInf(0xC1) || (player->getItemId == getItemEntry.getItemId && getItemEntry.getItemId != GI_ICE_TRAP)) && - player != NULL && !Player_InBlockingCsMode(globalCtx, player)) { + if (!Flags_GetEventChkInf(0xC1) && player != NULL && !Player_InBlockingCsMode(globalCtx, player)) { GiveItemEntryWithoutActor(globalCtx, getItemEntry); - Flags_SetEventChkInf(0xC1); + player->pendingFlag.flagType = FLAG_EVENT_CHECK_INF; + player->pendingFlag.flagID = 0xC1; } } } diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index b7c260a233..c8befa586a 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -4,10 +4,11 @@ #include #include #include +#include "soh/Enhancements/randomizer/adult_trade_shuffle.h" #define NUM_DUNGEONS 8 -#define NUM_TRIALS 6 #define NUM_COWS 10 +#define NUM_SCRUBS 35 /** * Initialize new save. @@ -308,6 +309,11 @@ void GiveLinkDekuNutUpgrade(GetItemID giid) { } } +void GiveLinkSkullToken() { + gSaveContext.inventory.questItems |= gBitFlags[QUEST_SKULL_TOKEN]; + gSaveContext.inventory.gsTokens++; +} + void GiveLinkMagic(GetItemID giid) { if (giid == RG_MAGIC_SINGLE) { gSaveContext.magicLevel = 1; @@ -507,6 +513,53 @@ void GiveLinkDungeonItem(GetItemID getItemId) { } } +void GiveLinkAdultTradeItem(GetItemID giid) { + ItemID item; + switch (giid) { + case GI_POCKET_EGG: + item = ITEM_POCKET_EGG; + break; + case GI_POCKET_CUCCO: + item = ITEM_POCKET_CUCCO; + break; + case GI_COJIRO: + item = ITEM_COJIRO; + break; + case GI_ODD_MUSHROOM: + item = ITEM_ODD_MUSHROOM; + break; + case GI_ODD_POTION: + item = ITEM_ODD_POTION; + break; + case GI_SAW: + item = ITEM_SAW; + break; + case GI_SWORD_BROKEN: + item = ITEM_SWORD_BROKEN; + break; + case GI_PRESCRIPTION: + item = ITEM_PRESCRIPTION; + break; + case GI_FROG: + item = ITEM_FROG; + break; + case GI_EYEDROPS: + item = ITEM_EYEDROPS; + break; + case GI_CLAIM_CHECK: + item = ITEM_CLAIM_CHECK; + break; + } + if ((item == ITEM_SAW) && CVar_GetS32("gDekuNutUpgradeFix", 0) == 0) { + gSaveContext.itemGetInf[1] |= 0x8000; + } + + if (item >= ITEM_POCKET_EGG) { + gSaveContext.adultTradeItems |= ADULT_TRADE_FLAG(item); + } + INV_CONTENT(ITEM_TRADE_ADULT) = item; +} + void GiveLinksPocketMedallion() { GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(RC_LINKS_POCKET, RG_NONE); @@ -708,6 +761,15 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { gSaveContext.randomizerInf[i] = 0; } + // Set all trials to cleared if trial count is random or anything other than 6 + if (Randomizer_GetSettingValue(RSK_RANDOM_TRIALS) || (Randomizer_GetSettingValue(RSK_TRIAL_COUNT) != 6)) { + for (u16 i = RAND_INF_TRIALS_DONE_LIGHT_TRIAL; i <= RAND_INF_TRIALS_DONE_SHADOW_TRIAL; i++) { + if (!Randomizer_IsTrialRequired(i)) { + Flags_SetRandomizerInf(i); + } + } + } + // Set Cutscene flags to skip them gSaveContext.eventChkInf[0xC] |= 0x10; // returned to tot with medallions gSaveContext.eventChkInf[0xC] |= 0x20; //sheik at tot pedestal @@ -734,6 +796,11 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { gSaveContext.eventChkInf[3] |= 0x800; gSaveContext.eventChkInf[12] |= 1; + // shuffle adult trade quest + if (Randomizer_GetSettingValue(RSK_SHUFFLE_ADULT_TRADE)) { + gSaveContext.adultTradeItems = 0; + } + // Give Link's pocket item GiveLinksPocketMedallion(); @@ -791,7 +858,7 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { s32 giid = getItem.getItemId; if (getItem.modIndex == MOD_NONE) { - if(getItem.itemId >= ITEM_KOKIRI_EMERALD && getItem.itemId <= ITEM_MEDALLION_LIGHT) { + if (getItem.itemId >= ITEM_MEDALLION_FOREST && getItem.itemId <= ITEM_ZORA_SAPPHIRE) { GiveLinkDungeonReward(getItem.getItemId); } else if (giid == GI_RUPEE_GREEN || giid == GI_RUPEE_BLUE || giid == GI_RUPEE_RED || giid == GI_RUPEE_PURPLE || giid == GI_RUPEE_GOLD) { @@ -848,6 +915,10 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { GiveLinkDekuStickUpgrade(giid); } else if (giid == GI_NUT_UPGRADE_30 || giid == GI_NUT_UPGRADE_40) { GiveLinkDekuNutUpgrade(giid); + } else if (giid == GI_SKULL_TOKEN) { + GiveLinkSkullToken(); + } else if (giid >= GI_POCKET_EGG && giid <= GI_CLAIM_CHECK || giid == GI_COJIRO) { + GiveLinkAdultTradeItem(giid); } else { s32 iid = getItem.itemId; if (iid != -1) INV_CONTENT(iid) = iid; @@ -964,11 +1035,6 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { } } - // shuffle adult trade quest - if (Randomizer_GetSettingValue(RSK_SHUFFLE_ADULT_TRADE)) { - gSaveContext.adultTradeItems = 0; - } - // complete mask quest if (Randomizer_GetSettingValue(RSK_COMPLETE_MASK_QUEST)) { gSaveContext.itemGetInf[3] |= 0x100; // Sold Keaton Mask diff --git a/soh/src/overlays/actors/ovl_Demo_Kekkai/z_demo_kekkai.c b/soh/src/overlays/actors/ovl_Demo_Kekkai/z_demo_kekkai.c index adc534a2d4..6d0b51db23 100644 --- a/soh/src/overlays/actors/ovl_Demo_Kekkai/z_demo_kekkai.c +++ b/soh/src/overlays/actors/ovl_Demo_Kekkai/z_demo_kekkai.c @@ -64,12 +64,33 @@ static u8 sEnergyColors[] = { /* Forest prim */ 255, 255, 170, /* env */ 0, 200, 0, }; +// Translates from the barrier's actor params to their corresponding randInf flags. +RandomizerInf trialParamToRandInf(u16 params) { + switch (params) { + case KEKKAI_LIGHT: + return RAND_INF_TRIALS_DONE_LIGHT_TRIAL; + case KEKKAI_FOREST: + return RAND_INF_TRIALS_DONE_FOREST_TRIAL; + case KEKKAI_FIRE: + return RAND_INF_TRIALS_DONE_FIRE_TRIAL; + case KEKKAI_WATER: + return RAND_INF_TRIALS_DONE_WATER_TRIAL; + case KEKKAI_SPIRIT: + return RAND_INF_TRIALS_DONE_SPIRIT_TRIAL; + case KEKKAI_SHADOW: + return RAND_INF_TRIALS_DONE_SHADOW_TRIAL; + } +} + s32 DemoKekkai_CheckEventFlag(s32 params) { static s32 eventFlags[] = { 0xC3, 0xBC, 0xBF, 0xBE, 0xBD, 0xAD, 0xBB }; if ((params < KEKKAI_TOWER) || (params > KEKKAI_FOREST)) { return true; } + if (gSaveContext.n64ddFlag) { + return Flags_GetRandomizerInf(trialParamToRandInf(params)); + } return Flags_GetEventChkInf(eventFlags[params]); } @@ -128,8 +149,7 @@ void DemoKekkai_Init(Actor* thisx, GlobalContext* globalCtx) { this->collider2.dim.yShift = 300; if (gSaveContext.n64ddFlag) { - int trialsToComplete = Randomizer_GetSettingValue(RSK_TRIAL_COUNT); - if (trialsToComplete <= TrialsDoneCount()) { + if (TrialsDoneCount() == NUM_TRIALS) { Actor_Kill(thisx); return; } @@ -141,6 +161,10 @@ void DemoKekkai_Init(Actor* thisx, GlobalContext* globalCtx) { case KEKKAI_SHADOW: case KEKKAI_SPIRIT: case KEKKAI_FOREST: + if (gSaveContext.n64ddFlag && Flags_GetRandomizerInf(trialParamToRandInf(thisx->params))) { + Actor_Kill(thisx); + return; + } this->energyAlpha = 1.0f; this->orbScale = 1.0f; Actor_SetScale(thisx, 0.1f); @@ -247,27 +271,10 @@ void DemoKekkai_TrialBarrierDispel(Actor* thisx, GlobalContext* globalCtx) { DemoKekkai* this = (DemoKekkai*)thisx; if (gSaveContext.n64ddFlag) { - switch (thisx->params) { - case KEKKAI_WATER: - Flags_SetRandomizerInf(RAND_INF_TRIALS_DONE_WATER_TRIAL); - break; - case KEKKAI_LIGHT: - Flags_SetRandomizerInf(RAND_INF_TRIALS_DONE_LIGHT_TRIAL); - break; - case KEKKAI_FIRE: - Flags_SetRandomizerInf(RAND_INF_TRIALS_DONE_FIRE_TRIAL); - break; - case KEKKAI_SHADOW: - Flags_SetRandomizerInf(RAND_INF_TRIALS_DONE_SHADOW_TRIAL); - break; - case KEKKAI_SPIRIT: - Flags_SetRandomizerInf(RAND_INF_TRIALS_DONE_SPIRIT_TRIAL); - break; - case KEKKAI_FOREST: - Flags_SetRandomizerInf(RAND_INF_TRIALS_DONE_FOREST_TRIAL); - break; - } - Flags_SetEventChkInf(eventFlags[thisx->params]); + Flags_SetRandomizerInf(trialParamToRandInf(thisx->params)); + // May or may not be needed. Not sure if needed for anything + // that randoInf isn't already covering. Leaving it for safety. + Flags_SetEventChkInf(eventFlags[thisx->params]); } if (globalCtx->csCtx.frames == csFrames[this->actor.params]) { diff --git a/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c b/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c index d3a9243566..5b6f097d01 100644 --- a/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c +++ b/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c @@ -15,6 +15,7 @@ void EnDns_Destroy(Actor* thisx, GlobalContext* globalCtx); void EnDns_Update(Actor* thisx, GlobalContext* globalCtx); void EnDns_Draw(Actor* thisx, GlobalContext* globalCtx); +u32 EnDns_RandomizerPurchaseableCheck(EnDns* this); u32 func_809EF5A4(EnDns* this); u32 func_809EF658(EnDns* this); u32 func_809EF70C(EnDns* this); @@ -24,6 +25,7 @@ u32 func_809EF854(EnDns* this); u32 func_809EF8F4(EnDns* this); u32 func_809EF9A4(EnDns* this); +void EnDns_RandomizerPurchase(EnDns* this); void func_809EF9F8(EnDns* this); void func_809EFA28(EnDns* this); void func_809EFA58(EnDns* this); @@ -155,7 +157,6 @@ void EnDns_Init(Actor* thisx, GlobalContext* globalCtx) { Collider_InitCylinder(globalCtx, &this->collider); Collider_SetCylinderType1(globalCtx, &this->collider, &this->actor, &sCylinderInit); ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 35.0f); - this->actor.textId = D_809F040C[this->actor.params]; Actor_SetScale(&this->actor, 0.01f); this->actor.colChkInfo.mass = MASS_IMMOVABLE; this->maintainCollider = 1; @@ -164,7 +165,27 @@ void EnDns_Init(Actor* thisx, GlobalContext* globalCtx) { this->actor.speedXZ = 0.0f; this->actor.velocity.y = 0.0f; this->actor.gravity = -1.0f; + this->actor.textId = D_809F040C[this->actor.params]; this->dnsItemEntry = sItemEntries[this->actor.params]; + if (gSaveContext.n64ddFlag) { + // Ugly, but the best way we can identify which grotto we are in, same method 3DRando uses, but we'll need to account for entrance rando + s16 respawnData = gSaveContext.respawn[RESPAWN_MODE_RETURN].data & ((1 << 8) - 1); + this->scrubIdentity = Randomizer_IdentifyScrub(globalCtx->sceneNum, this->actor.params, respawnData); + + if (Randomizer_GetSettingValue(RSK_SHUFFLE_SCRUBS) == 1 || Randomizer_GetSettingValue(RSK_SHUFFLE_SCRUBS) == 3 && this->scrubIdentity.itemPrice != -1) { + this->dnsItemEntry->itemPrice = this->scrubIdentity.itemPrice; + } + + if (this->scrubIdentity.isShuffled) { + this->dnsItemEntry->getItemId = this->scrubIdentity.getItemId; + this->dnsItemEntry->purchaseableCheck = EnDns_RandomizerPurchaseableCheck; + this->dnsItemEntry->setRupeesAndFlags = EnDns_RandomizerPurchase; + this->dnsItemEntry->itemAmount = 1; + // Currently the textID is simply identified by the item price since that is the only thing + // unique to it, later on this will change to identifying by scrubIdentity.randomizerInf + this->actor.textId = 0x9000 + this->dnsItemEntry->itemPrice; + } + } this->actionFunc = EnDns_SetupWait; } @@ -185,6 +206,13 @@ void EnDns_ChangeAnim(EnDns* this, u8 index) { /* Item give checking functions */ +u32 EnDns_RandomizerPurchaseableCheck(EnDns* this) { + if (gSaveContext.rupees < this->dnsItemEntry->itemPrice || Flags_GetRandomizerInf(this->scrubIdentity.randomizerInf)) { + return 0; + } + return 4; +} + u32 func_809EF5A4(EnDns* this) { if ((CUR_CAPACITY(UPG_NUTS) != 0) && (AMMO(ITEM_NUT) >= CUR_CAPACITY(UPG_NUTS))) { return 1; @@ -281,6 +309,10 @@ u32 func_809EF9A4(EnDns* this) { } /* Paying and flagging functions */ +void EnDns_RandomizerPurchase(EnDns* this) { + Rupees_ChangeBy(-this->dnsItemEntry->itemPrice); + Flags_SetRandomizerInf(this->scrubIdentity.randomizerInf); +} void func_809EF9F8(EnDns* this) { Rupees_ChangeBy(-this->dnsItemEntry->itemPrice); @@ -369,31 +401,25 @@ void EnDns_Talk(EnDns* this, GlobalContext* globalCtx) { } void func_809EFDD0(EnDns* this, GlobalContext* globalCtx) { - if (this->actor.params == 0x9) { - if (gSaveContext.n64ddFlag) { - GetItemEntry getItemEntry = Randomizer_GetRandomizedItem(GI_STICK_UPGRADE_30, this->actor.id, this->actor.params, globalCtx->sceneNum); - GiveItemEntryFromActor(&this->actor, globalCtx, getItemEntry, 130.0f, 100.0f); - } else if (CUR_UPG_VALUE(UPG_STICKS) < 2) { - func_8002F434(&this->actor, globalCtx, GI_STICK_UPGRADE_20, 130.0f, 100.0f); + if (!gSaveContext.n64ddFlag || !this->scrubIdentity.isShuffled) { + if (this->actor.params == 0x9) { + if (CUR_UPG_VALUE(UPG_STICKS) < 2) { + func_8002F434(&this->actor, globalCtx, GI_STICK_UPGRADE_20, 130.0f, 100.0f); + } else { + func_8002F434(&this->actor, globalCtx, GI_STICK_UPGRADE_30, 130.0f, 100.0f); + } + } else if (this->actor.params == 0xA) { + if (CUR_UPG_VALUE(UPG_NUTS) < 2) { + func_8002F434(&this->actor, globalCtx, GI_NUT_UPGRADE_30, 130.0f, 100.0f); + } else { + func_8002F434(&this->actor, globalCtx, GI_NUT_UPGRADE_40, 130.0f, 100.0f); + } } else { - func_8002F434(&this->actor, globalCtx, GI_STICK_UPGRADE_30, 130.0f, 100.0f); - } - } else if (this->actor.params == 0xA) { - if (gSaveContext.n64ddFlag) { - GetItemEntry getItemEntry = Randomizer_GetRandomizedItem(GI_NUT_UPGRADE_40, this->actor.id, this->actor.params, globalCtx->sceneNum); - GiveItemEntryFromActor(&this->actor, globalCtx, getItemEntry, 130.0f, 100.0f); - } else if (CUR_UPG_VALUE(UPG_NUTS) < 2) { - func_8002F434(&this->actor, globalCtx, GI_NUT_UPGRADE_30, 130.0f, 100.0f); - } else { - func_8002F434(&this->actor, globalCtx, GI_NUT_UPGRADE_40, 130.0f, 100.0f); + func_8002F434(&this->actor, globalCtx, this->dnsItemEntry->getItemId, 130.0f, 100.0f); } } else { - if (!gSaveContext.n64ddFlag) { - func_8002F434(&this->actor, globalCtx, this->dnsItemEntry->getItemId, 130.0f, 100.0f); - } else { - GetItemEntry getItemEntry = Randomizer_GetRandomizedItem(this->dnsItemEntry->getItemId, this->actor.id, this->actor.params, globalCtx->sceneNum); - GiveItemEntryFromActor(&this->actor, globalCtx, getItemEntry, 130.0f, 100.0f); - } + GetItemEntry itemEntry = Randomizer_GetItemFromKnownCheck(this->scrubIdentity.randomizerCheck, this->scrubIdentity.getItemId); + GiveItemEntryFromActor(&this->actor, globalCtx, itemEntry, 130.0f, 100.0f); } } @@ -489,6 +515,11 @@ void EnDns_Update(Actor* thisx, GlobalContext* globalCtx) { this->dustTimer++; this->actor.textId = D_809F040C[this->actor.params]; + if (gSaveContext.n64ddFlag && this->scrubIdentity.isShuffled) { + // Currently the textID is simply identified by the item price since that is the only thing + // unique to it, later on this will change to identifying by scrubIdentity.randomizerInf + this->actor.textId = 0x9000 + this->dnsItemEntry->itemPrice; + } Actor_SetFocus(&this->actor, 60.0f); Actor_SetScale(&this->actor, 0.01f); SkelAnime_Update(&this->skelAnime); diff --git a/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.h b/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.h index 90bd3d0955..4dab2bf398 100644 --- a/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.h +++ b/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.h @@ -32,6 +32,7 @@ typedef struct EnDns { /* 0x02BD */ u8 dropCollectible; /* 0x02C0 */ DnsItemEntry* dnsItemEntry; /* 0x02C4 */ f32 yInitPos; + /* */ ScrubIdentity scrubIdentity; } EnDns; // size = 0x02C8 #endif diff --git a/soh/src/overlays/actors/ovl_En_Ma1/z_en_ma1.c b/soh/src/overlays/actors/ovl_En_Ma1/z_en_ma1.c index 88c5b07245..c2a19e6217 100644 --- a/soh/src/overlays/actors/ovl_En_Ma1/z_en_ma1.c +++ b/soh/src/overlays/actors/ovl_En_Ma1/z_en_ma1.c @@ -90,7 +90,16 @@ static void* sEyeTextures[] = { gMalonChildEyeClosedTex, }; +bool Randomizer_ObtainedMalonHCReward() { + return Flags_GetEventChkInf(0x12); +} + u16 EnMa1_GetText(GlobalContext* globalCtx, Actor* thisx) { + // Special case for Malon Hyrule Castle Text. Placing it here at the beginning + // has the added benefit of circumventing mask text if wearing bunny hood. + if (gSaveContext.n64ddFlag && globalCtx->sceneNum == SCENE_SPOT15) { + return Randomizer_ObtainedMalonHCReward() ? 0x2044 : 0x2043; + } u16 faceReaction = Text_GetFaceReaction(globalCtx, 0x17); if (faceReaction != 0) { @@ -191,25 +200,34 @@ s32 func_80AA08C4(EnMa1* this, GlobalContext* globalCtx) { if (!LINK_IS_CHILD) { return 0; } + // Causes Malon to appear in the market if you haven't met her yet. if (((globalCtx->sceneNum == SCENE_MARKET_NIGHT) || (globalCtx->sceneNum == SCENE_MARKET_DAY)) && !(gSaveContext.eventChkInf[1] & 0x10) && !(gSaveContext.infTable[8] & 0x800)) { return 1; } - if ((globalCtx->sceneNum == SCENE_SPOT15) && !(gSaveContext.eventChkInf[1] & 0x10)) { - if (gSaveContext.infTable[8] & 0x800) { - return 1; - } else { - gSaveContext.infTable[8] |= 0x800; - return 0; + if ((globalCtx->sceneNum == SCENE_SPOT15) && // if we're at hyrule castle + (!(gSaveContext.eventChkInf[1] & 0x10) || // and talon hasn't left + (gSaveContext.n64ddFlag && + !Randomizer_ObtainedMalonHCReward()))) { // or we're rando'd and haven't gotten malon's HC check + if (gSaveContext.infTable[8] & 0x800) { // if we've met malon + return 1; // make her appear at the castle + } else { // if we haven't met malon + gSaveContext.infTable[8] |= 0x800; // set the flag for meeting malon + return 0; // don't make her appear at the castle } } + // Malon asleep in her bed if Talon has left Hyrule Castle and it is nighttime. if ((globalCtx->sceneNum == SCENE_SOUKO) && IS_NIGHT && (gSaveContext.eventChkInf[1] & 0x10)) { return 1; } + // Don't spawn Malon if none of the above are true and we are not in Lon Lon Ranch. if (globalCtx->sceneNum != SCENE_SPOT20) { return 0; } - if ((this->actor.shape.rot.z == 3) && IS_DAY && (gSaveContext.eventChkInf[1] & 0x10)) { + // If we've gotten this far, we're in Lon Lon Ranch. Spawn Malon if it is daytime, Talon has left Hyrule Castle, and + // either we are not randomized, or we are and we have received Malon's item at Hyrule Castle. + if ((this->actor.shape.rot.z == 3) && IS_DAY && (gSaveContext.eventChkInf[1] & 0x10) && + ((gSaveContext.n64ddFlag && Randomizer_ObtainedMalonHCReward()) || !gSaveContext.n64ddFlag)) { return 1; } return 0; @@ -290,10 +308,16 @@ void EnMa1_Init(Actor* thisx, GlobalContext* globalCtx) { this->actor.targetMode = 6; this->unk_1E8.unk_00 = 0; - if (!(gSaveContext.eventChkInf[1] & 0x10) || (CHECK_QUEST_ITEM(QUEST_SONG_EPONA) && !gSaveContext.n64ddFlag) || + // To avoid missing a check, we want Malon to have the actionFunc for singing, but not reacting to Ocarina, if any of + // the following are true. + // 1. Talon has not left Hyrule Castle. + // 2. We are Randomized and have not obtained Malon's Weird Egg Check. + // 3. We are not Randomized and have obtained Epona's Song + if (!(gSaveContext.eventChkInf[1] & 0x10) || (gSaveContext.n64ddFlag && !Randomizer_ObtainedMalonHCReward()) || (CHECK_QUEST_ITEM(QUEST_SONG_EPONA) && !gSaveContext.n64ddFlag) || (gSaveContext.n64ddFlag && Flags_GetTreasure(globalCtx, 0x1F))) { this->actionFunc = func_80AA0D88; EnMa1_ChangeAnim(this, ENMA1_ANIM_2); + // If none of the above conditions were true, set Malon up to teach Epona's Song. } else { if (gSaveContext.n64ddFlag) { // Skip straight to "let's sing it together" textbox in the ranch gSaveContext.eventChkInf[1] |= 0x40; @@ -322,9 +346,16 @@ void func_80AA0D88(EnMa1* this, GlobalContext* globalCtx) { } } - if ((globalCtx->sceneNum == SCENE_SPOT15) && (gSaveContext.eventChkInf[1] & 0x10)) { + // We want to Kill Malon's Actor outside of randomizer when Talon is freed. In Randomizer we don't kill Malon's + // Actor here, otherwise if we wake up Talon first and then get her check she will spontaneously + // disappear. + if ((globalCtx->sceneNum == SCENE_SPOT15) && (!gSaveContext.n64ddFlag && gSaveContext.eventChkInf[1] & 0x10)) { Actor_Kill(&this->actor); - } else if (!(gSaveContext.eventChkInf[1] & 0x10) || (CHECK_QUEST_ITEM(QUEST_SONG_EPONA) && !gSaveContext.n64ddFlag)) { + // We want Malon to give the Weird Egg Check (see function below) in the following situations: + // 1. Talon as not left Hyrule Castle (Vanilla) OR + // 2. We haven't obtained Malon's Weird Egg Check (Randomizer only) OR + // 3. We have Epona's Song? (Vanilla only, not sure why it's here but I didn't write that one) + } else if ((!(gSaveContext.eventChkInf[1] & 0x10) || (gSaveContext.n64ddFlag && !Randomizer_ObtainedMalonHCReward())) || (CHECK_QUEST_ITEM(QUEST_SONG_EPONA) && !gSaveContext.n64ddFlag)) { if (this->unk_1E8.unk_00 == 2) { this->actionFunc = func_80AA0EA0; globalCtx->msgCtx.stateTimer = 4; diff --git a/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c b/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c index 04919761ef..9f67a21fc2 100644 --- a/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c +++ b/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c @@ -125,11 +125,19 @@ void EnMs_Talk(EnMs* this, GlobalContext* globalCtx) { } else if (Message_ShouldAdvance(globalCtx)) { switch (globalCtx->msgCtx.choiceIndex) { case 0: // yes - if (gSaveContext.rupees < sPrices[BEANS_BOUGHT]) { + if (gSaveContext.rupees < + ((gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_MAGIC_BEANS)) + ? 60 + : sPrices[BEANS_BOUGHT])) { Message_ContinueTextbox(globalCtx, 0x4069); // not enough rupees text return; } - func_8002F434(&this->actor, globalCtx, GI_BEAN, 90.0f, 10.0f); + if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_MAGIC_BEANS)) { + GiveItemEntryFromActor(&this->actor, globalCtx, + Randomizer_GetItemFromKnownCheck(RC_ZR_MAGIC_BEAN_SALESMAN, GI_BEAN), 90.0f, 10.0f); + } else { + func_8002F434(&this->actor, globalCtx, GI_BEAN, 90.0f, 10.0f); + } this->actionFunc = EnMs_Sell; return; case 1: // no @@ -142,11 +150,18 @@ void EnMs_Talk(EnMs* this, GlobalContext* globalCtx) { void EnMs_Sell(EnMs* this, GlobalContext* globalCtx) { if (Actor_HasParent(&this->actor, globalCtx)) { - Rupees_ChangeBy(-sPrices[BEANS_BOUGHT]); + Rupees_ChangeBy((gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_MAGIC_BEANS)) ? -60 : -sPrices[BEANS_BOUGHT]); this->actor.parent = NULL; - this->actionFunc = EnMs_TalkAfterPurchase; + this->actionFunc = + (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_MAGIC_BEANS)) ? EnMs_Wait : EnMs_TalkAfterPurchase; } else { - func_8002F434(&this->actor, globalCtx, GI_BEAN, 90.0f, 10.0f); + if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_MAGIC_BEANS)) { + GiveItemEntryFromActor(&this->actor, globalCtx, + Randomizer_GetItemFromKnownCheck(RC_ZR_MAGIC_BEAN_SALESMAN, GI_BEAN), 90.0f, 10.0f); + BEANS_BOUGHT = 10; + } else { + func_8002F434(&this->actor, globalCtx, GI_BEAN, 90.0f, 10.0f); + } } } diff --git a/soh/src/overlays/actors/ovl_En_Shopnuts/z_en_shopnuts.c b/soh/src/overlays/actors/ovl_En_Shopnuts/z_en_shopnuts.c index 2b1f6a12e7..70a4c8e10f 100644 --- a/soh/src/overlays/actors/ovl_En_Shopnuts/z_en_shopnuts.c +++ b/soh/src/overlays/actors/ovl_En_Shopnuts/z_en_shopnuts.c @@ -69,6 +69,15 @@ void EnShopnuts_Init(Actor* thisx, GlobalContext* globalCtx) { CollisionCheck_SetInfo(&this->actor.colChkInfo, NULL, &sColChkInfoInit); Collider_UpdateCylinder(&this->actor, &this->collider); + if (gSaveContext.n64ddFlag) { + s16 respawnData = gSaveContext.respawn[RESPAWN_MODE_RETURN].data & ((1 << 8) - 1); + ScrubIdentity scrubIdentity = Randomizer_IdentifyScrub(globalCtx->sceneNum, this->actor.params, respawnData); + + if (scrubIdentity.isShuffled && Flags_GetRandomizerInf(scrubIdentity.randomizerInf)) { + Actor_Kill(&this->actor); + } + } + if (((this->actor.params == 0x0002) && (gSaveContext.itemGetInf[0] & 0x800)) || ((this->actor.params == 0x0009) && (gSaveContext.infTable[25] & 4)) || ((this->actor.params == 0x000A) && (gSaveContext.infTable[25] & 8))) { diff --git a/soh/src/overlays/actors/ovl_En_Si/z_en_si.c b/soh/src/overlays/actors/ovl_En_Si/z_en_si.c index 36fc46abb3..39e1195f54 100644 --- a/soh/src/overlays/actors/ovl_En_Si/z_en_si.c +++ b/soh/src/overlays/actors/ovl_En_Si/z_en_si.c @@ -17,7 +17,8 @@ s32 func_80AFB748(EnSi* this, GlobalContext* globalCtx); void func_80AFB768(EnSi* this, GlobalContext* globalCtx); void func_80AFB89C(EnSi* this, GlobalContext* globalCtx); void func_80AFB950(EnSi* this, GlobalContext* globalCtx); -void Randomizer_GrantSkullReward(EnSi* this, GlobalContext* globalCtx); +void Randomizer_UpdateSkullReward(EnSi* this, GlobalContext* globalCtx); +void Randomizer_GiveSkullReward(EnSi* this, GlobalContext* globalCtx); s32 textId = 0xB4; s32 giveItemId = ITEM_SKULL_TOKEN; @@ -99,21 +100,25 @@ void func_80AFB768(EnSi* this, GlobalContext* globalCtx) { if (this->collider.base.ocFlags2 & OC2_HIT_PLAYER) { this->collider.base.ocFlags2 &= ~OC2_HIT_PLAYER; + if (gSaveContext.n64ddFlag) { - Randomizer_GrantSkullReward(this, globalCtx); + Randomizer_UpdateSkullReward(this, globalCtx); } else { Item_Give(globalCtx, giveItemId); } if ((CVar_GetS32("gSkulltulaFreeze", 0) != 1 || giveItemId != ITEM_SKULL_TOKEN) && getItemId != RG_ICE_TRAP) { player->actor.freezeTimer = 20; } + Message_StartTextbox(globalCtx, textId, NULL); if (gSaveContext.n64ddFlag && getItemId != RG_ICE_TRAP) { + Randomizer_GiveSkullReward(this, globalCtx); Audio_PlayFanfare_Rando(getItem); } else { Audio_PlayFanfare(NA_BGM_SMALL_ITEM_GET); } + player->getItemEntry = (GetItemEntry)GET_ITEM_NONE; this->actionFunc = func_80AFB950; } else { @@ -133,16 +138,20 @@ void func_80AFB89C(EnSi* this, GlobalContext* globalCtx) { if (!CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_13)) { if (gSaveContext.n64ddFlag) { - Randomizer_GrantSkullReward(this, globalCtx); + Randomizer_UpdateSkullReward(this, globalCtx); } else { Item_Give(globalCtx, giveItemId); } + Message_StartTextbox(globalCtx, textId, NULL); + if (gSaveContext.n64ddFlag && getItemId != RG_ICE_TRAP) { + Randomizer_GiveSkullReward(this, globalCtx); Audio_PlayFanfare_Rando(getItem); } else { Audio_PlayFanfare(NA_BGM_SMALL_ITEM_GET); } + player->getItemEntry = (GetItemEntry)GET_ITEM_NONE; this->actionFunc = func_80AFB950; } @@ -190,7 +199,7 @@ void EnSi_Draw(Actor* thisx, GlobalContext* globalCtx) { } } -void Randomizer_GrantSkullReward(EnSi* this, GlobalContext* globalCtx) { +void Randomizer_UpdateSkullReward(EnSi* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); getItem = Randomizer_GetRandomizedItem(GI_SKULL_TOKEN, this->actor.id, this->actor.params, globalCtx->sceneNum); @@ -201,12 +210,20 @@ void Randomizer_GrantSkullReward(EnSi* this, GlobalContext* globalCtx) { } else { textId = getItem.textId; giveItemId = getItem.itemId; - if (getItem.modIndex == MOD_NONE) { - Item_Give(globalCtx, giveItemId); - } else if (getItem.modIndex == MOD_RANDOMIZER) { - Randomizer_Item_Give(globalCtx, getItem); - } } - // player->getItemId = getItemId; player->getItemEntry = getItem; } + +void Randomizer_GiveSkullReward(EnSi* this, GlobalContext* globalCtx) { + Player* player = GET_PLAYER(globalCtx); + + if (getItem.modIndex == MOD_NONE) { + Item_Give(globalCtx, giveItemId); + } else if (getItem.modIndex == MOD_RANDOMIZER) { + Randomizer_Item_Give(globalCtx, getItem); + } + // RANDOTOD: Move this into Item_Give() or some other more central location + if (getItem.getItemId == GI_SWORD_BGS) { + gSaveContext.bgsFlag = true; + } +} diff --git a/soh/src/overlays/actors/ovl_Object_Kankyo/z_object_kankyo.c b/soh/src/overlays/actors/ovl_Object_Kankyo/z_object_kankyo.c index 22b7969644..6aff361444 100644 --- a/soh/src/overlays/actors/ovl_Object_Kankyo/z_object_kankyo.c +++ b/soh/src/overlays/actors/ovl_Object_Kankyo/z_object_kankyo.c @@ -135,6 +135,27 @@ void ObjectKankyo_Init(Actor* thisx, GlobalContext* globalCtx) { this->effects[5].size = 0.0f; } + if (gSaveContext.n64ddFlag) { + if (Flags_GetRandomizerInf(RAND_INF_TRIALS_DONE_FOREST_TRIAL)) { + this->effects[0].size = 0.0f; + } + if (Flags_GetRandomizerInf(RAND_INF_TRIALS_DONE_WATER_TRIAL)) { + this->effects[1].size = 0.0f; + } + if (Flags_GetRandomizerInf(RAND_INF_TRIALS_DONE_SHADOW_TRIAL)) { + this->effects[2].size = 0.0f; + } + if (Flags_GetRandomizerInf(RAND_INF_TRIALS_DONE_FIRE_TRIAL)) { + this->effects[3].size = 0.0f; + } + if (Flags_GetRandomizerInf(RAND_INF_TRIALS_DONE_LIGHT_TRIAL)) { + this->effects[4].size = 0.0f; + } + if (Flags_GetRandomizerInf(RAND_INF_TRIALS_DONE_SPIRIT_TRIAL)) { + this->effects[5].size = 0.0f; + } + } + if (gSaveContext.cutsceneTrigger != 0) { if (gSaveContext.entranceIndex == 0x0538) { this->effects[0].size = 0.1f; diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 2631e39b99..2ac6e0fc25 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -6108,7 +6108,7 @@ s32 func_8083E5A8(Player* this, GlobalContext* globalCtx) { } GetItemEntry giEntry; - if (this->getItemEntry.objectId == OBJECT_INVALID) { + if (this->getItemEntry.objectId == OBJECT_INVALID || (this->getItemId != this->getItemEntry.getItemId)) { giEntry = ItemTable_Retrieve(this->getItemId); } else { giEntry = this->getItemEntry; @@ -6165,7 +6165,7 @@ s32 func_8083E5A8(Player* this, GlobalContext* globalCtx) { } } else if (CHECK_BTN_ALL(sControlInput->press.button, BTN_A) && !(this->stateFlags1 & PLAYER_STATE1_11) && !(this->stateFlags2 & PLAYER_STATE2_10)) { - if (this->getItemId != GI_NONE && this->getItemEntry.objectId != OBJECT_INVALID) { + if (this->getItemId != GI_NONE) { GetItemEntry giEntry; if (this->getItemEntry.objectId == OBJECT_INVALID) { giEntry = ItemTable_Retrieve(-this->getItemId); @@ -9687,8 +9687,8 @@ void func_808473D4(GlobalContext* globalCtx, Player* this) { else if ((!(this->stateFlags1 & PLAYER_STATE1_11) || (heldActor == NULL)) && (interactRangeActor != NULL) && ((!sp1C && (this->getItemId == GI_NONE)) || - ((this->getItemId < 0 && this->getItemEntry.getItemId < 0) && !(this->stateFlags1 & PLAYER_STATE1_27)))) { - if (this->getItemId < 0 && this->getItemEntry.getItemId < 0) { + (this->getItemId < 0 && !(this->stateFlags1 & PLAYER_STATE1_27)))) { + if (this->getItemId < 0) { doAction = DO_ACTION_OPEN; } else if ((interactRangeActor->id == ACTOR_BG_TOKI_SWD) && LINK_IS_ADULT) { doAction = DO_ACTION_DROP; diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index 75cc7ca27b..f363751ee2 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -433,6 +433,7 @@ void FileChoose_UpdateMainMenu(GameState* thisx) { const char* fileLoc = CVar_GetString("gSpoilerLog", ""); Randomizer_LoadSettings(fileLoc); Randomizer_LoadHintLocations(fileLoc); + Randomizer_LoadRequiredTrials(fileLoc); Randomizer_LoadItemLocations(fileLoc, silent); fileSelectSpoilerFileLoaded = true; }