diff --git a/mm/2s2h/Rando/ConvertItem.cpp b/mm/2s2h/Rando/ConvertItem.cpp index 26ba83a947..3fdbf2290c 100644 --- a/mm/2s2h/Rando/ConvertItem.cpp +++ b/mm/2s2h/Rando/ConvertItem.cpp @@ -1,4 +1,5 @@ #include "Rando/Rando.h" +#include #include "2s2h/ShipUtils.h" #include @@ -49,51 +50,8 @@ extern GetItemEntry sGetItemTable[GI_MAX - 1]; // obtain it. If not, we convert it to a junk item. // // Junk Items: -// - The list of junk items is defined below. We attempt to roll a random junk item one time, based on the RC provided, -// and if we fail, we return a blue rupee. This will still result in the "lots of blue rupees" problem, but it's better -// than _always_ converting to a blue rupee. - -static std::vector junkItems = { - // Rupees - RI_RUPEE_GREEN, - RI_RUPEE_BLUE, - RI_RUPEE_RED, - RI_RUPEE_PURPLE, - // Ammo - RI_ARROWS_10, - RI_BOMBCHU_5, - RI_BOMBS_5, - RI_DEKU_NUTS_5, - RI_DEKU_STICKS_5, - RI_MAGIC_JAR_SMALL, - // Refill - Disabling for now, maybe temporarily, maybe permanently - // RI_RED_POTION_REFILL, - // RI_GREEN_POTION_REFILL, - // RI_BLUE_POTION_REFILL, - // RI_MILK_REFILL, - // Misc - RI_RECOVERY_HEART, - RI_NONE, -}; - -// Pick a random junk item every second -RandoItemId Rando::CurrentJunkItem() { - static RandoItemId lastJunkItem = RI_UNKNOWN; - static u32 lastChosenAt = 0; - if (gPlayState != NULL && ABS(gPlayState->gameplayFrames - lastChosenAt) > 20) { - lastChosenAt = gPlayState->gameplayFrames; - lastJunkItem = RI_UNKNOWN; - } - - while (lastJunkItem == RI_UNKNOWN) { - RandoItemId randJunkItem = junkItems[rand() % junkItems.size()]; - if (Rando::IsItemObtainable(randJunkItem)) { - lastJunkItem = randJunkItem; - } - } - - return lastJunkItem; -} +// - The list of junk items is defined in JunkItem.cpp. There are 3 options for Junk selection, this is outlined in the +// tooltip bool Rando::IsItemObtainable(RandoItemId randoItemId, RandoCheckId randoCheckId) { bool hasObtainedCheck = false; diff --git a/mm/2s2h/Rando/DrawItem.cpp b/mm/2s2h/Rando/DrawItem.cpp index 73e752e4f6..bc48d0ae75 100644 --- a/mm/2s2h/Rando/DrawItem.cpp +++ b/mm/2s2h/Rando/DrawItem.cpp @@ -440,7 +440,7 @@ void Rando::DrawItem(RandoItemId randoItemId, Actor* actor) { switch (randoItemId) { case RI_JUNK: - Rando::DrawItem(Rando::CurrentJunkItem(), actor); + Rando::DrawItem(Rando::CurrentJunkItem(actor), actor); break; case RI_GREAT_BAY_SMALL_KEY: case RI_SNOWHEAD_SMALL_KEY: diff --git a/mm/2s2h/Rando/JunkItem.cpp b/mm/2s2h/Rando/JunkItem.cpp new file mode 100644 index 0000000000..4c6a738b1c --- /dev/null +++ b/mm/2s2h/Rando/JunkItem.cpp @@ -0,0 +1,241 @@ +#include "Rando/Rando.h" +#include +#include "2s2h/ShipUtils.h" + +#define FAIL_OVER_MAX 5 // Number of rand() attempts before defaulting to RI_NONE + +// clang-format off +std::vector> Rando::junkCvarMap = { + { RI_RECOVERY_HEART, "Recovery Heart", "gRando.Junk.Heart." }, + { RI_MAGIC_JAR_SMALL, "Small Magic Jar", "gRando.Junk.MagicSmall." }, + { RI_DEKU_STICKS_5, "Deku Sticks (5)", "gRando.Junk.DekuSticks." }, + { RI_DEKU_NUTS_5, "Deku Nuts (5)", "gRando.Junk.DekuNuts." }, + { RI_BOMBS_5, "Bombs (5)", "gRando.Junk.Bombs." }, + { RI_ARROWS_10, "Arrows (10)", "gRando.Junk.Arrows." }, + { RI_BOMBCHU_5, "Bombchus (5)", "gRando.Junk.Bombchus." }, + { RI_RUPEE_GREEN, "Green Rupee", "gRando.Junk.RupeeGreen." }, + { RI_RUPEE_BLUE, "Blue Rupee", "gRando.Junk.RupeeBlue." }, + { RI_RUPEE_RED, "Red Rupee", "gRando.Junk.RupeeRed." }, + { RI_RUPEE_PURPLE, "Purple Rupee", "gRando.Junk.RupeePurple." }, + { RI_NONE, "Literally Nothing", "gRando.Junk.Nothing." }, +}; + +static std::vector rupeeList = { + RI_RUPEE_GREEN, + RI_RUPEE_BLUE, + RI_RUPEE_RED, + RI_RUPEE_PURPLE, +}; + +static std::map> junkThresholdMap = { + { RI_RECOVERY_HEART, { 48, 320 } }, + { RI_MAGIC_JAR_SMALL, { 12, 96 } }, + { RI_DEKU_STICKS_5, { 3, 10 } }, + { RI_DEKU_NUTS_5, { 5, 20 } }, + { RI_BOMBS_5, { 5, 40 } }, + { RI_ARROWS_10, { 5, 50 } }, + { RI_BOMBCHU_5, { 5, 40 } }, +}; +// clang-format on + +std::vector> junkSelectionList; +Actor* currentActor; + +static bool junkInit = false; +void InitJunkOptions() { + COND_HOOK(OnSceneInit, IS_RANDO, [](s8 sceneId, s8 spawnNum) { currentActor = nullptr; }); + junkInit = true; +} + +const std::tuple& Rando::GetJunkTuple(RandoItemId id) { + for (const auto& t : Rando::junkCvarMap) { + if (std::get<0>(t) == id) { + return t; + } + } +} + +RandoItemId Rando::CurrentJunkItem(Actor* actor) { + static RandoItemId lastJunkItem = RI_UNKNOWN; + static RandoItemId selectedRupee = RI_RUPEE_GREEN; + RandoItemId currentJunkItem = RI_UNKNOWN; + static u32 lastChosenAt = 0; + static int32_t lastRupee = -1; + + uint32_t totalWeight = 0; + uint32_t weightedValue = 0; + uint32_t currentWeight = 0; + uint16_t failOver = 0; + + if (junkSelectionList.size() == 0) { + Rando::UpdateJunkOptions(); + } + + switch (CVarGetInteger("gRando.Junk.ItemType", (uint32_t)RO_JUNK_TYPE_DEFAULT)) { + case RO_JUNK_TYPE_DEFAULT: + if (gPlayState != NULL && ABS(gPlayState->gameplayFrames - lastChosenAt) > 20) { + lastChosenAt = gPlayState->gameplayFrames; + lastJunkItem = RI_UNKNOWN; + } + + while (lastJunkItem == RI_UNKNOWN) { + RandoItemId randJunkItem = std::get<0>(junkSelectionList[rand() % junkSelectionList.size()]); + if (Rando::IsItemObtainable(randJunkItem)) { + lastJunkItem = randJunkItem; + } + if (failOver >= FAIL_OVER_MAX) { + randJunkItem = RI_NONE; + lastJunkItem = randJunkItem; + } + failOver++; + } + break; + case RO_JUNK_TYPE_WEIGHTED: + if (currentActor == actor) { + break; + } + + currentActor = actor; + for (auto& [itemId, weight, threshold] : junkSelectionList) { + if (weight == 0) { + continue; + } + if (!Rando::IsItemObtainable(itemId)) { + continue; + } + + totalWeight += weight; + } + if (totalWeight == 0) { + lastJunkItem = RI_NONE; + break; + } + + weightedValue = rand() % totalWeight; + for (auto& [itemId, weight, threshold] : junkSelectionList) { + currentWeight += weight; + if (weightedValue < currentWeight) { + lastJunkItem = itemId; + break; + } + } + break; + case RO_JUNK_TYPE_SUPPLY: + for (auto& [itemId, weight, threshold] : junkSelectionList) { + lastJunkItem = RI_UNKNOWN; + switch (itemId) { + case RI_RECOVERY_HEART: + if (gSaveContext.save.saveInfo.playerData.health <= threshold * 16) { + lastJunkItem = RI_RECOVERY_HEART; + break; + } + break; + case RI_MAGIC_JAR_SMALL: + if (Rando::IsItemObtainable(itemId)) { + if (gSaveContext.save.saveInfo.playerData.magic <= threshold) { + lastJunkItem = RI_MAGIC_JAR_SMALL; + break; + } + } + break; + case RI_DEKU_STICKS_5: + if (AMMO(ITEM_DEKU_STICK) <= threshold) { + lastJunkItem = RI_DEKU_STICKS_5; + break; + } + break; + case RI_DEKU_NUTS_5: + if (AMMO(ITEM_DEKU_NUT) <= threshold) { + lastJunkItem = RI_DEKU_NUTS_5; + break; + } + break; + case RI_BOMBS_5: + if (Rando::IsItemObtainable(itemId)) { + if (AMMO(ITEM_BOMB) <= threshold) { + lastJunkItem = RI_BOMBS_5; + break; + } + } + break; + case RI_ARROWS_10: + if (Rando::IsItemObtainable(itemId)) { + if (AMMO(ITEM_BOW) <= threshold) { + lastJunkItem = RI_ARROWS_10; + break; + } + } + break; + case RI_BOMBCHU_5: + if (Rando::IsItemObtainable(itemId)) { + if (AMMO(ITEM_BOMBCHU) <= threshold) { + lastJunkItem = RI_BOMBCHU_5; + break; + } + } + break; + default: + break; + } + if (lastJunkItem == RI_UNKNOWN) { + if (lastRupee != gSaveContext.save.saveInfo.playerData.rupees) { + selectedRupee = rupeeList[rand() % rupeeList.size()]; + lastRupee = gSaveContext.save.saveInfo.playerData.rupees; + } + lastJunkItem = selectedRupee; + break; + } + break; + } + break; + default: + break; + } + + if (lastJunkItem == RI_UNKNOWN) { + lastJunkItem = RI_NONE; + } + + return lastJunkItem; +} + +void Rando::UpdateJunkOptions() { + if (!junkInit) { + InitJunkOptions(); + } + junkSelectionList.clear(); + + for (const auto& [itemId, itemName, cvar] : junkCvarMap) { + if (!CVarGetInteger(JUNK_CVAR(itemId, "Enabled"), 1)) { + continue; + } + std::tuple junkItem; + + junkItem = std::make_tuple( + itemId, CVarGetInteger(JUNK_CVAR(itemId, "Weight"), 10), + CVarGetInteger(JUNK_CVAR(itemId, "Threshold"), Rando::GetJunkThresholds(itemId, "Default"))); + + junkSelectionList.push_back(junkItem); + } + + if (junkSelectionList.size() == 0) { + junkSelectionList.push_back({ RI_NONE, 100, 0 }); + } +} + +uint32_t Rando::GetJunkThresholds(RandoItemId randoItemId, std::string entry) { + uint16_t value; + auto findMax = junkThresholdMap.find(randoItemId); + if (findMax != junkThresholdMap.end()) { + if (entry == "Default") { + value = findMax->second.first; + } else { + value = findMax->second.second; + } + if (randoItemId == RI_RECOVERY_HEART) { + value = value / 16; + } + return value; + } + return 500; +} diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index 4f489d03ad..7d17d679d7 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -28,6 +28,12 @@ std::unordered_map accessTrialsOptions = { { RO_ACCESS_TRIALS_OPEN, "Open" }, }; +std::unordered_map junkTypeOptions = { + { RO_JUNK_TYPE_DEFAULT, "Default" }, + { RO_JUNK_TYPE_WEIGHTED, "Weighted" }, + { RO_JUNK_TYPE_SUPPLY, "Low Resource" }, +}; + std::vector incompatibleWithVanilla = { RO_SHUFFLE_BOSS_SOULS, RO_SHUFFLE_SWIM, @@ -273,13 +279,30 @@ static void DrawShufflesTab() { } static void DrawItemsTab() { - f32 columnWidth = ImGui::GetContentRegionAvail().x / 3 - (ImGui::GetStyle().ItemSpacing.x * 2); + f32 columnWidth = ImGui::GetContentRegionAvail().x / 2 - (ImGui::GetStyle().ItemSpacing.x * 2); ImGui::BeginChild("randoItemsColumn1", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y)); CVarCheckbox("Shuffle Swim", Rando::StaticData::Options[RO_SHUFFLE_SWIM].cvar, CheckboxOptions({ { .tooltip = "Shuffles the ability to Swim, entering the Swim state or submerging\n" "into deep water will respawn Link.", .disabled = IncompatibleWithLogicSetting(RO_SHUFFLE_SWIM), .disabledTooltip = "Incompatible with current Logic Setting" } })); + CVarCheckbox( + "Plentiful Items", Rando::StaticData::Options[RO_PLENTIFUL_ITEMS].cvar, + CheckboxOptions({ { .tooltip = "Major items, masks, and keys will have an extra copy added to the item pool. \n" + "Lesser items, stray fairies, and skulltula tokens will have a chance for an " + "extra copy to be added to the item pool.", + .disabled = IncompatibleWithLogicSetting(RO_PLENTIFUL_ITEMS), + .disabledTooltip = "Incompatible with current Logic Setting" } })); + CVarCheckbox( + "Boss Souls", Rando::StaticData::Options[RO_SHUFFLE_BOSS_SOULS].cvar, + CheckboxOptions({ { .tooltip = "Adds the \"souls\" of the five bosses to the item pool. Boss Souls are items " + "that must be found in order for their corresponding boss to spawn.", + .disabled = IncompatibleWithLogicSetting(RO_SHUFFLE_BOSS_SOULS), + .disabledTooltip = "Incompatible with current Logic Setting" } })); + CVarCheckbox("Enemy Drops", Rando::StaticData::Options[RO_SHUFFLE_ENEMY_DROPS].cvar, + CheckboxOptions({ { .tooltip = "Shuffles the first drop from a non Boss Enemy." } })); + CVarCheckbox("Enemy Souls", "gPlaceholderBool", + CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } })); CVarCheckbox("Deku Stick Bag", "gPlaceholderBool", CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } })); CVarCheckbox("Deku Nut Bag", "gPlaceholderBool", @@ -301,26 +324,6 @@ static void DrawItemsTab() { ImGui::EndChild(); ImGui::SameLine(); ImGui::BeginChild("randoItemsColumn2", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y)); - CVarCheckbox( - "Plentiful Items", Rando::StaticData::Options[RO_PLENTIFUL_ITEMS].cvar, - CheckboxOptions({ { .tooltip = "Major items, masks, and keys will have an extra copy added to the item pool. \n" - "Lesser items, stray fairies, and skulltula tokens will have a chance for an " - "extra copy to be added to the item pool.", - .disabled = IncompatibleWithLogicSetting(RO_PLENTIFUL_ITEMS), - .disabledTooltip = "Incompatible with current Logic Setting" } })); - CVarCheckbox( - "Boss Souls", Rando::StaticData::Options[RO_SHUFFLE_BOSS_SOULS].cvar, - CheckboxOptions({ { .tooltip = "Adds the \"souls\" of the five bosses to the item pool. Boss Souls are items " - "that must be found in order for their corresponding boss to spawn.", - .disabled = IncompatibleWithLogicSetting(RO_SHUFFLE_BOSS_SOULS), - .disabledTooltip = "Incompatible with current Logic Setting" } })); - CVarCheckbox("Enemy Drops", Rando::StaticData::Options[RO_SHUFFLE_ENEMY_DROPS].cvar, - CheckboxOptions({ { .tooltip = "Shuffles the first drop from a non Boss Enemy." } })); - CVarCheckbox("Enemy Souls", "gPlaceholderBool", - CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } })); - ImGui::EndChild(); - ImGui::SameLine(); - ImGui::BeginChild("randoItemsColumn3", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y)); CVarCheckbox("Shuffle Traps", Rando::StaticData::Options[RO_SHUFFLE_TRAPS].cvar, CheckboxOptions({ { .tooltip = "Ice Trap time!" } })); CVarSliderInt( @@ -682,6 +685,91 @@ static void DrawHintsTab() { ImGui::EndChild(); } +static void DrawJunkTab() { + ImGui::BeginChild("randoItemsColumn2", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y)); + ImGui::SeparatorText("Junk Item Options"); + if (ImGui::BeginTable("JunkCombo", 2)) { + ImGui::TableSetupColumn("Combobox", ImGuiTableColumnFlags_WidthFixed, (ImGui::GetContentRegionAvail().x / 3)); + ImGui::TableSetupColumn("Tooltips"); + ImGui::TableNextColumn(); + UIWidgets::PushStyleCombobox(); + UIWidgets::CVarCombobox("##junkOptions", "gRando.Junk.ItemType", &junkTypeOptions, + UIWidgets::ComboboxOptions({}).LabelPosition(LabelPosition::None)); + UIWidgets::PopStyleCombobox(); + + ImGui::TableNextColumn(); + ImGui::TextWrapped("Default - Junk Item will cycle through selected junk options."); + ImGui::TextWrapped("Weighted - Junk will be weighted, higher weight equals better chance."); + ImGui::TextWrapped("Low Resource - Junk is determined by resource thresholds."); + ImGui::EndTable(); + } + + ImGui::SeparatorText("Customize Junk Items"); + ImGui::SameLine(ImGui::GetContentRegionMax().x - (ImGui::CalcTextSize("Enabled All & Disable All").x * 1.5f)); + if (UIWidgets::Button("Enabled All", { .size = ImVec2(0, 0), .color = UIWidgets::Colors::Green })) { + for (auto& [itemId, itemName, cvar] : Rando::junkCvarMap) { + CVarSetInteger(JUNK_CVAR(itemId, "Enabled"), 1); + } + Rando::UpdateJunkOptions(); + } + ImGui::SameLine(); + if (UIWidgets::Button("Disable All", { .size = ImVec2(0, 0), .color = UIWidgets::Colors::Red })) { + for (auto& [itemId, itemName, cvar] : Rando::junkCvarMap) { + CVarSetInteger(JUNK_CVAR(itemId, "Enabled"), 0); + } + Rando::UpdateJunkOptions(); + } + if (ImGui::BeginTable("Junk Option List", 3)) { + ImGui::TableSetupColumn("Enabled"); + ImGui::TableSetupColumn("Weight"); + ImGui::TableSetupColumn("Threshold"); + ImGui::TableHeadersRow(); + uint32_t junkOptionIndex = 0; + for (auto& [itemId, itemName, cvar] : Rando::junkCvarMap) { + uint16_t defaultThreshold = Rando::GetJunkThresholds(itemId, "Default"); + uint32_t maxThreshold = Rando::GetJunkThresholds(itemId, "Max"); + ImGui::PushID(junkOptionIndex); + ImGui::TableNextColumn(); + if (CVarCheckbox(itemName, JUNK_CVAR(itemId, "Enabled"), { .defaultValue = true })) { + Rando::UpdateJunkOptions(); + } + ImGui::TableNextColumn(); + ImGui::BeginDisabled(CVarGetInteger("gRando.Junk.ItemType", 0) != RO_JUNK_TYPE_WEIGHTED); + if (CVarSliderInt("Weight", JUNK_CVAR(itemId, "Weight"), + IntSliderOptions({ {} }) + .LabelPosition(LabelPosition::None) + .Color(UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5))) + .Format("%i") + .Min(0) + .Max(100) + .DefaultValue(10))) { + Rando::UpdateJunkOptions(); + } + ImGui::EndDisabled(); + ImGui::TableNextColumn(); + ImGui::BeginDisabled(CVarGetInteger("gRando.Junk.ItemType", 0) != RO_JUNK_TYPE_SUPPLY); + if (itemId < RI_RUPEE_BLUE && itemId != RI_NONE) { + if (CVarSliderInt("Threshold", JUNK_CVAR(itemId, "Threshold"), + IntSliderOptions({ {} }) + .LabelPosition(LabelPosition::None) + .Color(UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5))) + .Format("%i") + .Min(0) + .Max(maxThreshold) + .DefaultValue(defaultThreshold))) { + Rando::UpdateJunkOptions(); + } + } + ImGui::EndDisabled(); + ImGui::PopID(); + junkOptionIndex++; + } + ImGui::EndTable(); + } + + ImGui::EndChild(); +} + void Rando::RegisterMenu() { mBenMenu->AddMenuEntry("Rando", "gSettings.Menu.RandoSidebarSection"); mBenMenu->AddSidebarEntry("Rando", "General", 1); @@ -711,6 +799,9 @@ void Rando::RegisterMenu() { mBenMenu->AddSidebarEntry("Rando", "Hints", 1); path.sidebarName = "Hints"; mBenMenu->AddWidget(path, "Hints", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { DrawHintsTab(); }); + mBenMenu->AddSidebarEntry("Rando", "Junk Options", 1); + path.sidebarName = "Junk Options"; + mBenMenu->AddWidget(path, "Junk Options", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { DrawJunkTab(); }); } static RegisterMenuInitFunc initFunc(Rando::RegisterMenu); diff --git a/mm/2s2h/Rando/Rando.h b/mm/2s2h/Rando/Rando.h index 4f592b0c5f..1b7b23a797 100644 --- a/mm/2s2h/Rando/Rando.h +++ b/mm/2s2h/Rando/Rando.h @@ -14,16 +14,26 @@ #define RANDO_STARTING_ITEMS_DEFAULT \ "109,126,91,146" // This includes a Progressive Sword, Hero's Shield, Ocarina of Time, and Song of Time +#define JUNK_CVAR_BASE(itemId) (std::get<2>(Rando::GetJunkTuple(itemId))) +#define JUNK_CVAR(itemId, suffix) (std::string(std::get<2>(Rando::GetJunkTuple(itemId))) + (suffix)).c_str() + namespace Rando { void Init(); void DrawItem(RandoItemId randoItemId, Actor* actor = nullptr); void GiveItem(RandoItemId randoItemId); void RemoveItem(RandoItemId randoItemId); -RandoItemId CurrentJunkItem(); bool IsItemObtainable(RandoItemId randoItemId, RandoCheckId randoCheckId = RC_UNKNOWN); RandoItemId ConvertItem(RandoItemId randoItemId, RandoCheckId randoCheckId = RC_UNKNOWN); RandoCheckId FindItemPlacement(RandoItemId randoItemId); + +// Junk Items +RandoItemId CurrentJunkItem(Actor* actor = nullptr); +void UpdateJunkOptions(); +uint32_t GetJunkThresholds(RandoItemId randoItemId, std::string entry); +extern std::vector> junkCvarMap; +extern const std::tuple& GetJunkTuple(RandoItemId id); + void RegisterMenu(); } // namespace Rando diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h index 297749727a..984ca89a7b 100644 --- a/mm/2s2h/Rando/Types.h +++ b/mm/2s2h/Rando/Types.h @@ -2850,6 +2850,12 @@ typedef enum { RO_ACCESS_TRIALS_OPEN, } RandoOptionAccessTrials; +typedef enum { + RO_JUNK_TYPE_DEFAULT, + RO_JUNK_TYPE_WEIGHTED, + RO_JUNK_TYPE_SUPPLY, +} RandoOptionJunkTypes; + typedef enum { RANDO_INF_PURCHASED_BEANS_FROM_SOUTHERN_SWAMP_SCRUB, RANDO_INF_PURCHASED_BOMB_BAG_FROM_GORON_VILLAGE_SCRUB,