summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett Cox <garrettjcox@gmail.com>2026-07-23 10:28:55 -0500
committerGitHub <noreply@github.com>2026-07-23 10:28:55 -0500
commitb690db6fdfe1d06a58ba1c36cdfff8f9e2a8c44d (patch)
treeefe8b58d9a5d10683722750ca8a1b3071f096dd3
parent4ce76f3de288fde206140b5fe15c0a7a6dab60ae (diff)
Shuffle butterflies (#1770)
-rw-r--r--mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp1
-rw-r--r--mm/2s2h/Rando/ActorBehavior/ActorBehavior.h1
-rw-r--r--mm/2s2h/Rando/ActorBehavior/EnButte.cpp162
-rw-r--r--mm/2s2h/Rando/CheckTracker/CheckTracker.cpp1
-rw-r--r--mm/2s2h/Rando/Logic/GeneratePools.cpp5
-rw-r--r--mm/2s2h/Rando/Logic/Regions/North.cpp9
-rw-r--r--mm/2s2h/Rando/Logic/Regions/South.cpp4
-rw-r--r--mm/2s2h/Rando/Logic/Regions/TerminaField.cpp7
-rw-r--r--mm/2s2h/Rando/Logic/Regions/West.cpp5
-rw-r--r--mm/2s2h/Rando/Menu.cpp1
-rw-r--r--mm/2s2h/Rando/StaticData/Checks.cpp27
-rw-r--r--mm/2s2h/Rando/StaticData/Options.cpp1
-rw-r--r--mm/2s2h/Rando/Types.h28
13 files changed, 252 insertions, 0 deletions
diff --git a/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp
index 1eaf0b927..f31d40759 100644
--- a/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp
@@ -60,6 +60,7 @@ void Rando::ActorBehavior::OnFileLoad() {
Rando::ActorBehavior::InitEnBjtBehavior();
Rando::ActorBehavior::InitEnBomBowlManBehavior();
Rando::ActorBehavior::InitEnBoxBehavior();
+ Rando::ActorBehavior::InitEnButteBehavior();
Rando::ActorBehavior::InitEnCowBehavior();
Rando::ActorBehavior::InitEnDaiBehavior();
Rando::ActorBehavior::InitEnDnhBehavior();
diff --git a/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h
index 0f9ca1d54..0b46a56d7 100644
--- a/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h
+++ b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h
@@ -29,6 +29,7 @@ void InitEnBalBehavior();
void InitEnBjtBehavior();
void InitEnBomBowlManBehavior();
void InitEnBoxBehavior();
+void InitEnButteBehavior();
void InitEnCowBehavior();
void InitEnDaiBehavior();
void InitEnDnhBehavior();
diff --git a/mm/2s2h/Rando/ActorBehavior/EnButte.cpp b/mm/2s2h/Rando/ActorBehavior/EnButte.cpp
new file mode 100644
index 000000000..1413a1000
--- /dev/null
+++ b/mm/2s2h/Rando/ActorBehavior/EnButte.cpp
@@ -0,0 +1,162 @@
+#include "ActorBehavior.h"
+#include <libultraship/bridge/consolevariablebridge.h>
+
+#include "2s2h/CustomItem/CustomItem.h"
+#include "2s2h/Rando/Rando.h"
+#include "2s2h/ShipInit.hpp"
+#include "2s2h/ObjectExtension/ActorListIndex.h"
+#include "2s2h/ObjectExtension/ObjectExtension.h"
+#include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h"
+#include "assets/2s2h_assets.h"
+#include <spdlog/spdlog.h>
+
+extern "C" {
+#include "variables.h"
+#include "overlays/actors/ovl_Obj_Mure/z_obj_mure.h"
+
+void ObjMure_CulledState(ObjMure*, PlayState*);
+void ObjMure_ActiveState(ObjMure*, PlayState*);
+void EnButte_Draw(Actor* thisx, PlayState* play);
+void EnButte_Update(Actor* thisx, PlayState* play);
+}
+
+typedef enum {
+ BUTTERFLY_CATCH_IDLE,
+ BUTTERFLY_CATCH_APPROACHING,
+} ButterflyCatchPhase;
+
+struct ButterflyCatchState {
+ ButterflyCatchPhase phase = BUTTERFLY_CATCH_IDLE;
+ // Our own tracked facing, independent of shape.rot.y. Vanilla's own turning (func_8091C6B4, run every
+ // frame via EnButte_Update) keeps nudging shape.rot.y towards its own ambient-flight heading, so smoothing
+ // from the live shape.rot.y value fights that every frame and never settles. Smoothing our own value and
+ // writing it into shape.rot.y afterwards avoids that tug-of-war entirely.
+ s16 facingYaw = 0;
+};
+static ObjectExtension::Register<ButterflyCatchState> ButterflyCatchStateRegister;
+
+struct ObjMureLastActionFunc {
+ void* value = nullptr;
+};
+static ObjectExtension::Register<ObjMureLastActionFunc> ObjMureLastActionFuncRegister;
+
+template <typename T> T& GetOrCreateExtension(const void* object) {
+ T* existing = ObjectExtension::GetInstance().Get<T>(object);
+ if (existing != nullptr) {
+ return *existing;
+ }
+ ObjectExtension::GetInstance().Set<T>(object, T{});
+ return *ObjectExtension::GetInstance().Get<T>(object);
+}
+
+// clang-format off
+std::map<std::tuple<s16, u8, u8>, std::tuple<RandoCheckId, u8>> butterflyMap = {
+ { { SCENE_00KEIKOKU, 0, 171 }, { RC_TERMINA_FIELD_BUTTERFLY_01, 2 } },
+ { { SCENE_30GYOSON, 0, 64 }, { RC_GREAT_BAY_COAST_BUTTERFLY_01, 2 } },
+ { { SCENE_30GYOSON, 0, 133 }, { RC_GREAT_BAY_COAST_BUTTERFLY_01, 2 } }, // Post-Great Bay Temple clear (4 spawn here, only first 2 are checks)
+ { { SCENE_10YUKIYAMANOMURA2, 0, 32 }, { RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_01, 4 } },
+ { { SCENE_10YUKIYAMANOMURA2, 0, 38 }, { RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_01, 4 } }, // First-time entrance cutscene layer
+ { { SCENE_10YUKIYAMANOMURA2, 0, 33 }, { RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_05, 5 } },
+ { { SCENE_10YUKIYAMANOMURA2, 0, 39 }, { RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_05, 5 } }, // First-time entrance cutscene layer
+ { { SCENE_KAKUSIANA, 0, 2 }, { RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_BUTTERFLY_01, 2 } },
+ { { SCENE_KAKUSIANA, 10, 6 }, { RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_01, 3 } }, // Adjusted for Termina Field cow grotto below
+ { { SCENE_KAKUSIANA, 12, 4 }, { RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_01, 4 } },
+};
+// clang-format on
+
+void DrawButterfly(Actor* actor, PlayState* play) {
+ EnButte_Draw(actor, play);
+
+ OPEN_DISPS(gPlayState->state.gfxCtx);
+ RandoCheckId randoCheckId = Rando::ActorBehavior::GetObjectRandoCheckId(actor);
+ Matrix_Scale(8.0f, 8.0f, 8.0f, MTXMODE_APPLY);
+ Rando::DrawItem(Rando::ConvertItem(RANDO_SAVE_CHECKS[randoCheckId].randoItemId, randoCheckId), randoCheckId, actor);
+ CLOSE_DISPS(gPlayState->state.gfxCtx);
+}
+
+// Runs the actual vanilla update (flight AI, wing animation, gravity, collider sync) every frame so a butterfly
+// looks and moves exactly like normal until it's lured in, then eases its position toward the stick tip on top.
+// The moment it gets close enough it instantly grants the check and goes back to idle, where vanilla's own
+// home-seeking behavior (func_8091C794) carries it back home on its own.
+void EnButte_UpdateRandoCheck(Actor* thisx, PlayState* play) {
+ EnButte_Update(thisx, play);
+
+ RandoCheckId randoCheckId = Rando::ActorBehavior::GetObjectRandoCheckId(thisx);
+ RandoSaveCheck& randoSaveCheck = RANDO_SAVE_CHECKS[randoCheckId];
+ if (randoSaveCheck.cycleObtained) {
+ thisx->draw = EnButte_Draw;
+ thisx->update = EnButte_Update;
+ return;
+ }
+
+ ButterflyCatchState& state = GetOrCreateExtension<ButterflyCatchState>(thisx);
+ Player* player = GET_PLAYER(play);
+ bool holdingStick = player->heldItemAction == PLAYER_IA_DEKU_STICK;
+
+ if (state.phase == BUTTERFLY_CATCH_IDLE) {
+ if (randoSaveCheck.eligible || !holdingStick || thisx->xzDistToPlayer > 240.0f ||
+ fabsf(thisx->playerHeightRel) > 400.0f) {
+ return;
+ }
+ state.phase = BUTTERFLY_CATCH_APPROACHING;
+ state.facingYaw = thisx->shape.rot.y;
+ } else if (!holdingStick) {
+ state.phase = BUTTERFLY_CATCH_IDLE;
+ return;
+ }
+
+ Vec3f target = player->meleeWeaponInfo[0].tip;
+
+ f32 dist = Math_Vec3f_DistXYZ(&thisx->world.pos, &target);
+ f32 step = CLAMP(dist * 0.06f, 1.0f, 2.2f);
+ Math_Vec3f_StepTo(&thisx->world.pos, &target, step);
+
+ s16 yawTowardsTarget = BINANG_ROT180(Math_Vec3f_Yaw(&thisx->world.pos, &target));
+ Math_SmoothStepToS(&state.facingYaw, yawTowardsTarget, 6, 0x7D0, 1);
+ thisx->shape.rot.y = state.facingYaw;
+
+ if (dist <= 10.0f) {
+ randoSaveCheck.eligible = true;
+ state.phase = BUTTERFLY_CATCH_IDLE;
+ }
+}
+
+void Rando::ActorBehavior::InitEnButteBehavior() {
+ bool shouldRegister = IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_BUTTERFLIES] != RO_GENERIC_OFF;
+
+ COND_ID_HOOK(OnActorUpdate, ACTOR_OBJ_MURE, shouldRegister, [](Actor* actor) {
+ ObjMure* objMure = (ObjMure*)actor;
+ ObjMureLastActionFunc& lastActionFuncState = GetOrCreateExtension<ObjMureLastActionFunc>(actor);
+
+ if (lastActionFuncState.value == (void*)ObjMure_CulledState &&
+ (void*)objMure->actionFunc == (void*)ObjMure_ActiveState) {
+ auto it =
+ butterflyMap.find({ gPlayState->sceneId, gPlayState->roomCtx.curRoom.num, GetActorListIndex(actor) });
+ if (it != butterflyMap.end()) {
+ RandoCheckId startingRandoCheckId = std::get<0>(it->second);
+ u8 butterflyCount = std::get<1>(it->second);
+ // Adjust for Termina Field cow grotto
+ if (gPlayState->sceneId == SCENE_KAKUSIANA && gPlayState->roomCtx.curRoom.num == 10 &&
+ gSaveContext.respawn[RESPAWN_MODE_UNK_3].data == 31) {
+ startingRandoCheckId =
+ static_cast<RandoCheckId>(startingRandoCheckId + (RC_TERMINA_FIELD_COW_GROTTO_BUTTERFLY_01 -
+ RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_01));
+ }
+ for (u8 i = 0; i < butterflyCount; i++) {
+ RandoCheckId randoCheckId = static_cast<RandoCheckId>(static_cast<int>(startingRandoCheckId) + i);
+ if (!RANDO_SAVE_CHECKS[randoCheckId].shuffled || RANDO_SAVE_CHECKS[randoCheckId].cycleObtained ||
+ objMure->children[i] == nullptr) {
+ continue;
+ }
+ Actor* butterfly = objMure->children[i];
+ SetObjectRandoCheckId(butterfly, randoCheckId);
+ butterfly->draw = DrawButterfly;
+ butterfly->update = EnButte_UpdateRandoCheck;
+ // Clear the "golden butterfly" params bit so vanilla's own stick-attraction never triggers
+ butterfly->params &= ~1;
+ }
+ }
+ }
+ lastActionFuncState.value = (void*)objMure->actionFunc;
+ });
+}
diff --git a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp
index fc7bedf36..050ea90f5 100644
--- a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp
+++ b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp
@@ -120,6 +120,7 @@ std::set<RandoCheckType> checkTypeFilter;
std::vector<const char*> checkTypeIconList = {
/*RCTYPE_UNKNOWN*/ gItemIconBombersNotebookTex,
/*RCTYPE_BARREL*/ gBarrelTrackerIcon,
+ /*RCTYPE_BUTTERFLY*/ gItemIconDekuStickTex,
/*RCTYPE_CHEST*/ gChestTrackerIcon,
/*RCTYPE_COW*/ gItemIconRomaniMaskTex,
/*RCTYPE_CRATE*/ gCrateTrackerIcon,
diff --git a/mm/2s2h/Rando/Logic/GeneratePools.cpp b/mm/2s2h/Rando/Logic/GeneratePools.cpp
index ccc38b24d..08dcd6fe5 100644
--- a/mm/2s2h/Rando/Logic/GeneratePools.cpp
+++ b/mm/2s2h/Rando/Logic/GeneratePools.cpp
@@ -85,6 +85,11 @@ void GeneratePools(RandoSaveInfo& saveInfo, std::vector<RandoCheckId>& checkPool
continue;
}
+ if (randoStaticCheck.randoCheckType == RCTYPE_BUTTERFLY &&
+ saveInfo.randoSaveOptions[RO_SHUFFLE_BUTTERFLIES] == RO_GENERIC_NO) {
+ continue;
+ }
+
if (randoStaticCheck.randoCheckType == RCTYPE_FREESTANDING &&
saveInfo.randoSaveOptions[RO_SHUFFLE_FREESTANDING_ITEMS] == RO_GENERIC_NO) {
continue;
diff --git a/mm/2s2h/Rando/Logic/Regions/North.cpp b/mm/2s2h/Rando/Logic/Regions/North.cpp
index aa827aa76..df559e67d 100644
--- a/mm/2s2h/Rando/Logic/Regions/North.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/North.cpp
@@ -264,6 +264,15 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_MOUNTAIN_VILLAGE_SPRING_GRASS_28, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]),
CHECK(RC_MOUNTAIN_VILLAGE_SPRING_GRASS_29, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]),
CHECK(RC_MOUNTAIN_VILLAGE_SPRING_GRASS_30, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]),
+ CHECK(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_01, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_02, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_03, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_04, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_05, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_06, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_07, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_08, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_09, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] && HAS_ITEM(ITEM_DEKU_STICK)),
CHECK(RC_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_01, CanKillEnemy(ACTOR_OBJ_SNOWBALL)),
CHECK(RC_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_02, CanKillEnemy(ACTOR_OBJ_SNOWBALL)),
CHECK(RC_MOUNTAIN_VILLAGE_LARGE_SNOWBALL_03, (FIRST_DAY() || SECOND_DAY()) && CanKillEnemy(ACTOR_OBJ_SNOWBALL)), // Goron Elder inside on Final Day
diff --git a/mm/2s2h/Rando/Logic/Regions/South.cpp b/mm/2s2h/Rando/Logic/Regions/South.cpp
index 7bca9c8b8..323ae3276 100644
--- a/mm/2s2h/Rando/Logic/Regions/South.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/South.cpp
@@ -70,6 +70,10 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_GRASS_10, true),
CHECK(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_GRASS_11, true),
CHECK(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_GRASS_12, true),
+ CHECK(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_01, HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_02, HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_03, HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_04, HAS_ITEM(ITEM_DEKU_STICK)),
// TODO: Bean salesman check
},
.exits = { // TO FROM
diff --git a/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp b/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp
index 79276189a..0c1df9df8 100644
--- a/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp
@@ -112,6 +112,9 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_TERMINA_FIELD_COW_GROTTO_GRASS_70, true),
CHECK(RC_TERMINA_FIELD_COW_GROTTO_GRASS_71, true),
CHECK(RC_TERMINA_FIELD_COW_GROTTO_GRASS_72, true),
+ CHECK(RC_TERMINA_FIELD_COW_GROTTO_BUTTERFLY_01, HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_TERMINA_FIELD_COW_GROTTO_BUTTERFLY_02, HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_TERMINA_FIELD_COW_GROTTO_BUTTERFLY_03, HAS_ITEM(ITEM_DEKU_STICK)),
CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE && CanKillEnemy(ACTOR_EN_BEE)), // In a beehive
CHECK(RC_TERMINA_FIELD_COW_GROTTO_BEEHIVE, CAN_USE_PROJECTILE),
},
@@ -156,6 +159,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_GRASS_03, true),
CHECK(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_GRASS_04, true),
CHECK(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_GRASS_05, true),
+ CHECK(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_BUTTERFLY_01, HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_BUTTERFLY_02, HAS_ITEM(ITEM_DEKU_STICK)),
CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE && CanKillEnemy(ACTOR_EN_BEE)), // In a beehive
CHECK(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_BEEHIVE, CAN_USE_PROJECTILE),
},
@@ -515,6 +520,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_EENO, CanKillEnemy(ACTOR_EN_SNOWMAN) && IS_NIGHT()), // Night only
CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT)),
CHECK(RC_ENEMY_DROP_TAKKURI, CanKillEnemy(ACTOR_EN_THIEFBIRD)),
+ CHECK(RC_TERMINA_FIELD_BUTTERFLY_01, HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_TERMINA_FIELD_BUTTERFLY_02, HAS_ITEM(ITEM_DEKU_STICK)),
},
.exits = { // TO FROM
EXIT(ENTRANCE(GROTTOS, 0), ENTRANCE(TERMINA_FIELD, 0), CAN_USE_EXPLOSIVE || CAN_BE_GORON), // Gossip Stone #3
diff --git a/mm/2s2h/Rando/Logic/Regions/West.cpp b/mm/2s2h/Rando/Logic/Regions/West.cpp
index a2585095b..bf9d932e2 100644
--- a/mm/2s2h/Rando/Logic/Regions/West.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/West.cpp
@@ -92,6 +92,9 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_GRASS_70, true),
CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_GRASS_71, true),
CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_GRASS_72, true),
+ CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_01, HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_02, HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_03, HAS_ITEM(ITEM_DEKU_STICK)),
CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE && CanKillEnemy(ACTOR_EN_BEE)), // In a beehive
CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_BEEHIVE, CAN_USE_PROJECTILE),
},
@@ -132,6 +135,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_GREAT_BAY_COAST_GRASS_03, true),
CHECK(RC_GREAT_BAY_COAST_GRASS_04, true),
CHECK(RC_GREAT_BAY_COAST_GRASS_05, true),
+ CHECK(RC_GREAT_BAY_COAST_BUTTERFLY_01, HAS_ITEM(ITEM_DEKU_STICK)),
+ CHECK(RC_GREAT_BAY_COAST_BUTTERFLY_02, HAS_ITEM(ITEM_DEKU_STICK)),
CHECK(RC_GREAT_BAY_COAST_TREE_02, true),
CHECK(RC_GREAT_BAY_COAST_TREE_03, true),
CHECK(RC_GREAT_BAY_COAST_TREE_04, true),
diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp
index 2b1df7727..22b7fe6fd 100644
--- a/mm/2s2h/Rando/Menu.cpp
+++ b/mm/2s2h/Rando/Menu.cpp
@@ -541,6 +541,7 @@ static void DrawShufflesTab() {
CVarCheckbox("Shuffle Snowball Drops", Rando::StaticData::Options[RO_SHUFFLE_SNOWBALL_DROPS].cvar);
CVarCheckbox("Shuffle Grass Drops", Rando::StaticData::Options[RO_SHUFFLE_GRASS_DROPS].cvar);
CVarCheckbox("Shuffle Tree Drops", Rando::StaticData::Options[RO_SHUFFLE_TREE_DROPS].cvar);
+ CVarCheckbox("Shuffle Butterflies", Rando::StaticData::Options[RO_SHUFFLE_BUTTERFLIES].cvar);
CVarCheckbox("Shuffle Frogs", Rando::StaticData::Options[RO_SHUFFLE_FROGS].cvar);
CVarCheckbox("Shuffle Hive Drops", Rando::StaticData::Options[RO_SHUFFLE_HIVE_DROPS].cvar);
CVarCheckbox("Shuffle Freestanding Items", Rando::StaticData::Options[RO_SHUFFLE_FREESTANDING_ITEMS].cvar);
diff --git a/mm/2s2h/Rando/StaticData/Checks.cpp b/mm/2s2h/Rando/StaticData/Checks.cpp
index 526dbced4..d270063da 100644
--- a/mm/2s2h/Rando/StaticData/Checks.cpp
+++ b/mm/2s2h/Rando/StaticData/Checks.cpp
@@ -2367,6 +2367,33 @@ std::map<RandoCheckId, RandoStaticCheck> Checks = {
RC(RC_ZORA_CAPE_TREE_03, RCTYPE_TREE, SCENE_31MISAKI, FLAG_NONE, 0x0, RI_NONE),
RC(RC_ZORA_CAPE_TREE_04, RCTYPE_TREE, SCENE_31MISAKI, FLAG_NONE, 0x0, RI_NONE),
RC(RC_ZORA_CAPE_TREE_05, RCTYPE_TREE, SCENE_31MISAKI, FLAG_NONE, 0x0, RI_NONE),
+
+ // Butterflies //
+ RC(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_01, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_02, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_03, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_04, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_GREAT_BAY_COAST_BUTTERFLY_01, RCTYPE_BUTTERFLY, SCENE_30GYOSON, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_GREAT_BAY_COAST_BUTTERFLY_02, RCTYPE_BUTTERFLY, SCENE_30GYOSON, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_01, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_02, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_03, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_01, RCTYPE_BUTTERFLY, SCENE_10YUKIYAMANOMURA2, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_02, RCTYPE_BUTTERFLY, SCENE_10YUKIYAMANOMURA2, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_03, RCTYPE_BUTTERFLY, SCENE_10YUKIYAMANOMURA2, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_04, RCTYPE_BUTTERFLY, SCENE_10YUKIYAMANOMURA2, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_05, RCTYPE_BUTTERFLY, SCENE_10YUKIYAMANOMURA2, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_06, RCTYPE_BUTTERFLY, SCENE_10YUKIYAMANOMURA2, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_07, RCTYPE_BUTTERFLY, SCENE_10YUKIYAMANOMURA2, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_08, RCTYPE_BUTTERFLY, SCENE_10YUKIYAMANOMURA2, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_09, RCTYPE_BUTTERFLY, SCENE_10YUKIYAMANOMURA2, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_TERMINA_FIELD_BUTTERFLY_01, RCTYPE_BUTTERFLY, SCENE_00KEIKOKU, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_TERMINA_FIELD_BUTTERFLY_02, RCTYPE_BUTTERFLY, SCENE_00KEIKOKU, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_TERMINA_FIELD_COW_GROTTO_BUTTERFLY_01, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_TERMINA_FIELD_COW_GROTTO_BUTTERFLY_02, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_TERMINA_FIELD_COW_GROTTO_BUTTERFLY_03, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_BUTTERFLY_01, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
+ RC(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_BUTTERFLY_02, RCTYPE_BUTTERFLY, SCENE_KAKUSIANA, FLAG_NONE, 0x0, RI_NONE),
};
// clang-format on
diff --git a/mm/2s2h/Rando/StaticData/Options.cpp b/mm/2s2h/Rando/StaticData/Options.cpp
index 5db59ab6b..a96579934 100644
--- a/mm/2s2h/Rando/StaticData/Options.cpp
+++ b/mm/2s2h/Rando/StaticData/Options.cpp
@@ -44,6 +44,7 @@ std::map<RandoOptionId, RandoStaticOption> Options = {
RO(RO_SHUFFLE_BARREL_DROPS, RO_GENERIC_OFF),
RO(RO_SHUFFLE_BOSS_REMAINS, RO_GENERIC_OFF),
RO(RO_SHUFFLE_BOSS_SOULS, RO_GENERIC_OFF),
+ RO(RO_SHUFFLE_BUTTERFLIES, RO_GENERIC_OFF),
RO(RO_SHUFFLE_COWS, RO_GENERIC_OFF),
RO(RO_SHUFFLE_CRATE_DROPS, RO_GENERIC_OFF),
RO(RO_SHUFFLE_ENEMY_DROPS, RO_GENERIC_OFF),
diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h
index f85437cfa..5abd92850 100644
--- a/mm/2s2h/Rando/Types.h
+++ b/mm/2s2h/Rando/Types.h
@@ -4,6 +4,7 @@
typedef enum {
RCTYPE_UNKNOWN,
RCTYPE_BARREL,
+ RCTYPE_BUTTERFLY,
RCTYPE_CHEST,
RCTYPE_COW,
RCTYPE_CRATE,
@@ -2371,6 +2372,32 @@ typedef enum {
RC_ZORA_CAPE_TREE_04,
RC_ZORA_CAPE_TREE_05,
+ RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_01,
+ RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_02,
+ RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_03,
+ RC_DEKU_PALACE_BEAN_SALESMAN_GROTTO_BUTTERFLY_04,
+ RC_GREAT_BAY_COAST_BUTTERFLY_01,
+ RC_GREAT_BAY_COAST_BUTTERFLY_02,
+ RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_01,
+ RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_02,
+ RC_GREAT_BAY_COAST_COW_GROTTO_BUTTERFLY_03,
+ RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_01,
+ RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_02,
+ RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_03,
+ RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_04,
+ RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_05,
+ RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_06,
+ RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_07,
+ RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_08,
+ RC_MOUNTAIN_VILLAGE_SPRING_BUTTERFLY_09,
+ RC_TERMINA_FIELD_BUTTERFLY_01,
+ RC_TERMINA_FIELD_BUTTERFLY_02,
+ RC_TERMINA_FIELD_COW_GROTTO_BUTTERFLY_01,
+ RC_TERMINA_FIELD_COW_GROTTO_BUTTERFLY_02,
+ RC_TERMINA_FIELD_COW_GROTTO_BUTTERFLY_03,
+ RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_BUTTERFLY_01,
+ RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_3_BUTTERFLY_02,
+
RC_MAX,
} RandoCheckId;
@@ -2979,6 +3006,7 @@ typedef enum {
RO_SHUFFLE_BARREL_DROPS,
RO_SHUFFLE_BOSS_REMAINS,
RO_SHUFFLE_BOSS_SOULS,
+ RO_SHUFFLE_BUTTERFLIES,
RO_SHUFFLE_COWS,
RO_SHUFFLE_CRATE_DROPS,
RO_SHUFFLE_ENEMY_DROPS,