Shipwright/soh/soh/Enhancements/randomizer/hook_handlers.cpp

1172 lines
53 KiB
C++

#include <libultraship/bridge.h>
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/enhancementTypes.h"
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
#include "soh/Enhancements/randomizer/randomizerTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
extern "C" {
#include "macros.h"
#include "functions.h"
#include "variables.h"
#include "soh/Enhancements/randomizer/adult_trade_shuffle.h"
#include "src/overlays/actors/ovl_En_Si/z_en_si.h"
#include "src/overlays/actors/ovl_En_Cow/z_en_cow.h"
#include "src/overlays/actors/ovl_En_Shopnuts/z_en_shopnuts.h"
#include "src/overlays/actors/ovl_En_Dns/z_en_dns.h"
#include "src/overlays/actors/ovl_Item_B_Heart/z_item_b_heart.h"
#include "src/overlays/actors/ovl_En_Ko/z_en_ko.h"
#include "src/overlays/actors/ovl_En_Mk/z_en_mk.h"
#include "src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.h"
#include "src/overlays/actors/ovl_En_Kz/z_en_kz.h"
#include "src/overlays/actors/ovl_En_Go2/z_en_go2.h"
#include "src/overlays/actors/ovl_En_Ms/z_en_ms.h"
#include "src/overlays/actors/ovl_En_Fr/z_en_fr.h"
#include "src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.h"
#include "src/overlays/actors/ovl_En_Sth/z_en_sth.h"
#include "src/overlays/actors/ovl_Item_Etcetera/z_item_etcetera.h"
#include "src/overlays/actors/ovl_En_Box/z_en_box.h"
#include "adult_trade_shuffle.h"
extern SaveContext gSaveContext;
extern PlayState* gPlayState;
}
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetSelectedOptionIndex()
RandomizerCheck GetRandomizerCheckFromFlag(int16_t flagType, int16_t flag) {
for (auto& loc : Rando::StaticData::GetLocationTable()) {
if (loc.GetCollectionCheck().flag == flag && (
(flagType == FLAG_INF_TABLE && loc.GetCollectionCheck().type == SPOILER_CHK_INF_TABLE) ||
(flagType == FLAG_EVENT_CHECK_INF && loc.GetCollectionCheck().type == SPOILER_CHK_EVENT_CHK_INF) ||
(flagType == FLAG_ITEM_GET_INF && loc.GetCollectionCheck().type == SPOILER_CHK_ITEM_GET_INF) ||
(flagType == FLAG_RANDOMIZER_INF && loc.GetCollectionCheck().type == SPOILER_CHK_RANDOMIZER_INF)
) ||
(loc.GetActorParams() == flag && flagType == FLAG_GS_TOKEN && loc.GetCollectionCheck().type == SPOILER_CHK_GOLD_SKULLTULA)
) {
return loc.GetRandomizerCheck();
}
}
return RC_UNKNOWN_CHECK;
}
RandomizerCheck GetRandomizerCheckFromSceneFlag(int16_t sceneNum, int16_t flagType, int16_t flag) {
for (auto& loc : Rando::StaticData::GetLocationTable()) {
if (loc.GetCollectionCheck().scene == sceneNum && loc.GetCollectionCheck().flag == flag && (
(flagType == FLAG_SCENE_TREASURE && loc.GetCollectionCheck().type == SPOILER_CHK_CHEST) ||
(flagType == FLAG_SCENE_COLLECTIBLE && loc.GetCollectionCheck().type == SPOILER_CHK_COLLECTABLE) ||
(flagType == FLAG_GS_TOKEN && loc.GetCollectionCheck().type == SPOILER_CHK_GOLD_SKULLTULA)
)) {
return loc.GetRandomizerCheck();
}
}
return RC_UNKNOWN_CHECK;
}
bool MeetsLACSRequirements() {
switch (RAND_GET_OPTION(RSK_GANONS_BOSS_KEY)) {
case RO_GANON_BOSS_KEY_LACS_STONES:
if ((CheckStoneCount() + CheckLACSRewardCount()) >= RAND_GET_OPTION(RSK_LACS_STONE_COUNT)) {
return true;
}
break;
case RO_GANON_BOSS_KEY_LACS_MEDALLIONS:
if ((CheckMedallionCount() + CheckLACSRewardCount()) >= RAND_GET_OPTION(RSK_LACS_MEDALLION_COUNT)) {
return true;
}
break;
case RO_GANON_BOSS_KEY_LACS_REWARDS:
if ((CheckMedallionCount() + CheckStoneCount() + CheckLACSRewardCount()) >= RAND_GET_OPTION(RSK_LACS_REWARD_COUNT)) {
return true;
}
break;
case RO_GANON_BOSS_KEY_LACS_DUNGEONS:
if ((CheckDungeonCount() + CheckLACSRewardCount()) >= RAND_GET_OPTION(RSK_LACS_DUNGEON_COUNT)) {
return true;
}
break;
case RO_GANON_BOSS_KEY_LACS_TOKENS:
if (gSaveContext.inventory.gsTokens >= RAND_GET_OPTION(RSK_LACS_TOKEN_COUNT)) {
return true;
}
break;
default:
if (CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW)) {
return true;
}
break;
}
return false;
}
// Todo Move this to randomizer context, clear it out on save load etc
static std::queue<RandomizerCheck> randomizerQueuedChecks;
static RandomizerCheck randomizerQueuedCheck = RC_UNKNOWN_CHECK;
static GetItemEntry randomizerQueuedItemEntry = GET_ITEM_NONE;
void RandomizerOnFlagSetHandler(int16_t flagType, int16_t flag) {
// Consume adult trade items
if (RAND_GET_OPTION(RSK_SHUFFLE_ADULT_TRADE) && flagType == FLAG_RANDOMIZER_INF) {
switch (flag) {
case RAND_INF_ADULT_TRADES_DMT_TRADE_BROKEN_SWORD:
Randomizer_ConsumeAdultTradeItem(gPlayState, ITEM_SWORD_BROKEN);
break;
case RAND_INF_ADULT_TRADES_DMT_TRADE_EYEDROPS:
Randomizer_ConsumeAdultTradeItem(gPlayState, ITEM_EYEDROPS);
break;
}
}
RandomizerCheck rc = GetRandomizerCheckFromFlag(flagType, flag);
if (rc == RC_UNKNOWN_CHECK) return;
auto loc = Rando::Context::GetInstance()->GetItemLocation(rc);
if (loc == nullptr || loc->HasObtained()) return;
SPDLOG_INFO("Queuing RC: {}", rc);
randomizerQueuedChecks.push(rc);
}
void RandomizerOnSceneFlagSetHandler(int16_t sceneNum, int16_t flagType, int16_t flag) {
RandomizerCheck rc = GetRandomizerCheckFromSceneFlag(sceneNum, flagType, flag);
if (rc == RC_UNKNOWN_CHECK) return;
auto loc = Rando::Context::GetInstance()->GetItemLocation(rc);
if (loc == nullptr || loc->HasObtained()) return;
SPDLOG_INFO("Queuing RC: {}", rc);
randomizerQueuedChecks.push(rc);
}
static Vec3f spawnPos = { 0.0f, -999.0f, 0.0f };
void RandomizerOnPlayerUpdateForRCQueueHandler() {
// If we're already queued, don't queue again
if (randomizerQueuedCheck != RC_UNKNOWN_CHECK) return;
// If there's nothing to queue, don't queue
if (randomizerQueuedChecks.size() < 1) return;
// If we're in a cutscene, don't queue
Player* player = GET_PLAYER(gPlayState);
if (Player_InBlockingCsMode(gPlayState, player) || player->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS || player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM || player->stateFlags1 & PLAYER_STATE1_ITEM_OVER_HEAD) {
return;
}
RandomizerCheck rc = randomizerQueuedChecks.front();
auto loc = Rando::Context::GetInstance()->GetItemLocation(rc);
GetItemEntry getItemEntry = Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, (GetItemID)Rando::StaticData::GetLocation(rc)->GetVanillaItem());
if (loc->HasObtained()) {
SPDLOG_INFO("RC {} already obtained, skipping", rc);
} else {
randomizerQueuedCheck = rc;
randomizerQueuedItemEntry = getItemEntry;
SPDLOG_INFO("Queueing Item mod {} item {} from RC {}", getItemEntry.modIndex, getItemEntry.itemId, rc);
if (
// Skipping ItemGet animation incompatible with checks that require closing a text box to finish
rc != RC_HF_OCARINA_OF_TIME_ITEM &&
rc != RC_SPIRIT_TEMPLE_SILVER_GAUNTLETS_CHEST &&
// Always show ItemGet animation for ice traps
!(getItemEntry.modIndex == MOD_RANDOMIZER && getItemEntry.getItemId == RG_ICE_TRAP) &&
(
CVarGetInteger("gTimeSavers.SkipGetItemAnimation", SGIA_DISABLED) == SGIA_ALL ||
(
CVarGetInteger("gTimeSavers.SkipGetItemAnimation", SGIA_DISABLED) == SGIA_JUNK &&
(
getItemEntry.getItemCategory == ITEM_CATEGORY_JUNK ||
getItemEntry.getItemCategory == ITEM_CATEGORY_SKULLTULA_TOKEN ||
getItemEntry.getItemCategory == ITEM_CATEGORY_LESSER
)
)
)
) {
Item_DropCollectible(gPlayState, &spawnPos, ITEM00_SOH_GIVE_ITEM_ENTRY | 0x8000);
}
}
randomizerQueuedChecks.pop();
}
void RandomizerOnPlayerUpdateForItemQueueHandler() {
if (randomizerQueuedCheck == RC_UNKNOWN_CHECK) return;
Player* player = GET_PLAYER(gPlayState);
if (player == NULL || Player_InBlockingCsMode(gPlayState, player) || player->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS || player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM || player->stateFlags1 & PLAYER_STATE1_ITEM_OVER_HEAD) {
return;
}
SPDLOG_INFO("Attempting to give Item mod {} item {} from RC {}", randomizerQueuedItemEntry.modIndex, randomizerQueuedItemEntry.itemId, randomizerQueuedCheck);
GiveItemEntryWithoutActor(gPlayState, randomizerQueuedItemEntry);
if (player->stateFlags1 & PLAYER_STATE1_IN_WATER) {
// Allow the player to receive the item while swimming
player->stateFlags2 |= PLAYER_STATE2_UNDERWATER;
Player_ActionChange_2(player, gPlayState);
}
}
void RandomizerOnItemReceiveHandler(GetItemEntry receivedItemEntry) {
if (randomizerQueuedCheck == RC_UNKNOWN_CHECK) return;
auto loc = Rando::Context::GetInstance()->GetItemLocation(randomizerQueuedCheck);
if (randomizerQueuedItemEntry.modIndex == receivedItemEntry.modIndex && randomizerQueuedItemEntry.itemId == receivedItemEntry.itemId) {
SPDLOG_INFO("Item received mod {} item {} from RC {}", receivedItemEntry.modIndex, receivedItemEntry.itemId, randomizerQueuedCheck);
loc->MarkAsObtained();
randomizerQueuedCheck = RC_UNKNOWN_CHECK;
randomizerQueuedItemEntry = GET_ITEM_NONE;
}
if (
receivedItemEntry.modIndex == MOD_NONE && (
receivedItemEntry.itemId == ITEM_HEART_PIECE ||
receivedItemEntry.itemId == ITEM_HEART_PIECE_2 ||
receivedItemEntry.itemId == ITEM_HEART_CONTAINER
)
) {
gSaveContext.healthAccumulator = 0x140; // Refill 20 hearts
if ((s32)(gSaveContext.inventory.questItems & 0xF0000000) == 0x40000000) {
gSaveContext.inventory.questItems ^= 0x40000000;
gSaveContext.healthCapacity += 0x10;
gSaveContext.health += 0x10;
}
}
if (loc->GetRandomizerCheck() == RC_SPIRIT_TEMPLE_SILVER_GAUNTLETS_CHEST && !CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO)) {
static uint32_t updateHook;
updateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>([]() {
Player* player = GET_PLAYER(gPlayState);
if (player == NULL || Player_InBlockingCsMode(gPlayState, player) || player->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS || player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM || player->stateFlags1 & PLAYER_STATE1_ITEM_OVER_HEAD) {
return;
}
gPlayState->nextEntranceIndex = ENTR_DESERT_COLOSSUS_0;
gPlayState->transitionTrigger = TRANS_TRIGGER_START;
gSaveContext.nextCutsceneIndex = 0xFFF1;
gPlayState->transitionType = TRANS_TYPE_SANDSTORM_END;
GET_PLAYER(gPlayState)->stateFlags1 &= ~PLAYER_STATE1_IN_CUTSCENE;
Player_TryCsAction(gPlayState, NULL, 8);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayerUpdate>(updateHook);
});
}
}
void EnItem00_DrawRandomizedItem(EnItem00* enItem00, PlayState* play) {
f32 mtxScale = CVarGetFloat("gTimeSavers.SkipGetItemAnimationScale", 10.0f);
Matrix_Scale(mtxScale, mtxScale, mtxScale, MTXMODE_APPLY);
EnItem00_CustomItemsParticles(&enItem00->actor, play, enItem00->itemEntry);
GetItemEntry_Draw(play, enItem00->itemEntry);
}
void ItemBHeart_DrawRandomizedItem(ItemBHeart* itemBHeart, PlayState* play) {
EnItem00_CustomItemsParticles(&itemBHeart->actor, play, itemBHeart->sohItemEntry);
GetItemEntry_Draw(play, itemBHeart->sohItemEntry);
}
void ItemBHeart_UpdateRandomizedItem(Actor* actor, PlayState* play) {
ItemBHeart* itemBHeart = (ItemBHeart*)actor;
func_80B85264(itemBHeart, play);
Actor_UpdateBgCheckInfo(play, &itemBHeart->actor, 0.0f, 0.0f, 0.0f, 4);
if ((itemBHeart->actor.xzDistToPlayer < 30.0f) && (fabsf(itemBHeart->actor.yDistToPlayer) < 40.0f)) {
Flags_SetCollectible(play, 0x1F);
Actor_Kill(&itemBHeart->actor);
}
}
void ItemEtcetera_DrawRandomizedItem(ItemEtcetera* itemEtcetera, PlayState* play) {
EnItem00_CustomItemsParticles(&itemEtcetera->actor, play, itemEtcetera->sohItemEntry);
func_8002EBCC(&itemEtcetera->actor, play, 0);
func_8002ED80(&itemEtcetera->actor, play, 0);
GetItemEntry_Draw(play, itemEtcetera->sohItemEntry);
}
void ItemEtcetera_DrawRandomizedItemThroughLens(ItemEtcetera* itemEtcetera, PlayState* play) {
if (play->actorCtx.lensActive) {
ItemEtcetera_DrawRandomizedItem(itemEtcetera, play);
}
}
void ItemEtcetera_func_80B858B4_Randomized(ItemEtcetera* itemEtcetera, PlayState* play) {
if (itemEtcetera->actor.xzDistToPlayer < 30.0f &&
fabsf(itemEtcetera->actor.yDistToPlayer) < 50.0f) {
if ((itemEtcetera->actor.params & 0xFF) == 1) {
Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_RUTOS_LETTER);
Flags_SetSwitch(play, 0xB);
}
Actor_Kill(&itemEtcetera->actor);
} else {
if ((play->gameplayFrames & 0xD) == 0) {
EffectSsBubble_Spawn(play, &itemEtcetera->actor.world.pos, 0.0f, 0.0f, 10.0f, 0.13f);
}
}
}
void ItemEtcetera_func_80B85824_Randomized(ItemEtcetera* itemEtcetera, PlayState* play) {
if ((itemEtcetera->actor.params & 0xFF) != 7) {
return;
}
if (itemEtcetera->actor.xzDistToPlayer < 30.0f &&
fabsf(itemEtcetera->actor.yDistToPlayer) < 50.0f) {
Flags_SetTreasure(play, 0x1F);
Actor_Kill(&itemEtcetera->actor);
}
}
void ItemEtcetera_MoveRandomizedFireArrowDown(ItemEtcetera* itemEtcetera, PlayState* play) {
Actor_UpdateBgCheckInfo(play, &itemEtcetera->actor, 10.0f, 10.0f, 0.0f, 5);
Actor_MoveForward(&itemEtcetera->actor);
if (!(itemEtcetera->actor.bgCheckFlags & 1)) {
ItemEtcetera_SpawnSparkles(itemEtcetera, play);
}
itemEtcetera->actor.shape.rot.y += 0x400;
ItemEtcetera_func_80B85824_Randomized(itemEtcetera, play);
}
void ItemEtcetera_UpdateRandomizedFireArrow(ItemEtcetera* itemEtcetera, PlayState* play) {
if ((play->csCtx.state != CS_STATE_IDLE) && (play->csCtx.npcActions[0] != NULL)) {
if (play->csCtx.npcActions[0]->action == 2) {
itemEtcetera->actor.draw = (ActorFunc)ItemEtcetera_DrawRandomizedItem;
itemEtcetera->actor.gravity = -0.1f;
itemEtcetera->actor.minVelocityY = -4.0f;
itemEtcetera->actionFunc = ItemEtcetera_MoveRandomizedFireArrowDown;
}
} else {
itemEtcetera->actor.gravity = -0.1f;
itemEtcetera->actor.minVelocityY = -4.0f;
itemEtcetera->actionFunc = ItemEtcetera_MoveRandomizedFireArrowDown;
}
}
void EnCow_MoveForRandomizer(EnCow* enCow, PlayState* play) {
bool moved = false;
// Don't reposition the tail
if (enCow->actor.params != 0) {
return;
}
// Move left cow in lon lon tower
if (play->sceneNum == SCENE_LON_LON_BUILDINGS && enCow->actor.world.pos.x == -108 &&
enCow->actor.world.pos.z == -65) {
enCow->actor.world.pos.x = -229.0f;
enCow->actor.world.pos.z = 157.0f;
enCow->actor.shape.rot.y = 15783.0f;
moved = true;
// Move right cow in lon lon stable
} else if (play->sceneNum == SCENE_STABLE && enCow->actor.world.pos.x == -3 && enCow->actor.world.pos.z == -254) {
enCow->actor.world.pos.x += 119.0f;
moved = true;
}
if (moved) {
// Reposition collider
func_809DEE9C(enCow);
}
}
u8 EnDs_RandoCanGetGrannyItem() {
return RAND_GET_OPTION(RSK_SHUFFLE_MERCHANTS) != RO_SHUFFLE_MERCHANTS_OFF &&
!Flags_GetRandomizerInf(RAND_INF_MERCHANTS_GRANNYS_SHOP) &&
// Traded odd mushroom when adult trade is on
((RAND_GET_OPTION(RSK_SHUFFLE_ADULT_TRADE) && Flags_GetItemGetInf(ITEMGETINF_30)) ||
// Found claim check when adult trade is off
(!RAND_GET_OPTION(RSK_SHUFFLE_ADULT_TRADE) &&
INV_CONTENT(ITEM_CLAIM_CHECK) == ITEM_CLAIM_CHECK));
}
RandomizerCheck EnFr_RandomizerCheckFromSongIndex(u16 songIndex) {
switch (songIndex) {
case FROG_ZL:
return RC_ZR_FROGS_ZELDAS_LULLABY;
case FROG_EPONA:
return RC_ZR_FROGS_EPONAS_SONG;
case FROG_SARIA:
return RC_ZR_FROGS_SARIAS_SONG;
case FROG_SUNS:
return RC_ZR_FROGS_SUNS_SONG;
case FROG_SOT:
return RC_ZR_FROGS_SONG_OF_TIME;
case FROG_STORMS:
return RC_ZR_FROGS_IN_THE_RAIN;
case FROG_CHOIR_SONG:
return RC_ZR_FROGS_OCARINA_GAME;
default:
return RC_UNKNOWN_CHECK;
}
}
void RandomizerSetChestGameRandomizerInf(RandomizerCheck rc) {
switch (rc) {
case RC_MARKET_TREASURE_CHEST_GAME_ITEM_1:
Flags_SetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_ITEM_1);
break;
case RC_MARKET_TREASURE_CHEST_GAME_ITEM_2:
Flags_SetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_ITEM_2);
break;
case RC_MARKET_TREASURE_CHEST_GAME_ITEM_3:
Flags_SetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_ITEM_3);
break;
case RC_MARKET_TREASURE_CHEST_GAME_ITEM_4:
Flags_SetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_ITEM_4);
break;
case RC_MARKET_TREASURE_CHEST_GAME_ITEM_5:
Flags_SetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_ITEM_5);
break;
case RC_MARKET_TREASURE_CHEST_GAME_KEY_1:
Flags_SetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_KEY_1);
break;
case RC_MARKET_TREASURE_CHEST_GAME_KEY_2:
Flags_SetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_KEY_2);
break;
case RC_MARKET_TREASURE_CHEST_GAME_KEY_3:
Flags_SetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_KEY_3);
break;
case RC_MARKET_TREASURE_CHEST_GAME_KEY_4:
Flags_SetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_KEY_4);
break;
case RC_MARKET_TREASURE_CHEST_GAME_KEY_5:
Flags_SetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_KEY_5);
break;
}
}
void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, void* optionalArg) {
switch (id) {
case GI_VB_GIVE_ITEM_FROM_CHEST: {
EnBox* chest = static_cast<EnBox*>(optionalArg);
RandomizerCheck rc = OTRGlobals::Instance->gRandomizer->GetCheckFromActor(chest->dyna.actor.id, gPlayState->sceneNum, chest->dyna.actor.params);
// if this is a treasure chest game chest then set the appropriate rando inf
RandomizerSetChestGameRandomizerInf(rc);
Player* player = GET_PLAYER(gPlayState);
player->av2.actionVar2 = 1;
player->getItemId = GI_NONE;
player->getItemEntry = GetItemEntry(GET_ITEM_NONE);
*should = false;
break;
}
case GI_VB_PLAY_NABOORU_CAPTURED_CS:
// This behavior is replicated for randomizer in RandomizerOnItemReceiveHandler
*should = false;
break;
case GI_VB_SHIEK_PREPARE_TO_GIVE_SERENADE_OF_WATER: {
*should = !Flags_GetEventChkInf(EVENTCHKINF_LEARNED_SERENADE_OF_WATER) && !Flags_GetTreasure(gPlayState, 0x2);
break;
}
case GI_VB_BE_ELIGIBLE_FOR_SERENADE_OF_WATER:
*should = !Flags_GetEventChkInf(EVENTCHKINF_LEARNED_SERENADE_OF_WATER) && Flags_GetTreasure(gPlayState, 0x2);
break;
case GI_VB_BE_ELIGIBLE_FOR_PRELUDE_OF_LIGHT:
*should = !Flags_GetEventChkInf(EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT) && CHECK_QUEST_ITEM(QUEST_MEDALLION_FOREST);
break;
case GI_VB_MOVE_MIDO_IN_KOKIRI_FOREST:
if (RAND_GET_OPTION(RSK_FOREST) == RO_FOREST_OPEN) {
*should = true;
}
break;
case GI_VB_MIDO_CONSIDER_DEKU_TREE_DEAD:
*should = Flags_GetEventChkInf(EVENTCHKINF_OBTAINED_KOKIRI_EMERALD_DEKU_TREE_DEAD);
break;
case GI_VB_OPEN_KOKIRI_FOREST:
*should = Flags_GetEventChkInf(EVENTCHKINF_OBTAINED_KOKIRI_EMERALD_DEKU_TREE_DEAD) || RAND_GET_OPTION(RSK_FOREST) != RO_FOREST_CLOSED;
break;
case GI_VB_BE_ELIGIBLE_FOR_DARUNIAS_JOY_REWARD:
*should = !Flags_GetRandomizerInf(RAND_INF_DARUNIAS_JOY);
break;
case GI_VB_BE_ELIGIBLE_FOR_LIGHT_ARROWS:
*should = !Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS) && MeetsLACSRequirements();
break;
case GI_VB_BE_ELIGIBLE_FOR_NOCTURNE_OF_SHADOW:
*should =
!Flags_GetEventChkInf(EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL) &&
LINK_IS_ADULT &&
gEntranceTable[((void)0, gSaveContext.entranceIndex)].scene == SCENE_KAKARIKO_VILLAGE &&
CHECK_QUEST_ITEM(QUEST_MEDALLION_FOREST) &&
CHECK_QUEST_ITEM(QUEST_MEDALLION_FIRE) &&
CHECK_QUEST_ITEM(QUEST_MEDALLION_WATER);
break;
case GI_VB_BE_ELIGIBLE_FOR_CHILD_ROLLING_GORON_REWARD: {
// Don't require a bomb bag to get prize in rando
*should = true;
break;
}
case GI_VB_BE_ELIGIBLE_FOR_MAGIC_BEANS_PURCHASE: {
if (RAND_GET_OPTION(RSK_SHUFFLE_MAGIC_BEANS)) {
*should = gSaveContext.rupees >= 60;
}
break;
}
case GI_VB_GIVE_ITEM_MASTER_SWORD:
if (RAND_GET_OPTION(RSK_SHUFFLE_MASTER_SWORD)) {
*should = false;
} else {
*should = true;
Rando::Context::GetInstance()->GetItemLocation(RC_TOT_MASTER_SWORD)->MarkAsObtained();
}
break;
case GI_VB_ITEM00_DESPAWN: {
EnItem00* item00 = static_cast<EnItem00*>(optionalArg);
if (item00->actor.params == ITEM00_HEART_PIECE || item00->actor.params == ITEM00_SMALL_KEY) {
RandomizerCheck rc = OTRGlobals::Instance->gRandomizer->GetCheckFromActor(item00->actor.id, gPlayState->sceneNum, item00->ogParams);
if (rc != RC_UNKNOWN_CHECK) {
item00->actor.params = ITEM00_SOH_DUMMY;
item00->itemEntry = Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, (GetItemID)Rando::StaticData::GetLocation(rc)->GetVanillaItem());
item00->actor.draw = (ActorFunc)EnItem00_DrawRandomizedItem;
*should = Rando::Context::GetInstance()->GetItemLocation(rc)->HasObtained();
}
} else if (item00->actor.params == ITEM00_SOH_GIVE_ITEM_ENTRY || item00->actor.params == ITEM00_SOH_GIVE_ITEM_ENTRY_GI) {
GetItemEntry itemEntry = randomizerQueuedItemEntry;
item00->itemEntry = itemEntry;
item00->actor.draw = (ActorFunc)EnItem00_DrawRandomizedItem;
}
break;
}
case GI_VB_MALON_ALREADY_TAUGHT_EPONAS_SONG: {
*should = Flags_GetRandomizerInf(RAND_INF_LEARNED_EPONA_SONG);
break;
}
case GI_VB_SET_CUCCO_COUNT: {
EnNiwLady* enNiwLady = static_cast<EnNiwLady*>(optionalArg);
// Override starting Cucco count using setting value
enNiwLady->cuccosInPen = 7 - RAND_GET_OPTION(RSK_CUCCO_COUNT);
*should = false;
break;
}
case GI_VB_KING_ZORA_THANK_CHILD: {
// Allow turning in Ruto's letter even if you have already rescued her
if (!Flags_GetEventChkInf(EVENTCHKINF_KING_ZORA_MOVED)) {
GET_PLAYER(gPlayState)->exchangeItemId = EXCH_ITEM_LETTER_RUTO;
}
*should = Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_JABU_JABUS_BELLY);
break;
}
case GI_VB_BE_ABLE_TO_EXCHANGE_RUTOS_LETTER: {
*should = LINK_IS_CHILD;
break;
}
case GI_VB_KING_ZORA_BE_MOVED: {
*should = false;
switch (RAND_GET_OPTION(RSK_ZORAS_FOUNTAIN)) {
case RO_ZF_CLOSED:
if (Flags_GetEventChkInf(EVENTCHKINF_KING_ZORA_MOVED)) {
*should = true;
}
break;
case RO_ZF_CLOSED_CHILD:
if (LINK_IS_ADULT) {
*should = true;
} else if (Flags_GetEventChkInf(EVENTCHKINF_KING_ZORA_MOVED)) {
*should = true;
}
break;
case RO_ZF_OPEN:
*should = true;
break;
}
break;
}
case GI_VB_BIGGORON_CONSIDER_SWORD_COLLECTED: {
*should = Flags_GetRandomizerInf(RAND_INF_ADULT_TRADES_DMT_TRADE_CLAIM_CHECK);
break;
}
case GI_VB_BIGGORON_CONSIDER_TRADE_COMPLETE: {
// This being true will prevent other biggoron trades, there are already safegaurds in place to prevent
// claim check from being traded multiple times, so we don't really need the quest to ever be considered "complete"
*should = false;
break;
}
case GI_VB_GORONS_CONSIDER_FIRE_TEMPLE_FINISHED: {
*should = Flags_GetEventChkInf(EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP);
break;
}
case GI_VB_GORONS_CONSIDER_DODONGOS_CAVERN_FINISHED: {
*should = Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP);
break;
}
case GI_VB_GORONS_CONSIDER_TUNIC_COLLECTED: {
*should = Flags_GetInfTable(INFTABLE_GORON_CITY_DOORS_UNLOCKED);
break;
}
case GI_VB_GIVE_ITEM_FROM_ITEM_00: {
EnItem00* item00 = static_cast<EnItem00*>(optionalArg);
if (item00->actor.params == ITEM00_SOH_DUMMY) {
Flags_SetCollectible(gPlayState, item00->collectibleFlag);
Actor_Kill(&item00->actor);
*should = false;
} else if (item00->actor.params == ITEM00_SOH_GIVE_ITEM_ENTRY) {
Audio_PlaySoundGeneral(NA_SE_SY_GET_ITEM, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
if (item00->itemEntry.modIndex == MOD_NONE) {
if (item00->itemEntry.getItemId == GI_SWORD_BGS) {
gSaveContext.bgsFlag = true;
}
Item_Give(gPlayState, item00->itemEntry.itemId);
} else if (item00->itemEntry.modIndex == MOD_RANDOMIZER) {
if (item00->itemEntry.getItemId == RG_ICE_TRAP) {
gSaveContext.pendingIceTrapCount++;
} else {
Randomizer_Item_Give(gPlayState, item00->itemEntry);
}
}
// EnItem00_SetupAction(item00, func_8001E5C8);
// *should = false;
} else if (item00->actor.params == ITEM00_SOH_GIVE_ITEM_ENTRY_GI) {
if (!Actor_HasParent(&item00->actor, gPlayState)) {
GiveItemEntryFromActorWithFixedRange(&item00->actor, gPlayState, item00->itemEntry);
}
EnItem00_SetupAction(item00, func_8001E5C8);
*should = false;
}
break;
}
case GI_VB_BE_ELIGIBLE_FOR_SARIAS_SONG: {
*should = !Flags_GetEventChkInf(EVENTCHKINF_LEARNED_SARIAS_SONG);
break;
}
case GI_VB_GIVE_ITEM_FROM_COW: {
if (!RAND_GET_OPTION(RSK_SHUFFLE_COWS)) {
break;
}
EnCow* enCow = static_cast<EnCow*>(optionalArg);
CowIdentity cowIdentity = OTRGlobals::Instance->gRandomizer->IdentifyCow(gPlayState->sceneNum, enCow->actor.world.pos.x, enCow->actor.world.pos.z);
// Has this cow already rewarded an item?
if (Flags_GetRandomizerInf(cowIdentity.randomizerInf)) {
break;
}
Flags_SetRandomizerInf(cowIdentity.randomizerInf);
// setting the ocarina mode here prevents intermittent issues
// with the item get not triggering until walking away
gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_00;
*should = false;
break;
}
case GI_VB_GIVE_ITEM_FROM_GRANNYS_SHOP: {
if (!EnDs_RandoCanGetGrannyItem()) {
break;
}
// Only setting the inf if we've actually gotten the rando item and not the vanilla blue potion
Flags_SetRandomizerInf(RAND_INF_MERCHANTS_GRANNYS_SHOP);
*should = false;
break;
}
case GI_VB_GIVE_ITEM_FROM_THAWING_KING_ZORA: {
EnKz* enKz = static_cast<EnKz*>(optionalArg);
// If we aren't setting up the item offer, then we're just checking if it should be possible.
if (enKz->actionFunc != (EnKzActionFunc)EnKz_SetupGetItem) {
// Always give the reward in rando
*should = true;
break;
}
*should = false;
break;
}
case GI_VB_GIVE_ITEM_FROM_ANJU_AS_CHILD: {
Flags_SetItemGetInf(ITEMGETINF_0C);
*should = false;
break;
}
case GI_VB_GIVE_ITEM_FROM_ANJU_AS_ADULT: {
Flags_SetItemGetInf(ITEMGETINF_2C);
*should = false;
break;
}
case GI_VB_GIVE_ITEM_FROM_CARPET_SALESMAN: {
*should = RAND_GET_OPTION(RSK_SHUFFLE_MERCHANTS) == RO_SHUFFLE_MERCHANTS_OFF ||
// If the rando check has already been awarded, use vanilla behavior.
Flags_GetRandomizerInf(RAND_INF_MERCHANTS_CARPET_SALESMAN);
break;
}
case GI_VB_GIVE_ITEM_FROM_MEDIGORON: {
// fallthrough
case GI_VB_BE_ELIGIBLE_FOR_GIANTS_KNIFE_PURCHASE:
if (RAND_GET_OPTION(RSK_SHUFFLE_MERCHANTS) != RO_SHUFFLE_MERCHANTS_OFF &&
!Flags_GetRandomizerInf(RAND_INF_MERCHANTS_MEDIGORON)) {
if (id == GI_VB_GIVE_ITEM_FROM_MEDIGORON) {
Flags_SetInfTable(INFTABLE_B1);
*should = false;
} else {
// Resets "Talked to Medigoron" flag in infTable to restore initial conversation state
Flags_UnsetInfTable(INFTABLE_B1);
*should = true;
}
}
break;
}
case GI_VB_GIVE_ITEM_FROM_MAGIC_BEAN_SALESMAN: {
EnMs* enMs = static_cast<EnMs*>(optionalArg);
if (RAND_GET_OPTION(RSK_SHUFFLE_MAGIC_BEANS)) {
Rupees_ChangeBy(-60);
BEANS_BOUGHT = 10;
// Only set inf for buying rando check
Flags_SetRandomizerInf(RAND_INF_MERCHANTS_MAGIC_BEAN_SALESMAN);
enMs->actionFunc = (EnMsActionFunc)EnMs_Wait;
*should = false;
}
break;
}
case GI_VB_GIVE_ITEM_FROM_FROGS: {
EnFr* enFr = static_cast<EnFr*>(optionalArg);
// Skip GiveReward+SetIdle action func if the reward is an ice trap
if (enFr->actionFunc == (EnFrActionFunc)EnFr_GiveReward) {
RandomizerCheck rc = EnFr_RandomizerCheckFromSongIndex(enFr->songIndex);
GetItemEntry gi = Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, (GetItemID)Rando::StaticData::GetLocation(rc)->GetVanillaItem());
if (gi.getItemId == RG_ICE_TRAP) {
enFr->actionFunc = (EnFrActionFunc)EnFr_Idle;
}
}
*should = false;
break;
}
case GI_VB_TRADE_POCKET_CUCCO: {
Randomizer_ConsumeAdultTradeItem(gPlayState, ITEM_POCKET_CUCCO);
// Trigger the reward now
Flags_SetItemGetInf(ITEMGETINF_2E);
*should = false;
break;
}
case GI_VB_TRADE_COJIRO: {
Randomizer_ConsumeAdultTradeItem(gPlayState, ITEM_COJIRO);
*should = false;
break;
}
case GI_VB_TRADE_ODD_MUSHROOM: {
Randomizer_ConsumeAdultTradeItem(gPlayState, ITEM_ODD_MUSHROOM);
// Trigger the reward now
Flags_SetItemGetInf(ITEMGETINF_30);
*should = false;
break;
}
case GI_VB_TRADE_ODD_POTION: {
EnKo* enKo = static_cast<EnKo*>(optionalArg);
Randomizer_ConsumeAdultTradeItem(gPlayState, ITEM_ODD_POTION);
// Trigger the reward now
Flags_SetItemGetInf(ITEMGETINF_31);
*should = false;
break;
}
case GI_VB_TRADE_SAW: {
Randomizer_ConsumeAdultTradeItem(gPlayState, ITEM_SAW);
*should = false;
break;
}
case GI_VB_TRADE_PRESCRIPTION: {
EnKz* enKz = static_cast<EnKz*>(optionalArg);
// If we aren't setting up the item offer, then we're just checking if it should be possible.
if (enKz->actionFunc != (EnKzActionFunc)EnKz_SetupGetItem) {
*should = !Flags_GetRandomizerInf(RAND_INF_ADULT_TRADES_ZD_TRADE_PRESCRIPTION);
break;
}
Randomizer_ConsumeAdultTradeItem(gPlayState, ITEM_PRESCRIPTION);
*should = false;
break;
}
case GI_VB_TRADE_FROG: {
Randomizer_ConsumeAdultTradeItem(gPlayState, ITEM_FROG);
*should = false;
break;
}
case GI_VB_DESPAWN_HORSE_RACE_COW: {
if (!RAND_GET_OPTION(RSK_SHUFFLE_COWS)) {
break;
}
EnCow* enCow = static_cast<EnCow*>(optionalArg);
// If this is a cow we have to move, then move it now.
EnCow_MoveForRandomizer(enCow, gPlayState);
break;
}
case GI_VB_BUSINESS_SCRUB_DESPAWN: {
EnShopnuts* enShopnuts = static_cast<EnShopnuts*>(optionalArg);
s16 respawnData = gSaveContext.respawn[RESPAWN_MODE_RETURN].data & ((1 << 8) - 1);
ScrubIdentity scrubIdentity = OTRGlobals::Instance->gRandomizer->IdentifyScrub(gPlayState->sceneNum, enShopnuts->actor.params, respawnData);
if (scrubIdentity.isShuffled) {
*should = Flags_GetRandomizerInf(scrubIdentity.randomizerInf);
}
break;
}
case GI_VB_GIVE_ITEM_FROM_BUSINESS_SCRUB: {
EnDns* enDns = static_cast<EnDns*>(optionalArg);
*should = !enDns->sohScrubIdentity.isShuffled;
break;
}
// To explain the logic because Fado and Grog are linked:
// - If you have Cojiro, then spawn Grog and not Fado.
// - If you don't have Cojiro but do have Odd Potion, spawn Fado and not Grog.
// - If you don't have either, spawn Grog if you haven't traded the Odd Mushroom.
// - If you don't have either but have traded the mushroom, don't spawn either.
case GI_VB_DESPAWN_GROG: {
if (!RAND_GET_OPTION(RSK_SHUFFLE_ADULT_TRADE)) {
break;
}
if (PLAYER_HAS_SHUFFLED_ADULT_TRADE_ITEM(ITEM_COJIRO)) {
*should = false;
} else if (PLAYER_HAS_SHUFFLED_ADULT_TRADE_ITEM(ITEM_ODD_POTION)) {
*should = true;
} else {
*should = Flags_GetItemGetInf(ITEMGETINF_30); // Traded odd mushroom
}
break;
}
case GI_VB_SPAWN_LW_FADO: {
if (!RAND_GET_OPTION(RSK_SHUFFLE_ADULT_TRADE)) {
break;
}
if (PLAYER_HAS_SHUFFLED_ADULT_TRADE_ITEM(ITEM_COJIRO)) {
*should = false;
} else {
*should = PLAYER_HAS_SHUFFLED_ADULT_TRADE_ITEM(ITEM_ODD_POTION);
}
break;
}
case GI_VB_USE_EYEDROP_DIALOGUE: {
// Skip eye drop text on rando if Link went in the water, so you can still receive the dive check
EnMk* enMk = static_cast<EnMk*>(optionalArg);
*should &= enMk->swimFlag == 0;
break;
}
case GI_VB_OFFER_BLUE_POTION: {
// Always offer blue potion when adult trade is off
*should |= RAND_GET_OPTION(RSK_SHUFFLE_ADULT_TRADE) == RO_GENERIC_OFF;
break;
}
case GI_VB_NEED_BOTTLE_FOR_GRANNYS_ITEM: {
// Allow buying the rando item regardless of having a bottle
*should &= !EnDs_RandoCanGetGrannyItem();
break;
}
case GI_VB_GIVE_ITEM_FROM_SHOOTING_GALLERY: {
EnSyatekiMan* enSyatekiMan = static_cast<EnSyatekiMan*>(optionalArg);
enSyatekiMan->getItemId = GI_RUPEE_PURPLE;
if (LINK_IS_ADULT) {
// Give purple rupee if we've already obtained the reward OR we don't have a bow
*should = Flags_GetItemGetInf(ITEMGETINF_0E) || CUR_UPG_VALUE(UPG_QUIVER) == 0;
} else {
// Give purple rupee if we've already obtained the reward
*should = Flags_GetItemGetInf(ITEMGETINF_0D);
}
break;
}
case GI_VB_BE_ELIGIBLE_FOR_ADULT_SHOOTING_GAME_REWARD: {
*should = CUR_UPG_VALUE(UPG_QUIVER) > 0;
if (!*should) {
// In Rando without a quiver, display a message reminding the player to come back with a bow
Message_StartTextbox(gPlayState, TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW, NULL);
}
break;
}
case GI_VB_BE_ELIGIBLE_TO_OPEN_DOT: {
bool eligible = RAND_GET_OPTION(RSK_DOOR_OF_TIME) != RO_DOOROFTIME_CLOSED || (
INV_CONTENT(ITEM_OCARINA_FAIRY) == ITEM_OCARINA_TIME &&
CHECK_QUEST_ITEM(QUEST_KOKIRI_EMERALD) &&
CHECK_QUEST_ITEM(QUEST_GORON_RUBY) &&
CHECK_QUEST_ITEM(QUEST_ZORA_SAPPHIRE)
);
*should = eligible;
break;
}
case GI_VB_GIVE_ITEM_FROM_HORSEBACK_ARCHERY: {
// give both rewards at the same time
if (gSaveContext.minigameScore >= 1500) {
Flags_SetItemGetInf(ITEMGETINF_0F);
}
*should = false;
break;
}
case GI_VB_GIVE_ITEM_FROM_SKULLTULA_REWARD: {
// In z_en_sth.c the rewards are stored in sGetItemIds, the first entry
// in that array is GI_RUPEE_GOLD, and the reward is picked in EnSth_GivePlayerItem
// via sGetItemIds[this->actor.params]. This means if actor.params == 0 we're looking
// at the 100 GS reward
EnSth* enSth = static_cast<EnSth*>(optionalArg);
if (enSth->actor.params == 0) {
// if nothing is shuffled onto 100 GS,
// or we already got the 100 GS reward,
// let the player farm
if (!RAND_GET_OPTION(RSK_SHUFFLE_100_GS_REWARD) ||
Flags_GetRandomizerInf(RAND_INF_KAK_100_GOLD_SKULLTULA_REWARD)) {
*should = true;
break;
}
// we're giving the 100 GS rando reward! set the rando inf
Flags_SetRandomizerInf(RAND_INF_KAK_100_GOLD_SKULLTULA_REWARD);
// also set the actionfunc so this doesn't immediately get
// called again (and lead to a vanilla+rando item give
// because the flag check will pass next time)
enSth->actionFunc = (EnSthActionFunc)EnSth_RewardObtainedTalk;
}
*should = false;
break;
}
case GI_VB_TRADE_TIMER_ODD_MUSHROOM:
case GI_VB_TRADE_TIMER_EYEDROPS:
case GI_VB_TRADE_TIMER_FROG:
case GI_VB_ANJU_SET_OBTAINED_TRADE_ITEM:
case GI_VB_GIVE_ITEM_FROM_TARGET_IN_WOODS:
case GI_VB_GIVE_ITEM_FROM_TALONS_CHICKENS:
case GI_VB_GIVE_ITEM_FROM_DIVING_MINIGAME:
case GI_VB_GIVE_ITEM_FROM_GORON:
case GI_VB_GIVE_ITEM_FROM_LAB_DIVE:
case GI_VB_GIVE_ITEM_FROM_SKULL_KID_SARIAS_SONG:
case GI_VB_GIVE_ITEM_FROM_MAN_ON_ROOF:
case GI_VB_GIVE_ITEM_SKULL_TOKEN:
case GI_VB_GIVE_ITEM_FROM_BLUE_WARP:
case GI_VB_GIVE_ITEM_FAIRY_OCARINA:
case GI_VB_GIVE_ITEM_WEIRD_EGG:
case GI_VB_GIVE_ITEM_LIGHT_ARROW:
case GI_VB_GIVE_ITEM_STRENGTH_1:
case GI_VB_GIVE_ITEM_ZELDAS_LETTER:
case GI_VB_GIVE_ITEM_OCARINA_OF_TIME:
case GI_VB_GIVE_ITEM_KOKIRI_EMERALD:
case GI_VB_GIVE_ITEM_GORON_RUBY:
case GI_VB_GIVE_ITEM_ZORA_SAPPHIRE:
case GI_VB_GIVE_ITEM_LIGHT_MEDALLION:
case GI_VB_GIVE_ITEM_FOREST_MEDALLION:
case GI_VB_GIVE_ITEM_FIRE_MEDALLION:
case GI_VB_GIVE_ITEM_WATER_MEDALLION:
case GI_VB_GIVE_ITEM_SPIRIT_MEDALLION:
case GI_VB_GIVE_ITEM_SHADOW_MEDALLION:
*should = false;
break;
}
}
void RandomizerOnSceneInitHandler(int16_t sceneNum) {
// Treasure Chest Game
// todo: for now we're just unsetting all of them, we will
// probably need to do something different when we implement shuffle
if (sceneNum == SCENE_TREASURE_BOX_SHOP) {
Flags_UnsetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_ITEM_1);
Rando::Context::GetInstance()->GetItemLocation(RC_MARKET_TREASURE_CHEST_GAME_ITEM_1)->MarkAsNotObtained();
Flags_UnsetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_ITEM_2);
Rando::Context::GetInstance()->GetItemLocation(RC_MARKET_TREASURE_CHEST_GAME_ITEM_2)->MarkAsNotObtained();
Flags_UnsetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_ITEM_3);
Rando::Context::GetInstance()->GetItemLocation(RC_MARKET_TREASURE_CHEST_GAME_ITEM_3)->MarkAsNotObtained();
Flags_UnsetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_ITEM_4);
Rando::Context::GetInstance()->GetItemLocation(RC_MARKET_TREASURE_CHEST_GAME_ITEM_4)->MarkAsNotObtained();
Flags_UnsetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_ITEM_5);
Rando::Context::GetInstance()->GetItemLocation(RC_MARKET_TREASURE_CHEST_GAME_ITEM_5)->MarkAsNotObtained();
Flags_UnsetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_KEY_1);
Rando::Context::GetInstance()->GetItemLocation(RC_MARKET_TREASURE_CHEST_GAME_KEY_1)->MarkAsNotObtained();
Flags_UnsetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_KEY_2);
Rando::Context::GetInstance()->GetItemLocation(RC_MARKET_TREASURE_CHEST_GAME_KEY_2)->MarkAsNotObtained();
Flags_UnsetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_KEY_3);
Rando::Context::GetInstance()->GetItemLocation(RC_MARKET_TREASURE_CHEST_GAME_KEY_3)->MarkAsNotObtained();
Flags_UnsetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_KEY_4);
Rando::Context::GetInstance()->GetItemLocation(RC_MARKET_TREASURE_CHEST_GAME_KEY_4)->MarkAsNotObtained();
Flags_UnsetRandomizerInf(RAND_INF_MARKET_TREASURE_CHEST_GAME_KEY_5);
Rando::Context::GetInstance()->GetItemLocation(RC_MARKET_TREASURE_CHEST_GAME_KEY_5)->MarkAsNotObtained();
}
// LACs & Prelude checks
static uint32_t updateHook = 0;
if (updateHook) {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayerUpdate>(updateHook);
updateHook = 0;
}
if (
sceneNum != SCENE_TEMPLE_OF_TIME ||
(
Flags_GetEventChkInf(EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT) &&
Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS)
)
) return;
updateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>([]() {
if (!Flags_GetEventChkInf(EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT) && LINK_IS_ADULT && CHECK_QUEST_ITEM(QUEST_MEDALLION_FOREST) && gPlayState->roomCtx.curRoom.num == 0) {
Flags_SetEventChkInf(EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT);
}
if (!Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS) && MeetsLACSRequirements()) {
Flags_SetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS);
}
if (
Flags_GetEventChkInf(EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT) &&
Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS)
) {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayerUpdate>(updateHook);
}
});
}
void EnSi_DrawRandomizedItem(EnSi* enSi, PlayState* play) {
func_8002ED80(&enSi->actor, play, 0);
func_8002EBCC(&enSi->actor, play, 0);
EnItem00_CustomItemsParticles(&enSi->actor, play, enSi->sohGetItemEntry);
GetItemEntry_Draw(play, enSi->sohGetItemEntry);
}
u32 EnDns_RandomizerPurchaseableCheck(EnDns* enDns) {
if (Flags_GetRandomizerInf(enDns->sohScrubIdentity.randomizerInf)) {
return 3; // Can't get this now
}
if (gSaveContext.rupees < enDns->dnsItemEntry->itemPrice) {
return 0; // Not enough rupees
}
return 4;
}
void EnDns_RandomizerPurchase(EnDns* enDns) {
Rupees_ChangeBy(-enDns->dnsItemEntry->itemPrice);
Flags_SetRandomizerInf(enDns->sohScrubIdentity.randomizerInf);
}
void RandomizerOnActorInitHandler(void* actorRef) {
Actor* actor = static_cast<Actor*>(actorRef);
if (actor->id == ACTOR_EN_SI) {
RandomizerCheck rc = OTRGlobals::Instance->gRandomizer->GetCheckFromActor(actor->id, gPlayState->sceneNum, actor->params);
if (rc != RC_UNKNOWN_CHECK) {
EnSi* enSi = static_cast<EnSi*>(actorRef);
enSi->sohGetItemEntry = Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, (GetItemID)Rando::StaticData::GetLocation(rc)->GetVanillaItem());
actor->draw = (ActorFunc)EnSi_DrawRandomizedItem;
}
}
if (actor->id == ACTOR_ITEM_B_HEART) {
ItemBHeart* itemBHeart = static_cast<ItemBHeart*>(actorRef);
RandomizerCheck rc = OTRGlobals::Instance->gRandomizer->GetCheckFromActor(itemBHeart->actor.id, gPlayState->sceneNum, itemBHeart->actor.params);
if (rc != RC_UNKNOWN_CHECK) {
itemBHeart->sohItemEntry = Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, (GetItemID)Rando::StaticData::GetLocation(rc)->GetVanillaItem());
itemBHeart->actor.draw = (ActorFunc)ItemBHeart_DrawRandomizedItem;
itemBHeart->actor.update = (ActorFunc)ItemBHeart_UpdateRandomizedItem;
}
}
if (actor->id == ACTOR_EN_DNS) {
EnDns* enDns = static_cast<EnDns*>(actorRef);
s16 respawnData = gSaveContext.respawn[RESPAWN_MODE_RETURN].data & ((1 << 8) - 1);
enDns->sohScrubIdentity = OTRGlobals::Instance->gRandomizer->IdentifyScrub(gPlayState->sceneNum, enDns->actor.params, respawnData);
if (enDns->sohScrubIdentity.isShuffled) {
// DNS uses pointers so we're creating our own entry instead of modifying the original
enDns->sohDnsItemEntry = {
enDns->dnsItemEntry->itemPrice,
1,
enDns->sohScrubIdentity.getItemId,
EnDns_RandomizerPurchaseableCheck,
EnDns_RandomizerPurchase,
};
enDns->dnsItemEntry = &enDns->sohDnsItemEntry;
if (enDns->sohScrubIdentity.itemPrice != -1) {
enDns->dnsItemEntry->itemPrice = enDns->sohScrubIdentity.itemPrice;
}
enDns->actor.textId = TEXT_SCRUB_RANDOM;
static uint32_t enDnsUpdateHook = 0;
static uint32_t enDnsKillHook = 0;
if (!enDnsUpdateHook) {
enDnsUpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) {
Actor* innerActor = static_cast<Actor*>(innerActorRef);
if (innerActor->id == ACTOR_EN_DNS) {
EnDns* innerEnDns = static_cast<EnDns*>(innerActorRef);
if (innerEnDns->sohScrubIdentity.isShuffled) {
innerActor->textId = TEXT_SCRUB_RANDOM;
}
}
});
enDnsKillHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(enDnsUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(enDnsKillHook);
enDnsUpdateHook = 0;
enDnsKillHook = 0;
});
}
}
}
if (actor->id == ACTOR_ITEM_ETCETERA) {
ItemEtcetera* itemEtcetera = static_cast<ItemEtcetera*>(actorRef);
RandomizerCheck rc = OTRGlobals::Instance->gRandomizer->GetCheckFromActor(itemEtcetera->actor.id, gPlayState->sceneNum, itemEtcetera->actor.params);
if (rc != RC_UNKNOWN_CHECK) {
itemEtcetera->sohItemEntry = Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, (GetItemID)Rando::StaticData::GetLocation(rc)->GetVanillaItem());
itemEtcetera->drawFunc = (ActorFunc)ItemEtcetera_DrawRandomizedItem;
}
int32_t type = itemEtcetera->actor.params & 0xFF;
switch (type) {
case ITEM_ETC_LETTER: {
itemEtcetera->futureActionFunc = (ItemEtceteraActionFunc)ItemEtcetera_func_80B858B4_Randomized;
break;
}
case ITEM_ETC_ARROW_FIRE: {
itemEtcetera->futureActionFunc = (ItemEtceteraActionFunc)ItemEtcetera_UpdateRandomizedFireArrow;
break;
}
case ITEM_ETC_RUPEE_GREEN_CHEST_GAME:
case ITEM_ETC_RUPEE_BLUE_CHEST_GAME:
case ITEM_ETC_RUPEE_RED_CHEST_GAME:
case ITEM_ETC_RUPEE_PURPLE_CHEST_GAME:
case ITEM_ETC_HEART_PIECE_CHEST_GAME:
case ITEM_ETC_KEY_SMALL_CHEST_GAME: {
if (rc != RC_UNKNOWN_CHECK) {
itemEtcetera->drawFunc = (ActorFunc)ItemEtcetera_DrawRandomizedItemThroughLens;
}
break;
}
}
}
}
void RandomizerRegisterHooks() {
static uint32_t onFlagSetHook = 0;
static uint32_t onSceneFlagSetHook = 0;
static uint32_t onPlayerUpdateForRCQueueHook = 0;
static uint32_t onPlayerUpdateForItemQueueHook = 0;
static uint32_t onItemReceiveHook = 0;
static uint32_t onVanillaBehaviorHook = 0;
static uint32_t onSceneInitHook = 0;
static uint32_t onActorInitHook = 0;
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([](int32_t fileNum) {
randomizerQueuedChecks = std::queue<RandomizerCheck>();
randomizerQueuedCheck = RC_UNKNOWN_CHECK;
randomizerQueuedItemEntry = GET_ITEM_NONE;
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnFlagSet>(onFlagSetHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneFlagSet>(onSceneFlagSetHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayerUpdate>(onPlayerUpdateForRCQueueHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayerUpdate>(onPlayerUpdateForItemQueueHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnItemReceive>(onItemReceiveHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnVanillaBehavior>(onVanillaBehaviorHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(onSceneInitHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorInit>(onActorInitHook);
onFlagSetHook = 0;
onSceneFlagSetHook = 0;
onPlayerUpdateForRCQueueHook = 0;
onPlayerUpdateForItemQueueHook = 0;
onItemReceiveHook = 0;
onVanillaBehaviorHook = 0;
onSceneInitHook = 0;
onActorInitHook = 0;
if (!IS_RANDO) return;
onFlagSetHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagSet>(RandomizerOnFlagSetHandler);
onSceneFlagSetHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneFlagSet>(RandomizerOnSceneFlagSetHandler);
onPlayerUpdateForRCQueueHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>(RandomizerOnPlayerUpdateForRCQueueHandler);
onPlayerUpdateForItemQueueHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>(RandomizerOnPlayerUpdateForItemQueueHandler);
onItemReceiveHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>(RandomizerOnItemReceiveHandler);
onVanillaBehaviorHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnVanillaBehavior>(RandomizerOnVanillaBehaviorHandler);
onSceneInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>(RandomizerOnSceneInitHandler);
onActorInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>(RandomizerOnActorInitHandler);
});
}