summaryrefslogtreecommitdiff
path: root/mm/2s2h/Rando/ActorBehavior
diff options
context:
space:
mode:
authorEblo <7004497+Eblo@users.noreply.github.com>2026-07-28 00:21:18 -0400
committerGitHub <noreply@github.com>2026-07-27 23:21:18 -0500
commit630536f681e0b362f58793cd1d8d003c817621bf (patch)
treef0b582f26c680f37b759c24d47f8486c70d59d87 /mm/2s2h/Rando/ActorBehavior
parent2c83cc890d70f4f68e1ca1e7bee753905efca467 (diff)
[Rando] Shuffle Wonder Items (#1785)
* Shuffle Wonder Items * Add swordsman school gong Rupee * Add Termina Field wall items * Remove unneeded hardcoded actor position * Make EnItem00 wonder items invisible
Diffstat (limited to 'mm/2s2h/Rando/ActorBehavior')
-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/EnItem00.cpp7
-rw-r--r--mm/2s2h/Rando/ActorBehavior/WonderItems.cpp182
4 files changed, 190 insertions, 1 deletions
diff --git a/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp
index f31d40759..e2e3aaef7 100644
--- a/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp
@@ -134,6 +134,7 @@ void Rando::ActorBehavior::OnFileLoad() {
Rando::ActorBehavior::InitPlayerBehavior();
Rando::ActorBehavior::InitSoulsBehavior();
Rando::ActorBehavior::InitTrapsBehavior();
+ Rando::ActorBehavior::InitWonderItemsBehavior();
COND_HOOK(ShouldVanillaBehavior, IS_RANDO, MiscVanillaBehaviorHandler);
}
diff --git a/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h
index 0b46a56d7..c932b24e2 100644
--- a/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h
+++ b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h
@@ -103,6 +103,7 @@ void InitObjWarpstoneBehavior();
void InitPlayerBehavior();
void InitSoulsBehavior();
void InitTrapsBehavior();
+void InitWonderItemsBehavior();
} // namespace ActorBehavior
diff --git a/mm/2s2h/Rando/ActorBehavior/EnItem00.cpp b/mm/2s2h/Rando/ActorBehavior/EnItem00.cpp
index feed09798..718652112 100644
--- a/mm/2s2h/Rando/ActorBehavior/EnItem00.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/EnItem00.cpp
@@ -113,6 +113,11 @@ void Rando::ActorBehavior::InitEnItem00Behavior() {
// Prevent the original item from spawning
*should = false;
- spawnReplacementItem(actor->world.pos, randoStaticCheck);
+ EnItem00* replacementItem = spawnReplacementItem(actor->world.pos, randoStaticCheck);
+
+ // Wonder Items are supposed to be invisible
+ if (randoStaticCheck.randoCheckType == RCTYPE_WONDER_ITEM) {
+ replacementItem->actor.draw = NULL;
+ }
});
}
diff --git a/mm/2s2h/Rando/ActorBehavior/WonderItems.cpp b/mm/2s2h/Rando/ActorBehavior/WonderItems.cpp
new file mode 100644
index 000000000..5d5ee4d3c
--- /dev/null
+++ b/mm/2s2h/Rando/ActorBehavior/WonderItems.cpp
@@ -0,0 +1,182 @@
+#include "ActorBehavior.h"
+#include <libultraship/bridge/consolevariablebridge.h>
+#include "2s2h/ObjectExtension/ActorListIndex.h"
+
+extern "C" {
+#include "overlays/actors/ovl_En_Invisible_Ruppe/z_en_invisible_ruppe.h"
+#include "overlays/actors/ovl_En_Gakufu/z_en_gakufu.h"
+#include "overlays/actors/ovl_En_Hit_Tag/z_en_hit_tag.h"
+#include "overlays/actors/ovl_Obj_Dora/z_obj_dora.h"
+
+extern Vec3f sRewardDropsSpawnTerminaFieldPos;
+extern u8 sRewardDropsIndex[];
+extern void EnGakufu_PlayRewardCutscene(EnGakufu* enGakufu, PlayState* play);
+}
+
+extern EnItem00* spawnReplacementItem(Vec3f& pos, Rando::StaticData::RandoStaticCheck& randoStaticCheck);
+extern int isDropActorAtPosition(PlayState* play, Actor* callingActor_, Actor* actor, void* verifyData);
+extern void SpawnDropItem(Vec3f position, RandoCheckId randoCheckId);
+
+// clang-format off
+std::map<std::tuple<s16, s16, s16, s16>, RandoCheckId> enWonderItemMap = {
+ // Pirates' Fortress
+ { { ACTOR_EN_HIT_TAG, SCENE_KAIZOKU, 0, 24 }, RC_PIRATE_FORTRESS_PLAZA_WONDER_ITEM_01 },
+ { { ACTOR_EN_HIT_TAG, SCENE_KAIZOKU, 0, 25 }, RC_PIRATE_FORTRESS_PLAZA_WONDER_ITEM_04 },
+ { { ACTOR_EN_HIT_TAG, SCENE_PIRATE, 3, 9 }, RC_PIRATE_FORTRESS_INTERIOR_WONDER_ITEM_01 },
+ // Swamp Spider House
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_KINSTA1, 4, 25 }, RC_SWAMP_SPIDER_HOUSE_WONDER_ITEM_01 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_KINSTA1, 4, 26 }, RC_SWAMP_SPIDER_HOUSE_WONDER_ITEM_02 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_KINSTA1, 4, 27 }, RC_SWAMP_SPIDER_HOUSE_WONDER_ITEM_03 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_KINSTA1, 4, 28 }, RC_SWAMP_SPIDER_HOUSE_WONDER_ITEM_04 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_KINSTA1, 4, 29 }, RC_SWAMP_SPIDER_HOUSE_WONDER_ITEM_05 },
+ { { ACTOR_EN_HIT_TAG, SCENE_KINSTA1, 1, 10 }, RC_SWAMP_SPIDER_HOUSE_WONDER_ITEM_06 },
+ { { ACTOR_EN_HIT_TAG, SCENE_KINSTA1, 1, 11 }, RC_SWAMP_SPIDER_HOUSE_WONDER_ITEM_09 },
+ { { ACTOR_EN_HIT_TAG, SCENE_KINSTA1, 1, 12 }, RC_SWAMP_SPIDER_HOUSE_WONDER_ITEM_12 },
+ { { ACTOR_EN_HIT_TAG, SCENE_KINSTA1, 1, 13 }, RC_SWAMP_SPIDER_HOUSE_WONDER_ITEM_15 },
+ // Oceanside Spider House
+ { { ACTOR_EN_HIT_TAG, SCENE_KINDAN2, 1, 0 }, RC_OCEAN_SPIDER_HOUSE_WONDER_ITEM_01 },
+ { { ACTOR_EN_HIT_TAG, SCENE_KINDAN2, 1, 1 }, RC_OCEAN_SPIDER_HOUSE_WONDER_ITEM_04 },
+ { { ACTOR_EN_HIT_TAG, SCENE_KINDAN2, 1, 2 }, RC_OCEAN_SPIDER_HOUSE_WONDER_ITEM_07 },
+ // Termina Field
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 89 }, RC_TERMINA_FIELD_WONDER_ITEM_01 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 90 }, RC_TERMINA_FIELD_WONDER_ITEM_02 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 91 }, RC_TERMINA_FIELD_WONDER_ITEM_03 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 92 }, RC_TERMINA_FIELD_WONDER_ITEM_04 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 93 }, RC_TERMINA_FIELD_WONDER_ITEM_05 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 94 }, RC_TERMINA_FIELD_WONDER_ITEM_06 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 95 }, RC_TERMINA_FIELD_WONDER_ITEM_07 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 96 }, RC_TERMINA_FIELD_WONDER_ITEM_08 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 97 }, RC_TERMINA_FIELD_WONDER_ITEM_09 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 98 }, RC_TERMINA_FIELD_WONDER_ITEM_10 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_00KEIKOKU, 0, 99 }, RC_TERMINA_FIELD_WONDER_ITEM_11 },
+ { { ACTOR_EN_HIT_TAG, SCENE_00KEIKOKU, 0, 100 }, RC_TERMINA_FIELD_WONDER_ITEM_12 },
+ { { ACTOR_EN_HIT_TAG, SCENE_00KEIKOKU, 0, 101 }, RC_TERMINA_FIELD_WONDER_ITEM_15 },
+ { { ACTOR_EN_HIT_TAG, SCENE_00KEIKOKU, 0, 102 }, RC_TERMINA_FIELD_WONDER_ITEM_18 },
+ // Romani Ranch
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_F01, 0, 42 }, RC_ROMANI_RANCH_WONDER_ITEM_01 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_F01, 0, 43 }, RC_ROMANI_RANCH_WONDER_ITEM_02 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_F01, 0, 44 }, RC_ROMANI_RANCH_WONDER_ITEM_03 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_F01, 0, 45 }, RC_ROMANI_RANCH_WONDER_ITEM_04 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_F01, 0, 46 }, RC_ROMANI_RANCH_WONDER_ITEM_05 },
+ { { ACTOR_EN_INVISIBLE_RUPPE, SCENE_F01, 0, 47 }, RC_ROMANI_RANCH_WONDER_ITEM_06 },
+ // Cucco Shack
+ { { ACTOR_EN_HIT_TAG, SCENE_F01C, 0, 17 }, RC_CUCCO_SHACK_WONDER_ITEM_01 },
+ { { ACTOR_EN_HIT_TAG, SCENE_F01C, 0, 18 }, RC_CUCCO_SHACK_WONDER_ITEM_04 },
+ // Ikana Graveyard
+ { { ACTOR_EN_HIT_TAG, SCENE_BOTI, 0, 10 }, RC_IKANA_GRAVEYARD_WONDER_ITEM_01 },
+ { { ACTOR_EN_HIT_TAG, SCENE_BOTI, 0, 11 }, RC_IKANA_GRAVEYARD_WONDER_ITEM_04 },
+ { { ACTOR_EN_HIT_TAG, SCENE_BOTI, 0, 12 }, RC_IKANA_GRAVEYARD_WONDER_ITEM_07 },
+ { { ACTOR_EN_HIT_TAG, SCENE_BOTI, 1, 9 }, RC_IKANA_GRAVEYARD_WONDER_ITEM_10 },
+ // Stock Pot Inn
+ { { ACTOR_EN_HIT_TAG, SCENE_YADOYA, 0, 14 }, RC_STOCK_POT_INN_WONDER_ITEM_01 },
+ // East Clock Town
+ { { ACTOR_EN_HIT_TAG, SCENE_TOWN, 0, 42 }, RC_CLOCK_TOWN_EAST_WONDER_ITEM_01 },
+ { { ACTOR_EN_HIT_TAG, SCENE_TOWN, 0, 43 }, RC_CLOCK_TOWN_EAST_WONDER_ITEM_04 },
+ { { ACTOR_EN_HIT_TAG, SCENE_TOWN, 0, 44 }, RC_CLOCK_TOWN_EAST_WONDER_ITEM_07 },
+ // South Clock Town
+ { { ACTOR_EN_HIT_TAG, SCENE_CLOCKTOWER, 0, 29 }, RC_CLOCK_TOWN_SOUTH_WONDER_ITEM_01 },
+};
+// clang-format on
+
+bool SpawnDroppedWonderItems(Vec3f position, u32 params) {
+ Actor* dropActor = SubS_FindActorCustom(gPlayState, NULL, NULL, ACTORCAT_ITEMACTION, ACTOR_EN_HIT_TAG, &position,
+ isDropActorAtPosition);
+
+ if (dropActor == nullptr) {
+ dropActor = Actor_FindNearby(gPlayState, &GET_PLAYER(gPlayState)->actor, ACTOR_EN_GAKUFU, ACTORCAT_ITEMACTION,
+ 99999.9f);
+ if (dropActor == nullptr || ((EnGakufu*)dropActor)->actionFunc != EnGakufu_PlayRewardCutscene) {
+ return false;
+ }
+ }
+
+ s16 actorListIndex = GetActorListIndex(dropActor);
+
+ // Cheesing it to avoid dupe spawns
+ if (actorListIndex == -1) {
+ return true;
+ }
+
+ RandoCheckId baseRandoCheckId = RC_UNKNOWN;
+ if (dropActor->id == ACTOR_EN_GAKUFU) {
+ s32 hour = TIME_TO_HOURS_F(CURRENT_TIME);
+ baseRandoCheckId = RandoCheckId(RC_TERMINA_FIELD_WALL_WONDER_ITEM_01 + sRewardDropsIndex[hour]);
+ position = sRewardDropsSpawnTerminaFieldPos;
+ } else {
+ auto it = enWonderItemMap.find(
+ { ACTOR_EN_HIT_TAG, gPlayState->sceneId, gPlayState->roomCtx.curRoom.num, actorListIndex });
+ if (it != enWonderItemMap.end()) {
+ baseRandoCheckId = it->second;
+ }
+ }
+
+ if (baseRandoCheckId == RC_UNKNOWN) {
+ return false;
+ }
+
+ if (!RANDO_SAVE_CHECKS[baseRandoCheckId].cycleObtained)
+ SpawnDropItem(position, baseRandoCheckId);
+ if (!RANDO_SAVE_CHECKS[baseRandoCheckId + 1].cycleObtained)
+ SpawnDropItem(position, RandoCheckId(baseRandoCheckId + 1));
+ if (!RANDO_SAVE_CHECKS[baseRandoCheckId + 2].cycleObtained)
+ SpawnDropItem(position, RandoCheckId(baseRandoCheckId + 2));
+ // Set this as a quick and dirty condition to avoid spawning dupes
+ SetActorListIndex(dropActor, -1);
+ return true;
+}
+
+void WonderItemInit(Actor* actor, RandoCheckId randoCheckId, bool isInvisible, bool* should) {
+ auto randoSaveCheck = RANDO_SAVE_CHECKS[randoCheckId];
+
+ if (!randoSaveCheck.shuffled || randoSaveCheck.cycleObtained) {
+ return;
+ }
+
+ Actor* customActor = (Actor*)spawnReplacementItem(actor->world.pos, Rando::StaticData::Checks[randoCheckId]);
+ if (isInvisible) {
+ customActor->draw = NULL;
+ }
+
+ *should = false;
+}
+
+void Rando::ActorBehavior::InitWonderItemsBehavior() {
+ bool shouldRegister = IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_WONDER_ITEMS];
+
+ COND_VB_SHOULD(VB_DROP_COLLECTIBLE, shouldRegister, {
+ Vec3f position = va_arg(args, Vec3f);
+ u32 params = va_arg(args, u32);
+ if (SpawnDroppedWonderItems(position, params)) {
+ *should = false;
+ }
+ });
+
+ COND_VB_SHOULD(VB_GONG_DROP_COLLECTIBLE, shouldRegister, {
+ ObjDora* objDora = va_arg(args, ObjDora*);
+ if (!RANDO_SAVE_CHECKS[RC_SWORDSMAN_SCHOOL_WONDER_ITEM].cycleObtained) {
+ SpawnDropItem(objDora->actor.world.pos, RC_SWORDSMAN_SCHOOL_WONDER_ITEM);
+ *should = false;
+ }
+ });
+
+ COND_ID_HOOK(ShouldActorInit, ACTOR_EN_INVISIBLE_RUPPE, shouldRegister, [](Actor* actor, bool* should) {
+ RandoCheckId randoCheckId = RC_UNKNOWN;
+ auto it = enWonderItemMap.find(
+ { actor->id, gPlayState->sceneId, gPlayState->roomCtx.curRoom.num, GetActorListIndex(actor) });
+ if (it != enWonderItemMap.end()) {
+ randoCheckId = it->second;
+ }
+
+ if (randoCheckId != RC_UNKNOWN && RANDO_SAVE_CHECKS[randoCheckId].shuffled ||
+ RANDO_SAVE_CHECKS[randoCheckId].cycleObtained) {
+ WonderItemInit(actor, randoCheckId, true, should);
+ }
+ });
+
+ COND_ID_HOOK(ShouldActorInit, ACTOR_EN_SCOPECOIN, shouldRegister, [](Actor* actor, bool* should) {
+ if (GetActorListIndex(actor) == 207) { // Pillar Rupee in Termina Field
+ WonderItemInit(actor, RC_TERMINA_FIELD_WONDER_ITEM_21,
+ !(gPlayState->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON), should);
+ }
+ });
+}