mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-11-25 02:42:18 -05:00
Entrance Rando (#1760)
This commit is contained in:
parent
8be2c4ddd7
commit
15a9975200
@ -188,6 +188,8 @@ source_group("Header Files\\soh\\Enhancements\\debugger" FILES ${Header_Files__s
|
||||
|
||||
set(Header_Files__soh__Enhancements__randomizer
|
||||
"soh/Enhancements/randomizer/randomizer.h"
|
||||
"soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
"soh/Enhancements/randomizer/randomizer_grotto.h"
|
||||
"soh/Enhancements/randomizer/randomizer_inf.h"
|
||||
"soh/Enhancements/randomizer/randomizer_item_tracker.h"
|
||||
"soh/Enhancements/randomizer/adult_trade_shuffle.h"
|
||||
@ -314,6 +316,8 @@ source_group("Source Files\\soh\\Enhancements\\debugger" FILES ${Source_Files__s
|
||||
|
||||
set(Source_Files__soh__Enhancements__randomizer
|
||||
"soh/Enhancements/randomizer/randomizer.cpp"
|
||||
"soh/Enhancements/randomizer/randomizer_entrance.c"
|
||||
"soh/Enhancements/randomizer/randomizer_grotto.c"
|
||||
"soh/Enhancements/randomizer/randomizer_item_tracker.cpp"
|
||||
"soh/Enhancements/randomizer/adult_trade_shuffle.c"
|
||||
"soh/Enhancements/randomizer/randomizer_check_objects.cpp"
|
||||
|
@ -1181,6 +1181,7 @@ typedef struct {
|
||||
/* */ s32 returnEntranceIndex;
|
||||
/* */ s8 roomIndex;
|
||||
/* */ s8 data;
|
||||
/* */ s8 exitScene;
|
||||
/* */ Vec3f pos;
|
||||
} BetterSceneSelectGrottoData;
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "z64audio.h"
|
||||
#include "soh/Enhancements/randomizer/randomizerTypes.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_inf.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
typedef struct {
|
||||
/* 0x00 */ u8 buttonItems[8];
|
||||
@ -185,6 +186,7 @@ typedef struct {
|
||||
RandoSetting randoSettings[300];
|
||||
ItemLocationRando itemLocations[RC_MAX];
|
||||
HintLocationRando hintLocations[50];
|
||||
EntranceOverride entranceOverrides[ENTRANCE_OVERRIDES_MAX_COUNT];
|
||||
char childAltarText[250];
|
||||
char adultAltarText[750];
|
||||
char ganonHintText[150];
|
||||
|
@ -7,60 +7,11 @@
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include "../randomizer_entrance.h"
|
||||
|
||||
#define ENTRANCE_SHUFFLE_SUCCESS 0
|
||||
#define ENTRANCE_SHUFFLE_FAILURE 1
|
||||
|
||||
|
||||
typedef struct {
|
||||
int16_t index;
|
||||
int16_t destination;
|
||||
int16_t blueWarp;
|
||||
int16_t override;
|
||||
int16_t overrideDestination;
|
||||
} EntranceOverride;
|
||||
|
||||
typedef enum {
|
||||
ENTRANCE_GROUP_NO_GROUP,
|
||||
ENTRANCE_GROUP_KOKIRI_FOREST,
|
||||
ENTRANCE_GROUP_LOST_WOODS,
|
||||
ENTRANCE_GROUP_KAKARIKO,
|
||||
ENTRANCE_GROUP_GRAVEYARD,
|
||||
ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL,
|
||||
ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER,
|
||||
ENTRANCE_GROUP_GORON_CITY,
|
||||
ENTRANCE_GROUP_ZORAS_DOMAIN,
|
||||
ENTRANCE_GROUP_HYRULE_FIELD,
|
||||
ENTRANCE_GROUP_LON_LON_RANCH,
|
||||
ENTRANCE_GROUP_LAKE_HYLIA,
|
||||
ENTRANCE_GROUP_GERUDO_VALLEY,
|
||||
ENTRANCE_GROUP_HAUNTED_WASTELAND,
|
||||
ENTRANCE_GROUP_MARKET,
|
||||
ENTRANCE_GROUP_HYRULE_CASTLE,
|
||||
SPOILER_ENTRANCE_GROUP_COUNT,
|
||||
} SpoilerEntranceGroup;
|
||||
|
||||
typedef enum {
|
||||
ENTRANCE_TYPE_OVERWORLD,
|
||||
ENTRANCE_TYPE_INTERIOR,
|
||||
ENTRANCE_TYPE_GROTTO,
|
||||
ENTRANCE_TYPE_DUNGEON,
|
||||
ENTRANCE_TYPE_COUNT,
|
||||
} TrackerEntranceType;
|
||||
|
||||
typedef struct {
|
||||
int16_t index;
|
||||
char* name;
|
||||
SpoilerEntranceGroup group;
|
||||
TrackerEntranceType type;
|
||||
uint8_t oneExit;
|
||||
} EntranceData;
|
||||
|
||||
typedef struct {
|
||||
uint8_t EntranceCount;
|
||||
uint16_t GroupEntranceCounts[SPOILER_ENTRANCE_GROUP_COUNT];
|
||||
uint16_t GroupOffsets[SPOILER_ENTRANCE_GROUP_COUNT];
|
||||
} EntranceTrackingData;
|
||||
|
||||
extern std::list<EntranceOverride> entranceOverrides;
|
||||
|
||||
enum class EntranceType {
|
||||
@ -314,7 +265,6 @@ private:
|
||||
|
||||
int ShuffleAllEntrances();
|
||||
void CreateEntranceOverrides();
|
||||
EntranceTrackingData* GetEntranceTrackingData();
|
||||
|
||||
extern std::vector<std::list<Entrance*>> playthroughEntrances;
|
||||
extern bool noRandomEntrances;
|
||||
|
@ -122,7 +122,7 @@ void AreaTable_Init_DeathMountain() {
|
||||
Entrance(GC_SHOP, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || (IsChild && (CanBlastOrSmash || GoronBracelet || GoronCityChildFire || CanUse(BOW)));},
|
||||
/*Glitched*/[]{return IsChild && Sticks && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED);}}),
|
||||
Entrance(GC_DARUNIAS_CHAMBER, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || GCDaruniasDoorOpenChild;}}),
|
||||
Entrance(GC_GROTTO, {[]{return IsAdult && ((CanPlay(SongOfTime) && ((EffectiveHealth > 2) || CanUse(GORON_TUNIC) || CanUse(LONGSHOT) || CanUse(NAYRUS_LOVE))) || (EffectiveHealth > 1 && CanUse(GORON_TUNIC) && CanUse(HOOKSHOT)) || (CanUse(NAYRUS_LOVE) && CanUse(HOOKSHOT)));},
|
||||
Entrance(GC_GROTTO_PLATFORM, {[]{return IsAdult && ((CanPlay(SongOfTime) && ((EffectiveHealth > 2) || CanUse(GORON_TUNIC) || CanUse(LONGSHOT) || CanUse(NAYRUS_LOVE))) || (EffectiveHealth > 1 && CanUse(GORON_TUNIC) && CanUse(HOOKSHOT)) || (CanUse(NAYRUS_LOVE) && CanUse(HOOKSHOT)));},
|
||||
/*Glitched*/[]{return (HasBombchus && ((IsChild && CanDoGlitch(GlitchType::BombHover, GlitchDifficulty::NOVICE)) || CanDoGlitch(GlitchType::BombHover, GlitchDifficulty::INTERMEDIATE))) || (IsChild && CanUse(LONGSHOT));}}),
|
||||
});
|
||||
|
||||
|
@ -2563,6 +2563,13 @@ namespace Settings {
|
||||
ShuffleKokiriSword.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_KOKIRI_SWORD]);
|
||||
ShuffleOcarinas.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_OCARINA]);
|
||||
|
||||
// Shuffle Entrances
|
||||
ShuffleEntrances.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_ENTRANCES]);
|
||||
ShuffleDungeonEntrances.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_DUNGEON_ENTRANCES]);
|
||||
ShuffleOverworldEntrances.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_OVERWORLD_ENTRANCES]);
|
||||
ShuffleInteriorEntrances.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_INTERIOR_ENTRANCES]);
|
||||
ShuffleGrottoEntrances.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_GROTTO_ENTRANCES]);
|
||||
|
||||
// if we skip child zelda, we start with zelda's letter, and malon starts
|
||||
// at the ranch, so we should *not* shuffle the weird egg
|
||||
if(cvarSettings[RSK_SKIP_CHILD_ZELDA]) {
|
||||
|
@ -296,25 +296,45 @@ static void WriteLocation(
|
||||
}
|
||||
|
||||
//Writes a shuffled entrance to the specified node
|
||||
static void WriteShuffledEntrance(
|
||||
tinyxml2::XMLElement* parentNode,
|
||||
Entrance* entrance,
|
||||
const bool withPadding = false
|
||||
) {
|
||||
auto node = parentNode->InsertNewChildElement("entrance");
|
||||
node->SetAttribute("name", entrance->GetName().c_str());
|
||||
auto text = entrance->GetConnectedRegion()->regionName + " from " + entrance->GetReplacement()->GetParentRegion()->regionName;
|
||||
node->SetText(text.c_str());
|
||||
static void WriteShuffledEntrance(std::string sphereString, Entrance* entrance) {
|
||||
int16_t originalIndex = entrance->GetIndex();
|
||||
int16_t destinationIndex = entrance->GetReverse()->GetIndex();
|
||||
int16_t originalBlueWarp = entrance->GetBlueWarp();
|
||||
int16_t replacementBlueWarp = entrance->GetReplacement()->GetReverse()->GetBlueWarp();
|
||||
int16_t replacementIndex = entrance->GetReplacement()->GetIndex();
|
||||
int16_t replacementDestinationIndex = entrance->GetReplacement()->GetReverse()->GetIndex();
|
||||
std::string name = entrance->GetName();
|
||||
std::string text = entrance->GetConnectedRegion()->regionName + " from " + entrance->GetReplacement()->GetParentRegion()->regionName;
|
||||
|
||||
if (withPadding) {
|
||||
constexpr int16_t LONGEST_NAME = 56; //The longest name of a vanilla entrance
|
||||
switch (gSaveContext.language) {
|
||||
case LANGUAGE_ENG:
|
||||
case LANGUAGE_FRA:
|
||||
default:
|
||||
json entranceJson = json::object({
|
||||
{"index", originalIndex},
|
||||
{"destination", destinationIndex},
|
||||
{"blueWarp", originalBlueWarp},
|
||||
{"override", replacementIndex},
|
||||
{"overrideDestination", replacementDestinationIndex},
|
||||
});
|
||||
|
||||
//Insert padding so we get a kind of table in the XML document
|
||||
int16_t requiredPadding = LONGEST_NAME - entrance->GetName().length();
|
||||
if (requiredPadding > 0) {
|
||||
std::string padding(requiredPadding, ' ');
|
||||
node->SetAttribute("_", padding.c_str());
|
||||
jsonData["entrances"].push_back(entranceJson);
|
||||
|
||||
// When decoupled entrances is off, handle saving reverse entrances with blue warps
|
||||
if (!false) { // RANDOTODO: add check for decoupled entrances
|
||||
json reverseEntranceJson = json::object({
|
||||
{"index", replacementDestinationIndex},
|
||||
{"destination", replacementIndex},
|
||||
{"blueWarp", replacementBlueWarp},
|
||||
{"override", destinationIndex},
|
||||
{"overrideDestination", originalIndex},
|
||||
});
|
||||
|
||||
jsonData["entrances"].push_back(reverseEntranceJson);
|
||||
}
|
||||
|
||||
jsonData["entrancesMap"][sphereString][name] = text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -512,7 +532,7 @@ static void WritePlaythrough() {
|
||||
for (uint32_t i = 0; i < playthroughLocations.size(); ++i) {
|
||||
auto sphereNum = std::to_string(i);
|
||||
std::string sphereString = "sphere ";
|
||||
if (sphereNum.length() == 1) sphereString += "0";
|
||||
if (i < 10) sphereString += "0";
|
||||
sphereString += sphereNum;
|
||||
for (const uint32_t key : playthroughLocations[i]) {
|
||||
WriteLocation(sphereString, key, true);
|
||||
@ -523,23 +543,16 @@ static void WritePlaythrough() {
|
||||
}
|
||||
|
||||
//Write the randomized entrance playthrough to the spoiler log, if applicable
|
||||
static void WriteShuffledEntrances(tinyxml2::XMLDocument& spoilerLog) {
|
||||
if (!Settings::ShuffleEntrances || noRandomEntrances) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto playthroughNode = spoilerLog.NewElement("entrance-playthrough");
|
||||
|
||||
static void WriteShuffledEntrances() {
|
||||
for (uint32_t i = 0; i < playthroughEntrances.size(); ++i) {
|
||||
auto sphereNode = playthroughNode->InsertNewChildElement("sphere");
|
||||
sphereNode->SetAttribute("level", i + 1);
|
||||
|
||||
auto sphereNum = std::to_string(i);
|
||||
std::string sphereString = "sphere ";
|
||||
if (i < 10) sphereString += "0";
|
||||
sphereString += sphereNum;
|
||||
for (Entrance* entrance : playthroughEntrances[i]) {
|
||||
WriteShuffledEntrance(sphereNode, entrance, true);
|
||||
WriteShuffledEntrance(sphereString, entrance);
|
||||
}
|
||||
}
|
||||
|
||||
spoilerLog.RootElement()->InsertEndChild(playthroughNode);
|
||||
}
|
||||
|
||||
// Writes the WOTH locations to the spoiler log, if there are any.
|
||||
@ -744,7 +757,7 @@ const char* SpoilerLog_Write(int language) {
|
||||
wothLocations.clear();
|
||||
|
||||
WriteHints(language);
|
||||
//WriteShuffledEntrances(spoilerLog);
|
||||
WriteShuffledEntrances();
|
||||
WriteAllLocations(language);
|
||||
|
||||
if (!std::filesystem::exists(Ship::Window::GetPathRelativeToAppDirectory("Randomizer"))) {
|
||||
|
@ -220,6 +220,11 @@ std::unordered_map<std::string, RandomizerSettingKey> SpoilerfileSettingNameToEn
|
||||
{ "World Settings:Starting Age", RSK_STARTING_AGE },
|
||||
{ "World Settings:Ammo Drops", RSK_ENABLE_BOMBCHU_DROPS },
|
||||
{ "World Settings:Bombchus in Logic", RSK_BOMBCHUS_IN_LOGIC },
|
||||
{ "World Settings:Shuffle Entrances", RSK_SHUFFLE_ENTRANCES },
|
||||
{ "World Settings:Dungeon Entrances", RSK_SHUFFLE_DUNGEON_ENTRANCES },
|
||||
{ "World Settings:Overworld Entrances", RSK_SHUFFLE_OVERWORLD_ENTRANCES },
|
||||
{ "World Settings:Interior Entrances", RSK_SHUFFLE_INTERIOR_ENTRANCES },
|
||||
{ "World Settings:Grottos Entrances", RSK_SHUFFLE_GROTTO_ENTRANCES },
|
||||
{ "Misc Settings:Gossip Stone Hints", RSK_GOSSIP_STONE_HINTS },
|
||||
{ "Misc Settings:Hint Clarity", RSK_HINT_CLARITY },
|
||||
{ "Misc Settings:Hint Distribution", RSK_HINT_DISTRIBUTION },
|
||||
@ -543,6 +548,12 @@ void Randomizer::LoadRequiredTrials(const char* spoilerFileName) {
|
||||
}
|
||||
}
|
||||
|
||||
void Randomizer::LoadEntranceOverrides(const char* spoilerFileName, bool silent){
|
||||
if (strcmp(spoilerFileName, "") != 0) {
|
||||
ParseEntranceDataFile(spoilerFileName, silent);
|
||||
}
|
||||
}
|
||||
|
||||
void Randomizer::LoadMasterQuestDungeons(const char* spoilerFileName) {
|
||||
if (strcmp(spoilerFileName, "") != 0) {
|
||||
ParseMasterQuestDungeonsFile(spoilerFileName);
|
||||
@ -704,6 +715,9 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) {
|
||||
case RSK_BLUE_FIRE_ARROWS:
|
||||
case RSK_SUNLIGHT_ARROWS:
|
||||
case RSK_BOMBCHUS_IN_LOGIC:
|
||||
case RSK_SHUFFLE_ENTRANCES:
|
||||
case RSK_SHUFFLE_OVERWORLD_ENTRANCES:
|
||||
case RSK_SHUFFLE_GROTTO_ENTRANCES:
|
||||
if(it.value() == "Off") {
|
||||
gSaveContext.randoSettings[index].value = 0;
|
||||
} else if(it.value() == "On") {
|
||||
@ -900,6 +914,24 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) {
|
||||
numericValueString = it.value();
|
||||
gSaveContext.randoSettings[index].value = std::stoi(numericValueString);
|
||||
break;
|
||||
case RSK_SHUFFLE_DUNGEON_ENTRANCES:
|
||||
if (it.value() == "Off") {
|
||||
gSaveContext.randoSettings[index].value = 0;
|
||||
} else if (it.value() == "On") {
|
||||
gSaveContext.randoSettings[index].value = 1;
|
||||
} else if (it.value() == "On + Ganon") {
|
||||
gSaveContext.randoSettings[index].value = 2;
|
||||
}
|
||||
break;
|
||||
case RSK_SHUFFLE_INTERIOR_ENTRANCES:
|
||||
if (it.value() == "Off") {
|
||||
gSaveContext.randoSettings[index].value = 0;
|
||||
} else if (it.value() == "Simple") {
|
||||
gSaveContext.randoSettings[index].value = 1;
|
||||
} else if (it.value() == "All") {
|
||||
gSaveContext.randoSettings[index].value = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1165,6 +1197,49 @@ void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent
|
||||
}
|
||||
}
|
||||
|
||||
void Randomizer::ParseEntranceDataFile(const char* spoilerFileName, bool silent) {
|
||||
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
|
||||
if (!spoilerFileStream) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set all the entrances to be 0 to indicate an unshuffled entrance
|
||||
for (auto &entranceOveride : gSaveContext.entranceOverrides) {
|
||||
entranceOveride.index = 0;
|
||||
entranceOveride.destination = 0;
|
||||
entranceOveride.blueWarp = 0;
|
||||
entranceOveride.override = 0;
|
||||
entranceOveride.overrideDestination = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
json spoilerFileJson;
|
||||
spoilerFileStream >> spoilerFileJson;
|
||||
json EntrancesJson = spoilerFileJson["entrances"];
|
||||
|
||||
size_t i = 0;
|
||||
for (auto it = EntrancesJson.begin(); it != EntrancesJson.end(); ++it, i++) {
|
||||
json entranceJson = *it;
|
||||
|
||||
for (auto entranceIt = entranceJson.begin(); entranceIt != entranceJson.end(); ++entranceIt) {
|
||||
if (entranceIt.key() == "index") {
|
||||
gSaveContext.entranceOverrides[i].index = entranceIt.value();
|
||||
} else if (entranceIt.key() == "destination") {
|
||||
gSaveContext.entranceOverrides[i].destination = entranceIt.value();
|
||||
} else if (entranceIt.key() == "blueWarp") {
|
||||
gSaveContext.entranceOverrides[i].blueWarp = entranceIt.value();
|
||||
} else if (entranceIt.key() == "override") {
|
||||
gSaveContext.entranceOverrides[i].override = entranceIt.value();
|
||||
} else if (entranceIt.key() == "overrideDestination") {
|
||||
gSaveContext.entranceOverrides[i].overrideDestination = entranceIt.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool Randomizer::IsTrialRequired(RandomizerInf trial) {
|
||||
return this->trialsRequired.contains(trial);
|
||||
}
|
||||
@ -2553,6 +2628,7 @@ void GenerateRandomizerImgui() {
|
||||
// Link's Pocket has to have a dungeon reward if the other rewards are shuffled to end of dungeon.
|
||||
cvarSettings[RSK_LINKS_POCKET] = CVar_GetS32("gRandomizeShuffleDungeonReward", 0) != 0 ?
|
||||
CVar_GetS32("gRandomizeLinksPocket", 0) : 0;
|
||||
|
||||
if (OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal()) {
|
||||
// If both OTRs are loaded.
|
||||
cvarSettings[RSK_RANDOM_MQ_DUNGEONS] = CVar_GetS32("gRandomizeMqDungeons", 0);
|
||||
@ -2567,6 +2643,17 @@ void GenerateRandomizerImgui() {
|
||||
cvarSettings[RSK_MQ_DUNGEON_COUNT] = 0;
|
||||
}
|
||||
|
||||
// Enable if any of the entrance rando options are enabled.
|
||||
cvarSettings[RSK_SHUFFLE_ENTRANCES] = CVar_GetS32("gRandomizeShuffleDungeonsEntrances", 0) ||
|
||||
CVar_GetS32("gRandomizeShuffleOverworldEntrances", 0) ||
|
||||
CVar_GetS32("gRandomizeShuffleInteriorsEntrances", 0) ||
|
||||
CVar_GetS32("gRandomizeShuffleGrottosEntrances", 0);
|
||||
|
||||
cvarSettings[RSK_SHUFFLE_DUNGEON_ENTRANCES] = CVar_GetS32("gRandomizeShuffleDungeonsEntrances", 0);
|
||||
cvarSettings[RSK_SHUFFLE_OVERWORLD_ENTRANCES] = CVar_GetS32("gRandomizeShuffleOverworldEntrances", 0);
|
||||
cvarSettings[RSK_SHUFFLE_INTERIOR_ENTRANCES] = CVar_GetS32("gRandomizeShuffleInteriorsEntrances", 0);
|
||||
cvarSettings[RSK_SHUFFLE_GROTTO_ENTRANCES] = CVar_GetS32("gRandomizeShuffleGrottosEntrances", 0);
|
||||
|
||||
// todo: this efficently when we build out cvar array support
|
||||
std::set<RandomizerCheck> excludedLocations;
|
||||
std::stringstream excludedLocationStringStream(CVar_GetString("gRandomizeExcludedLocations", ""));
|
||||
@ -2612,11 +2699,8 @@ void DrawRandoEditor(bool& open) {
|
||||
|
||||
// World Settings
|
||||
const char* randoStartingAge[3] = { "Child", "Adult", "Random" };
|
||||
const char* randoShuffleEntrances[2] = { "Off", "On" };
|
||||
const char* randoShuffleDungeonsEntrances[2] = { "Off", "On" };
|
||||
const char* randoShuffleOverworldEntrances[2] = { "Off", "On" };
|
||||
const char* randoShuffleInteriorsEntrances[2] = { "Off", "On" };
|
||||
const char* randoShuffleGrottosEntrances[2] = { "Off", "On" };
|
||||
const char* randoShuffleDungeonsEntrances[3] = { "Off", "On", "On + Ganon" };
|
||||
const char* randoShuffleInteriorsEntrances[3] = { "Off", "Simple", "All" };
|
||||
const char* randoBombchusInLogic[2] = { "Off", "On" };
|
||||
const char* randoAmmoDrops[3] = { "On + Bombchu", "Off", "On" };
|
||||
const char* randoHeartDropsAndRefills[4] = { "On", "No Drop", "No Refill", "Off" };
|
||||
@ -2934,10 +3018,57 @@ void DrawRandoEditor(bool& open) {
|
||||
ImGui::BeginChild("ChildShuffleEntrances", ImVec2(0, -8));
|
||||
ImGui::PushItemWidth(-FLT_MIN);
|
||||
|
||||
ImGui::Text("Coming soon");
|
||||
// Shuffle Dungeon Entrances
|
||||
ImGui::Text("Shuffle Dungeon Entrances");
|
||||
UIWidgets::InsertHelpHoverText(
|
||||
"Shuffle the pool of dungeon entrances, including Bottom of the Well, Ice Cavern and Gerudo Training Grounds.\n"
|
||||
"\n"
|
||||
"Shuffling Ganon's Castle can be enabled separately.\n"
|
||||
"\n"
|
||||
"Additionally, the entrances of Deku Tree, Fire Temple, Bottom of the Well and Gerudo Training Ground are opened for both child and adult.\n"
|
||||
"\n"
|
||||
"- Deku Tree will be open for adult after Mido has seen child Link with a sword and shield.\n"
|
||||
"- Bottom of the Well will be open for adult after playing Song of Storms to the Windmill guy as child.\n"
|
||||
"- Gerudo Training Ground will be open for child after adult has paid to open the gate once."
|
||||
);
|
||||
UIWidgets::EnhancementCombobox("gRandomizeShuffleDungeonsEntrances", randoShuffleDungeonsEntrances, 3, 0);
|
||||
|
||||
UIWidgets::PaddedSeparator();
|
||||
|
||||
// Shuffle Overworld Entrances
|
||||
UIWidgets::EnhancementCheckbox("Shuffle Overworld Entrances", "gRandomizeShuffleOverworldEntrances");
|
||||
UIWidgets::InsertHelpHoverText(
|
||||
"Shuffle the pool of Overworld entrances, which corresponds to almost all loading zones between overworld areas.\n"
|
||||
"\n"
|
||||
"Some entrances are unshuffled to avoid issues:\n"
|
||||
"- Hyrule Castle Courtyard and Garden entrance\n"
|
||||
"- Both Market Back Alley entrances\n"
|
||||
"- Gerudo Valley to Lake Hylia (unless entrances are decoupled)"
|
||||
);
|
||||
|
||||
UIWidgets::PaddedSeparator();
|
||||
|
||||
// Shuffle Interior Entrances
|
||||
ImGui::Text("Shuffle Interior Entrances");
|
||||
UIWidgets::InsertHelpHoverText(
|
||||
"Shuffle the pool of interior entrances which contains most Houses and all Great Fairies.\n"
|
||||
"\n"
|
||||
"All - An extended version of 'Simple' with some extra places:\n"
|
||||
"- Windmill\n"
|
||||
"- Link's House\n"
|
||||
"- Temple of Time\n"
|
||||
"- Kakariko Potion Shop"
|
||||
);
|
||||
UIWidgets::EnhancementCombobox("gRandomizeShuffleInteriorsEntrances", randoShuffleInteriorsEntrances, 3, 0);
|
||||
|
||||
UIWidgets::PaddedSeparator();
|
||||
|
||||
// Shuffle Grotto Entrances
|
||||
UIWidgets::EnhancementCheckbox("Shuffle Grotto Entrances", "gRandomizeShuffleGrottosEntrances");
|
||||
UIWidgets::InsertHelpHoverText(
|
||||
"Shuffle the pool of grotto entrances, including all graves, small Fairy fountains and the Deku Theatre."
|
||||
);
|
||||
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTable();
|
||||
|
@ -28,6 +28,7 @@ class Randomizer {
|
||||
void ParseRequiredTrialsFile(const char* spoilerFileName);
|
||||
void ParseMasterQuestDungeonsFile(const char* spoilerFileName);
|
||||
void ParseItemLocationsFile(const char* spoilerFileName, bool silent);
|
||||
void ParseEntranceDataFile(const char* spoilerFileName, bool silent);
|
||||
bool IsItemVanilla(RandomizerGet randoGet);
|
||||
GetItemEntry GetItemEntryFromRGData(RandomizerGetData rgData, GetItemID ogItemId, bool checkObtainability = true);
|
||||
|
||||
@ -58,6 +59,7 @@ class Randomizer {
|
||||
void LoadRequiredTrials(const char* spoilerFileName);
|
||||
void LoadMasterQuestDungeons(const char* spoilerFileName);
|
||||
bool IsTrialRequired(RandomizerInf trial);
|
||||
void LoadEntranceOverrides(const char* spoilerFileName, bool silent);
|
||||
u8 GetRandoSettingValue(RandomizerSettingKey randoSettingKey);
|
||||
RandomizerCheck GetCheckFromActor(s16 actorId, s16 sceneNum, s16 actorParams);
|
||||
RandomizerCheck GetCheckFromRandomizerInf(RandomizerInf randomizerInf);
|
||||
|
@ -1052,6 +1052,20 @@ typedef enum {
|
||||
RSK_KEYRINGS_BOTTOM_OF_THE_WELL,
|
||||
RSK_KEYRINGS_GTG,
|
||||
RSK_KEYRINGS_GANONS_CASTLE,
|
||||
RSK_SHUFFLE_ENTRANCES,
|
||||
RSK_SHUFFLE_DUNGEON_ENTRANCES,
|
||||
RSK_SHUFFLE_OVERWORLD_ENTRANCES,
|
||||
RSK_SHUFFLE_INTERIOR_ENTRANCES,
|
||||
RSK_SHUFFLE_GROTTO_ENTRANCES,
|
||||
RSK_SHUFFLE_OWL_DROPS,
|
||||
RSK_SHUFFLE_WARP_SONGS,
|
||||
RSK_SHUFFLE_OVERWORLD_SPAWNS,
|
||||
RSK_MIXED_ENTRANCE_POOLS,
|
||||
RSK_MIX_DUNGEON_ENTRANCES,
|
||||
RSK_MIX_OVERWORLD_ENTRANCES,
|
||||
RSK_MIX_INTERIOR_ENTRANCES,
|
||||
RSK_MIX_GROTTO_ENTRANCES,
|
||||
RSK_DECOUPLED_ENTRANCES,
|
||||
RSK_MAX
|
||||
} RandomizerSettingKey;
|
||||
|
||||
|
526
soh/soh/Enhancements/randomizer/randomizer_entrance.c
Normal file
526
soh/soh/Enhancements/randomizer/randomizer_entrance.c
Normal file
@ -0,0 +1,526 @@
|
||||
/*
|
||||
* Much of the code here was borrowed from https://github.com/gamestabled/OoT3D_Randomizer/blob/main/code/src/entrance.c
|
||||
* It's been adapted for SoH to use our gPlayState vs their gGlobalContext with slightly different named properties, and our enums for some scenes/entrances.
|
||||
*
|
||||
* Unlike 3DS rando, we need to be able to support the user loading up vanilla and rando saves, so the logic around
|
||||
* modifying the entrance table requires that we save the original table and reset whenever loading a vanilla save.
|
||||
* A modified dynamicExitList is manually included since we can't read it from addressing like 3ds rando.
|
||||
*/
|
||||
|
||||
#include "randomizer_entrance.h"
|
||||
#include "randomizer_grotto.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "global.h"
|
||||
|
||||
extern PlayState* gPlayState;
|
||||
|
||||
//Overwrite the dynamic exit for the OGC Fairy Fountain to be 0x3E8 instead
|
||||
//of 0x340 (0x340 will stay as the exit for the HC Fairy Fountain -> Castle Grounds)
|
||||
s16 dynamicExitList[] = { 0x045B, 0x0482, 0x03E8, 0x044B, 0x02A2, 0x0201, 0x03B8, 0x04EE, 0x03C0, 0x0463, 0x01CD, 0x0394, 0x0340, 0x057C };
|
||||
// OGC Fairy HC Fairy
|
||||
|
||||
// Warp Song indices array : 0x53C33C = { 0x0600, 0x04F6, 0x0604, 0x01F1, 0x0568, 0x05F4 }
|
||||
|
||||
// Owl Flights : 0x492064 and 0x492080
|
||||
|
||||
static s16 entranceOverrideTable[ENTRANCE_TABLE_SIZE] = {0};
|
||||
|
||||
EntranceInfo originalEntranceTable[1556] = {0};
|
||||
|
||||
//These variables store the new entrance indices for dungeons so that
|
||||
//savewarping and game overs respawn players at the proper entrance.
|
||||
//By default, these will be their vanilla values.
|
||||
static s16 newDekuTreeEntrance = DEKU_TREE_ENTRANCE;
|
||||
static s16 newDodongosCavernEntrance = DODONGOS_CAVERN_ENTRANCE;
|
||||
static s16 newJabuJabusBellyEntrance = JABU_JABUS_BELLY_ENTRANCE;
|
||||
static s16 newForestTempleEntrance = FOREST_TEMPLE_ENTRANCE;
|
||||
static s16 newFireTempleEntrance = FIRE_TEMPLE_ENTRANCE;
|
||||
static s16 newWaterTempleEntrance = WATER_TEMPLE_ENTRANCE;
|
||||
static s16 newSpiritTempleEntrance = SPIRIT_TEMPLE_ENTRANCE;
|
||||
static s16 newShadowTempleEntrance = SHADOW_TEMPLE_ENTRANCE;
|
||||
static s16 newBottomOfTheWellEntrance = BOTTOM_OF_THE_WELL_ENTRANCE;
|
||||
static s16 newGerudoTrainingGroundsEntrance = GERUDO_TRAINING_GROUNDS_ENTRANCE;
|
||||
static s16 newIceCavernEntrance = ICE_CAVERN_ENTRANCE;
|
||||
|
||||
static s8 hasCopiedEntranceTable = 0;
|
||||
static s8 hasModifiedEntranceTable = 0;
|
||||
|
||||
u8 Entrance_EntranceIsNull(EntranceOverride* entranceOverride) {
|
||||
return entranceOverride->index == 0 && entranceOverride->destination == 0 && entranceOverride->blueWarp == 0
|
||||
&& entranceOverride->override == 0 && entranceOverride->overrideDestination == 0;
|
||||
}
|
||||
|
||||
static void Entrance_SeparateOGCFairyFountainExit(void) {
|
||||
//Overwrite unused entrance 0x03E8 with values from 0x0340 to use it as the
|
||||
//exit from OGC Great Fairy Fountain -> Castle Grounds
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
gEntranceTable[0x3E8 + i] = gEntranceTable[0x340 + i];
|
||||
}
|
||||
}
|
||||
|
||||
void Entrance_CopyOriginalEntranceTable(void) {
|
||||
if (!hasCopiedEntranceTable) {
|
||||
memcpy(originalEntranceTable, gEntranceTable, sizeof(EntranceInfo) * 1556);
|
||||
hasCopiedEntranceTable = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Entrance_ResetEntranceTable(void) {
|
||||
if (hasCopiedEntranceTable && hasModifiedEntranceTable) {
|
||||
memcpy(gEntranceTable, originalEntranceTable, sizeof(EntranceInfo) * 1556);
|
||||
hasModifiedEntranceTable = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Entrance_Init(void) {
|
||||
s32 index;
|
||||
|
||||
Entrance_CopyOriginalEntranceTable();
|
||||
|
||||
// Skip Child Stealth if given by settings
|
||||
if (Randomizer_GetSettingValue(RSK_SKIP_CHILD_STEALTH)) {
|
||||
gEntranceTable[0x07A].scene = 0x4A;
|
||||
gEntranceTable[0x07A].spawn = 0x00;
|
||||
gEntranceTable[0x07A].field = 0x0183;
|
||||
}
|
||||
|
||||
// Delete the title card and add a fade in for Hyrule Field from Ocarina of Time cutscene
|
||||
for (index = 0x50F; index < 0x513; ++index) {
|
||||
gEntranceTable[index].field = 0x010B;
|
||||
}
|
||||
|
||||
Entrance_SeparateOGCFairyFountainExit();
|
||||
|
||||
// Initialize the entrance override table with each index leading to itself. An
|
||||
// index referring to itself means that the entrance is not currently shuffled.
|
||||
for (s16 i = 0; i < ENTRANCE_TABLE_SIZE; i++) {
|
||||
entranceOverrideTable[i] = i;
|
||||
}
|
||||
|
||||
// Initialize the grotto exit and load lists
|
||||
Grotto_InitExitAndLoadLists();
|
||||
|
||||
// Then overwrite the indices which are shuffled
|
||||
for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
|
||||
|
||||
if (Entrance_EntranceIsNull(&gSaveContext.entranceOverrides[i])) {
|
||||
break;
|
||||
}
|
||||
|
||||
s16 originalIndex = gSaveContext.entranceOverrides[i].index;
|
||||
s16 blueWarpIndex = gSaveContext.entranceOverrides[i].blueWarp;
|
||||
s16 overrideIndex = gSaveContext.entranceOverrides[i].override;
|
||||
|
||||
//Overwrite grotto related indices
|
||||
if (originalIndex >= 0x0800) {
|
||||
Grotto_SetExitOverride(originalIndex, overrideIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (originalIndex >= 0x0700 && originalIndex < 0x0800) {
|
||||
Grotto_SetLoadOverride(originalIndex, overrideIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Overwrite the indices which we want to shuffle, leaving the rest as they are
|
||||
entranceOverrideTable[originalIndex] = overrideIndex;
|
||||
|
||||
if (blueWarpIndex != 0) {
|
||||
entranceOverrideTable[blueWarpIndex] = overrideIndex;
|
||||
}
|
||||
|
||||
//Override both land and water entrances for Hyrule Field -> ZR Front and vice versa
|
||||
if (originalIndex == 0x00EA) { //Hyrule Field -> ZR Front land entrance
|
||||
entranceOverrideTable[0x01D9] = overrideIndex;
|
||||
} else if (originalIndex == 0x0181) { //ZR Front -> Hyrule Field land entrance
|
||||
entranceOverrideTable[0x0311] = overrideIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop playing background music during shuffled entrance transitions
|
||||
// so that we don't get duplicated or overlapping music tracks
|
||||
if (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) {
|
||||
|
||||
s16 indicesToSilenceBackgroundMusic[2] = {
|
||||
// The lost woods music playing near the GC Woods Warp keeps playing
|
||||
// in the next area if the bvackground music is allowed to keep playing
|
||||
entranceOverrideTable[0x04D6], // Goron City -> Lost Woods override
|
||||
|
||||
// If Malon is singing at night, then her singing will be transferred
|
||||
// to the next area if it allows the background music to keep playing
|
||||
entranceOverrideTable[0x025A], // Castle Grounds -> Market override
|
||||
};
|
||||
|
||||
for (size_t j = 0; j < sizeof(indicesToSilenceBackgroundMusic) / sizeof(s16); j++) {
|
||||
|
||||
s16 override = indicesToSilenceBackgroundMusic[j];
|
||||
for (s16 i = 0; i < 4; i++) {
|
||||
// Zero out the bit in the field which tells the game to keep playing
|
||||
// background music for all four scene setups at each index
|
||||
gEntranceTable[override + i].field &= ~0x8000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasModifiedEntranceTable = 1;
|
||||
}
|
||||
|
||||
s16 Entrance_GetOverride(s16 index) {
|
||||
|
||||
// The game sometimes uses special indices from 0x7FF9 -> 0x7FFF for exiting
|
||||
// grottos and fairy fountains. These aren't handled here since the game
|
||||
// naturally handles them later.
|
||||
if (index >= ENTRANCE_TABLE_SIZE) {
|
||||
return index;
|
||||
}
|
||||
|
||||
return entranceOverrideTable[index];
|
||||
}
|
||||
|
||||
s16 Entrance_OverrideNextIndex(s16 nextEntranceIndex) {
|
||||
// When entering Spirit Temple, clear temp flags so they don't carry over to the randomized dungeon
|
||||
if (nextEntranceIndex == 0x0082 && Entrance_GetOverride(nextEntranceIndex) != nextEntranceIndex &&
|
||||
gPlayState != NULL) {
|
||||
gPlayState->actorCtx.flags.tempSwch = 0;
|
||||
gPlayState->actorCtx.flags.tempCollect = 0;
|
||||
}
|
||||
|
||||
// Exiting through the crawl space from Hyrule Castle courtyard is the same exit as leaving Ganon's castle
|
||||
// If we came from the Castle courtyard, then don't override the entrance to keep Link in Hyrule Castle area
|
||||
if (gPlayState->sceneNum == 69 && nextEntranceIndex == 0x023D) {
|
||||
return nextEntranceIndex;
|
||||
}
|
||||
|
||||
return Grotto_OverrideSpecialEntrance(Entrance_GetOverride(nextEntranceIndex));
|
||||
}
|
||||
|
||||
s16 Entrance_OverrideDynamicExit(s16 dynamicExitIndex) {
|
||||
return Grotto_OverrideSpecialEntrance(Entrance_GetOverride(dynamicExitList[dynamicExitIndex]));
|
||||
}
|
||||
|
||||
u32 Entrance_SceneAndSpawnAre(u8 scene, u8 spawn) {
|
||||
EntranceInfo currentEntrance = gEntranceTable[gSaveContext.entranceIndex];
|
||||
return currentEntrance.scene == scene && currentEntrance.spawn == spawn;
|
||||
}
|
||||
|
||||
//Properly respawn the player after a game over, accounding for dungeon entrance
|
||||
//randomizer. It's easier to rewrite this entirely compared to performing an ASM
|
||||
//dance for just the boss rooms. Entrance Indexes can be found here:
|
||||
//https://wiki.cloudmodding.com/oot/Entrance_Table_(Data)
|
||||
void Entrance_SetGameOverEntrance(void) {
|
||||
|
||||
//Set the current entrance depending on which entrance the player last came through
|
||||
switch (gSaveContext.entranceIndex) {
|
||||
case 0x040F : //Deku Tree Boss Room
|
||||
gSaveContext.entranceIndex = newDekuTreeEntrance;
|
||||
return;
|
||||
case 0x040B : //Dodongos Cavern Boss Room
|
||||
gSaveContext.entranceIndex = newDodongosCavernEntrance;
|
||||
return;
|
||||
case 0x0301 : //Jabu Jabus Belly Boss Room
|
||||
gSaveContext.entranceIndex = newJabuJabusBellyEntrance;
|
||||
return;
|
||||
case 0x000C : //Forest Temple Boss Room
|
||||
gSaveContext.entranceIndex = newForestTempleEntrance;
|
||||
return;
|
||||
case 0x0305 : //Fire Temple Boss Room
|
||||
gSaveContext.entranceIndex = newFireTempleEntrance;
|
||||
return;
|
||||
case 0x0417 : //Water Temple Boss Room
|
||||
gSaveContext.entranceIndex = newWaterTempleEntrance;
|
||||
return;
|
||||
case 0x008D : //Spirit Temple Boss Room
|
||||
gSaveContext.entranceIndex = newSpiritTempleEntrance;
|
||||
return;
|
||||
case 0x0413 : //Shadow Temple Boss Room
|
||||
gSaveContext.entranceIndex = newShadowTempleEntrance;
|
||||
return;
|
||||
case 0x041F : //Ganondorf Boss Room
|
||||
gSaveContext.entranceIndex = 0x041B; // Inside Ganon's Castle -> Ganon's Tower Climb
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Properly savewarp the player accounting for dungeon entrance randomizer.
|
||||
//It's easier to rewrite this entirely compared to performing an ASM
|
||||
//dance for just the boss rooms.
|
||||
//https://wiki.cloudmodding.com/oot/Entrance_Table_(Data)
|
||||
void Entrance_SetSavewarpEntrance(void) {
|
||||
|
||||
s16 scene = gSaveContext.savedSceneNum;
|
||||
|
||||
if (scene == SCENE_YDAN || scene == SCENE_YDAN_BOSS) {
|
||||
gSaveContext.entranceIndex = newDekuTreeEntrance;
|
||||
} else if (scene == SCENE_DDAN || scene == SCENE_DDAN_BOSS) {
|
||||
gSaveContext.entranceIndex = newDodongosCavernEntrance;
|
||||
} else if (scene == SCENE_BDAN || scene == SCENE_BDAN_BOSS) {
|
||||
gSaveContext.entranceIndex = newJabuJabusBellyEntrance;
|
||||
} else if (scene == SCENE_BMORI1 || scene == SCENE_MORIBOSSROOM) { //Forest Temple Boss Room
|
||||
gSaveContext.entranceIndex = newForestTempleEntrance;
|
||||
} else if (scene == SCENE_HIDAN || scene == SCENE_FIRE_BS) { //Fire Temple Boss Room
|
||||
gSaveContext.entranceIndex = newFireTempleEntrance;
|
||||
} else if (scene == SCENE_MIZUSIN || scene == SCENE_MIZUSIN_BS) { //Water Temple Boss Room
|
||||
gSaveContext.entranceIndex = newWaterTempleEntrance;
|
||||
} else if (scene == SCENE_JYASINZOU || scene == SCENE_JYASINBOSS) { //Spirit Temple Boss Room
|
||||
gSaveContext.entranceIndex = newSpiritTempleEntrance;
|
||||
} else if (scene == SCENE_HAKADAN || scene == SCENE_HAKADAN_BS) { //Shadow Temple Boss Room
|
||||
gSaveContext.entranceIndex = newShadowTempleEntrance;
|
||||
} else if (scene == SCENE_HAKADANCH) { // BOTW
|
||||
gSaveContext.entranceIndex = newBottomOfTheWellEntrance;
|
||||
} else if (scene == SCENE_MEN) { // GTG
|
||||
gSaveContext.entranceIndex = newGerudoTrainingGroundsEntrance;
|
||||
} else if (scene == SCENE_ICE_DOUKUTO) { // Ice cavern
|
||||
gSaveContext.entranceIndex = newIceCavernEntrance;
|
||||
} else if (scene == SCENE_GANONTIKA) {
|
||||
gSaveContext.entranceIndex = GANONS_CASTLE_ENTRANCE;
|
||||
} else if (scene == SCENE_GANON || scene == SCENE_GANONTIKA_SONOGO || scene == SCENE_GANON_SONOGO || scene == SCENE_GANON_DEMO || scene == SCENE_GANON_FINAL) {
|
||||
gSaveContext.entranceIndex = 0x041B; // Inside Ganon's Castle -> Ganon's Tower Climb
|
||||
} else if (scene == SCENE_GERUDOWAY) { // Theives hideout
|
||||
gSaveContext.entranceIndex = 0x0486; // Gerudo Fortress -> Thieve's Hideout spawn 0
|
||||
} else if (scene == SCENE_LINK_HOME) {
|
||||
gSaveContext.entranceIndex = LINK_HOUSE_SAVEWARP_ENTRANCE;
|
||||
} else if (LINK_IS_CHILD) {
|
||||
gSaveContext.entranceIndex = Entrance_GetOverride(LINK_HOUSE_SAVEWARP_ENTRANCE);
|
||||
} else {
|
||||
gSaveContext.entranceIndex = Entrance_GetOverride(0x05F4); // Temple of Time Adult Spawn
|
||||
}
|
||||
}
|
||||
|
||||
void Entrance_OverrideBlueWarp(void) {
|
||||
switch (gPlayState->sceneNum) {
|
||||
case SCENE_YDAN_BOSS: // Ghoma boss room
|
||||
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x0457);
|
||||
return;
|
||||
case SCENE_DDAN_BOSS: // King Dodongo boss room
|
||||
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x047A);
|
||||
return;
|
||||
case SCENE_BDAN_BOSS: // Barinade boss room
|
||||
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x010E);
|
||||
return;
|
||||
case SCENE_MORIBOSSROOM: // Phantom Ganon boss room
|
||||
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x0608);
|
||||
return;
|
||||
case SCENE_FIRE_BS: // Volvagia boss room
|
||||
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x0564);
|
||||
return;
|
||||
case SCENE_MIZUSIN_BS: // Morpha boss room
|
||||
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x060C);
|
||||
return;
|
||||
case SCENE_JYASINBOSS: // Bongo-Bongo boss room
|
||||
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x0610);
|
||||
return;
|
||||
case SCENE_HAKADAN_BS: // Twinrova boss room
|
||||
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x0580);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Entrance_OverrideCutsceneEntrance(u16 cutsceneCmd) {
|
||||
switch (cutsceneCmd) {
|
||||
case 24: // Dropping a fish for Jabu Jabu
|
||||
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(newJabuJabusBellyEntrance);
|
||||
gPlayState->sceneLoadFlag = 0x14;
|
||||
gPlayState->fadeTransition = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Entrance_EnableFW(void) {
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
// Leave restriction in Tower Collapse Interior, Castle Collapse, Treasure Box Shop, Tower Collapse Exterior,
|
||||
// Grottos area, Fishing Pond, Ganon Battle and for states that disable buttons.
|
||||
if (!false /* farores wind anywhere */ ||
|
||||
gPlayState->sceneNum == 14 || gPlayState->sceneNum == 15 || (gPlayState->sceneNum == 16 && !false /* shuffled chest mini game */) ||
|
||||
gPlayState->sceneNum == 26 || gPlayState->sceneNum == 62 || gPlayState->sceneNum == 73 ||
|
||||
gPlayState->sceneNum == 79 ||
|
||||
gSaveContext.eventInf[0] & 0x1 || // Ingo's Minigame state
|
||||
player->stateFlags1 & 0x08A02000 || // Swimming, riding horse, Down A, hanging from a ledge
|
||||
player->stateFlags2 & 0x00040000 // Blank A
|
||||
// Shielding, spinning and getting skull tokens still disable buttons automatically
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 1; i < 5; i++) {
|
||||
if (gSaveContext.equips.buttonItems[i] == 13) {
|
||||
gSaveContext.buttonStatus[i] = BTN_ENABLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if Link should still be riding epona after changing entrances
|
||||
void Entrance_HandleEponaState(void) {
|
||||
s32 entrance = gPlayState->nextEntranceIndex;
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
//If Link is riding Epona but he's about to go through an entrance where she can't spawn,
|
||||
//unset the Epona flag to avoid Master glitch, and restore temp B.
|
||||
if (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES) && (player->stateFlags1 & PLAYER_STATE1_23)) {
|
||||
|
||||
static const s16 validEponaEntrances[] = {
|
||||
0x0102, // Hyrule Field -> Lake Hylia
|
||||
0x0189, // Lake Hylia -> Hyrule Field
|
||||
0x0309, // LH Fishing Hole -> LH Fishing Island
|
||||
0x03CC, // LH Lab -> Lake Hylia
|
||||
0x0117, // Hyrule Field -> Gerudo Valley
|
||||
0x018D, // Gerudo Valley -> Hyrule Field
|
||||
0x0157, // Hyrule Field -> Lon Lon Ranch
|
||||
0x01F9, // Lon Lon Ranch -> Hyrule Field
|
||||
0x01FD, // Market Entrance -> Hyrule Field
|
||||
0x00EA, // ZR Front -> Hyrule Field
|
||||
0x0181, // LW Bridge -> Hyrule Field
|
||||
0x0129, // GV Fortress Side -> Gerudo Fortress
|
||||
0x022D, // Gerudo Fortress -> GV Fortress Side
|
||||
0x03D0, // GV Carpenter Tent -> GV Fortress Side
|
||||
0x0496, // Gerudo Fortress -> Thieves Hideout
|
||||
0x042F, // LLR Stables -> Lon Lon Ranch
|
||||
0x05D4, // LLR Tower -> Lon Lon Ranch
|
||||
0x0378, // LLR Talons House -> Lon Lon Ranch
|
||||
0x028A, // LLR Southern Fence Jump
|
||||
0x028E, // LLR Western Fence Jump
|
||||
0x0292, // LLR Eastern Fence Jump
|
||||
0x0476, // LLR Front Gate Jump
|
||||
// The following indices currently aren't randomized, but we'll list
|
||||
// them in case they ever are. They're all Theives Hideout -> Gerudo Fortress
|
||||
0x231,
|
||||
0x235,
|
||||
0x239,
|
||||
0x2BA,
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_COUNT(validEponaEntrances); i++) {
|
||||
// If the entrance is equal to any of the valid ones, return and
|
||||
// don't change anything
|
||||
if (entrance == validEponaEntrances[i]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update Link's status to no longer be riding epona so that the next time link
|
||||
// enters an epona supported area, he isn't automatically placed on epona
|
||||
player->stateFlags1 &= ~PLAYER_STATE1_23;
|
||||
player->actor.parent = NULL;
|
||||
AREG(6) = 0;
|
||||
gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0]; //"temp B"
|
||||
}
|
||||
}
|
||||
|
||||
// Certain weather states don't clear normally in entrance rando, so we need to reset and re-apply
|
||||
// the correct weather state based on the current entrance and scene
|
||||
void Entrance_OverrideWeatherState() {
|
||||
gWeatherMode = 0;
|
||||
gPlayState->envCtx.gloomySkyMode = 0;
|
||||
|
||||
// Weather only applyies to adult link
|
||||
if (LINK_IS_CHILD || gSaveContext.sceneSetupIndex >= 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hyrule Market
|
||||
if (gSaveContext.entranceIndex == 0x01FD) { // Hyrule Field by Market Entrance
|
||||
gWeatherMode = 1;
|
||||
return;
|
||||
}
|
||||
// Lon Lon Ranch (No Epona)
|
||||
if (!Flags_GetEventChkInf(0x18)){ // if you don't have Epona
|
||||
switch (gSaveContext.entranceIndex) {
|
||||
case 0x0157: // Lon Lon Ranch from HF
|
||||
case 0x01F9: // Hyrule Field from LLR
|
||||
gWeatherMode = 2;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Water Temple
|
||||
if (!(gSaveContext.eventChkInf[4] & 0x0400)) { // have not beaten Water Temple
|
||||
switch (gSaveContext.entranceIndex) {
|
||||
case 0x019D: // Zora River from behind waterfall
|
||||
case 0x01DD: // Zora River from LW water shortcut
|
||||
case 0x04DA: // Lost Woods water shortcut from ZR
|
||||
gWeatherMode = 3;
|
||||
return;
|
||||
}
|
||||
switch (gPlayState->sceneNum) {
|
||||
case 88: // Zora's Domain
|
||||
case 89: // Zora's Fountain
|
||||
gWeatherMode = 3;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Kakariko Thunderstorm
|
||||
if (((gSaveContext.inventory.questItems & 0x7) == 0x7) && // Have forest, fire, and water medallion
|
||||
!(gSaveContext.sceneFlags[24].clear & 0x02)) { // have not beaten Bongo Bongo
|
||||
switch (gPlayState->sceneNum) {
|
||||
case 82: // Kakariko
|
||||
case 83: // Graveyard
|
||||
gPlayState->envCtx.gloomySkyMode = 2;
|
||||
switch (gSaveContext.entranceIndex) {
|
||||
case 0x00DB: // Kakariko from HF
|
||||
case 0x0191: // Kakariko from Death Mountain Trail
|
||||
case 0x0205: // Graveyard from Shadow Temple
|
||||
break;
|
||||
default:
|
||||
gWeatherMode = 5;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Death Mountain Cloudy
|
||||
if (!(gSaveContext.eventChkInf[4] & 0x0200)) { // have not beaten Fire Temple
|
||||
if (gPlayState->nextEntranceIndex == 0x04D6) { // Lost Woods Goron City Shortcut
|
||||
gWeatherMode = 2;
|
||||
return;
|
||||
}
|
||||
switch (gPlayState->sceneNum) {
|
||||
case 82: // Kakariko
|
||||
case 83: // Graveyard
|
||||
case 96: // Death Mountain Trail
|
||||
case 97: // Death Mountain Crater
|
||||
if (!gPlayState->envCtx.gloomySkyMode) {
|
||||
gPlayState->envCtx.gloomySkyMode = 1;
|
||||
}
|
||||
switch (gSaveContext.entranceIndex) {
|
||||
case 0x00DB: // Kakariko from HF
|
||||
case 0x0195: // Kakariko from Graveyard
|
||||
break;
|
||||
default:
|
||||
gWeatherMode = 2;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rectify the "Getting Caught By Gerudo" entrance index if necessary, based on the age and current scene
|
||||
// In ER, Adult should be placed at the fortress entrance when getting caught in the fortress without a hookshot, instead of being thrown in the valley
|
||||
// Child should always be thrown in the stream when caught in the valley, and placed at the fortress entrance from valley when caught in the fortress
|
||||
void Entrance_OverrideGeurdoGuardCapture(void) {
|
||||
if (LINK_IS_CHILD) {
|
||||
gPlayState->nextEntranceIndex = 0x1A5;
|
||||
}
|
||||
|
||||
if ((LINK_IS_CHILD || Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) &&
|
||||
gPlayState->nextEntranceIndex == 0x1A5) { // Geurdo Valley thrown out
|
||||
if (gPlayState->sceneNum != 0x5A) { // Geurdo Valley
|
||||
gPlayState->nextEntranceIndex = 0x129; // Gerudo Fortress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Entrance_OverrideSpawnScene(s32 sceneNum, s32 spawn) {
|
||||
if (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) == 2) { // Shuffle Ganon's Castle
|
||||
// Move Hyrule's Castle Courtyard exit spawn to be before the crates so players don't skip Talon
|
||||
if (sceneNum == 95 && spawn == 1) {
|
||||
gPlayState->linkActorEntry->pos.x = 0x033A;
|
||||
gPlayState->linkActorEntry->pos.y = 0x0623;
|
||||
gPlayState->linkActorEntry->pos.z = 0xFF22;
|
||||
}
|
||||
|
||||
// Move Ganon's Castle exit spawn to be on the small ledge near the castle and not over the void
|
||||
if (sceneNum == 100 && spawn == 1) {
|
||||
gPlayState->linkActorEntry->pos.x = 0xFEA8;
|
||||
gPlayState->linkActorEntry->pos.y = 0x065C;
|
||||
gPlayState->linkActorEntry->pos.z = 0x0290;
|
||||
gPlayState->linkActorEntry->rot.y = 0x0700;
|
||||
gPlayState->linkActorEntry->params = 0x0DFF; // stationary spawn
|
||||
}
|
||||
}
|
||||
}
|
50
soh/soh/Enhancements/randomizer/randomizer_entrance.h
Normal file
50
soh/soh/Enhancements/randomizer/randomizer_entrance.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef _RANDO_ENTRANCE_H_
|
||||
#define _RANDO_ENTRANCE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
//Entrance Table Data: https://wiki.cloudmodding.com/oot/Entrance_Table_(Data)
|
||||
//Accessed June 2021, published content date at the time was 14 March 2020, at 21:47
|
||||
|
||||
#define ENTRANCE_TABLE_SIZE 0x0614
|
||||
|
||||
#define DEKU_TREE_ENTRANCE 0x0000
|
||||
#define DODONGOS_CAVERN_ENTRANCE 0x0004
|
||||
#define JABU_JABUS_BELLY_ENTRANCE 0x0028
|
||||
#define FOREST_TEMPLE_ENTRANCE 0x169
|
||||
#define FIRE_TEMPLE_ENTRANCE 0x165
|
||||
#define WATER_TEMPLE_ENTRANCE 0x0010
|
||||
#define SPIRIT_TEMPLE_ENTRANCE 0x0082
|
||||
#define SHADOW_TEMPLE_ENTRANCE 0x0037
|
||||
#define BOTTOM_OF_THE_WELL_ENTRANCE 0x0098
|
||||
#define GERUDO_TRAINING_GROUNDS_ENTRANCE 0x0008
|
||||
#define ICE_CAVERN_ENTRANCE 0x0088
|
||||
#define GANONS_CASTLE_ENTRANCE 0x0467
|
||||
#define LINK_HOUSE_SAVEWARP_ENTRANCE 0x00BB
|
||||
|
||||
#define ENTRANCE_OVERRIDES_MAX_COUNT 256
|
||||
|
||||
typedef struct {
|
||||
int16_t index;
|
||||
int16_t destination;
|
||||
int16_t blueWarp;
|
||||
int16_t override;
|
||||
int16_t overrideDestination;
|
||||
} EntranceOverride;
|
||||
|
||||
void Entrance_Init(void);
|
||||
void Entrance_ResetEntranceTable(void);
|
||||
uint8_t Entrance_EntranceIsNull(EntranceOverride* entranceOverride);
|
||||
int16_t Entrance_GetOverride(int16_t index);
|
||||
int16_t Entrance_OverrideNextIndex(int16_t nextEntranceIndex);
|
||||
int16_t Entrance_OverrideDynamicExit(int16_t dynamicExitIndex);
|
||||
uint32_t Entrance_SceneAndSpawnAre(uint8_t scene, uint8_t spawn);
|
||||
void Entrance_SetSavewarpEntrance(void);
|
||||
void Entrance_OverrideBlueWarp(void);
|
||||
void Entrance_OverrideCutsceneEntrance(uint16_t cutsceneCmd);
|
||||
void Entrance_HandleEponaState(void);
|
||||
void Entrance_OverrideWeatherState(void);
|
||||
void Entrance_OverrideGeurdoGuardCapture(void);
|
||||
void Entrance_OverrideSpawnScene(int32_t sceneNum, int32_t spawn);
|
||||
|
||||
#endif //_RANDO_ENTRANCE_H_
|
261
soh/soh/Enhancements/randomizer/randomizer_grotto.c
Normal file
261
soh/soh/Enhancements/randomizer/randomizer_grotto.c
Normal file
@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Much of the code here was borrowed from https://github.com/gamestabled/OoT3D_Randomizer/blob/main/code/src/grotto.c
|
||||
* It's been adapted for SoH to use our gPlayState vs their gGlobalContext and slightly different named properties.
|
||||
*/
|
||||
|
||||
#include "randomizer_grotto.h"
|
||||
|
||||
#include "global.h"
|
||||
|
||||
extern PlayState* gPlayState;
|
||||
|
||||
// Information necessary for entering each grotto
|
||||
static const GrottoLoadInfo grottoLoadTable[NUM_GROTTOS] = {
|
||||
{.entranceIndex = 0x05BC, .content = 0xFD, .scene = 0x5C}, // Desert Colossus -> Colossus Grotto
|
||||
{.entranceIndex = 0x05A4, .content = 0xEF, .scene = 0x57}, // Lake Hylia -> LH Grotto
|
||||
{.entranceIndex = 0x05BC, .content = 0xEB, .scene = 0x54}, // Zora River -> ZR Storms Grotto
|
||||
{.entranceIndex = 0x036D, .content = 0xE6, .scene = 0x54}, // Zora River -> ZR Fairy Grotto
|
||||
{.entranceIndex = 0x003F, .content = 0x29, .scene = 0x54}, // Zora River -> ZR Open Grotto
|
||||
{.entranceIndex = 0x05A4, .content = 0xF9, .scene = 0x61}, // DMC Lower Nearby -> DMC Hammer Grotto
|
||||
{.entranceIndex = 0x003F, .content = 0x7A, .scene = 0x61}, // DMC Upper Nearby -> DMC Upper Grotto
|
||||
{.entranceIndex = 0x05A4, .content = 0xFB, .scene = 0x62}, // GC Grotto Platform -> GC Grotto
|
||||
{.entranceIndex = 0x003F, .content = 0x57, .scene = 0x60}, // Death Mountain -> DMT Storms Grotto
|
||||
{.entranceIndex = 0x05FC, .content = 0xF8, .scene = 0x60}, // Death Mountain Summit -> DMT Cow Grotto
|
||||
{.entranceIndex = 0x003F, .content = 0x28, .scene = 0x52}, // Kak Backyard -> Kak Open Grotto
|
||||
{.entranceIndex = 0x05A0, .content = 0xE7, .scene = 0x52}, // Kakariko Village -> Kak Redead Grotto
|
||||
{.entranceIndex = 0x05B8, .content = 0xF6, .scene = 0x5F}, // Hyrule Castle Grounds -> HC Storms Grotto
|
||||
{.entranceIndex = 0x05C0, .content = 0xE1, .scene = 0x51}, // Hyrule Field -> HF Tektite Grotto
|
||||
{.entranceIndex = 0x0598, .content = 0xE5, .scene = 0x51}, // Hyrule Field -> HF Near Kak Grotto
|
||||
{.entranceIndex = 0x036D, .content = 0xFF, .scene = 0x51}, // Hyrule Field -> HF Fairy Grotto
|
||||
{.entranceIndex = 0x003F, .content = 0x00, .scene = 0x51}, // Hyrule Field -> HF Near Market Grotto
|
||||
{.entranceIndex = 0x05A8, .content = 0xE4, .scene = 0x51}, // Hyrule Field -> HF Cow Grotto
|
||||
{.entranceIndex = 0x059C, .content = 0xE6, .scene = 0x51}, // Hyrule Field -> HF Inside Fence Grotto
|
||||
{.entranceIndex = 0x003F, .content = 0x03, .scene = 0x51}, // Hyrule Field -> HF Open Grotto
|
||||
{.entranceIndex = 0x003F, .content = 0x22, .scene = 0x51}, // Hyrule Field -> HF Southeast Grotto
|
||||
{.entranceIndex = 0x05A4, .content = 0xFC, .scene = 0x63}, // Lon Lon Ranch -> LLR Grotto
|
||||
{.entranceIndex = 0x05B4, .content = 0xED, .scene = 0x56}, // SFM Entryway -> SFM Wolfos Grotto
|
||||
{.entranceIndex = 0x05BC, .content = 0xEE, .scene = 0x56}, // Sacred Forest Meadow -> SFM Storms Grotto
|
||||
{.entranceIndex = 0x036D, .content = 0xFF, .scene = 0x56}, // Sacred Forest Meadow -> SFM Fairy Grotto
|
||||
{.entranceIndex = 0x05B0, .content = 0xF5, .scene = 0x5B}, // LW Beyond Mido -> LW Scrubs Grotto
|
||||
{.entranceIndex = 0x003F, .content = 0x14, .scene = 0x5B}, // Lost Woods -> LW Near Shortcuts Grotto
|
||||
{.entranceIndex = 0x003F, .content = 0x2C, .scene = 0x55}, // Kokiri Forest -> KF Storms Grotto
|
||||
{.entranceIndex = 0x036D, .content = 0xFF, .scene = 0x58}, // Zoras Domain -> ZD Storms Grotto
|
||||
{.entranceIndex = 0x036D, .content = 0xFF, .scene = 0x5D}, // Gerudo Fortress -> GF Storms Grotto
|
||||
{.entranceIndex = 0x05BC, .content = 0xF0, .scene = 0x5A}, // GV Fortress Side -> GV Storms Grotto
|
||||
{.entranceIndex = 0x05AC, .content = 0xF2, .scene = 0x5A}, // GV Grotto Ledge -> GV Octorok Grotto
|
||||
{.entranceIndex = 0x05C4, .content = 0xF3, .scene = 0x5B}, // LW Beyond Mido -> Deku Theater
|
||||
};
|
||||
|
||||
// Information necessary for setting up returning from a grotto
|
||||
static const GrottoReturnInfo grottoReturnTable[NUM_GROTTOS] = {
|
||||
{.entranceIndex = 0x0123, .room = 0x00, .angle = 0xA71C, .pos = {.x = 62.5078f, .y = -32.0f, .z = -1296.2f}}, // Colossus Grotto -> Desert Colossus
|
||||
{.entranceIndex = 0x0102, .room = 0x00, .angle = 0x0000, .pos = {.x = -3039.34f, .y = -1033.0f, .z = 6080.74f}}, // LH Grotto -> Lake Hylia
|
||||
{.entranceIndex = 0x00EA, .room = 0x00, .angle = 0x0000, .pos = {.x = -1630.05f, .y = 100.0f, .z = -132.104f}}, // ZR Storms Grotto -> Zora River
|
||||
{.entranceIndex = 0x00EA, .room = 0x00, .angle = 0xE000, .pos = {.x = 649.507f, .y = 570.0f, .z = -346.853f}}, // ZR Fairy Grotto -> Zora River
|
||||
{.entranceIndex = 0x00EA, .room = 0x00, .angle = 0x8000, .pos = {.x = 362.29f, .y = 570.0f, .z = 111.48f}}, // ZR Open Grotto -> Zora River
|
||||
{.entranceIndex = 0x0246, .room = 0x01, .angle = 0x31C7, .pos = {.x = -1666.73f, .y = 721.0f, .z = -459.21f}}, // DMC Hammer Grotto -> DMC Lower Local
|
||||
{.entranceIndex = 0x0147, .room = 0x01, .angle = 0x238E, .pos = {.x = 63.723f, .y = 1265.0f, .z = 1791.39f}}, // DMC Upper Grotto -> DMC Upper Local
|
||||
{.entranceIndex = 0x014D, .room = 0x03, .angle = 0x0000, .pos = {.x = 1104.73f, .y = 580.0f, .z = -1159.95f}}, // GC Grotto -> GC Grotto Platform
|
||||
{.entranceIndex = 0x01B9, .room = 0x00, .angle = 0x8000, .pos = {.x = -387.584f, .y = 1386.0f, .z = -1213.05f}}, // DMT Storms Grotto -> Death Mountain
|
||||
{.entranceIndex = 0x01B9, .room = 0x00, .angle = 0x8000, .pos = {.x = -691.022f, .y = 1946.0f, .z = -312.969f}}, // DMT Cow Grotto -> Death Mountain Summit
|
||||
{.entranceIndex = 0x00DB, .room = 0x00, .angle = 0x0000, .pos = {.x = 855.238f, .y = 80.0f, .z = -234.095f}}, // Kak Open Grotto -> Kak Backyard
|
||||
{.entranceIndex = 0x00DB, .room = 0x00, .angle = 0x0000, .pos = {.x = -401.873f, .y = 0.0f, .z = 402.792f}}, // Kak Redead Grotto -> Kakariko Village
|
||||
{.entranceIndex = 0x0138, .room = 0x00, .angle = 0x9555, .pos = {.x = 1009.02f, .y = 1571.0f, .z = 855.532f}}, // HC Storms Grotto -> Castle Grounds
|
||||
{.entranceIndex = 0x01F9, .room = 0x00, .angle = 0x1555, .pos = {.x = -4949.58f, .y = -300.0f, .z = 2837.59f}}, // HF Tektite Grotto -> Hyrule Field
|
||||
{.entranceIndex = 0x01F9, .room = 0x00, .angle = 0xC000, .pos = {.x = 2050.6f, .y = 20.0f, .z = -160.397f}}, // HF Near Kak Grotto -> Hyrule Field
|
||||
{.entranceIndex = 0x01F9, .room = 0x00, .angle = 0x0000, .pos = {.x = -4447.66f, .y = -300.0f, .z = -393.191f}}, // HF Fairy Grotto -> Hyrule Field
|
||||
{.entranceIndex = 0x01F9, .room = 0x00, .angle = 0xE000, .pos = {.x = -1446.56f, .y = 0.0f, .z = 830.775f}}, // HF Near Market Grotto -> Hyrule Field
|
||||
{.entranceIndex = 0x01F9, .room = 0x00, .angle = 0x0000, .pos = {.x = -7874.07f, .y = -300.0f, .z = 6921.31f}}, // HF Cow Grotto -> Hyrule Field
|
||||
{.entranceIndex = 0x01F9, .room = 0x00, .angle = 0xEAAB, .pos = {.x = -4989.13f, .y = -700.0f, .z = 13821.1f}}, // HF Inside Fence Grotto -> Hyrule Field
|
||||
{.entranceIndex = 0x01F9, .room = 0x00, .angle = 0x8000, .pos = {.x = -4032.61f, .y = -700.0f, .z = 13831.5f}}, // HF Open Grotto -> Hyrule Field
|
||||
{.entranceIndex = 0x01F9, .room = 0x00, .angle = 0x9555, .pos = {.x = -288.313f, .y = -500.0f, .z = 12320.2f}}, // HF Southeast Grotto -> Hyrule Field
|
||||
{.entranceIndex = 0x0157, .room = 0x00, .angle = 0xAAAB, .pos = {.x = 1775.92f, .y = 0.0f, .z = 1486.82f}}, // LLR Grotto -> Lon Lon Ranch
|
||||
{.entranceIndex = 0x00FC, .room = 0x00, .angle = 0x8000, .pos = {.x = -189.861f, .y = 0.0f, .z = 1898.09f}}, // SFM Wolfos Grotto -> SFM Entryway
|
||||
{.entranceIndex = 0x00FC, .room = 0x00, .angle = 0xAAAB, .pos = {.x = 314.853f, .y = 480.0f, .z = -2300.39f}}, // SFM Storms Grotto -> Sacred Forest Meadow
|
||||
{.entranceIndex = 0x00FC, .room = 0x00, .angle = 0x0000, .pos = {.x = 55.034f, .y = 0.0f, .z = 250.595f}}, // SFM Fairy Grotto -> Sacred Forest Meadow
|
||||
{.entranceIndex = 0x01A9, .room = 0x08, .angle = 0x2000, .pos = {.x = 691.994f, .y = 0.0f, .z = -2502.2f}}, // LW Scrubs Grotto -> LW Beyond Mido
|
||||
{.entranceIndex = 0x011E, .room = 0x02, .angle = 0xE000, .pos = {.x = 905.755f, .y = 0.0f, .z = -901.43f}}, // LW Near Shortcuts Grotto -> Lost Woods
|
||||
{.entranceIndex = 0x0286, .room = 0x00, .angle = 0x4000, .pos = {.x = -507.065f, .y = 380.0f, .z = -1220.43f}}, // KF Storms Grotto -> Kokiri Forest
|
||||
{.entranceIndex = 0x0108, .room = 0x01, .angle = 0xD555, .pos = {.x = -855.68f, .y = 14.0f, .z = -474.422f}}, // ZD Storms Grotto -> Zoras Domain
|
||||
{.entranceIndex = 0x0129, .room = 0x00, .angle = 0x4000, .pos = {.x = 380.521f, .y = 333.0f, .z = -1560.74f}}, // GF Storms Grotto -> Gerudo Fortress
|
||||
{.entranceIndex = 0x022D, .room = 0x00, .angle = 0x9555, .pos = {.x = -1326.34f, .y = 15.0f, .z = -983.994f}}, // GV Storms Grotto -> GV Fortress Side
|
||||
{.entranceIndex = 0x0117, .room = 0x00, .angle = 0x8000, .pos = {.x = 291.513f, .y = -555.0f, .z = 1478.39f}}, // GV Octorok Grotto -> GV Grotto Ledge
|
||||
{.entranceIndex = 0x01A9, .room = 0x06, .angle = 0x4000, .pos = {.x = 109.281f, .y = -20.0f, .z = -1601.42f}}, // Deku Theater -> LW Beyond Mido
|
||||
};
|
||||
|
||||
static s16 grottoExitList[NUM_GROTTOS] = {0};
|
||||
static s16 grottoLoadList[NUM_GROTTOS] = {0};
|
||||
static s8 grottoId = 0xFF;
|
||||
static s8 lastEntranceType = NOT_GROTTO;
|
||||
|
||||
// Initialize both lists so that each index refers to itself. An index referring
|
||||
// to itself means that the entrance is not shuffled. Indices will be overwritten
|
||||
// later in Entrance_Init() in entrance.c if entrance shuffle is enabled.
|
||||
// For the grotto load list, the entrance index is 0x0700 + the grotto id
|
||||
// For the grotto exit list, the entrance index is 0x0800 + the grotto id
|
||||
void Grotto_InitExitAndLoadLists(void) {
|
||||
for (u8 i = 0; i < NUM_GROTTOS; i++) {
|
||||
grottoLoadList[i] = 0x0700 + i;
|
||||
grottoExitList[i] = 0x0800 + i;
|
||||
}
|
||||
}
|
||||
|
||||
void Grotto_SetExitOverride(s16 originalIndex, s16 overrideIndex) {
|
||||
s16 id = originalIndex & 0x00FF;
|
||||
grottoExitList[id] = overrideIndex;
|
||||
}
|
||||
|
||||
void Grotto_SetLoadOverride(s16 originalIndex, s16 overrideIndex) {
|
||||
s16 id = originalIndex & 0x00FF;
|
||||
grottoLoadList[id] = overrideIndex;
|
||||
}
|
||||
|
||||
static void Grotto_SetupReturnInfo(GrottoReturnInfo grotto, RespawnMode respawnMode) {
|
||||
// Set necessary grotto return data to the Entrance Point, so that voiding out and setting FW work correctly
|
||||
gSaveContext.respawn[respawnMode].entranceIndex = grotto.entranceIndex;
|
||||
gSaveContext.respawn[respawnMode].roomIndex = grotto.room;
|
||||
|
||||
if (false /*mixGrottos == ON*/ || false /*decoupledEntrances == ON*/) {
|
||||
gSaveContext.respawn[respawnMode].playerParams = 0x04FF; // exiting grotto with no initial camera focus
|
||||
}
|
||||
|
||||
gSaveContext.respawn[respawnMode].yaw = grotto.angle;
|
||||
gSaveContext.respawn[respawnMode].pos = grotto.pos;
|
||||
//TODO If Mixed Entrance Pools or decoupled entrances are active, set these flags to 0 instead of restoring them
|
||||
if (false /*mixGrottos == ON*/ || false /*decoupledEntrances == ON*/) {
|
||||
gSaveContext.respawn[respawnMode].tempSwchFlags = 0;
|
||||
gSaveContext.respawn[respawnMode].tempCollectFlags = 0;
|
||||
} else {
|
||||
gSaveContext.respawn[respawnMode].tempSwchFlags = gSaveContext.respawn[RESPAWN_MODE_RETURN].tempSwchFlags;
|
||||
gSaveContext.respawn[respawnMode].tempCollectFlags = gSaveContext.respawn[RESPAWN_MODE_RETURN].tempCollectFlags;
|
||||
}
|
||||
}
|
||||
|
||||
// Translates and overrides the passed in entrance index if it corresponds to a
|
||||
// special grotto entrance (grotto load or returnpoint)
|
||||
s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex) {
|
||||
|
||||
// Don't change anything unless grotto shuffle has been enabled
|
||||
if (!Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES)) {
|
||||
return nextEntranceIndex;
|
||||
}
|
||||
|
||||
// If Link hits a grotto exit, load the entrance index from the grotto exit list
|
||||
// based on the current grotto ID
|
||||
if (nextEntranceIndex == 0x7FFF) {
|
||||
// SaveFile_SetEntranceDiscovered(0x0800 + grottoId);
|
||||
nextEntranceIndex = grottoExitList[grottoId];
|
||||
}
|
||||
|
||||
// Get the new grotto id from the next entrance
|
||||
grottoId = nextEntranceIndex & 0x00FF;
|
||||
|
||||
// Grotto Returns
|
||||
if (nextEntranceIndex >= 0x0800 && nextEntranceIndex < 0x0800 + NUM_GROTTOS) {
|
||||
|
||||
GrottoReturnInfo grotto = grottoReturnTable[grottoId];
|
||||
Grotto_SetupReturnInfo(grotto, RESPAWN_MODE_RETURN);
|
||||
Grotto_SetupReturnInfo(grotto, RESPAWN_MODE_DOWN);
|
||||
|
||||
// When the nextEntranceIndex is determined by a dynamic exit, we have
|
||||
// to set the respawn information and nextEntranceIndex manually
|
||||
if (gPlayState != NULL && gPlayState->nextEntranceIndex != -1) {
|
||||
gSaveContext.respawnFlag = 2;
|
||||
nextEntranceIndex = grotto.entranceIndex;
|
||||
gPlayState->fadeTransition = 3;
|
||||
gSaveContext.nextTransition = 3;
|
||||
// Otherwise return 0x7FFF and let the game handle it
|
||||
} else {
|
||||
nextEntranceIndex = 0x7FFF;
|
||||
}
|
||||
|
||||
lastEntranceType = GROTTO_RETURN;
|
||||
// Grotto Loads
|
||||
} else if (nextEntranceIndex >= 0x0700 && nextEntranceIndex < 0x0800) {
|
||||
|
||||
// Set the respawn data to load the correct grotto
|
||||
GrottoLoadInfo grotto = grottoLoadTable[grottoId];
|
||||
gSaveContext.respawn[RESPAWN_MODE_RETURN].data = grotto.content;
|
||||
nextEntranceIndex = grotto.entranceIndex;
|
||||
|
||||
lastEntranceType = NOT_GROTTO;
|
||||
// Otherwise just unset the current grotto ID
|
||||
} else {
|
||||
grottoId = 0xFF;
|
||||
lastEntranceType = NOT_GROTTO;
|
||||
}
|
||||
|
||||
return nextEntranceIndex;
|
||||
}
|
||||
|
||||
// Override the entrance index when entering into a grotto actor
|
||||
// thisx - pointer to the grotto actor
|
||||
void Grotto_OverrideActorEntrance(Actor* thisx) {
|
||||
|
||||
// Vanilla Behavior if grottos aren't shuffled
|
||||
if (!Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES)) {
|
||||
return;
|
||||
}
|
||||
|
||||
s8 grottoContent = thisx->params & 0x00FF;
|
||||
|
||||
// Loop through the grotto load table until we find the matching index based
|
||||
// on content and scene
|
||||
for (s16 index = 0; index < NUM_GROTTOS; index++) {
|
||||
|
||||
if (grottoContent == grottoLoadTable[index].content && gPlayState->sceneNum == grottoLoadTable[index].scene) {
|
||||
// Find the override for the matching index from the grotto Load List
|
||||
// SaveFile_SetEntranceDiscovered(0x0700 + index);
|
||||
index = grottoLoadList[index];
|
||||
|
||||
// Run the index through the special entrances override check
|
||||
lastEntranceType = GROTTO_LOAD;
|
||||
gPlayState->nextEntranceIndex = Grotto_OverrideSpecialEntrance(index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the respawn flag for when we want to return from a grotto entrance
|
||||
// Used for Sun's Song and Game Over, which usually don't restore saved position data
|
||||
void Grotto_ForceGrottoReturn(void) {
|
||||
if (lastEntranceType == GROTTO_RETURN && Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES)) {
|
||||
gSaveContext.respawnFlag = 2;
|
||||
gSaveContext.respawn[RESPAWN_MODE_RETURN].playerParams = 0x0DFF;
|
||||
gSaveContext.respawn[RESPAWN_MODE_RETURN].pos = grottoReturnTable[grottoId].pos;
|
||||
//Save the current temp flags in the grotto return point, so they'll properly keep their values.
|
||||
gSaveContext.respawn[RESPAWN_MODE_RETURN].tempSwchFlags = gPlayState->actorCtx.flags.tempSwch;
|
||||
gSaveContext.respawn[RESPAWN_MODE_RETURN].tempCollectFlags = gPlayState->actorCtx.flags.tempCollect;
|
||||
}
|
||||
}
|
||||
|
||||
// Used for the DMT special voids, which usually don't restore saved position data
|
||||
void Grotto_ForceRegularVoidOut(void) {
|
||||
if (lastEntranceType == GROTTO_RETURN && Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES)) {
|
||||
gSaveContext.respawn[RESPAWN_MODE_DOWN] = gSaveContext.respawn[RESPAWN_MODE_RETURN];
|
||||
gSaveContext.respawn[RESPAWN_MODE_DOWN].playerParams = 0x0DFF;
|
||||
gSaveContext.respawn[RESPAWN_MODE_DOWN].pos = grottoReturnTable[grottoId].pos;
|
||||
gSaveContext.respawnFlag = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If returning to a FW point saved at a grotto exit, copy the FW data to the Grotto Return Point
|
||||
// so that Sun's Song and Game Over will behave correctly
|
||||
void Grotto_SetupReturnInfoOnFWReturn(void) {
|
||||
if (Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES)) {
|
||||
gSaveContext.respawn[RESPAWN_MODE_RETURN] = gSaveContext.respawn[RESPAWN_MODE_TOP];
|
||||
gSaveContext.respawn[RESPAWN_MODE_RETURN].playerParams = 0x0DFF;
|
||||
lastEntranceType = GROTTO_RETURN;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the renamed entrance index based on the grotto contents and exit scene number
|
||||
s16 Grotto_GetRenamedGrottoIndexFromOriginal(s8 content, s8 scene) {
|
||||
for (s16 index = 0; index < NUM_GROTTOS; index++) {
|
||||
if (content == grottoLoadTable[index].content && scene == grottoLoadTable[index].scene) {
|
||||
return 0x0700 | index;
|
||||
}
|
||||
}
|
||||
|
||||
return 0x0700;
|
||||
}
|
32
soh/soh/Enhancements/randomizer/randomizer_grotto.h
Normal file
32
soh/soh/Enhancements/randomizer/randomizer_grotto.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef _RANDO_GROTTO_H_
|
||||
#define _RANDO_GROTTO_H_
|
||||
|
||||
#include "z64math.h"
|
||||
|
||||
#define NUM_GROTTOS 33
|
||||
#define NOT_GROTTO 0
|
||||
#define GROTTO_LOAD 1
|
||||
#define GROTTO_RETURN 2
|
||||
|
||||
typedef struct {
|
||||
s16 entranceIndex;
|
||||
s8 content;
|
||||
s8 scene;
|
||||
} GrottoLoadInfo;
|
||||
|
||||
typedef struct {
|
||||
s16 entranceIndex;
|
||||
s8 room;
|
||||
s16 angle;
|
||||
Vec3f pos;
|
||||
} GrottoReturnInfo;
|
||||
|
||||
void Grotto_InitExitAndLoadLists(void);
|
||||
void Grotto_SetExitOverride(s16 originalIndex, s16 overrideIndex);
|
||||
void Grotto_SetLoadOverride(s16 originalIndex, s16 overrideIndex);
|
||||
s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex);
|
||||
void Grotto_ForceGrottoReturn(void);
|
||||
void Grotto_ForceRegularVoidOut(void);
|
||||
s16 Grotto_GetRenamedGrottoIndexFromOriginal(s8 content, s8 scene);
|
||||
|
||||
#endif //_RANDO_GROTTO_H_
|
@ -1836,6 +1836,10 @@ extern "C" bool Randomizer_IsTrialRequired(RandomizerInf trial) {
|
||||
return OTRGlobals::Instance->gRandomizer->IsTrialRequired(trial);
|
||||
}
|
||||
|
||||
extern "C" void Randomizer_LoadEntranceOverrides(const char* spoilerFileName, bool silent) {
|
||||
OTRGlobals::Instance->gRandomizer->LoadEntranceOverrides(spoilerFileName, silent);
|
||||
}
|
||||
|
||||
extern "C" u32 SpoilerFileExists(const char* spoilerFileName) {
|
||||
return OTRGlobals::Instance->gRandomizer->SpoilerFileExists(spoilerFileName);
|
||||
}
|
||||
|
@ -120,6 +120,7 @@ void Randomizer_LoadMerchantMessages(const char* spoilerFileName);
|
||||
void Randomizer_LoadRequiredTrials(const char* spoilerFileName);
|
||||
void Randomizer_LoadMasterQuestDungeons(const char* spoilerFileName);
|
||||
void Randomizer_LoadItemLocations(const char* spoilerFileName, bool silent);
|
||||
void Randomizer_LoadEntranceOverrides(const char* spoilerFileName, bool silent);
|
||||
bool Randomizer_IsTrialRequired(RandomizerInf trial);
|
||||
GetItemEntry Randomizer_GetItemFromActor(s16 actorId, s16 sceneNum, s16 actorParams, GetItemID ogId);
|
||||
GetItemEntry Randomizer_GetItemFromActorWithoutObtainabilityCheck(s16 actorId, s16 sceneNum, s16 actorParams, GetItemID ogId);
|
||||
|
@ -137,6 +137,16 @@ void SaveManager::LoadRandomizerVersion2() {
|
||||
});
|
||||
});
|
||||
|
||||
SaveManager::Instance->LoadArray("entrances", ARRAY_COUNT(gSaveContext.entranceOverrides), [&](size_t i) {
|
||||
SaveManager::Instance->LoadStruct("", [&]() {
|
||||
SaveManager::Instance->LoadData("index", gSaveContext.entranceOverrides[i].index);
|
||||
SaveManager::Instance->LoadData("destination", gSaveContext.entranceOverrides[i].destination);
|
||||
SaveManager::Instance->LoadData("blueWarp", gSaveContext.entranceOverrides[i].blueWarp);
|
||||
SaveManager::Instance->LoadData("override", gSaveContext.entranceOverrides[i].override);
|
||||
SaveManager::Instance->LoadData("overrideDestination", gSaveContext.entranceOverrides[i].overrideDestination);
|
||||
});
|
||||
});
|
||||
|
||||
SaveManager::Instance->LoadArray("seed", ARRAY_COUNT(gSaveContext.seedIcons), [&](size_t i) {
|
||||
SaveManager::Instance->LoadData("", gSaveContext.seedIcons[i]);
|
||||
});
|
||||
@ -209,6 +219,16 @@ void SaveManager::SaveRandomizer() {
|
||||
});
|
||||
});
|
||||
|
||||
SaveManager::Instance->SaveArray("entrances", ARRAY_COUNT(gSaveContext.entranceOverrides), [&](size_t i) {
|
||||
SaveManager::Instance->SaveStruct("", [&]() {
|
||||
SaveManager::Instance->SaveData("index", gSaveContext.entranceOverrides[i].index);
|
||||
SaveManager::Instance->SaveData("destination", gSaveContext.entranceOverrides[i].destination);
|
||||
SaveManager::Instance->SaveData("blueWarp", gSaveContext.entranceOverrides[i].blueWarp);
|
||||
SaveManager::Instance->SaveData("override", gSaveContext.entranceOverrides[i].override);
|
||||
SaveManager::Instance->SaveData("overrideDestination", gSaveContext.entranceOverrides[i].overrideDestination);
|
||||
});
|
||||
});
|
||||
|
||||
SaveManager::Instance->SaveArray("seed", ARRAY_COUNT(gSaveContext.seedIcons), [&](size_t i) {
|
||||
SaveManager::Instance->SaveData("", gSaveContext.seedIcons[i]);
|
||||
});
|
||||
|
@ -31,6 +31,8 @@
|
||||
|
||||
#include "scenes/misc/hakaana_ouke/hakaana_ouke_scene.h"
|
||||
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
u16 D_8011E1C0 = 0;
|
||||
u16 D_8011E1C4 = 0;
|
||||
|
||||
@ -1236,6 +1238,10 @@ void Cutscene_Command_Terminator(PlayState* play, CutsceneContext* csCtx, CsCmdB
|
||||
play->fadeTransition = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
if (randoCsSkip) {
|
||||
Entrance_OverrideCutsceneEntrance(cmd->base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "textures/do_action_static/do_action_static.h"
|
||||
#include "textures/icon_item_static/icon_item_static.h"
|
||||
#include "soh/Enhancements/randomizer/adult_trade_shuffle.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <stdlib.h>
|
||||
@ -6361,6 +6362,12 @@ void Interface_Update(PlayState* play) {
|
||||
|
||||
gSaveContext.respawnFlag = -2;
|
||||
play->nextEntranceIndex = gSaveContext.entranceIndex;
|
||||
|
||||
// In ER, handle sun song respawn from last entrance from grottos
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
|
||||
Grotto_ForceGrottoReturn();
|
||||
}
|
||||
|
||||
play->sceneLoadFlag = 0x14;
|
||||
gSaveContext.sunsSongState = SUNSSONG_INACTIVE;
|
||||
func_800F6964(30);
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <ImGuiImpl.h>
|
||||
#include "soh/frame_interpolation.h"
|
||||
#include "soh/Enhancements/debugconsole.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
#include <overlays/actors/ovl_En_Niw/z_en_niw.h>
|
||||
|
||||
#include <time.h>
|
||||
@ -158,6 +159,11 @@ void Play_Destroy(GameState* thisx) {
|
||||
PlayState* play = (PlayState*)thisx;
|
||||
Player* player = GET_PLAYER(play);
|
||||
|
||||
// In ER, remove link from epona when entering somewhere that doesn't support epona
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) {
|
||||
Entrance_HandleEponaState();
|
||||
}
|
||||
|
||||
play->state.gfxCtx->callback = NULL;
|
||||
play->state.gfxCtx->callbackParam = 0;
|
||||
SREG(91) = 0;
|
||||
@ -512,6 +518,7 @@ void Play_Init(GameState* thisx) {
|
||||
play,
|
||||
gEntranceTable[((void)0, gSaveContext.entranceIndex) + ((void)0, gSaveContext.sceneSetupIndex)].scene,
|
||||
gEntranceTable[((void)0, gSaveContext.sceneSetupIndex) + ((void)0, gSaveContext.entranceIndex)].spawn);
|
||||
|
||||
osSyncPrintf("\nSCENE_NO=%d COUNTER=%d\n", ((void)0, gSaveContext.entranceIndex), gSaveContext.sceneSetupIndex);
|
||||
|
||||
Cutscene_HandleEntranceTriggers(play);
|
||||
@ -1754,6 +1761,10 @@ void* Play_LoadFile(PlayState* play, RomFile* file) {
|
||||
}
|
||||
|
||||
void Play_InitEnvironment(PlayState* play, s16 skyboxId) {
|
||||
// For entrance rando, ensure the correct weather state and sky mode is applied
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
|
||||
Entrance_OverrideWeatherState();
|
||||
}
|
||||
Skybox_Init(&play->state, &play->skyboxCtx, skyboxId);
|
||||
Environment_Init(play, &play->envCtx, 0);
|
||||
}
|
||||
@ -1781,6 +1792,10 @@ void Play_InitScene(PlayState* play, s32 spawn)
|
||||
void Play_SpawnScene(PlayState* play, s32 sceneNum, s32 spawn) {
|
||||
|
||||
OTRPlay_SpawnScene(play, sceneNum, spawn);
|
||||
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
|
||||
Entrance_OverrideSpawnScene(sceneNum, spawn);
|
||||
}
|
||||
}
|
||||
|
||||
void func_800C016C(PlayState* play, Vec3f* src, Vec3f* dest) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <string.h>
|
||||
#include <soh/Enhancements/randomizer/randomizerTypes.h>
|
||||
#include <soh/Enhancements/randomizer/randomizer_inf.h>
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
#include "soh/Enhancements/randomizer/adult_trade_shuffle.h"
|
||||
|
||||
#define NUM_DUNGEONS 8
|
||||
@ -196,6 +197,19 @@ void Sram_OpenSave() {
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the modified entrance table and entrance shuffle table for rando
|
||||
if (gSaveContext.n64ddFlag) {
|
||||
Entrance_Init();
|
||||
if (!CVar_GetS32("gRememberSaveLocation", 0) || gSaveContext.savedSceneNum == SCENE_YOUSEI_IZUMI_TATE ||
|
||||
gSaveContext.savedSceneNum == SCENE_KAKUSIANA) {
|
||||
Entrance_SetSavewarpEntrance();
|
||||
}
|
||||
} else {
|
||||
// When going from a rando save to a vanilla save within the same game instance
|
||||
// we need to reset the entrance table back to its vanilla state
|
||||
Entrance_ResetEntranceTable();
|
||||
}
|
||||
|
||||
osSyncPrintf("scene_no = %d\n", gSaveContext.entranceIndex);
|
||||
osSyncPrintf(VT_RST);
|
||||
|
||||
|
@ -47,7 +47,10 @@ void BgSpot01Idosoko_Init(Actor* thisx, PlayState* play) {
|
||||
Actor_ProcessInitChain(&this->dyna.actor, sInitChain);
|
||||
CollisionHeader_GetVirtual(&gKakarikoBOTWStoneCol, &colHeader);
|
||||
this->dyna.bgId = DynaPoly_SetBgActor(play, &play->colCtx.dyna, &this->dyna.actor, colHeader);
|
||||
if (!LINK_IS_ADULT) {
|
||||
// If dungeon entrance randomizer is on, remove the well stone as adult Link when
|
||||
// child Link has drained the water to the well
|
||||
if (!LINK_IS_ADULT || gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) &&
|
||||
Flags_GetEventChkInf(0x67)) {
|
||||
Actor_Kill(&this->dyna.actor);
|
||||
} else {
|
||||
BgSpot01Idosoko_SetupAction(this, func_808ABF54);
|
||||
|
@ -58,6 +58,12 @@ void func_808B3420(BgSpot12Saku* this, PlayState* play, CollisionHeader* collisi
|
||||
void BgSpot12Saku_Init(Actor* thisx, PlayState* play) {
|
||||
BgSpot12Saku* this = (BgSpot12Saku*)thisx;
|
||||
|
||||
// If ER is on, force the gate to always use its permanent flag
|
||||
// (which it only uses in Child Gerudo Fortress in the vanilla game)
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES)) {
|
||||
thisx->params = 0x0002;
|
||||
}
|
||||
|
||||
func_808B3420(this, play, &gGerudoFortressGTGShutterCol, DPM_UNK);
|
||||
Actor_ProcessInitChain(&this->dyna.actor, sInitChain);
|
||||
if (Flags_GetSwitch(play, this->dyna.actor.params & 0x3F)) {
|
||||
@ -125,6 +131,12 @@ void func_808B37AC(BgSpot12Saku* this, PlayState* play) {
|
||||
void BgSpot12Saku_Update(Actor* thisx, PlayState* play) {
|
||||
BgSpot12Saku* this = (BgSpot12Saku*)thisx;
|
||||
|
||||
// If ER is on, when the guard opens the GtG gate its permanent flag will be set.
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) &&
|
||||
Flags_GetSwitch(play, 0x3A)) {
|
||||
Flags_SetSwitch(play, 0x2);
|
||||
}
|
||||
|
||||
if (this->timer > 0) {
|
||||
this->timer--;
|
||||
}
|
||||
|
@ -73,7 +73,9 @@ void BgTreemouth_Init(Actor* thisx, PlayState* play) {
|
||||
|
||||
if ((gSaveContext.sceneSetupIndex < 4) && !LINK_IS_ADULT) {
|
||||
BgTreemouth_SetupAction(this, func_808BC8B8);
|
||||
} else if (LINK_IS_ADULT || (gSaveContext.sceneSetupIndex == 7)) {
|
||||
// If dungeon entrance randomizer is on, keep the tree mouth open when link is adult and sword & shield have been shown to mido
|
||||
} else if ((LINK_IS_ADULT && (!gSaveContext.n64ddFlag || !Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES)) ||
|
||||
!Flags_GetEventChkInf(0x4)) || (gSaveContext.sceneSetupIndex == 7)) {
|
||||
this->unk_168 = 0.0f;
|
||||
BgTreemouth_SetupAction(this, BgTreemouth_DoNothing);
|
||||
} else {
|
||||
|
@ -867,7 +867,12 @@ void func_80986B2C(PlayState* play) {
|
||||
if (Message_GetState(&play->msgCtx) == TEXT_STATE_CLOSING) {
|
||||
Player* player = GET_PLAYER(play);
|
||||
|
||||
// In entrance rando have impa bring link back to the front of castle grounds
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) {
|
||||
play->nextEntranceIndex = 0x0138;
|
||||
} else {
|
||||
play->nextEntranceIndex = 0xCD;
|
||||
}
|
||||
play->fadeTransition = 38;
|
||||
play->sceneLoadFlag = 0x14;
|
||||
func_8002DF54(play, &player->actor, 8);
|
||||
@ -911,7 +916,12 @@ void GivePlayerRandoRewardImpa(Actor* impa, PlayState* play, RandomizerCheck che
|
||||
play->sceneLoadFlag = 0x14;
|
||||
play->fadeTransition = 3;
|
||||
gSaveContext.nextTransition = 3;
|
||||
// In entrance rando have impa bring link back to the front of castle grounds
|
||||
if (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) {
|
||||
play->nextEntranceIndex = 0x0138;
|
||||
} else {
|
||||
play->nextEntranceIndex = 0x0594;
|
||||
}
|
||||
gSaveContext.nextCutsceneIndex = 0;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "z_door_ana.h"
|
||||
#include "objects/gameplay_field_keep/gameplay_field_keep.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
#define FLAGS ACTOR_FLAG_25
|
||||
|
||||
@ -139,6 +140,12 @@ void DoorAna_WaitOpen(DoorAna* this, PlayState* play) {
|
||||
destinationIdx = this->actor.home.rot.z + 1;
|
||||
}
|
||||
play->nextEntranceIndex = entrances[destinationIdx];
|
||||
|
||||
// In ER, load the correct entrance based on the grotto link is falling into
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
|
||||
Grotto_OverrideActorEntrance(&this->actor);
|
||||
}
|
||||
|
||||
DoorAna_SetupAction(this, DoorAna_GrabPlayer);
|
||||
} else {
|
||||
if (!Player_InCsMode(play) && !(player->stateFlags1 & 0x8800000) &&
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "z_door_warp1.h"
|
||||
#include "objects/object_warp1/object_warp1.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
#define FLAGS 0
|
||||
|
||||
@ -580,6 +581,11 @@ void DoorWarp1_ChildWarpOut(DoorWarp1* this, PlayState* play) {
|
||||
play->nextEntranceIndex = 0x10E;
|
||||
gSaveContext.nextCutsceneIndex = 0;
|
||||
}
|
||||
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES)) {
|
||||
Entrance_OverrideBlueWarp();
|
||||
}
|
||||
|
||||
osSyncPrintf("\n\n\nおわりおわり");
|
||||
play->sceneLoadFlag = 0x14;
|
||||
play->fadeTransition = 7;
|
||||
@ -682,6 +688,10 @@ void DoorWarp1_RutoWarpOut(DoorWarp1* this, PlayState* play) {
|
||||
gSaveContext.nextCutsceneIndex = 0xFFF0;
|
||||
}
|
||||
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES)) {
|
||||
Entrance_OverrideBlueWarp();
|
||||
}
|
||||
|
||||
play->sceneLoadFlag = 0x14;
|
||||
play->fadeTransition = 7;
|
||||
}
|
||||
@ -890,6 +900,11 @@ void DoorWarp1_AdultWarpOut(DoorWarp1* this, PlayState* play) {
|
||||
gSaveContext.nextCutsceneIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES)) {
|
||||
Entrance_OverrideBlueWarp();
|
||||
}
|
||||
|
||||
play->sceneLoadFlag = 0x14;
|
||||
play->fadeTransition = 3;
|
||||
gSaveContext.nextTransition = 7;
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "z_en_ge1.h"
|
||||
#include "vt.h"
|
||||
#include "objects/object_ge1/object_ge1.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
#define FLAGS (ACTOR_FLAG_0 | ACTOR_FLAG_3)
|
||||
|
||||
@ -93,6 +94,20 @@ void EnGe1_Init(Actor* thisx, PlayState* play) {
|
||||
s32 pad;
|
||||
EnGe1* this = (EnGe1*)thisx;
|
||||
|
||||
// When spawning the gate operator, also spawn an extra gate operator on the wasteland side
|
||||
if (gSaveContext.n64ddFlag && (Randomizer_GetSettingValue(RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD) ||
|
||||
Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) && (this->actor.params & 0xFF) == GE1_TYPE_GATE_OPERATOR) {
|
||||
// Spawn the extra gaurd with params matching the custom type added (0x0300 + 0x02)
|
||||
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_GE1, -1358.0f, 88.0f, -3018.0f, 0, 0x95B0, 0,
|
||||
0x0300 | GE1_TYPE_EXTRA_GATE_OPERATOR);
|
||||
}
|
||||
|
||||
// Convert the "extra" gate operator into a normal one so it matches the same params
|
||||
if ((this->actor.params & 0xFF) == GE1_TYPE_EXTRA_GATE_OPERATOR) {
|
||||
this->actor.params &= ~0xFF;
|
||||
this->actor.params |= GE1_TYPE_GATE_OPERATOR;
|
||||
}
|
||||
|
||||
ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 30.0f);
|
||||
SkelAnime_InitFlex(play, &this->skelAnime, &gGerudoWhiteSkel, &gGerudoWhiteIdleAnim, this->jointTable,
|
||||
this->morphTable, GE1_LIMB_MAX);
|
||||
@ -169,7 +184,12 @@ void EnGe1_Init(Actor* thisx, PlayState* play) {
|
||||
this->hairstyle = GE1_HAIR_STRAIGHT;
|
||||
|
||||
if (EnGe1_CheckCarpentersFreed()) {
|
||||
// If the gtg gate is permanently open, don't let the gaurd charge to open it again
|
||||
if (gSaveContext.n64ddFlag && gSaveContext.sceneFlags[93].swch & 0x00000004) {
|
||||
this->actionFunc = EnGe1_SetNormalText;
|
||||
} else {
|
||||
this->actionFunc = EnGe1_CheckForCard_GTGGuard;
|
||||
}
|
||||
} else {
|
||||
this->actionFunc = EnGe1_WatchForPlayerFrontOnly;
|
||||
}
|
||||
@ -247,6 +267,10 @@ void EnGe1_KickPlayer(EnGe1* this, PlayState* play) {
|
||||
play->nextEntranceIndex = 0x3B4;
|
||||
}
|
||||
|
||||
if (gSaveContext.n64ddFlag) {
|
||||
Entrance_OverrideGeurdoGuardCapture();
|
||||
}
|
||||
|
||||
play->fadeTransition = 0x26;
|
||||
play->sceneLoadFlag = 0x14;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ typedef void (*EnGe1ActionFunc)(struct EnGe1*, PlayState*);
|
||||
typedef enum {
|
||||
/* 0x00 */ GE1_TYPE_GATE_GUARD,
|
||||
/* 0x01 */ GE1_TYPE_GATE_OPERATOR,
|
||||
/* 0x02 */ GE1_TYPE_EXTRA_GATE_OPERATOR, // Custom guard type for entrance randomizer to open the gate
|
||||
/* 0x04 */ GE1_TYPE_NORMAL = 4,
|
||||
/* 0x05 */ GE1_TYPE_VALLEY_FLOOR,
|
||||
/* 0x45 */ GE1_TYPE_HORSEBACK_ARCHERY = 0x45,
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "z_en_ge2.h"
|
||||
#include "vt.h"
|
||||
#include "objects/object_gla/object_gla.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
#define FLAGS (ACTOR_FLAG_0 | ACTOR_FLAG_3 | ACTOR_FLAG_4)
|
||||
|
||||
@ -253,6 +254,10 @@ void EnGe2_CaptureClose(EnGe2* this, PlayState* play) {
|
||||
play->nextEntranceIndex = 0x3B4;
|
||||
}
|
||||
|
||||
if (gSaveContext.n64ddFlag) {
|
||||
Entrance_OverrideGeurdoGuardCapture();
|
||||
}
|
||||
|
||||
play->fadeTransition = 0x26;
|
||||
play->sceneLoadFlag = 0x14;
|
||||
}
|
||||
@ -279,6 +284,10 @@ void EnGe2_CaptureCharge(EnGe2* this, PlayState* play) {
|
||||
play->nextEntranceIndex = 0x3B4;
|
||||
}
|
||||
|
||||
if (gSaveContext.n64ddFlag) {
|
||||
Entrance_OverrideGeurdoGuardCapture();
|
||||
}
|
||||
|
||||
play->fadeTransition = 0x26;
|
||||
play->sceneLoadFlag = 0x14;
|
||||
}
|
||||
|
@ -330,6 +330,12 @@ void EnIshi_Init(Actor* thisx, PlayState* play) {
|
||||
Actor_Kill(&this->actor);
|
||||
return;
|
||||
}
|
||||
// If dungeon entrance randomizer is on, remove the grey boulders that normally
|
||||
// block child Link from reaching the Fire Temple entrance.
|
||||
if (type == ROCK_LARGE && gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) &&
|
||||
play->sceneNum == 0x061) { // Death Mountain Creater
|
||||
Actor_Kill(&this->actor);
|
||||
}
|
||||
EnIshi_SetupWait(this);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "vt.h"
|
||||
#include "overlays/actors/ovl_En_Syateki_Itm/z_en_syateki_itm.h"
|
||||
#include "objects/object_ossan/object_ossan.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
#define FLAGS (ACTOR_FLAG_0 | ACTOR_FLAG_3 | ACTOR_FLAG_4 | ACTOR_FLAG_27)
|
||||
|
||||
@ -154,6 +155,15 @@ void EnSyatekiMan_Init(Actor* thisx, PlayState* play) {
|
||||
s32 pad;
|
||||
EnSyatekiMan* this = (EnSyatekiMan*)thisx;
|
||||
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_INTERIOR_ENTRANCES)) {
|
||||
// If child is in the adult shooting gallery or adult in the child shooting gallery, then despawn the shooting gallery man
|
||||
if ((LINK_IS_CHILD && Entrance_SceneAndSpawnAre(0x42, 0x00)) || //Kakariko Village -> Adult Shooting Gallery, index 003B in the entrance table
|
||||
(LINK_IS_ADULT && Entrance_SceneAndSpawnAre(0x42, 0x01))) { //Market -> Child Shooting Gallery, index 016D in the entrance table
|
||||
Actor_Kill(thisx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
osSyncPrintf("\n\n");
|
||||
// "Old man appeared!! Muhohohohohohohon"
|
||||
osSyncPrintf(VT_FGCOL(GREEN) "☆☆☆☆☆ 親父登場!!むほほほほほほほーん ☆☆☆☆☆ \n" VT_RST);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <soh/Enhancements/custom-message/CustomMessageTypes.h>
|
||||
#include "soh/Enhancements/item-tables/ItemTableTypes.h"
|
||||
#include "soh/Enhancements/debugconsole.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
typedef enum {
|
||||
/* 0x00 */ KNOB_ANIM_ADULT_L,
|
||||
@ -4221,14 +4222,26 @@ s32 func_80839034(PlayState* play, Player* this, CollisionPoly* poly, u32 bgId)
|
||||
func_800994A0(play);
|
||||
} else {
|
||||
play->nextEntranceIndex = play->setupExitList[sp3C - 1];
|
||||
|
||||
// Main override for entrance rando and entrance skips
|
||||
if (gSaveContext.n64ddFlag) {
|
||||
play->nextEntranceIndex = Entrance_OverrideNextIndex(play->nextEntranceIndex);
|
||||
}
|
||||
|
||||
if (play->nextEntranceIndex == 0x7FFF) {
|
||||
gSaveContext.respawnFlag = 2;
|
||||
play->nextEntranceIndex = gSaveContext.respawn[RESPAWN_MODE_RETURN].entranceIndex;
|
||||
play->fadeTransition = 3;
|
||||
gSaveContext.nextTransition = 3;
|
||||
} else if (play->nextEntranceIndex >= 0x7FF9) {
|
||||
// handle dynamic exits
|
||||
if (gSaveContext.n64ddFlag) {
|
||||
play->nextEntranceIndex = Entrance_OverrideDynamicExit(D_80854514[play->nextEntranceIndex - 0x7FF9] + play->curSpawn);
|
||||
} else {
|
||||
play->nextEntranceIndex =
|
||||
D_808544F8[D_80854514[play->nextEntranceIndex - 0x7FF9] + play->curSpawn];
|
||||
}
|
||||
|
||||
func_800994A0(play);
|
||||
} else {
|
||||
if (SurfaceType_GetSlope(&play->colCtx, poly, bgId) == 2) {
|
||||
@ -13400,6 +13413,10 @@ void func_8084F88C(Player* this, PlayState* play) {
|
||||
play->nextEntranceIndex = 0x0088;
|
||||
} else if (this->unk_84F < 0) {
|
||||
Play_TriggerRespawn(play);
|
||||
// In ER, handle DMT and other special void outs to respawn from last entrance from grotto
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
|
||||
Grotto_ForceRegularVoidOut();
|
||||
}
|
||||
} else {
|
||||
Play_TriggerVoidOut(play);
|
||||
}
|
||||
|
@ -458,6 +458,7 @@ void FileChoose_UpdateRandomizer() {
|
||||
Randomizer_LoadItemLocations(fileLoc, silent);
|
||||
Randomizer_LoadMerchantMessages(fileLoc);
|
||||
Randomizer_LoadMasterQuestDungeons(fileLoc);
|
||||
Randomizer_LoadEntranceOverrides(fileLoc, silent);
|
||||
fileSelectSpoilerFileLoaded = true;
|
||||
}
|
||||
}
|
||||
@ -2136,6 +2137,7 @@ void FileChoose_LoadGame(GameState* thisx) {
|
||||
Randomizer_LoadRequiredTrials("");
|
||||
Randomizer_LoadMerchantMessages("");
|
||||
Randomizer_LoadMasterQuestDungeons("");
|
||||
Randomizer_LoadEntranceOverrides("", true);
|
||||
|
||||
gSaveContext.respawn[0].entranceIndex = -1;
|
||||
gSaveContext.respawnFlag = 0;
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include "vt.h"
|
||||
#include "alloca.h"
|
||||
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
void Select_LoadTitle(SelectContext* this) {
|
||||
this->state.running = false;
|
||||
SET_NEXT_GAMESTATE(&this->state, Title_Init, TitleContext);
|
||||
@ -32,6 +34,12 @@ void Select_LoadGame(SelectContext* this, s32 entranceIndex) {
|
||||
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_STOP);
|
||||
gSaveContext.entranceIndex = entranceIndex;
|
||||
|
||||
// Check the entrance to see if the exit should be overriden to a grotto return point for entrance rando
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
|
||||
// Ignore return value as we want to load into the entrance specified by the debug menu
|
||||
Grotto_OverrideSpecialEntrance(Entrance_GetOverride(entranceIndex));
|
||||
}
|
||||
|
||||
if (CVar_GetS32("gBetterDebugWarpScreen", 0)) {
|
||||
CVar_SetS32("gBetterDebugWarpScreenCurrentScene", this->currentScene);
|
||||
CVar_SetS32("gBetterDebugWarpScreenTopDisplayedScene", this->topDisplayedScene);
|
||||
@ -74,6 +82,14 @@ void Select_Grotto_LoadGame(SelectContext* this, s32 grottoIndex) {
|
||||
gSaveContext.respawn[RESPAWN_MODE_RETURN].playerParams = 0x4ff;
|
||||
gSaveContext.respawn[RESPAWN_MODE_RETURN].pos = this->betterGrottos[grottoIndex].pos;
|
||||
|
||||
// Check the entrance to see if the exit should be overriden to a grotto return point for entrance rando
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
|
||||
// Use grotto content and parent scene num to identify the right grotto
|
||||
s16 grottoEntrance = Grotto_GetRenamedGrottoIndexFromOriginal(this->betterGrottos[grottoIndex].data, this->betterGrottos[grottoIndex].exitScene);
|
||||
// Ignore return value as we want to load into the entrance specified by the debug menu
|
||||
Grotto_OverrideSpecialEntrance(Entrance_GetOverride(grottoEntrance));
|
||||
}
|
||||
|
||||
if (CVar_GetS32("gBetterDebugWarpScreen", 0)) {
|
||||
CVar_SetS32("gBetterDebugWarpScreenCurrentScene", this->currentScene);
|
||||
CVar_SetS32("gBetterDebugWarpScreenTopDisplayedScene", this->topDisplayedScene);
|
||||
@ -623,34 +639,34 @@ static BetterSceneSelectEntry sBetterScenes[] = {
|
||||
};
|
||||
|
||||
static BetterSceneSelectGrottoData sBetterGrottos[] = {
|
||||
{ 0x003F, 0x00EE, 0, 0x2C, { -504.0, 380.0, -1224.0 }},
|
||||
{ 0x003F, 0x04D6, 2, 0x14, { 922.0, 0.0, -933.0 }},
|
||||
{ 0x05B4, 0x00FC, 0, 0xFFFFFFED, { -201.0, 0.0, 1906.0 }},
|
||||
{ 0x003F, 0x00CD, 0, 0x00, { -1428.0, 0.0, 790.0 }},
|
||||
{ 0x003F, 0x0189, 0, 0x03, { -4026.0, -700.0, 13858.0 }},
|
||||
{ 0x003F, 0x0189, 0, 0x22, { -259.0, -500.0, 12356.0 }},
|
||||
{ 0x003F, 0x034D, 0, 0x28, { 861.0, 80.0, -253.0 }},
|
||||
{ 0x05A0, 0x034D, 0, 0xFFFFFFE7, { -400.0, 0.0, 408.0 }},
|
||||
{ 0x003F, 0x01B9, 0, 0x57, { -389.0, 1386.0, -1202.0 }},
|
||||
{ 0x003F, 0x0147, 1, 0x7A, { 50.0, 1233.0, 1776.0 }},
|
||||
{ 0x003F, 0x019D, 0, 0x29, { 369.0, 570.0, 128.0 }},
|
||||
{ 0x059C, 0x0189, 0, 0xFFFFFFE6, { -5002.0, -700.0, 13823.0 }},
|
||||
{ 0x05A4, 0x0246, 1, 0xFFFFFFF9, { -1703.0, 722.0, -481.0 }},
|
||||
{ 0x05A4, 0x014D, 3, 0xFFFFFFFB, { 1091.0, 580.0, -1192.0 }},
|
||||
{ 0x05A4, 0x05D4, 0, 0xFFFFFFFC, { 1798.0, 0.0, 1498.0 }},
|
||||
{ 0x05A4, 0x021D, 0, 0xFFFFFFEF, { -3044.0, -1033.0, 6070.0 }},
|
||||
{ 0x05B0, 0x01A9, 8, 0xFFFFFFF5, { 677.0, 0.0, -2515.0 }},
|
||||
{ 0x05BC, 0x00EA, 0, 0xFFFFFFEB, { -1632.0, 100.0, -123.0 }},
|
||||
{ 0x05BC, 0x0215, 0, 0xFFFFFFEE, { 317.0, 480.0, -2303.0 }},
|
||||
{ 0x05BC, 0x03D0, 0, 0xFFFFFFF0, { -1321.0, 15.0, -968.0 }},
|
||||
{ 0x05BC, 0x01F1, 0, 0xFFFFFFFD, { 71.0, -32.0, -1303.0 }},
|
||||
{ 0x05C4, 0x04D6, 6, 0xFFFFFFF3, { 75.0, -20.0, -1596.0 }},
|
||||
{ 0x0598, 0x017D, 0, 0xFFFFFFE5, { 2059.0, 20.0, -174.0 }},
|
||||
{ 0x05B8, 0x023D, 0, 0xFFFFFFF6, { 986.0, 1571.0, 837.0 }},
|
||||
{ 0x05A8, 0x018D, 0, 0xFFFFFFE4, { -7873.0, -300.0, 6916.0 }},
|
||||
{ 0x05FC, 0x01B9, 0, 0xFFFFFFF8, { -678.0, 1946.0, -284.0 }},
|
||||
{ 0x05AC, 0x0117, 0, 0xFFFFFFF2, { 271.0, -555.0, 1465.0 }},
|
||||
{ 0x05C0, 0x00CD, 0, 0xFFFFFFE1, { -4945.0, -300.0, 2841.0 }},
|
||||
{ 0x003F, 0x00EE, 0, 0x2C, 0x55, { -504.0, 380.0, -1224.0 }}, // Kokiri Forest -> KF Storms Grotto
|
||||
{ 0x003F, 0x04D6, 2, 0x14, 0x5B, { 922.0, 0.0, -933.0 }}, // Lost Woods -> LW Near Shortcuts Grotto
|
||||
{ 0x05B4, 0x00FC, 0, 0xED, 0x56, { -201.0, 0.0, 1906.0 }}, // SFM Entryway -> SFM Wolfos Grotto
|
||||
{ 0x003F, 0x00CD, 0, 0x00, 0x51, { -1428.0, 0.0, 790.0 }}, // Hyrule Field -> HF Near Market Grotto
|
||||
{ 0x003F, 0x0189, 0, 0x03, 0x51, { -4026.0, -700.0, 13858.0 }}, // Hyrule Field -> HF Open Grotto
|
||||
{ 0x003F, 0x0189, 0, 0x22, 0x51, { -259.0, -500.0, 12356.0 }}, // Hyrule Field -> HF Southeast Grotto
|
||||
{ 0x003F, 0x034D, 0, 0x28, 0x52, { 861.0, 80.0, -253.0 }}, // Kak Backyard -> Kak Open Grotto
|
||||
{ 0x05A0, 0x034D, 0, 0xE7, 0x52, { -400.0, 0.0, 408.0 }}, // Kakariko Village -> Kak Redead Grotto
|
||||
{ 0x003F, 0x01B9, 0, 0x57, 0x60, { -389.0, 1386.0, -1202.0 }}, // Death Mountain -> DMT Storms Grotto
|
||||
{ 0x003F, 0x0147, 1, 0x7A, 0x61, { 50.0, 1233.0, 1776.0 }}, // DMC Upper Nearby -> DMC Upper Grotto
|
||||
{ 0x003F, 0x019D, 0, 0x29, 0x54, { 369.0, 570.0, 128.0 }}, // Zora River -> ZR Open Grotto
|
||||
{ 0x059C, 0x0189, 0, 0xE6, 0x51, { -5002.0, -700.0, 13823.0 }}, // Hyrule Field -> HF Inside Fence Grotto
|
||||
{ 0x05A4, 0x0246, 1, 0xF9, 0x61, { -1703.0, 722.0, -481.0 }}, // DMC Lower Nearby -> DMC Hammer Grotto
|
||||
{ 0x05A4, 0x014D, 3, 0xFB, 0x62, { 1091.0, 580.0, -1192.0 }}, // GC Grotto Platform -> GC Grotto
|
||||
{ 0x05A4, 0x05D4, 0, 0xFC, 0x63, { 1798.0, 0.0, 1498.0 }}, // Lon Lon Ranch -> LLR Grotto
|
||||
{ 0x05A4, 0x021D, 0, 0xEF, 0x57, { -3044.0, -1033.0, 6070.0 }}, // Lake Hylia -> LH Grotto
|
||||
{ 0x05B0, 0x01A9, 8, 0xF5, 0x5B, { 677.0, 0.0, -2515.0 }}, // LW Beyond Mido -> LW Scrubs Grotto
|
||||
{ 0x05BC, 0x00EA, 0, 0xEB, 0x54, { -1632.0, 100.0, -123.0 }}, // Zora River -> ZR Storms Grotto
|
||||
{ 0x05BC, 0x0215, 0, 0xEE, 0x56, { 317.0, 480.0, -2303.0 }}, // Sacred Forest Meadow -> SFM Storms Grotto
|
||||
{ 0x05BC, 0x03D0, 0, 0xF0, 0x5A, { -1321.0, 15.0, -968.0 }}, // GV Fortress Side -> GV Storms Grotto
|
||||
{ 0x05BC, 0x01F1, 0, 0xFD, 0x5C, { 71.0, -32.0, -1303.0 }}, // Desert Colossus -> Colossus Grotto
|
||||
{ 0x05C4, 0x04D6, 6, 0xF3, 0x5B, { 75.0, -20.0, -1596.0 }}, // LW Beyond Mido -> Deku Theater
|
||||
{ 0x0598, 0x017D, 0, 0xE5, 0x51, { 2059.0, 20.0, -174.0 }}, // Hyrule Field -> HF Near Kak Grotto
|
||||
{ 0x05B8, 0x023D, 0, 0xF6, 0x5F, { 986.0, 1571.0, 837.0 }}, // Hyrule Castle Grounds -> HC Storms Grotto
|
||||
{ 0x05A8, 0x018D, 0, 0xE4, 0x51, { -7873.0, -300.0, 6916.0 }}, // Hyrule Field -> HF Cow Grotto
|
||||
{ 0x05FC, 0x01B9, 0, 0xF8, 0x60, { -678.0, 1946.0, -284.0 }}, // Death Mountain Summit -> DMT Cow Grotto
|
||||
{ 0x05AC, 0x0117, 0, 0xF2, 0x5A, { 271.0, -555.0, 1465.0 }}, // GV Grotto Ledge -> GV Octorok Grotto
|
||||
{ 0x05C0, 0x00CD, 0, 0xE1, 0x51, { -4945.0, -300.0, 2841.0 }}, // Hyrule Field -> HF Tektite Grotto
|
||||
};
|
||||
|
||||
void Select_UpdateMenu(SelectContext* this) {
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "vt.h"
|
||||
|
||||
#include "soh/frame_interpolation.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
|
||||
static void* sEquipmentFRATexs[] = {
|
||||
gPauseEquipment00FRATex, gPauseEquipment01Tex, gPauseEquipment02Tex, gPauseEquipment03Tex, gPauseEquipment04Tex,
|
||||
@ -4213,6 +4214,10 @@ void KaleidoScope_Update(PlayState* play)
|
||||
if (pauseCtx->promptChoice == 0) {
|
||||
Play_TriggerRespawn(play);
|
||||
gSaveContext.respawnFlag = -2;
|
||||
// In ER, handle death warp to last entrance from grottos
|
||||
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
|
||||
Grotto_ForceGrottoReturn();
|
||||
}
|
||||
gSaveContext.nextTransition = 2;
|
||||
gSaveContext.health = 0x30;
|
||||
Audio_QueueSeqCmd(0xF << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0xA);
|
||||
|
Loading…
Reference in New Issue
Block a user