Skip to content

Commit

Permalink
Rewrite game interfacing code of fairy shuffle
Browse files Browse the repository at this point in the history
  • Loading branch information
aMannus committed Jan 2, 2025
1 parent d550548 commit 73ab45f
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 137 deletions.
9 changes: 5 additions & 4 deletions soh/soh/Enhancements/game-interactor/GameInteractor.h
Original file line number Diff line number Diff line change
Expand Up @@ -511,13 +511,14 @@ typedef enum {
// Opt: *Actor
VB_BOTTLE_ACTOR,

/*** Fairy Shuffle ***/
// Opt: *EnElf
VB_SPAWN_FAIRY_GROUP,
/*** Shuffle Fairies ***/
// Opt: *EnElf
VB_SPAWN_FOUNTAIN_FAIRIES,
VB_FAIRY_HEAL,
// Opt: *ObjBean
VB_BEAN_SPAWN_FAIRIES,
VB_SPAWN_BEAN_STALK_FAIRIES,
// Opt: *EnGs
VB_SPAWN_GOSSIP_STONE_FAIRY,
} GIVanillaBehavior;

#ifdef __cplusplus
Expand Down
6 changes: 3 additions & 3 deletions soh/soh/Enhancements/randomizer/3drando/item_pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ static void PlaceVanillaOverworldFish() {

static void PlaceVanillaFairies() {
auto ctx = Rando::Context::GetInstance();
for (auto rc : Rando::StaticData::GetOverworldFairyLocations()) {
for (auto rc : Rando::StaticData::GetFairyLocations()) {
ctx->PlaceItemInLocation(rc, GetJunkItem(), false, true);
}
if (ctx->GetDungeon(Rando::GANONS_CASTLE)->IsMQ()) {
Expand Down Expand Up @@ -1307,9 +1307,9 @@ void GenerateItemPool() {
AddItemsToPool(ItemPool, shopsanityRupees); //Shopsanity gets extra large rupees
}

//Fairysanity
// Shuffle Fairies
if (ctx->GetOption(RSK_SHUFFLE_FAIRIES)) {
for (auto rc : Rando::StaticData::GetOverworldFairyLocations()) {
for (auto rc : Rando::StaticData::GetFairyLocations()) {
AddItemToMainPool(GetJunkItem());
}
// 8 extra for Ganon's Castle + 2 Dodongo's Cavern Gossip Stone + 3 Shadow Temple
Expand Down
178 changes: 178 additions & 0 deletions soh/soh/Enhancements/randomizer/ShuffleFairies.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#include "ShuffleFairies.h"
#include "randomizer_grotto.h"
#include "draw.h"
#include "src/overlays/actors/ovl_En_Elf/z_en_elf.h"
#include "src/overlays/actors/ovl_Obj_Bean/z_obj_bean.h"
#include "src/overlays/actors/ovl_En_Gs/z_en_gs.h"
#include "../../OTRGlobals.h"
#include "../../cvar_prefixes.h"

#define FAIRY_FLAG_TIMED (1 << 8)

void FairyShuffleDrawRandomizedItem(EnElf* enElf, PlayState* play) {
GetItemEntry randoGetItem = enElf->sohFairyIdentity.itemEntry;
if (CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0)) {
randoGetItem = GET_ITEM_MYSTERY;
}
Matrix_Push();
Matrix_Scale(37.5, 37.5, 37.5, MTXMODE_APPLY);
EnItem00_CustomItemsParticles(&enElf->actor, play, randoGetItem);
GetItemEntry_Draw(play, randoGetItem);
Matrix_Pop();
}

bool FairyShuffleFairyExists(FairyIdentity fairyIdentity) {
Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_ITEMACTION].head;

while (actor != NULL) {
if (actor->id != ACTOR_EN_ELF) {
actor = actor->next;
} else {
EnElf* enElf = (EnElf*)(actor);
if (fairyIdentity.randomizerInf == enElf->sohFairyIdentity.randomizerInf) {
return true;
}
actor = actor->next;
}
}

return false;
}

FairyIdentity FairyShuffleGetIdentity(int32_t params) {
FairyIdentity fairyIdentity;
s16 sceneNum = gPlayState->sceneNum;
fairyIdentity.randomizerInf = RAND_INF_MAX;

if (sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_NIGHT || sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_RUINS) {
sceneNum = SCENE_TEMPLE_OF_TIME_EXTERIOR_DAY;
}

Rando::Location* location = OTRGlobals::Instance->gRandomizer->GetCheckObjectFromActor(ACTOR_EN_ELF, sceneNum, params);

if (location->GetRandomizerCheck() == RC_UNKNOWN_CHECK) {
LUSLOG_WARN("FairyGetIdentity did not receive a valid RC value (%d).", location->GetRandomizerCheck());
assert(false);
} else {
fairyIdentity.randomizerInf = static_cast<RandomizerInf>(location->GetCollectionCheck().flag);
fairyIdentity.itemEntry = Rando::Context::GetInstance()->GetFinalGIEntry(location->GetRandomizerCheck(), true, GI_FAIRY);
}

return fairyIdentity;
}

bool FairyShuffleSpawnFairy(f32 posX, f32 posY, f32 posZ, int32_t params) {
FairyIdentity fairyIdentity = FairyShuffleGetIdentity(params);
if (!Flags_GetRandomizerInf(fairyIdentity.randomizerInf)) {
EnElf* fairy = (EnElf*)Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_ELF, posX, posY - 30.0f, posZ, 0,
0, 0, FAIRY_HEAL, true);
fairy->sohFairyIdentity = fairyIdentity;
fairy->actor.draw = (ActorFunc)FairyShuffleDrawRandomizedItem;
fairy->fairyFlags |= FAIRY_FLAG_TIMED;
return true;
}
return false;
}

void FairyShuffleOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs) {
va_list args;
va_copy(args, originalArgs);

Actor* actor = va_arg(args, Actor*);

va_end(args);

// Grant item when picking up fairy. If randomized, disable healing effect.
if (id == VB_FAIRY_HEAL) {
EnElf* enElf = (EnElf*)(actor);
if (enElf->sohFairyIdentity.randomizerInf && enElf->sohFairyIdentity.randomizerInf != RAND_INF_MAX) {
Flags_SetRandomizerInf(enElf->sohFairyIdentity.randomizerInf);
}
// Spawn fairies in fairy fountains
} else if (id == VB_SPAWN_FOUNTAIN_FAIRIES) {
bool fairySpawned = false;
s16 grottoId = (gPlayState->sceneNum == SCENE_FAIRYS_FOUNTAIN) ? Grotto_CurrentGrotto() : 0;
for (s16 index = 0; index < 8; index++) {
int32_t params = (grottoId << 8) | index;
if (FairyShuffleSpawnFairy(actor->world.pos.x, actor->world.pos.y, actor->world.pos.z,
params)) {
fairySpawned = true;
}
}
if (fairySpawned) {
*should = false;
}
// Spawn 3 fairies when playing Song of Storms next to a planted bean
} else if (id == VB_SPAWN_BEAN_STALK_FAIRIES) {
ObjBean* objBean = (ObjBean*)(actor);
bool fairySpawned = false;
for (s16 index = 0; index < 3; index++) {
int32_t params = ((objBean->dyna.actor.params & 0x3F) << 8) | index;
if (FairyShuffleSpawnFairy(objBean->dyna.actor.world.pos.x, objBean->dyna.actor.world.pos.y,
objBean->dyna.actor.world.pos.z,
params)) {
fairySpawned = true;
}
}
if (fairySpawned) {
*should = false;
}
// Handle playing both misc songs and song of storms in front of a gossip stone.
} else if (id == VB_SPAWN_GOSSIP_STONE_FAIRY) {
EnGs* gossipStone = (EnGs*)(actor);

// If not any of the songs that normally spawn a fairy, mimic vanilla behaviour.
if (gPlayState->msgCtx.ocarinaMode == OCARINA_MODE_01) {
Player* player = GET_PLAYER(gPlayState);
player->stateFlags2 |= PLAYER_STATE2_NEAR_OCARINA_ACTOR;
return;
} else if (gPlayState->msgCtx.unk_E3F2 != OCARINA_SONG_LULLABY &&
gPlayState->msgCtx.unk_E3F2 != OCARINA_SONG_SARIAS &&
gPlayState->msgCtx.unk_E3F2 != OCARINA_SONG_EPONAS &&
gPlayState->msgCtx.unk_E3F2 != OCARINA_SONG_SUNS &&
gPlayState->msgCtx.unk_E3F2 != OCARINA_SONG_TIME &&
gPlayState->msgCtx.unk_E3F2 != OCARINA_SONG_STORMS &&
gPlayState->msgCtx.ocarinaMode != OCARINA_MODE_04) {
return;
}

int32_t params = (gPlayState->sceneNum == SCENE_GROTTOS) ? Grotto_CurrentGrotto() : 0;
// Distinguish storms fairies from the normal song fairies
if (gPlayState->msgCtx.unk_E3F2 == OCARINA_SONG_STORMS) {
params |= 0x1000;
}

// Combine actor + song params with position to get the right randomizer check
params = TWO_ACTOR_PARAMS(params, (int32_t)gossipStone->actor.world.pos.z);

// Check if a fairy already exists with the same identity as the stone is trying to spawn.
// Because the gossip stone code runs several times after playing the song, we need to
// stop spawning the vanilla fairy as well when these fairies exist, otherwise both
// the randomized and the vanilla fairy will spawn. When the randomized fairy is already
// collected, the vanilla code will handle that part automatically.
FairyIdentity fairyIdentity = FairyShuffleGetIdentity(params);
if (!FairyShuffleFairyExists(fairyIdentity)) {
if (FairyShuffleSpawnFairy(gossipStone->actor.world.pos.x, gossipStone->actor.world.pos.y,
gossipStone->actor.world.pos.z, params)) {
Audio_PlayActorSound2(&gossipStone->actor, NA_SE_EV_BUTTERFRY_TO_FAIRY);
// Set vanilla check for fairy spawned so it doesn't spawn the vanilla fairy afterwards as well.
gossipStone->unk_19D = 0;
*should = false;
}
} else {
*should = false;
}
}
}

uint32_t onVanillaBehaviorHook = 0;

void FairyShuffleRegisterHooks() {
onVanillaBehaviorHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnVanillaBehavior>(FairyShuffleOnVanillaBehaviorHandler);
}

void FairyShuffleUnregisterHooks() {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnVanillaBehavior>(onVanillaBehaviorHook);

onVanillaBehaviorHook = 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ typedef struct FairyIdentity {
GetItemEntry itemEntry;
} FairyIdentity;


void FairyRegisterHooks();
void FairyUnregisterHooks();
void FairyShuffleRegisterHooks();
void FairyShuffleUnregisterHooks();
110 changes: 0 additions & 110 deletions soh/soh/Enhancements/randomizer/fairy_shuffle.cpp

This file was deleted.

6 changes: 3 additions & 3 deletions soh/soh/Enhancements/randomizer/hook_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#include "soh/ImGuiUtils.h"
#include "soh/Notification/Notification.h"
#include "soh/SaveManager.h"
#include "soh/Enhancements/randomizer/fairy_shuffle.h"
#include "soh/Enhancements/randomizer/ShuffleFairies.h"

extern "C" {
#include "macros.h"
Expand Down Expand Up @@ -2407,7 +2407,7 @@ void RandomizerRegisterHooks() {
shufflePotsOnActorInitHook = 0;
shufflePotsOnVanillaBehaviorHook = 0;

FairyUnregisterHooks();
FairyShuffleUnregisterHooks();

if (!IS_RANDO) return;

Expand Down Expand Up @@ -2453,7 +2453,7 @@ void RandomizerRegisterHooks() {
}

if (RAND_GET_OPTION(RSK_SHUFFLE_FAIRIES)) {
FairyRegisterHooks();
FairyShuffleRegisterHooks();
}
});
}
13 changes: 5 additions & 8 deletions soh/soh/Enhancements/randomizer/location_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ std::vector<RandomizerCheck> Rando::StaticData::GetShopLocations() {
return shopLocations;
}


std::vector<RandomizerCheck> Rando::StaticData::GetOverworldLocations() {
//RANDOTODO better way of filling the initial location pool, among other things.
std::vector<RandomizerCheck> overworldLocations = {};
Expand Down Expand Up @@ -136,18 +135,16 @@ std::vector<RandomizerCheck> Rando::StaticData::GetAllDungeonLocations() {
return dungeonLocations;
}


std::vector<RandomizerCheck> Rando::StaticData::GetOverworldFairyLocations() {
std::vector<RandomizerCheck> overworldPotLocations = {};
std::vector<RandomizerCheck> Rando::StaticData::GetFairyLocations() {
std::vector<RandomizerCheck> fairyLocations = {};
for (Location& location : locationTable) {
if (location.GetRCType() == RCTYPE_FAIRY && location.IsOverworld() && location.GetRandomizerCheck() != RC_UNKNOWN_CHECK) {
overworldPotLocations.push_back(location.GetRandomizerCheck());
if (location.GetRCType() == RCTYPE_FAIRY && location.GetRandomizerCheck() != RC_UNKNOWN_CHECK) {
fairyLocations.push_back(location.GetRandomizerCheck());
}
}
return overworldPotLocations;
return fairyLocations;
}


void Rando::StaticData::InitLocationTable() { // Randomizer Check Quest Type Area Actor ID Scene ID Params Flags Short Name Hint Text Key Vanilla Item Spoiler Collection Check Vanilla Progression Price
// clang-format off
locationTable[RC_UNKNOWN_CHECK] = Location::Base(RC_UNKNOWN_CHECK, RCQUEST_BOTH, RCTYPE_STANDARD, RCAREA_INVALID, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, "Invalid Location", "Invalid Location", RHT_NONE, RG_NONE);
Expand Down
Loading

0 comments on commit 73ab45f

Please sign in to comment.