diff --git a/.github/workflows/generate-builds.yml b/.github/workflows/generate-builds.yml index cd8a6fd9b0f..38cc4c9ad69 100644 --- a/.github/workflows/generate-builds.yml +++ b/.github/workflows/generate-builds.yml @@ -191,7 +191,7 @@ jobs: needs: generate-soh-otr runs-on: ${{ (vars.LINUX_RUNNER && fromJSON(vars.LINUX_RUNNER)) || 'ubuntu-latest' }} container: - image: devkitpro/devkita64:latest + image: devkitpro/devkita64:20240120 steps: - name: Install dependencies run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ae0360d8bd..2c8644af4a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,8 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") -project(Ship VERSION 8.0.3 LANGUAGES C CXX) -set(PROJECT_BUILD_NAME "MacReady Delta" CACHE STRING "") +project(Ship VERSION 8.0.5 LANGUAGES C CXX) +set(PROJECT_BUILD_NAME "MacReady Foxtrot" CACHE STRING "") set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "") set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh) diff --git a/libultraship b/libultraship index 59427a67bf9..96c8a8929c1 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit 59427a67bf9af060a4928bb72e3acce3b0782177 +Subproject commit 96c8a8929c18c1bffd7d92a35a589f74cf16fc59 diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index fd0c6ac250a..5c239ae132f 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -328,7 +328,7 @@ endif() include(FetchContent) FetchContent_Declare( Boost - URL https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.gz + URL https://archives.boost.io/release/1.81.0/source/boost_1_81_0.tar.gz URL_HASH SHA256=205666dea9f6a7cfed87c7a6dfbeb52a2c1b9de55712c9c1a87735d7181452b6 SOURCE_SUBDIR "null" # Set to a nonexistent directory so boost is not built (we don't need to build it) DOWNLOAD_EXTRACT_TIMESTAMP false # supress timestamp warning, not needed since the url wont change diff --git a/soh/include/z64.h b/soh/include/z64.h index 23ffcfb1717..f790dddc759 100644 --- a/soh/include/z64.h +++ b/soh/include/z64.h @@ -744,7 +744,6 @@ typedef struct { /* 0x0134 */ char** doActionSegment; /* 0x0138 */ u8* iconItemSegment; /* 0x013C */ char** mapSegment; - char** mapSegmentName; /* 0x0140 */ u8 mapPalette[32]; /* 0x0160 */ DmaRequest dmaRequest_160; /* 0x0180 */ DmaRequest dmaRequest_180; @@ -815,6 +814,10 @@ typedef struct { /* 0x026C */ u8 dinsNayrus; // "m_magic"; din's fire and nayru's love /* 0x026D */ u8 all; // "another"; enables all item restrictions } restrictions; + // #region SOH [General] + /* */ char* mapSegmentName[2]; // Tracks the map segment texture by OTR sig name + /* */ u8 mapPalettesPulse[40][32]; // Used to have unique pointers per map pulse color for the shader backend. 40 for map pulse timer x2 + // #endregion } InterfaceContext; // size = 0x270 typedef struct { diff --git a/soh/macosx/soh-macos.sh.in b/soh/macosx/soh-macos.sh.in index 0983f63b1b0..217496cf104 100755 --- a/soh/macosx/soh-macos.sh.in +++ b/soh/macosx/soh-macos.sh.in @@ -7,68 +7,6 @@ export RESPATH="${SNAME%/MacOS*}/Resources" export LIBPATH="${SNAME%/MacOS*}/Frameworks" export DYLD_FALLBACK_LIBRARY_PATH="$LIBPATH" -remap_hashes () -{ - # Remap v64 and n64 hashes to their z64 hash equivalent - # ZAPD will handle converting the data into z64 format - case "$ROMHASH" in - a9059b56e761c9034fbe02fe4c24985aaa835dac) # v64 - ROMHASH=cee6bc3c2a634b41728f2af8da54d9bf8cc14099 - ;; - 24708102dc504d3f375a37f4ae4e149c167dc515) # n64 - ROMHASH=cee6bc3c2a634b41728f2af8da54d9bf8cc14099 - ;; - 580dd0bd1b6d2c51cc20a764eece84dba558964c) # v64 - ROMHASH=0227d7c0074f2d0ac935631990da8ec5914597b4 - ;; - d6342c59007e57c1194661ec6880b2f078403f4e) # n64 - ROMHASH=0227d7c0074f2d0ac935631990da8ec5914597b4 - ;; - d0bdc2eb320668b4ba6893b9aefe4040a73123ff) # v64 - ROMHASH=328a1f1beba30ce5e178f031662019eb32c5f3b5 - ;; - 4946ab250f6ac9b32d76b21f309ebb8ebc8103d2) # n64 - ROMHASH=328a1f1beba30ce5e178f031662019eb32c5f3b5 - ;; - 663c34f1b2c05a09e5beffe4d0dcd440f7d49dc7) # v64 - ROMHASH=cfbb98d392e4a9d39da8285d10cbef3974c2f012 - ;; - 24c73d378b0620a380ce5ef9f2b186c6c157a68b) # n64 - ROMHASH=cfbb98d392e4a9d39da8285d10cbef3974c2f012 - ;; - 8ebf2e29313f44f2d49e5b4191971d09919e8e48) # v64 - ROMHASH=f46239439f59a2a594ef83cf68ef65043b1bffe2 - ;; - 4264bf7b875737b8fae77d52322a5099d051fc11) # n64 - ROMHASH=f46239439f59a2a594ef83cf68ef65043b1bffe2 - ;; - 973bc6fe56010a8d646166a1182a81b4f13b8cf9) # v64 - ROMHASH=50bebedad9e0f10746a52b07239e47fa6c284d03 - ;; - d327752c46edc70ff3668b9514083dbbee08927c) # v64 - ROMHASH=50bebedad9e0f10746a52b07239e47fa6c284d03 - ;; - ecdeb1747560834e079c22243febea7f6f26ba3b) # v64 - ROMHASH=079b855b943d6ad8bd1eb026c0ed169ecbdac7da - ;; - f19f8662ec7abee29484a272a6fda53e39efe0f1) # n64 - ROMHASH=079b855b943d6ad8bd1eb026c0ed169ecbdac7da - ;; - ab519ce04a33818ce2c39b3c514a751d807a494a) # v64 - ROMHASH=cfecfdc58d650e71a200c81f033de4e6d617a9f6 - ;; - c19a34f7646305e1755249fca2071e178bd7cd00) # n64 - ROMHASH=cfecfdc58d650e71a200c81f033de4e6d617a9f6 - ;; - 25e8ae79ea0839ca5c984473f7460d8040c36f9c) # v64 - ROMHASH=517bd9714c73cb96c21e7c2ef640d7b55186102f - ;; - 166c02770d67fcc3954c443eb400a6a3573d3fc0) # n64 - ROMHASH=517bd9714c73cb96c21e7c2ef640d7b55186102f - ;; - esac -} - if [ ! -e "$SHIP_HOME" ]; then mkdir "$SHIP_HOME"; fi if [ ! -e "$SHIP_HOME"/mods ]; then @@ -76,178 +14,6 @@ if [ ! -e "$SHIP_HOME"/mods ]; then touch "$SHIP_HOME"/mods/custom_otr_files_go_here.txt fi -# If either OTR doesn't exist kick off the OTR gen process -if [ ! -e "$SHIP_HOME"/oot.otr ] || [ ! -e "$SHIP_HOME"/oot-mq.otr ]; then - - # If no ROMs exist kick off the file selection prompts - while [ ! -e "$SHIP_HOME"/*.*64 ] && [ ! -e "$SHIP_HOME"/oot*.otr ]; do - - SHOULD_PROMPT_FOR_ROM=1 - while [ $SHOULD_PROMPT_FOR_ROM -eq 1 ]; do - SHOULD_PROMPT_FOR_ROM=0 - # Use osascript to prompt the user to chose a file - DROPROM=`osascript <<-EOF - set romFile to choose file of type {"b64","n64","v64","z64"} with prompt "Please select your ROM:" - return POSIX path of romFile - EOF` - - # If no rom was selected, the user cancelled, so exit - if [[ -z $DROPROM ]] && [[ -z "$UPLOAD_ANOTHER_RESULT" ]]; then - echo "No ROM selected. Exiting..." - exit 1 - elif [[ -z $DROPROM ]]; then - break; - fi - - # If an invalid rom was selected, let the user know and ask to try again - ROMHASH="$(shasum "$DROPROM" | awk '{ print $1 }')" - - remap_hashes - - case "$ROMHASH" in - cee6bc3c2a634b41728f2af8da54d9bf8cc14099) - ROM_TYPE=0;; - 0227d7c0074f2d0ac935631990da8ec5914597b4) - ROM_TYPE=0;; - 328a1f1beba30ce5e178f031662019eb32c5f3b5) - ROM_TYPE=0;; - cfbb98d392e4a9d39da8285d10cbef3974c2f012) - ROM_TYPE=0;; - f46239439f59a2a594ef83cf68ef65043b1bffe2) - ROM_TYPE=1;; - 50bebedad9e0f10746a52b07239e47fa6c284d03) - ROM_TYPE=1;; - 079b855b943d6ad8bd1eb026c0ed169ecbdac7da) - ROM_TYPE=1;; - cfecfdc58d650e71a200c81f033de4e6d617a9f6) - ROM_TYPE=1;; - 517bd9714c73cb96c21e7c2ef640d7b55186102f) - ROM_TYPE=1;; - *) - TRY_AGAIN_RESULT=`osascript <<-EOF - set alertText to "Incompatible ROM hash" - set alertMessage to "Incompatible ROM provided, would you like to try again?" - return display alert alertText \ - message alertMessage \ - as critical \ - buttons {"Cancel", "Try Again"} - EOF` - if [[ "$TRY_AGAIN_RESULT" == "button returned:Try Again" ]]; then - SHOULD_PROMPT_FOR_ROM=1 - continue; - else - echo "No ROM selected. Exiting..." - exit 1 - fi - esac - - cp "$DROPROM" "$SHIP_HOME" - - # Ask user if they would also like to select the other variant (MQ/Vanilla) - if [ $ROM_TYPE -eq 0 ] && [[ -z "$UPLOAD_ANOTHER_RESULT" ]]; then - UPLOAD_ANOTHER_RESULT=`osascript <<-EOF - set alertText to "Success" - set alertMessage to "Would you also like to provide a Master Quest ROM?" - return display alert alertText \ - message alertMessage \ - buttons {"No", "Yes"} - EOF` - elif [[ -z "$UPLOAD_ANOTHER_RESULT" ]]; then - UPLOAD_ANOTHER_RESULT=`osascript <<-EOF - set alertText to "Success" - set alertMessage to "Would you also like to provide a Vanilla (Non Master Quest) ROM?" - return display alert alertText \ - message alertMessage \ - buttons {"No", "Yes"} - EOF` - fi - - if [[ "$UPLOAD_ANOTHER_RESULT" == "button returned:Yes" ]]; then - UPLOAD_ANOTHER_RESULT="button returned:No" - SHOULD_PROMPT_FOR_ROM=1 - continue; - fi - break - done - done - - # At this point we should now have 1 or more valid roms in $SHIP_HOME directory - - # Prepare tmp dir - for ROMPATH in "$SHIP_HOME"/*.*64 - do - ASSETDIR="$(mktemp -d /tmp/assets-XXXXX)" - export ASSETDIR - cp -r "$RESPATH/assets" "$ASSETDIR" - mkdir -p "$ASSETDIR"/tmp - cp "$ROMPATH" "$ASSETDIR"/tmp/rom.z64 - cd "$ASSETDIR" || return - - # If an invalid rom was detected, let the user know - ROMHASH="$(shasum "$ASSETDIR"/tmp/rom.z64 | awk '{ print $1 }')" - - remap_hashes - - case "$ROMHASH" in - cee6bc3c2a634b41728f2af8da54d9bf8cc14099) - ROM=GC_NMQ_D - OTRNAME="oot.otr";; - 0227d7c0074f2d0ac935631990da8ec5914597b4) - ROM=GC_NMQ_PAL_F - OTRNAME="oot.otr";; - 328a1f1beba30ce5e178f031662019eb32c5f3b5) - ROM=N64_PAL_10 - OTRNAME="oot.otr";; - cfbb98d392e4a9d39da8285d10cbef3974c2f012) - ROM=N64_PAL_11 - OTRNAME="oot.otr";; - f46239439f59a2a594ef83cf68ef65043b1bffe2) - ROM=GC_MQ_PAL_F - OTRNAME="oot-mq.otr";; - 50bebedad9e0f10746a52b07239e47fa6c284d03) - ROM=GC_MQ_D - OTRNAME="oot-mq.otr";; - 079b855b943d6ad8bd1eb026c0ed169ecbdac7da) - ROM=GC_MQ_D - OTRNAME="oot-mq.otr";; - cfecfdc58d650e71a200c81f033de4e6d617a9f6) - ROM=GC_MQ_D - OTRNAME="oot-mq.otr";; - 517bd9714c73cb96c21e7c2ef640d7b55186102f) - ROM=GC_MQ_D - OTRNAME="oot-mq.otr";; - *) - osascript -e 'display notification "One or more invalid ROM provided" with title "Ship Of Harkinian"' - rm -r "$ASSETDIR" - cd "$SNAME" - continue; - esac - - # Only generate OTR if we don't have on of this type yet - if [ -e "$SHIP_HOME"/"$OTRNAME" ]; then - rm -r "$ASSETDIR" - cd "$SNAME" - continue; - fi - - osascript -e 'display notification "Generating OTR..." with title "Ship Of Harkinian"' - assets/extractor/ZAPD.out ed -i assets/extractor/xmls/"${ROM}" -b tmp/rom.z64 -fl assets/extractor/filelists -o placeholder -osf placeholder -gsf 1 -rconf assets/extractor/Config_"${ROM}".xml -se OTR --portVer "@CMAKE_PROJECT_VERSION@" - if [ -e "$ASSETDIR"/oot.otr ]; then - osascript -e 'display notification "OTR successfully generated" with title "Ship Of Harkinian"' - cp "$ASSETDIR"/oot.otr "$SHIP_HOME"/"$OTRNAME" - rm -r "$ASSETDIR" - cd "$SNAME" - fi - done - - if [ ! -e "$SHIP_HOME"/oot*.otr ]; then - osascript -e 'display notification "OTR failed to generate" with title "Ship Of Harkinian"' - exit 1; - fi -fi - -cd "$SNAME" - arch_name="$(uname -m)" launch_arch="arm64" if [ "${arch_name}" = "x86_64" ] && [ "$(sysctl -in sysctl.proc_translated)" != "1" ]; then diff --git a/soh/soh/Enhancements/controls/GameControlEditor.cpp b/soh/soh/Enhancements/controls/GameControlEditor.cpp index eb69f3cc852..976912b63a7 100644 --- a/soh/soh/Enhancements/controls/GameControlEditor.cpp +++ b/soh/soh/Enhancements/controls/GameControlEditor.cpp @@ -258,24 +258,28 @@ namespace GameControlEditor { window->EndGroupPanelPublic(0); UIWidgets::Spacer(0); - window->BeginGroupPanelPublic("Third-Person Camera", ImGui::GetContentRegionAvail()); + window->BeginGroupPanelPublic("Free Look/Third-person Camera", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Free Camera", "gFreeCamera"); - DrawHelpIcon("Enables free camera control\nNote: You must remap C buttons off of the right stick in the " + UIWidgets::PaddedEnhancementCheckbox("Enable Free Look", "gFreeCamera"); + DrawHelpIcon("Enables free look camera control\nNote: You must remap C buttons off of the right stick in the " "controller config menu, and map the camera stick to the right stick."); - UIWidgets::PaddedEnhancementCheckbox("Invert Camera X Axis", "gInvertXAxis"); - DrawHelpIcon("Inverts the Camera X Axis in:\n-Free camera"); - UIWidgets::PaddedEnhancementCheckbox("Invert Camera Y Axis", "gInvertYAxis", true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - DrawHelpIcon("Inverts the Camera Y Axis in:\n-Free camera"); + UIWidgets::PaddedEnhancementCheckbox("Invert X Axis", "gInvertXAxis"); + DrawHelpIcon("Inverts the Camera X Axis in:\n-Free Look"); + UIWidgets::PaddedEnhancementCheckbox("Invert Y Axis", "gInvertYAxis", true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); + DrawHelpIcon("Inverts the Camera Y Axis in:\n-Free Look"); UIWidgets::Spacer(0); - UIWidgets::PaddedEnhancementSliderFloat("Third-Person Horizontal Sensitivity: %d %%", "##ThirdPersonSensitivity Horizontal", + UIWidgets::PaddedEnhancementSliderFloat("Horizontal Sensitivity: %d %%", "##ThirdPersonSensitivity Horizontal", "gThirdPersonCameraSensitivityX", 0.01f, 5.0f, "", 1.0f, true, true, false, true); - UIWidgets::PaddedEnhancementSliderFloat("Third-Person Vertical Sensitivity: %d %%", "##ThirdPersonSensitivity Vertical", + DrawHelpIcon("Changes the sensitivity of the X axis control for Free Look"); + UIWidgets::PaddedEnhancementSliderFloat("Vertical Sensitivity: %d %%", "##ThirdPersonSensitivity Vertical", "gThirdPersonCameraSensitivityY", 0.01f, 5.0f, "", 1.0f, true, true, false, true); + DrawHelpIcon("Changes the sensitivity of the Y axis control for Free Look"); UIWidgets::PaddedEnhancementSliderInt("Camera Distance: %d", "##CamDist", "gFreeCameraDistMax", 100, 900, "", 185, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Camera Transition Speed: %d", "##CamTranSpeed", + DrawHelpIcon("How far the camera sits from Link while in Free Look mode"); + UIWidgets::PaddedEnhancementSliderInt("Transition Speed: %d", "##CamTranSpeed", "gFreeCameraTransitionSpeed", 0, 900, "", 25, true, false, true); + DrawHelpIcon("How quickly the camera changes to the distance specified above"); window->EndGroupPanelPublic(0); } diff --git a/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp b/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp index de97f3840b2..709854c3569 100644 --- a/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp +++ b/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp @@ -8,6 +8,7 @@ extern "C" { #include "objects/object_gi_soldout/object_gi_soldout.h" #include "objects/object_ik/object_ik.h" #include "objects/object_link_child/object_link_child.h" +#include "objects/object_ru2/object_ru2.h" uint32_t ResourceMgr_GameHasMasterQuest(); uint32_t ResourceMgr_GameHasOriginal(); @@ -187,10 +188,25 @@ void PatchIronKnuckleTextureOverflow() { } } +void PatchPrincessRutoEaring() { + // FAST3D: This is a hack for the issue of both TEXEL0 and TEXEL1 using the same texture with different settings. + // Ruto's earring uses both TEXEL0 and TEXEL1 to render. The issue is that it never loads anything into TEXEL1, so + // it reuses whatever happens to be there, which is the water temple brick texture. It just so happens that the + // earring texture loads into the same place in TMEM as the brick texture, so when it comes to rendering, TEXEL1 + // uses the earring texture with different clamp settings, and it displays without noticeable error. However, both + // texel samplers are not intended to be used for the same texture with different settings, so this misuse confuses + // our texture cache, and we load the wrong settings for the earrings texture. This patch is a hack that replaces + // TEXEL1 with TEXEL0, which is most likely the original intention, and all is well. + ResourceMgr_PatchGfxByName(gAdultRutoHeadDL, "RutoEaringTileFix", 162, + gsDPSetCombineLERP(TEXEL0, 0, PRIMITIVE, 0, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, COMBINED, + TEXEL0, 0, PRIM_LOD_FRAC, COMBINED)); +} + void ApplyAuthenticGfxPatches() { PatchDekuStickTextureOverflow(); PatchFreezardTextureOverflow(); PatchIronKnuckleTextureOverflow(); + PatchPrincessRutoEaring(); } // Patches the Sold Out GI DL to render the texture in the mirror boundary diff --git a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h index 916e1a7f822..d93bae98357 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/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/soh/soh/Enhancements/game-interactor/GameInteractor.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor.cpp index eb330947bbb..5a0d0efd302 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.cpp @@ -37,12 +37,19 @@ GameInteractionEffectQueryResult GameInteractor::RemoveEffect(GameInteractionEff // MARK: - Helpers -bool GameInteractor::IsSaveLoaded() { +bool GameInteractor::IsSaveLoaded(bool allowDbgSave) { Player* player; if (gPlayState != NULL) { player = GET_PLAYER(gPlayState); } - return (gPlayState == NULL || player == NULL || gSaveContext.fileNum < 0 || gSaveContext.fileNum > 2) ? false : true; + + // Checking for normal game mode prevents debug saves from reporting true on title screen + if (gPlayState == NULL || player == NULL || gSaveContext.gameMode != GAMEMODE_NORMAL) { + return false; + } + + // Valid save file or debug save + return (gSaveContext.fileNum >= 0 && gSaveContext.fileNum <= 2) || (allowDbgSave && gSaveContext.fileNum == 0xFF); } bool GameInteractor::IsGameplayPaused() { diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 809ccda0991..b56a001d206 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/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,10 +194,11 @@ class GameInteractor { DEFINE_HOOK(OnSetGameLanguage, void()); + DEFINE_HOOK(OnFileDropped, void(std::string filePath)); DEFINE_HOOK(OnAssetAltChange, void()); // Helpers - static bool IsSaveLoaded(); + static bool IsSaveLoaded(bool allowDbgSave = false); static bool IsGameplayPaused(); static bool CanSpawnActor(); static bool CanAddOrTakeAmmo(int16_t amount, int16_t item); diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index a6e5a47e84d..efcf44efb90 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/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/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 95503fda95f..258fbba4232 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -47,7 +47,7 @@ void ReloadSceneTogglingLinkAge() { void RegisterInfiniteMoney() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) return; + if (!GameInteractor::IsSaveLoaded(true)) return; if (CVarGetInteger("gInfiniteMoney", 0) != 0) { if (gSaveContext.rupees < CUR_CAPACITY(UPG_WALLET)) { gSaveContext.rupees = CUR_CAPACITY(UPG_WALLET); @@ -58,7 +58,7 @@ void RegisterInfiniteMoney() { void RegisterInfiniteHealth() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) return; + if (!GameInteractor::IsSaveLoaded(true)) return; if (CVarGetInteger("gInfiniteHealth", 0) != 0) { if (gSaveContext.health < gSaveContext.healthCapacity) { gSaveContext.health = gSaveContext.healthCapacity; @@ -69,7 +69,7 @@ void RegisterInfiniteHealth() { void RegisterInfiniteAmmo() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) return; + if (!GameInteractor::IsSaveLoaded(true)) return; if (CVarGetInteger("gInfiniteAmmo", 0) != 0) { // Deku Sticks if (AMMO(ITEM_STICK) < CUR_CAPACITY(UPG_STICKS)) { @@ -106,7 +106,7 @@ void RegisterInfiniteAmmo() { void RegisterInfiniteMagic() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) return; + if (!GameInteractor::IsSaveLoaded(true)) return; if (CVarGetInteger("gInfiniteMagic", 0) != 0) { if (gSaveContext.isMagicAcquired && gSaveContext.magic != (gSaveContext.isDoubleMagicAcquired + 1) * 0x30) { gSaveContext.magic = (gSaveContext.isDoubleMagicAcquired + 1) * 0x30; @@ -117,7 +117,7 @@ void RegisterInfiniteMagic() { void RegisterInfiniteNayrusLove() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) return; + if (!GameInteractor::IsSaveLoaded(true)) return; if (CVarGetInteger("gInfiniteNayru", 0) != 0) { gSaveContext.nayrusLoveTimer = 0x44B; } @@ -126,7 +126,7 @@ void RegisterInfiniteNayrusLove() { void RegisterMoonJumpOnL() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) return; + if (!GameInteractor::IsSaveLoaded(true)) return; if (CVarGetInteger("gMoonJumpOnL", 0) != 0) { Player* player = GET_PLAYER(gPlayState); @@ -141,7 +141,7 @@ void RegisterMoonJumpOnL() { void RegisterInfiniteISG() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) return; + if (!GameInteractor::IsSaveLoaded(true)) return; if (CVarGetInteger("gEzISG", 0) != 0) { Player* player = GET_PLAYER(gPlayState); @@ -153,7 +153,7 @@ void RegisterInfiniteISG() { //Permanent quick put away (QPA) glitched damage value void RegisterEzQPA() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) return; + if (!GameInteractor::IsSaveLoaded(true)) return; if (CVarGetInteger("gEzQPA", 0) != 0) { Player* player = GET_PLAYER(gPlayState); @@ -165,7 +165,7 @@ void RegisterEzQPA() { void RegisterUnrestrictedItems() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) return; + if (!GameInteractor::IsSaveLoaded(true)) return; if (CVarGetInteger("gNoRestrictItems", 0) != 0) { u8 sunsBackup = gPlayState->interfaceCtx.restrictions.sunsSong; @@ -193,11 +193,14 @@ void RegisterFreezeTime() { /// Switches Link's age and respawns him at the last entrance he entered. void RegisterSwitchAge() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) { + static bool warped = false; + + if (!GameInteractor::IsSaveLoaded(true)) { CVarClear("gSwitchAge"); + warped = false; return; } - static bool warped = false; + static Vec3f playerPos; static int16_t playerYaw; static RoomContext* roomCtx; @@ -231,7 +234,7 @@ void RegisterSwitchAge() { void RegisterOcarinaTimeTravel() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) { + if (!GameInteractor::IsSaveLoaded(true)) { CVarClear("gTimeTravel"); return; } @@ -1038,8 +1041,8 @@ void RegisterRandomizedEnemySizes() { uint8_t excludedEnemy = actor->id == ACTOR_EN_BROB || actor->id == ACTOR_EN_DHA || (actor->id == ACTOR_BOSS_SST && actor->params == -1); // Dodongo, Volvagia and Dead Hand are always smaller because they're impossible when bigger. - uint8_t smallOnlyEnemy = - actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || actor->id == ACTOR_BOSS_FD2 || ACTOR_EN_DH; + 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) { diff --git a/soh/soh/Enhancements/presets.cpp b/soh/soh/Enhancements/presets.cpp index 7b2ca659584..726863e0550 100644 --- a/soh/soh/Enhancements/presets.cpp +++ b/soh/soh/Enhancements/presets.cpp @@ -12,6 +12,14 @@ void clearCvars(std::vector cvarsToClear) { } } +std::string FormatLocations(std::vector locs) { + std::string locString = ""; + for (auto loc: locs) { + locString += std::to_string(loc) + ","; + } + return locString; +} + void applyPreset(std::vector entries) { for(auto& [cvar, type, value] : entries) { switch (type) { @@ -24,6 +32,9 @@ void applyPreset(std::vector entries) { case PRESET_ENTRY_TYPE_STRING: CVarSetString(cvar, std::get(value)); break; + case PRESET_ENTRY_TYPE_CPP_STRING: + CVarSetString(cvar, std::get(value).c_str()); + break; } } } diff --git a/soh/soh/Enhancements/presets.h b/soh/soh/Enhancements/presets.h index 29a8c428541..b19aec50ae1 100644 --- a/soh/soh/Enhancements/presets.h +++ b/soh/soh/Enhancements/presets.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -11,6 +12,7 @@ enum PresetEntryType { PRESET_ENTRY_TYPE_S32, PRESET_ENTRY_TYPE_FLOAT, PRESET_ENTRY_TYPE_STRING, + PRESET_ENTRY_TYPE_CPP_STRING, }; enum PresetType { @@ -36,15 +38,19 @@ enum RandomizerPreset { typedef struct PresetEntry { const char* cvar; PresetEntryType type; - std::variant value; + std::variant value; } PresetEntry; +std::string FormatLocations(std::vector locs); + #define PRESET_ENTRY_S32(cvar, value) \ { cvar, PRESET_ENTRY_TYPE_S32, value } #define PRESET_ENTRY_FLOAT(cvar, value) \ { cvar, PRESET_ENTRY_TYPE_FLOAT, value } #define PRESET_ENTRY_STRING(cvar, value) \ { cvar, PRESET_ENTRY_TYPE_STRING, value } +#define PRESET_ENTRY_CPP_STRING(cvar, value) \ + { cvar, PRESET_ENTRY_TYPE_CPP_STRING, value } void DrawPresetSelector(PresetType presetType); void clearCvars(std::vector cvarsToClear); @@ -867,7 +873,8 @@ const std::vector spockRacePresetEntries = { PRESET_ENTRY_S32("gRandomizeDampeHint", 1), PRESET_ENTRY_S32("gRandomizeDoorOfTime", RO_DOOROFTIME_OPEN), PRESET_ENTRY_S32("gRandomizeEnableBombchuDrops", 1), - PRESET_ENTRY_STRING("gRandomizeExcludedLocations", "78,143,144,229,"), + PRESET_ENTRY_CPP_STRING("gRandomizeExcludedLocations", FormatLocations( + { RC_MARKET_10_BIG_POES, RC_KAK_40_GOLD_SKULLTULA_REWARD, RC_KAK_50_GOLD_SKULLTULA_REWARD, RC_ZR_FROGS_OCARINA_GAME })), PRESET_ENTRY_S32("gRandomizeForest", RO_FOREST_OPEN), PRESET_ENTRY_S32("gRandomizeFullWallets", 1), PRESET_ENTRY_S32("gRandomizeGanonTrial", RO_GANONS_TRIALS_SKIP), @@ -959,7 +966,8 @@ const std::vector spockRaceNoLogicPresetEntries = { PRESET_ENTRY_S32("gRandomizeDampeHint", 1), PRESET_ENTRY_S32("gRandomizeDoorOfTime", RO_DOOROFTIME_OPEN), PRESET_ENTRY_S32("gRandomizeEnableBombchuDrops", 1), - PRESET_ENTRY_STRING("gRandomizeExcludedLocations", "78,143,144,229,"), + PRESET_ENTRY_CPP_STRING("gRandomizeExcludedLocations", FormatLocations( + { RC_MARKET_10_BIG_POES, RC_KAK_40_GOLD_SKULLTULA_REWARD, RC_KAK_50_GOLD_SKULLTULA_REWARD, RC_ZR_FROGS_OCARINA_GAME })), PRESET_ENTRY_S32("gRandomizeForest", RO_FOREST_OPEN), PRESET_ENTRY_S32("gRandomizeFullWallets", 1), PRESET_ENTRY_S32("gRandomizeGanonTrial", RO_GANONS_TRIALS_SKIP), @@ -1012,7 +1020,7 @@ const std::vector s6PresetEntries = { PRESET_ENTRY_S32("gRandomizeBigPoeTargetCount", 1), PRESET_ENTRY_S32("gRandomizeCuccosToReturn", 4), PRESET_ENTRY_S32("gRandomizeDoorOfTime", RO_DOOROFTIME_OPEN), - PRESET_ENTRY_STRING("gRandomizeExcludedLocations", "48,"), + PRESET_ENTRY_CPP_STRING("gRandomizeExcludedLocations", FormatLocations({ RC_DEKU_THEATER_MASK_OF_TRUTH })), PRESET_ENTRY_S32("gRandomizeForest", RO_FOREST_CLOSED_DEKU), PRESET_ENTRY_S32("gRandomizeGanonTrial", RO_GANONS_TRIALS_SKIP), PRESET_ENTRY_S32("gRandomizeGerudoFortress", RO_GF_FAST), diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp index 8988b045bd9..19960f1be87 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp +++ b/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/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_fire_temple.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_fire_temple.cpp index 440483af64f..ff2fb885c53 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_fire_temple.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_fire_temple.cpp @@ -40,7 +40,7 @@ void AreaTable_Init_FireTemple() { }, { //Exits Entrance(FIRE_TEMPLE_FIRST_ROOM, {[]{return true;}}), - Entrance(FIRE_TEMPLE_BOSS_ENTRYWAY, {[]{return BossKeyFireTemple && ((IsAdult && LogicFireBossDoorJump) || CanUse(HOVER_BOOTS) || Here(FIRE_TEMPLE_FIRE_MAZE_UPPER, []{return CanUse(MEGATON_HAMMER);}));}}), + Entrance(FIRE_TEMPLE_BOSS_ENTRYWAY, {[]{return BossKeyFireTemple && ((IsAdult && (LogicFireBossDoorJump || Here(FIRE_TEMPLE_FIRE_MAZE_UPPER, []{return CanUse(MEGATON_HAMMER);}))) || CanUse(HOVER_BOOTS));}}), }); areaTable[FIRE_TEMPLE_LOOP_ENEMIES] = Area("Fire Temple Loop Enemies", "Fire Temple", FIRE_TEMPLE, NO_DAY_NIGHT_CYCLE, {}, {}, { diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_forest_temple.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_forest_temple.cpp index fea9fda6f76..5e64aee6ce9 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_forest_temple.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_forest_temple.cpp @@ -172,7 +172,7 @@ void AreaTable_Init_ForestTemple() { Entrance(FOREST_TEMPLE_WEST_CORRIDOR, {[]{return true;}}), Entrance(FOREST_TEMPLE_NW_OUTDOORS_UPPER, {[]{return CanUse(HOVER_BOOTS) || (LogicForestOutsideBackdoor && CanJumpslash && GoronBracelet);}}), Entrance(FOREST_TEMPLE_NW_CORRIDOR_TWISTED, {[]{return IsAdult && GoronBracelet && SmallKeys(FOREST_TEMPLE, 2);}}), - Entrance(FOREST_TEMPLE_NW_CORRIDOR_STRAIGHTENED, {[]{return (CanUse(BOW) || CanUse(SLINGSHOT)) && GoronBracelet && SmallKeys(FOREST_TEMPLE, 2);}}), + Entrance(FOREST_TEMPLE_NW_CORRIDOR_STRAIGHTENED, {[]{return IsAdult && (CanUse(BOW) || CanUse(SLINGSHOT)) && GoronBracelet && SmallKeys(FOREST_TEMPLE, 2);}}), }); areaTable[FOREST_TEMPLE_NW_CORRIDOR_TWISTED] = Area("Forest Temple NW Corridor Twisted", "Forest Temple", FOREST_TEMPLE, NO_DAY_NIGHT_CYCLE, {}, {}, { diff --git a/soh/soh/Enhancements/randomizer/3drando/logic.cpp b/soh/soh/Enhancements/randomizer/3drando/logic.cpp index 580c687d096..df127e69931 100644 --- a/soh/soh/Enhancements/randomizer/3drando/logic.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/logic.cpp @@ -536,7 +536,7 @@ namespace Logic { Fish = HasBottle && FishAccess; Fairy = HasBottle && FairyAccess; - FoundBombchus = (BombchuDrop || Bombchus || Bombchus5 || Bombchus10 || Bombchus20); + FoundBombchus = (BombchuDrop || Bombchus || Bombchus5 || Bombchus10 || Bombchus20) && (BombBag || BombchusInLogic); CanPlayBowling = (BombchusInLogic && FoundBombchus) || (!BombchusInLogic && BombBag); HasBombchus = (BuyBombchus10 || BuyBombchus20 || (AmmoDrops.Is(AMMODROPS_BOMBCHU) && FoundBombchus)); diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 7f261393516..835621fd35f 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -364,31 +364,16 @@ std::unordered_map SpoilerfileSettingNameToEn #pragma GCC push_options #pragma GCC optimize ("O0") bool Randomizer::SpoilerFileExists(const char* spoilerFileName) { - try { - if (strcmp(spoilerFileName, "") != 0) { - std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName)); - if (!spoilerFileStream) { - return false; - } - - json spoilerFileJson; - spoilerFileStream >> spoilerFileJson; - - if (!spoilerFileJson.contains("version") || !spoilerFileJson.contains("finalSeed")) { - return false; - } - + if (strcmp(spoilerFileName, "") != 0) { + std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName)); + if (!spoilerFileStream) { + return false; + } else { return true; } - - return false; - } catch (std::exception& e) { - SPDLOG_ERROR("Error checking if spoiler file exists: {}", e.what()); - return false; - } catch (...) { - SPDLOG_ERROR("Error checking if spoiler file exists"); - return false; } + + return false; } #pragma GCC pop_options #pragma optimize("", on) @@ -494,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!", @@ -4215,7 +4207,7 @@ void RandomizerSettingsWindow::DrawElement() { break; case RO_LACS_GREG_REWARD: UIWidgets::PaddedEnhancementSliderInt("Stone Count: %d", "##RandoLacsStoneCount", - "gRandomizeLacsStoneCount", 1, 4, "", 4, true, true, false); + "gRandomizeLacsStoneCount", 1, 4, "", 3, true, true, false); break; case RO_LACS_WILDCARD_REWARD: UIWidgets::PaddedEnhancementSliderInt("Stone Count: %d", "##RandoLacsStoneCount", @@ -4244,7 +4236,7 @@ void RandomizerSettingsWindow::DrawElement() { break; case RO_LACS_GREG_REWARD: UIWidgets::PaddedEnhancementSliderInt("Medallion Count: %d", "##RandoLacsMedallionCount", - "gRandomizeLacsMedallionCount", 1, 7, "", 7, true, true, false); + "gRandomizeLacsMedallionCount", 1, 7, "", 6, true, true, false); break; case RO_LACS_WILDCARD_REWARD: UIWidgets::PaddedEnhancementSliderInt("Medallion Count: %d", "##RandoLacsMedallionCount", @@ -4273,7 +4265,7 @@ void RandomizerSettingsWindow::DrawElement() { break; case RO_LACS_GREG_REWARD: UIWidgets::PaddedEnhancementSliderInt("Reward Count: %d", "##RandoLacsRewardCount", - "gRandomizeLacsRewardCount", 1, 10, "", 10, true, true, false); + "gRandomizeLacsRewardCount", 1, 10, "", 9, true, true, false); break; case RO_LACS_WILDCARD_REWARD: UIWidgets::PaddedEnhancementSliderInt("Reward Count: %d", "##RandoLacsRewardCount", @@ -4302,7 +4294,7 @@ void RandomizerSettingsWindow::DrawElement() { break; case RO_LACS_GREG_REWARD: UIWidgets::PaddedEnhancementSliderInt("Dungeon Count: %d", "##RandoLacsDungeonCount", - "gRandomizeLacsDungeonCount", 1, 9, "", 9, true, true, false); + "gRandomizeLacsDungeonCount", 1, 9, "", 8, true, true, false); break; case RO_LACS_WILDCARD_REWARD: UIWidgets::PaddedEnhancementSliderInt("Dungeon Count: %d", "##RandoLacsDungeonCount", @@ -4697,7 +4689,11 @@ void RandomizerSettingsWindow::DrawElement() { excludedLocationString += std::to_string(excludedLocationIt); excludedLocationString += ","; } - CVarSetString("gRandomizeExcludedLocations", excludedLocationString.c_str()); + if (excludedLocationString == "") { + CVarClear("gRandomizeExcludedLocations"); + } else { + CVarSetString("gRandomizeExcludedLocations", excludedLocationString.c_str()); + } LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } ImGui::SameLine(); @@ -4874,7 +4870,7 @@ void RandomizerSettingsWindow::DrawElement() { enabledTrickString += std::to_string(enabledTrickIt); enabledTrickString += ","; } - CVarSetString("gRandomizeEnabledTricks", enabledTrickString.c_str()); + CVarClear("gRandomizeEnabledTricks"); LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } ImGui::SameLine(); @@ -5078,7 +5074,7 @@ void RandomizerSettingsWindow::DrawElement() { enabledTrickString += std::to_string(enabledTrickIt); enabledTrickString += ","; } - CVarSetString("gRandomizeEnabledTricks", enabledTrickString.c_str()); + CVarClear("gRandomizeEnabledTricks"); LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } @@ -5116,7 +5112,11 @@ void RandomizerSettingsWindow::DrawElement() { enabledTrickString += std::to_string(enabledTrickIt); enabledTrickString += ","; } - CVarSetString("gRandomizeEnabledTricks", enabledTrickString.c_str()); + if (enabledTrickString == "") { + CVarClear("gRandomizeEnabledTricks"); + } else { + CVarSetString("gRandomizeEnabledTricks", enabledTrickString.c_str()); + } LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } DrawTagChips(*rtObject.rtTags); diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index 003c8c135a9..b9ca97a9b2a 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -78,6 +78,7 @@ bool initialized; bool doAreaScroll; bool previousShowHidden = false; bool hideShopRightChecks = true; +bool alwaysShowGS = false; std::map startingShopItem = { { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 }, { SCENE_BAZAAR, RC_MARKET_BAZAAR_ITEM_1 }, @@ -106,8 +107,9 @@ std::map> checksByArea; bool areasFullyChecked[RCAREA_INVALID]; u32 areasSpoiled = 0; bool showVOrMQ; -s8 areaChecksGotten[32]; //| "Kokiri Forest (4/9)" -bool optCollapseAll; // A bool that will collapse all checks once +s8 areaChecksGotten[RCAREA_INVALID]; //| "Kokiri Forest (4/9)" +s8 areaCheckTotals[RCAREA_INVALID]; +bool optCollapseAll; // A bool that will collapse all checks once bool optExpandAll; // A bool that will expand all checks once RandomizerCheck lastLocationChecked = RC_UNKNOWN_CHECK; RandomizerCheckArea previousArea = RCAREA_INVALID; @@ -224,6 +226,26 @@ void TrySetAreas() { } } +void RecalculateAreaTotals() { + for (auto [rcArea, rcObjects] : checksByArea) { + if (rcArea == RCAREA_INVALID) { + return; + } + areaChecksGotten[rcArea] = 0; + areaCheckTotals[rcArea] = 0; + for (auto rcObj : rcObjects) { + if (!IsVisibleInCheckTracker(rcObj)) { + continue; + } + areaCheckTotals[rcArea]++; + if (gSaveContext.checkTrackerData[rcObj.rc].skipped || gSaveContext.checkTrackerData[rcObj.rc].status == RCSHOW_COLLECTED + || gSaveContext.checkTrackerData[rcObj.rc].status == RCSHOW_SAVED) { + areaChecksGotten[rcArea]++; + } + } + } +} + void SetCheckCollected(RandomizerCheck rc) { gSaveContext.checkTrackerData[rc].status = RCSHOW_COLLECTED; RandomizerCheckObject rcObj; @@ -232,10 +254,12 @@ void SetCheckCollected(RandomizerCheck rc) { } else { rcObj = RandomizerCheckObjects::GetAllRCObjects().find(rc)->second; } - if (!gSaveContext.checkTrackerData[rc].skipped) { - areaChecksGotten[rcObj.rcArea]++; - } else { - gSaveContext.checkTrackerData[rc].skipped = false; + if (IsVisibleInCheckTracker(rcObj)) { + if (!gSaveContext.checkTrackerData[rc].skipped) { + areaChecksGotten[rcObj.rcArea]++; + } else { + gSaveContext.checkTrackerData[rc].skipped = false; + } } SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); @@ -345,6 +369,7 @@ void ClearAreaChecksAndTotals() { for (auto& [rcArea, vec] : checksByArea) { vec.clear(); areaChecksGotten[rcArea] = 0; + areaCheckTotals[rcArea] = 0; } } @@ -424,9 +449,9 @@ void CheckTrackerLoadGame(int32_t fileNum) { TrySetAreas(); for (auto [rc, rcObj] : RandomizerCheckObjects::GetAllRCObjects()) { RandomizerCheckTrackerData rcTrackerData = gSaveContext.checkTrackerData[rc]; - if (rc == RC_UNKNOWN_CHECK || rc == RC_MAX || rc == RC_LINKS_POCKET || - !RandomizerCheckObjects::GetAllRCObjects().contains(rc)) + if (rc == RC_UNKNOWN_CHECK || rc == RC_MAX || rc == RC_LINKS_POCKET || !RandomizerCheckObjects::GetAllRCObjects().contains(rc)) { continue; + } RandomizerCheckObject realRcObj; if (rc == RC_GIFT_FROM_SAGES && !IS_RANDO) { @@ -434,11 +459,13 @@ 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) { - areaChecksGotten[realRcObj.rcArea]++; + if (IsVisibleInCheckTracker(realRcObj)) { + areaCheckTotals[realRcObj.rcArea]++; + if (rcTrackerData.status == RCSHOW_COLLECTED || rcTrackerData.status == RCSHOW_SAVED || rcTrackerData.skipped) { + areaChecksGotten[realRcObj.rcArea]++; + } } if (areaChecksGotten[realRcObj.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(realRcObj.rcArea)) { @@ -463,6 +490,7 @@ void CheckTrackerLoadGame(int32_t fileNum) { checksByArea.find(startingArea)->second.push_back(linksPocket); areaChecksGotten[startingArea]++; + areaCheckTotals[startingArea]++; } showVOrMQ = (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_RANDOM_NUMBER || @@ -783,9 +811,13 @@ void Teardown() { void UpdateCheck(uint32_t check, RandomizerCheckTrackerData data) { auto area = RandomizerCheckObjects::GetAllRCObjects().find(static_cast(check))->second.rcArea; - if (!gSaveContext.checkTrackerData[check].skipped && data.skipped) { + if ((!gSaveContext.checkTrackerData[check].skipped && data.skipped) || + ((gSaveContext.checkTrackerData[check].status != RCSHOW_COLLECTED && gSaveContext.checkTrackerData[check].status != RCSHOW_SAVED) && + (data.status == RCSHOW_COLLECTED || data.status == RCSHOW_SAVED))) { areaChecksGotten[area]++; - } else if (gSaveContext.checkTrackerData[check].skipped && !data.skipped) { + } else if ((gSaveContext.checkTrackerData[check].skipped && !data.skipped) || + ((gSaveContext.checkTrackerData[check].status == RCSHOW_COLLECTED || gSaveContext.checkTrackerData[check].status == RCSHOW_SAVED) && + (data.status != RCSHOW_COLLECTED && data.status != RCSHOW_SAVED))) { areaChecksGotten[area]--; } gSaveContext.checkTrackerData[check] = data; @@ -898,8 +930,7 @@ void CheckTrackerWindow::DrawElement() { for (auto& [rcArea, objs] : checksByArea) { RandomizerCheckArea thisArea = currentArea; - const int areaChecksTotal = static_cast(objs.size()); - thisAreaFullyChecked = (areaChecksGotten[rcArea] == areaChecksTotal); + thisAreaFullyChecked = (areaChecksGotten[rcArea] == areaCheckTotals[rcArea]); //Last Area needs to be cleaned up if (lastArea != RCAREA_INVALID && doDraw) { UIWidgets::PaddedSeparator(); @@ -936,10 +967,11 @@ void CheckTrackerWindow::DrawElement() { stemp = RandomizerCheckObjects::GetRCAreaName(rcArea) + "##TreeNode"; ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(mainColor.r / 255.0f, mainColor.g / 255.0f, mainColor.b / 255.0f, mainColor.a / 255.0f)); - if (doingCollapseOrExpand) + if (doingCollapseOrExpand) { ImGui::SetNextItemOpen(collapseLogic, ImGuiCond_Always); - else + } else { ImGui::SetNextItemOpen(!thisAreaFullyChecked, ImGuiCond_Once); + } doDraw = ImGui::TreeNode(stemp.c_str()); ImGui::PopStyleColor(); ImGui::SameLine(); @@ -954,12 +986,14 @@ void CheckTrackerWindow::DrawElement() { if (isThisAreaSpoiled) { if (showVOrMQ && RandomizerCheckObjects::AreaIsDungeon(rcArea)) { - if (OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(DungeonSceneLookupByArea(rcArea))) - ImGui::Text("(%d/%d) - MQ", areaChecksGotten[rcArea], areaChecksTotal); - else - ImGui::Text("(%d/%d) - Vanilla", areaChecksGotten[rcArea], areaChecksTotal); + if (OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains( + DungeonSceneLookupByArea(rcArea))) { + ImGui::Text("(%d/%d) - MQ", areaChecksGotten[rcArea], areaCheckTotals[rcArea]); + } else { + ImGui::Text("(%d/%d) - Vanilla", areaChecksGotten[rcArea], areaCheckTotals[rcArea]); + } } else { - ImGui::Text("(%d/%d)", areaChecksGotten[rcArea], areaChecksTotal); + ImGui::Text("(%d/%d)", areaChecksGotten[rcArea], areaCheckTotals[rcArea]); } } else { ImGui::Text("???"); @@ -973,11 +1007,13 @@ void CheckTrackerWindow::DrawElement() { doAreaScroll = false; } for (auto rco : objs) { - if (doDraw && isThisAreaSpoiled && IsVisibleInCheckTracker(rco)) + if (IsVisibleInCheckTracker(rco) && doDraw && isThisAreaSpoiled) { DrawLocation(rco); + } } - if (doDraw) + if (doDraw) { ImGui::TreePop(); + } } areaMask <<= 1; } @@ -1084,7 +1120,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)) { @@ -1148,7 +1183,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)) ) && @@ -1197,8 +1232,9 @@ void UpdateAreaFullyChecked(RandomizerCheckArea area) { void UpdateAreas(RandomizerCheckArea area) { areasFullyChecked[area] = areaChecksGotten[area] == checksByArea.find(area)->second.size(); - if (areaChecksGotten[area] != 0 || RandomizerCheckObjects::AreaIsOverworld(area)) + if (areaChecksGotten[area] != 0 || RandomizerCheckObjects::AreaIsOverworld(area)) { areasSpoiled |= (1 << area); + } } void UpdateAllOrdering() { @@ -1226,30 +1262,36 @@ bool CompareChecks(RandomizerCheckObject i, RandomizerCheckObject j) { bool iSaved = iShow.status == RCSHOW_SAVED; bool jCollected = jShow.status == RCSHOW_COLLECTED || jShow.status == RCSHOW_SAVED; bool jSaved = jShow.status == RCSHOW_SAVED; - if (!iCollected && jCollected) + + if (!iCollected && jCollected) { return true; - else if (iCollected && !jCollected) + } else if (iCollected && !jCollected) { return false; + } - if (!iSaved && jSaved) + if (!iSaved && jSaved) { return true; - else if (iSaved && !jSaved) + } else if (iSaved && !jSaved) { return false; + } - if (!iShow.skipped && jShow.skipped) + if (!iShow.skipped && jShow.skipped) { return true; - else if (iShow.skipped && !jShow.skipped) + } else if (iShow.skipped && !jShow.skipped) { return false; + } - if (!IsEoDCheck(i.rcType) && IsEoDCheck(j.rcType)) + if (!IsEoDCheck(i.rcType) && IsEoDCheck(j.rcType)) { return true; - else if (IsEoDCheck(i.rcType) && !IsEoDCheck(j.rcType)) + } else if (IsEoDCheck(i.rcType) && !IsEoDCheck(j.rcType)) { return false; + } - if (i.rc < j.rc) + if (i.rc < j.rc) { return true; - else if (i.rc > j.rc) + } else if (i.rc > j.rc) { return false; + } return false; } @@ -1267,47 +1309,54 @@ void DrawLocation(RandomizerCheckObject rcObj) { RandomizerCheckStatus status = checkData.status; bool skipped = checkData.skipped; if (status == RCSHOW_COLLECTED) { - if (!showHidden && CVarGetInteger("gCheckTrackerCollectedHide", 0)) + if (!showHidden && CVarGetInteger("gCheckTrackerCollectedHide", 0)) { return; + } mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerCollectedExtraColor", Color_Collected_Extra_Default) : - CVarGetColor("gCheckTrackerCollectedMainColor", Color_Main_Default); + CVarGetColor("gCheckTrackerCollectedMainColor", Color_Main_Default); extraColor = CVarGetColor("gCheckTrackerCollectedExtraColor", Color_Collected_Extra_Default); } else if (status == RCSHOW_SAVED) { - if (!showHidden && CVarGetInteger("gCheckTrackerSavedHide", 0)) + if (!showHidden && CVarGetInteger("gCheckTrackerSavedHide", 0)) { return; - mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerSavedExtraColor", Color_Saved_Extra_Default) : - CVarGetColor("gCheckTrackerSavedMainColor", Color_Main_Default); + } + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerSavedExtraColor", Color_Saved_Extra_Default) : + CVarGetColor("gCheckTrackerSavedMainColor", Color_Main_Default); extraColor = CVarGetColor("gCheckTrackerSavedExtraColor", Color_Saved_Extra_Default); } else if (skipped) { - if (!showHidden && CVarGetInteger("gCheckTrackerSkippedHide", 0)) + if (!showHidden && CVarGetInteger("gCheckTrackerSkippedHide", 0)) { return; - mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerSkippedExtraColor", Color_Skipped_Extra_Default) : - CVarGetColor("gCheckTrackerSkippedMainColor", Color_Main_Default); + } + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerSkippedExtraColor", Color_Skipped_Extra_Default) : + CVarGetColor("gCheckTrackerSkippedMainColor", Color_Main_Default); extraColor = CVarGetColor("gCheckTrackerSkippedExtraColor", Color_Skipped_Extra_Default); } else if (status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED) { - if (!showHidden && CVarGetInteger("gCheckTrackerSeenHide", 0)) + if (!showHidden && CVarGetInteger("gCheckTrackerSeenHide", 0)) { return; - mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerSeenExtraColor", Color_Seen_Extra_Default) : - CVarGetColor("gCheckTrackerSeenMainColor", Color_Main_Default); + } + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerSeenExtraColor", Color_Seen_Extra_Default) : + CVarGetColor("gCheckTrackerSeenMainColor", Color_Main_Default); extraColor = CVarGetColor("gCheckTrackerSeenExtraColor", Color_Seen_Extra_Default); } else if (status == RCSHOW_SCUMMED) { - if (!showHidden && CVarGetInteger("gCheckTrackerKnownHide", 0)) + if (!showHidden && CVarGetInteger("gCheckTrackerScummedHide", 0)) { return; - mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerScummedExtraColor", Color_Scummed_Extra_Default) : - CVarGetColor("gCheckTrackerScummedMainColor", Color_Main_Default); + } + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerScummedExtraColor", Color_Scummed_Extra_Default) : + CVarGetColor("gCheckTrackerScummedMainColor", Color_Main_Default); extraColor = CVarGetColor("gCheckTrackerScummedExtraColor", Color_Scummed_Extra_Default); } else if (status == RCSHOW_UNCHECKED) { - if (!showHidden && CVarGetInteger("gCheckTrackerUncheckedHide", 0)) + if (!showHidden && CVarGetInteger("gCheckTrackerUncheckedHide", 0)) { return; - mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerUncheckedExtraColor", Color_Unchecked_Extra_Default) : - CVarGetColor("gCheckTrackerUncheckedMainColor", Color_Main_Default); + } + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerUncheckedExtraColor", Color_Unchecked_Extra_Default) : + CVarGetColor("gCheckTrackerUncheckedMainColor", Color_Main_Default); extraColor = CVarGetColor("gCheckTrackerUncheckedExtraColor", Color_Unchecked_Extra_Default); } //Main Text txt = rcObj.rcShortName; - if (lastLocationChecked == rcObj.rc) + if (lastLocationChecked == rcObj.rc) { txt = "* " + txt; + } // Draw button - for Skipped/Seen/Scummed/Unchecked only if (status == RCSHOW_UNCHECKED || status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED || status == RCSHOW_SCUMMED || skipped) { @@ -1382,8 +1431,9 @@ void DrawLocation(RandomizerCheckObject rcObj) { break; } } - if (txt == "" && skipped) - txt = "Skipped"; //TODO language + if (txt == "" && skipped) { + txt = "Skipped"; // TODO language + } if (txt != "") { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(extraColor.r / 255.0f, extraColor.g / 255.0f, extraColor.b / 255.0f, extraColor.a / 255.0f)); @@ -1409,8 +1459,9 @@ int hue = 0; void RainbowTick() { float freqHue = hue * 2 * M_PI / (360 * CVarGetFloat("gCosmetics.RainbowSpeed", 0.6f)); for (auto& cvar : rainbowCVars) { - if (CVarGetInteger((cvar + "RBM").c_str(), 0) == 0) + if (CVarGetInteger((cvar + "RBM").c_str(), 0) == 0) { continue; + } Color_RGBA8 newColor; newColor.r = sin(freqHue + 0) * 127 + 128; @@ -1517,8 +1568,16 @@ 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."); + if (UIWidgets::EnhancementCheckbox("Hide right-side shop item checks", "gCheckTrackerOptionHideRightShopChecks", false, "", UIWidgets::CheckboxGraphics::Cross, true)) { + hideShopRightChecks = !hideShopRightChecks; + RecalculateAreaTotals(); + } + UIWidgets::Tooltip("If enabled, will prevent the tracker from displaying slots 1-4 in all shops."); + if (UIWidgets::EnhancementCheckbox("Always show gold skulltulas", "gCheckTrackerOptionAlwaysShowGSLocs", false, "")) { + alwaysShowGS = !alwaysShowGS; + RecalculateAreaTotals(); + } + UIWidgets::Tooltip("If enabled, will show GS locations in the tracker regardless of tokensanity settings."); ImGui::TableNextColumn(); @@ -1572,6 +1631,9 @@ void CheckTrackerWindow::InitElement() { GameInteractor::Instance->RegisterGameHook(CheckTrackerSceneFlagSet); GameInteractor::Instance->RegisterGameHook(CheckTrackerFlagSet); + hideShopRightChecks = CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1); + alwaysShowGS = CVarGetInteger("gCheckTrackerOptionAlwaysShowGSLocs", 0); + LocationTable_Init(); } diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.c b/soh/soh/Enhancements/randomizer/randomizer_entrance.c index e6ec3fef3fa..eda97647345 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance.c +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.c @@ -398,6 +398,11 @@ void Entrance_SetSavewarpEntrance(void) { gSaveContext.entranceIndex = 0x0486; // Gerudo Fortress -> Thieve's Hideout spawn 0 } else if (scene == SCENE_LINKS_HOUSE) { gSaveContext.entranceIndex = Entrance_OverrideNextIndex(LINK_HOUSE_SAVEWARP_ENTRANCE); + } else if (CVarGetInteger("gRememberSaveLocation", 0) && scene != SCENE_FAIRYS_FOUNTAIN && scene != SCENE_GROTTOS && + // Use the saved entrance value with remember save location, except when in grottos/fairy fountains or if + // the entrance index is -1 (new save) + gSaveContext.entranceIndex != -1) { + return; } else if (LINK_IS_CHILD) { gSaveContext.entranceIndex = Entrance_OverrideNextIndex(LINK_HOUSE_SAVEWARP_ENTRANCE); // Child Overworld Spawn } else { diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index f24ab818b9d..f63b37b628b 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -30,6 +30,8 @@ void DrawBottle(ItemTrackerItem item); void DrawQuest(ItemTrackerItem item); void DrawSong(ItemTrackerItem item); +int itemTrackerSectionId; + bool shouldUpdateVectors = true; std::vector mainWindowItems = {}; @@ -282,11 +284,6 @@ void ItemTrackerOnFrame() { } } -void SaveNotes(uint32_t fileNum) { - CVarSetString(("gItemTrackerNotes" + std::to_string(fileNum)).c_str(), std::string(std::begin(itemTrackerNotes), std::end(itemTrackerNotes)).c_str()); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); -} - bool IsValidSaveFile() { bool validSave = gSaveContext.fileNum >= 0 && gSaveContext.fileNum <= 2; return validSave; @@ -734,7 +731,7 @@ void DrawNotes(bool resizeable = false) { } if ((ImGui::IsItemDeactivatedAfterEdit() || (notesNeedSave && notesIdleFrames > notesMaxIdleFrames)) && IsValidSaveFile()) { notesNeedSave = false; - SaveNotes(gSaveContext.fileNum); + SaveManager::Instance->SaveSection(gSaveContext.fileNum, itemTrackerSectionId, true); } ImGui::EndGroup(); } @@ -959,6 +956,26 @@ void UpdateVectors() { shouldUpdateVectors = false; } +void ItemTrackerInitFile(bool isDebug) { + itemTrackerNotes.clear(); + itemTrackerNotes.push_back(0); +} + +void ItemTrackerSaveFile(SaveContext* saveContext, int sectionID, bool fullSave) { + SaveManager::Instance->SaveData("personalNotes", std::string(std::begin(itemTrackerNotes), std::end(itemTrackerNotes)).c_str()); +} + +void ItemTrackerLoadFile() { + std::string initialTrackerNotes = ""; + SaveManager::Instance->LoadData("personalNotes", initialTrackerNotes); + itemTrackerNotes.resize(initialTrackerNotes.length() + 1); + if (initialTrackerNotes != "") { + SohUtils::CopyStringToCharArray(itemTrackerNotes.Data, initialTrackerNotes.c_str(), itemTrackerNotes.size()); + } else { + itemTrackerNotes.push_back(0); + } +} + void ItemTrackerWindow::DrawElement() { UpdateVectors(); @@ -1223,14 +1240,9 @@ void ItemTrackerWindow::InitElement() { itemTrackerNotes.push_back(0); } - GameInteractor::Instance->RegisterGameHook([](uint32_t fileNum) { - const char* initialTrackerNotes = CVarGetString(("gItemTrackerNotes" + std::to_string(fileNum)).c_str(), ""); - itemTrackerNotes.resize(strlen(initialTrackerNotes) + 1); - strcpy(itemTrackerNotes.Data, initialTrackerNotes); - }); - GameInteractor::Instance->RegisterGameHook([](uint32_t fileNum) { - CVarSetString(("gItemTrackerNotes" + std::to_string(fileNum)).c_str(), ""); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - }); + SaveManager::Instance->AddInitFunction(ItemTrackerInitFile); + itemTrackerSectionId = SaveManager::Instance->AddSaveFunction("itemTrackerData", 1, ItemTrackerSaveFile, true, -1); + SaveManager::Instance->AddLoadFunction("itemTrackerData", 1, ItemTrackerLoadFile); + GameInteractor::Instance->RegisterGameHook(ItemTrackerOnFrame); } diff --git a/soh/soh/Enhancements/randomizer/savefile.cpp b/soh/soh/Enhancements/randomizer/savefile.cpp index 33278d0b757..fb04eca0c61 100644 --- a/soh/soh/Enhancements/randomizer/savefile.cpp +++ b/soh/soh/Enhancements/randomizer/savefile.cpp @@ -207,6 +207,9 @@ extern "C" void Randomizer_InitSaveFile() { gSaveContext.randomizerInf[i] = 0; } + // Reset triforce pieces collected + gSaveContext.triforcePiecesCollected = 0; + gSaveContext.cutsceneIndex = 0; // no intro cutscene // Starts pending ice traps out at 0 before potentially incrementing them down the line. gSaveContext.pendingIceTrapCount = 0; @@ -442,8 +445,5 @@ extern "C" void Randomizer_InitSaveFile() { gSaveContext.itemGetInf[3] |= 0x8000; // Obtained Mask of Truth } - // Reset triforce pieces collected - gSaveContext.triforcePiecesCollected = 0; - SetStartingItems(); } diff --git a/soh/soh/Enhancements/tts/tts.cpp b/soh/soh/Enhancements/tts/tts.cpp index 0b46cd18fcd..9e5ff4a42ae 100644 --- a/soh/soh/Enhancements/tts/tts.cpp +++ b/soh/soh/Enhancements/tts/tts.cpp @@ -168,7 +168,7 @@ void RegisterOnInterfaceUpdateHook() { prevTimer = timer; - if (!GameInteractor::IsSaveLoaded()) return; + if (!GameInteractor::IsSaveLoaded(true)) return; static int16_t lostHealth = 0; static int16_t prevHealth = 0; diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 6372de06080..64d70da8806 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -118,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; @@ -295,6 +297,7 @@ OTRGlobals::OTRGlobals() { }; // tell LUS to reserve 3 SoH specific threads (Game, Audio, Save) context = LUS::Context::CreateInstance("Ship of Harkinian", appShortName, "shipofharkinian.json", OTRFiles, {}, 3); + SPDLOG_INFO("Starting Ship of Harkinian version {}", (char*)gBuildVersion); context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::SOH_Animation, "Animation", std::make_shared()); context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::SOH_PlayerAnimation, "PlayerAnimation", std::make_shared()); @@ -1035,9 +1038,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__ @@ -1057,6 +1060,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); @@ -1136,8 +1144,7 @@ extern "C" uint64_t GetUnixTimestamp() { auto time = std::chrono::system_clock::now(); auto since_epoch = time.time_since_epoch(); auto millis = std::chrono::duration_cast(since_epoch); - long now = millis.count(); - return now; + return (uint64_t)millis.count(); } // C->C++ Bridge @@ -1145,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__ @@ -1228,14 +1235,21 @@ extern "C" void Graph_StartFrame() { } #endif case KbScancode::LUS_KB_TAB: { - // Toggle HD Assets - CVarSetInteger("gAltAssets", !CVarGetInteger("gAltAssets", 0)); - GameInteractor::Instance->ExecuteHooks(); - 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(); } @@ -1301,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 @@ -2099,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 { @@ -2468,8 +2486,7 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { randoInf = RAND_INF_MERCHANTS_CARPET_SALESMAN; } messageEntry = OTRGlobals::Instance->gRandomizer->GetMerchantMessage(randoInf, textId, Randomizer_GetSettingValue(RSK_SHUFFLE_MERCHANTS) != RO_SHUFFLE_MERCHANTS_ON_HINT); - } else if (Randomizer_GetSettingValue(RSK_BOMBCHUS_IN_LOGIC) && - (textId == TEXT_BUY_BOMBCHU_10_DESC || textId == TEXT_BUY_BOMBCHU_10_PROMPT)) { + } else if (textId == TEXT_BUY_BOMBCHU_10_DESC || textId == TEXT_BUY_BOMBCHU_10_PROMPT) { messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, textId); } else if (textId == TEXT_CURSED_SKULLTULA_PEOPLE) { actorParams = GET_PLAYER(play)->targetActor->params; @@ -2488,6 +2505,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); @@ -2577,70 +2596,92 @@ extern "C" void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* repla gfx_register_blended_texture(name, mask, replacement); } -// #region SOH [TODO] Ideally this should move to being event based, it's currently run every frame on the file select screen -extern "C" void SoH_ProcessDroppedFiles() { - const char* droppedFile = CVarGetString("gDroppedFile", ""); - if (CVarGetInteger("gNewFileDropped", 0) && strcmp(droppedFile, "") != 0) { - try { - std::ifstream configStream(SohUtils::Sanitize(droppedFile)); - if (!configStream) { - return; - } +extern "C" void Gfx_UnregisterBlendedTexture(const char* name) { + gfx_unregister_blended_texture(name); +} - nlohmann::json configJson; - configStream >> configJson; +extern "C" void Gfx_TextureCacheDelete(const uint8_t* texAddr) { + char* imgName = (char*)texAddr; - if (!configJson.contains("CVars")) { - return; - } + if (texAddr == nullptr) { + return; + } - clearCvars(enhancementsCvars); - clearCvars(cheatCvars); - clearCvars(randomizerCvars); + if (ResourceMgr_OTRSigCheck(imgName)) { + texAddr = (const uint8_t*)GetResourceDataByNameHandlingMQ(imgName); + } - // Flatten everything under CVars into a single array - auto cvars = configJson["CVars"].flatten(); + gfx_texture_cache_delete(texAddr); +} - 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()); - } - } +void SoH_ProcessDroppedFiles(std::string filePath) { + try { + std::ifstream configStream(filePath); + if (!configStream) { + return; + } - 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"); + 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; - } 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"); + } + // #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/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index 88c57ced336..a93df7efbf2 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -173,9 +173,10 @@ void Entrance_InitEntranceTrackingData(void); void EntranceTracker_SetCurrentGrottoID(s16 entranceIndex); void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex); void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement); +void Gfx_UnregisterBlendedTexture(const char* name); +void Gfx_TextureCacheDelete(const uint8_t* addr); void SaveManager_ThreadPoolWait(); void CheckTracker_OnMessageClose(); -void SoH_ProcessDroppedFiles(); int32_t GetGIID(uint32_t itemID); #endif diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index e61cf922354..d8003988fcd 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -9,6 +9,7 @@ #include #include "soh/Enhancements/boss-rush/BossRush.h" #include +#include "SohGui.hpp" #define NOGDI // avoid various windows defines that conflict with things in z64.h #include @@ -1025,51 +1026,67 @@ void SaveManager::SaveGlobal() { void SaveManager::LoadFile(int fileNum) { SPDLOG_INFO("Load File - fileNum: {}", fileNum); - assert(std::filesystem::exists(GetFileName(fileNum))); + std::filesystem::path fileName = GetFileName(fileNum); + assert(std::filesystem::exists(fileName)); InitFile(false); - std::ifstream input(GetFileName(fileNum)); - - saveBlock = nlohmann::json::object(); - input >> saveBlock; - if (!saveBlock.contains("version")) { - SPDLOG_ERROR("Save at " + GetFileName(fileNum).string() + " contains no version"); - assert(false); - } - switch (saveBlock["version"].get()) { - case 1: - for (auto& block : saveBlock["sections"].items()) { - int sectionVersion = block.value()["version"]; - std::string sectionName = block.key(); - if (!sectionLoadHandlers.contains(sectionName)) { - // Unloadable sections aren't necessarily errors, they are probably mods that were unloaded - // TODO report in a more noticeable manner - SPDLOG_WARN("Save " + GetFileName(fileNum).string() + " contains unloadable section " + sectionName); - continue; - } - SectionLoadHandler& handler = sectionLoadHandlers[sectionName]; - if (!handler.contains(sectionVersion)) { - // A section that has a loader without a handler for the specific version means that the user has a mod - // at an earlier version than the save has. In this case, the user probably wants to load the save. - // Report the error so that the user can rectify the error. - // TODO report in a more noticeable manner - SPDLOG_ERROR("Save " + GetFileName(fileNum).string() + " contains section " + sectionName + - " with an unloadable version " + std::to_string(sectionVersion)); - assert(false); - continue; - } - currentJsonContext = &block.value()["data"]; - handler[sectionVersion](); - } - break; - default: - SPDLOG_ERROR("Unrecognized save version " + std::to_string(saveBlock["version"].get()) + " in " + - GetFileName(fileNum).string()); + std::ifstream input(fileName); + + try { + saveBlock = nlohmann::json::object(); + input >> saveBlock; + if (!saveBlock.contains("version")) { + SPDLOG_ERROR("Save at " + fileName.string() + " contains no version"); assert(false); - break; + } + switch (saveBlock["version"].get()) { + case 1: + for (auto& block : saveBlock["sections"].items()) { + int sectionVersion = block.value()["version"]; + std::string sectionName = block.key(); + if (!sectionLoadHandlers.contains(sectionName)) { + // Unloadable sections aren't necessarily errors, they are probably mods that were unloaded + // TODO report in a more noticeable manner + SPDLOG_WARN("Save " + GetFileName(fileNum).string() + " contains unloadable section " + + sectionName); + continue; + } + SectionLoadHandler& handler = sectionLoadHandlers[sectionName]; + if (!handler.contains(sectionVersion)) { + // A section that has a loader without a handler for the specific version means that the user + // has a mod at an earlier version than the save has. In this case, the user probably wants to + // load the save. Report the error so that the user can rectify the error. + // TODO report in a more noticeable manner + SPDLOG_ERROR("Save " + GetFileName(fileNum).string() + " contains section " + sectionName + + " with an unloadable version " + std::to_string(sectionVersion)); + assert(false); + continue; + } + currentJsonContext = &block.value()["data"]; + handler[sectionVersion](); + } + break; + default: + SPDLOG_ERROR("Unrecognized save version " + std::to_string(saveBlock["version"].get()) + " in " + + GetFileName(fileNum).string()); + assert(false); + break; + } + InitMeta(fileNum); + GameInteractor::Instance->ExecuteHooks(fileNum); + } catch (const std::exception& e) { + input.close(); + std::filesystem::path newFile(LUS::Context::GetPathRelativeToAppDirectory("Save") + ("/file" + std::to_string(fileNum + 1) + "-" + std::to_string(GetUnixTimestamp()) + ".bak")); +#if defined(__SWITCH__) || defined(__WIIU__) + copy_file(fileName.c_str(), newFile.c_str()); +#else + std::filesystem::copy_file(fileName, newFile); +#endif + + std::filesystem::remove(fileName); + SohGui::RegisterPopup("Error loading save file", "A problem occurred loading the save in slot " + std::to_string(fileNum + 1) + ".\nSave file corruption is suspected.\n" + + "The file has been renamed to prevent further issues."); } - InitMeta(fileNum); - GameInteractor::Instance->ExecuteHooks(fileNum); } void SaveManager::ThreadPoolWait() { diff --git a/soh/soh/SohGui.cpp b/soh/soh/SohGui.cpp index 73b006df997..90f6e59e5ea 100644 --- a/soh/soh/SohGui.cpp +++ b/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" { @@ -125,6 +125,7 @@ namespace SohGui { std::shared_ptr mItemTrackerSettingsWindow; std::shared_ptr mItemTrackerWindow; std::shared_ptr mRandomizerSettingsWindow; + std::shared_ptr mModalWindow; void SetupGuiElements() { auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); @@ -183,9 +184,13 @@ namespace SohGui { gui->AddGuiWindow(mItemTrackerSettingsWindow); mRandomizerSettingsWindow = std::make_shared("gRandomizerSettingsEnabled", "Randomizer Settings"); gui->AddGuiWindow(mRandomizerSettingsWindow); + mModalWindow = std::make_shared("gOpenWindows.modalWindowEnabled", "Modal Window"); + gui->AddGuiWindow(mModalWindow); + mModalWindow->Show(); } void Destroy() { + mModalWindow = nullptr; mRandomizerSettingsWindow = nullptr; mItemTrackerWindow = nullptr; mItemTrackerSettingsWindow = nullptr; @@ -205,4 +210,8 @@ namespace SohGui { mConsoleWindow = nullptr; mSohMenuBar = nullptr; } + + void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function button1callback, std::function button2callback) { + mModalWindow->RegisterPopup(title, message, button1, button2, button1callback, button2callback); + } } diff --git a/soh/soh/SohGui.hpp b/soh/soh/SohGui.hpp index 59333fd4249..dead44b51f3 100644 --- a/soh/soh/SohGui.hpp +++ b/soh/soh/SohGui.hpp @@ -22,6 +22,7 @@ #include "Enhancements/randomizer/randomizer_entrance_tracker.h" #include "Enhancements/randomizer/randomizer_item_tracker.h" #include "Enhancements/randomizer/randomizer_settings_window.h" +#include "SohModals.h" #ifdef __cplusplus extern "C" { @@ -37,6 +38,7 @@ namespace SohGui { void SetupGuiElements(); void Draw(); void Destroy(); + void RegisterPopup(std::string title, std::string message, std::string button1 = "OK", std::string button2 = "", std::function button1callback = nullptr, std::function button2callback = nullptr); } #endif /* SohGui_hpp */ diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index 9900cd50bc9..0806a4a6e95 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/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; @@ -558,7 +558,7 @@ void DrawEnhancementsMenu() { UIWidgets::Tooltip("Pierre appears when Ocarina is pulled out. Requires learning scarecrow song."); UIWidgets::PaddedEnhancementCheckbox("Remember Save Location", "gRememberSaveLocation", true, false); UIWidgets::Tooltip("When loading a save, places Link at the last entrance he went through.\n" - "This doesn't work if the save was made in a grotto."); + "This doesn't work if the save was made in grottos/fairy fountains or dungeons."); UIWidgets::PaddedEnhancementCheckbox("Skip Magic Arrow Equip Animation", "gSkipArrowAnimation", true, false); UIWidgets::PaddedEnhancementCheckbox("Skip save confirmation", "gSkipSaveConfirmation", true, false); UIWidgets::Tooltip("Skip the \"Game saved.\" confirmation screen"); @@ -910,8 +910,10 @@ void DrawEnhancementsMenu() { { if (ImGui::BeginMenu("Mods")) { if (UIWidgets::PaddedEnhancementCheckbox("Use Alternate Assets", "gAltAssets", false, false)) { - ShouldClearTextureCacheAtEndOfFrame = true; - GameInteractor::Instance->ExecuteHooks(); + // 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); diff --git a/soh/soh/SohModals.cpp b/soh/soh/SohModals.cpp new file mode 100644 index 00000000000..087bc8ab12b --- /dev/null +++ b/soh/soh/SohModals.cpp @@ -0,0 +1,54 @@ +#include "SohModals.h" +#include "ImGui/imgui.h" +#include +#include +#include +#include +#include "UIWidgets.hpp" +#include "OTRGlobals.h" +#include "z64.h" + +extern "C" PlayState* gPlayState; +struct SohModal { + std::string title_; + std::string message_; + std::string button1_; + std::string button2_; + std::function button1callback_; + std::function button2callback_; +}; +std::vector modals; + +void SohModalWindow::DrawElement() { + if (modals.size() > 0) { + SohModal curModal = modals.at(0); + if (!ImGui::IsPopupOpen(curModal.title_.c_str())) { + ImGui::OpenPopup(curModal.title_.c_str()); + } + if (ImGui::BeginPopupModal(curModal.title_.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings)) { + ImGui::Text(curModal.message_.c_str()); + if (ImGui::Button(curModal.button1_.c_str())) { + if (curModal.button1callback_ != nullptr) { + curModal.button1callback_(); + } + ImGui::CloseCurrentPopup(); + modals.erase(modals.begin()); + } + ImGui::SameLine(); + if (curModal.button2_ != "") { + if (ImGui::Button(curModal.button2_.c_str())) { + if (curModal.button2callback_ != nullptr) { + curModal.button2callback_(); + } + ImGui::CloseCurrentPopup(); + modals.erase(modals.begin()); + } + } + } + ImGui::EndPopup(); + } +} + +void SohModalWindow::RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function button1callback, std::function button2callback) { + modals.push_back({ title, message, button1, button2, button1callback, button2callback }); +} \ No newline at end of file diff --git a/soh/soh/SohModals.h b/soh/soh/SohModals.h new file mode 100644 index 00000000000..6f5acb2c0be --- /dev/null +++ b/soh/soh/SohModals.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include "window/gui/GuiMenuBar.h" +#include "window/gui/GuiElement.h" + +class SohModalWindow : public LUS::GuiWindow { + public: + using LUS::GuiWindow::GuiWindow; + + void InitElement() override {}; + void DrawElement() override; + void UpdateElement() override {}; + void RegisterPopup(std::string title, std::string message, std::string button1 = "OK", std::string button2 = "", std::function button1callback = nullptr, std::function button2callback = nullptr); +}; \ No newline at end of file diff --git a/soh/soh/resource/importer/SkeletonLimbFactory.cpp b/soh/soh/resource/importer/SkeletonLimbFactory.cpp index 22c18da243e..7ca8ae1ccad 100644 --- a/soh/soh/resource/importer/SkeletonLimbFactory.cpp +++ b/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/soh/soh/z_scene_otr.cpp b/soh/soh/z_scene_otr.cpp index ef98a4b3af2..be1840ecdba 100644 --- a/soh/soh/z_scene_otr.cpp +++ b/soh/soh/z_scene_otr.cpp @@ -149,6 +149,13 @@ bool Scene_CommandMeshHeader(PlayState* play, LUS::ISceneCommand* cmd) { extern "C" void* func_800982FC(ObjectContext* objectCtx, s32 bankIndex, s16 objectId); +bool OTRfunc_800982FC(ObjectContext* objectCtx, s32 bankIndex, s16 objectId) { + + objectCtx->status[bankIndex].id = -objectId; + + return false; +} + bool Scene_CommandObjectList(PlayState* play, LUS::ISceneCommand* cmd) { // LUS::SetObjectList* cmdObj = static_pointer_cast(cmd); LUS::SetObjectList* cmdObj = (LUS::SetObjectList*)cmd; @@ -164,49 +171,30 @@ bool Scene_CommandObjectList(PlayState* play, LUS::ISceneCommand* cmd) { void* nextPtr; k = 0; - // i = play->objectCtx.unk_09; - i = 0; + i = play->objectCtx.unk_09; firstStatus = &play->objectCtx.status[0]; status = &play->objectCtx.status[i]; - for (int i = 0; i < cmdObj->objects.size(); i++) { - bool alreadyIncluded = false; - - for (int j = 0; j < play->objectCtx.num; j++) { - if (play->objectCtx.status[j].id == cmdObj->objects[i]) { - alreadyIncluded = true; - break; + // Loop until a mismatch in the object lists + // Then clear all object ids past that in the context object list and kill actors for those objects + for (i = play->objectCtx.unk_09, k = 0; i < play->objectCtx.num; i++, k++) { + if (k >= cmdObj->objects.size() || play->objectCtx.status[i].id != cmdObj->objects[k]) { + for (j = i; j < play->objectCtx.num; j++) { + play->objectCtx.status[j].id = OBJECT_INVALID; } - } - - if (!alreadyIncluded) { - play->objectCtx.status[play->objectCtx.num++].id = cmdObj->objects[i]; func_80031A28(play, &play->actorCtx); + break; } } - /* - while (i < play->objectCtx.num) { - if (status->id != *objectEntry) { - status2 = &play->objectCtx.status[i]; - for (j = i; j < play->objectCtx.num; j++) { - status2->id = OBJECT_INVALID; - status2++; - } - play->objectCtx.num = i; - func_80031A28(play, &play->actorCtx); - - continue; + // Continuing from the last index, add the remaining object ids from the command object list + for (; k < cmdObj->objects.size(); k++, i++) { + if (i < OBJECT_EXCHANGE_BANK_MAX - 1) { + OTRfunc_800982FC(&play->objectCtx, i, cmdObj->objects[k]); } - - i++; - k++; - objectEntry++; - status++; } play->objectCtx.num = i; - */ return false; } diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 79a3c8ace21..c1dc66e23bf 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -1219,8 +1219,7 @@ void Actor_Init(Actor* actor, PlayState* play) { CollisionCheck_InitInfo(&actor->colChkInfo); actor->floorBgId = BGCHECK_SCENE; ActorShape_Init(&actor->shape, 0.0f, NULL, 0.0f); - //if (Object_IsLoaded(&play->objectCtx, actor->objBankIndex)) - { + if (Object_IsLoaded(&play->objectCtx, actor->objBankIndex)) { //Actor_SetObjectDependency(play, actor); actor->init(actor, play); actor->init = NULL; @@ -2861,11 +2860,19 @@ s32 func_800314D4(PlayState* play, Actor* actor, Vec3f* arg2, f32 arg3) { if ((arg2->z > -actor->uncullZoneScale) && (arg2->z < (actor->uncullZoneForward + actor->uncullZoneScale))) { var = (arg3 < 1.0f) ? 1.0f : 1.0f / arg3; - if ((((fabsf(arg2->x) - actor->uncullZoneScale) * var) < 2.0f) && - (((arg2->y + actor->uncullZoneDownward) * var) > -2.0f) && - (((arg2->y - actor->uncullZoneScale) * var) < 2.0f)) { + // #region SoH [Widescreen support] + // Doors will cull quite noticeably on wider screens. For these actors the zone is increased + f32 limit = 1.0f; + if (((actor->id == ACTOR_EN_DOOR) || (actor->id == ACTOR_DOOR_SHUTTER)) && CVarGetInteger("gIncreaseDoorUncullZones", 1)) { + limit = 2.0f; + } + + if ((((fabsf(arg2->x) - actor->uncullZoneScale) * var) < limit) && + (((arg2->y + actor->uncullZoneDownward) * var) > -limit) && + (((arg2->y - actor->uncullZoneScale) * var) < limit)) { return true; } + // #endregion } return false; @@ -3129,6 +3136,9 @@ void Actor_FreeOverlay(ActorDBEntry* dbEntry) { osSyncPrintf(VT_RST); } +// SoH: Flag to check if actors are being spawned from the actor entry list +// This flag is checked against to allow actors which dont have an objectBankIndex in the objectCtx slot/status array to spawn +// An example of what this fixes, is that it allows hookshot to be used as child int gMapLoading = 0; Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 posX, f32 posY, f32 posZ, diff --git a/soh/src/code/z_bgcheck.c b/soh/src/code/z_bgcheck.c index 4acc68e55c2..7c99e7ca28c 100644 --- a/soh/src/code/z_bgcheck.c +++ b/soh/src/code/z_bgcheck.c @@ -1902,7 +1902,7 @@ s32 BgCheck_CheckWallImpl(CollisionContext* colCtx, u16 xpFlags, Vec3f* posResul s32 bgId2; f32 nx, ny, nz; // unit normal of polygon - if (CVarGetInteger("gNoClip", 0) != 0) { + if (CVarGetInteger("gNoClip", 0) && actor != NULL && actor->id == ACTOR_PLAYER) { return false; } diff --git a/soh/src/code/z_camera.c b/soh/src/code/z_camera.c index a177e29e7b2..4353d397e6c 100644 --- a/soh/src/code/z_camera.c +++ b/soh/src/code/z_camera.c @@ -7887,7 +7887,7 @@ s32 Camera_ChangeModeFlags(Camera* camera, s16 mode, u8 flags) { } } - // Clear free camera if an action is performed that would move the camera (targeting, first person, talking) + // Clear free look if an action is performed that would move the camera (targeting, first person, talking) if (CVarGetInteger("gFreeCamera", 0) && SetCameraManual(camera) == 1 && ((mode >= CAM_MODE_TARGET && mode <= CAM_MODE_BATTLE) || (mode >= CAM_MODE_FIRSTPERSON && mode <= CAM_MODE_CLIMBZ) || mode == CAM_MODE_HANGZ || diff --git a/soh/src/code/z_camera_data.inc b/soh/src/code/z_camera_data.inc index b9e0ba864ac..90b1874c73a 100644 --- a/soh/src/code/z_camera_data.inc +++ b/soh/src/code/z_camera_data.inc @@ -16,9 +16,11 @@ typedef struct { union { u32 unk_00; struct { - u32 unk_bit0 : 1; - u32 unk_bit1 : 1; - u32 validModes : 30; + // SoH [Port] These bitfield values are unused and led to shifting in validModes for little endian systems + // Removing those so that validModes can be a complete 32 bit value + // u32 unk_bit0 : 1; + // u32 unk_bit1 : 1; + u32 validModes; }; }; CameraMode* cameraModes; diff --git a/soh/src/code/z_map_exp.c b/soh/src/code/z_map_exp.c index c4db0098b23..213ee018d54 100644 --- a/soh/src/code/z_map_exp.c +++ b/soh/src/code/z_map_exp.c @@ -524,7 +524,6 @@ void Map_Init(PlayState* play) { interfaceCtx->unk_25A = -1; interfaceCtx->mapSegment = GAMESTATE_ALLOC_MC(&play->state, 2 * sizeof(char*)); - interfaceCtx->mapSegmentName = GAMESTATE_ALLOC_MC(&play->state, 2 * sizeof(char*)); // "MAP texture initialization scene_data_ID=%d mapSegment=%x" osSyncPrintf("\n\n\nMAP テクスチャ初期化 scene_data_ID=%d\nmapSegment=%x\n\n", play->sceneNum, interfaceCtx->mapSegment, play); diff --git a/soh/src/code/z_message_PAL.c b/soh/src/code/z_message_PAL.c index 1ab090ce661..92d97916a77 100644 --- a/soh/src/code/z_message_PAL.c +++ b/soh/src/code/z_message_PAL.c @@ -8,6 +8,7 @@ #include "textures/message_static/message_static.h" #include "textures/message_texture_static/message_texture_static.h" #include "soh/Enhancements/cosmetics/cosmeticsTypes.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/OTRGlobals.h" @@ -3072,7 +3073,9 @@ void Message_Draw(PlayState* play) { POLY_OPA_DISP = plusOne; } plusOne = Graph_GfxPlusOne(polyOpaP = POLY_OPA_DISP); - gSPDisplayList(OVERLAY_DISP++, plusOne); + if (!GameInteractor_NoUIActive()) { + gSPDisplayList(OVERLAY_DISP++, plusOne); + } Message_DrawMain(play, &plusOne); gSPEndDisplayList(plusOne++); Graph_BranchDlist(polyOpaP, plusOne); diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 4290dcd53e9..7d109a75d8f 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1521,6 +1521,13 @@ 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 { @@ -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); diff --git a/soh/src/code/z_scene.c b/soh/src/code/z_scene.c index 2a0a5d5d782..579e84eda37 100644 --- a/soh/src/code/z_scene.c +++ b/soh/src/code/z_scene.c @@ -83,9 +83,10 @@ void Object_UpdateBank(ObjectContext* objectCtx) { RomFile* objectFile; size_t size; - /* + for (i = 0; i < objectCtx->num; i++) { if (status->id < 0) { + /* if (status->dmaRequest.vromAddr == 0) { osCreateMesgQueue(&status->loadQueue, &status->loadMsg, 1); objectFile = &gObjectTable[-status->id]; @@ -96,10 +97,12 @@ void Object_UpdateBank(ObjectContext* objectCtx) { } else if (!osRecvMesg(&status->loadQueue, NULL, OS_MESG_NOBLOCK)) { status->id = -status->id; } + */ + status->id = -status->id; } status++; } - */ + } s32 Object_GetIndex(ObjectContext* objectCtx, s16 objectId) { diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index 1ce20abc3f9..0152ecf2ec1 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -59,65 +59,68 @@ void Sram_OpenSave() { Save_LoadFile(); - if (!CVarGetInteger("gRememberSaveLocation", 0) || gSaveContext.savedSceneNum == SCENE_FAIRYS_FOUNTAIN || - gSaveContext.savedSceneNum == SCENE_GROTTOS) { - switch (gSaveContext.savedSceneNum) { - case SCENE_DEKU_TREE: - case SCENE_DODONGOS_CAVERN: - case SCENE_JABU_JABU: - case SCENE_FOREST_TEMPLE: - case SCENE_FIRE_TEMPLE: - case SCENE_WATER_TEMPLE: - case SCENE_SPIRIT_TEMPLE: - case SCENE_SHADOW_TEMPLE: - case SCENE_BOTTOM_OF_THE_WELL: - case SCENE_ICE_CAVERN: - case SCENE_GANONS_TOWER: - case SCENE_GERUDO_TRAINING_GROUND: - case SCENE_THIEVES_HIDEOUT: - case SCENE_INSIDE_GANONS_CASTLE: - gSaveContext.entranceIndex = dungeonEntrances[gSaveContext.savedSceneNum]; - break; - case SCENE_DEKU_TREE_BOSS: - gSaveContext.entranceIndex = 0; - break; - case SCENE_DODONGOS_CAVERN_BOSS: - gSaveContext.entranceIndex = 4; - break; - case SCENE_JABU_JABU_BOSS: - gSaveContext.entranceIndex = 0x28; - break; - case SCENE_FOREST_TEMPLE_BOSS: - gSaveContext.entranceIndex = 0x169; - break; - case SCENE_FIRE_TEMPLE_BOSS: - gSaveContext.entranceIndex = 0x165; - break; - case SCENE_WATER_TEMPLE_BOSS: - gSaveContext.entranceIndex = 0x10; - break; - case SCENE_SPIRIT_TEMPLE_BOSS: - gSaveContext.entranceIndex = 0x82; - break; - case SCENE_SHADOW_TEMPLE_BOSS: - gSaveContext.entranceIndex = 0x37; - break; - case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR: - case SCENE_INSIDE_GANONS_CASTLE_COLLAPSE: - case SCENE_GANONDORF_BOSS: - case SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR: - case SCENE_GANON_BOSS: - gSaveContext.entranceIndex = 0x41B; + switch (gSaveContext.savedSceneNum) { + case SCENE_DEKU_TREE: + case SCENE_DODONGOS_CAVERN: + case SCENE_JABU_JABU: + case SCENE_FOREST_TEMPLE: + case SCENE_FIRE_TEMPLE: + case SCENE_WATER_TEMPLE: + case SCENE_SPIRIT_TEMPLE: + case SCENE_SHADOW_TEMPLE: + case SCENE_BOTTOM_OF_THE_WELL: + case SCENE_ICE_CAVERN: + case SCENE_GANONS_TOWER: + case SCENE_GERUDO_TRAINING_GROUND: + case SCENE_THIEVES_HIDEOUT: + case SCENE_INSIDE_GANONS_CASTLE: + gSaveContext.entranceIndex = dungeonEntrances[gSaveContext.savedSceneNum]; + break; + case SCENE_DEKU_TREE_BOSS: + gSaveContext.entranceIndex = 0; + break; + case SCENE_DODONGOS_CAVERN_BOSS: + gSaveContext.entranceIndex = 4; + break; + case SCENE_JABU_JABU_BOSS: + gSaveContext.entranceIndex = 0x28; + break; + case SCENE_FOREST_TEMPLE_BOSS: + gSaveContext.entranceIndex = 0x169; + break; + case SCENE_FIRE_TEMPLE_BOSS: + gSaveContext.entranceIndex = 0x165; + break; + case SCENE_WATER_TEMPLE_BOSS: + gSaveContext.entranceIndex = 0x10; + break; + case SCENE_SPIRIT_TEMPLE_BOSS: + gSaveContext.entranceIndex = 0x82; + break; + case SCENE_SHADOW_TEMPLE_BOSS: + gSaveContext.entranceIndex = 0x37; + break; + case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR: + case SCENE_INSIDE_GANONS_CASTLE_COLLAPSE: + case SCENE_GANONDORF_BOSS: + case SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR: + case SCENE_GANON_BOSS: + gSaveContext.entranceIndex = 0x41B; + break; + + default: + // Use the saved entrance value with remember save location, except when in grottos/fairy fountains + if (CVarGetInteger("gRememberSaveLocation", 0) && gSaveContext.savedSceneNum != SCENE_FAIRYS_FOUNTAIN && + gSaveContext.savedSceneNum != SCENE_GROTTOS) { break; + } - default: - if (gSaveContext.savedSceneNum != SCENE_LINKS_HOUSE) { - gSaveContext.entranceIndex = (LINK_AGE_IN_YEARS == YEARS_CHILD) ? 0xBB : 0x5F4; - } else { - gSaveContext.entranceIndex = 0xBB; - } - break; - } + if (gSaveContext.savedSceneNum != SCENE_LINKS_HOUSE) { + gSaveContext.entranceIndex = (LINK_AGE_IN_YEARS == YEARS_CHILD) ? 0xBB : 0x5F4; + } else { + gSaveContext.entranceIndex = 0xBB; + } + break; } osSyncPrintf("scene_no = %d\n", gSaveContext.entranceIndex); diff --git a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c index 6a0d4957922..1f279154070 100644 --- a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c +++ b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c @@ -10,10 +10,6 @@ #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 @@ -69,7 +65,7 @@ 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; @@ -112,6 +108,22 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() { 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); + Gfx_TextureCacheDelete(sMaskTexLava); + return; + } sLavaFloorModifiedTexRaw = malloc(lavaSize); sLavaWavyTexRaw = malloc(floorSize); @@ -121,7 +133,6 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() { // When KD is dead, just immediately copy the rock texture if (Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num)) { u32* rockTex = ResourceGetDataByName(sLavaFloorRockTex); - size_t rockSize = ResourceGetSizeByName(sLavaFloorRockTex); memcpy(sLavaFloorModifiedTexRaw, rockTex, rockSize); } @@ -145,7 +156,16 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() { Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaWavyTex); } - gfx_texture_cache_clear(); + // 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_TextureCacheDelete(sMaskTexLava); + Gfx_TextureCacheDelete(sLavaWavyTex); + Gfx_TextureCacheDelete(sLavaFloorModifiedTex); } void func_808C12C4(u8* arg1, s16 arg2) { @@ -170,6 +190,11 @@ void func_808C12C4(u8* arg1, s16 arg2) { // 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 width = ResourceGetTexWidthByName(arg0); s32 size = ResourceGetTexHeightByName(arg0) * width; @@ -203,9 +228,7 @@ void func_808C1554_Raw(void* arg0, void* floorTex, s32 arg2, f32 arg3) { } free(sp54); - - // Need to clear the cache after updating sLavaWavyTexRaw - gfx_texture_cache_clear(); + Gfx_TextureCacheDelete(sLavaWavyTexRaw); } // Modified to support CPU modified texture with the resource system @@ -234,8 +257,7 @@ void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) { } } - // Need to clear the cache after updating sLavaWavyTex - gfx_texture_cache_clear(); + Gfx_TextureCacheDelete(sLavaWavyTex); } void func_808C17C8(PlayState* play, Vec3f* arg1, Vec3f* arg2, Vec3f* arg3, f32 arg4, s16 arg5) { @@ -325,7 +347,7 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) { this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; // #region SOH [General] - // Init mask values for all blended textures + // Init mask values for all KD blended textures for (int i = 0; i < ARRAY_COUNT(sMaskTex8x16); i++) { sMaskTex8x16[i] = 0; } @@ -341,10 +363,6 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) { for (int i = 0; i < ARRAY_COUNT(sMaskTex32x16); i++) { sMaskTex32x16[i] = 0; } - // Set all true for the lava as it will always replace the scene texture - for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) { - sMaskTexLava[i] = 1; - } // Register all blended textures Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015890, sMaskTex8x16, NULL); @@ -358,6 +376,13 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) { Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016990, sMaskTex32x16, NULL); Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016E10, sMaskTex32x16, NULL); + // Clear cache for masks + Gfx_TextureCacheDelete(sMaskTex8x16); + Gfx_TextureCacheDelete(sMaskTex8x32); + Gfx_TextureCacheDelete(sMaskTex16x16); + Gfx_TextureCacheDelete(sMaskTex16x32); + Gfx_TextureCacheDelete(sMaskTex32x16); + BossDodongo_RegisterBlendedLavaTextureUpdate(); // Register alt listener to update the blended lava for the replacement texture based on alt path @@ -1174,15 +1199,24 @@ void BossDodongo_Update(Actor* thisx, PlayState* play2) { for (i2 = 0; i2 < 20; i2++) { s16 new_var = this->unk_1C2 & (LAVA_TEX_SIZE - 1); - // Compute the index to a scaled position (scaling pseudo x,y as a 1D value) - s32 indexStart = ((new_var % LAVA_TEX_WIDTH) * widthScale) + ((new_var / LAVA_TEX_WIDTH) * width * heightScale); - - // From the starting index, apply extra pixels right/down based on the scale - for (size_t j = 0; j < heightScale; j++) { - for (size_t i3 = 0; i3 < widthScale; i3++) { - s32 scaledIndex = (indexStart + i3 + (j * width)) & (size - 1); - ptr1[scaledIndex] = ptr2[scaledIndex]; + + // 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; + Gfx_TextureCacheDelete(sMaskTexLava); } this->unk_1C2 += 37; @@ -1322,10 +1356,6 @@ void BossDodongo_Draw(Actor* thisx, PlayState* play) { gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTex32x16); } - if (this->unk_1C6 != 0) { - gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTexLava); - } - if ((this->unk_1C0 >= 2) && (this->unk_1C0 & 1)) { POLY_OPA_DISP = Gfx_SetFog(POLY_OPA_DISP, 255, 255, 255, 0, 900, 1099); } else { diff --git a/soh/src/overlays/actors/ovl_Demo_Gj/z_demo_gj.c b/soh/src/overlays/actors/ovl_Demo_Gj/z_demo_gj.c index 5f3737b44d9..357b2c0ca92 100644 --- a/soh/src/overlays/actors/ovl_Demo_Gj/z_demo_gj.c +++ b/soh/src/overlays/actors/ovl_Demo_Gj/z_demo_gj.c @@ -192,11 +192,11 @@ void DemoGj_Explode(DemoGj* this, PlayState* play, Vec3f* initialPos, Vec3f* dir phi_s0 = 0x21; } - Gfx* gfx = ResourceMgr_LoadGfxByName(gGanonRubbleDL); - + // SoH [Port] Changed from &gGanonsCastleRubbleAroundArenaDL[28] to gGanonRubbleDL as it seems this was an error in the original rom/decomp + // Other calls to EffectSsKakera_Spawn with OBJECT_GEFF use gGanonRubbleDL, so this change is to match that EffectSsKakera_Spawn(play, &explosionPos, &velocity, initialPos, -200, phi_s0, 10, 10, 0, Rand_ZeroOne() * 20.0f + 20.0f, 20, 300, (s32)(Rand_ZeroOne() * 30.0f) + 30, -1, - OBJECT_GEFF, gfx); + OBJECT_GEFF, gGanonRubbleDL); theta += 0x2AAA; } diff --git a/soh/src/overlays/actors/ovl_En_Box/z_en_box.c b/soh/src/overlays/actors/ovl_En_Box/z_en_box.c index 9e8705c33dd..4f1ef73626a 100644 --- a/soh/src/overlays/actors/ovl_En_Box/z_en_box.c +++ b/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/soh/src/overlays/actors/ovl_En_Dnt_Jiji/z_en_dnt_jiji.c b/soh/src/overlays/actors/ovl_En_Dnt_Jiji/z_en_dnt_jiji.c index ae09cb4384d..9ce27f4c1ea 100644 --- a/soh/src/overlays/actors/ovl_En_Dnt_Jiji/z_en_dnt_jiji.c +++ b/soh/src/overlays/actors/ovl_En_Dnt_Jiji/z_en_dnt_jiji.c @@ -101,6 +101,9 @@ void EnDntJiji_Destroy(Actor* thisx, PlayState* play) { } void EnDntJiji_SetFlower(EnDntJiji* this, PlayState* play) { + // SOH: Due to removed object dependencies, parent was still NULL when Init was called. In order to properly set + // stage, redo it here now that we are a frame later. + this->stage = (EnDntDemo*)this->actor.parent; if (this->actor.bgCheckFlags & 1) { this->flowerPos = this->actor.world.pos; this->actionFunc = EnDntJiji_SetupWait; diff --git a/soh/src/overlays/actors/ovl_En_GirlA/z_en_girla.c b/soh/src/overlays/actors/ovl_En_GirlA/z_en_girla.c index 037b4b59438..6aa1b7d412d 100644 --- a/soh/src/overlays/actors/ovl_En_GirlA/z_en_girla.c +++ b/soh/src/overlays/actors/ovl_En_GirlA/z_en_girla.c @@ -1027,8 +1027,8 @@ void EnGirlA_BuyEvent_ObtainBombchuPack(PlayState* play, EnGirlA* this) { Rupees_ChangeBy(-this->basePrice); // Normally, buying a bombchu pack sets a flag indicating the pack is now sold out - // If they're in logic for rando, skip setting that flag so they can be purchased repeatedly - if (IS_RANDO && Randomizer_GetSettingValue(RSK_BOMBCHUS_IN_LOGIC)) { + // If we're in rando, skip setting that flag so they can be purchased repeatedly + if (IS_RANDO) { return; } @@ -1255,8 +1255,7 @@ void EnGirlA_InitializeItemAction(EnGirlA* this, PlayState* play) { this->itemGiveFunc = itemEntry->itemGiveFunc; this->buyEventFunc = itemEntry->buyEventFunc; // If chus are in logic, make the 10 pack affordable without a wallet upgrade - if (IS_RANDO && Randomizer_GetSettingValue(RSK_BOMBCHUS_IN_LOGIC) && - this->getItemId == GI_BOMBCHUS_10) { + if (IS_RANDO && this->getItemId == GI_BOMBCHUS_10) { this->basePrice = 99; } else { this->basePrice = itemEntry->price; diff --git a/soh/src/overlays/actors/ovl_En_Part/z_en_part.c b/soh/src/overlays/actors/ovl_En_Part/z_en_part.c index e1b65efcad0..be1143ff115 100644 --- a/soh/src/overlays/actors/ovl_En_Part/z_en_part.c +++ b/soh/src/overlays/actors/ovl_En_Part/z_en_part.c @@ -8,6 +8,8 @@ #include "objects/object_tite/object_tite.h" #include "objects/object_ik/object_ik.h" +#include // strcmp + #define FLAGS ACTOR_FLAG_UPDATE_WHILE_CULLED void EnPart_Init(Actor* thisx, PlayState* play); @@ -297,11 +299,11 @@ void EnPart_Draw(Actor* thisx, PlayState* play) { gSPSegment(POLY_OPA_DISP++, 0x08, func_80ACEAC0(play->state.gfxCtx, 255, 255, 255, 180, 180, 180)); gSPSegment(POLY_OPA_DISP++, 0x09, func_80ACEAC0(play->state.gfxCtx, 225, 205, 115, 25, 20, 0)); gSPSegment(POLY_OPA_DISP++, 0x0A, func_80ACEAC0(play->state.gfxCtx, 225, 205, 115, 25, 20, 0)); - } else if ((thisx->params == 9) && (this->displayList == ResourceMgr_LoadGfxByName(object_tite_DL_002FF0))) { + } else if ((thisx->params == 9) && (strcmp((const char*)this->displayList, object_tite_DL_002FF0) == 0)) { gSPSegment(POLY_OPA_DISP++, 0x08, object_tite_Tex_001300); gSPSegment(POLY_OPA_DISP++, 0x09, object_tite_Tex_001700); gSPSegment(POLY_OPA_DISP++, 0x0A, object_tite_Tex_001900); - } else if ((thisx->params == 10) && (this->displayList == ResourceMgr_LoadGfxByName(object_tite_DL_002FF0))) { + } else if ((thisx->params == 10) && (strcmp((const char*)this->displayList, object_tite_DL_002FF0) == 0)) { gSPSegment(POLY_OPA_DISP++, 0x08, object_tite_Tex_001B00); gSPSegment(POLY_OPA_DISP++, 0x09, object_tite_Tex_001F00); gSPSegment(POLY_OPA_DISP++, 0x0A, object_tite_Tex_002100); diff --git a/soh/src/overlays/actors/ovl_En_Ru2/z_en_ru2.c b/soh/src/overlays/actors/ovl_En_Ru2/z_en_ru2.c index 6be42b42dad..fe4a7772490 100644 --- a/soh/src/overlays/actors/ovl_En_Ru2/z_en_ru2.c +++ b/soh/src/overlays/actors/ovl_En_Ru2/z_en_ru2.c @@ -821,19 +821,6 @@ void func_80AF3F20(EnRu2* this, PlayState* play) { void EnRu2_Draw(Actor* thisx, PlayState* play) { EnRu2* this = (EnRu2*)thisx; - // FAST3D: This is a hack for the issue of both TEXEL0 and TEXEL1 using the same texture with different settings. - // Ruto's earring uses both TEXEL0 and TEXEL1 to render. The issue is that it never loads anything into TEXEL1, so - // it reuses whatever happens to be there, which is the water temple brick texture. It just so happens that the - // earring texture loads into the same place in tmem as the brick texture, so when it comes to rendering, TEXEL1 - // uses the earring texture with diffrent clamp settings, and it displays without noticeable error. However, both - // texel samplers are not intended to be used for the same texture with different settings, so this misuse confuses - // our texture cache, and we load the wrong settings for the earrings texture. This patch is a hack that replaces - // TEXEL1 with TEXEL0, which is most likely the original intention, and all is well. - Gfx* gfx = ResourceMgr_LoadGfxByName(gAdultRutoHeadDL); - Gfx patch = gsDPSetCombineLERP(TEXEL0, 0, PRIMITIVE, 0, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, COMBINED, TEXEL0, 0, - PRIM_LOD_FRAC, COMBINED); - gfx[0xA2] = patch; - if ((this->drawConfig < 0) || (this->drawConfig >= ARRAY_COUNT(sDrawFuncs)) || (sDrawFuncs[this->drawConfig] == 0)) { // "Draw Mode is improper!" diff --git a/soh/src/overlays/actors/ovl_En_Si/z_en_si.c b/soh/src/overlays/actors/ovl_En_Si/z_en_si.c index 49e88f90345..f6c87074db9 100644 --- a/soh/src/overlays/actors/ovl_En_Si/z_en_si.c +++ b/soh/src/overlays/actors/ovl_En_Si/z_en_si.c @@ -16,6 +16,7 @@ void EnSi_Init(Actor* thisx, PlayState* play); void EnSi_Destroy(Actor* thisx, PlayState* play); void EnSi_Update(Actor* thisx, PlayState* play); void EnSi_Draw(Actor* thisx, PlayState* play); +void EnSi_Reset(); s32 func_80AFB748(EnSi* this, PlayState* play); void func_80AFB768(EnSi* this, PlayState* play); @@ -61,7 +62,7 @@ const ActorInit En_Si_InitVars = { (ActorFunc)EnSi_Destroy, (ActorFunc)EnSi_Update, (ActorFunc)EnSi_Draw, - NULL, + (ActorResetFunc)EnSi_Reset, }; void EnSi_Init(Actor* thisx, PlayState* play) { @@ -224,6 +225,11 @@ void EnSi_Draw(Actor* thisx, PlayState* play) { } } +void EnSi_Reset() { + textId = 0xB4; + giveItemId = ITEM_SKULL_TOKEN; +} + void Randomizer_UpdateSkullReward(EnSi* this, PlayState* play) { Player* player = GET_PLAYER(play); diff --git a/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c b/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c index 888b0ef3454..6ab91ddc048 100644 --- a/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c +++ b/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/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 9bd4e9922ff..e0f2da173f6 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -10641,7 +10641,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/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index 9e6150593d2..59b6367f96b 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -1030,18 +1030,18 @@ void FileChoose_UpdateRandomizer() { 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); @@ -1076,7 +1076,6 @@ void FileChoose_UpdateMainMenu(GameState* thisx) { Input* input = &this->state.input[0]; bool dpad = CVarGetInteger("gDpadText", 0); - SoH_ProcessDroppedFiles(); FileChoose_UpdateRandomizer(); if (CHECK_BTN_ALL(input->press.button, BTN_START) || CHECK_BTN_ALL(input->press.button, BTN_A)) { @@ -1267,7 +1266,6 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) { s8 i = 0; bool dpad = CVarGetInteger("gDpadText", 0); - SoH_ProcessDroppedFiles(); FileChoose_UpdateRandomizer(); if (ABS(this->stickRelX) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT | BTN_DRIGHT))) { @@ -3038,11 +3036,7 @@ void FileChoose_LoadGame(GameState* thisx) { Entrance_Init(); // Handle randomized spawn positions after the save context has been setup from load - // When remeber save location is on, set save warp if the save was in an a grotto, or - // the entrance index is -1 from shuffle overwarld spawn - if (Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES) && ((!CVarGetInteger("gRememberSaveLocation", 0) || - gSaveContext.savedSceneNum == SCENE_FAIRYS_FOUNTAIN || gSaveContext.savedSceneNum == SCENE_GROTTOS) || - (CVarGetInteger("gRememberSaveLocation", 0) && Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS) && gSaveContext.entranceIndex == -1))) { + if (Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) { Entrance_SetSavewarpEntrance(); } } diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c index 377c23eb38e..5c5cb3e6d69 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c +++ b/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/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c index 238a1e4e2d0..34dc47aa900 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c @@ -312,6 +312,9 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) { KaleidoScope_DrawQuadTextureRGBA32(gfxCtx, gQuestIconGoldSkulltulaTex, 24, 24, 8); } + // Unique index for both pulse phases + uint8_t palettePulseIdx = (mapBgPulseStage ? 40 : 20) - mapBgPulseTimer; + if ((play->sceneNum >= SCENE_DEKU_TREE) && (play->sceneNum <= SCENE_TREASURE_BOX_SHOP)) { stepR = (mapBgPulseR - mapBgPulseColors[mapBgPulseStage][0]) / mapBgPulseTimer; stepG = (mapBgPulseG - mapBgPulseColors[mapBgPulseStage][1]) / mapBgPulseTimer; @@ -324,6 +327,9 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) { interfaceCtx->mapPalette[28] = (rgba16 & 0xFF00) >> 8; interfaceCtx->mapPalette[29] = rgba16 & 0xFF; + interfaceCtx->mapPalettesPulse[palettePulseIdx][28] = (rgba16 & 0xFF00) >> 8; + interfaceCtx->mapPalettesPulse[palettePulseIdx][29] = rgba16 & 0xFF; + mapBgPulseTimer--; if (mapBgPulseTimer == 0) { mapBgPulseStage ^= 1; @@ -335,7 +341,8 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) { gDPSetTextureFilter(POLY_KAL_DISP++, G_TF_POINT); gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, 255, 255, 255, pauseCtx->alpha); - gDPLoadTLUT_pal16(POLY_KAL_DISP++, 0, interfaceCtx->mapPalette); + // Use a unique palette address per frame so the renderer/shader can cache all variations + gDPLoadTLUT_pal16(POLY_KAL_DISP++, 0, interfaceCtx->mapPalettesPulse[palettePulseIdx]); gDPSetTextureLUT(POLY_KAL_DISP++, G_TT_RGBA16); u8 mirroredWorld = CVarGetInteger("gMirroredWorld", 0); @@ -343,18 +350,15 @@ 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; } } gSPVertex(POLY_KAL_DISP++, &pauseCtx->mapPageVtx[60], 8, 0); - // The dungeon map textures are recreated each frame, so always invalidate them - 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 +367,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/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h index 6f4ed7ad257..8c214d9b0ae 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h +++ b/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/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c index 35490f0798d..1475472a9af 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c @@ -3315,13 +3315,121 @@ 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; +static uint8_t mapBlendMask[MAP_48x85_TEX_WIDTH * MAP_48x85_TEX_HEIGHT]; + +// 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 and unregister the blended effect to avoid crashing + if (size < ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0])) { + interfaceCtx->mapSegment[0] = NULL; + interfaceCtx->mapSegment[1] = NULL; + + Gfx_UnregisterBlendedTexture(interfaceCtx->mapSegmentName[0]); + Gfx_UnregisterBlendedTexture(interfaceCtx->mapSegmentName[1]); + + Gfx_TextureCacheDelete(interfaceCtx->mapSegmentName[0]); + Gfx_TextureCacheDelete(interfaceCtx->mapSegmentName[1]); + 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]); + + Gfx_TextureCacheDelete(interfaceCtx->mapSegmentName[0]); + Gfx_TextureCacheDelete(interfaceCtx->mapSegmentName[1]); + Gfx_TextureCacheDelete(interfaceCtx->mapSegment[0]); + Gfx_TextureCacheDelete(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 +3441,34 @@ void KaleidoScope_UpdateDungeonMap(PlayState* play) { KaleidoScope_LoadDungeonMap(play); Map_SetFloorPalettesData(play, pauseCtx->dungeonMapSlot - 3); + // Copy the map palette values to all pulse palettes + for (uint8_t i = 0; i < ARRAY_COUNT(interfaceCtx->mapPalettesPulse); i++) { + memcpy(interfaceCtx->mapPalettesPulse[i], interfaceCtx->mapPalette, sizeof(interfaceCtx->mapPalette)); + } + + 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/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c b/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c index a73ca826505..fec1d803366 100644 --- a/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c +++ b/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[] = {