diff --git a/CMakeLists.txt b/CMakeLists.txt
index 53b6b0ac386..9ae0360d8bd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,8 +5,8 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use")
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
-project(Ship VERSION 8.0.2 LANGUAGES C CXX)
-set(PROJECT_BUILD_NAME "MacReady Charlie" CACHE STRING "")
+project(Ship VERSION 8.0.3 LANGUAGES C CXX)
+set(PROJECT_BUILD_NAME "MacReady Delta" CACHE STRING "")
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh)
diff --git a/libultraship b/libultraship
index 1b41afa9eae..4600eedcc18 160000
--- a/libultraship
+++ b/libultraship
@@ -1 +1 @@
-Subproject commit 1b41afa9eaedb19a9073f5dd1eca2cda4dea59c8
+Subproject commit 4600eedcc18f496319c99e07b8b2b4f11a0f6e64
diff --git a/soh/assets/xml/GC_MQ_D/objects/object_mo.xml b/soh/assets/xml/GC_MQ_D/objects/object_mo.xml
index f25286df87c..f31a345d6a4 100644
--- a/soh/assets/xml/GC_MQ_D/objects/object_mo.xml
+++ b/soh/assets/xml/GC_MQ_D/objects/object_mo.xml
@@ -79,7 +79,7 @@
-
+
diff --git a/soh/assets/xml/GC_MQ_D/overlays/ovl_Boss_Dodongo.xml b/soh/assets/xml/GC_MQ_D/overlays/ovl_Boss_Dodongo.xml
index f2ee5b7b8c0..0e2c84f578b 100644
--- a/soh/assets/xml/GC_MQ_D/overlays/ovl_Boss_Dodongo.xml
+++ b/soh/assets/xml/GC_MQ_D/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/soh/assets/xml/GC_MQ_PAL_F/objects/object_mo.xml b/soh/assets/xml/GC_MQ_PAL_F/objects/object_mo.xml
index 2a6247c9f5c..b4528ef3176 100644
--- a/soh/assets/xml/GC_MQ_PAL_F/objects/object_mo.xml
+++ b/soh/assets/xml/GC_MQ_PAL_F/objects/object_mo.xml
@@ -8,7 +8,7 @@
-
+
@@ -69,17 +69,17 @@
-
+
-
+
-
+
diff --git a/soh/assets/xml/GC_MQ_PAL_F/overlays/ovl_Boss_Dodongo.xml b/soh/assets/xml/GC_MQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
index 0e1303c17d7..8a1c4ee94cc 100644
--- a/soh/assets/xml/GC_MQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
+++ b/soh/assets/xml/GC_MQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/soh/assets/xml/GC_MQ_PAL_F/text/message_data_static.xml b/soh/assets/xml/GC_MQ_PAL_F/text/message_data_static.xml
index 400a27e27d0..5241e3ef766 100644
--- a/soh/assets/xml/GC_MQ_PAL_F/text/message_data_static.xml
+++ b/soh/assets/xml/GC_MQ_PAL_F/text/message_data_static.xml
@@ -1,14 +1,14 @@
-
+
-
+
-
+
-
+
diff --git a/soh/assets/xml/GC_NMQ_D/objects/object_mo.xml b/soh/assets/xml/GC_NMQ_D/objects/object_mo.xml
index 2a6247c9f5c..b4528ef3176 100644
--- a/soh/assets/xml/GC_NMQ_D/objects/object_mo.xml
+++ b/soh/assets/xml/GC_NMQ_D/objects/object_mo.xml
@@ -8,7 +8,7 @@
-
+
@@ -69,17 +69,17 @@
-
+
-
+
-
+
diff --git a/soh/assets/xml/GC_NMQ_D/overlays/ovl_Boss_Dodongo.xml b/soh/assets/xml/GC_NMQ_D/overlays/ovl_Boss_Dodongo.xml
index f6a52f04433..d31e4abcc60 100644
--- a/soh/assets/xml/GC_NMQ_D/overlays/ovl_Boss_Dodongo.xml
+++ b/soh/assets/xml/GC_NMQ_D/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/soh/assets/xml/GC_NMQ_PAL_F/objects/object_mo.xml b/soh/assets/xml/GC_NMQ_PAL_F/objects/object_mo.xml
index 2a6247c9f5c..b4528ef3176 100644
--- a/soh/assets/xml/GC_NMQ_PAL_F/objects/object_mo.xml
+++ b/soh/assets/xml/GC_NMQ_PAL_F/objects/object_mo.xml
@@ -8,7 +8,7 @@
-
+
@@ -69,17 +69,17 @@
-
+
-
+
-
+
diff --git a/soh/assets/xml/GC_NMQ_PAL_F/overlays/ovl_Boss_Dodongo.xml b/soh/assets/xml/GC_NMQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
index f2ee5b7b8c0..67d85d16896 100644
--- a/soh/assets/xml/GC_NMQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
+++ b/soh/assets/xml/GC_NMQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
-
-
+
+
+
diff --git a/soh/assets/xml/N64_PAL_10/objects/object_mo.xml b/soh/assets/xml/N64_PAL_10/objects/object_mo.xml
index 2a6247c9f5c..b4528ef3176 100644
--- a/soh/assets/xml/N64_PAL_10/objects/object_mo.xml
+++ b/soh/assets/xml/N64_PAL_10/objects/object_mo.xml
@@ -8,7 +8,7 @@
-
+
@@ -69,17 +69,17 @@
-
+
-
+
-
+
diff --git a/soh/assets/xml/N64_PAL_10/overlays/ovl_Boss_Dodongo.xml b/soh/assets/xml/N64_PAL_10/overlays/ovl_Boss_Dodongo.xml
index 8f0c7612c2e..5e4975bc1d4 100644
--- a/soh/assets/xml/N64_PAL_10/overlays/ovl_Boss_Dodongo.xml
+++ b/soh/assets/xml/N64_PAL_10/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/soh/assets/xml/N64_PAL_11/objects/object_mo.xml b/soh/assets/xml/N64_PAL_11/objects/object_mo.xml
index 2a6247c9f5c..b4528ef3176 100644
--- a/soh/assets/xml/N64_PAL_11/objects/object_mo.xml
+++ b/soh/assets/xml/N64_PAL_11/objects/object_mo.xml
@@ -8,7 +8,7 @@
-
+
@@ -69,17 +69,17 @@
-
+
-
+
-
+
diff --git a/soh/assets/xml/N64_PAL_11/overlays/ovl_Boss_Dodongo.xml b/soh/assets/xml/N64_PAL_11/overlays/ovl_Boss_Dodongo.xml
index cd9069e13f2..d6f77448d1e 100644
--- a/soh/assets/xml/N64_PAL_11/overlays/ovl_Boss_Dodongo.xml
+++ b/soh/assets/xml/N64_PAL_11/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/soh/soh/Enhancements/controls/GameControlEditor.cpp b/soh/soh/Enhancements/controls/GameControlEditor.cpp
index 3783f8b608a..a452bf4b171 100644
--- a/soh/soh/Enhancements/controls/GameControlEditor.cpp
+++ b/soh/soh/Enhancements/controls/GameControlEditor.cpp
@@ -304,6 +304,7 @@ namespace GameControlEditor {
DrawHelpIcon("Allows the cursor on the pause menu to be over any slot. Sometimes required in rando to select "
"certain items.");
UIWidgets::Spacer(0);
+ ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
UIWidgets::PaddedEnhancementCheckbox("Enable walk speed modifiers", "gEnableWalkModify", true, false);
DrawHelpIcon("Hold the assigned button to change the maximum walking speed\nTo change the assigned button, go into the Ports tabs above");
if (CVarGetInteger("gEnableWalkModify", 0)) {
@@ -315,6 +316,7 @@ namespace GameControlEditor {
UIWidgets::PaddedEnhancementSliderFloat("Modifier 2: %d %%", "##WalkMod2", "gWalkModifierTwo", 0.0f, 5.0f, "", 1.0f, true, true, false, true);
window->EndGroupPanelPublic(0);
}
+ ImGui::EndDisabled();
UIWidgets::Spacer(0);
UIWidgets::PaddedEnhancementCheckbox("Answer Navi Prompt with L Button", "gNaviOnL");
DrawHelpIcon("Speak to Navi with L but enter first-person camera with C-Up");
diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
index 9c56ac32ba1..378197bcdd8 100644
--- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
+++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
@@ -1485,6 +1485,7 @@ void Draw_Placements(){
}
void DrawSillyTab() {
+ ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
if (CVarGetInteger("gLetItSnow", 0)) {
if (UIWidgets::EnhancementCheckbox("Let It Snow", "gLetItSnow")) {
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
@@ -1569,6 +1570,7 @@ void DrawSillyTab() {
CVarClear("gCosmetics.Kak_Windmill_Speed.Changed");
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
}
+ ImGui::EndDisabled();
}
// Copies the RGB values from one cosmetic option to another, multiplied by the passed in amount, this
diff --git a/soh/soh/Enhancements/enhancementTypes.h b/soh/soh/Enhancements/enhancementTypes.h
index 529ebed81ed..c8461bbf1a3 100644
--- a/soh/soh/Enhancements/enhancementTypes.h
+++ b/soh/soh/Enhancements/enhancementTypes.h
@@ -1,3 +1,6 @@
+#ifndef _ENHANCEMENT_TYPES_H_
+#define _ENHANCEMENT_TYPES_H_
+
typedef enum {
WARP_MODE_OVERRIDE_OFF,
WARP_MODE_OVERRIDE_MQ_AS_VANILLA,
@@ -74,3 +77,5 @@ typedef enum {
DEKU_STICK_UNBREAKABLE,
DEKU_STICK_UNBREAKABLE_AND_ALWAYS_ON_FIRE,
} DekuStickType;
+
+#endif
diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h
index 1da584eb2b7..56f0f470190 100644
--- a/soh/soh/Enhancements/game-interactor/GameInteractor.h
+++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h
@@ -194,6 +194,8 @@ class GameInteractor {
DEFINE_HOOK(OnSetGameLanguage, void());
+ DEFINE_HOOK(OnAssetAltChange, void());
+
// Helpers
static bool IsSaveLoaded();
static bool IsGameplayPaused();
diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp
index 13df133a0d9..4bd5354a67a 100644
--- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp
+++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp
@@ -181,3 +181,9 @@ void GameInteractor_ExecuteOnUpdateFileNameSelection(int16_t charCode) {
void GameInteractor_ExecuteOnSetGameLanguage() {
GameInteractor::Instance->ExecuteHooks();
}
+
+// MARK: - System
+
+void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void)) {
+ GameInteractor::Instance->RegisterGameHook(fn);
+}
diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h
index 47114a300a0..7b7b226faf2 100644
--- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h
+++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h
@@ -56,6 +56,10 @@ void GameInteractor_ExecuteOnUpdateFileNameSelection(int16_t charCode);
// MARK: - Game
void GameInteractor_ExecuteOnSetGameLanguage();
+
+// MARK: - System
+void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void));
+
#ifdef __cplusplus
}
#endif
diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp
index e9d62f63c20..13d90a3938d 100644
--- a/soh/soh/Enhancements/mods.cpp
+++ b/soh/soh/Enhancements/mods.cpp
@@ -47,6 +47,7 @@ void ReloadSceneTogglingLinkAge() {
void RegisterInfiniteMoney() {
GameInteractor::Instance->RegisterGameHook([]() {
+ if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gInfiniteMoney", 0) != 0) {
if (gSaveContext.rupees < CUR_CAPACITY(UPG_WALLET)) {
gSaveContext.rupees = CUR_CAPACITY(UPG_WALLET);
@@ -57,6 +58,7 @@ void RegisterInfiniteMoney() {
void RegisterInfiniteHealth() {
GameInteractor::Instance->RegisterGameHook([]() {
+ if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gInfiniteHealth", 0) != 0) {
if (gSaveContext.health < gSaveContext.healthCapacity) {
gSaveContext.health = gSaveContext.healthCapacity;
@@ -67,6 +69,7 @@ void RegisterInfiniteHealth() {
void RegisterInfiniteAmmo() {
GameInteractor::Instance->RegisterGameHook([]() {
+ if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gInfiniteAmmo", 0) != 0) {
// Deku Sticks
if (AMMO(ITEM_STICK) < CUR_CAPACITY(UPG_STICKS)) {
@@ -103,6 +106,7 @@ void RegisterInfiniteAmmo() {
void RegisterInfiniteMagic() {
GameInteractor::Instance->RegisterGameHook([]() {
+ if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gInfiniteMagic", 0) != 0) {
if (gSaveContext.isMagicAcquired && gSaveContext.magic != (gSaveContext.isDoubleMagicAcquired + 1) * 0x30) {
gSaveContext.magic = (gSaveContext.isDoubleMagicAcquired + 1) * 0x30;
@@ -113,6 +117,7 @@ void RegisterInfiniteMagic() {
void RegisterInfiniteNayrusLove() {
GameInteractor::Instance->RegisterGameHook([]() {
+ if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gInfiniteNayru", 0) != 0) {
gSaveContext.nayrusLoveTimer = 0x44B;
}
@@ -121,7 +126,7 @@ void RegisterInfiniteNayrusLove() {
void RegisterMoonJumpOnL() {
GameInteractor::Instance->RegisterGameHook([]() {
- if (!gPlayState) return;
+ if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gMoonJumpOnL", 0) != 0) {
Player* player = GET_PLAYER(gPlayState);
@@ -136,7 +141,7 @@ void RegisterMoonJumpOnL() {
void RegisterInfiniteISG() {
GameInteractor::Instance->RegisterGameHook([]() {
- if (!gPlayState) return;
+ if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gEzISG", 0) != 0) {
Player* player = GET_PLAYER(gPlayState);
@@ -148,7 +153,7 @@ void RegisterInfiniteISG() {
//Permanent quick put away (QPA) glitched damage value
void RegisterEzQPA() {
GameInteractor::Instance->RegisterGameHook([]() {
- if (!gPlayState) return;
+ if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gEzQPA", 0) != 0) {
Player* player = GET_PLAYER(gPlayState);
@@ -160,7 +165,7 @@ void RegisterEzQPA() {
void RegisterUnrestrictedItems() {
GameInteractor::Instance->RegisterGameHook([]() {
- if (!gPlayState) return;
+ if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gNoRestrictItems", 0) != 0) {
u8 sunsBackup = gPlayState->interfaceCtx.restrictions.sunsSong;
@@ -188,14 +193,16 @@ void RegisterFreezeTime() {
/// Switches Link's age and respawns him at the last entrance he entered.
void RegisterSwitchAge() {
GameInteractor::Instance->RegisterGameHook([]() {
+ if (!GameInteractor::IsSaveLoaded()) {
+ CVarClear("gSwitchAge");
+ return;
+ }
static bool warped = false;
static Vec3f playerPos;
static int16_t playerYaw;
static RoomContext* roomCtx;
static s32 roomNum;
- if (!gPlayState) return;
-
if (CVarGetInteger("gSwitchAge", 0) && !warped) {
playerPos = GET_PLAYER(gPlayState)->actor.world.pos;
playerYaw = GET_PLAYER(gPlayState)->actor.shape.rot.y;
@@ -215,7 +222,7 @@ void RegisterSwitchAge() {
func_80097534(gPlayState, roomCtx); // load map for new room (unloading the previous room)
}
warped = false;
- CVarSetInteger("gSwitchAge", 0);
+ CVarClear("gSwitchAge");
}
});
}
@@ -224,7 +231,8 @@ void RegisterSwitchAge() {
void RegisterOcarinaTimeTravel() {
GameInteractor::Instance->RegisterGameHook([]() {
- if (!gPlayState) {
+ if (!GameInteractor::IsSaveLoaded()) {
+ CVarClear("gTimeTravel");
return;
}
@@ -1028,8 +1036,16 @@ void RegisterRandomizedEnemySizes() {
Player* player = GET_PLAYER(gPlayState);
Actor* actor = static_cast(refActor);
- // Only apply to enemies and bosses. Exclude the wobbly platforms in Jabu because they need to act like platforms.
- if (!CVarGetInteger("gRandomizedEnemySizes", 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || actor->id == ACTOR_EN_BROB) {
+ // Exclude wobbly platforms in Jabu because they need to act like platforms.
+ // Exclude Dead Hand hands and Bongo Bongo main body because they make the fights (near) impossible.
+ uint8_t excludedEnemy = actor->id == ACTOR_EN_BROB || actor->id == ACTOR_EN_DHA || (actor->id == ACTOR_BOSS_SST && actor->params == -1);
+
+ // Dodongo, Volvagia and Dead Hand are always smaller because they're impossible when bigger.
+ uint8_t smallOnlyEnemy =
+ actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || actor->id == ACTOR_BOSS_FD2 || ACTOR_EN_DH;
+
+ // Only apply to enemies and bosses.
+ if (!CVarGetInteger("gRandomizedEnemySizes", 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) {
return;
}
@@ -1038,9 +1054,8 @@ void RegisterRandomizedEnemySizes() {
uint8_t bigActor = rand() % 2;
- // Big actor. Dodongo and Volvagia are always smaller because they're impossible when bigger.
- if (bigActor && actor->id != ACTOR_BOSS_DODONGO && actor->id != ACTOR_BOSS_FD &&
- actor->id != ACTOR_BOSS_FD2) {
+ // Big actor
+ if (bigActor && !smallOnlyEnemy) {
randomNumber = rand() % 200;
// Between 100% and 300% size.
randomScale = 1.0f + (randomNumber / 100);
diff --git a/soh/soh/Enhancements/presets.h b/soh/soh/Enhancements/presets.h
index 5e1c15681a6..af836dff520 100644
--- a/soh/soh/Enhancements/presets.h
+++ b/soh/soh/Enhancements/presets.h
@@ -218,6 +218,31 @@ const std::vector enhancementsCvars = {
"gFixTexturesOOB",
"gIvanCoopModeEnabled",
"gEnemySpawnsOverWaterboxes",
+ "gTreeStickDrops",
+ "gShadowTag",
+ "gRandomizedEnemySizes",
+ "gRandomizedEnemies",
+ "gMirroredWorldMode",
+ "gMirroredWorld",
+ "gHyperEnemies",
+ "gHookshotableReticle",
+ "gHideBunnyHood",
+ "gFixVineFall",
+ "gFileSelectMoreInfo",
+ "gEnemyHealthBar",
+ "gBushDropFix",
+ "gAllDogsRichard",
+ "gAddTraps.enabled",
+ "gAddTraps.Ammo",
+ "gAddTraps.Bomb",
+ "gAddTraps.Burn",
+ "gAddTraps.Ice",
+ "gAddTraps.Kill",
+ "gAddTraps.Knock",
+ "gAddTraps.Shock",
+ "gAddTraps.Speed",
+ "gAddTraps.Tele",
+ "gAddTraps.Void",
};
const std::vector cheatCvars = {
@@ -270,7 +295,23 @@ const std::vector cheatCvars = {
"gNoRedeadFreeze",
"gBombTimerMultiplier",
"gNoFishDespawn",
- "gNoBugsDespawn"
+ "gNoBugsDespawn",
+ "gWalkModifierDoesntChangeJump",
+ "gStatsEnabled",
+ "gSaveStatesEnabled",
+ "gSaveStatePromise",
+ "gRegEditEnabled",
+ "gPreset0",
+ "gPreset1",
+ "gDekuStickCheat",
+ "gDebugWarpScreenTranslation",
+ "gDebugSaveFileMode",
+ "gCosmetics.Link_BodyScale.Changed",
+ "gCosmetics.Link_BodyScale.Value",
+ "gCosmetics.Link_HeadScale.Changed",
+ "gCosmetics.Link_HeadScale.Value",
+ "gCosmetics.Link_SwordScale.Changed",
+ "gCosmetics.Link_SwordScale.Value",
};
const std::vector randomizerCvars = {
@@ -400,6 +441,15 @@ const std::vector randomizerCvars = {
"gRandomizeGregHint",
"gRandoManualSeedEntry",
"gRandomizerSettingsEnabled",
+ "gRandomizeTriforceHuntTotalPieces",
+ "gRandomizeTriforceHuntRequiredPieces",
+ "gRandomizeTriforceHunt",
+ "gRandomizeShuffleMasterSword",
+ "gRandomizeSariaHint",
+ "gRandomizeRupeeNames",
+ "gRandomizeFrogsHint",
+ "gRandoRelevantNavi",
+ "gRandoQuestItemFanfares",
};
const std::vector vanillaPlusPresetEntries = {
diff --git a/soh/soh/Enhancements/randomizer/3drando/item_location.cpp b/soh/soh/Enhancements/randomizer/3drando/item_location.cpp
index 577de1c1958..0ab29d8692d 100644
--- a/soh/soh/Enhancements/randomizer/3drando/item_location.cpp
+++ b/soh/soh/Enhancements/randomizer/3drando/item_location.cpp
@@ -24,11 +24,11 @@ void LocationTable_Init() {
//Lost Woods
locationTable[LW_NEAR_SHORTCUTS_GROTTO_CHEST] = ItemLocation::Chest (RC_LW_NEAR_SHORTCUTS_GROTTO_CHEST, 0x3E, 0x14, "LW Near Shortcuts Grotto Chest", LW_NEAR_SHORTCUTS_GROTTO_CHEST, BLUE_RUPEE, {}, SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
- locationTable[LW_SKULL_KID] = ItemLocation::Base (RC_LW_SKULL_KID, 0x5B, "LW Skull Kid", LW_SKULL_KID, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(30), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
+ locationTable[LW_SKULL_KID] = ItemLocation::Base (RC_LW_SKULL_KID, 0x5B, "LW Skull Kid", LW_SKULL_KID, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(22), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_TRADE_COJIRO] = ItemLocation::Base (RC_LW_TRADE_COJIRO, 0x5B, "LW Trade Cojiro", LW_TRADE_COJIRO, ODD_MUSHROOM, {Category::cAdultTrade}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
- locationTable[LW_TRADE_ODD_POTION] = ItemLocation::Base (RC_LW_TRADE_ODD_POTION, 0x5B, "LW Trade Odd Potion", LW_TRADE_ODD_POTION, ODD_POTION, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(57), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
- locationTable[LW_OCARINA_MEMORY_GAME] = ItemLocation::Base (RC_LW_OCARINA_MEMORY_GAME, 0x5B, "LW Ocarina Memory Game", LW_OCARINA_MEMORY_GAME, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(31), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
- locationTable[LW_TARGET_IN_WOODS] = ItemLocation::Base (RC_LW_TARGET_IN_WOODS, 0x5B, "LW Target in Woods", LW_TARGET_IN_WOODS, PROGRESSIVE_SLINGSHOT, {}, SpoilerCollectionCheck::ItemGetInf(21), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
+ locationTable[LW_TRADE_ODD_POTION] = ItemLocation::Base (RC_LW_TRADE_ODD_POTION, 0x5B, "LW Trade Odd Potion", LW_TRADE_ODD_POTION, ODD_POTION, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(49), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
+ locationTable[LW_OCARINA_MEMORY_GAME] = ItemLocation::Base (RC_LW_OCARINA_MEMORY_GAME, 0x5B, "LW Ocarina Memory Game", LW_OCARINA_MEMORY_GAME, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(23), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
+ locationTable[LW_TARGET_IN_WOODS] = ItemLocation::Base (RC_LW_TARGET_IN_WOODS, 0x5B, "LW Target in Woods", LW_TARGET_IN_WOODS, PROGRESSIVE_SLINGSHOT, {}, SpoilerCollectionCheck::ItemGetInf(29), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT] = ItemLocation::Base (RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT, 0x5B, "LW Deku Scrub Near Deku Theater Right",LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT, BUY_DEKU_NUT_5, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT] = ItemLocation::Base (RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT, 0x5B, "LW Deku Scrub Near Deku Theater Left", LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT, BUY_DEKU_STICK_1, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_DEKU_SCRUB_NEAR_BRIDGE] = ItemLocation::Base (RC_LW_DEKU_SCRUB_NEAR_BRIDGE, 0x5B, "LW Deku Scrub Near Bridge", LW_DEKU_SCRUB_NEAR_BRIDGE, PROGRESSIVE_STICK_UPGRADE, {Category::cDekuScrub, Category::cDekuScrubUpgrades}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
@@ -53,7 +53,7 @@ void LocationTable_Init() {
//Lake Hylia
locationTable[LH_CHILD_FISHING] = ItemLocation::Base (RC_LH_CHILD_FISHING, 0x49, "LH Child Fishing", LH_CHILD_FISHING, PIECE_OF_HEART, {}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
locationTable[LH_ADULT_FISHING] = ItemLocation::Base (RC_LH_ADULT_FISHING, 0x49, "LH Adult Fishing", LH_ADULT_FISHING, PROGRESSIVE_SCALE, {}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
- locationTable[LH_LAB_DIVE] = ItemLocation::Base (RC_LH_LAB_DIVE, 0x38, "LH Lab Dive", LH_LAB_DIVE, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(24), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
+ locationTable[LH_LAB_DIVE] = ItemLocation::Base (RC_LH_LAB_DIVE, 0x38, "LH Lab Dive", LH_LAB_DIVE, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(16), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
locationTable[LH_TRADE_FROG] = ItemLocation::Base (RC_LH_TRADE_FROG, 0x38, "LH Lab Trade Eyeball Frog", LH_TRADE_FROG, EYEDROPS, {Category::cAdultTrade}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
locationTable[LH_UNDERWATER_ITEM] = ItemLocation::Base (RC_LH_UNDERWATER_ITEM, 0x57, "LH Underwater Item", LH_UNDERWATER_ITEM, RUTOS_LETTER, {}, SpoilerCollectionCheck::EventChkInf(0x31), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
locationTable[LH_SUN] = ItemLocation::Base (RC_LH_SUN, 0x57, "LH Sun", LH_SUN, FIRE_ARROWS, {}, SpoilerCollectionCheck::Chest(0x57, 0x1F), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
@@ -73,7 +73,7 @@ void LocationTable_Init() {
//Gerudo Fortress
locationTable[GF_CHEST] = ItemLocation::Chest (RC_GF_CHEST, 0x5D, 0x00, "GF Chest", GF_CHEST, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
locationTable[GF_HBA_1000_POINTS] = ItemLocation::Base (RC_GF_HBA_1000_POINTS, 0x5D, "GF HBA 1000 Points", GF_HBA_1000_POINTS, PIECE_OF_HEART, {}, SpoilerCollectionCheck::InfTable(0x19, 0x08), SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
- locationTable[GF_HBA_1500_POINTS] = ItemLocation::Base (RC_GF_HBA_1500_POINTS, 0x5D, "GF HBA 1500 Points", GF_HBA_1500_POINTS, PROGRESSIVE_BOW, {}, SpoilerCollectionCheck::ItemGetInf(7), SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
+ locationTable[GF_HBA_1500_POINTS] = ItemLocation::Base (RC_GF_HBA_1500_POINTS, 0x5D, "GF HBA 1500 Points", GF_HBA_1500_POINTS, PROGRESSIVE_BOW, {}, SpoilerCollectionCheck::ItemGetInf(15), SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
locationTable[GF_GERUDO_MEMBERSHIP_CARD] = ItemLocation::Base (RC_GF_GERUDO_MEMBERSHIP_CARD, 0x0C, "GF Gerudo Membership Card", GF_GERUDO_MEMBERSHIP_CARD, GERUDO_MEMBERSHIP_CARD, {}, SpoilerCollectionCheck::GerudoToken(), SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
locationTable[GF_NORTH_F1_CARPENTER] = ItemLocation::Collectable(RC_GF_NORTH_F1_CARPENTER, 0x0C, 0x0C, "GF North F1 Carpenter", GF_NORTH_F1_CARPENTER, GERUDO_FORTRESS_SMALL_KEY, {Category::cVanillaGFSmallKey}, SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
locationTable[GF_NORTH_F2_CARPENTER] = ItemLocation::Collectable(RC_GF_NORTH_F2_CARPENTER, 0x0C, 0x0A, "GF North F2 Carpenter", GF_NORTH_F2_CARPENTER, GERUDO_FORTRESS_SMALL_KEY, {Category::cVanillaGFSmallKey}, SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
@@ -90,12 +90,12 @@ void LocationTable_Init() {
locationTable[COLOSSUS_DEKU_SCRUB_GROTTO_FRONT] = ItemLocation::GrottoScrub(RC_COLOSSUS_DEKU_SCRUB_GROTTO_FRONT, 0xFD, "Colossus Deku Scrub Grotto Front", COLOSSUS_DEKU_SCRUB_GROTTO_FRONT, BUY_GREEN_POTION, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
//Market
- locationTable[MARKET_TREASURE_CHEST_GAME_REWARD] = ItemLocation::Chest (RC_MARKET_TREASURE_CHEST_GAME_REWARD, 0x10, "MK Treasure Chest Game Reward", MARKET_TREASURE_CHEST_GAME_REWARD, TREASURE_GAME_HEART, {}, SpoilerCollectionCheck::ItemGetInf(19), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
- locationTable[MARKET_BOMBCHU_BOWLING_FIRST_PRIZE] = ItemLocation::Base (RC_MARKET_BOMBCHU_BOWLING_FIRST_PRIZE, 0x4B, "MK Bombchu Bowling First Prize", MARKET_BOMBCHU_BOWLING_FIRST_PRIZE, PROGRESSIVE_BOMB_BAG, {}, SpoilerCollectionCheck::ItemGetInf(25), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
- locationTable[MARKET_BOMBCHU_BOWLING_SECOND_PRIZE] = ItemLocation::Base (RC_MARKET_BOMBCHU_BOWLING_SECOND_PRIZE, 0x4B, "MK Bombchu Bowling Second Prize", MARKET_BOMBCHU_BOWLING_SECOND_PRIZE, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(26), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
+ locationTable[MARKET_TREASURE_CHEST_GAME_REWARD] = ItemLocation::Chest (RC_MARKET_TREASURE_CHEST_GAME_REWARD, 0x10, "MK Treasure Chest Game Reward", MARKET_TREASURE_CHEST_GAME_REWARD, TREASURE_GAME_HEART, {}, SpoilerCollectionCheck::ItemGetInf(27), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
+ locationTable[MARKET_BOMBCHU_BOWLING_FIRST_PRIZE] = ItemLocation::Base (RC_MARKET_BOMBCHU_BOWLING_FIRST_PRIZE, 0x4B, "MK Bombchu Bowling First Prize", MARKET_BOMBCHU_BOWLING_FIRST_PRIZE, PROGRESSIVE_BOMB_BAG, {}, SpoilerCollectionCheck::ItemGetInf(17), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
+ locationTable[MARKET_BOMBCHU_BOWLING_SECOND_PRIZE] = ItemLocation::Base (RC_MARKET_BOMBCHU_BOWLING_SECOND_PRIZE, 0x4B, "MK Bombchu Bowling Second Prize", MARKET_BOMBCHU_BOWLING_SECOND_PRIZE, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(18), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_BOMBCHU_BOWLING_BOMBCHUS] = ItemLocation::Base (RC_MARKET_BOMBCHU_BOWLING_BOMBCHUS, 0x4B, "MK Bombchu Bowling Bombchus", NONE, BOMBCHU_DROP, {}, SpoilerCollectionCheck::None(), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_LOST_DOG] = ItemLocation::Base (RC_MARKET_LOST_DOG, 0x35, "MK Lost Dog", MARKET_LOST_DOG, PIECE_OF_HEART, {}, SpoilerCollectionCheck::InfTable(0x19, 0x09), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
- locationTable[MARKET_SHOOTING_GALLERY_REWARD] = ItemLocation::Base (RC_MARKET_SHOOTING_GALLERY_REWARD, 0x42, "MK Shooting Gallery", MARKET_SHOOTING_GALLERY_REWARD, PROGRESSIVE_SLINGSHOT, {}, SpoilerCollectionCheck::ItemGetInf(5), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
+ locationTable[MARKET_SHOOTING_GALLERY_REWARD] = ItemLocation::Base (RC_MARKET_SHOOTING_GALLERY_REWARD, 0x42, "MK Shooting Gallery", MARKET_SHOOTING_GALLERY_REWARD, PROGRESSIVE_SLINGSHOT, {}, SpoilerCollectionCheck::ItemGetInf(13), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_10_BIG_POES] = ItemLocation::Base (RC_MARKET_10_BIG_POES, 0x4D, "MK 10 Big Poes", MARKET_10_BIG_POES, EMPTY_BOTTLE, {}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_TREASURE_CHEST_GAME_ITEM_1] = ItemLocation::Chest (RC_MARKET_TREASURE_CHEST_GAME_ITEM_1, 0x10, 0x01, "MK Chest Game First Room Chest", MARKET_TREASURE_CHEST_GAME_ITEM_1, TREASURE_GAME_SMALL_KEY, {Category::cChestMinigame}, SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_TREASURE_CHEST_GAME_ITEM_2] = ItemLocation::Chest (RC_MARKET_TREASURE_CHEST_GAME_ITEM_2, 0x10, 0x03, "MK Chest Game Second Room Chest", MARKET_TREASURE_CHEST_GAME_ITEM_2, TREASURE_GAME_SMALL_KEY, {Category::cChestMinigame}, SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
@@ -115,14 +115,14 @@ void LocationTable_Init() {
locationTable[KAK_30_GOLD_SKULLTULA_REWARD] = ItemLocation::Base (RC_KAK_30_GOLD_SKULLTULA_REWARD, 0x50, "Kak 30 Gold Skulltula Reward", KAK_30_GOLD_SKULLTULA_REWARD, PROGRESSIVE_WALLET, {}, SpoilerCollectionCheck::EventChkInf(0xDC), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_40_GOLD_SKULLTULA_REWARD] = ItemLocation::Base (RC_KAK_40_GOLD_SKULLTULA_REWARD, 0x50, "Kak 40 Gold Skulltula Reward", KAK_40_GOLD_SKULLTULA_REWARD, BOMBCHU_10, {}, SpoilerCollectionCheck::EventChkInf(0xDD), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_50_GOLD_SKULLTULA_REWARD] = ItemLocation::Base (RC_KAK_50_GOLD_SKULLTULA_REWARD, 0x50, "Kak 50 Gold Skulltula Reward", KAK_50_GOLD_SKULLTULA_REWARD, PIECE_OF_HEART, {}, SpoilerCollectionCheck::EventChkInf(0xDE), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
- locationTable[KAK_100_GOLD_SKULLTULA_REWARD] = ItemLocation::Base (RC_KAK_100_GOLD_SKULLTULA_REWARD, 0x50, "Kak 100 Gold Skulltula Reward", KAK_100_GOLD_SKULLTULA_REWARD, HUGE_RUPEE, {}, SpoilerCollectionCheck::EventChkInf(0xDF), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
- locationTable[KAK_MAN_ON_ROOF] = ItemLocation::Base (RC_KAK_MAN_ON_ROOF, 0x52, "Kak Man on Roof", KAK_MAN_ON_ROOF, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(29), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
+ locationTable[KAK_100_GOLD_SKULLTULA_REWARD] = ItemLocation::Base (RC_KAK_100_GOLD_SKULLTULA_REWARD, 0x50, "Kak 100 Gold Skulltula Reward", KAK_100_GOLD_SKULLTULA_REWARD, HUGE_RUPEE, {}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
+ locationTable[KAK_MAN_ON_ROOF] = ItemLocation::Base (RC_KAK_MAN_ON_ROOF, 0x52, "Kak Man on Roof", KAK_MAN_ON_ROOF, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(21), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_SHOOTING_GALLERY_REWARD] = ItemLocation::Base (RC_KAK_SHOOTING_GALLERY_REWARD, 0x42, "Kak Shooting Gallery Reward", KAK_SHOOTING_GALLERY_REWARD, PROGRESSIVE_BOW, {}, SpoilerCollectionCheck::Chest(0x42, 0x1F), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
- locationTable[KAK_TRADE_ODD_MUSHROOM] = ItemLocation::Base (RC_KAK_TRADE_ODD_MUSHROOM, 0x4E, "Kak Trade Odd Mushroom", KAK_TRADE_ODD_MUSHROOM, ODD_POTION, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(56), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
+ locationTable[KAK_TRADE_ODD_MUSHROOM] = ItemLocation::Base (RC_KAK_TRADE_ODD_MUSHROOM, 0x4E, "Kak Trade Odd Mushroom", KAK_TRADE_ODD_MUSHROOM, ODD_POTION, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(48), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_GRANNYS_SHOP] = ItemLocation::Base (RC_KAK_GRANNYS_SHOP, 0x4E, "Kak Granny's Shop", KAK_GRANNYS_SHOP, BLUE_POTION_REFILL, {Category::cMerchant}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
- locationTable[KAK_ANJU_AS_ADULT] = ItemLocation::Base (RC_KAK_ANJU_AS_ADULT, 0x52, "Kak Anju as Adult", KAK_ANJU_AS_ADULT, CLAIM_CHECK, {}, SpoilerCollectionCheck::ItemGetInf(36), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
- locationTable[KAK_ANJU_AS_CHILD] = ItemLocation::Base (RC_KAK_ANJU_AS_CHILD, 0x52, "Kak Anju as Child", KAK_ANJU_AS_CHILD, EMPTY_BOTTLE, {}, SpoilerCollectionCheck::ItemGetInf(4), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
- locationTable[KAK_TRADE_POCKET_CUCCO] = ItemLocation::Base (RC_KAK_TRADE_POCKET_CUCCO, 0x52, "Kak Trade Pocket Cucco", KAK_TRADE_POCKET_CUCCO, COJIRO, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(38), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
+ locationTable[KAK_ANJU_AS_ADULT] = ItemLocation::Base (RC_KAK_ANJU_AS_ADULT, 0x52, "Kak Anju as Adult", KAK_ANJU_AS_ADULT, CLAIM_CHECK, {}, SpoilerCollectionCheck::ItemGetInf(44), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
+ locationTable[KAK_ANJU_AS_CHILD] = ItemLocation::Base (RC_KAK_ANJU_AS_CHILD, 0x52, "Kak Anju as Child", KAK_ANJU_AS_CHILD, EMPTY_BOTTLE, {}, SpoilerCollectionCheck::ItemGetInf(12), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
+ locationTable[KAK_TRADE_POCKET_CUCCO] = ItemLocation::Base (RC_KAK_TRADE_POCKET_CUCCO, 0x52, "Kak Trade Pocket Cucco", KAK_TRADE_POCKET_CUCCO, COJIRO, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(46), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_IMPAS_HOUSE_FREESTANDING_POH] = ItemLocation::Collectable(RC_KAK_IMPAS_HOUSE_FREESTANDING_POH, 0x37, 0x01, "Kak Impas House Freestanding PoH", KAK_IMPAS_HOUSE_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_WINDMILL_FREESTANDING_POH] = ItemLocation::Collectable(RC_KAK_WINDMILL_FREESTANDING_POH, 0x48, 0x01, "Kak Windmill Freestanding PoH", KAK_WINDMILL_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
@@ -133,7 +133,7 @@ void LocationTable_Init() {
locationTable[GRAVEYARD_HOOKSHOT_CHEST] = ItemLocation::Chest (RC_GRAVEYARD_HOOKSHOT_CHEST, 0x48, 0x00, "GY Hookshot Chest", GRAVEYARD_HOOKSHOT_CHEST, PROGRESSIVE_HOOKSHOT, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[GRAVEYARD_DAMPE_RACE_FREESTANDING_POH] = ItemLocation::Collectable(RC_GRAVEYARD_DAMPE_RACE_FREESTANDING_POH, 0x48, 0x07, "GY Dampe Race Freestanding PoH", GRAVEYARD_DAMPE_RACE_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[GRAVEYARD_FREESTANDING_POH] = ItemLocation::Collectable(RC_GRAVEYARD_FREESTANDING_POH, 0x53, 0x04, "GY Freestanding PoH", GRAVEYARD_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
- locationTable[GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR] = ItemLocation::Collectable(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, 0x53, "GY Dampe Gravedigging Tour", GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, PIECE_OF_HEART, {}, SpoilerCollectionCheck::Gravedigger(0x53, 0x1F), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
+ locationTable[GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR] = ItemLocation::Collectable(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, 0x53, "GY Dampe Gravedigging Tour", GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, PIECE_OF_HEART, {}, SpoilerCollectionCheck::Gravedigger(0x53, 0x19), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
//Death Mountain
locationTable[DMT_CHEST] = ItemLocation::Chest (RC_DMT_CHEST, 0x60, 0x01, "DMT Chest", DMT_CHEST, PURPLE_RUPEE, {}, SpoilerCollectionCheckGroup::GROUP_DEATH_MOUNTAIN);
@@ -191,7 +191,7 @@ void LocationTable_Init() {
locationTable[ZF_BOTTOM_FREESTANDING_POH] = ItemLocation::Collectable(RC_ZF_BOTTOM_FREESTANDING_POH, 0x59, 0x14, "ZF Bottom Freestanding PoH", ZF_BOTTOM_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_ZORAS_DOMAIN);
//Lon Lon Ranch
- locationTable[LLR_TALONS_CHICKENS] = ItemLocation::Base (RC_LLR_TALONS_CHICKENS, 0x4C, "LLR Talons Chickens", LLR_TALONS_CHICKENS, BOTTLE_WITH_MILK, {}, SpoilerCollectionCheck::ItemGetInf(10), SpoilerCollectionCheckGroup::GROUP_LON_LON_RANCH);
+ locationTable[LLR_TALONS_CHICKENS] = ItemLocation::Base (RC_LLR_TALONS_CHICKENS, 0x4C, "LLR Talons Chickens", LLR_TALONS_CHICKENS, BOTTLE_WITH_MILK, {}, SpoilerCollectionCheck::ItemGetInf(2), SpoilerCollectionCheckGroup::GROUP_LON_LON_RANCH);
locationTable[LLR_FREESTANDING_POH] = ItemLocation::Collectable(RC_LLR_FREESTANDING_POH, 0x4C, 0x01, "LLR Freestanding PoH", LLR_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_LON_LON_RANCH);
locationTable[LLR_DEKU_SCRUB_GROTTO_LEFT] = ItemLocation::GrottoScrub(RC_LLR_DEKU_SCRUB_GROTTO_LEFT, 0xFC, "LLR Deku Scrub Grotto Left", LLR_DEKU_SCRUB_GROTTO_LEFT, BUY_DEKU_NUT_5, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LON_LON_RANCH);
locationTable[LLR_DEKU_SCRUB_GROTTO_RIGHT] = ItemLocation::GrottoScrub(RC_LLR_DEKU_SCRUB_GROTTO_RIGHT, 0xFC, "LLR Deku Scrub Grotto Right", LLR_DEKU_SCRUB_GROTTO_RIGHT, BUY_BOMBS_535, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LON_LON_RANCH);
diff --git a/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp b/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp
index 088ba506756..62042246c36 100644
--- a/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp
+++ b/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp
@@ -1,5 +1,6 @@
#include "playthrough.hpp"
+#include
#include
#include "custom_messages.hpp"
#include "fill.hpp"
@@ -8,6 +9,7 @@
#include "random.hpp"
#include "spoiler_log.hpp"
#include "soh/Enhancements/randomizer/randomizerTypes.h"
+#include "variables.h"
namespace Playthrough {
@@ -39,6 +41,10 @@ int Playthrough_Init(uint32_t seed, std::unordered_map{}(std::to_string(Settings::seed) + settingsStr);
Random_Init(finalHash);
Settings::hash = std::to_string(finalHash);
diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp
index 9c3985a0938..acc97cd34e2 100644
--- a/soh/soh/Enhancements/randomizer/randomizer.cpp
+++ b/soh/soh/Enhancements/randomizer/randomizer.cpp
@@ -361,37 +361,35 @@ std::unordered_map SpoilerfileSettingNameToEn
{ "Shuffle Dungeon Quest:Ganon's Castle", RSK_MQ_GANONS_CASTLE },
};
-std::string sanitize(std::string stringValue) {
- // Add backslashes.
- for (auto i = stringValue.begin();;) {
- auto const pos = std::find_if(i, stringValue.end(), [](char const c) { return '\\' == c || '\'' == c || '"' == c; });
- if (pos == stringValue.end()) {
- break;
- }
- i = std::next(stringValue.insert(pos, '\\'), 2);
- }
-
- // Removes others.
- stringValue.erase(std::remove_if(stringValue.begin(), stringValue.end(), [](char const c) {
- return '\n' == c || '\r' == c || '\0' == c || '\x1A' == c; }), stringValue.end());
-
- return stringValue;
-}
-
#pragma optimize("", off)
#pragma GCC push_options
#pragma GCC optimize ("O0")
bool Randomizer::SpoilerFileExists(const char* spoilerFileName) {
- if (strcmp(spoilerFileName, "") != 0) {
- std::ifstream spoilerFileStream(sanitize(spoilerFileName));
- if (!spoilerFileStream) {
- return false;
- } else {
+ try {
+ if (strcmp(spoilerFileName, "") != 0) {
+ std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
+ if (!spoilerFileStream) {
+ return false;
+ }
+
+ json spoilerFileJson;
+ spoilerFileStream >> spoilerFileJson;
+
+ if (!spoilerFileJson.contains("version") || !spoilerFileJson.contains("finalSeed")) {
+ return false;
+ }
+
return true;
}
- }
- return false;
+ return false;
+ } catch (std::exception& e) {
+ SPDLOG_ERROR("Error checking if spoiler file exists: {}", e.what());
+ return false;
+ } catch (...) {
+ SPDLOG_ERROR("Error checking if spoiler file exists");
+ return false;
+ }
}
#pragma GCC pop_options
#pragma optimize("", on)
@@ -660,7 +658,7 @@ void Randomizer::LoadMasterQuestDungeons(const char* spoilerFileName) {
}
void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) {
- std::ifstream spoilerFileStream(sanitize(spoilerFileName));
+ std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream)
return;
@@ -1294,7 +1292,7 @@ std::string FormatJsonHintText(std::string jsonHint) {
}
void Randomizer::ParseHintLocationsFile(const char* spoilerFileName) {
- std::ifstream spoilerFileStream(sanitize(spoilerFileName));
+ std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream)
return;
@@ -1395,7 +1393,7 @@ void Randomizer::ParseHintLocationsFile(const char* spoilerFileName) {
}
void Randomizer::ParseRequiredTrialsFile(const char* spoilerFileName) {
- std::ifstream spoilerFileStream(sanitize(spoilerFileName));
+ std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream) {
return;
}
@@ -1416,7 +1414,7 @@ void Randomizer::ParseRequiredTrialsFile(const char* spoilerFileName) {
}
void Randomizer::ParseMasterQuestDungeonsFile(const char* spoilerFileName) {
- std::ifstream spoilerFileStream(sanitize(spoilerFileName));
+ std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream) {
return;
}
@@ -1496,7 +1494,7 @@ int16_t Randomizer::GetVanillaMerchantPrice(RandomizerCheck check) {
}
void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent) {
- std::ifstream spoilerFileStream(sanitize(spoilerFileName));
+ std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream)
return;
@@ -1559,7 +1557,7 @@ void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent
}
void Randomizer::ParseEntranceDataFile(const char* spoilerFileName, bool silent) {
- std::ifstream spoilerFileStream(sanitize(spoilerFileName));
+ std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream) {
return;
}
@@ -2548,6 +2546,7 @@ std::map rcToRandomizerInf = {
{ RC_LH_CHILD_FISHING, RAND_INF_CHILD_FISHING },
{ RC_LH_ADULT_FISHING, RAND_INF_ADULT_FISHING },
{ RC_MARKET_10_BIG_POES, RAND_INF_10_BIG_POES },
+ { RC_KAK_100_GOLD_SKULLTULA_REWARD, RAND_INF_KAK_100_GOLD_SKULLTULA_REWARD },
};
RandomizerCheckObject Randomizer::GetCheckObjectFromActor(s16 actorId, s16 sceneNum, s32 actorParams = 0x00) {
@@ -3144,7 +3143,9 @@ void RandomizerSettingsWindow::DrawElement() {
UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
}
+ ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
DrawPresetSelector(PRESET_TYPE_RANDOMIZER);
+ ImGui::EndDisabled();
UIWidgets::Spacer(0);
UIWidgets::EnhancementCheckbox("Manual seed entry", "gRandoManualSeedEntry", false, "");
@@ -3167,13 +3168,17 @@ void RandomizerSettingsWindow::DrawElement() {
}
UIWidgets::Spacer(0);
+ ImGui::BeginDisabled(CVarGetInteger("gRandomizerDontGenerateSpoiler", 0) && gSaveContext.gameMode != GAMEMODE_FILE_SELECT);
if (ImGui::Button("Generate Randomizer")) {
GenerateRandomizer(CVarGetInteger("gRandoManualSeedEntry", 0) ? seedString : "");
}
+ ImGui::EndDisabled();
UIWidgets::Spacer(0);
- std::string spoilerfilepath = CVarGetString("gSpoilerLog", "");
- ImGui::Text("Spoiler File: %s", spoilerfilepath.c_str());
+ if (!CVarGetInteger("gRandomizerDontGenerateSpoiler", 0)) {
+ std::string spoilerfilepath = CVarGetString("gSpoilerLog", "");
+ ImGui::Text("Spoiler File: %s", spoilerfilepath.c_str());
+ }
// RANDOTODO settings presets
// std::string presetfilepath = CVarGetString("gLoadedPreset", "");
@@ -3181,6 +3186,8 @@ void RandomizerSettingsWindow::DrawElement() {
UIWidgets::PaddedSeparator();
+ ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
+
ImGuiWindow* window = ImGui::GetCurrentWindow();
static ImVec2 cellPadding(8.0f, 8.0f);
@@ -5225,6 +5232,8 @@ void RandomizerSettingsWindow::DrawElement() {
ImGui::EndTabBar();
}
+
+ ImGui::EndDisabled();
if (disableEditingRandoSettings) {
UIWidgets::ReEnableComponent("");
diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp
index d7f99edfb9c..d551108615b 100644
--- a/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp
+++ b/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp
@@ -39,7 +39,7 @@ std::map rcObjects = {
RC_OBJECT(RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT, RCVORMQ_BOTH, RCTYPE_SCRUB, RCAREA_LOST_WOODS, ACTOR_EN_DNS, SCENE_LOST_WOODS, 0x01, GI_STICKS_1, "Deku Scrub Near Deku Theater Left", "LW Deku Scrub Near Deku Theater Left", false),
RC_OBJECT(RC_LW_DEKU_SCRUB_NEAR_BRIDGE, RCVORMQ_BOTH, RCTYPE_SCRUB, RCAREA_LOST_WOODS, ACTOR_EN_DNS, SCENE_LOST_WOODS, 0x09, GI_STICK_UPGRADE_20, "Deku Scrub Near Bridge", "LW Deku Scrub Near Bridge", true),
RC_OBJECT(RC_LW_DEKU_SCRUB_GROTTO_REAR, RCVORMQ_BOTH, RCTYPE_SCRUB, RCAREA_LOST_WOODS, ACTOR_EN_DNS, SCENE_GROTTOS, TWO_ACTOR_PARAMS(0x03,0xF5), GI_SEEDS_30, "Deku Scrub Grotto Rear", "LW Deku Scrub Grotto Rear", false),
- RC_OBJECT(RC_LW_DEKU_SCRUB_GROTTO_FRONT, RCVORMQ_BOTH, RCTYPE_SCRUB, RCAREA_LOST_WOODS, ACTOR_EN_DNS, SCENE_GROTTOS, TWO_ACTOR_PARAMS(0x0A,0xF5), GI_NUT_UPGRADE_30, "Deku Scrub Grotto Front", "LW Deku Scrub Grotto Front", false),
+ RC_OBJECT(RC_LW_DEKU_SCRUB_GROTTO_FRONT, RCVORMQ_BOTH, RCTYPE_SCRUB, RCAREA_LOST_WOODS, ACTOR_EN_DNS, SCENE_GROTTOS, TWO_ACTOR_PARAMS(0x0A,0xF5), GI_NUT_UPGRADE_30, "Deku Scrub Grotto Front", "LW Deku Scrub Grotto Front", true),
RC_OBJECT(RC_DEKU_THEATER_SKULL_MASK, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_LOST_WOODS, ACTOR_ID_MAX, SCENE_GROTTOS, 0x00, GI_NONE, "Deku Theater Skull Mask", "Deku Theater Skull Mask", true),
RC_OBJECT(RC_DEKU_THEATER_MASK_OF_TRUTH, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_LOST_WOODS, ACTOR_ID_MAX, SCENE_GROTTOS, 0x00, GI_NONE, "Deku Theater Mask of Truth", "Deku Theater Mask of Truth", true),
RC_OBJECT(RC_LW_GS_BEAN_PATCH_NEAR_BRIDGE, RCVORMQ_BOTH, RCTYPE_SKULL_TOKEN, RCAREA_LOST_WOODS, ACTOR_EN_SI, SCENE_LOST_WOODS, 27905, GI_SKULL_TOKEN, "GS Bean Patch Near Bridge", "LW GS Bean Patch Near Bridge", true),
@@ -238,7 +238,7 @@ std::map rcObjects = {
RC_OBJECT(RC_GRAVEYARD_HOOKSHOT_CHEST, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_GRAVEYARD, ACTOR_EN_BOX, SCENE_WINDMILL_AND_DAMPES_GRAVE, 4352, GI_HOOKSHOT, "Hookshot Chest", "GY Hookshot Chest", true),
RC_OBJECT(RC_GRAVEYARD_DAMPE_RACE_FREESTANDING_POH, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_GRAVEYARD, ACTOR_EN_ITEM00, SCENE_WINDMILL_AND_DAMPES_GRAVE, 1798, GI_HEART_PIECE, "Dampe Race Freestanding PoH", "GY Dampe Race Freestanding PoH", true),
RC_OBJECT(RC_GRAVEYARD_FREESTANDING_POH, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_GRAVEYARD, ACTOR_EN_ITEM00, SCENE_GRAVEYARD, 1030, GI_HEART_PIECE, "Freestanding PoH", "GY Freestanding PoH", true),
- RC_OBJECT(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_GRAVEYARD, ACTOR_EN_ITEM00, SCENE_GRAVEYARD, 7942, GI_HEART_PIECE, "Dampe Gravedigging Tour", "GY Dampe Gravedigging Tour", true),
+ RC_OBJECT(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_GRAVEYARD, ACTOR_EN_ITEM00, SCENE_GRAVEYARD, 6406, GI_HEART_PIECE, "Dampe Gravedigging Tour", "GY Dampe Gravedigging Tour", true),
RC_OBJECT(RC_GRAVEYARD_GS_WALL, RCVORMQ_BOTH, RCTYPE_SKULL_TOKEN, RCAREA_GRAVEYARD, ACTOR_EN_SI, SCENE_GRAVEYARD, 20608, GI_SKULL_TOKEN, "GS Wall", "Graveyard GS Wall", true),
RC_OBJECT(RC_GRAVEYARD_GS_BEAN_PATCH, RCVORMQ_BOTH, RCTYPE_SKULL_TOKEN, RCAREA_GRAVEYARD, ACTOR_EN_SI, SCENE_GRAVEYARD, 28673, GI_SKULL_TOKEN, "GS Bean Patch", "Graveyard GS Bean Patch", true),
RC_OBJECT(RC_SONG_FROM_ROYAL_FAMILYS_TOMB, RCVORMQ_BOTH, RCTYPE_SONG_LOCATION, RCAREA_GRAVEYARD, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, GI_NONE, "Song from Composers Grave", "Song from Composers Grave", true),
diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp
index 6123c1a2797..003c8c135a9 100644
--- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp
+++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp
@@ -73,21 +73,12 @@ bool showLinksPocket;
bool fortressFast;
bool fortressNormal;
-bool bypassRandoCheck = true;
// persistent during gameplay
bool initialized;
bool doAreaScroll;
bool previousShowHidden = false;
bool hideShopRightChecks = true;
-bool checkCollected = false;
-int checkLoops = 0;
-int checkCounter = 0;
-u16 savedFrames = 0;
-bool messageCloseCheck = false;
-bool pendingSaleCheck = false;
-bool transitionCheck = false;
-
std::map startingShopItem = { { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 },
{ SCENE_BAZAAR, RC_MARKET_BAZAAR_ITEM_1 },
{ SCENE_POTION_SHOP_MARKET, RC_MARKET_POTION_SHOP_ITEM_1 },
@@ -118,12 +109,9 @@ bool showVOrMQ;
s8 areaChecksGotten[32]; //| "Kokiri Forest (4/9)"
bool optCollapseAll; // A bool that will collapse all checks once
bool optExpandAll; // A bool that will expand all checks once
-RandomizerCheck lastItemGetCheck = RC_UNKNOWN_CHECK;
RandomizerCheck lastLocationChecked = RC_UNKNOWN_CHECK;
RandomizerCheckArea previousArea = RCAREA_INVALID;
RandomizerCheckArea currentArea = RCAREA_INVALID;
-std::vector checkAreas;
-std::vector itemsReceived;
OSContPad* trackerButtonsPressed;
void BeginFloatWindows(std::string UniqueName, bool& open, ImGuiWindowFlags flags = 0);
@@ -194,10 +182,6 @@ Color_RGBA8 Color_Saved_Extra = { 0, 185, 0, 255 }; // Green
std::vector buttons = { BTN_A, BTN_B, BTN_CUP, BTN_CDOWN, BTN_CLEFT, BTN_CRIGHT, BTN_L,
BTN_Z, BTN_R, BTN_START, BTN_DUP, BTN_DDOWN, BTN_DLEFT, BTN_DRIGHT };
-void SetLastItemGetRC(RandomizerCheck rc) {
- lastItemGetCheck = rc;
-}
-
void DefaultCheckData(RandomizerCheck rc) {
gSaveContext.checkTrackerData[rc].status = RCSHOW_UNCHECKED;
gSaveContext.checkTrackerData[rc].skipped = 0;
@@ -253,9 +237,6 @@ void SetCheckCollected(RandomizerCheck rc) {
} else {
gSaveContext.checkTrackerData[rc].skipped = false;
}
- if (!checkAreas.empty()) {
- checkAreas.erase(checkAreas.begin());
- }
SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true);
doAreaScroll = true;
@@ -360,32 +341,18 @@ bool vector_contains_scene(std::vector vec, const int16_t scene) {
std::vector skipScenes = {SCENE_GANON_BOSS, SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR, SCENE_GANON_BOSS, SCENE_INSIDE_GANONS_CASTLE_COLLAPSE, SCENE_GANONS_TOWER_COLLAPSE_INTERIOR};
-bool EvaluateCheck(RandomizerCheckObject rco) {
- if (HasItemBeenCollected(rco.rc) && gSaveContext.checkTrackerData[rco.rc].status != RCSHOW_COLLECTED &&
- gSaveContext.checkTrackerData[rco.rc].status != RCSHOW_SAVED) {
- SetCheckCollected(rco.rc);
- return true;
- }
- return false;
-}
-
-bool CheckByArea(RandomizerCheckArea area = RCAREA_INVALID) {
- if (area == RCAREA_INVALID) {
- area = checkAreas.front();
- }
- if (area != RCAREA_INVALID) {
- auto areaChecks = checksByArea.find(area)->second;
- if (checkCounter >= areaChecks.size()) {
- checkCounter = 0;
- checkLoops++;
- }
- auto rco = areaChecks.at(checkCounter);
- return EvaluateCheck(rco);
+void ClearAreaChecksAndTotals() {
+ for (auto& [rcArea, vec] : checksByArea) {
+ vec.clear();
+ areaChecksGotten[rcArea] = 0;
}
}
void SetShopSeen(uint32_t sceneNum, bool prices) {
RandomizerCheck start = startingShopItem.find(sceneNum)->second;
+ if (sceneNum == SCENE_POTION_SHOP_KAKARIKO && !LINK_IS_ADULT) {
+ return;
+ }
if (GetCheckArea() == RCAREA_KAKARIKO_VILLAGE && sceneNum == SCENE_BAZAAR) {
start = RC_KAK_BAZAAR_ITEM_1;
}
@@ -509,12 +476,6 @@ void CheckTrackerLoadGame(int32_t fileNum) {
UpdateInventoryChecks();
}
-void CheckTrackerDialogClosed() {
- if (messageCloseCheck) {
- messageCloseCheck = false;
- }
-}
-
void CheckTrackerShopSlotChange(uint8_t cursorSlot, int16_t basePrice) {
if (gPlayState->sceneNum == SCENE_HAPPY_MASK_SHOP) { // Happy Mask Shop is not used in rando, so is not tracked
return;
@@ -536,10 +497,6 @@ void CheckTrackerTransition(uint32_t sceneNum) {
if (!GameInteractor::IsSaveLoaded()) {
return;
}
- gSaveContext;
- if (transitionCheck) {
- transitionCheck = false;
- }
doAreaScroll = true;
previousArea = currentArea;
currentArea = GetCheckArea();
@@ -560,29 +517,13 @@ void CheckTrackerFrame() {
if (!GameInteractor::IsSaveLoaded()) {
return;
}
- if (!checkAreas.empty() && !transitionCheck && !messageCloseCheck && !pendingSaleCheck) {
- for (int i = 0; i < 10; i++) {
- if (CheckByArea()) {
- checkCounter = 0;
- break;
- } else {
- checkCounter++;
- }
- }
- if (checkLoops > 15) {
- checkAreas.erase(checkAreas.begin());
- checkLoops = 0;
+ // TODO: Move to OnAmmoChange hook once it gets added.
+ if (gSaveContext.checkTrackerData[RC_ZR_MAGIC_BEAN_SALESMAN].status != RCSHOW_COLLECTED &&
+ gSaveContext.checkTrackerData[RC_ZR_MAGIC_BEAN_SALESMAN].status != RCSHOW_SAVED) {
+ if (BEANS_BOUGHT >= 10) {
+ SetCheckCollected(RC_ZR_MAGIC_BEAN_SALESMAN);
}
}
- if (savedFrames > 0 && !pendingSaleCheck && !messageCloseCheck) {
- savedFrames--;
- }
-}
-
-void CheckTrackerSaleEnd(GetItemEntry giEntry) {
- if (pendingSaleCheck) {
- pendingSaleCheck = false;
- }
}
void CheckTrackerItemReceive(GetItemEntry giEntry) {
@@ -593,7 +534,7 @@ void CheckTrackerItemReceive(GetItemEntry giEntry) {
// Vanilla special item checks
if (!IS_RANDO) {
if (giEntry.itemId == ITEM_SHIELD_DEKU) {
- SetCheckCollected(RC_KF_SHOP_ITEM_3);
+ SetCheckCollected(RC_KF_SHOP_ITEM_1);
return;
}else if (giEntry.itemId == ITEM_KOKIRI_EMERALD) {
SetCheckCollected(RC_QUEEN_GOHMA);
@@ -622,16 +563,19 @@ void CheckTrackerItemReceive(GetItemEntry giEntry) {
} else if (giEntry.itemId == ITEM_MEDALLION_LIGHT) {
SetCheckCollected(RC_GIFT_FROM_SAGES);
return;
- } else if (giEntry.itemId == ITEM_SONG_LULLABY) {
- SetCheckCollected(RC_SONG_FROM_IMPA);
- return;
} else if (giEntry.itemId == ITEM_SONG_EPONA) {
SetCheckCollected(RC_SONG_FROM_MALON);
return;
} else if (giEntry.itemId == ITEM_SONG_SARIA) {
SetCheckCollected(RC_SONG_FROM_SARIA);
return;
- } else if (giEntry.itemId == ITEM_SONG_SUN) {
+ } else if (giEntry.itemId == ITEM_BEAN) {
+ SetCheckCollected(RC_ZR_MAGIC_BEAN_SALESMAN);
+ return;
+ } else if (giEntry.itemId == ITEM_BRACELET) {
+ SetCheckCollected(RC_GC_DARUNIAS_JOY);
+ return;
+ }/* else if (giEntry.itemId == ITEM_SONG_SUN) {
SetCheckCollected(RC_SONG_FROM_ROYAL_FAMILYS_TOMB);
return;
} else if (giEntry.itemId == ITEM_SONG_TIME) {
@@ -658,42 +602,127 @@ void CheckTrackerItemReceive(GetItemEntry giEntry) {
} else if (giEntry.itemId == ITEM_SONG_PRELUDE) {
SetCheckCollected(RC_SHEIK_AT_TEMPLE);
return;
- } else if (giEntry.itemId == ITEM_BRACELET) {
- SetCheckCollected(RC_GC_DARUNIAS_JOY);
- return;
- } else if (giEntry.itemId == ITEM_LETTER_ZELDA) {
- SetCheckCollected(RC_HC_ZELDAS_LETTER);
- return;
- } else if (giEntry.itemId == ITEM_WEIRD_EGG) {
- SetCheckCollected(RC_HC_MALON_EGG);
- return;
- } else if (giEntry.itemId == ITEM_BEAN) {
- SetCheckCollected(RC_ZR_MAGIC_BEAN_SALESMAN);
- return;
- }
+ }*/
}
- auto checkArea = GetCheckArea();
- if (gSaveContext.pendingSale != ITEM_NONE) {
- pendingSaleCheck = true;
- checkAreas.push_back(checkArea);
+}
+
+void CheckTrackerSceneFlagSet(int16_t sceneNum, int16_t flagType, int32_t flag) {
+ if (flagType != FLAG_SCENE_TREASURE && flagType != FLAG_SCENE_COLLECTIBLE) {
return;
}
- if (scene == SCENE_DESERT_COLOSSUS && (gSaveContext.entranceIndex == 485 || gSaveContext.entranceIndex == 489)) {
- checkAreas.push_back(RCAREA_SPIRIT_TEMPLE);
+ if (sceneNum == SCENE_GRAVEYARD && flag == 0x19 && flagType == FLAG_SCENE_COLLECTIBLE) { // Gravedigging tour special case
+ SetCheckCollected(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR);
return;
}
- if (GET_PLAYER(gPlayState) == nullptr) {
- transitionCheck = true;
- return;
+ for (auto [rc, rcObj] : RandomizerCheckObjects::GetAllRCObjects()) {
+ if (!IsVisibleInCheckTracker(rcObj)) {
+ continue;
+ }
+ SpoilerCollectionCheckType checkMatchType = flagType == FLAG_SCENE_TREASURE ? SpoilerCollectionCheckType::SPOILER_CHK_CHEST : SpoilerCollectionCheckType::SPOILER_CHK_COLLECTABLE;
+ SpoilerCollectionCheck scCheck = Location(rc)->GetCollectionCheck();
+ if (scCheck.scene == sceneNum && scCheck.flag == flag && scCheck.type == checkMatchType) {
+ SetCheckCollected(rc);
+ return;
+ }
+ }
+}
+
+void CheckTrackerFlagSet(int16_t flagType, int32_t flag) {
+ SpoilerCollectionCheckType checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_NONE;
+ switch (flagType) {
+ case FLAG_GS_TOKEN:
+ checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_GOLD_SKULLTULA;
+ break;
+ case FLAG_EVENT_CHECK_INF:
+ if ((flag == EVENTCHKINF_CARPENTERS_FREE(0) || flag == EVENTCHKINF_CARPENTERS_FREE(1) ||
+ flag == EVENTCHKINF_CARPENTERS_FREE(2) || flag == EVENTCHKINF_CARPENTERS_FREE(3))
+ && GET_EVENTCHKINF_CARPENTERS_FREE_ALL()) {
+ SetCheckCollected(RC_GF_GERUDO_MEMBERSHIP_CARD);
+ return;
+ }
+ checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_EVENT_CHK_INF;
+ break;
+ case FLAG_INF_TABLE:
+ if (flag == INFTABLE_190) {
+ SetCheckCollected(RC_GF_HBA_1000_POINTS);
+ return;
+ } else if (flag == INFTABLE_11E) {
+ SetCheckCollected(RC_GC_ROLLING_GORON_AS_CHILD);
+ return;
+ } else if (flag == INFTABLE_GORON_CITY_DOORS_UNLOCKED) {
+ SetCheckCollected(RC_GC_ROLLING_GORON_AS_ADULT);
+ return;
+ } else if (flag == INFTABLE_139) {
+ SetCheckCollected(RC_ZD_KING_ZORA_THAWED);
+ return;
+ } else if (flag == INFTABLE_191) {
+ SetCheckCollected(RC_MARKET_LOST_DOG);
+ return;
+ }
+ if (!IS_RANDO) {
+ if (flag == INFTABLE_192) {
+ SetCheckCollected(RC_LW_DEKU_SCRUB_NEAR_BRIDGE);
+ return;
+ } else if (flag == INFTABLE_193) {
+ SetCheckCollected(RC_LW_DEKU_SCRUB_GROTTO_FRONT);
+ return;
+ }
+ }
+ break;
+ case FLAG_ITEM_GET_INF:
+ if (!IS_RANDO) {
+ if (flag == ITEMGETINF_OBTAINED_STICK_UPGRADE_FROM_STAGE) {
+ SetCheckCollected(RC_DEKU_THEATER_SKULL_MASK);
+ return;
+ } else if (flag == ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE) {
+ SetCheckCollected(RC_DEKU_THEATER_MASK_OF_TRUTH);
+ return;
+ } else if (flag == ITEMGETINF_0B) {
+ SetCheckCollected(RC_HF_DEKU_SCRUB_GROTTO);
+ return;
+ }
+ }
+ checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_ITEM_GET_INF;
+ break;
+ case FLAG_RANDOMIZER_INF:
+ checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF;
+ break;
}
- if (gPlayState->msgCtx.msgMode != MSGMODE_NONE) {
- checkAreas.push_back(checkArea);
- messageCloseCheck = true;
+ if (checkMatchType == SpoilerCollectionCheckType::SPOILER_CHK_NONE) {
return;
}
- if (IS_RANDO || (!IS_RANDO && giEntry.getItemCategory != ITEM_CATEGORY_JUNK)) {
- checkAreas.push_back(checkArea);
- checkCollected = true;
+ for (auto [rc, rcObj] : RandomizerCheckObjects::GetAllRCObjects()) {
+ if ((!IS_RANDO && ((rcObj.vOrMQ == RCVORMQ_MQ && !IS_MASTER_QUEST) ||
+ (rcObj.vOrMQ == RCVORMQ_VANILLA && IS_MASTER_QUEST))) ||
+ (IS_RANDO && ((OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) &&
+ rcObj.vOrMQ == RCVORMQ_VANILLA) ||
+ !OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) &&
+ rcObj.vOrMQ == RCVORMQ_MQ))) {
+ continue;
+ }
+ SpoilerCollectionCheck scCheck = Location(rc)->GetCollectionCheck();
+ SpoilerCollectionCheckType scCheckType = scCheck.type;
+ if (checkMatchType == SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF &&
+ (scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_MERCHANT ||
+ scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_SHOP_ITEM ||
+ scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_COW ||
+ scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_SCRUB ||
+ scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_MASTER_SWORD ||
+ scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF)) {
+ if (flag == OTRGlobals::Instance->gRandomizer->GetRandomizerInfFromCheck(rc)) {
+ SetCheckCollected(rc);
+ return;
+ }
+ continue;
+ }
+ int16_t checkFlag = scCheck.flag;
+ if (checkMatchType == SpoilerCollectionCheckType::SPOILER_CHK_GOLD_SKULLTULA) {
+ checkFlag = rcObj.actorParams;
+ }
+ if (checkFlag == flag && scCheck.type == checkMatchType) {
+ SetCheckCollected(rc);
+ return;
+ }
}
}
@@ -711,7 +740,7 @@ void InitTrackerData(bool isDebug) {
void SaveTrackerData(SaveContext* saveContext, int sectionID, bool gameSave) {
SaveManager::Instance->SaveArray("checks", ARRAY_COUNT(saveContext->checkTrackerData), [&](size_t i) {
if (saveContext->checkTrackerData[i].status == RCSHOW_COLLECTED) {
- if (gameSave || savedFrames > 0) {
+ if (gameSave) {
gSaveContext.checkTrackerData[i].status = saveContext->checkTrackerData[i].status = RCSHOW_SAVED;
UpdateAllOrdering();
UpdateInventoryChecks();
@@ -730,9 +759,6 @@ void SaveTrackerData(SaveContext* saveContext, int sectionID, bool gameSave) {
void SaveFile(SaveContext* saveContext, int sectionID, bool fullSave) {
SaveTrackerData(saveContext, sectionID, fullSave);
- if (fullSave) {
- savedFrames = 40;
- }
}
void LoadFile() {
@@ -748,14 +774,9 @@ void LoadFile() {
void Teardown() {
initialized = false;
- for (auto& [rcArea, vec] : checksByArea) {
- vec.clear();
- areaChecksGotten[rcArea] = 0;
- }
+ ClearAreaChecksAndTotals();
checksByArea.clear();
areasSpoiled = 0;
- checkCollected = false;
- checkLoops = 0;
lastLocationChecked = RC_UNKNOWN_CHECK;
}
@@ -925,7 +946,11 @@ void CheckTrackerWindow::DrawElement() {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(extraColor.r / 255.0f, extraColor.g / 255.0f,
extraColor.b / 255.0f, extraColor.a / 255.0f));
- isThisAreaSpoiled = areasSpoiled & areaMask || CVarGetInteger("gCheckTrackerOptionMQSpoilers", 0);
+ isThisAreaSpoiled = areasSpoiled & areaMask || CVarGetInteger("gCheckTrackerOptionMQSpoilers", 0) || !IS_RANDO ||
+ OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_NONE ||
+ OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SELECTION ||
+ (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SET_NUMBER &&
+ OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) == 12);
if (isThisAreaSpoiled) {
if (showVOrMQ && RandomizerCheckObjects::AreaIsDungeon(rcArea)) {
@@ -1151,8 +1176,8 @@ bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj) {
}
else if (rcObj.vanillaCompletion) {
return (rcObj.vOrMQ == RCVORMQ_BOTH ||
- rcObj.vOrMQ == RCVORMQ_MQ && OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) ||
- rcObj.vOrMQ == RCVORMQ_VANILLA && !OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) ||
+ (rcObj.vOrMQ == RCVORMQ_MQ && IS_MASTER_QUEST) ||
+ (rcObj.vOrMQ == RCVORMQ_VANILLA && !IS_MASTER_QUEST) ||
rcObj.rc == RC_GIFT_FROM_SAGES) && rcObj.rc != RC_LINKS_POCKET;
}
return false;
@@ -1541,10 +1566,11 @@ void CheckTrackerWindow::InitElement() {
Teardown();
});
GameInteractor::Instance->RegisterGameHook(CheckTrackerItemReceive);
- GameInteractor::Instance->RegisterGameHook(CheckTrackerSaleEnd);
GameInteractor::Instance->RegisterGameHook(CheckTrackerFrame);
GameInteractor::Instance->RegisterGameHook(CheckTrackerTransition);
GameInteractor::Instance->RegisterGameHook(CheckTrackerShopSlotChange);
+ GameInteractor::Instance->RegisterGameHook(CheckTrackerSceneFlagSet);
+ GameInteractor::Instance->RegisterGameHook(CheckTrackerFlagSet);
LocationTable_Init();
}
diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h
index b2193f696e0..a8928cca3e4 100644
--- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h
+++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h
@@ -48,10 +48,7 @@ void Teardown();
void UpdateAllOrdering();
bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj);
void InitTrackerData(bool isDebug);
-void SetLastItemGetRC(RandomizerCheck rc);
RandomizerCheckArea GetCheckArea();
-void CheckTrackerDialogClosed();
-void ToggleShopRightChecks();
void UpdateCheck(uint32_t, RandomizerCheckTrackerData);
} // namespace CheckTracker
diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.c b/soh/soh/Enhancements/randomizer/randomizer_entrance.c
index 1dd200df3b7..ee65d99e47f 100644
--- a/soh/soh/Enhancements/randomizer/randomizer_entrance.c
+++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.c
@@ -90,7 +90,7 @@ static s16 newIceCavernEntrance = ICE_CAVERN_ENTRANCE;
static s8 hasCopiedEntranceTable = 0;
static s8 hasModifiedEntranceTable = 0;
-void Entrance_SetEntranceDiscovered(u16 entranceIndex);
+void Entrance_SetEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance);
u8 Entrance_EntranceIsNull(EntranceOverride* entranceOverride) {
return entranceOverride->index == 0 && entranceOverride->destination == 0 && entranceOverride->blueWarp == 0
@@ -290,13 +290,13 @@ s16 Entrance_OverrideNextIndex(s16 nextEntranceIndex) {
return nextEntranceIndex;
}
- Entrance_SetEntranceDiscovered(nextEntranceIndex);
+ Entrance_SetEntranceDiscovered(nextEntranceIndex, false);
EntranceTracker_SetLastEntranceOverride(nextEntranceIndex);
return Grotto_OverrideSpecialEntrance(Entrance_GetOverride(nextEntranceIndex));
}
s16 Entrance_OverrideDynamicExit(s16 dynamicExitIndex) {
- Entrance_SetEntranceDiscovered(dynamicExitList[dynamicExitIndex]);
+ Entrance_SetEntranceDiscovered(dynamicExitList[dynamicExitIndex], false);
EntranceTracker_SetLastEntranceOverride(dynamicExitList[dynamicExitIndex]);
return Grotto_OverrideSpecialEntrance(Entrance_GetOverride(dynamicExitList[dynamicExitIndex]));
}
@@ -799,7 +799,7 @@ u8 Entrance_GetIsEntranceDiscovered(u16 entranceIndex) {
return 0;
}
-void Entrance_SetEntranceDiscovered(u16 entranceIndex) {
+void Entrance_SetEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance) {
// Skip if already set to save time from setting the connected entrance or
// if this entrance is outside of the randomized entrance range (i.e. is a dynamic entrance)
if (entranceIndex > MAX_ENTRANCE_RANDO_USED_INDEX || Entrance_GetIsEntranceDiscovered(entranceIndex)) {
@@ -811,14 +811,20 @@ void Entrance_SetEntranceDiscovered(u16 entranceIndex) {
if (idx < SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT) {
u32 entranceBit = 1 << (entranceIndex - (idx * bitsPerIndex));
gSaveContext.sohStats.entrancesDiscovered[idx] |= entranceBit;
- // Set connected
- for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
- if (entranceIndex == gSaveContext.entranceOverrides[i].index) {
- Entrance_SetEntranceDiscovered(gSaveContext.entranceOverrides[i].overrideDestination);
- break;
+
+ // Set reverse entrance when not decoupled
+ if (!Randomizer_GetSettingValue(RSK_DECOUPLED_ENTRANCES) && !isReversedEntrance) {
+ for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
+ if (entranceIndex == gSaveContext.entranceOverrides[i].index) {
+ Entrance_SetEntranceDiscovered(gSaveContext.entranceOverrides[i].overrideDestination, true);
+ break;
+ }
}
}
}
- // Save entrancesDiscovered
- Save_SaveSection(SECTION_ID_ENTRANCES);
+
+ // Save entrancesDiscovered when it is not the reversed entrance
+ if (!isReversedEntrance) {
+ Save_SaveSection(SECTION_ID_ENTRANCES);
+ }
}
diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.h b/soh/soh/Enhancements/randomizer/randomizer_entrance.h
index 6751bbbad2f..10a485a9450 100644
--- a/soh/soh/Enhancements/randomizer/randomizer_entrance.h
+++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.h
@@ -109,7 +109,7 @@ void Entrance_OverrideSpawnScene(int32_t sceneNum, int32_t spawn);
int32_t Entrance_OverrideSpawnSceneRoom(int32_t sceneNum, int32_t spawn, int32_t room);
void Entrance_EnableFW(void);
uint8_t Entrance_GetIsEntranceDiscovered(uint16_t entranceIndex);
-void Entrance_SetEntranceDiscovered(uint16_t entranceIndex);
+void Entrance_SetEntranceDiscovered(uint16_t entranceIndex, uint8_t isReversedEntrance);
#ifdef __cplusplus
}
#endif
diff --git a/soh/soh/Enhancements/randomizer/randomizer_grotto.c b/soh/soh/Enhancements/randomizer/randomizer_grotto.c
index 6626025430b..b552dd2cd67 100644
--- a/soh/soh/Enhancements/randomizer/randomizer_grotto.c
+++ b/soh/soh/Enhancements/randomizer/randomizer_grotto.c
@@ -140,7 +140,7 @@ s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex) {
// If Link hits a grotto exit, load the entrance index from the grotto exit list
// based on the current grotto ID
if (nextEntranceIndex == ENTR_RETURN_GROTTO) {
- Entrance_SetEntranceDiscovered(ENTRANCE_RANDO_GROTTO_EXIT_START + grottoId);
+ Entrance_SetEntranceDiscovered(ENTRANCE_RANDO_GROTTO_EXIT_START + grottoId, false);
EntranceTracker_SetLastEntranceOverride(ENTRANCE_RANDO_GROTTO_EXIT_START + grottoId);
nextEntranceIndex = grottoExitList[grottoId];
}
@@ -211,7 +211,7 @@ void Grotto_OverrideActorEntrance(Actor* thisx) {
if (grottoContent == grottoLoadTable[index].content && gPlayState->sceneNum == grottoLoadTable[index].scene) {
// Find the override for the matching index from the grotto Load List
- Entrance_SetEntranceDiscovered(ENTRANCE_RANDO_GROTTO_LOAD_START + index);
+ Entrance_SetEntranceDiscovered(ENTRANCE_RANDO_GROTTO_LOAD_START + index, false);
EntranceTracker_SetLastEntranceOverride(ENTRANCE_RANDO_GROTTO_LOAD_START + index);
index = grottoLoadList[index];
diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp
index 43aad0d368e..7bf6785a5f0 100644
--- a/soh/soh/OTRGlobals.cpp
+++ b/soh/soh/OTRGlobals.cpp
@@ -50,6 +50,9 @@
#include "Fonts.h"
#include
#include "Enhancements/custom-message/CustomMessageManager.h"
+#include "Enhancements/presets.h"
+#include "util.h"
+#include
#if not defined (__SWITCH__) && not defined(__WIIU__)
#include "Extractor/Extract.h"
@@ -254,15 +257,26 @@ OTRGlobals::OTRGlobals() {
OTRFiles.push_back(sohOtrPath);
}
std::string patchesPath = LUS::Context::LocateFileAcrossAppDirs("mods", appShortName);
+ std::vector patchOTRs = {};
if (patchesPath.length() > 0 && std::filesystem::exists(patchesPath)) {
if (std::filesystem::is_directory(patchesPath)) {
- for (const auto& p : std::filesystem::recursive_directory_iterator(patchesPath)) {
+ for (const auto& p : std::filesystem::recursive_directory_iterator(patchesPath, std::filesystem::directory_options::follow_directory_symlink)) {
if (StringHelper::IEquals(p.path().extension().string(), ".otr")) {
- OTRFiles.push_back(p.path().generic_string());
+ patchOTRs.push_back(p.path().generic_string());
}
}
}
}
+ std::sort(patchOTRs.begin(), patchOTRs.end(), [](const std::string& a, const std::string& b) {
+ return std::lexicographical_compare(
+ a.begin(), a.end(),
+ b.begin(), b.end(),
+ [](char c1, char c2) {
+ return std::tolower(c1) < std::tolower(c2);
+ }
+ );
+ });
+ OTRFiles.insert(OTRFiles.end(), patchOTRs.begin(), patchOTRs.end());
std::unordered_set ValidHashes = {
OOT_PAL_MQ,
OOT_NTSC_JP_MQ,
@@ -1234,6 +1248,7 @@ extern "C" void Graph_StartFrame() {
case KbScancode::LUS_KB_TAB: {
// Toggle HD Assets
CVarSetInteger("gAltAssets", !CVarGetInteger("gAltAssets", 0));
+ GameInteractor::Instance->ExecuteHooks();
ShouldClearTextureCacheAtEndOfFrame = true;
break;
}
@@ -1433,6 +1448,14 @@ extern "C" void ResourceMgr_DirtyDirectory(const char* resName) {
LUS::Context::GetInstance()->GetResourceManager()->DirtyDirectory(resName);
}
+extern "C" void ResourceMgr_UnloadResource(const char* resName) {
+ std::string path = resName;
+ if (path.substr(0, 7) == "__OTR__") {
+ path = path.substr(7);
+ }
+ auto res = LUS::Context::GetInstance()->GetResourceManager()->UnloadResource(path);
+}
+
// OTRTODO: There is probably a more elegant way to go about this...
// Kenix: This is definitely leaking memory when it's called.
extern "C" char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize) {
@@ -1459,6 +1482,27 @@ extern "C" uint8_t ResourceMgr_FileExists(const char* filePath) {
return ExtensionCache.contains(path);
}
+extern "C" uint8_t ResourceMgr_FileAltExists(const char* filePath) {
+ std::string path = filePath;
+ if (path.substr(0, 7) == "__OTR__") {
+ path = path.substr(7);
+ }
+
+ if (path.substr(0, 4) != "alt/") {
+ path = "alt/" + path;
+ }
+
+ return ExtensionCache.contains(path);
+}
+
+// Unloads a resource if an alternate version exists when alt assets are enabled
+// The resource is only removed from the internal cache to prevent it from used in the next resource lookup
+extern "C" void ResourceMgr_UnloadOriginalWhenAltExists(const char* resName) {
+ if (CVarGetInteger("gAltAssets", 0) && ResourceMgr_FileAltExists((char*) resName)) {
+ ResourceMgr_UnloadResource((char*) resName);
+ }
+}
+
extern "C" void ResourceMgr_LoadFile(const char* resName) {
LUS::Context::GetInstance()->GetResourceManager()->LoadResource(resName);
}
@@ -1498,6 +1542,11 @@ extern "C" char* ResourceMgr_LoadFileFromDisk(const char* filePath) {
return data;
}
+extern "C" uint8_t ResourceMgr_TexIsRaw(const char* texPath) {
+ auto res = std::static_pointer_cast(GetResourceByNameHandlingMQ(texPath));
+ return res->Flags & TEX_FLAG_LOAD_AS_RAW;
+}
+
extern "C" uint8_t ResourceMgr_ResourceIsBackground(char* texPath) {
auto res = GetResourceByNameHandlingMQ(texPath);
return res->GetInitData()->Type == LUS::ResourceType::SOH_Background;
@@ -1583,6 +1632,11 @@ extern "C" void ResourceMgr_PushCurrentDirectory(char* path)
extern "C" Gfx* ResourceMgr_LoadGfxByName(const char* path)
{
+ // When an alt resource exists for the DL, we need to unload the original asset
+ // to clear the cache so the alt asset will be loaded instead
+ // OTRTODO: If Alt loading over original cache is fixed, this line can most likely be removed
+ ResourceMgr_UnloadOriginalWhenAltExists(path);
+
auto res = std::static_pointer_cast(GetResourceByNameHandlingMQ(path));
return (Gfx*)&res->Instructions[0];
}
@@ -2548,6 +2602,70 @@ extern "C" void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* repla
gfx_register_blended_texture(name, mask, replacement);
}
-extern "C" void CheckTracker_OnMessageClose() {
- CheckTracker::CheckTrackerDialogClosed();
+// #region SOH [TODO] Ideally this should move to being event based, it's currently run every frame on the file select screen
+extern "C" void SoH_ProcessDroppedFiles() {
+ const char* droppedFile = CVarGetString("gDroppedFile", "");
+ if (CVarGetInteger("gNewFileDropped", 0) && strcmp(droppedFile, "") != 0) {
+ try {
+ std::ifstream configStream(SohUtils::Sanitize(droppedFile));
+ if (!configStream) {
+ return;
+ }
+
+ nlohmann::json configJson;
+ configStream >> configJson;
+
+ if (!configJson.contains("CVars")) {
+ return;
+ }
+
+ clearCvars(enhancementsCvars);
+ clearCvars(cheatCvars);
+ clearCvars(randomizerCvars);
+
+ // Flatten everything under CVars into a single array
+ auto cvars = configJson["CVars"].flatten();
+
+ for (auto& [key, value] : cvars.items()) {
+ // Replace slashes with dots in key, and remove leading dot
+ std::string path = key;
+ std::replace(path.begin(), path.end(), '/', '.');
+ if (path[0] == '.') {
+ path.erase(0, 1);
+ }
+ if (value.is_string()) {
+ CVarSetString(path.c_str(), value.get().c_str());
+ } else if (value.is_number_integer()) {
+ CVarSetInteger(path.c_str(), value.get());
+ } else if (value.is_number_float()) {
+ CVarSetFloat(path.c_str(), value.get());
+ }
+ }
+
+ auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
+ gui->GetGuiWindow("Console")->Hide();
+ gui->GetGuiWindow("Actor Viewer")->Hide();
+ gui->GetGuiWindow("Collision Viewer")->Hide();
+ gui->GetGuiWindow("Save Editor")->Hide();
+ gui->GetGuiWindow("Display List Viewer")->Hide();
+ gui->GetGuiWindow("Stats")->Hide();
+ std::dynamic_pointer_cast(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->ClearBindings();
+
+ gui->SaveConsoleVariablesOnNextTick();
+
+ uint32_t finalHash = boost::hash_32{}(configJson.dump());
+ gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Configuration Loaded. Hash: %d", finalHash);
+ } catch (std::exception& e) {
+ SPDLOG_ERROR("Failed to load config file: {}", e.what());
+ auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
+ gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file");
+ return;
+ } catch (...) {
+ SPDLOG_ERROR("Failed to load config file");
+ auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
+ gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file");
+ return;
+ }
+ }
}
+// #endregion
diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h
index e51ad458b00..403d493f5f9 100644
--- a/soh/soh/OTRGlobals.h
+++ b/soh/soh/OTRGlobals.h
@@ -89,11 +89,15 @@ uint32_t ResourceMgr_GetGameVersion(int index);
uint32_t ResourceMgr_GetGamePlatform(int index);
uint32_t ResourceMgr_GetGameRegion(int index);
void ResourceMgr_LoadDirectory(const char* resName);
+void ResourceMgr_UnloadResource(const char* resName);
char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize);
uint8_t ResourceMgr_FileExists(const char* resName);
+uint8_t ResourceMgr_FileAltExists(const char* resName);
+void ResourceMgr_UnloadOriginalWhenAltExists(const char* resName);
char* GetResourceDataByNameHandlingMQ(const char* path);
void ResourceMgr_LoadFile(const char* resName);
char* ResourceMgr_LoadFileFromDisk(const char* filePath);
+uint8_t ResourceMgr_TexIsRaw(const char* texPath);
uint8_t ResourceMgr_ResourceIsBackground(char* texPath);
char* ResourceMgr_LoadJPEG(char* data, size_t dataSize);
uint16_t ResourceMgr_LoadTexWidthByName(char* texPath);
@@ -174,6 +178,7 @@ void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex);
void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement);
void SaveManager_ThreadPoolWait();
void CheckTracker_OnMessageClose();
+void SoH_ProcessDroppedFiles();
int32_t GetGIID(uint32_t itemID);
#endif
diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp
index 23c94ce3a4f..879b2d5f44c 100644
--- a/soh/soh/SohMenuBar.cpp
+++ b/soh/soh/SohMenuBar.cpp
@@ -493,6 +493,8 @@ extern std::shared_ptr mGameplayStatsWindow;
void DrawEnhancementsMenu() {
if (ImGui::BeginMenu("Enhancements"))
{
+ ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
+
DrawPresetSelector(PRESET_TYPE_ENHANCEMENTS);
UIWidgets::PaddedSeparator();
@@ -608,7 +610,7 @@ void DrawEnhancementsMenu() {
UIWidgets::PaddedEnhancementCheckbox("Nuts explode bombs", "gNutsExplodeBombs", true, false);
UIWidgets::Tooltip("Makes nuts explode bombs, similar to how they interact with bombchus. This does not affect bombflowers.");
UIWidgets::PaddedEnhancementCheckbox("Equip Multiple Arrows at Once", "gSeparateArrows", true, false);
- UIWidgets::Tooltip("Allow the bow and magic arrows to be equipped at the same time on different slots");
+ UIWidgets::Tooltip("Allow the bow and magic arrows to be equipped at the same time on different slots. (Note this will disable the behaviour of the 'Equip Dupe' glitch)");
UIWidgets::PaddedEnhancementCheckbox("Bow as Child/Slingshot as Adult", "gBowSlingShotAmmoFix", true, false);
UIWidgets::Tooltip("Allows child to use bow with arrows.\nAllows adult to use slingshot with seeds.\n\nRequires glitches or 'Timeless Equipment' cheat to equip.");
UIWidgets::PaddedEnhancementCheckbox("Better Farore's Wind", "gBetterFW", true, false);
@@ -918,6 +920,7 @@ void DrawEnhancementsMenu() {
if (ImGui::BeginMenu("Mods")) {
if (UIWidgets::PaddedEnhancementCheckbox("Use Alternate Assets", "gAltAssets", false, false)) {
ShouldClearTextureCacheAtEndOfFrame = true;
+ GameInteractor::Instance->ExecuteHooks();
}
UIWidgets::Tooltip("Toggle between standard assets and alternate assets. Usually mods will indicate if this setting has to be used or not.");
UIWidgets::PaddedEnhancementCheckbox("Disable Bomb Billboarding", "gDisableBombBillboarding", true, false);
@@ -1076,6 +1079,9 @@ void DrawEnhancementsMenu() {
UIWidgets::Tooltip("Prevents immediately falling off climbable surfaces if climbing on the edges.");
UIWidgets::PaddedEnhancementCheckbox("Fix Link's eyes open while sleeping", "gFixEyesOpenWhileSleeping", true, false);
UIWidgets::Tooltip("Fixes Link's eyes being open in the opening cutscene when he is supposed to be sleeping.");
+ UIWidgets::PaddedEnhancementCheckbox("Fix Darunia dancing too fast", "gEnhancements.FixDaruniaDanceSpeed",
+ true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true);
+ UIWidgets::Tooltip("Fixes Darunia's dancing speed so he dances to the beat of Saria's Song, like in vanilla.");
ImGui::EndMenu();
}
@@ -1211,6 +1217,8 @@ void DrawEnhancementsMenu() {
UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f);
+ ImGui::EndDisabled();
+
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
@@ -1252,6 +1260,8 @@ void DrawEnhancementsMenu() {
void DrawCheatsMenu() {
if (ImGui::BeginMenu("Cheats"))
{
+ ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
+
if (ImGui::BeginMenu("Infinite...")) {
UIWidgets::EnhancementCheckbox("Money", "gInfiniteMoney");
UIWidgets::PaddedEnhancementCheckbox("Health", "gInfiniteHealth", true, false);
@@ -1406,6 +1416,8 @@ void DrawCheatsMenu() {
}
UIWidgets::Tooltip("Clears the cutscene pointer to a value safe for wrong warps.");
+ ImGui::EndDisabled();
+
ImGui::EndMenu();
}
}
@@ -1420,6 +1432,8 @@ extern std::shared_ptr mValueViewerWindow;
void DrawDeveloperToolsMenu() {
if (ImGui::BeginMenu("Developer Tools")) {
+ ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
+
UIWidgets::EnhancementCheckbox("OoT Debug Mode", "gDebugEnabled");
UIWidgets::Tooltip("Enables Debug Mode, allowing you to select maps with L + R + Z, noclip with L + D-pad Right, and open the debug menu with L on the pause screen");
if (CVarGetInteger("gDebugEnabled", 0)) {
@@ -1498,6 +1512,8 @@ void DrawDeveloperToolsMenu() {
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(1);
+ ImGui::EndDisabled();
+
ImGui::EndMenu();
}
}
@@ -1646,4 +1662,4 @@ void SohMenuBar::DrawElement() {
ImGui::EndMenuBar();
}
}
-} // namespace SohGui
\ No newline at end of file
+} // namespace SohGui
diff --git a/soh/soh/stubs.c b/soh/soh/stubs.c
index 9afd934680a..a162a7ac55a 100644
--- a/soh/soh/stubs.c
+++ b/soh/soh/stubs.c
@@ -260,7 +260,7 @@ void Audio_osWritebackDCache(void* mem, s32 size)
s32 osAiSetFrequency(u32 freq)
{
-
+ return 1;
}
s32 osEPiStartDma(OSPiHandle* handle, OSIoMesg* mb, s32 direction)
diff --git a/soh/soh/util.cpp b/soh/soh/util.cpp
index 62e1d2dce23..856432cbbd2 100644
--- a/soh/soh/util.cpp
+++ b/soh/soh/util.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
std::vector sceneNames = {
"Inside the Deku Tree",
@@ -318,3 +319,20 @@ void SohUtils::CopyStringToCharArray(char* destination, std::string source, size
strncpy(destination, source.c_str(), size - 1);
destination[size - 1] = '\0';
}
+
+std::string SohUtils::Sanitize(std::string stringValue) {
+ // Add backslashes.
+ for (auto i = stringValue.begin();;) {
+ auto const pos = std::find_if(i, stringValue.end(), [](char const c) { return '\\' == c || '\'' == c || '"' == c; });
+ if (pos == stringValue.end()) {
+ break;
+ }
+ i = std::next(stringValue.insert(pos, '\\'), 2);
+ }
+
+ // Removes others.
+ stringValue.erase(std::remove_if(stringValue.begin(), stringValue.end(), [](char const c) {
+ return '\n' == c || '\r' == c || '\0' == c || '\x1A' == c; }), stringValue.end());
+
+ return stringValue;
+}
diff --git a/soh/soh/util.h b/soh/soh/util.h
index bdbfcd777fb..db5af8636a3 100644
--- a/soh/soh/util.h
+++ b/soh/soh/util.h
@@ -12,4 +12,6 @@ namespace SohUtils {
// Copies a string and ensures the destination is null terminated if the source string is larger than size
// Only up to size-1 characters are copied from the source string
void CopyStringToCharArray(char* destination, std::string source, size_t size);
+
+ std::string Sanitize(std::string stringValue);
} // namespace SohUtils
diff --git a/soh/soh/z_message_OTR.cpp b/soh/soh/z_message_OTR.cpp
index f90e8bb9370..6bbd1eca9bd 100644
--- a/soh/soh/z_message_OTR.cpp
+++ b/soh/soh/z_message_OTR.cpp
@@ -72,6 +72,9 @@ MessageTableEntry* OTRMessage_LoadTable(const char* filePath, bool isNES) {
_message_0xFFFC_nes = (char*)file->messages[i].msg.c_str();
}
+ // Assert that the first message starts at the first text ID
+ assert(table[0].textId == 0x0001);
+
return table;
}
@@ -104,6 +107,9 @@ extern "C" void OTRMessage_Init()
sStaffMessageEntryTablePtr[i].segment = file2->messages[i].msg.c_str();
sStaffMessageEntryTablePtr[i].msgSize = file2->messages[i].msg.size();
}
+
+ // Assert staff credits start at the first credits ID
+ assert(sStaffMessageEntryTablePtr[0].textId == 0x0500);
}
CustomMessageManager::Instance->AddCustomMessageTable(customMessageTableID);
diff --git a/soh/src/code/z_message_PAL.c b/soh/src/code/z_message_PAL.c
index da0f5f55fe2..7bb3803fcc6 100644
--- a/soh/src/code/z_message_PAL.c
+++ b/soh/src/code/z_message_PAL.c
@@ -3384,7 +3384,7 @@ void Message_Update(PlayState* play) {
}
sLastPlayedSong = 0xFF;
osSyncPrintf("OCARINA_MODE=%d chk_ocarina_no=%d\n", play->msgCtx.ocarinaMode, msgCtx->unk_E3F2);
- CheckTracker_OnMessageClose();
+ // TODO: OnMessageClose hook
break;
case MSGMODE_PAUSED:
break;
diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c
index 04e806ede50..d7675fd9001 100644
--- a/soh/src/code/z_parameter.c
+++ b/soh/src/code/z_parameter.c
@@ -1526,13 +1526,13 @@ void Inventory_SwapAgeEquipment(void) {
} else {
// When becoming child, set swordless flag if player doesn't have kokiri sword
// Only in rando to keep swordless link bugs in vanilla
- if (IS_RANDO && (EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment) == 0) {
+ if (IS_RANDO && CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0) {
Flags_SetInfTable(INFTABLE_SWORDLESS);
}
// When using enhancements, set swordless flag if player doesn't have kokiri sword or hasn't equipped a sword yet.
// Then set the child equips button items to item none to ensure kokiri sword is not equipped
- if ((CVarGetInteger("gSwitchAge", 0) || CVarGetInteger("gSwitchTimeline", 0)) && ((EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment) == 0 || Flags_GetInfTable(INFTABLE_SWORDLESS))) {
+ if ((CVarGetInteger("gSwitchAge", 0) || CVarGetInteger("gSwitchTimeline", 0)) && (CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0 || Flags_GetInfTable(INFTABLE_SWORDLESS))) {
Flags_SetInfTable(INFTABLE_SWORDLESS);
gSaveContext.childEquips.buttonItems[0] = ITEM_NONE;
}
@@ -1568,7 +1568,7 @@ void Inventory_SwapAgeEquipment(void) {
gSaveContext.equips.equipment = gSaveContext.childEquips.equipment;
gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4));
// Equips kokiri sword in the inventory screen only if kokiri sword exists in inventory and a sword has been equipped already
- if (!((EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment) == 0) && !Flags_GetInfTable(INFTABLE_SWORDLESS)) {
+ if (!(CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0) && !Flags_GetInfTable(INFTABLE_SWORDLESS)) {
gSaveContext.equips.equipment |= EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4);
}
} else if (gSaveContext.childEquips.buttonItems[0] != ITEM_NONE) {
@@ -1598,7 +1598,7 @@ void Inventory_SwapAgeEquipment(void) {
(only kokiri tunic/boots equipped, no sword, no C-button items, no D-Pad items).
When becoming child, set swordless flag if player doesn't have kokiri sword
Only in rando to keep swordless link bugs in vanilla*/
- if (EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment == 0) {
+ if (CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0) {
Flags_SetInfTable(INFTABLE_SWORDLESS);
}
diff --git a/soh/src/code/z_room.c b/soh/src/code/z_room.c
index ccbc93d0ded..772978f0988 100644
--- a/soh/src/code/z_room.c
+++ b/soh/src/code/z_room.c
@@ -277,6 +277,11 @@ void func_8009638C(Gfx** displayList, void* source, void* tlut, u16 width, u16 h
bg->b.imagePal = 0;
bg->b.imageFlip = CVarGetInteger("gMirroredWorld", 0) ? G_BG_FLAG_FLIPS : 0;
+ // When an alt resource exists for the background, we need to unload the original asset
+ // to clear the cache so the alt asset will be loaded instead
+ // OTRTODO: If Alt loading over original cache is fixed, this line can most likely be removed
+ ResourceMgr_UnloadOriginalWhenAltExists((char*) source);
+
if (ResourceMgr_ResourceIsBackground((char*) source)) {
char* blob = (char*) ResourceGetDataByName((char *) source);
swapAndConvertJPEG(blob);
diff --git a/soh/src/code/z_vr_box.c b/soh/src/code/z_vr_box.c
index b77c3d86953..ff262218744 100644
--- a/soh/src/code/z_vr_box.c
+++ b/soh/src/code/z_vr_box.c
@@ -417,7 +417,12 @@ void func_800AEFC8(SkyboxContext* skyboxCtx, s16 skyboxId) {
s32 j;
s32 phi_s3 = 0;
- if (skyboxId == SKYBOX_BAZAAR || (skyboxId > SKYBOX_HOUSE_KAKARIKO && skyboxId <= SKYBOX_BOMBCHU_SHOP)) {
+ //! @bug All shops only provide 2 faces for their sky box. Mask shop is missing from the condition
+ // meaning that the Mask shop will calculate 4 faces
+ // This effect is not noticed as the faces are behind the camera, but will cause a crash in SoH.
+ // SOH General: We have added the Mask shop to this check so only the 2 expected faces are calculated.
+ if (skyboxId == SKYBOX_BAZAAR || skyboxId == SKYBOX_HAPPY_MASK_SHOP ||
+ (skyboxId >= SKYBOX_KOKIRI_SHOP && skyboxId <= SKYBOX_BOMBCHU_SHOP)) {
for (j = 0, i = 0; i < 2; i++, j += 2) {
phi_s3 = func_800ADBB0(skyboxCtx, skyboxCtx->roomVtx, phi_s3, D_8012AEBC[i].unk_0, D_8012AEBC[i].unk_4,
D_8012AEBC[i].unk_8, D_8012AEBC[i].unk_C, D_8012AEBC[i].unk_10, i, j);
diff --git a/soh/src/code/z_vr_box_draw.c b/soh/src/code/z_vr_box_draw.c
index 8eb97573e40..5b4867121bf 100644
--- a/soh/src/code/z_vr_box_draw.c
+++ b/soh/src/code/z_vr_box_draw.c
@@ -59,8 +59,14 @@ void SkyboxDraw_Draw(SkyboxContext* skyboxCtx, GraphicsContext* gfxCtx, s16 skyb
gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[2]);
gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[3]);
- if (skyboxId != SKYBOX_BAZAAR) {
- if (skyboxId <= SKYBOX_HOUSE_KAKARIKO || skyboxId > SKYBOX_BOMBCHU_SHOP) {
+ //! @bug All shops only provide 2 faces for their sky box. Mask shop is missing from the condition
+ // meaning that the Mask shop will render the previously loaded sky box values, or uninitialized data.
+ // This effect is not noticed as the faces are behind the camera, but will cause a crash in SoH.
+ // SOH General: We have added the Mask shop to this check so only the 2 expected faces are rendered.
+ if (skyboxId != SKYBOX_BAZAAR && skyboxId != SKYBOX_HAPPY_MASK_SHOP) {
+ if (skyboxId < SKYBOX_KOKIRI_SHOP || skyboxId > SKYBOX_BOMBCHU_SHOP) {
+ // Skip remaining faces for most shop skyboxes
+
gDPPipeSync(POLY_OPA_DISP++);
gDPLoadTLUT_pal256(POLY_OPA_DISP++, skyboxCtx->palettes[2]);
gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[4]);
diff --git a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c
index 2d1b6fdca76..f4c46d9528c 100644
--- a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c
+++ b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c
@@ -5,9 +5,21 @@
#include "scenes/dungeons/ddan_boss/ddan_boss_room_1.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
+
+#include // malloc
+#include // memcpy
+
+// OTRTODO: Replace usage of this method when we can clear the cache
+// for a single texture without the need of a DL opcode in the render code
+void gfx_texture_cache_clear();
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED)
+#define LAVA_TEX_WIDTH 32
+#define LAVA_TEX_HEIGHT 64
+#define LAVA_TEX_SIZE 2048
+
void BossDodongo_Init(Actor* thisx, PlayState* play);
void BossDodongo_Destroy(Actor* thisx, PlayState* play);
void BossDodongo_Update(Actor* thisx, PlayState* play);
@@ -59,6 +71,13 @@ static u8 sMaskTex8x8[8 * 8] = { { 0 } };
static u8 sMaskTex8x32[8 * 32] = { { 0 } };
static u8 sMaskTexLava[32 * 64] = { { 0 } };
+static u32* sLavaFloorModifiedTexRaw = NULL;
+static u32* sLavaWavyTexRaw = NULL;
+static u16 sLavaFloorModifiedTex[LAVA_TEX_SIZE];
+static u16 sLavaWavyTex[LAVA_TEX_SIZE];
+
+static u8 hasRegisteredBlendedHook = 0;
+
static InitChainEntry sInitChain[] = {
ICHAIN_U8(targetMode, 5, ICHAIN_CONTINUE),
ICHAIN_S8(naviEnemyId, 0x0C, ICHAIN_CONTINUE),
@@ -66,6 +85,69 @@ static InitChainEntry sInitChain[] = {
ICHAIN_F32(targetArrowOffset, 8200.0f, ICHAIN_STOP),
};
+void BossDodongo_RegisterBlendedLavaTextureUpdate() {
+ // Not in scene so there is nothing to do
+ if (gPlayState == NULL || gPlayState->sceneNum != SCENE_DODONGOS_CAVERN_BOSS) {
+ return;
+ }
+
+ // Free old textures
+ if (sLavaFloorModifiedTexRaw != NULL) {
+ free(sLavaFloorModifiedTexRaw);
+ sLavaFloorModifiedTexRaw = NULL;
+ }
+ if (sLavaWavyTexRaw != NULL) {
+ free(sLavaWavyTexRaw);
+ sLavaWavyTexRaw = NULL;
+ }
+
+ // Unload original textures to bypass cache result for lookups
+ ResourceMgr_UnloadOriginalWhenAltExists(sLavaFloorLavaTex);
+ ResourceMgr_UnloadOriginalWhenAltExists(sLavaFloorRockTex);
+ ResourceMgr_UnloadOriginalWhenAltExists(gDodongosCavernBossLavaFloorTex);
+
+ // When the texture is HD (raw) we need to work with u32 values for RGBA32
+ // Otherwise the original asset is u16 for RGBA16
+ if (ResourceMgr_TexIsRaw(gDodongosCavernBossLavaFloorTex)) {
+ u32* lavaTex = ResourceGetDataByName(sLavaFloorLavaTex);
+ size_t lavaSize = ResourceGetSizeByName(sLavaFloorLavaTex);
+ size_t floorSize = ResourceGetSizeByName(gDodongosCavernBossLavaFloorTex);
+
+ sLavaFloorModifiedTexRaw = malloc(lavaSize);
+ sLavaWavyTexRaw = malloc(floorSize);
+
+ memcpy(sLavaFloorModifiedTexRaw, lavaTex, lavaSize);
+
+ // When KD is dead, just immediately copy the rock texture
+ if (Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num)) {
+ u32* rockTex = ResourceGetDataByName(sLavaFloorRockTex);
+ size_t rockSize = ResourceGetSizeByName(sLavaFloorRockTex);
+ memcpy(sLavaFloorModifiedTexRaw, rockTex, rockSize);
+ }
+
+ memcpy(sLavaWavyTexRaw, sLavaFloorModifiedTexRaw, floorSize);
+
+ // Register the blended effect for the raw texture
+ Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaWavyTexRaw);
+ } else {
+ // When KD is dead, just immediately copy the rock texture
+ if (Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num)) {
+ u16* rockTex = ResourceGetDataByName(sLavaFloorRockTex);
+ memcpy(sLavaFloorModifiedTex, rockTex, sizeof(sLavaFloorModifiedTex));
+ } else {
+ u16* lavaTex = ResourceGetDataByName(sLavaFloorLavaTex);
+ memcpy(sLavaFloorModifiedTex, lavaTex, sizeof(sLavaFloorModifiedTex));
+ }
+
+ // Register the blended effect for the non-raw texture
+ memcpy(sLavaWavyTex, sLavaFloorModifiedTex, sizeof(sLavaWavyTex));
+
+ Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaWavyTex);
+ }
+
+ gfx_texture_cache_clear();
+}
+
void func_808C12C4(u8* arg1, s16 arg2) {
if (arg2[arg1] != 0) {
sMaskTex8x16[arg2 / 2] = 1;
@@ -86,12 +168,51 @@ void func_808C12C4(u8* arg1, s16 arg2) {
}
}
-void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
- arg0 = GetResourceDataByNameHandlingMQ(arg0);
- floorTex = ResourceGetDataByName(floorTex);
+// Same as func_808C1554 but works with u32 values for RGBA32 raw textures
+void func_808C1554_Raw(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
+ u16 width = ResourceGetTexWidthByName(arg0);
+ s32 size = ResourceGetTexHeightByName(arg0) * width;
+
+ u32* temp_s3 = sLavaWavyTexRaw;
+ u32* temp_s1 = sLavaFloorModifiedTexRaw;
+ s32 i;
+ s32 i2;
+ u32* sp54 = malloc(size * sizeof(u32)); // Match the size for lava floor tex
+ s32 temp;
+ s32 temp2;
+
+ // Multiplier is used to try to scale the wavy effect to match the scale of the HD texture
+ // Applying sqrt(multiplier) to arg3 is to control how many pixels move left/right for the selected row
+ // Applying to arg2 and M_PI help to space out the wave effect
+ // It's not perfect but close enough
+ u16 multiplier = width / LAVA_TEX_WIDTH;
+
+ for (i = 0; i < size; i += width) {
+ temp = sinf((((i / width) + (s32)(((arg2 * multiplier) * 50.0f) / 100.0f)) & (width - 1)) * (M_PI / (16 * multiplier))) * (arg3 * sqrt(multiplier));
+ for (i2 = 0; i2 < width; i2++) {
+ sp54[i + ((temp + i2) & (width - 1))] = temp_s1[i + i2];
+ }
+ }
+ for (i = 0; i < width; i++) {
+ temp = sinf(((i + (s32)(((arg2 * multiplier) * 80.0f) / 100.0f)) & (width - 1)) * (M_PI / (16 * multiplier))) * (arg3 * sqrt(multiplier));
+ temp *= width;
+ for (i2 = 0; i2 < size; i2 += width) {
+ temp2 = (temp + i2) & (size - 1);
+ temp_s3[i + temp2] = sp54[i + i2];
+ }
+ }
+
+ free(sp54);
+
+ // Need to clear the cache after updating sLavaWavyTexRaw
+ gfx_texture_cache_clear();
+}
- u16* temp_s3 = SEGMENTED_TO_VIRTUAL(arg0);
- u16* temp_s1 = SEGMENTED_TO_VIRTUAL(floorTex);
+// Modified to support CPU modified texture with the resource system
+// Used for the original non-raw asset working with u16 values
+void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
+ u16* temp_s3 = sLavaWavyTex;
+ u16* temp_s1 = sLavaFloorModifiedTex;
s16 i;
s16 i2;
u16 sp54[2048];
@@ -112,6 +233,9 @@ void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
temp_s3[i + temp2] = sp54[i + i2];
}
}
+
+ // Need to clear the cache after updating sLavaWavyTex
+ gfx_texture_cache_clear();
}
void func_808C17C8(PlayState* play, Vec3f* arg1, Vec3f* arg2, Vec3f* arg3, f32 arg4, s16 arg5) {
@@ -187,27 +311,21 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
Collider_SetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->items);
if (Flags_GetClear(play, play->roomCtx.curRoom.num)) { // KD is dead
- u16* LavaFloorTex = ResourceGetDataByName(gDodongosCavernBossLavaFloorTex);
- u16* LavaFloorRockTex = ResourceGetDataByName(sLavaFloorRockTex);
- temp_s1_3 = SEGMENTED_TO_VIRTUAL(LavaFloorTex);
- temp_s2 = SEGMENTED_TO_VIRTUAL(LavaFloorRockTex);
+ // SOH [General]
+ // Applying the "cooled off" lava rock CPU modified texture for re-visiting the scene
+ // is now handled by BossDodongo_RegisterBlendedLavaTextureUpdate below
+
Actor_Kill(&this->actor);
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, -890.0f, -1523.76f,
-3304.0f, 0, 0, 0, WARP_DUNGEON_CHILD);
Actor_Spawn(&play->actorCtx, play, ACTOR_BG_BREAKWALL, -890.0f, -1523.76f, -3304.0f, 0, 0, 0, 0x6000, true);
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, -690.0f, -1523.76f, -3304.0f, 0, 0, 0, 0, true);
-
- for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) {
- sMaskTexLava[i] = 1;
- }
- } else {
- for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) {
- sMaskTexLava[i] = 0;
- }
}
this->actor.flags &= ~ACTOR_FLAG_TARGETABLE;
+ // #region SOH [General]
+ // Init mask values for all blended textures
for (int i = 0; i < ARRAY_COUNT(sMaskTex8x16); i++) {
sMaskTex8x16[i] = 0;
}
@@ -223,6 +341,12 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
for (int i = 0; i < ARRAY_COUNT(sMaskTex32x16); i++) {
sMaskTex32x16[i] = 0;
}
+ // Set all true for the lava as it will always replace the scene texture
+ for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) {
+ sMaskTexLava[i] = 1;
+ }
+
+ // Register all blended textures
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015890, sMaskTex8x16, NULL);
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_017210, sMaskTex8x32, NULL);
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015D90, sMaskTex16x16, NULL);
@@ -234,10 +358,14 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016990, sMaskTex32x16, NULL);
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016E10, sMaskTex32x16, NULL);
- // OTRTODO: This is causing OOB memory reads with HD assets
- // commenting this out means the lava will stay lava even after beating king d
- //
- // Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaFloorRockTex);
+ BossDodongo_RegisterBlendedLavaTextureUpdate();
+
+ // Register alt listener to update the blended lava for the replacement texture based on alt path
+ if (!hasRegisteredBlendedHook) {
+ GameInteractor_RegisterOnAssetAltChange(BossDodongo_RegisterBlendedLavaTextureUpdate);
+ hasRegisteredBlendedHook = 1;
+ }
+ // #endregion
}
void BossDodongo_Destroy(Actor* thisx, PlayState* play) {
@@ -1014,21 +1142,64 @@ void BossDodongo_Update(Actor* thisx, PlayState* play2) {
}
}
- // TODO The lave floor bubbles with an effect that modifies the texture. This needs to be recreated shader-side.
- //func_808C1554(gDodongosCavernBossLavaFloorTex, sLavaFloorLavaTex, this->unk_19E, this->unk_224);
+ // The lava bubbles with a wavy effect as a CPU modified texture
+ // This has been done by maintaining copied/modified texture values in the actor code
+ // The "cooling off" effect for the lava is pre-applied to the lava texture before applying
+ // the wavy effect. Since this is two effects and closely related to the actor, I've opted
+ // to handle them here rather than as a shader effect.
+ //
+ // Apply the corresponding wavy effect based on the texture being raw or not
+ if (ResourceMgr_TexIsRaw(gDodongosCavernBossLavaFloorTex)) {
+ func_808C1554_Raw(gDodongosCavernBossLavaFloorTex, sLavaFloorLavaTex, this->unk_19E, this->unk_224);
+ } else {
+ func_808C1554(gDodongosCavernBossLavaFloorTex, sLavaFloorLavaTex, this->unk_19E, this->unk_224);
+ }
}
+ // Apply the "cooling off" effect for the lava
if (this->unk_1C6 != 0) {
- u16* ptr1 = ResourceGetDataByName(sLavaFloorLavaTex);
- u16* ptr2 = ResourceGetDataByName(sLavaFloorRockTex);
- s16 i2;
+ // Similar to above, the cooling off effect is a CPU modified texture effect
+ // Apply corresponding to the texture being raw or not
+ if (ResourceMgr_TexIsRaw(sLavaFloorRockTex)) {
+ u32* ptr1 = sLavaFloorModifiedTexRaw;
+ u32* ptr2 = ResourceGetDataByName(sLavaFloorRockTex);
+ u16 width = ResourceGetTexWidthByName(sLavaFloorRockTex);
+ u16 height = ResourceGetTexHeightByName(sLavaFloorRockTex);
+ s16 i2;
+
+ // Get the scale based on the original texture size
+ u16 widthScale = width / LAVA_TEX_WIDTH;
+ u16 heightScale = height / LAVA_TEX_HEIGHT;
+ u32 size = width * height;
+
+ for (i2 = 0; i2 < 20; i2++) {
+ s16 new_var = this->unk_1C2 & (LAVA_TEX_SIZE - 1);
+ // Compute the index to a scaled position (scaling pseudo x,y as a 1D value)
+ s32 indexStart = ((new_var % LAVA_TEX_WIDTH) * widthScale) + ((new_var / LAVA_TEX_WIDTH) * width * heightScale);
+
+ // From the starting index, apply extra pixels right/down based on the scale
+ for (size_t j = 0; j < heightScale; j++) {
+ for (size_t i3 = 0; i3 < widthScale; i3++) {
+ s32 scaledIndex = (indexStart + i3 + (j * width)) & (size - 1);
+ ptr1[scaledIndex] = ptr2[scaledIndex];
+ }
+ }
- for (i2 = 0; i2 < 20; i2++) {
- s16 new_var = this->unk_1C2 & 0x7FF;
+ this->unk_1C2 += 37;
+ }
+ } else {
+ u16* ptr1 = sLavaFloorModifiedTex;
+ u16* ptr2 = ResourceGetDataByName(sLavaFloorRockTex);
+ s16 i2;
- sMaskTexLava[new_var] = 1;
- this->unk_1C2 += 37;
+ for (i2 = 0; i2 < 20; i2++) {
+ s16 new_var = this->unk_1C2 & 0x7FF;
+
+ ptr1[new_var] = ptr2[new_var];
+ this->unk_1C2 += 37;
+ }
}
+
Math_SmoothStepToF(&this->unk_224, 0.0f, 1.0f, 0.01f, 0.0f);
}
diff --git a/soh/src/overlays/actors/ovl_En_Du/z_en_du.c b/soh/src/overlays/actors/ovl_En_Du/z_en_du.c
index fdca797ccc9..db13ecc547b 100644
--- a/soh/src/overlays/actors/ovl_En_Du/z_en_du.c
+++ b/soh/src/overlays/actors/ovl_En_Du/z_en_du.c
@@ -95,6 +95,26 @@ static AnimationInfo sAnimationInfo[] = {
{ &gDaruniaDancingEndAnim, 1.0f, 0.0f, -1.0f, ANIMMODE_ONCE, -6.0f },
};
+// #region SOH [Enhancement] Only animations too fast need to be slowed down, otherwise not touched
+static AnimationInfo sAnimationInfoFix[] = {
+ { NULL },
+ { NULL },
+ { NULL },
+ { NULL },
+ { NULL },
+ { NULL },
+ { NULL },
+ { &gDaruniaDancingLoop1Anim, 0.78f, 0.0f, -1.0f, ANIMMODE_ONCE, -10.0f }, //
+ { &gDaruniaDancingLoop1Anim, 0.77f, 0.0f, -1.0f, ANIMMODE_ONCE, 0.0f }, // hop
+ { &gDaruniaDancingLoop2Anim, 0.78f, 0.0f, -1.0f, ANIMMODE_ONCE, 0.0f }, // from hop to spin
+ { &gDaruniaDancingLoop3Anim, 0.77f, 0.0f, -1.0f, ANIMMODE_ONCE, 0.0f }, // spin
+ { NULL },
+ { NULL },
+ { &gDaruniaDancingLoop4Anim, 0.78f, 0.0f, -1.0f, ANIMMODE_ONCE, 0.0f }, // from spin to hop
+ { NULL },
+};
+// #endregion
+
void EnDu_SetupAction(EnDu* this, EnDuActionFunc actionFunc) {
this->actionFunc = actionFunc;
}
@@ -255,7 +275,13 @@ void func_809FE040(EnDu* this) {
if (this->unk_1E6 >= 8) {
this->unk_1E6 = 0;
}
- Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, animationIndices[this->unk_1E6]);
+ // #region SOH[Enhancement]
+ if (CVarGetInteger("gEnhancements.FixDaruniaDanceSpeed", 1)) {
+ Animation_ChangeByInfo(&this->skelAnime, sAnimationInfoFix, animationIndices[this->unk_1E6]);
+ // #endregion
+ } else {
+ Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, animationIndices[this->unk_1E6]);
+ }
}
}
@@ -271,7 +297,13 @@ void func_809FE104(EnDu* this) {
if (Animation_OnFrame(&this->skelAnime, this->skelAnime.endFrame)) {
this->unk_1E6++;
if (this->unk_1E6 < 4) {
- Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, animationIndices[this->unk_1E6]);
+ // #region SOH[Enhancement]
+ if (CVarGetInteger("gEnhancements.FixDaruniaDanceSpeed", 1) && this->unk_1E6 <= 1) {
+ Animation_ChangeByInfo(&this->skelAnime, sAnimationInfoFix, animationIndices[this->unk_1E6]);
+ // #endregion
+ } else {
+ Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, animationIndices[this->unk_1E6]);
+ }
}
}
}
@@ -465,7 +497,13 @@ void func_809FE890(EnDu* this, PlayState* play) {
}
if (csAction->action == 7 || csAction->action == 8) {
this->unk_1E6 = 0;
- Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENDU_ANIM_7);
+ // #region SOH[Enhancement]
+ if (CVarGetInteger("gEnhancements.FixDaruniaDanceSpeed", 1)) {
+ Animation_ChangeByInfo(&this->skelAnime, sAnimationInfoFix, ENDU_ANIM_7);
+ // #endregion
+ } else {
+ Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENDU_ANIM_7);
+ }
}
this->unk_1EA = csAction->action;
if (this->unk_1EA == 7) {
diff --git a/soh/src/overlays/actors/ovl_En_Insect/z_en_insect.c b/soh/src/overlays/actors/ovl_En_Insect/z_en_insect.c
index f1dff358a61..393e10a918b 100644
--- a/soh/src/overlays/actors/ovl_En_Insect/z_en_insect.c
+++ b/soh/src/overlays/actors/ovl_En_Insect/z_en_insect.c
@@ -211,6 +211,13 @@ void EnInsect_Init(Actor* thisx, PlayState* play2) {
func_80A7D39C(this);
+ // For bugs that aren't linked to a soil patch, we remove the "short lived" flag to prevent them from despawning
+ // And exit early to not increment the "bugs dropped count"
+ if (CVarGetInteger("gNoBugsDespawn", 0) && this->soilActor == NULL) {
+ this->unk_314 &= ~4;
+ return;
+ }
+
D_80A7DEB8++;
} else {
rand = Rand_ZeroOne();
@@ -394,9 +401,6 @@ void func_80A7CAD0(EnInsect* this, PlayState* play) {
}
void func_80A7CBC8(EnInsect* this) {
- if (CVarGetInteger("gNoBugsDespawn", 0) != 0) {
- return;
- }
this->unk_31A = 60;
func_80A7BF58(this);
this->skelAnime.playSpeed = 1.9f;
diff --git a/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c b/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c
index 81242aa77de..908a1af963c 100644
--- a/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c
+++ b/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c
@@ -313,7 +313,9 @@ void EnSth_GiveReward(EnSth* this, PlayState* play) {
this->actor.parent = NULL;
EnSth_SetupAction(this, EnSth_RewardObtainedTalk);
gSaveContext.eventChkInf[EVENTCHKINF_SKULLTULA_REWARD_INDEX] |= this->eventFlag;
- GameInteractor_ExecuteOnFlagSet(FLAG_EVENT_CHECK_INF, (EVENTCHKINF_SKULLTULA_REWARD_INDEX << 4) + sEventFlagsShift[this->actor.params]);
+ if (this->eventFlag != 0) {
+ GameInteractor_ExecuteOnFlagSet(FLAG_EVENT_CHECK_INF, (EVENTCHKINF_SKULLTULA_REWARD_INDEX << 4) + sEventFlagsShift[this->actor.params]);
+ }
} else {
EnSth_GivePlayerItem(this, play);
}
diff --git a/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c b/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c
index 738db714d0b..55fbed82469 100644
--- a/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c
+++ b/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c
@@ -408,7 +408,7 @@ s32 EnTk_ChooseReward(EnTk* this) {
f32 luck;
s32 reward;
- if ((IS_RANDO || CVarGetInteger("gDampeWin", 0)) && !Flags_GetCollectible(gPlayState, 0x1F) && this->heartPieceSpawned == 0) {
+ if ((IS_RANDO || CVarGetInteger("gDampeWin", 0)) && !Flags_GetCollectible(gPlayState, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE) && this->heartPieceSpawned == 0) {
return 3;
}
@@ -624,10 +624,8 @@ void EnTk_Dig(EnTk* this, PlayState* play) {
this->currentReward = EnTk_ChooseReward(this);
- // merging in dampe tour fix seems messy, so i'm just wrapping this whole thing
- // in an n64dd check for now
- if (IS_RANDO || CVarGetInteger("gDampeWin", 0)) {
- if (this->currentReward == 3) {
+ if (this->currentReward == 3) {
+ if (IS_RANDO || CVarGetInteger("gDampeWin", 0)) {
/*
* Upgrade the purple rupee reward to the heart piece if this
* is the first grand prize dig.
@@ -635,37 +633,31 @@ void EnTk_Dig(EnTk* this, PlayState* play) {
if (!Flags_GetItemGetInf(ITEMGETINF_1C) && !(IS_RANDO || CVarGetInteger("gDampeWin", 0))) {
Flags_SetItemGetInf(ITEMGETINF_1C);
this->currentReward = 4;
- } else if ((IS_RANDO || CVarGetInteger("gDampeWin", 0)) && !Flags_GetCollectible(gPlayState, 0x1F) && this->heartPieceSpawned == 0) {
+ } else if ((IS_RANDO || CVarGetInteger("gDampeWin", 0)) && !Flags_GetCollectible(gPlayState, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE) && this->heartPieceSpawned == 0) {
this->currentReward = 4;
}
}
-
- if ((IS_RANDO || CVarGetInteger("gDampeWin", 0)) && this->currentReward == 4) {
- Actor_Spawn(&play->actorCtx, play, ACTOR_EN_ITEM00, rewardPos.x, rewardPos.y, rewardPos.z, 0,
- 0, 0, 0x1F06, true);
- this->heartPieceSpawned = 1;
- } else {
- Item_DropCollectible(play, &rewardPos, rewardParams[this->currentReward]);
- }
- } else {
- if (this->currentReward == 3) {
- /*
- * Upgrade the purple rupee reward to the heart piece if this
- * is the first grand prize dig.
- */
- // If vanilla itemGetInf flag is not set, it's impossible for the new flag to be set, so return true.
- // Otherwise if the gGravediggingTourFix is enabled and the new flag hasn't been set, return true.
- // If true, spawn the heart piece and set the vanilla itemGetInf flag and new temp clear flag.
- if (!heartPieceSpawned &&
- (!(gSaveContext.itemGetInf[1] & ITEMGETINFFLAG_GRAVEDIGGING_HEART_PIECE) ||
- CVarGetInteger("gGravediggingTourFix", 0) &&
- !Flags_GetCollectible(play, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE))) {
- this->currentReward = 4;
- gSaveContext.itemGetInf[1] |= ITEMGETINFFLAG_GRAVEDIGGING_HEART_PIECE;
- heartPieceSpawned = true;
- }
+ /*
+ * Upgrade the purple rupee reward to the heart piece if this
+ * is the first grand prize dig.
+ */
+ // If vanilla itemGetInf flag is not set, it's impossible for the new flag to be set, so return true.
+ // Otherwise if the gGravediggingTourFix is enabled and the new flag hasn't been set, return true.
+ // If true, spawn the heart piece and set the vanilla itemGetInf flag and new temp clear flag.
+ if (!heartPieceSpawned &&
+ (!(gSaveContext.itemGetInf[1] & ITEMGETINFFLAG_GRAVEDIGGING_HEART_PIECE) ||
+ CVarGetInteger("gGravediggingTourFix", 0) &&
+ !Flags_GetCollectible(play, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE))) {
+ this->currentReward = 4;
+ gSaveContext.itemGetInf[1] |= ITEMGETINFFLAG_GRAVEDIGGING_HEART_PIECE;
+ heartPieceSpawned = true;
}
+ }
+ if (IS_RANDO && this->currentReward == 4) {
+ Actor_Spawn(&play->actorCtx, play, ACTOR_EN_ITEM00, rewardPos.x, rewardPos.y, rewardPos.z, 0, 0, 0, 0x1906, true);
+ this->heartPieceSpawned = 1;
+ } else {
EnItem00* reward = Item_DropCollectible(play, &rewardPos, rewardParams[this->currentReward]);
if (this->currentReward == 4) {
reward->collectibleFlag = COLLECTFLAG_GRAVEDIGGING_HEART_PIECE;
diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c
index fa63c23ea22..c0bb842125c 100644
--- a/soh/src/overlays/actors/ovl_player_actor/z_player.c
+++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c
@@ -10474,7 +10474,8 @@ void Player_ProcessSceneCollision(PlayState* play, Player* this) {
sTouchedWallFlags = func_80041DB8(&play->colCtx, this->actor.wallPoly, this->actor.wallBgId);
- if (CVarGetInteger("gFixVineFall", 0)) {
+ // conflicts arise from these two being enabled at once, and with ClimbEverything on, FixVineFall is redundant anyway
+ if (CVarGetInteger("gFixVineFall", 0) && !CVarGetInteger("gClimbEverything", 0)) {
/* This fixes the "started climbing a wall and then immediately fell off" bug.
* The main idea is if a climbing wall is detected, double-check that it will
* still be valid once climbing begins by doing a second raycast with a small
diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c
index 57ba2c230db..61645de1efa 100644
--- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c
+++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c
@@ -1,6 +1,7 @@
#include "file_choose.h"
#include
+#include
#include "textures/title_static/title_static.h"
#include "textures/parameter_static/parameter_static.h"
@@ -1024,7 +1025,7 @@ void FileChoose_UpdateRandomizer() {
return;
}
- if (!SpoilerFileExists(CVarGetString("gSpoilerLog", ""))) {
+ if (!SpoilerFileExists(CVarGetString("gSpoilerLog", "")) && !CVarGetInteger("gRandomizerDontGenerateSpoiler", 0)) {
CVarSetString("gSpoilerLog", "");
fileSelectSpoilerFileLoaded = false;
}
@@ -1051,6 +1052,10 @@ void FileChoose_UpdateRandomizer() {
Randomizer_LoadMasterQuestDungeons(fileLoc);
Randomizer_LoadEntranceOverrides(fileLoc, silent);
fileSelectSpoilerFileLoaded = true;
+
+ if (SpoilerFileExists(CVarGetString("gSpoilerLog", "")) && CVarGetInteger("gRandomizerDontGenerateSpoiler", 0)) {
+ remove(fileLoc);
+ }
}
}
@@ -1071,6 +1076,7 @@ void FileChoose_UpdateMainMenu(GameState* thisx) {
Input* input = &this->state.input[0];
bool dpad = CVarGetInteger("gDpadText", 0);
+ SoH_ProcessDroppedFiles();
FileChoose_UpdateRandomizer();
if (CHECK_BTN_ALL(input->press.button, BTN_START) || CHECK_BTN_ALL(input->press.button, BTN_A)) {
@@ -1261,6 +1267,7 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) {
s8 i = 0;
bool dpad = CVarGetInteger("gDpadText", 0);
+ SoH_ProcessDroppedFiles();
FileChoose_UpdateRandomizer();
if (ABS(this->stickRelX) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT | BTN_DRIGHT))) {
@@ -3686,4 +3693,7 @@ void FileChoose_Init(GameState* thisx) {
Font_LoadOrderedFont(&this->font);
Audio_QueueSeqCmd(0xF << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0xA);
func_800F5E18(SEQ_PLAYER_BGM_MAIN, NA_BGM_FILE_SELECT, 0, 7, 1);
+
+ // Originally this was only set when transitioning from the title screen, but gSkipLogoTitle skips that process so we're ensuring it's set here
+ gSaveContext.gameMode = GAMEMODE_FILE_SELECT;
}
diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c
index 74460691056..665c95b81af 100644
--- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c
+++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c
@@ -1054,8 +1054,9 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) {
//Fix for Equip Dupe
if (pauseCtx->equipTargetItem == ITEM_BOW) {
- if ((gSaveContext.equips.buttonItems[otherButtonIndex] >= ITEM_BOW_ARROW_FIRE) &&
- (gSaveContext.equips.buttonItems[otherButtonIndex] <= ITEM_BOW_ARROW_LIGHT)) {
+ if (gSaveContext.equips.buttonItems[otherButtonIndex] >= ITEM_BOW_ARROW_FIRE &&
+ gSaveContext.equips.buttonItems[otherButtonIndex] <= ITEM_BOW_ARROW_LIGHT &&
+ !CVarGetInteger("gSeparateArrows", 0)) {
gSaveContext.equips.buttonItems[otherButtonIndex] = gSaveContext.equips.buttonItems[targetButtonIndex];
gSaveContext.equips.cButtonSlots[otherSlotIndex] = gSaveContext.equips.cButtonSlots[pauseCtx->equipTargetCBtn];
Interface_LoadItemIcon2(play, otherButtonIndex);