diff --git a/mm/2s2h/BenGui/BenGui.cpp b/mm/2s2h/BenGui/BenGui.cpp index f7c9d8d077..7d27cde49a 100644 --- a/mm/2s2h/BenGui/BenGui.cpp +++ b/mm/2s2h/BenGui/BenGui.cpp @@ -169,7 +169,7 @@ void SetupGuiElements() { mNotificationWindow->Show(); mRandoCheckTrackerWindow = std::make_shared( - "gWindows.CheckTracker", "Check Tracker", ImVec2(375, 460)); + "gCheckTracker.Enable", "Check Tracker", ImVec2(375, 460)); gui->AddGuiWindow(mRandoCheckTrackerWindow); mRandoCheckTrackerSettingsWindow = std::make_shared( diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 610372d9dd..16938d5dfd 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -1575,6 +1575,9 @@ void BenMenu::AddEnhancements() { .Min(1) .Max(20) .DefaultValue(20)); + AddWidget(path, "Skip Little Beaver Brother Races", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Minigames.SkipLittleBeaver") + .Options(CheckboxOptions().Tooltip("Only Race the Older Beaver.")); AddWidget(path, "Mark Shooting Gallery Octoroks", WIDGET_CVAR_CHECKBOX) .CVar("gEnhancements.Minigames.MarkShootingGalleryOctoroks") .Options(CheckboxOptions().Tooltip("Places markers on the Town Shooting Gallery Octoroks, indicating whether " diff --git a/mm/2s2h/Enhancements/Minigames/BeaverRace.cpp b/mm/2s2h/Enhancements/Minigames/BeaverRace.cpp index 24c03b85f2..f2c74b2182 100644 --- a/mm/2s2h/Enhancements/Minigames/BeaverRace.cpp +++ b/mm/2s2h/Enhancements/Minigames/BeaverRace.cpp @@ -1,6 +1,7 @@ #include #include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/ShipInit.hpp" +#include "CustomMessage/CustomMessage.h" extern "C" { #include "overlays/actors/ovl_En_Az/z_en_az.h" @@ -9,18 +10,21 @@ void func_80A979DC(EnAz* thisx, PlayState* play); void func_80A97F9C(EnAz* thisx, PlayState* play); } -#define CVAR_NAME "gEnhancements.Minigames.BeaverRaceRingsCollected" -#define CVAR CVarGetInteger(CVAR_NAME, 20) +#define CVAR_RINGS_NAME "gEnhancements.Minigames.BeaverRaceRingsCollected" +#define CVAR_RINGS CVarGetInteger(CVAR_RINGS_NAME, 20) + +#define CVAR_SPEEDUP_NAME "gEnhancements.Minigames.SkipLittleBeaver" +#define CVAR_SPEEDUP CVarGetInteger(CVAR_SPEEDUP_NAME, 0) static bool minigameScoreSet = false; // Flag to track if the score has been set -void RegisterBeaverRace() { - COND_ID_HOOK(ShouldActorUpdate, ACTOR_EN_AZ, CVAR < 20, [](Actor* actor, bool* should) { +void RegisterBeaverRaceRings() { + COND_ID_HOOK(ShouldActorUpdate, ACTOR_EN_AZ, CVAR_RINGS < 20, [](Actor* actor, bool* should) { EnAz* enAz = (EnAz*)actor; Player* player = GET_PLAYER(gPlayState); if (!minigameScoreSet) { - gSaveContext.minigameScore = CVAR; + gSaveContext.minigameScore = CVAR_RINGS; minigameScoreSet = true; // Set the flag after assignment } @@ -60,4 +64,32 @@ void RegisterBeaverRace() { }); } -static RegisterShipInitFunc initFunc(RegisterBeaverRace, { CVAR_NAME }); +void RegisterBeaverRaceSpeedup() { + COND_ID_HOOK(ShouldActorUpdate, ACTOR_EN_AZ, CVAR_SPEEDUP, [](Actor* actor, bool* should) { + if (!CHECK_WEEKEVENTREG(WEEKEVENTREG_24_04)) { + SET_WEEKEVENTREG(WEEKEVENTREG_24_04); + } + }); + + COND_ID_HOOK(OnOpenText, 0x10D6, CVAR_SPEEDUP, [](u16* textId, bool* loadFromMessageTable) { + std::string replaceMsg = "My older brother will show you\x11the way, so follow him and\x11" + "don't get separated!"; + auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + entry.msg.replace(entry.msg.begin(), entry.msg.end(), replaceMsg); + + CustomMessage::LoadCustomMessageIntoFont(entry); + *loadFromMessageTable = false; + }); + + COND_ID_HOOK(OnOpenText, 0x10FA, CVAR_SPEEDUP, [](u16* textId, bool* loadFromMessageTable) { + std::string replaceMsg = "The time limit is %r1:50%w, let's race."; + auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + entry.msg.replace(entry.msg.begin(), entry.msg.end(), replaceMsg); + + CustomMessage::LoadCustomMessageIntoFont(entry); + *loadFromMessageTable = false; + }); +} + +static RegisterShipInitFunc initRingsFunc(RegisterBeaverRaceRings, { CVAR_RINGS_NAME }); +static RegisterShipInitFunc initSpeedupFunc(RegisterBeaverRaceSpeedup, { CVAR_SPEEDUP_NAME }); diff --git a/mm/2s2h/PresetManager/PresetDescriptions.h b/mm/2s2h/PresetManager/PresetDescriptions.h new file mode 100644 index 0000000000..9a594190bf --- /dev/null +++ b/mm/2s2h/PresetManager/PresetDescriptions.h @@ -0,0 +1,81 @@ +#pragma once +#include "2s2h/BenGui/UIWidgets.hpp" +#include + +#define COLOR_ORANGE UIWidgets::Colors::Orange +#define COLOR_GREEN UIWidgets::Colors::Green +#define TEXT_COLOR(color) UIWidgets::ColorValues.at(COLOR_##color) + +std::vector> deckScrubberReqs = { + { "Dungeon Access:", "Requires Transformation & Song" }, + { "Moon Access Remains:", "4 Boss Remains & Oath to Order" }, + { "Stray Fairies Required:", "5" }, +}; + +std::vector deckScrubberShuffles = { + "Shuffle Boss Remains", "Shuffle Owl Statues", "Shuffle Shops", "Shuffle Songs", "Shuffle Stray Fairies", +}; + +std::vector deckScrubberStarting = { + "Bunny Hood", "Full Wallets", "Hero's Shield", "Maps and Compasses", + "Kokiri Sword", "Random Boss Remain", "Ocarina of Time", "", + "Song of Time", +}; + +std::vector> deckScrubberHints = { + { "General Hints", "The Bomb shop 4th Item, Lottery, Great Fairy Fountains, and Mountain Smithy rewards can be " + "hinted by speaking to their respective NPCs." }, + { "Spider House Rewards", "Swamp is hinted within the Spider House.\n" + "Ocean is hinted in South Clock Town day 1 on top of the scaffolding." }, + { "Gossip Stone Static", "Similar to Ship of Harkinian, Gossip Stones will provide hints." }, + { "Boss Remains", "In South Clock Town there is a big poster on the side of the Chest Building, this will tell you " + "where each Boss Remain is located." }, + { "Oath to Order", + "Once you have all 4 Remains, talking to Skull Kid on the Clock Tower Rooftop will hint at the songs location." }, + { "Hookshot", "The Zora near Pirates Fortress will hint the items location." }, + { "Transformation Masks", "In South Clock Town, the sign leaning up against the stall near the Business Scrub will " + "tell you where one of each mask is located." }, +}; + +void DrawDeckScrubberDescription() { + ImGui::SeparatorText("Deckscrubber Settings"); + ImGui::TextColored(TEXT_COLOR(ORANGE), "Requirements"); + if (ImGui::BeginTable("DesckscrubberReq", 2)) { + for (auto& [key, value] : deckScrubberReqs) { + ImGui::TableNextColumn(); + ImGui::TextColored(TEXT_COLOR(GREEN), key.c_str()); + ImGui::TableNextColumn(); + ImGui::Text(value.c_str()); + } + ImGui::EndTable(); + } + ImGui::Separator(); + if (ImGui::BeginTable("DeckscrubberSeedSettings", 2)) { + ImGui::TableNextColumn(); + ImGui::TextColored(TEXT_COLOR(ORANGE), "Included Shuffles"); + if (ImGui::BeginTable("DesckscrubberShuffles", 2)) { + for (auto& shuffle : deckScrubberShuffles) { + ImGui::TableNextColumn(); + ImGui::Text(shuffle.c_str()); + } + ImGui::EndTable(); + } + ImGui::TableNextColumn(); + ImGui::TextColored(TEXT_COLOR(ORANGE), "Starting Items"); + if (ImGui::BeginTable("DesckscrubberStarting", 2)) { + for (auto& item : deckScrubberStarting) { + ImGui::TableNextColumn(); + ImGui::Text(item.c_str()); + } + ImGui::EndTable(); + } + ImGui::EndTable(); + } + ImGui::Separator(); + ImGui::TextColored(TEXT_COLOR(ORANGE), "Hints"); + for (auto& [key, value] : deckScrubberHints) { + ImGui::TextColored(TEXT_COLOR(GREEN), key.c_str()); + ImGui::TextWrapped(value.c_str()); + ImGui::Separator(); + } +} diff --git a/mm/2s2h/PresetManager/PresetManager.cpp b/mm/2s2h/PresetManager/PresetManager.cpp index 63462833bb..542909f8e2 100644 --- a/mm/2s2h/PresetManager/PresetManager.cpp +++ b/mm/2s2h/PresetManager/PresetManager.cpp @@ -256,6 +256,151 @@ nlohmann::json curatedPresetJ = R"( } )"_json; +nlohmann::json deckScrubberJ = R"( +{ + "ClearCVars": [ + "gCheats", + "gCollisionViewer", + "gDeveloperTools", + "gEnhancements", + "gEventLog", + "gFixes", + "gModes", + "gNetwork", + "gNotifications", + "gRando" + ], + "CVars": { + "gCheats": { + "EasyFrameAdvance": 1 + }, + "gEnhancements": { + "Cutscenes": { + "SkipEnemyCutscenes": 1, + "SkipEntranceCutscenes": 1, + "SkipFirstCycle": 1, + "SkipGetItemCutscenes": 1, + "SkipIntroSequence": 1, + "SkipMiscInteractions": 1, + "SkipOnePointCutscenes": 1, + "SkipStoryCutscenes": 1, + "SkipToFileSelect": 1 + }, + "Dialogue": { + "AutoBombersCode": 1, + "FastBankSelection": 1, + "FastText": 1 + }, + "DifficultyOptions": { + "DekuGuardSearchBalls": 1, + "LowerBankRewardThresholds": 1 + }, + "Dpad": { + "DpadEquips": 1 + }, + "Equipment": { + "BetterPictoMessage": 1, + "ChuDrops": 1, + "MagicArrowEquipSpeed": 1, + "TwoHandedSwordSpinAttack": 1 + }, + "Fixes": { + "CompletedHeartContainerAudio": 1, + "ControlCharacters": 1, + "FierceDeityZTargetMovement": 1 + }, + "Masks": { + "FastTransformation": 1, + "FierceDeitysAnywhere": 1, + "NoBlastMaskCooldown": 1, + "PersistentBunnyHood": { + "Enabled": 1 + } + }, + "Minigames": { + "AlwaysWinDoggyRace": 1, + "CuccoShackCuccoCount": 1, + "SwampArcheryScore": 2179, + "SkipLittleBeaver": 1 + }, + "Playback": { + "DpadOcarina": 1, + "NoDropOcarinaInput": 1, + "SkipScarecrowSong": 1 + }, + "Player": { + "ClimbSpeed": 2, + "FasterPushAndPull": 1, + "FierceDeityPutaway": 1, + "InstantPutaway": 1 + }, + "PlayerActions": { + "ArrowCycle": 1, + "InstantRecall": 1 + }, + "Restorations": { + "PowerCrouchStab": 2, + "WoodfallMountainAppearance": 1 + }, + "Saving": { + "PauseSave": 1 + }, + "Songs": { + "BetterSongOfDoubleTime": 1, + "FasterSongPlayback": 1, + "ZoraEggCount": 1 + }, + "Timesavers": { + "DampeDiggingSkip": 1, + "FastChests": 1, + "FasterSceneTransitions": 1, + "GalleryTwofer": 1, + "MarineLabHP": 1, + "SkipBalladOfWindfish": 1, + "SwampBoatSpeed": 1 + } + }, + "gFixes": { + "FixAmmoCountEnvColor": 1, + "FixEponaStealingSword": 1, + "FixIkanaGreatFairyFountainColor": 1 + }, + "gRando": { + "Enabled": 1, + "InputSeed": "", + "Options": { + "RO_LOGIC": 4, + "RO_HINTS_BOSS_REMAINS": 1, + "RO_HINTS_GOSSIP_STONES": 1, + "RO_HINTS_HOOKSHOT": 1, + "RO_HINTS_OATH_TO_ORDER": 1, + "RO_HINTS_SPIDER_HOUSES": 1, + "RO_HINTS_TRANSFORMATIONS": 1, + "RO_MINIMUM_STRAY_FAIRIES": 5, + "RO_PLENTIFUL_ITEMS": 1, + "RO_SHUFFLE_BOSS_REMAINS": 1, + "RO_SHUFFLE_GOLD_SKULLTULAS": 0, + "RO_SHUFFLE_OWL_STATUES": 1, + "RO_SHUFFLE_SHOPS": 1, + "RO_SHUFFLE_TRAPS": 1, + "RO_STARTING_MAPS_AND_COMPASSES": 1, + "RO_STARTING_RUPEES": 1, + "RO_TRAP_AMOUNT": 6 + }, + "SpoilerFile": "", + "SpoilerFileIndex": 0, + "StartingItems": "109,126,91,146,66", + "Traps": { + "Freeze": 1, + "Shock": 1 + } + } + }, + "type": "2S2H_PRESET", + "version": 1 +} +)"_json; + std::unordered_map>> presets = {}; const std::filesystem::path presetsFolderPath(Ship::Context::GetPathRelativeToAppDirectory("presets", appShortName)); @@ -264,6 +409,7 @@ void PresetManager_RefreshPresets() { presets.insert( { "Defaults (Everything Off)", { defaultsPresetJ, { "Developer Tools", "Enhancements", "HUD", "Rando" } } }); presets.insert({ "Curated", { curatedPresetJ, { "Developer Tools", "Enhancements", "HUD" } } }); + presets.insert({ "Deckscrubber v3", { deckScrubberJ, { "Developer Tools", "Enhancements", "HUD", "Rando" } } }); // ensure the presets folder exists if (!std::filesystem::exists(presetsFolderPath)) { diff --git a/mm/2s2h/PresetManager/PresetManager.h b/mm/2s2h/PresetManager/PresetManager.h index 7b55c97f71..ce81bb117c 100644 --- a/mm/2s2h/PresetManager/PresetManager.h +++ b/mm/2s2h/PresetManager/PresetManager.h @@ -3,8 +3,10 @@ #define PRESET_MANAGER_H #include +extern nlohmann::json deckScrubberJ; bool PresetManager_HandleFileDropped(const std::string& filePath); +void PresetManager_ApplyPreset(nlohmann::json j); void PresetManager_Draw(); #endif // PRESET_MANAGER_H diff --git a/mm/2s2h/Rando/ActorBehavior/EnTalk.cpp b/mm/2s2h/Rando/ActorBehavior/EnTalk.cpp index d95069ab60..6b8807d577 100644 --- a/mm/2s2h/Rando/ActorBehavior/EnTalk.cpp +++ b/mm/2s2h/Rando/ActorBehavior/EnTalk.cpp @@ -7,6 +7,81 @@ extern "C" { #include "variables.h" } +void ApplyTransformationHints(u16* textId, bool* loadFromMessageTable) { + static std::string placeholderMsg = "Last seen in"; + static int transformHintIndex = 0; + + if (transformHintIndex > 3) { + transformHintIndex = 0; + } + + u8 icon = 0xFE; + std::string msg; + RandoItemId randoItemId = RI_NONE; + + if (transformHintIndex == 0) { + msg = " %yDeparted Soul Alert%w!\n" + "The souls of the departed lay restless, " + "find and heal them!"; + } else { + msg = "%g{{mask}}%w:\n" + "%y{{locations}}%w."; + + switch (transformHintIndex) { + case 1: + CustomMessage::Replace(&msg, "{{mask}}", " Butler's Son"); + randoItemId = RI_MASK_DEKU; + break; + case 2: + CustomMessage::Replace(&msg, "{{mask}}", " Darmani"); + randoItemId = RI_MASK_GORON; + break; + case 3: + CustomMessage::Replace(&msg, "{{mask}}", " Mikau"); + randoItemId = RI_MASK_ZORA; + break; + default: + break; + } + + icon = Rando::StaticData::GetIconForZMessage(randoItemId); + std::vector itemPlacements = Rando::FindMultiItemPlacement(randoItemId); + std::string locationStr = ""; + if (!itemPlacements.empty()) { + for (int i = 0; i < itemPlacements.size(); i++) { + if (RANDO_SAVE_CHECKS[itemPlacements[i]].obtained) { + itemPlacements[i] = RC_UNKNOWN; + } + } + for (auto& location : itemPlacements) { + if (location == RC_UNKNOWN) { + locationStr = "%gLinks pocket%w"; + break; + } + + if (locationStr != "") { + locationStr += " %w&%y\n"; + } + + locationStr += Ship_GetSceneName(Rando::StaticData::Checks[location].sceneId); + } + CustomMessage::Replace(&msg, "{{locations}}", locationStr); + } else { + CustomMessage::Replace(&msg, "{{locations}}", "%gLinks pocket%w"); + } + } + + CustomMessage::Entry entry = { + .icon = icon, + .nextMessageID = transformHintIndex >= 3 ? (u16)0xFFFF : (u16)0x1C18, + .msg = msg, + }; + + CustomMessage::LoadCustomMessageIntoFont(entry); + *loadFromMessageTable = false; + transformHintIndex++; +} + void ApplyRemainsHint(u16* textId, bool* loadFromMessageTable) { static int remainsHintIndex = 0; @@ -24,8 +99,8 @@ void ApplyRemainsHint(u16* textId, bool* loadFromMessageTable) { "local townfolk, will pay good money " "for their remains."; } else { - msg = " %g{{boss}}%w:\n" - "Last seen in near %y{{location}}%w."; + msg = "%g{{boss}}%w was last seen in:\n" + "%y{{locations}}%w."; switch (remainsHintIndex) { case 1: @@ -47,9 +122,30 @@ void ApplyRemainsHint(u16* textId, bool* loadFromMessageTable) { } icon = Rando::StaticData::GetIconForZMessage(randoItemId); - RandoCheckId randoCheckId = Rando::FindItemPlacement(randoItemId); - CustomMessage::Replace(&msg, "{{location}}", - Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId)); + std::vector itemPlacements = Rando::FindMultiItemPlacement(randoItemId); + std::string locationStr = ""; + if (!itemPlacements.empty()) { + for (int i = 0; i < itemPlacements.size(); i++) { + if (RANDO_SAVE_CHECKS[itemPlacements[i]].obtained) { + itemPlacements[i] = RC_UNKNOWN; + } + } + for (auto& location : itemPlacements) { + if (location == RC_UNKNOWN) { + locationStr = "%gLinks pocket%w"; + break; + } + + if (locationStr != "") { + locationStr += " %w&%y\n"; + } + + locationStr += Ship_GetSceneName(Rando::StaticData::Checks[location].sceneId); + } + CustomMessage::Replace(&msg, "{{locations}}", locationStr); + } else { + CustomMessage::Replace(&msg, "{{locations}}", "%gLinks pocket%w"); + } } CustomMessage::Entry entry = { @@ -66,4 +162,7 @@ void ApplyRemainsHint(u16* textId, bool* loadFromMessageTable) { void Rando::ActorBehavior::InitEnTalkBehavior() { // "Recruiting Soldiers..." Posters around Clock Town COND_ID_HOOK(OnOpenText, 0x1C06, IS_RANDO && RANDO_SAVE_OPTIONS[RO_HINTS_BOSS_REMAINS], ApplyRemainsHint); + + COND_ID_HOOK(OnOpenText, 0x1C18, IS_RANDO && RANDO_SAVE_OPTIONS[RO_HINTS_TRANSFORMATIONS], + ApplyTransformationHints); } diff --git a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp index 48dfddb947..15eb7eff18 100644 --- a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp +++ b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp @@ -32,14 +32,14 @@ static std::unordered_map betterSceneIndex = { #undef DEFINE_SCENE #undef DEFINE_SCENE_UNSET -#define CVAR_NAME_SHOW_CHECK_TRACKER "gWindows.CheckTracker" -#define CVAR_NAME_SHOW_LOGIC "gRando.CheckTracker.OnlyShowChecksInLogic" -#define CVAR_NAME_HIDE_COLLECTED "gRando.CheckTracker.HideCollectedChecks" -#define CVAR_NAME_HIDE_SKIPPED "gRando.CheckTracker.HideSkippedChecks" -#define CVAR_NAME_SCROLL_TO_SCENE "gRando.CheckTracker.ScrollToCurrentScene" -#define CVAR_NAME_TRACKER_OPACITY "gRando.CheckTracker.Opacity" -#define CVAR_NAME_TRACKER_SCALE "gRando.CheckTracker.Scale" -#define CVAR_NAME_SHOW_CURRENT_SCENE "gRando.CheckTracker.ShowCurrentScene" +#define CVAR_NAME_SHOW_CHECK_TRACKER "gCheckTracker.Enable" +#define CVAR_NAME_SHOW_LOGIC "gCheckTracker.OnlyShowChecksInLogic" +#define CVAR_NAME_HIDE_COLLECTED "gCheckTracker.HideCollectedChecks" +#define CVAR_NAME_HIDE_SKIPPED "gCheckTracker.HideSkippedChecks" +#define CVAR_NAME_SCROLL_TO_SCENE "gCheckTracker.ScrollToCurrentScene" +#define CVAR_NAME_TRACKER_OPACITY "gCheckTracker.Opacity" +#define CVAR_NAME_TRACKER_SCALE "gCheckTracker.Scale" +#define CVAR_NAME_SHOW_CURRENT_SCENE "gCheckTracker.ShowCurrentScene" #define CVAR_SHOW_CHECK_TRACKER CVarGetInteger(CVAR_NAME_SHOW_CHECK_TRACKER, 0) #define CVAR_SHOW_LOGIC CVarGetInteger(CVAR_NAME_SHOW_LOGIC, 0) #define CVAR_HIDE_COLLECTED CVarGetInteger(CVAR_NAME_HIDE_COLLECTED, 0) diff --git a/mm/2s2h/Rando/Logic/DeckscrubberLogic.cpp b/mm/2s2h/Rando/Logic/DeckscrubberLogic.cpp new file mode 100644 index 0000000000..a8dfe0922d --- /dev/null +++ b/mm/2s2h/Rando/Logic/DeckscrubberLogic.cpp @@ -0,0 +1,298 @@ +#include "Logic.h" +#include "2s2h/Rando/Types.h" + +#include +#include +#include + +extern "C" { +#include "variables.h" +#include "ShipUtils.h" +uint64_t GetUnixTimestamp(); +} + +namespace Rando { + +namespace Logic { + +void ApplyDeckscrubberLogicToSaveContext(std::vector& checkPool, std::vector& itemPool) { + + std::map> itemToSceneBlacklist = { + { RI_MASK_DEKU, + { SCENE_MITURIN, SCENE_MITURIN_BS, SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, + SCENE_SOUGEN, SCENE_LAST_BS } }, + { RI_SONG_SONATA, + { SCENE_MITURIN, SCENE_MITURIN_BS, SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, + SCENE_SOUGEN, SCENE_LAST_BS } }, + { RI_MASK_ZORA, + { SCENE_SEA, SCENE_SEA_BS, SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, SCENE_SOUGEN, + SCENE_LAST_BS } }, + { RI_SONG_NOVA, + { SCENE_SEA, SCENE_SEA_BS, SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, SCENE_SOUGEN, + SCENE_LAST_BS } }, + { RI_MASK_GORON, + { SCENE_HAKUGIN, SCENE_HAKUGIN_BS, SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, + SCENE_SOUGEN, SCENE_LAST_BS } }, + { RI_PROGRESSIVE_LULLABY, + { SCENE_HAKUGIN, SCENE_HAKUGIN_BS, SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, + SCENE_SOUGEN, SCENE_LAST_BS } }, + { RI_REMAINS_ODOLWA, + { SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, SCENE_SOUGEN, SCENE_LAST_BS } }, + { RI_REMAINS_GOHT, + { SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, SCENE_SOUGEN, SCENE_LAST_BS } }, + { RI_REMAINS_GYORG, + { SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, SCENE_SOUGEN, SCENE_LAST_BS } }, + { RI_REMAINS_TWINMOLD, + { SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, SCENE_SOUGEN, SCENE_LAST_BS } }, + { RI_SONG_OATH, + { SCENE_LAST_DEKU, SCENE_LAST_GORON, SCENE_LAST_ZORA, SCENE_LAST_LINK, SCENE_SOUGEN, SCENE_LAST_BS } }, + }; + + uint64_t tick = GetUnixTimestamp(); + + SaveContext copiedSaveContext; + memcpy(&copiedSaveContext, &gSaveContext, sizeof(SaveContext)); + + std::set regionsInLogic = { RR_MAX }; + std::map checksInLogic; + std::set>*> eventsInLogic; + + RandoCheckId checkWithJunk = RC_UNKNOWN; + std::set nonJunkItemsThatWeHaveTried; + std::vector checksWithJunk; + std::vector checksWithJunkWeights; + int weight = 1; + + // Initial shuffle + if (itemPool.size() > 1) { + for (size_t i = 0; i < itemPool.size(); i++) { + size_t j = Ship_Random(0, itemPool.size() - 1); + std::swap(itemPool[i], itemPool[j]); + } + } + + auto handleError = [&](std::string message) { + SPDLOG_ERROR("Items/Checks: {}/{}", itemPool.size(), checkPool.size()); + + for (auto& randoCheckId : checkPool) { + SPDLOG_ERROR("Check still in pool: {}", Rando::StaticData::Checks[randoCheckId].name); + } + for (RandoItemId randoItemId : itemPool) { + SPDLOG_ERROR("Item still in pool: {}", Rando::StaticData::Items[randoItemId].spoilerName); + } + + memcpy(&gSaveContext, &copiedSaveContext, sizeof(SaveContext)); + throw std::runtime_error(message); + }; + + // Helper: pick valid item respecting the scene blacklist + RandoItemId currentRandoItemId = RI_UNKNOWN; + auto pickValidItemForScene = [&](std::vector& pool, SceneId scene) -> RandoItemId { + for (auto it = pool.rbegin(); it != pool.rend(); ++it) { + RandoItemId candidate = *it; + currentRandoItemId = candidate; + + auto blIt = itemToSceneBlacklist.find(candidate); + if (blIt != itemToSceneBlacklist.end()) { + const auto& blacklistScenes = blIt->second; + + if (std::find(blacklistScenes.begin(), blacklistScenes.end(), scene) != blacklistScenes.end()) { + // Item forbidden for this scene, skip it + continue; + } + } + + // Valid — remove and return + RandoItemId chosen = candidate; + pool.erase(std::next(it).base()); + return chosen; + } + + handleError("No valid item found for check due to blacklist."); + return RI_NONE; // unreachable + }; + + while (true) { + if (GetUnixTimestamp() - tick > 10000) { + handleError("Logic Generation Timeout"); + } + + bool regionsInLogicChanged = false; + bool eventsInLogicChanged = false; + bool checksInLogicChanged = false; + + // Discover reachable regions + auto prevRegionsInLogicSize = regionsInLogic.size(); + for (RandoRegionId regionId : regionsInLogic) { + FindReachableRegions(regionId, regionsInLogic); + } + if (regionsInLogic.size() != prevRegionsInLogicSize) { + regionsInLogicChanged = true; + } + + for (RandoRegionId regionId : regionsInLogic) { + auto& randoRegion = Regions[regionId]; + + for (auto& randoEvent : randoRegion.events) { + if (!eventsInLogic.contains(&randoEvent) && randoEvent.second()) { + RANDO_EVENTS[randoEvent.first]++; + eventsInLogic.insert(&randoEvent); + eventsInLogicChanged = true; + } + } + + for (auto& [randoCheckId, checkLogic] : randoRegion.checks) { + if (checksInLogic.find(randoCheckId) == checksInLogic.end() && checkLogic.first()) { + + auto it = std::find(checkPool.begin(), checkPool.end(), randoCheckId); + bool isShuffled = it != checkPool.end(); + checksInLogic.insert({ randoCheckId, isShuffled }); + + if (isShuffled) { + checkPool.erase(it); + } + + RandoItemId randoItemId; + + // Determine scene for blacklist check + SceneId checkSceneId = Rando::StaticData::Checks[randoCheckId].sceneId; + + if (RANDO_SAVE_CHECKS[randoCheckId].skipped) { + uint32_t index = 0; + for (auto& item : itemPool) { + if (Rando::StaticData::Items[item].randoItemType == RITYPE_JUNK) { + randoItemId = item; + itemPool.erase(itemPool.begin() + index); + break; + } + index++; + } + } else if (isShuffled) { + // NEW: pick item that is NOT blacklisted for this check's scene + randoItemId = pickValidItemForScene(itemPool, checkSceneId); + + if (Rando::StaticData::Items[randoItemId].randoItemType == RITYPE_JUNK || + Rando::StaticData::Items[randoItemId].randoItemType == RITYPE_HEALTH) { + + checksWithJunk.push_back(randoCheckId); + checksWithJunkWeights.push_back(weight); + } + + SPDLOG_TRACE("Check: {}:{}", Rando::StaticData::Checks[randoCheckId].name, + Rando::StaticData::Items[randoItemId].spoilerName); + } else { + randoItemId = Rando::StaticData::Checks[randoCheckId].randoItemId; + } + + RANDO_SAVE_CHECKS[randoCheckId].randoItemId = randoItemId; + RANDO_SAVE_CHECKS[randoCheckId].shuffled = isShuffled; + GiveItem(ConvertItem(randoItemId)); + checksInLogicChanged = true; + } + } + } + + if (itemPool.empty()) { + break; + } + + // Junk replacement logic — unchanged + if (!regionsInLogicChanged && !checksInLogicChanged && !eventsInLogicChanged) { + + if (checkWithJunk == RC_UNKNOWN) { + if (checksWithJunk.empty()) { + handleError("No checks with junk, not sure what to do"); + } + + if (checksWithJunk.size() == 1) { + checkWithJunk = checksWithJunk[0]; + } else { + std::vector cumulativeWeights(checksWithJunkWeights.size()); + std::partial_sum(checksWithJunkWeights.begin(), checksWithJunkWeights.end(), + cumulativeWeights.begin()); + double random = Ship_Random(0, cumulativeWeights.back()); + auto it = std::lower_bound(cumulativeWeights.begin(), cumulativeWeights.end(), random); + size_t index = std::distance(cumulativeWeights.begin(), it); + + checkWithJunk = checksWithJunk[index]; + + checksWithJunk.erase(checksWithJunk.begin() + index); + checksWithJunkWeights.erase(checksWithJunkWeights.begin() + index); + } + } + + std::vector> nonJunkItemsThatWeHaveNotTried; + bool anyNonJunkItemsLeft = false; + + for (size_t i = 0; i < itemPool.size(); i++) { + if (Rando::StaticData::Items[itemPool[i]].randoItemType != RITYPE_JUNK && + Rando::StaticData::Items[itemPool[i]].randoItemType != RITYPE_HEALTH) { + + anyNonJunkItemsLeft = true; + + if (nonJunkItemsThatWeHaveTried.find(itemPool[i]) == nonJunkItemsThatWeHaveTried.end()) { + nonJunkItemsThatWeHaveNotTried.push_back({ itemPool[i], i }); + } + } + } + + if (!anyNonJunkItemsLeft) { + handleError("No non-junk items left"); + } + + if (nonJunkItemsThatWeHaveNotTried.empty()) { + SPDLOG_TRACE("Already tried all non-junk items, leaving the last non-junk item in place: {}: {}", + Rando::StaticData::Checks[checkWithJunk].name, + Rando::StaticData::Items[RANDO_SAVE_CHECKS[checkWithJunk].randoItemId].spoilerName); + checkWithJunk = RC_UNKNOWN; + nonJunkItemsThatWeHaveTried.clear(); + continue; + } + + RandoItemId oldRandoItemId = RANDO_SAVE_CHECKS[checkWithJunk].randoItemId; + auto& [newRandoItemId, indexInPool] = nonJunkItemsThatWeHaveNotTried[0]; + + RANDO_SAVE_CHECKS[checkWithJunk].randoItemId = newRandoItemId; + + RemoveItem(oldRandoItemId); + GiveItem(ConvertItem(newRandoItemId)); + + itemPool.erase(itemPool.begin() + indexInPool); + itemPool.push_back(oldRandoItemId); + + nonJunkItemsThatWeHaveTried.insert(newRandoItemId); + SPDLOG_TRACE("Attempting to replaced junk item: {}:{}", Rando::StaticData::Checks[checkWithJunk].name, + Rando::StaticData::Items[newRandoItemId].spoilerName); + } else { + weight++; + if (checkWithJunk != RC_UNKNOWN) { + SPDLOG_TRACE("Successfully Replaced junk item with: {}:{}", + Rando::StaticData::Checks[checkWithJunk].name, + Rando::StaticData::Items[RANDO_SAVE_CHECKS[checkWithJunk].randoItemId].spoilerName); + } + checkWithJunk = RC_UNKNOWN; + nonJunkItemsThatWeHaveTried.clear(); + + if (itemPool.size() > 1) { + for (size_t i = 0; i < itemPool.size(); i++) { + size_t j = Ship_Random(0, itemPool.size() - 1); + std::swap(itemPool[i], itemPool[j]); + } + } + } + } + + for (auto& [randoCheckId, isShuffled] : checksInLogic) { + copiedSaveContext.save.shipSaveInfo.rando.randoSaveChecks[randoCheckId].randoItemId = + RANDO_SAVE_CHECKS[randoCheckId].randoItemId; + copiedSaveContext.save.shipSaveInfo.rando.randoSaveChecks[randoCheckId].shuffled = isShuffled; + } + + memcpy(&gSaveContext, &copiedSaveContext, sizeof(SaveContext)); + + SPDLOG_INFO("Successfully placed all items with Glitchless logic"); +} + +} // namespace Logic + +} // namespace Rando diff --git a/mm/2s2h/Rando/Logic/Logic.h b/mm/2s2h/Rando/Logic/Logic.h index 9f4d4b4974..371255453f 100644 --- a/mm/2s2h/Rando/Logic/Logic.h +++ b/mm/2s2h/Rando/Logic/Logic.h @@ -19,6 +19,7 @@ namespace Logic { void FindReachableRegions(RandoRegionId currentRegion, std::set& reachableRegions); RandoRegionId GetRegionIdFromEntrance(s32 entrance); +void ApplyDeckscrubberLogicToSaveContext(std::vector& checkPool, std::vector& itemPool); void ApplyGlitchlessLogicToSaveContext(std::vector& checkPool, std::vector& itemPool); void ApplyNearlyNoLogicToSaveContext(std::vector& checkPool, std::vector& itemPool); void ApplyNoLogicToSaveContext(std::vector& checkPool, std::vector& itemPool); diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index a57ae24469..8095dbac43 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -4,6 +4,8 @@ #include "Rando/CheckTracker/CheckTracker.h" #include "build.h" #include "2s2h/BenGui/BenMenu.h" +#include "PresetManager/PresetManager.h" +#include "PresetManager/PresetDescriptions.h" extern "C" { #include "overlays/actors/ovl_En_Sth/z_en_sth.h" @@ -11,10 +13,9 @@ extern "C" { // TODO: This block should come from elsewhere, tied to data in Rando::StaticData::Options std::unordered_map logicOptions = { - { RO_LOGIC_GLITCHLESS, "Glitchless" }, - { RO_LOGIC_NO_LOGIC, "No Logic" }, - { RO_LOGIC_NEARLY_NO_LOGIC, "Nearly No Logic" }, - { RO_LOGIC_VANILLA, "Vanilla" }, + { RO_LOGIC_GLITCHLESS, "Glitchless" }, { RO_LOGIC_NO_LOGIC, "No Logic" }, + { RO_LOGIC_NEARLY_NO_LOGIC, "Nearly No Logic" }, { RO_LOGIC_VANILLA, "Vanilla" }, + { RO_LOGIC_DECKSCRUBBER, "Deckscrubber" }, }; std::unordered_map accessDungeonOptions = { @@ -154,10 +155,12 @@ static void DrawGeneralTab() { UIWidgets::CVarCheckbox("Container Style Matches Contents", "gRando.CSMC"); UIWidgets::Tooltip("This will make the contents of a container match the container itself. This currently only " "applies to chests and pots."); - UIWidgets::WindowButton("Check Tracker", "gWindows.CheckTracker", BenGui::mRandoCheckTrackerWindow, - { .size = ImVec2((ImGui::GetContentRegionAvail().x - 48.0f), 40.0f) }); + UIWidgets::WindowButton("Check Tracker", "gCheckTracker.Enable", BenGui::mRandoCheckTrackerWindow, + { .size = ImVec2((ImGui::GetContentRegionAvail().x - 48.0f), 40.0f), + .color = BenGui::mBenMenu->GetMenuThemeColor() }); ImGui::SameLine(); - if (UIWidgets::Button(ICON_FA_COG, { .size = ImVec2(40.0f, 40.0f) })) { + if (UIWidgets::Button(ICON_FA_COG, + { .size = ImVec2(40.0f, 40.0f), .color = BenGui::mBenMenu->GetMenuThemeColor() })) { BenGui::mRandoCheckTrackerSettingsWindow->ToggleVisibility(); } ImGui::EndChild(); @@ -724,8 +727,7 @@ static void DrawCheckFilterTab() { static void DrawHintsTab() { f32 columnWidth = ImGui::GetContentRegionAvail().x / 3 - (ImGui::GetStyle().ItemSpacing.x * 2); - f32 halfHeight = ImGui::GetContentRegionAvail().y / 2 - (ImGui::GetStyle().ItemSpacing.y * 2); - ImGui::BeginChild("randoHintsColumn1", ImVec2(columnWidth, halfHeight)); + ImGui::BeginChild("randoHintsColumn1", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y)); CVarCheckbox( "Spider House", Rando::StaticData::Options[RO_HINTS_SPIDER_HOUSES].cvar, CheckboxOptions( @@ -749,16 +751,10 @@ static void DrawHintsTab() { CVarCheckbox("Oath to Order", Rando::StaticData::Options[RO_HINTS_OATH_TO_ORDER].cvar, CheckboxOptions({ { .tooltip = "Once you have the Moon Access Requirements, talking to Skull Kid on " "the Clock Tower Rooftop will hint the location of Oath to Order" } })); - CVarCheckbox( - "General Actor Hints", "gPlaceholderBool", - CheckboxOptions({ { .disabled = true, - .disabledTooltip = "Soon you will be able to disable these. Currently hinted:\n- Bomb Shop " - "4th Item\n- Lottery\n- Great Fairy Fountains\n- Mountain Smithy" } }) - .DefaultValue(true)); - CVarCheckbox("Saria's Song", "gPlaceholderBool", - CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } })); - CVarCheckbox("Song of Soaring", "gPlaceholderBool", - CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } })); + CVarCheckbox("Transformation Masks", Rando::StaticData::Options[RO_HINTS_TRANSFORMATIONS].cvar, + CheckboxOptions({ { .tooltip = "Checking the sign near the Business Scrub in South Clock Town " + "will reveal the location of Transformation Masks.\n" + "Note: This excludes Fierce Deity." } })); CVarCheckbox( "Hookshot Location", Rando::StaticData::Options[RO_HINTS_HOOKSHOT].cvar, CheckboxOptions( @@ -767,10 +763,30 @@ static void DrawHintsTab() { ImGui::EndChild(); } +void DrawRacesTab() { + ImGui::BeginChild("randoRacesColumn1", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y)); + ImGui::Text("Apply the Deckscrubber Race Preset and then create your File."); + ImGui::PushID("DeckscrubberSet"); + if (UIWidgets::Button("Apply Preset", { .color = COLOR_GREEN })) { + PresetManager_ApplyPreset(deckScrubberJ); + } + DrawDeckScrubberDescription(); + ImGui::PopID(); + ImGui::EndChild(); +} + void Rando::RegisterMenu() { mBenMenu->AddMenuEntry("Rando", "gSettings.Menu.RandoSidebarSection"); + + // New Race Menu + mBenMenu->AddSidebarEntry("Rando", "Races", 1); + WidgetPath path = { "Rando", "Races", SECTION_COLUMN_1 }; + path.sidebarName = "Races"; + mBenMenu->AddWidget(path, "Races", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { DrawRacesTab(); }); + + // Existing Rando Menu mBenMenu->AddSidebarEntry("Rando", "General", 1); - WidgetPath path = { "Rando", "General", SECTION_COLUMN_1 }; + path = { "Rando", "General", SECTION_COLUMN_1 }; mBenMenu->AddWidget(path, "General", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { DrawGeneralTab(); }); mBenMenu->AddSidebarEntry("Rando", "Logic/Conditions", 1); path.sidebarName = "Logic/Conditions"; diff --git a/mm/2s2h/Rando/MiscBehavior/MiscBehavior.cpp b/mm/2s2h/Rando/MiscBehavior/MiscBehavior.cpp index abef387215..3a5c47e478 100644 --- a/mm/2s2h/Rando/MiscBehavior/MiscBehavior.cpp +++ b/mm/2s2h/Rando/MiscBehavior/MiscBehavior.cpp @@ -16,6 +16,7 @@ void Rando::MiscBehavior::OnFileLoad() { Rando::MiscBehavior::CheckQueueReset(); Rando::MiscBehavior::InitKaleidoItemPage(); Rando::MiscBehavior::InitOfferGetItemBehavior(); + Rando::MiscBehavior::OnRaceFileInit(); COND_HOOK(OnFlagSet, IS_RANDO, Rando::MiscBehavior::OnFlagSet); COND_HOOK(OnSceneFlagSet, IS_RANDO, Rando::MiscBehavior::OnSceneFlagSet); diff --git a/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h b/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h index d0f93f4360..9261396012 100644 --- a/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h +++ b/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h @@ -22,6 +22,7 @@ void OnFlagSet(FlagType flagType, u32 flag); void OnSceneFlagSet(s16 sceneId, FlagType flagType, u32 flag); void OnSceneInit(s16 sceneId, s8 spawnNum); void OfferTrapItem(); +void OnRaceFileInit(); } // namespace MiscBehavior diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index ee22858a87..8fade0219a 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -267,6 +267,18 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { } } + // Link's Pocket for Deckscrubber Logic + if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_DECKSCRUBBER) { + RandoItemId remainsRoll = (RandoItemId)((uint16_t)RI_REMAINS_GOHT + (rand() % 4)); + for (int i = 0; i < 2; i++) { + auto it = std::find(itemPool.begin(), itemPool.end(), remainsRoll); + if (it != itemPool.end()) { + itemPool.erase(it); + } + } + startingItems.push_back(remainsRoll); + } + // Shuffle Triforce Pieces into the Pool int piecesShuffled = 0; if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) { @@ -442,6 +454,8 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { Rando::Logic::ApplyNearlyNoLogicToSaveContext(checkPool, itemPool); } else if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_GLITCHLESS) { Rando::Logic::ApplyGlitchlessLogicToSaveContext(checkPool, itemPool); + } else if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_DECKSCRUBBER) { + Rando::Logic::ApplyDeckscrubberLogicToSaveContext(checkPool, itemPool); } else { throw std::runtime_error("Logic option not implemented: " + std::to_string(RANDO_SAVE_OPTIONS[RO_LOGIC])); diff --git a/mm/2s2h/Rando/MiscBehavior/OnRaceInit.cpp b/mm/2s2h/Rando/MiscBehavior/OnRaceInit.cpp new file mode 100644 index 0000000000..c233f03ec3 --- /dev/null +++ b/mm/2s2h/Rando/MiscBehavior/OnRaceInit.cpp @@ -0,0 +1,22 @@ +#include "MiscBehavior.h" +#include "PresetManager/PresetManager.h" + +extern "C" { +#include "variables.h" +} + +static bool isRaceInitialized = false; + +void Rando::MiscBehavior::OnRaceFileInit() { + COND_HOOK(OnSaveLoad, RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_DECKSCRUBBER, + [](s16 fileNum) { isRaceInitialized = false; }); + + COND_HOOK(OnSceneInit, IS_RANDO, [](s8 sceneId, s8 spawnNum) { + if (!isRaceInitialized) { + if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_DECKSCRUBBER) { + PresetManager_ApplyPreset(deckScrubberJ); + } + isRaceInitialized = true; + } + }); +} \ No newline at end of file diff --git a/mm/2s2h/Rando/Rando.cpp b/mm/2s2h/Rando/Rando.cpp index 4140756e49..f4471e00c1 100644 --- a/mm/2s2h/Rando/Rando.cpp +++ b/mm/2s2h/Rando/Rando.cpp @@ -38,3 +38,13 @@ RandoCheckId Rando::FindItemPlacement(RandoItemId randoItemId) { return RC_UNKNOWN; } + +std::vector Rando::FindMultiItemPlacement(RandoItemId randoItemId) { + std::vector itemPlacements; + for (auto& [randocheckId, check] : Rando::StaticData::Checks) { + if (RANDO_SAVE_CHECKS[randocheckId].randoItemId == randoItemId) { + itemPlacements.push_back(randocheckId); + } + } + return itemPlacements; +} diff --git a/mm/2s2h/Rando/Rando.h b/mm/2s2h/Rando/Rando.h index 4f592b0c5f..7f82785663 100644 --- a/mm/2s2h/Rando/Rando.h +++ b/mm/2s2h/Rando/Rando.h @@ -24,6 +24,7 @@ RandoItemId CurrentJunkItem(); bool IsItemObtainable(RandoItemId randoItemId, RandoCheckId randoCheckId = RC_UNKNOWN); RandoItemId ConvertItem(RandoItemId randoItemId, RandoCheckId randoCheckId = RC_UNKNOWN); RandoCheckId FindItemPlacement(RandoItemId randoItemId); +std::vector FindMultiItemPlacement(RandoItemId randoItemId); void RegisterMenu(); } // namespace Rando diff --git a/mm/2s2h/Rando/StaticData/Options.cpp b/mm/2s2h/Rando/StaticData/Options.cpp index 0b8f6f0629..2ff01306c7 100644 --- a/mm/2s2h/Rando/StaticData/Options.cpp +++ b/mm/2s2h/Rando/StaticData/Options.cpp @@ -27,6 +27,7 @@ std::map Options = { RO(RO_HINTS_OATH_TO_ORDER, RO_GENERIC_OFF), RO(RO_HINTS_PURCHASEABLE, RO_GENERIC_OFF), RO(RO_HINTS_SPIDER_HOUSES, RO_GENERIC_OFF), + RO(RO_HINTS_TRANSFORMATIONS, RO_GENERIC_OFF), RO(RO_TRAP_AMOUNT, 5), RO(RO_LOGIC, RO_LOGIC_GLITCHLESS), RO(RO_MINIMUM_SKULLTULA_TOKENS, SPIDER_HOUSE_TOKENS_REQUIRED), diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h index d9bb50242c..0741db42b4 100644 --- a/mm/2s2h/Rando/Types.h +++ b/mm/2s2h/Rando/Types.h @@ -2794,6 +2794,7 @@ typedef enum { RO_HINTS_OATH_TO_ORDER, RO_HINTS_PURCHASEABLE, RO_HINTS_SPIDER_HOUSES, + RO_HINTS_TRANSFORMATIONS, RO_TRAP_AMOUNT, RO_LOGIC, RO_MINIMUM_SKULLTULA_TOKENS, @@ -2841,6 +2842,7 @@ typedef enum { RO_LOGIC_NO_LOGIC, RO_LOGIC_NEARLY_NO_LOGIC, RO_LOGIC_VANILLA, + RO_LOGIC_DECKSCRUBBER, } RandoOptionLogic; typedef enum {