diff --git a/app/build.gradle b/app/build.gradle
index 403599f..afb144f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,8 +16,8 @@ android {
}
minSdkVersion 18
targetSdkVersion 31
- versionCode 3
- versionName "1.1.1"
+ versionCode 4
+ versionName "1.1.2"
externalNativeBuild {
//ndkBuild {
// arguments "APP_PLATFORM=android-23"
diff --git a/app/jni/CMakeLists.txt b/app/jni/CMakeLists.txt
index 86a083e..d6526f4 100644
--- a/app/jni/CMakeLists.txt
+++ b/app/jni/CMakeLists.txt
@@ -5,8 +5,8 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 ")
-project(Game VERSION 8.0.2 LANGUAGES C CXX)
-set(PROJECT_BUILD_NAME "MacReady Charlie" CACHE STRING "")
+project(Game VERSION 8.0.4 LANGUAGES C CXX)
+set(PROJECT_BUILD_NAME "MacReady Echo" CACHE STRING "")
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
add_subdirectory(src)
diff --git a/app/jni/src/CMakeLists.txt b/app/jni/src/CMakeLists.txt
index 668f0b5..5b14dd3 100644
--- a/app/jni/src/CMakeLists.txt
+++ b/app/jni/src/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.4 LANGUAGES C CXX)
+set(PROJECT_BUILD_NAME "MacReady Echo" CACHE STRING "")
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh)
diff --git a/app/jni/src/libultraship b/app/jni/src/libultraship
index 8a9c48f..bbadf50 160000
--- a/app/jni/src/libultraship
+++ b/app/jni/src/libultraship
@@ -1 +1 @@
-Subproject commit 8a9c48fa66cbaa7c268c86c9780710f9a176fcca
+Subproject commit bbadf5030146d13a16372943aa54f314adb3e3ff
diff --git a/app/jni/src/soh/assets/xml/GC_MQ_D/objects/object_mo.xml b/app/jni/src/soh/assets/xml/GC_MQ_D/objects/object_mo.xml
index f25286d..f31a345 100644
--- a/app/jni/src/soh/assets/xml/GC_MQ_D/objects/object_mo.xml
+++ b/app/jni/src/soh/assets/xml/GC_MQ_D/objects/object_mo.xml
@@ -79,7 +79,7 @@
-
+
diff --git a/app/jni/src/soh/assets/xml/GC_MQ_D/overlays/ovl_Boss_Dodongo.xml b/app/jni/src/soh/assets/xml/GC_MQ_D/overlays/ovl_Boss_Dodongo.xml
index f2ee5b7..0e2c84f 100644
--- a/app/jni/src/soh/assets/xml/GC_MQ_D/overlays/ovl_Boss_Dodongo.xml
+++ b/app/jni/src/soh/assets/xml/GC_MQ_D/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/objects/object_mo.xml b/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/objects/object_mo.xml
index 2a6247c..b4528ef 100644
--- a/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/objects/object_mo.xml
+++ b/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/objects/object_mo.xml
@@ -8,7 +8,7 @@
-
+
@@ -69,17 +69,17 @@
-
+
-
+
-
+
diff --git a/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/overlays/ovl_Boss_Dodongo.xml b/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
index 0e1303c..8a1c4ee 100644
--- a/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
+++ b/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/text/message_data_static.xml b/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/text/message_data_static.xml
index 400a27e..5241e3e 100644
--- a/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/text/message_data_static.xml
+++ b/app/jni/src/soh/assets/xml/GC_MQ_PAL_F/text/message_data_static.xml
@@ -1,14 +1,14 @@
-
+
-
+
-
+
-
+
diff --git a/app/jni/src/soh/assets/xml/GC_NMQ_D/objects/object_mo.xml b/app/jni/src/soh/assets/xml/GC_NMQ_D/objects/object_mo.xml
index 2a6247c..b4528ef 100644
--- a/app/jni/src/soh/assets/xml/GC_NMQ_D/objects/object_mo.xml
+++ b/app/jni/src/soh/assets/xml/GC_NMQ_D/objects/object_mo.xml
@@ -8,7 +8,7 @@
-
+
@@ -69,17 +69,17 @@
-
+
-
+
-
+
diff --git a/app/jni/src/soh/assets/xml/GC_NMQ_D/overlays/ovl_Boss_Dodongo.xml b/app/jni/src/soh/assets/xml/GC_NMQ_D/overlays/ovl_Boss_Dodongo.xml
index f6a52f0..d31e4ab 100644
--- a/app/jni/src/soh/assets/xml/GC_NMQ_D/overlays/ovl_Boss_Dodongo.xml
+++ b/app/jni/src/soh/assets/xml/GC_NMQ_D/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/app/jni/src/soh/assets/xml/GC_NMQ_PAL_F/objects/object_mo.xml b/app/jni/src/soh/assets/xml/GC_NMQ_PAL_F/objects/object_mo.xml
index 2a6247c..b4528ef 100644
--- a/app/jni/src/soh/assets/xml/GC_NMQ_PAL_F/objects/object_mo.xml
+++ b/app/jni/src/soh/assets/xml/GC_NMQ_PAL_F/objects/object_mo.xml
@@ -8,7 +8,7 @@
-
+
@@ -69,17 +69,17 @@
-
+
-
+
-
+
diff --git a/app/jni/src/soh/assets/xml/GC_NMQ_PAL_F/overlays/ovl_Boss_Dodongo.xml b/app/jni/src/soh/assets/xml/GC_NMQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
index f2ee5b7..67d85d1 100644
--- a/app/jni/src/soh/assets/xml/GC_NMQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
+++ b/app/jni/src/soh/assets/xml/GC_NMQ_PAL_F/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
-
-
+
+
+
diff --git a/app/jni/src/soh/assets/xml/N64_PAL_10/objects/object_mo.xml b/app/jni/src/soh/assets/xml/N64_PAL_10/objects/object_mo.xml
index 2a6247c..b4528ef 100644
--- a/app/jni/src/soh/assets/xml/N64_PAL_10/objects/object_mo.xml
+++ b/app/jni/src/soh/assets/xml/N64_PAL_10/objects/object_mo.xml
@@ -8,7 +8,7 @@
-
+
@@ -69,17 +69,17 @@
-
+
-
+
-
+
diff --git a/app/jni/src/soh/assets/xml/N64_PAL_10/overlays/ovl_Boss_Dodongo.xml b/app/jni/src/soh/assets/xml/N64_PAL_10/overlays/ovl_Boss_Dodongo.xml
index 8f0c761..5e4975b 100644
--- a/app/jni/src/soh/assets/xml/N64_PAL_10/overlays/ovl_Boss_Dodongo.xml
+++ b/app/jni/src/soh/assets/xml/N64_PAL_10/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/app/jni/src/soh/assets/xml/N64_PAL_11/objects/object_mo.xml b/app/jni/src/soh/assets/xml/N64_PAL_11/objects/object_mo.xml
index 2a6247c..b4528ef 100644
--- a/app/jni/src/soh/assets/xml/N64_PAL_11/objects/object_mo.xml
+++ b/app/jni/src/soh/assets/xml/N64_PAL_11/objects/object_mo.xml
@@ -8,7 +8,7 @@
-
+
@@ -69,17 +69,17 @@
-
+
-
+
-
+
diff --git a/app/jni/src/soh/assets/xml/N64_PAL_11/overlays/ovl_Boss_Dodongo.xml b/app/jni/src/soh/assets/xml/N64_PAL_11/overlays/ovl_Boss_Dodongo.xml
index cd9069e..d6f7744 100644
--- a/app/jni/src/soh/assets/xml/N64_PAL_11/overlays/ovl_Boss_Dodongo.xml
+++ b/app/jni/src/soh/assets/xml/N64_PAL_11/overlays/ovl_Boss_Dodongo.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/app/jni/src/soh/properties.h b/app/jni/src/soh/properties.h
index f11a58b..592d007 100644
--- a/app/jni/src/soh/properties.h
+++ b/app/jni/src/soh/properties.h
@@ -1,9 +1,9 @@
-#define VER_FILEVERSION 8, 0, 2, 0
-#define VER_FILEVERSION_STR "8.0.2\0"
+#define VER_FILEVERSION 8, 0, 4, 0
+#define VER_FILEVERSION_STR "8.0.4\0"
-#define VER_PRODUCTVERSION 8, 0, 2, 0
-#define VER_PRODUCTVERSION_str "8.0.2\0"
+#define VER_PRODUCTVERSION 8, 0, 4, 0
+#define VER_PRODUCTVERSION_str "8.0.4\0"
#define VER_COMPANYNAME_STR "github.com/harbourmasters\0"
#define VER_PRODUCTNAME_STR "Ship of Harkinian\0"
@@ -11,4 +11,4 @@
#define VER_INTERNALNAME_STR "soh\0"
#define VER_ORIGINALFILENAME_STR "soh.exe\0"
-#define VER_FILEDESCRIPTION_STR "Ship of Harkinian - MacReady Charlie\0"
+#define VER_FILEDESCRIPTION_STR "Ship of Harkinian - MacReady Echo\0"
diff --git a/app/jni/src/soh/soh/Enhancements/controls/GameControlEditor.cpp b/app/jni/src/soh/soh/Enhancements/controls/GameControlEditor.cpp
index 935935c..eb69f3c 100644
--- a/app/jni/src/soh/soh/Enhancements/controls/GameControlEditor.cpp
+++ b/app/jni/src/soh/soh/Enhancements/controls/GameControlEditor.cpp
@@ -312,6 +312,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)) {
@@ -323,6 +324,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/app/jni/src/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/app/jni/src/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
index 9c56ac3..378197b 100644
--- a/app/jni/src/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
+++ b/app/jni/src/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/app/jni/src/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/app/jni/src/soh/soh/Enhancements/custom-message/CustomMessageTypes.h
index 916e1a7..d93bae9 100644
--- a/app/jni/src/soh/soh/Enhancements/custom-message/CustomMessageTypes.h
+++ b/app/jni/src/soh/soh/Enhancements/custom-message/CustomMessageTypes.h
@@ -51,6 +51,7 @@ typedef enum {
TEXT_WARP_NOCTURNE_OF_SHADOW = 0x891,
TEXT_WARP_PRELUDE_OF_LIGHT = 0x892,
TEXT_WARP_RANDOM_REPLACED_TEXT = 0x9200,
+ TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW = 0x9210,
TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN = 0x346, // 0x3yy for cuttable sign range
TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI = 0x1B3, // 0x1yy for Navi msg range
} TextIDs;
diff --git a/app/jni/src/soh/soh/Enhancements/enhancementTypes.h b/app/jni/src/soh/soh/Enhancements/enhancementTypes.h
index 529ebed..c8461bb 100644
--- a/app/jni/src/soh/soh/Enhancements/enhancementTypes.h
+++ b/app/jni/src/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/app/jni/src/soh/soh/Enhancements/game-interactor/GameInteractor.h b/app/jni/src/soh/soh/Enhancements/game-interactor/GameInteractor.h
index ae711c0..4f8ef25 100644
--- a/app/jni/src/soh/soh/Enhancements/game-interactor/GameInteractor.h
+++ b/app/jni/src/soh/soh/Enhancements/game-interactor/GameInteractor.h
@@ -94,6 +94,7 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state);
#include
#include
+#include
#define DEFINE_HOOK(name, type) \
struct name { \
@@ -193,6 +194,9 @@ public:
DEFINE_HOOK(OnSetGameLanguage, void());
+ DEFINE_HOOK(OnFileDropped, void(std::string filePath));
+ DEFINE_HOOK(OnAssetAltChange, void());
+
// Helpers
static bool IsSaveLoaded();
static bool IsGameplayPaused();
diff --git a/app/jni/src/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/app/jni/src/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp
index 13df133..4bd5354 100644
--- a/app/jni/src/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp
+++ b/app/jni/src/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/app/jni/src/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/app/jni/src/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h
index 47114a3..7b7b226 100644
--- a/app/jni/src/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h
+++ b/app/jni/src/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/app/jni/src/soh/soh/Enhancements/gameplaystats.cpp b/app/jni/src/soh/soh/Enhancements/gameplaystats.cpp
index a6e5a47..efcf44e 100644
--- a/app/jni/src/soh/soh/Enhancements/gameplaystats.cpp
+++ b/app/jni/src/soh/soh/Enhancements/gameplaystats.cpp
@@ -618,7 +618,7 @@ void DrawGameplayStatsOptionsTab() {
}
void GameplayStatsWindow::DrawElement() {
- ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_Appearing);
+ ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Gameplay Stats", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) {
ImGui::End();
return;
diff --git a/app/jni/src/soh/soh/Enhancements/mods.cpp b/app/jni/src/soh/soh/Enhancements/mods.cpp
index 0bbeff8..6692dd5 100644
--- a/app/jni/src/soh/soh/Enhancements/mods.cpp
+++ b/app/jni/src/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;
}
@@ -1025,8 +1033,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->id == ACTOR_EN_DH;
+
+ // Only apply to enemies and bosses.
+ if (!CVarGetInteger("gRandomizedEnemySizes", 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) {
return;
}
@@ -1035,9 +1051,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/app/jni/src/soh/soh/Enhancements/presets.h b/app/jni/src/soh/soh/Enhancements/presets.h
index f0f586e..22c9dd7 100644
--- a/app/jni/src/soh/soh/Enhancements/presets.h
+++ b/app/jni/src/soh/soh/Enhancements/presets.h
@@ -217,6 +217,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 = {
@@ -269,7 +294,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 = {
@@ -399,6 +440,15 @@ const std::vector randomizerCvars = {
"gRandomizeGregHint",
"gRandoManualSeedEntry",
"gRandomizerSettingsEnabled",
+ "gRandomizeTriforceHuntTotalPieces",
+ "gRandomizeTriforceHuntRequiredPieces",
+ "gRandomizeTriforceHunt",
+ "gRandomizeShuffleMasterSword",
+ "gRandomizeSariaHint",
+ "gRandomizeRupeeNames",
+ "gRandomizeFrogsHint",
+ "gRandoRelevantNavi",
+ "gRandoQuestItemFanfares",
};
const std::vector vanillaPlusPresetEntries = {
diff --git a/app/jni/src/soh/soh/Enhancements/randomizer/3drando/item_location.cpp b/app/jni/src/soh/soh/Enhancements/randomizer/3drando/item_location.cpp
index 577de1c..0ab29d8 100644
--- a/app/jni/src/soh/soh/Enhancements/randomizer/3drando/item_location.cpp
+++ b/app/jni/src/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/app/jni/src/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp b/app/jni/src/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp
index 8988b04..19960f1 100644
--- a/app/jni/src/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp
+++ b/app/jni/src/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp
@@ -104,7 +104,7 @@ void AreaTable_Init_DeathMountain() {
Entrance(DEATH_MOUNTAIN_TRAIL, {[]{return true;}}),
Entrance(GC_WOODS_WARP, {[]{return GCWoodsWarpOpen;}}),
Entrance(GC_SHOP, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || (IsChild && (CanBlastOrSmash || GoronBracelet || GoronCityChildFire || CanUse(BOW)));}}),
- Entrance(GC_DARUNIAS_CHAMBER, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || GCDaruniasDoorOpenChild;}}),
+ Entrance(GC_DARUNIAS_CHAMBER, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || (IsChild && GCDaruniasDoorOpenChild);}}),
Entrance(GC_GROTTO_PLATFORM, {[]{return IsAdult && ((CanPlay(SongOfTime) && ((EffectiveHealth > 2) || CanUse(GORON_TUNIC) || CanUse(LONGSHOT) || CanUse(NAYRUS_LOVE))) || (EffectiveHealth > 1 && CanUse(GORON_TUNIC) && CanUse(HOOKSHOT)) || (CanUse(NAYRUS_LOVE) && CanUse(HOOKSHOT)) || (EffectiveHealth > 2 && CanUse(HOOKSHOT) && LogicGoronCityGrotto));}}),
});
diff --git a/app/jni/src/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp b/app/jni/src/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp
index 088ba50..6204224 100644
--- a/app/jni/src/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp
+++ b/app/jni/src/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/app/jni/src/soh/soh/Enhancements/randomizer/randomizer.cpp b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer.cpp
index 8f88a72..4b9a84e 100644
--- a/app/jni/src/soh/soh/Enhancements/randomizer/randomizer.cpp
+++ b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer.cpp
@@ -360,29 +360,12 @@ 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));
+ std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream) {
return false;
} else {
@@ -496,6 +479,13 @@ void Randomizer::LoadHintLocations(const char* spoilerFileName) {
"Zu {{location}}?\x1B&%gOK&No%w\x02",
"Se téléporter vers&{{location}}?\x1B&%gOK!&Non%w\x02"));
+ // Bow Shooting Gallery reminder
+ CustomMessageManager::Instance->CreateMessage(Randomizer::hintMessageTableID, TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW,
+ CustomMessage("Come back when you have your own&bow and you'll get a %rdifferent prize%w!",
+ "Komm wieder sobald du deinen eigenen&Bogen hast, um einen %rspeziellen Preis%w zu&erhalten!",
+ "J'aurai %rune autre récompense%w pour toi&lorsque tu auras ton propre arc."));
+
+ // Lake Hylia water level system
CustomMessageManager::Instance->CreateMessage(Randomizer::hintMessageTableID, TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN,
CustomMessage("Water level control system.&Keep away!",
"Wasserstand Kontrollsystem&Finger weg!",
@@ -659,7 +649,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;
@@ -1293,7 +1283,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;
@@ -1394,7 +1384,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;
}
@@ -1415,7 +1405,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;
}
@@ -1495,7 +1485,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;
@@ -1558,7 +1548,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;
}
@@ -2547,6 +2537,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) {
@@ -3143,7 +3134,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, "");
@@ -3166,13 +3159,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", "");
@@ -3180,6 +3177,8 @@ void RandomizerSettingsWindow::DrawElement() {
UIWidgets::PaddedSeparator();
+ ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
+
ImGuiWindow* window = ImGui::GetCurrentWindow();
static ImVec2 cellPadding(8.0f, 8.0f);
@@ -5224,6 +5223,8 @@ void RandomizerSettingsWindow::DrawElement() {
ImGui::EndTabBar();
}
+
+ ImGui::EndDisabled();
if (disableEditingRandoSettings) {
UIWidgets::ReEnableComponent("");
diff --git a/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp
index d7f99ed..d551108 100644
--- a/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp
+++ b/app/jni/src/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/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp
index 6123c1a..d9c2424 100644
--- a/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp
+++ b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp
@@ -73,20 +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;
+bool alwaysShowGS = false;
std::map startingShopItem = { { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 },
{ SCENE_BAZAAR, RC_MARKET_BAZAAR_ITEM_1 },
@@ -118,12 +110,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 +183,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 +238,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 +342,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;
}
@@ -467,7 +435,6 @@ void CheckTrackerLoadGame(int32_t fileNum) {
} else {
realRcObj = rcObj;
}
- if (!IsVisibleInCheckTracker(realRcObj)) continue;
checksByArea.find(realRcObj.rcArea)->second.push_back(realRcObj);
if (rcTrackerData.status == RCSHOW_SAVED || rcTrackerData.skipped) {
@@ -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();
@@ -557,31 +514,19 @@ void CheckTrackerTransition(uint32_t sceneNum) {
}
void CheckTrackerFrame() {
+ if (IS_RANDO) {
+ hideShopRightChecks = CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1);
+ alwaysShowGS = CVarGetInteger("gCheckTrackerOptionAlwaysShowGSLocs", 0);
+ }
if (!GameInteractor::IsSaveLoaded()) {
return;
}
- if (!checkAreas.empty() && !transitionCheck && !messageCloseCheck && !pendingSaleCheck) {
- for (int i = 0; i < 10; i++) {
- if (CheckByArea()) {
- checkCounter = 0;
- break;
- } else {
- checkCounter++;
- }
+ // 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 (checkLoops > 15) {
- checkAreas.erase(checkAreas.begin());
- checkLoops = 0;
- }
- }
- if (savedFrames > 0 && !pendingSaleCheck && !messageCloseCheck) {
- savedFrames--;
- }
-}
-
-void CheckTrackerSaleEnd(GetItemEntry giEntry) {
- if (pendingSaleCheck) {
- pendingSaleCheck = false;
}
}
@@ -593,7 +538,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 +567,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 +606,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);
+ }*/
+ }
+}
+
+void CheckTrackerSceneFlagSet(int16_t sceneNum, int16_t flagType, int32_t flag) {
+ if (flagType != FLAG_SCENE_TREASURE && flagType != FLAG_SCENE_COLLECTIBLE) {
+ return;
+ }
+ if (sceneNum == SCENE_GRAVEYARD && flag == 0x19 && flagType == FLAG_SCENE_COLLECTIBLE) { // Gravedigging tour special case
+ SetCheckCollected(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR);
+ 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;
}
}
- auto checkArea = GetCheckArea();
- if (gSaveContext.pendingSale != ITEM_NONE) {
- pendingSaleCheck = true;
- checkAreas.push_back(checkArea);
+}
+
+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 (checkMatchType == SpoilerCollectionCheckType::SPOILER_CHK_NONE) {
return;
}
- if (scene == SCENE_DESERT_COLOSSUS && (gSaveContext.entranceIndex == 485 || gSaveContext.entranceIndex == 489)) {
- checkAreas.push_back(RCAREA_SPIRIT_TEMPLE);
- return;
- }
- if (GET_PLAYER(gPlayState) == nullptr) {
- transitionCheck = true;
- return;
- }
- if (gPlayState->msgCtx.msgMode != MSGMODE_NONE) {
- checkAreas.push_back(checkArea);
- messageCloseCheck = true;
- 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 +744,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 +763,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 +778,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 +950,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)) {
@@ -1059,7 +1088,6 @@ void LoadSettings() {
showLinksPocket = IS_RANDO ? // don't show Link's Pocket if not randomizer, or if rando and pocket is disabled
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING
:false;
- hideShopRightChecks = IS_RANDO ? CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1) : false;
if (IS_RANDO) {
switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_TOKENS)) {
@@ -1123,7 +1151,7 @@ bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj) {
) &&
(rcObj.rcType != RCTYPE_MERCHANT || showMerchants) &&
(rcObj.rcType != RCTYPE_OCARINA || showOcarinas) &&
- (rcObj.rcType != RCTYPE_SKULL_TOKEN ||
+ (rcObj.rcType != RCTYPE_SKULL_TOKEN || alwaysShowGS ||
(showOverworldTokens && RandomizerCheckObjects::AreaIsOverworld(rcObj.rcArea)) ||
(showDungeonTokens && RandomizerCheckObjects::AreaIsDungeon(rcObj.rcArea))
) &&
@@ -1151,8 +1179,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;
@@ -1493,7 +1521,9 @@ void CheckTrackerSettingsWindow::DrawElement() {
UIWidgets::EnhancementCheckbox("Vanilla/MQ Dungeon Spoilers", "gCheckTrackerOptionMQSpoilers");
UIWidgets::Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. Otherwise, Vanilla/MQ dungeon locations must be unlocked.");
UIWidgets::EnhancementCheckbox("Hide right-side shop item checks", "gCheckTrackerOptionHideRightShopChecks", false, "", UIWidgets::CheckboxGraphics::Cross, true);
- UIWidgets::Tooltip("If enabled, will prevent the tracker from displaying slots 1-4 in all shops. Requires save reload.");
+ UIWidgets::Tooltip("If enabled, will prevent the tracker from displaying slots 1-4 in all shops.");
+ UIWidgets::EnhancementCheckbox("Always show gold skulltulas", "gCheckTrackerOptionAlwaysShowGSLocs", false, "");
+ UIWidgets::Tooltip("If enabled, will show GS locations in the tracker regardless of tokensanity settings.");
ImGui::TableNextColumn();
@@ -1541,10 +1571,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/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h
index b2193f6..a8928cc 100644
--- a/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h
+++ b/app/jni/src/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/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_entrance.c b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_entrance.c
index f2f1a8a..e6ec3fe 100644
--- a/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_entrance.c
+++ b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_entrance.c
@@ -76,7 +76,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
@@ -276,13 +276,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]));
}
@@ -784,7 +784,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)) {
@@ -796,14 +796,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/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_entrance.h b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_entrance.h
index c00f297..c71b249 100644
--- a/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_entrance.h
+++ b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_entrance.h
@@ -63,7 +63,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/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_grotto.c b/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_grotto.c
index 408dc01..78e429f 100644
--- a/app/jni/src/soh/soh/Enhancements/randomizer/randomizer_grotto.c
+++ b/app/jni/src/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 == 0x7FFF) {
- 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/app/jni/src/soh/soh/OTRGlobals.cpp b/app/jni/src/soh/soh/OTRGlobals.cpp
index ad57d1c..4c564af 100644
--- a/app/jni/src/soh/soh/OTRGlobals.cpp
+++ b/app/jni/src/soh/soh/OTRGlobals.cpp
@@ -49,6 +49,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"
@@ -115,6 +118,8 @@ CrowdControl* CrowdControl::Instance;
#include "soh/config/ConfigUpdaters.h"
+void SoH_ProcessDroppedFiles(std::string filePath);
+
OTRGlobals* OTRGlobals::Instance;
SaveManager* SaveManager::Instance;
CustomMessageManager* CustomMessageManager::Instance;
@@ -253,15 +258,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,
@@ -1021,9 +1037,9 @@ extern "C" void InitOTR() {
OTRGlobals::Instance = new OTRGlobals();
CustomMessageManager::Instance = new CustomMessageManager();
ItemTableManager::Instance = new ItemTableManager();
+ GameInteractor::Instance = new GameInteractor();
SaveManager::Instance = new SaveManager();
SohGui::SetupGuiElements();
- GameInteractor::Instance = new GameInteractor();
AudioCollection::Instance = new AudioCollection();
ActorDB::Instance = new ActorDB();
#ifdef __APPLE__
@@ -1043,6 +1059,11 @@ extern "C" void InitOTR() {
InitMods();
ActorDB::AddBuiltInCustomActors();
+ // #region SOH [Randomizer] TODO: Remove these and refactor spoiler file handling for randomizer
+ CVarClear("gRandomizerNewFileDropped");
+ CVarClear("gRandomizerDroppedFile");
+ // #endregion
+ GameInteractor::Instance->RegisterGameHook(SoH_ProcessDroppedFiles);
time_t now = time(NULL);
tm *tm_now = localtime(&now);
@@ -1131,7 +1152,7 @@ extern "C" void Graph_ProcessFrame(void (*run_one_game_iter)(void)) {
OTRGlobals::Instance->context->GetWindow()->MainLoop(run_one_game_iter);
}
-extern bool ShouldClearTextureCacheAtEndOfFrame;
+extern bool ToggleAltAssetsAtEndOfFrame;
extern "C" void Graph_StartFrame() {
#ifndef __WIIU__
@@ -1214,13 +1235,21 @@ extern "C" void Graph_StartFrame() {
}
#endif
case KbScancode::LUS_KB_TAB: {
- // Toggle HD Assets
- CVarSetInteger("gAltAssets", !CVarGetInteger("gAltAssets", 0));
- ShouldClearTextureCacheAtEndOfFrame = true;
+ ToggleAltAssetsAtEndOfFrame = true;
break;
}
}
#endif
+
+ if (CVarGetInteger("gNewFileDropped", 0)) {
+ std::string filePath = SohUtils::Sanitize(CVarGetString("gDroppedFile", ""));
+ if (!filePath.empty()) {
+ GameInteractor::Instance->ExecuteHooks(filePath);
+ }
+ CVarClear("gNewFileDropped");
+ CVarClear("gDroppedFile");
+ }
+
OTRGlobals::Instance->context->GetWindow()->StartFrame();
}
@@ -1286,10 +1315,14 @@ extern "C" void Graph_ProcessGfxCommands(Gfx* commands) {
}
}
- if (ShouldClearTextureCacheAtEndOfFrame) {
+ if (ToggleAltAssetsAtEndOfFrame) {
+ ToggleAltAssetsAtEndOfFrame = false;
+
+ // Actually update the CVar now before runing the alt asset update listeners
+ CVarSetInteger("gAltAssets", !CVarGetInteger("gAltAssets", 0));
gfx_texture_cache_clear();
LUS::SkeletonPatcher::UpdateSkeletons();
- ShouldClearTextureCacheAtEndOfFrame = false;
+ GameInteractor::Instance->ExecuteHooks();
}
// OTRTODO: FIGURE OUT END FRAME POINT
@@ -1415,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) {
@@ -1441,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);
}
@@ -1480,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;
@@ -1565,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];
}
@@ -2045,10 +2117,10 @@ Color_RGB8 GetColorForControllerLED() {
if (source == LED_SOURCE_CUSTOM) {
color = CVarGetColor24("gLedPort1Color", { 255, 255, 255 });
}
- if (criticalOverride || source == LED_SOURCE_HEALTH) {
+ if (gPlayState && (criticalOverride || source == LED_SOURCE_HEALTH)) {
if (HealthMeter_IsCritical()) {
color = { 0xFF, 0, 0 };
- } else if (source == LED_SOURCE_HEALTH) {
+ } else if (gSaveContext.healthCapacity != 0 && source == LED_SOURCE_HEALTH) {
if (gSaveContext.health / gSaveContext.healthCapacity <= 0.4f) {
color = { 0xFF, 0xFF, 0 };
} else {
@@ -2434,6 +2506,8 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) {
messageEntry = OTRGlobals::Instance->gRandomizer->GetWarpSongMessage(textId, Randomizer_GetSettingValue(RSK_WARP_SONG_HINTS) == RO_GENERIC_OFF);
} else if (textId == TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI || textId == TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN) {
messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::hintMessageTableID, textId);
+ } else if (textId == TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW) {
+ messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::hintMessageTableID, TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW);
} else if (textId == 0x3052 || (textId >= 0x3069 && textId <= 0x3070)) { //Fire Temple gorons
u16 choice = Random(0, NUM_GORON_MESSAGES);
messageEntry = OTRGlobals::Instance->gRandomizer->GetGoronMessage(choice);
@@ -2523,6 +2597,74 @@ 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();
+void SoH_ProcessDroppedFiles(std::string filePath) {
+ try {
+ std::ifstream configStream(filePath);
+ if (!configStream) {
+ return;
+ }
+
+ nlohmann::json configJson;
+ configStream >> configJson;
+
+ // #region SOH [Randomizer] TODO: Refactor spoiler file handling for randomizer
+ if (configJson.contains("version") && configJson.contains("finalSeed")) {
+ CVarSetString("gRandomizerDroppedFile", filePath.c_str());
+ CVarSetInteger("gRandomizerNewFileDropped", 1);
+ return;
+ }
+ // #endregion
+
+ 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/app/jni/src/soh/soh/OTRGlobals.h b/app/jni/src/soh/soh/OTRGlobals.h
index ee4a859..ada030c 100644
--- a/app/jni/src/soh/soh/OTRGlobals.h
+++ b/app/jni/src/soh/soh/OTRGlobals.h
@@ -88,11 +88,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);
diff --git a/app/jni/src/soh/soh/SohGui.cpp b/app/jni/src/soh/soh/SohGui.cpp
index 7b818eb..8782915 100644
--- a/app/jni/src/soh/soh/SohGui.cpp
+++ b/app/jni/src/soh/soh/SohGui.cpp
@@ -38,7 +38,7 @@
#include "Enhancements/game-interactor/GameInteractor.h"
#include "Enhancements/cosmetics/authenticGfxPatches.h"
-bool ShouldClearTextureCacheAtEndOfFrame = false;
+bool ToggleAltAssetsAtEndOfFrame = false;
bool isBetaQuestEnabled = false;
extern "C" {
diff --git a/app/jni/src/soh/soh/SohMenuBar.cpp b/app/jni/src/soh/soh/SohMenuBar.cpp
index 9cbb76a..5d4e5a2 100644
--- a/app/jni/src/soh/soh/SohMenuBar.cpp
+++ b/app/jni/src/soh/soh/SohMenuBar.cpp
@@ -28,7 +28,7 @@
#include "Enhancements/randomizer/randomizer_item_tracker.h"
#include "Enhancements/randomizer/randomizer_settings_window.h"
-extern bool ShouldClearTextureCacheAtEndOfFrame;
+extern bool ToggleAltAssetsAtEndOfFrame;
extern bool isBetaQuestEnabled;
extern "C" PlayState* gPlayState;
@@ -496,6 +496,8 @@ extern std::shared_ptr mGameplayStatsWindow;
void DrawEnhancementsMenu() {
if (ImGui::BeginMenu("Enhancements"))
{
+ ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
+
DrawPresetSelector(PRESET_TYPE_ENHANCEMENTS);
UIWidgets::PaddedSeparator();
@@ -609,7 +611,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);
@@ -910,7 +912,10 @@ void DrawEnhancementsMenu() {
{
if (ImGui::BeginMenu("Mods")) {
if (UIWidgets::PaddedEnhancementCheckbox("Use Alternate Assets", "gAltAssets", false, false)) {
- ShouldClearTextureCacheAtEndOfFrame = true;
+ // The checkbox will flip the alt asset CVar, but we instead want it to change at the end of the game frame
+ // We toggle it back while setting the flag to update the CVar later
+ CVarSetInteger("gAltAssets", !CVarGetInteger("gAltAssets", 0));
+ ToggleAltAssetsAtEndOfFrame = true;
}
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);
@@ -1067,6 +1072,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();
}
@@ -1202,6 +1210,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);
@@ -1243,6 +1253,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);
@@ -1397,6 +1409,8 @@ void DrawCheatsMenu() {
}
UIWidgets::Tooltip("Clears the cutscene pointer to a value safe for wrong warps.");
+ ImGui::EndDisabled();
+
ImGui::EndMenu();
}
}
@@ -1410,6 +1424,8 @@ extern std::shared_ptr mDLViewerWindow;
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)) {
@@ -1482,6 +1498,8 @@ void DrawDeveloperToolsMenu() {
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(1);
+ ImGui::EndDisabled();
+
ImGui::EndMenu();
}
}
diff --git a/app/jni/src/soh/soh/resource/importer/SkeletonLimbFactory.cpp b/app/jni/src/soh/soh/resource/importer/SkeletonLimbFactory.cpp
index 22c18da..7ca8ae1 100644
--- a/app/jni/src/soh/soh/resource/importer/SkeletonLimbFactory.cpp
+++ b/app/jni/src/soh/soh/resource/importer/SkeletonLimbFactory.cpp
@@ -132,15 +132,15 @@ void LUS::SkeletonLimbFactoryV0::ParseFileBinary(std::shared_ptr r
skeletonLimb->limbData.lodLimb.sibling = skeletonLimb->siblingIndex;
if (skeletonLimb->dListPtr != "") {
- auto dList = LUS::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(skeletonLimb->dListPtr.c_str());
- skeletonLimb->limbData.lodLimb.dLists[0] = (Gfx*)(dList ? dList->GetRawPointer() : nullptr);
+ skeletonLimb->dListPtr = "__OTR__" + skeletonLimb->dListPtr;
+ skeletonLimb->limbData.lodLimb.dLists[0] = (Gfx*)skeletonLimb->dListPtr.c_str();
} else {
skeletonLimb->limbData.lodLimb.dLists[0] = nullptr;
}
if (skeletonLimb->dList2Ptr != "") {
- auto dList = LUS::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(skeletonLimb->dList2Ptr.c_str());
- skeletonLimb->limbData.lodLimb.dLists[1] = (Gfx*)(dList ? dList->GetRawPointer() : nullptr);
+ skeletonLimb->dList2Ptr = "__OTR__" + skeletonLimb->dList2Ptr;
+ skeletonLimb->limbData.lodLimb.dLists[1] = (Gfx*)skeletonLimb->dList2Ptr.c_str();
} else {
skeletonLimb->limbData.lodLimb.dLists[1] = nullptr;
}
@@ -153,8 +153,8 @@ void LUS::SkeletonLimbFactoryV0::ParseFileBinary(std::shared_ptr r
skeletonLimb->limbData.standardLimb.dList = nullptr;
if (!skeletonLimb->dListPtr.empty()) {
- const auto dList = LUS::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(skeletonLimb->dListPtr.c_str());
- skeletonLimb->limbData.standardLimb.dList = (Gfx*)(dList ? dList->GetRawPointer() : nullptr);
+ skeletonLimb->dListPtr = "__OTR__" + skeletonLimb->dListPtr;
+ skeletonLimb->limbData.standardLimb.dList = (Gfx*)skeletonLimb->dListPtr.c_str();
}
} else if (skeletonLimb->limbType == LUS::LimbType::Curve) {
skeletonLimb->limbData.skelCurveLimb.firstChildIdx = skeletonLimb->childIndex;
@@ -163,13 +163,13 @@ void LUS::SkeletonLimbFactoryV0::ParseFileBinary(std::shared_ptr r
skeletonLimb->limbData.skelCurveLimb.dList[1] = nullptr;
if (!skeletonLimb->dListPtr.empty()) {
- const auto dList = LUS::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(skeletonLimb->dListPtr.c_str());
- skeletonLimb->limbData.skelCurveLimb.dList[0] = (Gfx*)(dList ? dList->GetRawPointer() : nullptr);
+ skeletonLimb->dListPtr = "__OTR__" + skeletonLimb->dListPtr;
+ skeletonLimb->limbData.skelCurveLimb.dList[0] = (Gfx*)skeletonLimb->dListPtr.c_str();
}
if (!skeletonLimb->dList2Ptr.empty()) {
- const auto dList = LUS::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(skeletonLimb->dList2Ptr.c_str());
- skeletonLimb->limbData.skelCurveLimb.dList[1] = (Gfx*)(dList ? dList->GetRawPointer() : nullptr);
+ skeletonLimb->dList2Ptr = "__OTR__" + skeletonLimb->dList2Ptr;
+ skeletonLimb->limbData.skelCurveLimb.dList[1] = (Gfx*)skeletonLimb->dList2Ptr.c_str();
}
} else if (skeletonLimb->limbType == LUS::LimbType::Skin) {
skeletonLimb->limbData.skinLimb.jointPos.x = skeletonLimb->transX;
@@ -189,14 +189,23 @@ void LUS::SkeletonLimbFactoryV0::ParseFileBinary(std::shared_ptr r
}
if (skeletonLimb->skinSegmentType == LUS::ZLimbSkinType::SkinType_DList) {
- auto res = LUS::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(skeletonLimb->skinDList.c_str());
- skeletonLimb->limbData.skinLimb.segment = res ? res->GetRawPointer() : nullptr;
+ if (skeletonLimb->skinDList != "") {
+ skeletonLimb->skinDList = "__OTR__" + skeletonLimb->skinDList;
+ skeletonLimb->limbData.skinLimb.segment = (Gfx*)skeletonLimb->skinDList.c_str();
+ } else {
+ skeletonLimb->limbData.skinLimb.segment = nullptr;
+ }
} else if (skeletonLimb->skinSegmentType == LUS::ZLimbSkinType::SkinType_4) {
skeletonLimb->skinAnimLimbData.totalVtxCount = skeletonLimb->skinVtxCnt;
skeletonLimb->skinAnimLimbData.limbModifCount = skeletonLimb->skinLimbModifCount;
skeletonLimb->skinAnimLimbData.limbModifications = skeletonLimb->skinLimbModifArray.data();
- auto res = LUS::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(skeletonLimb->skinDList2.c_str());
- skeletonLimb->skinAnimLimbData.dlist = (Gfx*)(res ? res->GetRawPointer() : nullptr);
+
+ if (skeletonLimb->skinDList2 != "") {
+ skeletonLimb->skinDList2 = "__OTR__" + skeletonLimb->skinDList2;
+ skeletonLimb->skinAnimLimbData.dlist = (Gfx*)skeletonLimb->skinDList2.c_str();
+ } else {
+ skeletonLimb->skinAnimLimbData.dlist = nullptr;
+ }
for (size_t i = 0; i < skeletonLimb->skinLimbModifArray.size(); i++) {
skeletonLimb->skinAnimLimbData.limbModifications[i].vtxCount = skeletonLimb->skinLimbModifVertexArrays[i].size();
@@ -254,8 +263,8 @@ void SkeletonLimbFactoryV0::ParseFileXML(tinyxml2::XMLElement* reader, std::shar
limbData.lodLimb.jointPos.z = skelLimb->transZ;
if (skelLimb->dListPtr != "") {
- auto res = LUS::Context::GetInstance()->GetResourceManager()->LoadResourceProcess((const char*)skelLimb->dListPtr.c_str());
- limbData.lodLimb.dLists[0] = (Gfx*)(res ? res->GetRawPointer() : nullptr);
+ skelLimb->dListPtr = "__OTR__" + skelLimb->dListPtr;
+ limbData.lodLimb.dLists[0] = (Gfx*)skelLimb->dListPtr.c_str();
} else {
limbData.lodLimb.dLists[0] = nullptr;
}
diff --git a/app/jni/src/soh/soh/stubs.c b/app/jni/src/soh/soh/stubs.c
index 9afd934..a162a7a 100644
--- a/app/jni/src/soh/soh/stubs.c
+++ b/app/jni/src/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/app/jni/src/soh/soh/util.cpp b/app/jni/src/soh/soh/util.cpp
index 62e1d2d..856432c 100644
--- a/app/jni/src/soh/soh/util.cpp
+++ b/app/jni/src/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/app/jni/src/soh/soh/util.h b/app/jni/src/soh/soh/util.h
index bdbfcd7..db5af86 100644
--- a/app/jni/src/soh/soh/util.h
+++ b/app/jni/src/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/app/jni/src/soh/soh/z_message_OTR.cpp b/app/jni/src/soh/soh/z_message_OTR.cpp
index f90e8bb..6bbd1ec 100644
--- a/app/jni/src/soh/soh/z_message_OTR.cpp
+++ b/app/jni/src/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/app/jni/src/soh/src/code/z_message_PAL.c b/app/jni/src/soh/src/code/z_message_PAL.c
index c33f4e4..0988cf0 100644
--- a/app/jni/src/soh/src/code/z_message_PAL.c
+++ b/app/jni/src/soh/src/code/z_message_PAL.c
@@ -3399,7 +3399,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/app/jni/src/soh/src/code/z_parameter.c b/app/jni/src/soh/src/code/z_parameter.c
index bbaa8d9..a4d61f8 100644
--- a/app/jni/src/soh/src/code/z_parameter.c
+++ b/app/jni/src/soh/src/code/z_parameter.c
@@ -1521,18 +1521,25 @@ void Inventory_SwapAgeEquipment(void) {
}
}
+ // In Rando, when switching to adult for the second+ time, if a sword was not previously
+ // equiped in MS shuffle, then we need to set the swordless flag again
+ if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) &&
+ gSaveContext.equips.buttonItems[0] == ITEM_NONE) {
+ Flags_SetInfTable(INFTABLE_SWORDLESS);
+ }
+
gSaveContext.equips.equipment = gSaveContext.adultEquips.equipment;
}
} 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 +1575,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) {
@@ -1589,6 +1596,13 @@ void Inventory_SwapAgeEquipment(void) {
}
}
+ // In Rando, when switching to child from a swordless adult, and child Link previously had a
+ // sword equiped, then we need to unset the swordless flag to match
+ if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) &&
+ gSaveContext.equips.buttonItems[0] != ITEM_NONE) {
+ Flags_UnsetInfTable(INFTABLE_SWORDLESS);
+ }
+
gSaveContext.equips.equipment = gSaveContext.childEquips.equipment;
gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4));
gSaveContext.equips.equipment |= EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4);
@@ -1598,7 +1612,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/app/jni/src/soh/src/code/z_room.c b/app/jni/src/soh/src/code/z_room.c
index ccbc93d..772978f 100644
--- a/app/jni/src/soh/src/code/z_room.c
+++ b/app/jni/src/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/app/jni/src/soh/src/code/z_vr_box.c b/app/jni/src/soh/src/code/z_vr_box.c
index b77c3d8..ff26221 100644
--- a/app/jni/src/soh/src/code/z_vr_box.c
+++ b/app/jni/src/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/app/jni/src/soh/src/code/z_vr_box_draw.c b/app/jni/src/soh/src/code/z_vr_box_draw.c
index 8eb9757..5b48671 100644
--- a/app/jni/src/soh/src/code/z_vr_box_draw.c
+++ b/app/jni/src/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/app/jni/src/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c b/app/jni/src/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c
index e90e36e..deb7616 100644
--- a/app/jni/src/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c
+++ b/app/jni/src/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);
@@ -57,7 +69,14 @@ static u8 sMaskTex16x32[16 * 32] = { { 0 } };
static u8 sMaskTex32x16[32 * 16] = { { 0 } };
static u8 sMaskTex8x8[8 * 8] = { { 0 } };
static u8 sMaskTex8x32[8 * 32] = { { 0 } };
-static u8 sMaskTexLava[32 * 64] = { { 0 } };
+static u8 sMaskTexLava[LAVA_TEX_WIDTH * LAVA_TEX_HEIGHT] = { { 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),
@@ -66,6 +85,89 @@ 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);
+ size_t rockSize = ResourceGetSizeByName(sLavaFloorRockTex);
+
+ // If the sizes don't match, then don't bother with the blended effect to avoid crashing
+ if (floorSize != lavaSize || floorSize != rockSize) {
+ uint8_t maskVal = !!Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num);
+
+ if (sMaskTexLava[0] != maskVal) {
+ for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) {
+ sMaskTexLava[i] = maskVal;
+ }
+ }
+ Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, NULL);
+ return;
+ }
+
+ 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);
+ 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);
+ }
+
+ // Set all true for the lava as it will always replace the scene texture
+ if (sMaskTexLava[0] == 0) {
+ for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) {
+ sMaskTexLava[i] = 1;
+ }
+ }
+
+ gfx_texture_cache_clear();
+}
+
void func_808C12C4(u8* arg1, s16 arg2) {
if (arg2[arg1] != 0) {
sMaskTex8x16[arg2 / 2] = 1;
@@ -86,12 +188,53 @@ 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) {
+ // Raw lava not registered, so abort the wave modification
+ if (sLavaWavyTexRaw == NULL || sLavaFloorModifiedTexRaw == NULL) {
+ return;
+ }
- u16* temp_s3 = SEGMENTED_TO_VIRTUAL(arg0);
- u16* temp_s1 = SEGMENTED_TO_VIRTUAL(floorTex);
+ 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);
+}
+
+// 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];
@@ -187,27 +330,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 KD blended textures
for (int i = 0; i < ARRAY_COUNT(sMaskTex8x16); i++) {
sMaskTex8x16[i] = 0;
}
@@ -223,6 +360,8 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
for (int i = 0; i < ARRAY_COUNT(sMaskTex32x16); i++) {
sMaskTex32x16[i] = 0;
}
+
+ // 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 +373,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 +1157,72 @@ 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;
- for (i2 = 0; i2 < 20; i2++) {
- s16 new_var = this->unk_1C2 & 0x7FF;
+ // 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;
- sMaskTexLava[new_var] = 1;
- this->unk_1C2 += 37;
+ for (i2 = 0; i2 < 20; i2++) {
+ s16 new_var = this->unk_1C2 & (LAVA_TEX_SIZE - 1);
+
+ // Raw lava must be registered, otherwise skip the effect for incompatible texture pack
+ // and instead set the mask to simulate the lava disappearing by turning black
+ if (sLavaFloorModifiedTexRaw != NULL) {
+ // 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];
+ }
+ }
+ } else {
+ sMaskTexLava[new_var] = 1;
+ }
+
+ this->unk_1C2 += 37;
+ }
+ } else {
+ u16* ptr1 = sLavaFloorModifiedTex;
+ u16* ptr2 = ResourceGetDataByName(sLavaFloorRockTex);
+ s16 i2;
+
+ 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);
}
@@ -1151,8 +1345,16 @@ void BossDodongo_Draw(Actor* thisx, PlayState* play) {
gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTex32x16);
}
- if (this->unk_1C6 != 0) {
- gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTexLava);
+ gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTexLava);
+
+ // Using WORK_DISP to invalidate these textures as they are used in drawing the scene textures which happens
+ // before actors are drawn. WORK_DISP comes before POLAY_OPA_DISP. It is probably not meant for this, but it
+ // at least works for now.
+ // Alternatively, having a way to invalidate just these pointers from the Update func should be sufficient.
+ if (sLavaFloorModifiedTexRaw != NULL) {
+ gSPInvalidateTexCache(WORK_DISP++, sLavaWavyTexRaw);
+ } else {
+ gSPInvalidateTexCache(WORK_DISP++, sLavaWavyTex);
}
if ((this->unk_1C0 >= 2) && (this->unk_1C0 & 1)) {
diff --git a/app/jni/src/soh/src/overlays/actors/ovl_En_Box/z_en_box.c b/app/jni/src/soh/src/overlays/actors/ovl_En_Box/z_en_box.c
index 9e8705c..4f1ef73 100644
--- a/app/jni/src/soh/src/overlays/actors/ovl_En_Box/z_en_box.c
+++ b/app/jni/src/soh/src/overlays/actors/ovl_En_Box/z_en_box.c
@@ -75,7 +75,6 @@ static InitChainEntry sInitChain[] = {
};
static UNK_TYPE sUnused;
-GetItemEntry sItem;
Gfx gSkullTreasureChestChestSideAndLidDL[116] = {0};
Gfx gGoldTreasureChestChestSideAndLidDL[116] = {0};
@@ -472,7 +471,7 @@ void EnBox_WaitOpen(EnBox* this, PlayState* play) {
func_8002DBD0(&this->dyna.actor, &sp4C, &player->actor.world.pos);
if (sp4C.z > -50.0f && sp4C.z < 0.0f && fabsf(sp4C.y) < 10.0f && fabsf(sp4C.x) < 20.0f &&
Player_IsFacingActor(&this->dyna.actor, 0x3000, play)) {
- sItem = Randomizer_GetItemFromActor(this->dyna.actor.id, play->sceneNum, this->dyna.actor.params, this->dyna.actor.params >> 5 & 0x7F);
+ GetItemEntry sItem = Randomizer_GetItemFromActor(this->dyna.actor.id, play->sceneNum, this->dyna.actor.params, this->dyna.actor.params >> 5 & 0x7F);
GetItemEntry blueRupee = ItemTable_RetrieveEntry(MOD_NONE, GI_RUPEE_BLUE);
// RANDOTODO treasure chest game rando
@@ -628,7 +627,7 @@ void EnBox_Update(Actor* thisx, PlayState* play) {
}
if (((!IS_RANDO && ((this->dyna.actor.params >> 5 & 0x7F) == 0x7C)) ||
- (IS_RANDO && ABS(sItem.getItemId) == RG_ICE_TRAP)) &&
+ (IS_RANDO && this->getItemEntry.getItemId == RG_ICE_TRAP)) &&
this->actionFunc == EnBox_Open && this->skelanime.curFrame > 45 && this->iceSmokeTimer < 100) {
if (!CVarGetInteger("gAddTraps.enabled", 0)) {
EnBox_SpawnIceSmoke(this, play);
diff --git a/app/jni/src/soh/src/overlays/actors/ovl_En_Du/z_en_du.c b/app/jni/src/soh/src/overlays/actors/ovl_En_Du/z_en_du.c
index acd64e3..f9374f4 100644
--- a/app/jni/src/soh/src/overlays/actors/ovl_En_Du/z_en_du.c
+++ b/app/jni/src/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/app/jni/src/soh/src/overlays/actors/ovl_En_Insect/z_en_insect.c b/app/jni/src/soh/src/overlays/actors/ovl_En_Insect/z_en_insect.c
index f1dff35..393e10a 100644
--- a/app/jni/src/soh/src/overlays/actors/ovl_En_Insect/z_en_insect.c
+++ b/app/jni/src/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/app/jni/src/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c b/app/jni/src/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c
index 81242aa..908a1af 100644
--- a/app/jni/src/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c
+++ b/app/jni/src/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/app/jni/src/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c b/app/jni/src/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c
index 888b0ef..6ab91dd 100644
--- a/app/jni/src/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c
+++ b/app/jni/src/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c
@@ -3,6 +3,7 @@
#include "overlays/actors/ovl_En_Syateki_Itm/z_en_syateki_itm.h"
#include "objects/object_ossan/object_ossan.h"
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
+#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_NO_LOCKON)
@@ -371,7 +372,8 @@ void EnSyatekiMan_EndGame(EnSyatekiMan* this, PlayState* play) {
this->getItemId = GI_RUPEE_PURPLE;
}
} else {
- if(IS_RANDO && !Flags_GetTreasure(play, 0x1F)) {
+ // Only give the adult rando reward when the player has a quiver
+ if (IS_RANDO && !Flags_GetTreasure(play, 0x1F) && CUR_UPG_VALUE(UPG_QUIVER) > 0) {
this->getItemEntry = Randomizer_GetItemFromKnownCheck(RC_KAK_SHOOTING_GALLERY_REWARD, GI_QUIVER_50);
this->getItemId = this->getItemEntry.getItemId;
Flags_SetTreasure(play, 0x1F);
@@ -448,6 +450,9 @@ void EnSyatekiMan_FinishPrize(EnSyatekiMan* this, PlayState* play) {
Flags_SetItemGetInf(ITEMGETINF_0D);
} else if ((this->getItemId == GI_QUIVER_40) || (this->getItemId == GI_QUIVER_50)) {
Flags_SetItemGetInf(ITEMGETINF_0E);
+ } else if (IS_RANDO && LINK_IS_ADULT && CUR_UPG_VALUE(UPG_QUIVER) == 0) {
+ // In Rando without a quiver, display a message reminding the player to come back with a bow
+ Message_StartTextbox(play, TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW, NULL);
}
this->gameResult = SYATEKI_RESULT_NONE;
this->actor.parent = this->tempGallery;
diff --git a/app/jni/src/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c b/app/jni/src/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c
index 738db71..55fbed8 100644
--- a/app/jni/src/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c
+++ b/app/jni/src/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]);
+ /*
+ * 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 {
- 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;
- }
- }
-
EnItem00* reward = Item_DropCollectible(play, &rewardPos, rewardParams[this->currentReward]);
if (this->currentReward == 4) {
reward->collectibleFlag = COLLECTFLAG_GRAVEDIGGING_HEART_PIECE;
diff --git a/app/jni/src/soh/src/overlays/actors/ovl_player_actor/z_player.c b/app/jni/src/soh/src/overlays/actors/ovl_player_actor/z_player.c
index 497935a..ca73334 100644
--- a/app/jni/src/soh/src/overlays/actors/ovl_player_actor/z_player.c
+++ b/app/jni/src/soh/src/overlays/actors/ovl_player_actor/z_player.c
@@ -10101,7 +10101,8 @@ void func_80847BA0(PlayState* play, Player* this) {
D_808535F0 = 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
@@ -10628,7 +10629,14 @@ void Player_UseTunicBoots(Player* this, PlayState* play) {
s32 i;
s32 item;
s32 actionParam;
- if (!(this->stateFlags1 & PLAYER_STATE1_INPUT_DISABLED || this->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS || this->stateFlags1 & PLAYER_STATE1_IN_CUTSCENE || this->stateFlags1 & PLAYER_STATE1_TEXT_ON_SCREEN || this->stateFlags2 & PLAYER_STATE2_OCARINA_PLAYING)) {
+ if (!(
+ this->stateFlags1 & PLAYER_STATE1_INPUT_DISABLED ||
+ this->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS ||
+ this->stateFlags1 & PLAYER_STATE1_IN_CUTSCENE ||
+ this->stateFlags1 & PLAYER_STATE1_TEXT_ON_SCREEN ||
+ this->stateFlags1 & PLAYER_STATE1_DEAD ||
+ this->stateFlags2 & PLAYER_STATE2_OCARINA_PLAYING
+ )) {
for (i = 0; i < ARRAY_COUNT(D_80854388); i++) {
if (CHECK_BTN_ALL(sControlInput->press.button, D_80854388[i])) {
break;
diff --git a/app/jni/src/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/app/jni/src/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c
index 9c83f93..bb8dd14 100644
--- a/app/jni/src/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c
+++ b/app/jni/src/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,23 +1025,23 @@ void FileChoose_UpdateRandomizer() {
return;
}
- if (!SpoilerFileExists(CVarGetString("gSpoilerLog", ""))) {
+ if (!SpoilerFileExists(CVarGetString("gSpoilerLog", "")) && !CVarGetInteger("gRandomizerDontGenerateSpoiler", 0)) {
CVarSetString("gSpoilerLog", "");
fileSelectSpoilerFileLoaded = false;
}
- if ((CVarGetInteger("gNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0) ||
+ if ((CVarGetInteger("gRandomizerNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0) ||
(!fileSelectSpoilerFileLoaded && SpoilerFileExists(CVarGetString("gSpoilerLog", "")))) {
- if (CVarGetInteger("gNewFileDropped", 0) != 0) {
- CVarSetString("gSpoilerLog", CVarGetString("gDroppedFile", "None"));
+ if (CVarGetInteger("gRandomizerNewFileDropped", 0) != 0) {
+ CVarSetString("gSpoilerLog", CVarGetString("gRandomizerDroppedFile", "None"));
}
bool silent = true;
- if ((CVarGetInteger("gNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0)) {
+ if ((CVarGetInteger("gRandomizerNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0)) {
silent = false;
}
CVarSetInteger("gNewSeedGenerated", 0);
- CVarSetInteger("gNewFileDropped", 0);
- CVarSetString("gDroppedFile", "");
+ CVarSetInteger("gRandomizerNewFileDropped", 0);
+ CVarSetString("gRandomizerDroppedFile", "");
fileSelectSpoilerFileLoaded = false;
const char* fileLoc = CVarGetString("gSpoilerLog", "");
Randomizer_LoadSettings(fileLoc);
@@ -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);
+ }
}
}
@@ -3686,4 +3691,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/app/jni/src/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c b/app/jni/src/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c
index 377c23e..5c5cb3e 100644
--- a/app/jni/src/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c
+++ b/app/jni/src/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c
@@ -456,7 +456,6 @@ void FileChoose_DrawNameEntry(GameState* thisx) {
this->prevConfigMode = CM_MAIN_MENU;
this->configMode = CM_NAME_ENTRY_TO_MAIN;
CVarSetInteger("gOnFileSelectNameEntry", 0);
- CVarSetInteger("gNewFileDropped", 0);
this->nameBoxAlpha[this->buttonIndex] = this->nameAlpha[this->buttonIndex] = 200;
this->connectorAlpha[this->buttonIndex] = 255;
func_800AA000(300.0f, 0xB4, 0x14, 0x64);
diff --git a/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c b/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c
index 7446069..665c95b 100644
--- a/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c
+++ b/app/jni/src/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);
diff --git a/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c b/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c
index 238a1e4..28723d3 100644
--- a/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c
+++ b/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c
@@ -343,7 +343,7 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) {
// Offset the U value of each vertex to be in the mirror boundary for the map textures
if (mirroredWorld) {
for (size_t i = 0; i < 8; i++) {
- pauseCtx->mapPageVtx[60 + i].v.tc[0] += 48 << 5;
+ pauseCtx->mapPageVtx[60 + i].v.tc[0] += MAP_48x85_TEX_WIDTH << 5;
}
}
@@ -353,8 +353,9 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) {
gSPInvalidateTexCache(POLY_KAL_DISP++, interfaceCtx->mapSegment[0]);
gSPInvalidateTexCache(POLY_KAL_DISP++, interfaceCtx->mapSegment[1]);
- gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[0], G_IM_FMT_CI, 48, 85, 0, G_TX_WRAP | mirrorMode,
- G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
+ gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[0], G_IM_FMT_CI, MAP_48x85_TEX_WIDTH,
+ MAP_48x85_TEX_HEIGHT, 0, G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK,
+ G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
// Swap vertices to render left half on the right and vice-versa
if (mirroredWorld) {
@@ -363,9 +364,9 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) {
gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0);
}
- gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[1], G_IM_FMT_CI, 48, 85, 0,
- G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD,
- G_TX_NOLOD);
+ gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[1], G_IM_FMT_CI, MAP_48x85_TEX_WIDTH,
+ MAP_48x85_TEX_HEIGHT, 0, G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK,
+ G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
if (mirroredWorld) {
gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0);
diff --git a/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h b/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h
index 6f4ed7a..8c214d9 100644
--- a/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h
+++ b/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h
@@ -13,6 +13,10 @@ extern u8 gItemAgeReqs[];
extern u8 gAreaGsFlags[];
extern bool gSelectingMask;
+#define MAP_48x85_TEX_WIDTH 48
+#define MAP_48x85_TEX_HEIGHT 85
+#define MAP_48x85_TEX_SIZE ((MAP_48x85_TEX_WIDTH * MAP_48x85_TEX_HEIGHT) / 2) // 48x85 CI4 texture
+
#define AGE_REQ_ADULT LINK_AGE_ADULT
#define AGE_REQ_CHILD LINK_AGE_CHILD
#define AGE_REQ_NONE 9
diff --git a/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c b/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c
index 35490f0..877ea7e 100644
--- a/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c
+++ b/app/jni/src/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c
@@ -1205,6 +1205,8 @@ Gfx* KaleidoScope_DrawPageSections(Gfx* gfx, Vtx* vertices, void** textures) {
return gfx;
}
+static uint8_t mapBlendMask[MAP_48x85_TEX_WIDTH * MAP_48x85_TEX_HEIGHT];
+
void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) {
static Color_RGB8 D_8082ACF4[12] = {
{ 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 255, 255, 0 }, { 0, 0, 0 },
@@ -1373,6 +1375,10 @@ void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) {
}
}
+ // Need to invalidate the blend mask every frame. Ideally this would be done in KaleidoScope_DrawDungeonMap
+ // but the reference is not shared between files
+ gSPInvalidateTexCache(POLY_KAL_DISP++, mapBlendMask);
+
if (pauseCtx->pageIndex) { // pageIndex != PAUSE_ITEM
gDPPipeSync(OVERLAY_DISP++);
gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA, G_CC_MODULATEIA);
@@ -3315,13 +3321,118 @@ void KaleidoScope_UpdateCursorSize(PauseContext* pauseCtx) {
pauseCtx->cursorVtx[14].v.ob[1] = pauseCtx->cursorVtx[15].v.ob[1] = pauseCtx->cursorVtx[12].v.ob[1] - 16;
}
+// Modifed map texture buffers for registered blend effects and the room indicator color
+static uint8_t mapLeftTexModified[MAP_48x85_TEX_SIZE];
+static uint8_t mapRightTexModified[MAP_48x85_TEX_SIZE];
+static uint8_t* mapLeftTexModifiedRaw = NULL;
+static uint8_t* mapRightTexModifiedRaw = NULL;
+
+// Load dungeon maps into the interface context
+// SoH [General] - Modified to account for our resource system and HD textures
void KaleidoScope_LoadDungeonMap(PlayState* play) {
InterfaceContext* interfaceCtx = &play->interfaceCtx;
+ // Free old textures
+ if (mapLeftTexModifiedRaw != NULL) {
+ free(mapLeftTexModifiedRaw);
+ mapLeftTexModifiedRaw = NULL;
+ }
+ if (mapRightTexModifiedRaw != NULL) {
+ free(mapRightTexModifiedRaw);
+ mapRightTexModifiedRaw = NULL;
+ }
+
+ // Unload original textures to bypass cache result for lookups
+ ResourceMgr_UnloadOriginalWhenAltExists(sDungeonMapTexs[R_MAP_TEX_INDEX]);
+ ResourceMgr_UnloadOriginalWhenAltExists(sDungeonMapTexs[R_MAP_TEX_INDEX + 1]);
+
interfaceCtx->mapSegmentName[0] = sDungeonMapTexs[R_MAP_TEX_INDEX];
interfaceCtx->mapSegmentName[1] = sDungeonMapTexs[R_MAP_TEX_INDEX + 1];
- interfaceCtx->mapSegment[0] = ResourceGetDataByName(sDungeonMapTexs[R_MAP_TEX_INDEX]);
- interfaceCtx->mapSegment[1] = ResourceGetDataByName(sDungeonMapTexs[R_MAP_TEX_INDEX + 1]);
+
+ // When the texture is HD (raw) we need to copy a dynamic amount of data
+ // Otherwise the original asset has a static size
+ if (ResourceMgr_TexIsRaw(interfaceCtx->mapSegmentName[0])) {
+ u32 width = ResourceGetTexWidthByName(interfaceCtx->mapSegmentName[0]);
+ u32 height = ResourceGetTexHeightByName(interfaceCtx->mapSegmentName[0]);
+ size_t size = (width * height) / 2; // account for CI4 size
+
+ // Resource size being larger than the calculated CI size means it is most likely not a CI4 texture
+ // Abort early end undo the blended effect by clearing the mask to avoid crashing
+ if (size < ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0])) {
+ if (mapBlendMask[0] != 0) {
+ for (size_t i = 0; i < ARRAY_COUNT(mapBlendMask); i++) {
+ mapBlendMask[i] = 0;
+ }
+ }
+
+ interfaceCtx->mapSegment[0] = NULL;
+ interfaceCtx->mapSegment[1] = NULL;
+
+ Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[0], mapBlendMask, NULL);
+ Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[1], mapBlendMask, NULL);
+ return;
+ }
+
+ u8* map1TexRaw = ResourceGetDataByName(interfaceCtx->mapSegmentName[0]);
+ u8* map2TexRaw = ResourceGetDataByName(interfaceCtx->mapSegmentName[1]);
+
+ mapLeftTexModifiedRaw = malloc(size);
+ mapRightTexModifiedRaw = malloc(size);
+
+ memcpy(mapLeftTexModifiedRaw, map1TexRaw, size);
+ memcpy(mapRightTexModifiedRaw, map2TexRaw, size);
+
+ interfaceCtx->mapSegment[0] = mapLeftTexModifiedRaw;
+ interfaceCtx->mapSegment[1] = mapRightTexModifiedRaw;
+ } else {
+ u8* map1Tex = ResourceGetDataByName(interfaceCtx->mapSegmentName[0]);
+ u8* map2Tex = ResourceGetDataByName(interfaceCtx->mapSegmentName[1]);
+
+ memcpy(mapLeftTexModified, map1Tex, MAP_48x85_TEX_SIZE);
+ memcpy(mapRightTexModified, map2Tex, MAP_48x85_TEX_SIZE);
+
+ interfaceCtx->mapSegment[0] = mapLeftTexModified;
+ interfaceCtx->mapSegment[1] = mapRightTexModified;
+ }
+
+ // Mark and register the blend mask for the copied textures
+ if (mapBlendMask[0] != 1) {
+ for (size_t i = 0; i < ARRAY_COUNT(mapBlendMask); i++) {
+ mapBlendMask[i] = 1;
+ }
+ }
+
+ Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[0], mapBlendMask, interfaceCtx->mapSegment[0]);
+ Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[1], mapBlendMask, interfaceCtx->mapSegment[1]);
+}
+
+static uint8_t registeredDungeonMapTextureHook = false;
+
+void KaleidoScope_RegisterUpdatedDungeonMapTexture() {
+ if (gPlayState == NULL) {
+ return;
+ }
+
+ PauseContext* pauseCtx = &gPlayState->pauseCtx;
+
+ // Kaleido is not open in a dungeon so there is nothing to do
+ if (R_PAUSE_MENU_MODE < 3 || pauseCtx->state < 4 || pauseCtx->state > 7 || !sInDungeonScene) {
+ return;
+ }
+
+ KaleidoScope_UpdateDungeonMap(gPlayState);
+
+ // KaleidoScope_UpdateDungeonMap will update the palette index for the current floor if the cursor is on the floor
+ // If the player toggles alt assets while the cursor is not in the floor level, then we handle the palette index here
+ if (gPlayState->sceneNum >= SCENE_DEKU_TREE && gPlayState->sceneNum <= SCENE_TREASURE_BOX_SHOP &&
+ (VREG(30) + 3) == pauseCtx->dungeonMapSlot && (VREG(30) + 3) != pauseCtx->cursorPoint[PAUSE_MAP]) {
+
+ InterfaceContext* interfaceCtx = &gPlayState->interfaceCtx;
+ int32_t size = ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0]);
+
+ KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], size, interfaceCtx->mapPaletteIndex, 14);
+ KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], size, interfaceCtx->mapPaletteIndex, 14);
+ }
}
void KaleidoScope_UpdateDungeonMap(PlayState* play) {
@@ -3333,19 +3444,29 @@ void KaleidoScope_UpdateDungeonMap(PlayState* play) {
KaleidoScope_LoadDungeonMap(play);
Map_SetFloorPalettesData(play, pauseCtx->dungeonMapSlot - 3);
+ s32 size = MAP_48x85_TEX_SIZE;
+
+ if (ResourceMgr_TexIsRaw(interfaceCtx->mapSegmentName[0])) {
+ size = ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0]);
+ }
+
if ((play->sceneNum >= SCENE_DEKU_TREE) && (play->sceneNum <= SCENE_TREASURE_BOX_SHOP)) {
if ((VREG(30) + 3) == pauseCtx->cursorPoint[PAUSE_MAP]) {
- // HDTODO: Handle Runtime Modified Textures (HD)
- KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], 2040, interfaceCtx->mapPaletteIndex, 14);
+ KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], size, interfaceCtx->mapPaletteIndex, 14);
}
}
if ((play->sceneNum >= SCENE_DEKU_TREE) && (play->sceneNum <= SCENE_TREASURE_BOX_SHOP)) {
if ((VREG(30) + 3) == pauseCtx->cursorPoint[PAUSE_MAP]) {
- // HDTODO: Handle Runtime Modified Textures (HD)
- KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], 2040, interfaceCtx->mapPaletteIndex, 14);
+ KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], size, interfaceCtx->mapPaletteIndex, 14);
}
}
+
+ // Register alt listener to update the blended dungeon map textures on alt toggle
+ if (!registeredDungeonMapTextureHook) {
+ registeredDungeonMapTextureHook = true;
+ GameInteractor_RegisterOnAssetAltChange(KaleidoScope_RegisterUpdatedDungeonMapTexture);
+ }
}
void KaleidoScope_Update(PlayState* play)
diff --git a/app/jni/src/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c b/app/jni/src/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c
index a73ca82..fec1d80 100644
--- a/app/jni/src/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c
+++ b/app/jni/src/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c
@@ -1762,6 +1762,13 @@ static MapMarkData sMapMarkJabuJabuBellyMq[] = {
} },
{ MAP_MARK_NONE, 0, { 0 } },
},
+ // Jabu-Jabu's Belly minimap 16
+ // SoH [General] - This entry corresponds to Big Octorok's room and is missing in the MQ game
+ // N64 hardware does an OoB read and lands on MQ Forest Temple room 0
+ // To avoid UB with OoB for SoH, the correct entry is now added below
+ {
+ { MAP_MARK_NONE, 0, { 0 } },
+ },
};
static MapMarkData sMapMarkForestTempleMq[] = {
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
index 228c277..79aa92c 100644
--- a/app/release/output-metadata.json
+++ b/app/release/output-metadata.json
@@ -11,8 +11,8 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
- "versionCode": 2,
- "versionName": "1.1.0",
+ "versionCode": 3,
+ "versionName": "1.1.1",
"outputFile": "app-release.apk"
}
],
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 133b51a..36e9bc3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,8 +4,8 @@
-->
diff --git a/app/src/main/assets/soh.otr b/app/src/main/assets/soh.otr
index dd4e4a2..f39c3fc 100644
Binary files a/app/src/main/assets/soh.otr and b/app/src/main/assets/soh.otr differ