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 c3b85024ba4..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.4 LANGUAGES C CXX) -set(PROJECT_BUILD_NAME "MacReady Echo" 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 b4abd7c366b..96c8a8929c1 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit b4abd7c366b1fb38b2cd80ffb91e129035bee0ea +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/assets/custom/textures/icon_item_static/gArrowIconTex.rgba32.png b/soh/assets/custom/textures/icon_item_static/gArrowIconTex.rgba32.png new file mode 100644 index 00000000000..6e8affade3f Binary files /dev/null and b/soh/assets/custom/textures/icon_item_static/gArrowIconTex.rgba32.png differ diff --git a/soh/assets/custom/textures/icon_item_static/gFireArrowPower.rgba32.png b/soh/assets/custom/textures/icon_item_static/gFireArrowPower.rgba32.png new file mode 100644 index 00000000000..ed8e2600364 Binary files /dev/null and b/soh/assets/custom/textures/icon_item_static/gFireArrowPower.rgba32.png differ diff --git a/soh/assets/custom/textures/icon_item_static/gIceArrowPower.rgba32.png b/soh/assets/custom/textures/icon_item_static/gIceArrowPower.rgba32.png new file mode 100644 index 00000000000..713765372dc Binary files /dev/null and b/soh/assets/custom/textures/icon_item_static/gIceArrowPower.rgba32.png differ diff --git a/soh/assets/custom/textures/icon_item_static/gLightArrowPower.rgba32.png b/soh/assets/custom/textures/icon_item_static/gLightArrowPower.rgba32.png new file mode 100644 index 00000000000..79a3dd48423 Binary files /dev/null and b/soh/assets/custom/textures/icon_item_static/gLightArrowPower.rgba32.png differ diff --git a/soh/assets/soh_assets.h b/soh/assets/soh_assets.h index 2b193053c3b..c3d8216afbc 100644 --- a/soh/assets/soh_assets.h +++ b/soh/assets/soh_assets.h @@ -61,6 +61,18 @@ static const ALIGN_ASSET(2) char gTriforcePieceCompletedDL[] = dgTriforcePieceCo static const ALIGN_ASSET(2) char gOptionsDividerChangeLangVtx[] = dgOptionsDividerChangeLangVtx; // textures +#define dgArrowIconTex "__OTR__textures/icon_item_static/gArrowIconTex" +static const ALIGN_ASSET(2) char gArrowIconTex[] = dgArrowIconTex; + +#define dgFireArrowPower "__OTR__textures/icon_item_static/gFireArrowPower" +static const ALIGN_ASSET(2) char gFireArrowPowerTex[] = dgFireArrowPower; + +#define dgIceArrowPower "__OTR__textures/icon_item_static/gIceArrowPower" +static const ALIGN_ASSET(2) char gIceArrowPowerTex[] = dgIceArrowPower; + +#define dgLightArrowPower "__OTR__textures/icon_item_static/gLightArrowPower" +static const ALIGN_ASSET(2) char gLightArrowPowerTex[] = dgLightArrowPower; + #define dgDPad "__OTR__textures/parameter_static/gDPad" static const ALIGN_ASSET(2) char gDPadTex[] = dgDPad; diff --git a/soh/include/functions.h b/soh/include/functions.h index 3a1aa8ae24a..3af7011647e 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -1140,6 +1140,7 @@ s32 Player_ActionToBottle(Player* player, s32 actionParam); s32 Player_GetBottleHeld(Player* player); s32 Player_ActionToExplosive(Player* player, s32 actionParam); s32 Player_GetExplosiveHeld(Player* player); +bool Player_CanSwitchArrows(Player* player); s32 func_8008F2BC(Player* player, s32 actionParam); s32 Player_GetEnvironmentalHazard(PlayState* play); void Player_DrawImpl(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dListCount, s32 lod, s32 tunic, 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..2a5ae5cd966 100644 --- a/soh/soh/Enhancements/controls/GameControlEditor.cpp +++ b/soh/soh/Enhancements/controls/GameControlEditor.cpp @@ -85,6 +85,9 @@ namespace GameControlEditor { static CustomButtonMap ocarinaSharp = {"Pitch up", "gOcarinaSharpBtnMap", BTN_R}; static CustomButtonMap ocarinaFlat = {"Pitch down", "gOcarinaFlatBtnMap", BTN_Z}; + // Misc. + static CustomButtonMap arrowSwitch = {"Switch arrows", "gArrowSwitchBtnMap", BTN_R}; + void GameControlEditorWindow::InitElement() { addButtonName(BTN_A, "A"); addButtonName(BTN_B, "B"); @@ -258,24 +261,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); } @@ -295,6 +302,9 @@ namespace GameControlEditor { "To make the cursor only move a single space during name entry no matter how long a direction is held, manually set gDpadHoldChange to 0"); UIWidgets::PaddedEnhancementCheckbox("D-pad as Equip Items", "gDpadEquips"); DrawHelpIcon("Equip items and equipment on the D-pad\nIf used with D-pad on Pause Screen, you must hold C-Up to equip instead of navigate"); + UIWidgets::PaddedEnhancementCheckbox("Toggle minimap with D-pad down", "gMapOnDDown"); + DrawHelpIcon("Toggle the minimap by pressing down on the D-pad\nIf \"D-pad as Equip Items\" is enabled, equipping an item on D-pad down will prevent " + "you from toggling the map, but you can clear the item on D-pad down by re-equipping it over itself."); window->EndGroupPanelPublic(0); } @@ -328,6 +338,25 @@ namespace GameControlEditor { UIWidgets::Spacer(0); UIWidgets::PaddedEnhancementCheckbox("Answer Navi Prompt with L Button", "gNaviOnL"); DrawHelpIcon("Speak to Navi with L but enter first-person camera with C-Up"); + + float longestLabelWidth = ImGui::CalcTextSize(arrowSwitch.label).x + 10; + + // Switch arrows + bool arrowSwitchingEnabled = CVarGetInteger("gArrowSwitching", 0); + if (!arrowSwitchingEnabled) { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + } + N64ButtonMask arrowSwitchAllowedButtons = BTN_A | BTN_L | BTN_R | BTN_CUP; + DrawMapping(arrowSwitch, longestLabelWidth, ~arrowSwitchAllowedButtons); + if (!arrowSwitchingEnabled) { + ImGui::PopStyleVar(1); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("%s", "This option is disabled because Arrow Switching from Enhancements > Gameplay > Time Savers is disabled"); + } + ImGui::PopItemFlag(); + } + window->EndGroupPanelPublic(0); } diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index 378197bcdd8..f38a98a715b 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -1328,7 +1328,7 @@ void Draw_Placements(){ ImGui::EndTable(); } } - if (CVarGetInteger("gDpadEquips",0) && ImGui::CollapsingHeader("DPad items position")) { + if ((CVarGetInteger("gDpadEquips",0) || CVarGetInteger("gAltItemMenu", 0)) && ImGui::CollapsingHeader("DPad items position")) { if (ImGui::BeginTable("tabledpaditems", 1, FlagsTable)) { ImGui::TableSetupColumn("DPad items settings", FlagsCell, TablesCellsWidth); Table_InitHeader(false); 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 d93bae98357..05a79849827 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h @@ -54,6 +54,9 @@ typedef enum { 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 + TEXT_FIRE_ARROW = 0x0070, + TEXT_ICE_ARROW = 0x0071, + TEXT_LIGHT_ARROW = 0x0072, } TextIDs; #ifdef __cplusplus diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index 9af4eadb7e9..19ca9f96980 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -1705,10 +1705,12 @@ void DrawPlayerTab() { ImGui::SameLine(); ImGui::InputScalar("C Right", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[3], &one, NULL); - if (CVarGetInteger("gDpadEquips", 0)) { + if (CVarGetInteger("gDpadEquips", 0) || CVarGetInteger("gAltItemMenu", 0)) { ImGui::NewLine(); ImGui::Text("Current D-pad Equips"); ImGui::InputScalar("D-pad Up ", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[4], &one, NULL); // Two spaces at the end for aligning, not elegant but it's working + } + if (CVarGetInteger("gDpadEquips", 0)) { ImGui::SameLine(); ImGui::InputScalar("D-pad Down", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[5], &one, NULL); // Intentionnal to not put everything on the same line, else it's taking too much for lower resolution. 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 4f8ef259b4d..b56a001d206 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -198,7 +198,7 @@ class GameInteractor { 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/mods.cpp b/soh/soh/Enhancements/mods.cpp index 6692dd5c971..75268251e97 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; } @@ -1067,6 +1070,71 @@ void RegisterRandomizedEnemySizes() { }); } +void AltItemMenu_Update() { + if (CVarGetInteger("gAltItemMenu", 0)) { // Enabled: force equip ocarina on D-up and match equipped bow, if any + uint16_t targetCBtn = 3; + uint16_t targetButtonIndex = targetCBtn + 1; + for (uint16_t otherSlotIndex = 0; otherSlotIndex < ARRAY_COUNT(gSaveContext.equips.cButtonSlots); otherSlotIndex++) { + uint16_t otherButtonIndex = otherSlotIndex + 1; + if (otherSlotIndex == targetCBtn) { + continue; + } + + if (gSaveContext.equips.cButtonSlots[otherSlotIndex] == SLOT_OCARINA) { + // Assign the other button to the target's current item + if (gSaveContext.equips.buttonItems[targetButtonIndex] != ITEM_NONE) { + gSaveContext.equips.buttonItems[otherButtonIndex] = + gSaveContext.equips.buttonItems[targetButtonIndex]; + gSaveContext.equips.cButtonSlots[otherSlotIndex] = + gSaveContext.equips.cButtonSlots[targetCBtn]; + Interface_LoadItemIcon2(gPlayState, otherButtonIndex); + } else { + gSaveContext.equips.buttonItems[otherButtonIndex] = ITEM_NONE; + gSaveContext.equips.cButtonSlots[otherSlotIndex] = SLOT_NONE; + } + //break; // 'Assume there is only one possible pre-existing equip' + } + + if (gSaveContext.equips.cButtonSlots[otherSlotIndex] == SLOT_BOW) { + switch (gSaveContext.equips.buttonItems[otherButtonIndex]) { + case ITEM_BOW_ARROW_FIRE: + case ITEM_BOW_ARROW_ICE: + case ITEM_BOW_ARROW_LIGHT: + INV_CONTENT(ITEM_BOW) = gSaveContext.equips.buttonItems[otherButtonIndex]; + break; + } + } + } + + gSaveContext.equips.buttonItems[targetButtonIndex] = INV_CONTENT(ITEM_OCARINA_FAIRY); + gSaveContext.equips.cButtonSlots[targetCBtn] = SLOT_OCARINA; + Interface_LoadItemIcon1(gPlayState, targetButtonIndex); + } else { // Disabled: reset bow item to Fairy Bow if some magic arrow is equipped + switch (INV_CONTENT(ITEM_BOW)) { + case ITEM_BOW_ARROW_FIRE: + case ITEM_BOW_ARROW_ICE: + case ITEM_BOW_ARROW_LIGHT: + INV_CONTENT(ITEM_BOW) = ITEM_BOW; + break; + } + } +} + +void AltItemMenu_Register() { + static int altItemMenuOld = -1; + GameInteractor::Instance->RegisterGameHook([]() { + int altItemMenu = CVarGetInteger("gAltItemMenu", 0); + if (altItemMenu != altItemMenuOld) { + AltItemMenu_Update(); + } + altItemMenuOld = altItemMenu; + }); + + GameInteractor::Instance->RegisterGameHook([](int16_t _) { + AltItemMenu_Update(); + }); +} + void InitMods() { RegisterTTS(); RegisterInfiniteMoney(); @@ -1096,4 +1164,5 @@ void InitMods() { RegisterRandomizerSheikSpawn(); RegisterRandomizedEnemySizes(); NameTag_RegisterHooks(); + AltItemMenu_Register(); } 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 22c9dd7db3d..44cf0f81c58 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); @@ -73,6 +79,7 @@ const std::vector enhancementsCvars = { "gFasterHeavyBlockLift", "gNoForcedNavi", "gSkulltulaFreeze", + "gMMPoeBottling", "gMMBunnyHood", "gAdultBunnyHood", "gFastChests", @@ -170,6 +177,7 @@ const std::vector enhancementsCvars = { "gRestoreRBAValues", "gSkipSaveConfirmation", "gAutosave", + "gSaveAndQuit", "gDisableCritWiggle", "gChestSizeDependsStoneOfAgony", "gSkipArrowAnimation", @@ -184,6 +192,7 @@ const std::vector enhancementsCvars = { "gBombchuBowlingNoBigCucco", "gBombchuBowlingAmmunition", "gCreditsFix", + "gArrowSwitching", "gSilverRupeeJingleExtend", "gStaticExplosionRadius", "gNoInputForCredits", @@ -211,6 +220,9 @@ const std::vector enhancementsCvars = { "gFPSGauntlets", "gSceneSpecificDirtPathFix", "gZFightingMode", + "gVisualKeys", + "gSmallKeySpacing", + "gRightAlignKeys", "gAuthenticLogo", "gPauseLiveLinkRotationSpeed", "gBowReticle", @@ -242,6 +254,7 @@ const std::vector enhancementsCvars = { "gAddTraps.Speed", "gAddTraps.Tele", "gAddTraps.Void", + "gAltItemMenu", }; const std::vector cheatCvars = { @@ -866,7 +879,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), @@ -958,7 +972,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), @@ -1011,7 +1026,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_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 4b9a84eff2d..835621fd35f 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -4207,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", @@ -4236,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", @@ -4265,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", @@ -4294,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", @@ -4689,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(); @@ -4866,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(); @@ -5070,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(); } @@ -5108,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 d9c2424af51..b9ca97a9b2a 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -107,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; @@ -225,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; @@ -233,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); @@ -346,6 +369,7 @@ void ClearAreaChecksAndTotals() { for (auto& [rcArea, vec] : checksByArea) { vec.clear(); areaChecksGotten[rcArea] = 0; + areaCheckTotals[rcArea] = 0; } } @@ -425,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) { @@ -437,8 +461,11 @@ void CheckTrackerLoadGame(int32_t fileNum) { } 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 || @@ -514,10 +542,6 @@ void CheckTrackerTransition(uint32_t sceneNum) { } void CheckTrackerFrame() { - if (IS_RANDO) { - hideShopRightChecks = CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1); - alwaysShowGS = CVarGetInteger("gCheckTrackerOptionAlwaysShowGSLocs", 0); - } if (!GameInteractor::IsSaveLoaded()) { return; } @@ -787,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; @@ -902,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(); @@ -940,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(); @@ -958,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("???"); @@ -977,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; } @@ -1200,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() { @@ -1229,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; } @@ -1270,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) { @@ -1385,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)); @@ -1412,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; @@ -1520,9 +1568,15 @@ 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); + 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."); - UIWidgets::EnhancementCheckbox("Always show gold skulltulas", "gCheckTrackerOptionAlwaysShowGSLocs", false, ""); + 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(); @@ -1577,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 4c564afde72..c3a8fe9f210 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -297,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()); @@ -1143,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 @@ -2486,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; @@ -2552,6 +2551,9 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { if (textId == TEXT_MARKET_GUARD_NIGHT && CVarGetInteger("gMarketSneak", 0) && play->sceneNum == SCENE_MARKET_ENTRANCE_NIGHT) { messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, TEXT_MARKET_GUARD_NIGHT); } + if (textId >= TEXT_FIRE_ARROW && textId <= TEXT_LIGHT_ARROW && CVarGetInteger("gAltItemMenu", 0)) { + messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, textId); + } font->charTexBuf[0] = (messageEntry.GetTextBoxType() << 4) | messageEntry.GetTextBoxPosition(); switch (gSaveContext.language) { case LANGUAGE_FRA: @@ -2597,6 +2599,24 @@ extern "C" void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* repla gfx_register_blended_texture(name, mask, replacement); } +extern "C" void Gfx_UnregisterBlendedTexture(const char* name) { + gfx_unregister_blended_texture(name); +} + +extern "C" void Gfx_TextureCacheDelete(const uint8_t* texAddr) { + char* imgName = (char*)texAddr; + + if (texAddr == nullptr) { + return; + } + + if (ResourceMgr_OTRSigCheck(imgName)) { + texAddr = (const uint8_t*)GetResourceDataByNameHandlingMQ(imgName); + } + + gfx_texture_cache_delete(texAddr); +} + void SoH_ProcessDroppedFiles(std::string filePath) { try { std::ifstream configStream(filePath); diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index 9b42e689550..a93df7efbf2 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -173,6 +173,8 @@ 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(); 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 94b9e690d5c..90f6e59e5ea 100644 --- a/soh/soh/SohGui.cpp +++ b/soh/soh/SohGui.cpp @@ -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 2f654c04f91..e8a03f31180 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -391,7 +391,7 @@ void DrawSettingsMenu() { UIWidgets::Tooltip("Changes the scaling of the ImGui menu elements."); UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - + static std::unordered_map windowBackendNames = { { LUS::WindowBackend::DX11, "DirectX" }, { LUS::WindowBackend::SDL_OPENGL, "OpenGL"}, @@ -468,9 +468,9 @@ void DrawSettingsMenu() { } ImGui::EndMenu(); } - + UIWidgets::Spacer(0); - + if (ImGui::BeginMenu("Accessibility")) { #if defined(_WIN32) || defined(__APPLE__) UIWidgets::PaddedEnhancementCheckbox("Text to Speech", "gA11yTTS"); @@ -478,7 +478,7 @@ void DrawSettingsMenu() { #endif UIWidgets::PaddedEnhancementCheckbox("Disable Idle Camera Re-Centering", "gA11yDisableIdleCam"); UIWidgets::Tooltip("Disables the automatic re-centering of the camera when idle."); - + ImGui::EndMenu(); } ImGui::EndMenu(); @@ -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"); @@ -604,10 +604,17 @@ void DrawEnhancementsMenu() { UIWidgets::Tooltip("Allows the bunny hood to be equipped normally from the pause menu as adult."); UIWidgets::PaddedEnhancementCheckbox("Mask Select in Inventory", "gMaskSelect", true, false); UIWidgets::Tooltip("After completing the mask trading sub-quest, press A and any direction on the mask slot to change masks"); + UIWidgets::PaddedEnhancementCheckbox("Catch Poes with a bottle", "gMMPoeBottling", true, false); + UIWidgets::Tooltip("Catch Poes by swinging an empty bottle at them instead of from a text box like you can in Majora's Mask."); UIWidgets::PaddedEnhancementCheckbox("Nuts explode bombs", "gNutsExplodeBombs", true, false); UIWidgets::Tooltip("Makes nuts explode bombs, similar to how they interact with bombchus. This does not affect bombflowers."); - UIWidgets::PaddedEnhancementCheckbox("Equip Multiple Arrows at Once", "gSeparateArrows", true, false); + bool disableSeparateArrows = CVarGetInteger("gAltItemMenu", 0); + static const char* disableSeparateArrowsText = "This setting is diabled because \"Rearrange Item Menu\" is on."; + UIWidgets::PaddedEnhancementCheckbox("Equip Multiple Arrows at Once", "gSeparateArrows", true, false, disableSeparateArrows, disableSeparateArrowsText); UIWidgets::Tooltip("Allow the bow and magic arrows to be equipped at the same time on different slots. (Note this will disable the behaviour of the 'Equip Dupe' glitch)"); + UIWidgets::PaddedEnhancementCheckbox("Switch Arrow Types", "gArrowSwitching", true, false); + UIWidgets::Tooltip("Press R with the bow out to switch between normal, fire, ice, and light arrows\n" + "Use the \"Customize Game Controls\" window to switch with a different button"); UIWidgets::PaddedEnhancementCheckbox("Bow as Child/Slingshot as Adult", "gBowSlingShotAmmoFix", true, false); UIWidgets::Tooltip("Allows child to use bow with arrows.\nAllows adult to use slingshot with seeds.\n\nRequires glitches or 'Timeless Equipment' cheat to equip."); UIWidgets::PaddedEnhancementCheckbox("Better Farore's Wind", "gBetterFW", true, false); @@ -858,8 +865,14 @@ void DrawEnhancementsMenu() { UIWidgets::Tooltip("Displays an icon and plays a sound when Stone of Agony should be activated, for those without rumble"); UIWidgets::PaddedEnhancementCheckbox("Assignable Tunics and Boots", "gAssignableTunicsAndBoots", true, false); UIWidgets::Tooltip("Allows equipping the tunic and boots to c-buttons"); + UIWidgets::PaddedEnhancementCheckbox("Rearrange Item Menu", "gAltItemMenu", true, false); + UIWidgets::Tooltip("Change the organization of the item subscreen so that boots behave as regular items without the need for " + "\"Assignable Tunics and Boots\". Magic arrows are now accessed from the bow's slot, and the ocarina is always " + "equipped on D-pad up."); UIWidgets::PaddedEnhancementCheckbox("Equipment Toggle", "gEquipmentCanBeRemoved", true, false); UIWidgets::Tooltip("Allows equipment to be removed by toggling it off on\nthe equipment subscreen."); + UIWidgets::PaddedEnhancementCheckbox("Extra Underwater Actions", "gEnhancedIronBoots", true, false); + UIWidgets::Tooltip("Allows opening chests and using your sword and Bombchus when underwater with Iron Boots"); UIWidgets::PaddedEnhancementCheckbox("Link's Cow in Both Time Periods", "gCowOfTime", true, false); UIWidgets::Tooltip("Allows the Lon Lon Ranch obstacle course reward to be shared across time periods"); UIWidgets::PaddedEnhancementCheckbox("Enable visible guard vision", "gGuardVision", true, false); @@ -878,7 +891,7 @@ void DrawEnhancementsMenu() { OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_BLUE_FIRE_ARROWS); static const char* forceEnableBlueFireArrowsText = "This setting is forcefully enabled because a savefile\nwith \"Blue Fire Arrows\" is loaded."; - UIWidgets::PaddedEnhancementCheckbox("Blue Fire Arrows", "gBlueFireArrows", true, false, + UIWidgets::PaddedEnhancementCheckbox("Blue Fire Arrows", "gBlueFireArrows", true, false, forceEnableBlueFireArrows, forceEnableBlueFireArrowsText, UIWidgets::CheckboxGraphics::Checkmark); UIWidgets::Tooltip("Allows Ice Arrows to melt red ice.\nMay require a room reload if toggled during gameplay."); @@ -887,7 +900,7 @@ void DrawEnhancementsMenu() { OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SUNLIGHT_ARROWS); static const char* forceEnableSunLightArrowsText = "This setting is forcefully enabled because a savefile\nwith \"Sunlight Arrows\" is loaded."; - UIWidgets::PaddedEnhancementCheckbox("Sunlight Arrows", "gSunlightArrows", true, false, + UIWidgets::PaddedEnhancementCheckbox("Sunlight Arrows", "gSunlightArrows", true, false, forceEnableSunLightArrows, forceEnableSunLightArrowsText, UIWidgets::CheckboxGraphics::Checkmark); UIWidgets::Tooltip("Allows Light Arrows to activate sun switches.\nMay require a room reload if toggled during gameplay."); @@ -899,6 +912,11 @@ void DrawEnhancementsMenu() { UIWidgets::PaddedEnhancementCheckbox("Targetable Hookshot Reticle", "gHookshotableReticle", true, false); UIWidgets::Tooltip("Use a different color when aiming at hookshotable collision"); + UIWidgets::PaddedEnhancementCheckbox("Ask to continue playing after saving", "gSaveAndQuit", true, false); + UIWidgets::Tooltip( + "The save dialog from the pause menu will ask you to continue playing after you select Yes or No.\n" + "Pressing B or Start on the save prompt will close the pause menu without displaying the extra screen."); + ImGui::EndMenu(); } @@ -1001,6 +1019,11 @@ void DrawEnhancementsMenu() { "Consistent: Certain paths vanish the same way in all resolutions\n" "No Vanish: Paths do not vanish, Link seems to sink in to some paths\n" "This might affect other decal effects\n"); + UIWidgets::PaddedEnhancementCheckbox("Visual Small Key display", "gVisualKeys", true, false); + UIWidgets::Tooltip("Displays Small Key count using multiple icons rather than a numeric counter"); + const bool disableKeySpacing = !CVarGetInteger("gVisualKeys", 0); + static const char* disableKeySpacingTooltip = "This option is disabled because \"Visual Small Key display\" is turned off"; + UIWidgets::EnhancementSliderInt("Small Key icon spacing: %d", "##SmallKeySpacing", "gSmallKeySpacing", 1, 16, "", 8, true, disableKeySpacing, disableKeySpacingTooltip); UIWidgets::PaddedEnhancementSliderInt("Text Spacing: %d", "##TEXTSPACING", "gTextSpacing", 4, 6, "", 6, true, true, true); UIWidgets::Tooltip("Space between text characters (useful for HD font textures)"); UIWidgets::PaddedEnhancementCheckbox("More info in file select", "gFileSelectMoreInfo", true, false); @@ -1064,8 +1087,8 @@ void DrawEnhancementsMenu() { "Fixes an incorrect calculation that acted like water underneath ground was above it."); UIWidgets::PaddedEnhancementCheckbox("Fix Bush Item Drops", "gBushDropFix", true, false); UIWidgets::Tooltip("Fixes the bushes to drop items correctly rather than spawning undefined items."); - UIWidgets::PaddedEnhancementCheckbox("Fix falling from vine edges", "gFixVineFall", true, false); - UIWidgets::Tooltip("Prevents immediately falling off climbable surfaces if climbing on the edges."); + UIWidgets::PaddedEnhancementCheckbox("Fix falling from vine edges", "gFixVineFall", true, false); + UIWidgets::Tooltip("Prevents immediately falling off climbable surfaces if climbing on the edges."); UIWidgets::PaddedEnhancementCheckbox("Fix Link's eyes open while sleeping", "gFixEyesOpenWhileSleeping", true, false); UIWidgets::Tooltip("Fixes Link's eyes being open in the opening cutscene when he is supposed to be sleeping."); UIWidgets::PaddedEnhancementCheckbox("Fix Darunia dancing too fast", "gEnhancements.FixDaruniaDanceSpeed", @@ -1398,12 +1421,12 @@ void DrawCheatsMenu() { if (ImGui::Button("Change Age")) { CVarSetInteger("gSwitchAge", 1); } - UIWidgets::Tooltip("Switches Link's age and reloads the area."); + UIWidgets::Tooltip("Switches Link's age and reloads the area."); if (ImGui::Button("Clear Cutscene Pointer")) { GameInteractor::RawAction::ClearCutscenePointer(); } - UIWidgets::Tooltip("Clears the cutscene pointer to a value safe for wrong warps."); + UIWidgets::Tooltip("Clears the cutscene pointer to a value safe for wrong warps."); ImGui::EndDisabled(); @@ -1583,12 +1606,12 @@ void DrawRandomizerMenu() { OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_BOSS_KEYSANITY) == RO_DUNGEON_ITEM_LOC_ANYWHERE || (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GANONS_BOSS_KEY) != RO_GANON_BOSS_KEY_VANILLA && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GANONS_BOSS_KEY) != RO_GANON_BOSS_KEY_OWN_DUNGEON && - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GANONS_BOSS_KEY) != RO_GANON_BOSS_KEY_STARTWITH) || + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GANONS_BOSS_KEY) != RO_GANON_BOSS_KEY_STARTWITH) || !IS_RANDO) { disableKeyColors = false; } - static const char* disableKeyColorsText = + static const char* disableKeyColorsText = "This setting is disabled because a savefile is loaded without any key\n" "shuffle settings set to \"Any Dungeon\", \"Overworld\" or \"Anywhere\""; @@ -1644,4 +1667,4 @@ void SohMenuBar::DrawElement() { ImGui::EndMenuBar(); } } -} // namespace SohGui +} // namespace SohGui 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/z_message_OTR.cpp b/soh/soh/z_message_OTR.cpp index 6bbd1eca9bd..fe4c65cefb8 100644 --- a/soh/soh/z_message_OTR.cpp +++ b/soh/soh/z_message_OTR.cpp @@ -160,4 +160,37 @@ extern "C" void OTRMessage_Init() CustomMessage("You look bored. Wanna go out for a&walk?\x1B&%gYes&No%w", "Du siehst gelangweilt aus.&Willst du einen Spaziergang machen?\x1B&%gJa&Nein%w", "Tu as l'air de t'ennuyer. Tu veux&aller faire un tour?\x1B&%gOui&Non%w")); + CustomMessageManager::Instance->CreateGetItemMessage( + customMessageTableID, (GetItemID)TEXT_FIRE_ARROW, ITEM_ARROW_FIRE, + CustomMessage( + "You got the %rFire Arrow%w!&Any target it strikes will&catch fire.^" + "Select your bow with %b\x9f%w to&open the arrow submenu.&Tilt %c\xaa%w to equip your bow&with powered-up arrows!", + "You got the %rFire Arrow%w!&Any target it strikes will&catch fire.^" + "Select your bow with %b\x9f%w to&open the arrow submenu.&Tilt %c\xaa%w to equip your bow&with powered-up arrows!", + "You got the %rFire Arrow%w!&Any target it strikes will&catch fire.^" + "Select your bow with %b\x9f%w to&open the arrow submenu.&Tilt %c\xaa%w to equip your bow&with powered-up arrows!", + TEXTBOX_TYPE_BLUE)); + CustomMessageManager::Instance->CreateGetItemMessage( + customMessageTableID, (GetItemID)TEXT_ICE_ARROW, ITEM_ARROW_ICE, + CustomMessage( + "You got the %bIce Arrow%w!&Any target it strikes&will freeze.^" + "This arrow magic is granted&only to those who complete the&difficult training of the Gerudo,&so use it with pride!^" + "Select your bow with %b\x9f%w to&open the arrow submenu.&Tilt %c\xaa%w to equip your bow&with powered-up arrows!", + "You got the %bIce Arrow%w!&Any target it strikes&will freeze.^" + "This arrow magic is granted&only to those who complete the&difficult training of the Gerudo,&so use it with pride!^" + "Select your bow with %b\x9f%w to&open the arrow submenu.&Tilt %c\xaa%w to equip your bow&with powered-up arrows!", + "You got the %bIce Arrow%w!&Any target it strikes&will freeze.^" + "This arrow magic is granted&only to those who complete the&difficult training of the Gerudo,&so use it with pride!^" + "Select your bow with %b\x9f%w to&open the arrow submenu.&Tilt %c\xaa%w to equip your bow&with powered-up arrows!", + TEXTBOX_TYPE_BLUE)); + CustomMessageManager::Instance->CreateGetItemMessage( + customMessageTableID, (GetItemID)TEXT_LIGHT_ARROW, ITEM_ARROW_LIGHT, + CustomMessage( + "You got the %cLight Arrow%w!&The light of justice will&smite evil!^" + "Select your bow with %b\x9f%w to&open the arrow submenu.&Tilt %c\xaa%w to equip your bow&with powered-up arrows!", + "You got the %cLight Arrow%w!&The light of justice will&smite evil!^" + "Select your bow with %b\x9f%w to&open the arrow submenu.&Tilt %c\xaa%w to equip your bow&with powered-up arrows!", + "You got the %cLight Arrow%w!&The light of justice will&smite evil!^" + "Select your bow with %b\x9f%w to&open the arrow submenu.&Tilt %c\xaa%w to equip your bow&with powered-up arrows!", + TEXTBOX_TYPE_BLUE)); } 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_en_item00.c b/soh/src/code/z_en_item00.c index 42ea78f90ba..de5b91d8a85 100644 --- a/soh/src/code/z_en_item00.c +++ b/soh/src/code/z_en_item00.c @@ -333,7 +333,9 @@ void EnItem00_SetupAction(EnItem00* this, EnItem00ActionFunc actionFunc) { void EnItem00_SetObjectDependency(EnItem00* this, PlayState* play, s16 objectIndex) { // Remove object dependency for Enemy Randomizer and Crowd Control to allow Like-likes to // drop equipment correctly in rooms where Like-likes normally don't spawn. - if (CVarGetInteger("gRandomizedEnemies", 0) || CVarGetInteger("gCrowdControl", 0)) { + // Also remove object dependency for Master Quest Fire Temple to fix the Like-like there + if (CVarGetInteger("gRandomizedEnemies", 0) || CVarGetInteger("gCrowdControl", 0) || + (ResourceMgr_IsSceneMasterQuest(SCENE_FIRE_TEMPLE) && play->sceneNum == SCENE_FIRE_TEMPLE)) { this->actor.objBankIndex = 0; } else { this->actor.objBankIndex = Object_GetIndex(&play->objectCtx, objectIndex); diff --git a/soh/src/code/z_map_exp.c b/soh/src/code/z_map_exp.c index c4db0098b23..2f201fd724e 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); @@ -734,10 +733,20 @@ void Minimap_Draw(PlayState* play) { OPEN_DISPS(play->state.gfxCtx); - // If any of these CVars are enabled, disable toggling the minimap with L, unless gEnableMapToggle is set - bool enableMapToggle = - !(CVarGetInteger("gDebugEnabled", 0) || CVarGetInteger("gMoonJumpOnL", 0) || CVarGetInteger("gTurboOnL", 0)) || - CVarGetInteger("gEnableMapToggle", 0); + u16 minimapToggleBtn; + bool enableMapToggle; + if (CVarGetInteger("gMapOnDDown", 0)) { + minimapToggleBtn = BTN_DDOWN; + // If you can use an item with D down, disable toggling the minimap with it + enableMapToggle = !(CVarGetInteger("gDpadEquips", 0) && gSaveContext.equips.buttonItems[5] < 0xF0); + } else { + minimapToggleBtn = BTN_L; + // If any of these CVars are enabled, disable toggling the minimap with L + enableMapToggle = !(CVarGetInteger("gDebugEnabled", 0) || CVarGetInteger("gMoonJumpOnL", 0) || CVarGetInteger("gTurboOnL", 0) || + (CVarGetInteger("gArrowSwitchBtnMap", BTN_R) == BTN_L && Player_CanSwitchArrows(GET_PLAYER(play)))); + } + // If this CVar is set, allow toggling the map despite conflicts + enableMapToggle = enableMapToggle || CVarGetInteger("gEnableMapToggle", 0); if (play->pauseCtx.state < 4) { //Minimap margins @@ -808,7 +817,7 @@ void Minimap_Draw(PlayState* play) { } } - if (CHECK_BTN_ALL(play->state.input[0].press.button, BTN_L) && !Play_InCsMode(play) && enableMapToggle) { + if (CHECK_BTN_ALL(play->state.input[0].press.button, minimapToggleBtn) && !Play_InCsMode(play) && enableMapToggle) { osSyncPrintf("Game_play_demo_mode_check=%d\n", Play_InCsMode(play)); // clang-format off if (!R_MINIMAP_DISABLED) { Audio_PlaySoundGeneral(NA_SE_SY_CAMERA_ZOOM_UP, &D_801333D4, 4, @@ -964,7 +973,7 @@ void Minimap_Draw(PlayState* play) { Minimap_DrawCompassIcons(play); // Draw icons for the player spawn and current position } - if (CHECK_BTN_ALL(play->state.input[0].press.button, BTN_L) && !Play_InCsMode(play) && enableMapToggle) { + if (CHECK_BTN_ALL(play->state.input[0].press.button, minimapToggleBtn) && !Play_InCsMode(play) && enableMapToggle) { // clang-format off if (!R_MINIMAP_DISABLED) { Audio_PlaySoundGeneral(NA_SE_SY_CAMERA_ZOOM_UP, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); } diff --git a/soh/src/code/z_message_PAL.c b/soh/src/code/z_message_PAL.c index 7bb3803fcc6..87cff43da7c 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" @@ -3069,7 +3070,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 a4d61f87769..3034eddaa87 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -964,11 +964,19 @@ void func_80083108(PlayState* play) { gSaveContext.buttonStatus[3] = gSaveContext.buttonStatus[5] = gSaveContext.buttonStatus[6] = gSaveContext.buttonStatus[7] = gSaveContext.buttonStatus[8] = BTN_DISABLED; } else if ((Player_GetEnvironmentalHazard(play) >= 2) && (Player_GetEnvironmentalHazard(play) < 5)) { - if (gSaveContext.buttonStatus[0] != BTN_DISABLED) { - sp28 = 1; - } + if (CVarGetInteger("gEnhancedIronBoots", 0) && Player_GetEnvironmentalHazard(play) == 2) { + if (gSaveContext.buttonStatus[0] == BTN_DISABLED) { + sp28 = 1; + } + + gSaveContext.buttonStatus[0] = BTN_ENABLED; + } else { + if (gSaveContext.buttonStatus[0] != BTN_DISABLED) { + sp28 = 1; + } - gSaveContext.buttonStatus[0] = BTN_DISABLED; + gSaveContext.buttonStatus[0] = BTN_DISABLED; + } for (i = 1; i < ARRAY_COUNT(gSaveContext.equips.buttonItems); i++) { if ((gSaveContext.equips.buttonItems[i] >= ITEM_SHIELD_DEKU) && @@ -981,7 +989,9 @@ void func_80083108(PlayState* play) { gSaveContext.buttonStatus[BUTTON_STATUS_INDEX(i)] = BTN_ENABLED; } else if (Player_GetEnvironmentalHazard(play) == 2) { if ((gSaveContext.equips.buttonItems[i] != ITEM_HOOKSHOT) && - (gSaveContext.equips.buttonItems[i] != ITEM_LONGSHOT)) { + (gSaveContext.equips.buttonItems[i] != ITEM_LONGSHOT) && + (CVarGetInteger("gEnhancedIronBoots", 0) + && gSaveContext.equips.buttonItems[i] != ITEM_BOMBCHU)) { if (gSaveContext.buttonStatus[BUTTON_STATUS_INDEX(i)] == BTN_ENABLED) { sp28 = 1; } @@ -1441,7 +1451,7 @@ void Inventory_SwapAgeEquipment(void) { u16 shieldEquipValue; if (LINK_AGE_IN_YEARS == YEARS_CHILD) { - + for (i = 0; i < ARRAY_COUNT(gSaveContext.equips.buttonItems); i++) { if (i != 0) { @@ -1483,13 +1493,13 @@ void Inventory_SwapAgeEquipment(void) { gSaveContext.equips.buttonItems[3] = gSaveContext.inventory.items[SLOT_OCARINA]; gSaveContext.equips.cButtonSlots[1] = SLOT_BOMB; gSaveContext.equips.cButtonSlots[2] = SLOT_OCARINA; - + gSaveContext.equips.equipment = (EQUIP_VALUE_SWORD_MASTER << (EQUIP_TYPE_SWORD * 4)) | (EQUIP_VALUE_SHIELD_HYLIAN << (EQUIP_TYPE_SHIELD * 4)) | (EQUIP_VALUE_TUNIC_KOKIRI << (EQUIP_TYPE_TUNIC * 4)) | (EQUIP_VALUE_BOOTS_KOKIRI << (EQUIP_TYPE_BOOTS * 4)); - if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) && + if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) && gSaveContext.equips.buttonItems[0] == ITEM_NONE) { gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4)); } @@ -1828,14 +1838,14 @@ u8 Return_Item(u8 itemID, ModIndex modId, ItemID returnItem) { /** * @brief Adds the given item to Link's inventory. - * + * * NOTE: This function has been edited to be safe to use with a NULL play. * If you need to add to this function, be sure you check if the play is not * NULL before doing any operations requiring it. - * - * @param play - * @param item - * @return u8 + * + * @param play + * @param item + * @return u8 */ u8 Item_Give(PlayState* play, u8 item) { lusprintf(__FILE__, __LINE__, 2, "Item Give - item: %#x", item); @@ -1916,10 +1926,10 @@ u8 Item_Give(PlayState* play, u8 item) { // In rando, when buying Giant's Knife, also check // without the Koriri Sword in case we don't have it - if (ALL_EQUIP_VALUE(EQUIP_TYPE_SWORD) == + if (ALL_EQUIP_VALUE(EQUIP_TYPE_SWORD) == ((1 << EQUIP_INV_SWORD_KOKIRI) | (1 << EQUIP_INV_SWORD_MASTER) | (1 << EQUIP_INV_SWORD_BIGGORON) | - (1 << EQUIP_INV_SWORD_BROKENGIANTKNIFE)) || - (IS_RANDO && ALL_EQUIP_VALUE(EQUIP_TYPE_SWORD) == + (1 << EQUIP_INV_SWORD_BROKENGIANTKNIFE)) || + (IS_RANDO && ALL_EQUIP_VALUE(EQUIP_TYPE_SWORD) == ((1 << EQUIP_INV_SWORD_MASTER) | (1 << EQUIP_INV_SWORD_BIGGORON) | (1 << EQUIP_INV_SWORD_BROKENGIANTKNIFE)))) { gSaveContext.inventory.equipment ^= OWNED_EQUIP_FLAG_ALT(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BROKENGIANTKNIFE); @@ -1931,7 +1941,7 @@ u8 Item_Give(PlayState* play, u8 item) { } } } - + } else if (item == ITEM_SWORD_MASTER) { gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER; gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4)); @@ -2671,7 +2681,7 @@ u8 Item_CheckObtainability(u8 item) { return ITEM_NONE; } } - + if ((item >= ITEM_SONG_MINUET) && (item <= ITEM_SONG_STORMS)) { return ITEM_NONE; } else if ((item >= ITEM_MEDALLION_FOREST) && (item <= ITEM_MEDALLION_LIGHT)) { @@ -2935,7 +2945,7 @@ bool Inventory_HatchPocketCucco(PlayState* play) { return Inventory_ReplaceItem(play, ITEM_POCKET_EGG, ITEM_POCKET_CUCCO); } - if (!PLAYER_HAS_SHUFFLED_ADULT_TRADE_ITEM(ITEM_POCKET_EGG)) { + if (!PLAYER_HAS_SHUFFLED_ADULT_TRADE_ITEM(ITEM_POCKET_EGG)) { return 0; } @@ -2974,7 +2984,7 @@ void Interface_LoadActionLabel(InterfaceContext* interfaceCtx, u16 action, s16 l } doAction = newName[loadOffset]; } - + char* segment = interfaceCtx->doActionSegment[loadOffset]; interfaceCtx->doActionSegment[loadOffset] = action != DO_ACTION_NONE ? doAction : gEmptyTexture; gSegments[7] = interfaceCtx->doActionSegment[loadOffset]; @@ -3041,7 +3051,7 @@ void Interface_LoadActionLabelB(PlayState* play, u16 action) { } interfaceCtx->unk_1FC = action; - + char* segment = interfaceCtx->doActionSegment[1]; interfaceCtx->doActionSegment[1] = action != DO_ACTION_NONE ? doAction : gEmptyTexture; osRecvMesg(&interfaceCtx->loadQueue, NULL, OS_MESG_BLOCK); @@ -3066,7 +3076,7 @@ s32 Health_ChangeBy(PlayState* play, s16 healthChange) { if (healthChange < 0) { gSaveContext.health = 0; } - + return 0; } @@ -3135,7 +3145,7 @@ void Rupees_ChangeBy(s16 rupeeChange) { void GameplayStats_UpdateAmmoUsed(s16 item, s16 ammoUsed) { - switch (item) { + switch (item) { case ITEM_STICK: gSaveContext.sohStats.count[COUNT_AMMO_USED_STICK] += ammoUsed; break; @@ -4321,7 +4331,7 @@ void Interface_DrawItemButtons(PlayState* play) { G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); - gSPWideTextureRectangle(OVERLAY_DISP++, C_Up_BTN_Pos[0]-LabelX_Navi << 2, C_Up_BTN_Pos[1]+LabelY_Navi << 2, + gSPWideTextureRectangle(OVERLAY_DISP++, C_Up_BTN_Pos[0]-LabelX_Navi << 2, C_Up_BTN_Pos[1]+LabelY_Navi << 2, (C_Up_BTN_Pos[0]-LabelX_Navi + 32) << 2, (C_Up_BTN_Pos[1]+LabelY_Navi + 8) << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); } @@ -4439,7 +4449,7 @@ void Interface_DrawItemButtons(PlayState* play) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, cRightButtonColor.r, cRightButtonColor.g, cRightButtonColor.b, interfaceCtx->cRightAlpha); } - OVERLAY_DISP = Gfx_TextureIA8(OVERLAY_DISP, ((u8*)gButtonBackgroundTex), 32, 32, + OVERLAY_DISP = Gfx_TextureIA8(OVERLAY_DISP, ((u8*)gButtonBackgroundTex), 32, 32, ItemIconPos[temp-1][0], ItemIconPos[temp-1][1], ItemIconWidthFactor[temp-1][0], ItemIconWidthFactor[temp-1][0], ItemIconWidthFactor[temp-1][1], ItemIconWidthFactor[temp-1][1]); @@ -4514,8 +4524,8 @@ void Interface_DrawItemIconTexture(PlayState* play, void* texture, s16 button) { { C_DOWN_BUTTON_X+X_Margins_CD, C_DOWN_BUTTON_Y+Y_Margins_CD }, { C_RIGHT_BUTTON_X+X_Margins_CR, C_RIGHT_BUTTON_Y+Y_Margins_CR }, { DPAD_UP_X+X_Margins_DPad_Items, DPAD_UP_Y+Y_Margins_DPad_Items }, - { DPAD_DOWN_X+X_Margins_DPad_Items, DPAD_DOWN_Y+Y_Margins_DPad_Items }, - { DPAD_LEFT_X+X_Margins_DPad_Items, DPAD_LEFT_Y+Y_Margins_DPad_Items }, + { DPAD_DOWN_X+X_Margins_DPad_Items, DPAD_DOWN_Y+Y_Margins_DPad_Items }, + { DPAD_LEFT_X+X_Margins_DPad_Items, DPAD_LEFT_Y+Y_Margins_DPad_Items }, { DPAD_RIGHT_X+X_Margins_DPad_Items, DPAD_RIGHT_Y+Y_Margins_DPad_Items } }; u16 ItemsSlotsAlpha[8] = { @@ -4647,10 +4657,17 @@ void Interface_DrawItemIconTexture(PlayState* play, void* texture, s16 button) { ItemIconPos[3][1] = ItemIconPos_ori[3][1]; } - gDPLoadTextureBlock(OVERLAY_DISP++, texture, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, 0, G_TX_NOMIRROR | G_TX_WRAP, + // Exception specifically for dungeon map texture since only that appears on a button + bool smallTex = texture == gItemIcons[ITEM_DUNGEON_MAP]; + int texSize = smallTex ? 24 : 32; + int offset = smallTex ? 4 : 0; + + gDPLoadTextureBlock(OVERLAY_DISP++, texture, G_IM_FMT_RGBA, G_IM_SIZ_32b, texSize, texSize, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); - gSPWideTextureRectangle(OVERLAY_DISP++, ItemIconPos[button][0] << 2, ItemIconPos[button][1] << 2, + gSPWideTextureRectangle(OVERLAY_DISP++, + (ItemIconPos[button][0] + offset << 2), + (ItemIconPos[button][1] + offset << 2), (ItemIconPos[button][0] + gItemIconWidth[button]) << 2, (ItemIconPos[button][1] + gItemIconWidth[button]) << 2, G_TX_RENDERTILE, 0, 0, gItemIconDD[button] << 1, gItemIconDD[button] << 1); @@ -4891,11 +4908,11 @@ void Interface_DrawAmmoCount(PlayState* play, s16 button, s16 alpha) { } if (i != 0) { - OVERLAY_DISP = Gfx_TextureIA8(OVERLAY_DISP, (u8*)_gAmmoDigit0Tex[i], 8, 8, + OVERLAY_DISP = Gfx_TextureIA8(OVERLAY_DISP, (u8*)_gAmmoDigit0Tex[i], 8, 8, ItemIconPos[button][0], ItemIconPos[button][1], 8, 8, 1 << 10, 1 << 10); } - OVERLAY_DISP = Gfx_TextureIA8(OVERLAY_DISP, (u8*)_gAmmoDigit0Tex[ammo], 8, 8, + OVERLAY_DISP = Gfx_TextureIA8(OVERLAY_DISP, (u8*)_gAmmoDigit0Tex[ammo], 8, 8, ItemIconPos[button][0] + 6, ItemIconPos[button][1], 8, 8, 1 << 10, 1 << 10); } @@ -5254,6 +5271,21 @@ void Interface_Draw(PlayState* play) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, keyCountColor.r,keyCountColor.g,keyCountColor.b, interfaceCtx->magicAlpha); gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 20, 255); //We reset this here so it match user color :) + + if (CVarGetInteger("gVisualKeys", 0)) { + s8 keyCount = gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex]; + s16 rectLeft = PosX_SKC; + s16 keyOffset = CVarGetInteger("gSmallKeySpacing", 8); + if (CVarGetInteger("gRightAlignKeys", 0)) { + keyOffset = -keyOffset; + } + for (int i = 0; i < keyCount; i++, rectLeft += keyOffset) { + OVERLAY_DISP = Gfx_TextureIA8(OVERLAY_DISP, gSmallKeyCounterIconTex, 16, 16, rectLeft, PosY_SKC, 16, 16, + 1 << 10, 1 << 10); + } + break; + } + OVERLAY_DISP = Gfx_TextureIA8(OVERLAY_DISP, gSmallKeyCounterIconTex, 16, 16, PosX_SKC, PosY_SKC, 16, 16, 1 << 10, 1 << 10); @@ -5400,7 +5432,7 @@ void Interface_Draw(PlayState* play) { PosY_adjust = 6; PosX_adjust = -10; } - + s16 BbtnPosX; s16 BbtnPosY; s16 X_Margins_BtnB_label; @@ -5484,7 +5516,9 @@ void Interface_Draw(PlayState* play) { Interface_DrawAmmoCount(play, 3, interfaceCtx->cRightAlpha); } - if (CVarGetInteger("gDpadEquips", 0) != 0) { + bool altItemMenu = CVarGetInteger("gAltItemMenu", 0); + bool dpadEquips = CVarGetInteger("gDpadEquips", 0); + if (dpadEquips || altItemMenu) { // DPad is only greyed-out when all 4 DPad directions are too uint16_t dpadAlpha = MAX(MAX(MAX(interfaceCtx->dpadUpAlpha, interfaceCtx->dpadDownAlpha), interfaceCtx->dpadLeftAlpha), @@ -5531,7 +5565,9 @@ void Interface_Draw(PlayState* play) { (DpadPosX + 32) << 2, (DpadPosY + 32) << 2, G_TX_RENDERTILE, 0, 0, (1 << 10), (1 << 10)); } + } + if (dpadEquips || altItemMenu) { // DPad-Up Button Icon & Ammo Count if (gSaveContext.equips.buttonItems[4] < 0xF0) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->dpadUpAlpha); @@ -5544,16 +5580,26 @@ void Interface_Draw(PlayState* play) { } // DPad-Down Button Icon & Ammo Count - if (gSaveContext.equips.buttonItems[5] < 0xF0) { + bool itemOnDpad = dpadEquips && gSaveContext.equips.buttonItems[5] < 0xF0; + bool dpadMap = fullUi && CVarGetInteger("gMapOnDDown", 0) && + play->pauseCtx.state < 4 && + ((play->sceneNum >= SCENE_DEKU_TREE && play->sceneNum <= SCENE_ICE_CAVERN) || + (play->sceneNum >= SCENE_HYRULE_FIELD && play->sceneNum <= SCENE_OUTSIDE_GANONS_CASTLE)); + if (itemOnDpad || dpadMap) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->dpadDownAlpha); gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATERGBA_PRIM, G_CC_MODULATERGBA_PRIM); - Interface_DrawItemIconTexture(play, gItemIcons[gSaveContext.equips.buttonItems[5]], 5); + void* texture = gItemIcons[itemOnDpad ? gSaveContext.equips.buttonItems[5] : ITEM_DUNGEON_MAP]; + Interface_DrawItemIconTexture(play, texture, 5); gDPPipeSync(OVERLAY_DISP++); gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0); - Interface_DrawAmmoCount(play, 5, interfaceCtx->dpadDownAlpha); + if (itemOnDpad) { + Interface_DrawAmmoCount(play, 5, interfaceCtx->dpadDownAlpha); + } } + } + if (dpadEquips) { // DPad-Left Button Icon & Ammo Count if (gSaveContext.equips.buttonItems[6] < 0xF0) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->dpadLeftAlpha); @@ -5718,7 +5764,7 @@ void Interface_Draw(PlayState* play) { CarrotsPosY = CVarGetInteger("gCarrotsPosY", 0); if (CVarGetInteger("gCarrotsPosType", 0) == 1) {//Anchor Left if (CVarGetInteger("gCarrotsUseMargins", 0) != 0) {CarrotsMargins_X = Left_HUD_Margin;}; - CarrotsPosX = OTRGetDimensionFromLeftEdge(CVarGetInteger("gCarrotsPosX", 0)+CarrotsMargins_X); + CarrotsPosX = OTRGetDimensionFromLeftEdge(CVarGetInteger("gCarrotsPosX", 0)+CarrotsMargins_X); } else if (CVarGetInteger("gCarrotsPosType", 0) == 2) {//Anchor Right if (CVarGetInteger("gCarrotsUseMargins", 0) != 0) {CarrotsMargins_X = Right_HUD_Margin;}; CarrotsPosX = OTRGetDimensionFromRightEdge(CVarGetInteger("gCarrotsPosX", 0)+CarrotsMargins_X); @@ -6228,7 +6274,7 @@ void Interface_Draw(PlayState* play) { for (svar1 = 0; svar1 < 5; svar1++) { // clang-format off //svar5 = svar5 + 8; - //svar5 = OTRGetRectDimensionFromLeftEdge(gSaveContext.timerX[svar6]); + //svar5 = OTRGetRectDimensionFromLeftEdge(gSaveContext.timerX[svar6]); OVERLAY_DISP = Gfx_TextureI8(OVERLAY_DISP, digitTextures[timerDigits[svar1]], 8, 16, svar5 + timerDigitLeftPos[svar1], svar2, digitWidth[svar1], VREG(42), VREG(43) << 1, @@ -6372,7 +6418,7 @@ void Interface_Update(PlayState* play) { Left_HUD_Margin = CVarGetInteger("gHUDMargin_L", 0); Right_HUD_Margin = CVarGetInteger("gHUDMargin_R", 0); Bottom_HUD_Margin = CVarGetInteger("gHUDMargin_B", 0); - + GameInteractor_ExecuteOnInterfaceUpdate(); if (CHECK_BTN_ALL(debugInput->press.button, BTN_DLEFT)) { 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 deb761641de..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 @@ -123,7 +119,9 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() { sMaskTexLava[i] = maskVal; } } + Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, NULL); + Gfx_TextureCacheDelete(sMaskTexLava); return; } @@ -165,7 +163,9 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() { } } - gfx_texture_cache_clear(); + Gfx_TextureCacheDelete(sMaskTexLava); + Gfx_TextureCacheDelete(sLavaWavyTex); + Gfx_TextureCacheDelete(sLavaFloorModifiedTex); } void func_808C12C4(u8* arg1, s16 arg2) { @@ -228,6 +228,7 @@ void func_808C1554_Raw(void* arg0, void* floorTex, s32 arg2, f32 arg3) { } free(sp54); + Gfx_TextureCacheDelete(sLavaWavyTexRaw); } // Modified to support CPU modified texture with the resource system @@ -255,6 +256,8 @@ void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) { temp_s3[i + temp2] = sp54[i + i2]; } } + + Gfx_TextureCacheDelete(sLavaWavyTex); } void func_808C17C8(PlayState* play, Vec3f* arg1, Vec3f* arg2, Vec3f* arg3, f32 arg4, s16 arg5) { @@ -373,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 @@ -1206,6 +1216,7 @@ void BossDodongo_Update(Actor* thisx, PlayState* play2) { } } else { sMaskTexLava[new_var] = 1; + Gfx_TextureCacheDelete(sMaskTexLava); } this->unk_1C2 += 37; @@ -1345,18 +1356,6 @@ void BossDodongo_Draw(Actor* thisx, PlayState* play) { gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTex32x16); } - gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTexLava); - - // Using WORK_DISP to invalidate these textures as they are used in drawing the scene textures which happens - // before actors are drawn. WORK_DISP comes before POLAY_OPA_DISP. It is probably not meant for this, but it - // at least works for now. - // Alternatively, having a way to invalidate just these pointers from the Update func should be sufficient. - if (sLavaFloorModifiedTexRaw != NULL) { - gSPInvalidateTexCache(WORK_DISP++, sLavaWavyTexRaw); - } else { - gSPInvalidateTexCache(WORK_DISP++, sLavaWavyTex); - } - if ((this->unk_1C0 >= 2) && (this->unk_1C0 & 1)) { 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_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_Po_Field/z_en_po_field.c b/soh/src/overlays/actors/ovl_En_Po_Field/z_en_po_field.c index c11feb327d9..b7f2f5bb4ec 100644 --- a/soh/src/overlays/actors/ovl_En_Po_Field/z_en_po_field.c +++ b/soh/src/overlays/actors/ovl_En_Po_Field/z_en_po_field.c @@ -658,7 +658,7 @@ void func_80AD58D4(EnPoField* this, PlayState* play) { if (this->actionTimer != 0) { this->actionTimer--; } - if (Actor_ProcessTalkRequest(&this->actor, play)) { + if (!CVarGetInteger("gMMPoeBottling", 0) && Actor_ProcessTalkRequest(&this->actor, play)) { EnPoField_SetupInteractWithSoul(this); return; } @@ -668,7 +668,15 @@ void func_80AD58D4(EnPoField* this, PlayState* play) { EnPoField_SetupSoulDisappear(this); return; } - if (this->collider.base.ocFlags1 & OC1_HIT) { + if (CVarGetInteger("gMMPoeBottling", 0)) { + // do not collide + if (Actor_HasParent(&this->actor, play)) { + Actor_Kill(&this->actor); + } + + // GI_MAX in this case allows the player to catch the actor in a bottle + func_8002F434(&this->actor, play, GI_MAX, 35.0f, 60.0f); + } else if (this->collider.base.ocFlags1 & OC1_HIT) { this->actor.flags |= ACTOR_FLAG_WILL_TALK; func_8002F2F4(&this->actor, play); } else { diff --git a/soh/src/overlays/actors/ovl_En_Poh/z_en_poh.c b/soh/src/overlays/actors/ovl_En_Poh/z_en_poh.c index 809150fe630..fb682b7037e 100644 --- a/soh/src/overlays/actors/ovl_En_Poh/z_en_poh.c +++ b/soh/src/overlays/actors/ovl_En_Poh/z_en_poh.c @@ -770,7 +770,8 @@ void func_80ADFE80(EnPoh* this, PlayState* play) { if (this->unk_198 != 0) { this->unk_198--; } - if (Actor_ProcessTalkRequest(&this->actor, play)) { + if (!(CVarGetInteger("gMMPoeBottling", 0) && this->actor.params < EN_POH_SHARP) && + Actor_ProcessTalkRequest(&this->actor, play)) { if (this->actor.params >= EN_POH_SHARP) { func_80ADE9BC(this); } else { @@ -783,7 +784,15 @@ void func_80ADFE80(EnPoh* this, PlayState* play) { this->actor.flags &= ~ACTOR_FLAG_WILL_TALK; return; } - if (this->colliderCyl.base.ocFlags1 & OC1_HIT) { + if (CVarGetInteger("gMMPoeBottling", 0) && this->actor.params < EN_POH_SHARP) { + if (Actor_HasParent(&this->actor, play)) { + Actor_Kill(&this->actor); + return; + } + + // GI_MAX in this case allows the player to catch the actor in a bottle + func_8002F434(&this->actor, play, GI_MAX, 35.0f, 60.0f); + } else if (this->colliderCyl.base.ocFlags1 & OC1_HIT) { this->actor.flags |= ACTOR_FLAG_WILL_TALK; func_8002F2F4(&this->actor, play); } else { 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_Obj_Tsubo/z_obj_tsubo.c b/soh/src/overlays/actors/ovl_Obj_Tsubo/z_obj_tsubo.c index 2346b34ffeb..b6a7a85911c 100644 --- a/soh/src/overlays/actors/ovl_Obj_Tsubo/z_obj_tsubo.c +++ b/soh/src/overlays/actors/ovl_Obj_Tsubo/z_obj_tsubo.c @@ -86,6 +86,13 @@ static InitChainEntry sInitChain[] = { void ObjTsubo_SpawnCollectible(ObjTsubo* this, PlayState* play) { s16 dropParams = this->actor.params & 0x1F; + // Avoid spawning a Deku Shield if the item isn't loaded. + // Targeted fix for a crash due to a broken drop from the pot in Spirit + // Temple surrounded by a circling spike trap. + if (dropParams == ITEM00_SHIELD_DEKU && Object_GetIndex(&play->objectCtx, OBJECT_GI_SHIELD_1) == -1) { + return; + } + if ((dropParams >= ITEM00_RUPEE_GREEN) && (dropParams <= ITEM00_BOMBS_SPECIAL)) { Item_DropCollectible(play, &this->actor.world.pos, (dropParams | (((this->actor.params >> 9) & 0x3F) << 8))); 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 ca7333497a0..f18562caf56 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -2029,7 +2029,15 @@ s32 func_80833C98(s32 item1, s32 actionParam) { } s32 func_80833CDC(PlayState* play, s32 index) { - if (index >= ((CVarGetInteger("gDpadEquips", 0) != 0) ? 8 : 4)) { + int buttons; + if (CVarGetInteger("gDpadEquips", 0)) { + buttons = 8; + } else if (CVarGetInteger("gAltItemMenu", 0)) { + buttons = 5; + } else { + buttons = 4; + } + if (index >= buttons) { return ITEM_NONE; } else if (play->bombchuBowlingStatus != 0) { return (play->bombchuBowlingStatus > 0) ? ITEM_BOMBCHU : ITEM_NONE; @@ -2088,6 +2096,28 @@ void func_80833DF8(Player* this, PlayState* play) { } } + // Alt item menu: Since boots are items, take them off if not equipped on a button + if (CVarGetInteger("gAltItemMenu", 0) && this->currentBoots != PLAYER_BOOTS_KOKIRI) { + s32 bootsItemAction = this->currentBoots + PLAYER_IA_BOOTS_KOKIRI; + + bool hasOnCBtn = false; + bool hasOnDpad = false; + for (int buttonIndex = 0; buttonIndex < 3; buttonIndex++) { + hasOnCBtn |= func_80833C98(C_BTN_ITEM(buttonIndex), bootsItemAction); + } + if (CVarGetInteger("gDpadEquips", 0)) { + for (int buttonIndex = 1; buttonIndex < 4; buttonIndex++) { + hasOnDpad |= func_80833C98(DPAD_ITEM(buttonIndex), bootsItemAction); + } + } + + if (!hasOnCBtn && !hasOnDpad) { + Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, PLAYER_BOOTS_KOKIRI + 1); + Player_SetEquipmentData(play, this); + func_808328EC(this, NA_SE_PL_CHANGE_ARMS); + } + } + if (!(this->stateFlags1 & (PLAYER_STATE1_ITEM_OVER_HEAD | PLAYER_STATE1_IN_CUTSCENE)) && !func_8008F128(this)) { if (this->itemAction >= PLAYER_IA_FISHING_POLE) { bool hasOnDpad = false; @@ -2218,6 +2248,98 @@ s32 func_80834380(PlayState* play, Player* this, s32* itemPtr, s32* typePtr) { } } +typedef struct { + u8 asArrow; + u8 asBowArrow; +} ArrowItems; + +static ArrowItems arrowTypeToItem[] = { + /* normal arrows */ { ITEM_BOW, ITEM_BOW }, + /* fire arrows */ { ITEM_ARROW_FIRE, ITEM_BOW_ARROW_FIRE }, + /* ice arrows */ { ITEM_ARROW_ICE, ITEM_BOW_ARROW_ICE }, + /* light arrows */ { ITEM_ARROW_LIGHT, ITEM_BOW_ARROW_LIGHT }, + /* unused arrow types are excluded from cycling */ +}; + +static bool gSwitchingArrow = false; + +// Returns true if the player is in a state where they can switch arrows. +// Specifically, the gArrowSwitching CVar is enabled, the player is holding the +// bow with normal, fire, ice, or light arrows, and they're either aiming or +// have an arrow notched. +bool Player_CanSwitchArrows(Player* this) { + if (!CVarGetInteger("gArrowSwitching", 0)) { + return false; + } + + if (this->heldItemAction < PLAYER_IA_BOW || this->heldItemAction > PLAYER_IA_BOW_LIGHT) { + return false; + } + + return this->func_82C == func_808351D4 || this->func_82C == func_808353D8; +} + +bool Player_SwitchArrowsIfEnabled(Player* this) { + if (!CVarGetInteger("gArrowSwitching", 0)) { + return false; + } + if (this->heldItemAction < PLAYER_IA_BOW || this->heldItemAction > PLAYER_IA_BOW_LIGHT) { + return false; + } + if (!CHECK_BTN_ANY(sControlInput->press.button, CVarGetInteger("gArrowSwitchBtnMap", BTN_R))) { + return false; + } + + u8 i, newItem, newItemAction; + const u8 arrowCount = ARRAY_COUNT(arrowTypeToItem); + u8 heldArrowAP = this->heldItemAction - PLAYER_IA_BOW; + for (i = 1; i < arrowCount; i++) { + u8 arrowAP = (heldArrowAP + i) % arrowCount; + ArrowItems items = arrowTypeToItem[arrowAP]; + if (INV_CONTENT(items.asArrow) != ITEM_NONE) { + newItem = items.asBowArrow; + newItemAction = PLAYER_IA_BOW + arrowAP; + break; + } + } + if (i == arrowCount) { + return false; + } + + gSaveContext.equips.buttonItems[this->heldItemButton] = newItem; + this->heldItemId = newItem; + this->itemAction = newItemAction; + this->heldItemAction = newItemAction; + return true; +} + +// Replace previously nocked arrow with the new arrow type +void Player_ReplaceSwitchedArrow(PlayState* play, Player* this) { + s32 item, arrowType; + if (this->unk_860 < 0 || func_80834380(play, this, &item, &arrowType) <= 0) { + return; + } + + s32 newMagicArrowType = arrowType - ARROW_FIRE; + s32 arrowCost; + if (newMagicArrowType < 0 || newMagicArrowType > 2) { + arrowCost = 0; + } else { + arrowCost = sMagicArrowCosts[newMagicArrowType]; + } + + if (arrowCost == 0 || !Magic_RequestChange(play, arrowCost, MAGIC_CONSUME_WAIT_PREVIEW)) { + arrowType = ARROW_NORMAL; + } + + if (this->heldActor != NULL) { + Actor_Kill(this->heldActor); + } + this->heldActor = Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_EN_ARROW, + this->actor.world.pos.x, this->actor.world.pos.y, + this->actor.world.pos.z, 0, this->actor.shape.rot.y, 0, arrowType); +} + // The player has pressed the bow or hookshot button s32 func_8083442C(Player* this, PlayState* play) { s32 item; @@ -2241,7 +2363,8 @@ s32 func_8083442C(Player* this, PlayState* play) { if (this->unk_860 >= 0) { if ((magicArrowType >= 0) && (magicArrowType <= 2) && - !Magic_RequestChange(play, sMagicArrowCosts[magicArrowType], MAGIC_CONSUME_NOW)) { + !Magic_RequestChange(play, sMagicArrowCosts[magicArrowType], + CVarGetInteger("gArrowSwitching", 0) ? 4 : MAGIC_CONSUME_NOW)) { arrowType = ARROW_NORMAL; } @@ -2308,7 +2431,7 @@ s32 func_80834758(PlayState* play, Player* this) { if (!(this->stateFlags1 & (PLAYER_STATE1_SHIELDING | PLAYER_STATE1_ON_HORSE | PLAYER_STATE1_IN_CUTSCENE)) && (play->shootingGalleryStatus == 0) && (this->heldItemAction == this->itemAction) && (this->currentShield != PLAYER_SHIELD_NONE) && !Player_IsChildWithHylianShield(this) && func_80833BCC(this) && - CHECK_BTN_ALL(sControlInput->cur.button, BTN_R)) { + !Player_CanSwitchArrows(this) && CHECK_BTN_ALL(sControlInput->cur.button, BTN_R)) { anim = func_808346C4(play, this); frame = Animation_GetLastFrame(anim); @@ -2473,6 +2596,9 @@ s32 func_80834E44(PlayState* play) { s32 func_80834E7C(PlayState* play) { u16 buttonsToCheck = BTN_A | BTN_B | BTN_CUP | BTN_CLEFT | BTN_CRIGHT | BTN_CDOWN; + if (CVarGetInteger("gAltItemMenu", 0)) { + buttonsToCheck |= BTN_DUP; + } if (CVarGetInteger("gDpadEquips", 0) != 0) { buttonsToCheck |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; } @@ -2551,6 +2677,12 @@ s32 func_808350A4(PlayState* play, Player* this) { } } else { Inventory_ChangeAmmo(item, -1); + if (CVarGetInteger("gArrowSwitching", 0) && + arrowType >= ARROW_FIRE && arrowType <= ARROW_LIGHT + && this->heldActor->child != NULL) { + gSaveContext.magicState = 0; + Magic_RequestChange(play, sMagicArrowCosts[arrowType - ARROW_FIRE], MAGIC_CONSUME_NOW); + } } if (play->shootingGalleryStatus == 1) { @@ -2604,6 +2736,11 @@ s32 func_808351D4(Player* this, PlayState* play) { func_80834EB8(this, play); + if (gSwitchingArrow) { + Player_ReplaceSwitchedArrow(play, this); + gSwitchingArrow = false; + } + if ((this->unk_836 > 0) && ((this->unk_860 < 0) || (!D_80853618 && !func_80834E7C(play)))) { func_80833638(this, func_808353D8); if (this->unk_860 >= 0) { @@ -2621,6 +2758,14 @@ s32 func_808351D4(Player* this, PlayState* play) { this->stateFlags1 |= PLAYER_STATE1_READY_TO_FIRE; } + gSwitchingArrow = Player_SwitchArrowsIfEnabled(this); + if (gSwitchingArrow && this->heldActor != NULL) { + if (this->heldActor->child != NULL) { + Actor_Kill(this->heldActor->child); + } + gSwitchingArrow = true; + } + return 1; } @@ -2631,6 +2776,8 @@ s32 func_808353D8(Player* this, PlayState* play) { return 1; } + Player_SwitchArrowsIfEnabled(this); + if (!func_80834758(play, this) && (D_80853614 || ((this->unk_860 < 0) && D_80853618) || func_80834E44(play))) { this->unk_860 = ABS(this->unk_860); @@ -2960,7 +3107,9 @@ void func_80835F44(PlayState* play, Player* this, s32 item) { if ((actionParam == PLAYER_IA_NONE) || !(this->stateFlags1 & PLAYER_STATE1_IN_WATER) || ((this->actor.bgCheckFlags & 1) && - ((actionParam == PLAYER_IA_HOOKSHOT) || (actionParam == PLAYER_IA_LONGSHOT))) || + ((actionParam == PLAYER_IA_HOOKSHOT) || (actionParam == PLAYER_IA_LONGSHOT) || + (CVarGetInteger("gEnhancedIronBoots", 0) && + ((Player_ActionToMeleeWeapon(actionParam) != 0) || (actionParam == PLAYER_IA_BOMBCHU))))) || ((actionParam >= PLAYER_IA_SHIELD_DEKU) && (actionParam <= PLAYER_IA_BOOTS_HOVER))) { if ((play->bombchuBowlingStatus == 0) && @@ -4934,7 +5083,7 @@ s32 func_8083AD4C(PlayState* play, Player* this) { if(CVarGetInteger("gBowSlingShotAmmoFix", 0)){ shouldUseBowCamera = this->heldItemAction != PLAYER_IA_SLINGSHOT; } - + cameraMode = shouldUseBowCamera ? CAM_MODE_BOWARROW : CAM_MODE_SLINGSHOT; } else { cameraMode = CAM_MODE_BOOMERANG; @@ -5213,7 +5362,11 @@ s32 func_8083B644(Player* this, PlayState* play) { this->stateFlags2 |= PLAYER_STATE2_NAVI_ALERT; } - if (!CHECK_BTN_ALL(sControlInput->press.button, CVarGetInteger("gNaviOnL", 0) ? BTN_L : BTN_CUP) && + u16 naviButton = CVarGetInteger("gNaviOnL", 0) ? BTN_L : BTN_CUP; + if (Player_CanSwitchArrows(this)) { + naviButton &= ~CVarGetInteger("gArrowSwitchBtnMap", BTN_R); + } + if (!(naviButton && CHECK_BTN_ALL(sControlInput->press.button, naviButton)) && !sp28) { return 0; } @@ -5378,7 +5531,7 @@ s32 func_8083BDBC(Player* this, PlayState* play) { if (sp2C == 2) { gSaveContext.sohStats.count[COUNT_BACKFLIPS]++; } - + return 1; } } @@ -6106,8 +6259,8 @@ void func_8083DFE0(Player* this, f32* arg1, s16* arg2) { if (CVarGetInteger("gMMBunnyHood", BUNNY_HOOD_VANILLA) == BUNNY_HOOD_FAST_AND_JUMP && this->currentMask == PLAYER_MASK_BUNNY) { maxSpeed *= 1.5f; - } - + } + if (CVarGetInteger("gEnableWalkModify", 0) && !CVarGetInteger("gWalkModifierDoesntChangeJump", 0)) { if (CVarGetInteger("gWalkSpeedToggle", 0)) { if (gWalkSpeedToggle1) { @@ -6350,8 +6503,8 @@ s32 func_8083E5A8(Player* this, PlayState* play) { uint8_t showItemCutscene = play->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY || Item_CheckObtainability(giEntry.itemId) == ITEM_NONE || IS_RANDO; // Only skip cutscenes for drops when they're items/consumables from bushes/rocks/enemies. - uint8_t isDropToSkip = (interactedActor->id == ACTOR_EN_ITEM00 && interactedActor->params != 6 && interactedActor->params != 17) || - interactedActor->id == ACTOR_EN_KAREBABA || + uint8_t isDropToSkip = (interactedActor->id == ACTOR_EN_ITEM00 && interactedActor->params != 6 && interactedActor->params != 17) || + interactedActor->id == ACTOR_EN_KAREBABA || interactedActor->id == ACTOR_EN_DEKUBABA; // Skip cutscenes from picking up consumables with "Fast Pickup Text" enabled, even when the player never picked it up before. @@ -6386,7 +6539,7 @@ s32 func_8083E5A8(Player* this, PlayState* play) { this->getItemEntry = (GetItemEntry)GET_ITEM_NONE; } } else if (CHECK_BTN_ALL(sControlInput->press.button, BTN_A) && !(this->stateFlags1 & PLAYER_STATE1_ITEM_OVER_HEAD) && - !(this->stateFlags2 & PLAYER_STATE2_UNDERWATER)) { + (CVarGetInteger("gEnhancedIronBoots", 0) || !(this->stateFlags2 & PLAYER_STATE2_UNDERWATER))) { if (this->getItemId != GI_NONE) { GetItemEntry giEntry; if (this->getItemEntry.objectId == OBJECT_INVALID) { @@ -6483,6 +6636,9 @@ s32 func_8083EAF0(Player* this, Actor* actor) { s32 func_8083EB44(Player* this, PlayState* play) { u16 buttonsToCheck = BTN_A | BTN_B | BTN_CLEFT | BTN_CRIGHT | BTN_CDOWN; + if (CVarGetInteger("gAltItemMenu", 0)) { + buttonsToCheck |= BTN_DUP; + } if (CVarGetInteger("gDpadEquips", 0) != 0) { buttonsToCheck |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; } @@ -7759,7 +7915,7 @@ void func_80842180(Player* this, PlayState* play) { if (CVarGetInteger("gMMBunnyHood", BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA && this->currentMask == PLAYER_MASK_BUNNY) { sp2C *= 1.5f; } - + if (CVarGetInteger("gEnableWalkModify", 0)) { if (CVarGetInteger("gWalkSpeedToggle", 0)) { if (gWalkSpeedToggle1) { @@ -8547,6 +8703,9 @@ void func_8084411C(Player* this, PlayState* play) { Actor* heldActor = this->heldActor; u16 buttonsToCheck = BTN_A | BTN_B | BTN_CLEFT | BTN_CRIGHT | BTN_CDOWN; + if (CVarGetInteger("gAltItemMenu", 0)) { + buttonsToCheck |= BTN_DUP; + } if (CVarGetInteger("gDpadEquips", 0) != 0) { buttonsToCheck |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; } @@ -9329,6 +9488,9 @@ void func_80846260(Player* this, PlayState* play) { } u16 buttonsToCheck = BTN_A | BTN_B | BTN_CLEFT | BTN_CRIGHT | BTN_CDOWN; + if (CVarGetInteger("gAltItemMenu", 0)) { + buttonsToCheck |= BTN_DUP; + } if (CVarGetInteger("gDpadEquips", 0) != 0) { buttonsToCheck |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; } @@ -9841,7 +10003,8 @@ void func_808473D4(PlayState* play, Player* this) { } else if ((!(this->stateFlags1 & PLAYER_STATE1_ITEM_OVER_HEAD) || (heldActor == NULL)) && (interactRangeActor != NULL) && ((!sp1C && (this->getItemId == GI_NONE)) || - (this->getItemId < 0 && !(this->stateFlags1 & PLAYER_STATE1_IN_WATER)))) { + (this->getItemId < 0 && !(this->stateFlags1 & PLAYER_STATE1_IN_WATER)) || + CVarGetInteger("gEnhancedIronBoots", 0) && this->stateFlags2 & PLAYER_STATE2_UNDERWATER)) { if (this->getItemId < 0) { doAction = DO_ACTION_OPEN; } else if ((interactRangeActor->id == ACTOR_BG_TOKI_SWD) && LINK_IS_ADULT) { @@ -11194,7 +11357,7 @@ void Player_Update(Actor* thisx, PlayState* play) { // Play fan sound (too annoying) //func_8002F974(&player->actor, NA_SE_EV_WIND_TRAP - SFX_FLAG); } - + GameInteractor_ExecuteOnPlayerUpdate(); } @@ -11245,7 +11408,7 @@ void Player_DrawGameplay(PlayState* play, Player* this, s32 lod, Gfx* cullDList, MATRIX_TOMTX(sp70); } - + if (this->currentMask != PLAYER_MASK_BUNNY || !CVarGetInteger("gHideBunnyHood", 0)) { gSPDisplayList(POLY_OPA_DISP++, sMaskDlists[this->currentMask - 1]); } @@ -11597,15 +11760,23 @@ void func_8084B1D8(Player* this, PlayState* play) { func_80836670(this, play); } - u16 buttonsToCheck = BTN_A | BTN_B | BTN_R | BTN_CUP | BTN_CLEFT | BTN_CRIGHT | BTN_CDOWN; + u16 itemButtons = BTN_A | BTN_B | BTN_R | BTN_CUP | BTN_CLEFT | BTN_CRIGHT | BTN_CDOWN; + if (CVarGetInteger("gAltItemMenu", 0)) { + itemButtons |= BTN_DUP; + } if (CVarGetInteger("gDpadEquips", 0) != 0) { - buttonsToCheck |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; + itemButtons |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; + } + u16 returnButtons = BTN_A | BTN_B | BTN_R; + if (Player_CanSwitchArrows(this)) { + itemButtons &= ~CVarGetInteger("gArrowSwitchBtnMap", BTN_R); + returnButtons &= ~CVarGetInteger("gArrowSwitchBtnMap", BTN_R); } if ((this->csMode != 0) || (this->unk_6AD == 0) || (this->unk_6AD >= 4) || func_80833B54(this) || (this->unk_664 != NULL) || !func_8083AD4C(play, this) || - (((this->unk_6AD == 2) && (CHECK_BTN_ANY(sControlInput->press.button, BTN_A | BTN_B | BTN_R) || + (((this->unk_6AD == 2) && (CHECK_BTN_ANY(sControlInput->press.button, returnButtons) || func_80833B2C(this) || (!func_8002DD78(this) && !func_808334B4(this)))) || - ((this->unk_6AD == 1) && CHECK_BTN_ANY(sControlInput->press.button, buttonsToCheck)))) { + ((this->unk_6AD == 1) && CHECK_BTN_ANY(sControlInput->press.button, itemButtons)))) { func_8083C148(this, play); func_80078884(NA_SE_SY_CAMERA_ZOOM_UP); } else if ((DECR(this->unk_850) == 0) || (this->unk_6AD != 2)) { @@ -13247,6 +13418,8 @@ static BottleCatchInfo D_80854A04[] = { { ACTOR_EN_FISH, ITEM_FISH, 0x1F, 0x47 }, { ACTOR_EN_ICE_HONO, ITEM_BLUE_FIRE, 0x20, 0x5D }, { ACTOR_EN_INSECT, ITEM_BUG, 0x21, 0x7A }, + { ACTOR_EN_POH, ITEM_POE, PLAYER_IA_BOTTLE_POE, 0x97 }, + { ACTOR_EN_PO_FIELD, ITEM_BIG_POE, PLAYER_IA_BOTTLE_BIG_POE, 0xF9 }, }; void func_8084ECA4(Player* this, PlayState* play) { @@ -13289,13 +13462,29 @@ void func_8084ECA4(Player* this, PlayState* play) { if (this->interactRangeActor != NULL) { catchInfo = &D_80854A04[0]; - for (i = 0; i < 4; i++, catchInfo++) { + for (i = 0; i < ARRAY_COUNT(D_80854A04); i++, catchInfo++) { if (this->interactRangeActor->id == catchInfo->actorId) { break; } } - if (i < 4) { + if (catchInfo->actorId == ACTOR_EN_POH || catchInfo->actorId == ACTOR_EN_PO_FIELD) { + // Don't catch Sharp or Flat + // I think they talk to you before you can get in catching range, but might as well prevent it outright + if (catchInfo->actorId == ACTOR_EN_POH && this->interactRangeActor->params >= 2) { + i = ARRAY_COUNT(D_80854A04); + } + // If the catch is a small field Poe (as opposed to a Big Poe), catch a graveyard Poe instead + if (catchInfo->actorId == ACTOR_EN_PO_FIELD && this->interactRangeActor->params == 0) { + i--; + catchInfo--; + } + if (!CVarGetInteger("gMMPoeBottling", 0)) { + i = ARRAY_COUNT(D_80854A04); + } + } + + if (i < ARRAY_COUNT(D_80854A04)) { this->unk_84F = i + 1; this->unk_850 = 0; this->interactRangeActor->parent = &this->actor; @@ -13576,7 +13765,7 @@ void func_8084F88C(Player* this, PlayState* play) { play->nextEntranceIndex = 0x0088; } else if (this->unk_84F < 0) { Play_TriggerRespawn(play); - // In ER, handle DMT and other special void outs to respawn from last entrance from grotto + // In ER, handle DMT and other special void outs to respawn from last entrance from grotto if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) { Grotto_ForceRegularVoidOut(); } @@ -15003,7 +15192,7 @@ void func_80852648(PlayState* play, Player* this, CsCmdActorAction* arg2) { this->heldItemId = ITEM_NONE; this->modelGroup = this->nextModelGroup = Player_ActionToModelGroup(this, PLAYER_IA_NONE); this->leftHandDLists = gPlayerLeftHandOpenDLs; - + // If MS sword is shuffled and not in the players inventory, then we need to unequip the current sword // and set swordless flag to mimic Link having his weapon knocked out of his hand in the Ganon fight if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) && !CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER)) { @@ -15012,7 +15201,7 @@ void func_80852648(PlayState* play, Player* this, CsCmdActorAction* arg2) { Flags_SetInfTable(INFTABLE_SWORDLESS); return; } - + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER); gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER; Inventory_DeleteEquipment(play, EQUIP_TYPE_SWORD); 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 bb8dd14d41c..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 @@ -3036,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/misc/ovl_kaleido_scope/z_kaleido_equipment.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c index d255f40d248..939030f99d3 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c @@ -181,6 +181,7 @@ void KaleidoScope_DrawEquipment(PlayState* play) { bool dpad = (CVarGetInteger("gDpadPause", 0) && !CHECK_BTN_ALL(input->cur.button, BTN_CUP)); bool pauseAnyCursor = (CVarGetInteger("gPauseAnyCursor", 0) == PAUSE_ANY_CURSOR_RANDO_ONLY && IS_RANDO) || (CVarGetInteger("gPauseAnyCursor", 0) == PAUSE_ANY_CURSOR_ALWAYS_ON); + bool altItems = CVarGetInteger("gAltItemMenu", 0); OPEN_DISPS(play->state.gfxCtx); @@ -188,7 +189,7 @@ void KaleidoScope_DrawEquipment(PlayState* play) { gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, ZREG(39), ZREG(40), ZREG(41), pauseCtx->alpha); gDPSetEnvColor(POLY_KAL_DISP++, ZREG(43), ZREG(44), ZREG(45), 0); - for (i = 0, j = 64; i < 4; i++, j += 4) { + for (i = 0, j = 64; i < (altItems ? 3 : 4); i++, j += 4) { if (CUR_EQUIP_VALUE(i) != 0) { gDPPipeSync(POLY_KAL_DISP++); gSPVertex(POLY_KAL_DISP++, &pauseCtx->equipVtx[j], 4, 0); @@ -220,6 +221,12 @@ void KaleidoScope_DrawEquipment(PlayState* play) { pauseCtx->cursorX[PAUSE_EQUIP] -= 1; pauseCtx->cursorPoint[PAUSE_EQUIP] -= 1; + // Kokiri tunic -> scale + if (altItems && pauseCtx->cursorY[PAUSE_EQUIP] == 2 && pauseCtx->cursorX[PAUSE_EQUIP] == 0) { + pauseCtx->cursorY[PAUSE_EQUIP] += 1; + pauseCtx->cursorPoint[PAUSE_EQUIP] += 4; + } + if (pauseCtx->cursorX[PAUSE_EQUIP] == 0) { if (pauseCtx->cursorY[PAUSE_EQUIP] == 0) { if (CUR_UPG_VALUE(UPG_BULLET_BAG) != 0) { @@ -260,6 +267,13 @@ void KaleidoScope_DrawEquipment(PlayState* play) { pauseCtx->cursorX[PAUSE_EQUIP] += 1; pauseCtx->cursorPoint[PAUSE_EQUIP] += 1; + // Strength -> Deku shield + // Scale -> Kokiri tunic + if (altItems && pauseCtx->cursorX[PAUSE_EQUIP] == 1 && pauseCtx->cursorY[PAUSE_EQUIP] >= 2) { + pauseCtx->cursorY[PAUSE_EQUIP] -= 1; + pauseCtx->cursorPoint[PAUSE_EQUIP] -= 4; + } + if (pauseCtx->cursorX[PAUSE_EQUIP] == 0) { if (CUR_UPG_VALUE(pauseCtx->cursorY[PAUSE_EQUIP]) != 0) { cursorMoveResult = 1; @@ -328,6 +342,12 @@ void KaleidoScope_DrawEquipment(PlayState* play) { pauseCtx->cursorY[PAUSE_EQUIP] += 1; pauseCtx->cursorPoint[PAUSE_EQUIP] += 4; + // Tunics -> scale + if (altItems && pauseCtx->cursorY[PAUSE_EQUIP] == 3 && pauseCtx->cursorX[PAUSE_EQUIP] > 0) { + pauseCtx->cursorPoint[PAUSE_EQUIP] -= pauseCtx->cursorX[PAUSE_EQUIP]; + pauseCtx->cursorX[PAUSE_EQUIP] = 0; + } + if (pauseCtx->cursorX[PAUSE_EQUIP] == 0) { if (CUR_UPG_VALUE(pauseCtx->cursorY[PAUSE_EQUIP]) != 0) { cursorMoveResult = 1; @@ -506,7 +526,10 @@ void KaleidoScope_DrawEquipment(PlayState* play) { u16 buttonsToCheck = BTN_A | BTN_CLEFT | BTN_CDOWN | BTN_CRIGHT; if (CVarGetInteger("gDpadEquips", 0) && (!CVarGetInteger("gDpadPause", 0) || CHECK_BTN_ALL(input->cur.button, BTN_CUP))) { - buttonsToCheck |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; + if (!CVarGetInteger("gAltItemMenu", 0)) { + buttonsToCheck |= BTN_DUP; + } + buttonsToCheck |= BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; } if ((pauseCtx->cursorSpecialPos == 0) && (cursorItem != PAUSE_ITEM_NONE) && (pauseCtx->state == 6) && @@ -673,7 +696,7 @@ void KaleidoScope_DrawEquipment(PlayState* play) { point = CUR_UPG_VALUE(sChildUpgrades[i]); if ((point != 0) && (CUR_UPG_VALUE(sChildUpgrades[i]) != 0)) { if (drawGreyItems && - ((sChildUpgradeItemBases[i] + CUR_UPG_VALUE(sChildUpgrades[i]) - 1) == ITEM_GAUNTLETS_SILVER || + ((sChildUpgradeItemBases[i] + CUR_UPG_VALUE(sChildUpgrades[i]) - 1) == ITEM_GAUNTLETS_SILVER || (sChildUpgradeItemBases[i] + CUR_UPG_VALUE(sChildUpgrades[i]) - 1) == ITEM_GAUNTLETS_GOLD)) { // Grey Out the Gauntlets gDPSetGrayscaleColor(POLY_KAL_DISP++, 109, 109, 109, 255); gSPGrayscale(POLY_KAL_DISP++, true); @@ -700,6 +723,11 @@ void KaleidoScope_DrawEquipment(PlayState* play) { gSPGrayscale(POLY_KAL_DISP++, false); } } + + if (altItems && i == 3) { + continue; + } + // Draw inventory screen icons for (k = 0, bit = rowStart, point = 4; k < 3; k++, point += 4, temp++, bit++) { diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c index 665c95b81af..5de84a23002 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c @@ -5,6 +5,7 @@ #include "soh/Enhancements/randomizer/randomizerTypes.h" #include "soh/Enhancements/enhancementTypes.h" #include "soh/Enhancements/cosmetics/cosmeticsTypes.h" +#include "soh_assets.h" u8 gAmmoItems[] = { ITEM_STICK, ITEM_NUT, ITEM_BOMB, ITEM_BOW, ITEM_NONE, ITEM_NONE, ITEM_SLINGSHOT, ITEM_NONE, @@ -16,24 +17,52 @@ static s16 sEquipAnimTimer = 0; static s16 sEquipMoveTimer = 10; bool gSelectingMask; bool gSelectingAdultTrade; +bool gSelectingArrow; +/* Maps an inventory slot to double the position of its ammo count in sAmmoVtxTableIdx */ static s16 sAmmoVtxOffset[] = { 0, 2, 4, 6, 99, 99, 8, 99, 10, 99, 99, 99, 99, 99, 12, }; +typedef enum { + ES_MAGIC_ARROW_GLOWING = 0, + ES_MAGIC_ARROW_APPLYING = 1, + ES_MAGIC_ARROW_EQUIPPING = 2, // Unused in item screen mod + ES_DEFAULT = 3, +} EquipState; + extern const char* _gAmmoDigit0Tex[]; +u8 KaleidoScope_ItemInSlot(u8 slot) { + if (CVarGetInteger("gAltItemMenu", 0)) { + slot = gAltToMainSlot[slot]; + } + if (slot < 24) { + return gSaveContext.inventory.items[slot]; + } else if ((slot == SLOT_BOOTS_IRON) && (gSaveContext.inventory.equipment & 1 << 13)) { + return ITEM_BOOTS_IRON; + } else if ((slot == SLOT_BOOTS_HOVER) && (gSaveContext.inventory.equipment & 1 << 14)) { + return ITEM_BOOTS_HOVER; + } else { + return ITEM_NONE; + } +} + void KaleidoScope_DrawAmmoCount(PauseContext* pauseCtx, GraphicsContext* gfxCtx, s16 item, int slot) { s16 ammo; s16 i; OPEN_DISPS(gfxCtx); + if (item == ITEM_BOW_ARROW_FIRE || item == ITEM_BOW_ARROW_ICE || item == ITEM_BOW_ARROW_LIGHT) { + item = ITEM_BOW; + } + ammo = AMMO(item); gDPPipeSync(POLY_KAL_DISP++); - if (!CHECK_AGE_REQ_SLOT(SLOT(item))) { + if (!CHECK_AGE_REQ_SLOT(KALEIDO_SLOT(item))) { gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, 100, 100, 100, pauseCtx->alpha); } else { gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, 255, 255, 255, pauseCtx->alpha); @@ -143,7 +172,7 @@ void KaleidoScope_DrawItemCycleExtras(PlayState* play, u8 slot, u8 isCycling, u8 } } - u8 slotItem = gSaveContext.inventory.items[slot]; + u8 slotItem = KaleidoScope_ItemInSlot(slot); u8 showLeftItem = leftItem != ITEM_NONE && slotItem != leftItem; u8 showRightItem = rightItem != ITEM_NONE && slotItem != rightItem && leftItem != rightItem; @@ -220,6 +249,197 @@ void KaleidoScope_DrawItemCycleExtras(PlayState* play, u8 slot, u8 isCycling, u8 CLOSE_DISPS(play->state.gfxCtx); } +static u8 sBowArrowItems[] = {ITEM_BOW, ITEM_BOW_ARROW_FIRE, ITEM_BOW_ARROW_ICE, ITEM_BOW_ARROW_LIGHT}; +static u8 sArrowItems[] = {ITEM_BOW, ITEM_ARROW_FIRE, ITEM_ARROW_ICE, ITEM_ARROW_LIGHT}; + +// Vertices for the extra items +static Vtx sArrowSelectExtraItemVtx[] = { + // Fire Arrow + VTX(-16, 48, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(16, 48, 0, 32 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-16, 16, 0, 0 << 5, 32 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(16, 16, 0, 32 << 5, 32 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + // Ice Arrow + VTX(-48, 16, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-16, 16, 0, 32 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-48, -16, 0, 0 << 5, 32 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-16, -16, 0, 32 << 5, 32 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + // Light Arrow + VTX(16, 16, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(48, 16, 0, 32 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(16, -16, 0, 0 << 5, 32 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(48, -16, 0, 32 << 5, 32 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + // Arrow + VTX(-16, -16, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(16, -16, 0, 32 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-16, -48, 0, 0 << 5, 32 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(16, -48, 0, 32 << 5, 32 << 5, 0xFF, 0xFF, 0xFF, 0xFF), +}; + +// Vertices for the circle behind the items +static Vtx sArrowSelectCircleVtx[] = { + // Fire Arrow + VTX(-24, 56, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(24, 56, 0, 48 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-24, 8, 0, 0 << 5, 48 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(24, 8, 0, 48 << 5, 48 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + // Ice Arrow + VTX(-56, 24, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-8, 24, 0, 48 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-56, -24, 0, 0 << 5, 48 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-8, -24, 0, 48 << 5, 48 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + // Light Arrow + VTX(8, 24, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(56, 24, 0, 48 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(8, -24, 0, 0 << 5, 48 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(56, -24, 0, 48 << 5, 48 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + // Arrow + VTX(-24, -8, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(24, -8, 0, 48 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-24, -56, 0, 0 << 5, 48 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(24, -56, 0, 48 << 5, 48 << 5, 0xFF, 0xFF, 0xFF, 0xFF), +}; + +static Vtx sArrowSelectIndicatorVtx[] = { + // Fire Arrow + VTX(-18, 56, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(18, 56, 0, 16 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-18, 20, 0, 0 << 5, 16 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(18, 20, 0, 16 << 5, 16 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + // Ice Arrow + VTX(-56, 18, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-20, 18, 0, 16 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-56, -18, 0, 0 << 5, 16 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-20, -18, 0, 16 << 5, 16 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + // Light Arrow + VTX(20, 18, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(56, 18, 0, 16 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(20, -18, 0, 0 << 5, 16 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(56, -18, 0, 16 << 5, 16 << 5, 0xFF, 0xFF, 0xFF, 0xFF), +}; + +// Vertices for A button indicator (coordinates 1.5x larger than texture size) +static Vtx sArrowSelectAButtonVtx[] = { + VTX(-18, -20, 0, 0 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(18, -20, 0, 24 << 5, 0 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(-18, -44, 0, 0 << 5, 16 << 5, 0xFF, 0xFF, 0xFF, 0xFF), + VTX(18, -44, 0, 24 << 5, 16 << 5, 0xFF, 0xFF, 0xFF, 0xFF), +}; + +void KaleidoScope_DrawArrowSelectExtras(PlayState* play, u8 slot, u8 isSelecting, u8 canSelect) { + PauseContext* pauseCtx = &play->pauseCtx; + + OPEN_DISPS(play->state.gfxCtx); + + // Update active cycling animation timer + if (isSelecting) { + if (sSlotCycleActiveAnimTimer[slot] < 5) { + sSlotCycleActiveAnimTimer[slot]++; + } + } else { + if (sSlotCycleActiveAnimTimer[slot] > 0) { + sSlotCycleActiveAnimTimer[slot]--; + } + } + + u8 slotItem = KaleidoScope_ItemInSlot(slot); + u8 showFire = INV_CONTENT(ITEM_ARROW_FIRE) != ITEM_NONE; + u8 showIce = INV_CONTENT(ITEM_ARROW_ICE) != ITEM_NONE; + u8 showLight = INV_CONTENT(ITEM_ARROW_LIGHT) != ITEM_NONE; + + if (canSelect && slotItem != ITEM_NONE && (showFire || showIce || showLight)) { + Matrix_Push(); + + Vtx* itemTopLeft = &pauseCtx->itemVtx[slot * 4]; + Vtx* itemBottomRight = &itemTopLeft[3]; + + s16 halfX = (itemBottomRight->v.ob[0] - itemTopLeft->v.ob[0]) / 2; + s16 halfY = (itemBottomRight->v.ob[1] - itemTopLeft->v.ob[1]) / 2; + + Matrix_Translate(itemTopLeft->v.ob[0] + halfX, itemTopLeft->v.ob[1] + halfY, 0, MTXMODE_APPLY); + + f32 animScale = (f32)(5 - sSlotCycleActiveAnimTimer[slot]) / 5; + + // When not selecting or actively animating, shrink and move the items under the main slot item + if (!isSelecting || sSlotCycleActiveAnimTimer[slot] < 5) { + f32 finalScale = 1.0f - (0.675f * animScale); + Matrix_Scale(finalScale, finalScale, 1.0f, MTXMODE_APPLY); + } + + gSPMatrix(POLY_KAL_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + + // Render A button indicator when hovered and not cycling + if (!isSelecting && sSlotCycleActiveAnimTimer[slot] == 0 && pauseCtx->cursorSlot[PAUSE_ITEM] == slot && + pauseCtx->cursorSpecialPos == 0) { + Color_RGB8 aButtonColor = { 0, 100, 255 }; + if (CVarGetInteger("gCosmetics.Hud_AButton.Changed", 0)) { + aButtonColor = CVarGetColor24("gCosmetics.Hud_AButton.Value", aButtonColor); + } else if (CVarGetInteger("gCosmetics.DefaultColorScheme", COLORSCHEME_N64) == COLORSCHEME_GAMECUBE) { + aButtonColor = (Color_RGB8){ 0, 255, 100 }; + } + + gSPVertex(POLY_KAL_DISP++, sArrowSelectAButtonVtx, 4, 0); + gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, aButtonColor.r, aButtonColor.g, aButtonColor.b, pauseCtx->alpha); + gDPLoadTextureBlock(POLY_KAL_DISP++, gABtnSymbolTex, G_IM_FMT_IA, G_IM_SIZ_8b, 24, 16, 0, + G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, 4, 4, G_TX_NOLOD, G_TX_NOLOD); + gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0); + } + + // Render a dark circle behind the arrows when selecting + if (isSelecting) { + gSPVertex(POLY_KAL_DISP++, sArrowSelectCircleVtx, 16, 0); + gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, 0, 0, 0, pauseCtx->alpha * (1.0f - animScale)); + gDPLoadTextureBlock_4b(POLY_KAL_DISP++, gPausePromptCursorTex, G_IM_FMT_I, 48, 48, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, + G_TX_NOLOD, G_TX_NOLOD); + + if (showFire) { + gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0); + } + if (showIce) { + gSP1Quadrangle(POLY_KAL_DISP++, 4, 6, 7, 5, 0); + } + if (showLight) { + gSP1Quadrangle(POLY_KAL_DISP++, 8, 10, 11, 9, 0); + } + gSP1Quadrangle(POLY_KAL_DISP++, 12, 14, 15, 13, 0); + } + + if (isSelecting || sSlotCycleActiveAnimTimer[slot] != 0) { + // Render arrows + gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, 255, 255, 255, pauseCtx->alpha); + gSPVertex(POLY_KAL_DISP++, sArrowSelectExtraItemVtx, 16, 0); + if (showFire) { + KaleidoScope_DrawQuadTextureRGBA32(play->state.gfxCtx, gItemIcons[ITEM_ARROW_FIRE], 32, 32, 0); + } + if (showIce) { + KaleidoScope_DrawQuadTextureRGBA32(play->state.gfxCtx, gItemIcons[ITEM_ARROW_ICE], 32, 32, 4); + } + if (showLight) { + KaleidoScope_DrawQuadTextureRGBA32(play->state.gfxCtx, gItemIcons[ITEM_ARROW_LIGHT], 32, 32, 8); + } + KaleidoScope_DrawQuadTextureRGBA32(play->state.gfxCtx, gArrowIconTex, 32, 32, 12); + } else { + // Render arrows + gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, 255, 255, 255, pauseCtx->alpha); + gSPVertex(POLY_KAL_DISP++, sArrowSelectIndicatorVtx, 12, 0); + if (showFire) { + KaleidoScope_DrawQuadTextureRGBA32(play->state.gfxCtx, gFireArrowPowerTex, 16, 16, 0); + } + if (showIce) { + KaleidoScope_DrawQuadTextureRGBA32(play->state.gfxCtx, gIceArrowPowerTex, 16, 16, 4); + } + if (showLight) { + KaleidoScope_DrawQuadTextureRGBA32(play->state.gfxCtx, gLightArrowPowerTex, 16, 16, 8); + } + } + + Matrix_Pop(); + } + + CLOSE_DISPS(play->state.gfxCtx); +} + void KaleidoScope_DrawItemSelect(PlayState* play) { static s16 magicArrowEffectsR[] = { 255, 100, 255 }; static s16 magicArrowEffectsG[] = { 0, 100, 255 }; @@ -239,6 +459,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { bool dpad = (CVarGetInteger("gDpadPause", 0) && !CHECK_BTN_ALL(input->cur.button, BTN_CUP)); bool pauseAnyCursor = (CVarGetInteger("gPauseAnyCursor", 0) == PAUSE_ANY_CURSOR_RANDO_ONLY && IS_RANDO) || (CVarGetInteger("gPauseAnyCursor", 0) == PAUSE_ANY_CURSOR_ALWAYS_ON); + bool altItemMenu = CVarGetInteger("gAltItemMenu", 0); // only allow mask select when: // the shop is open: @@ -250,6 +471,12 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { Flags_GetEventChkInf(EVENTCHKINF_OBTAINED_ZELDAS_LETTER) && Flags_GetInfTable(INFTABLE_SHOWED_ZELDAS_LETTER_TO_GATE_GUARD); + bool canArrowSelect = altItemMenu && + INV_CONTENT(ITEM_BOW) != ITEM_NONE && + (INV_CONTENT(ITEM_ARROW_FIRE) != ITEM_NONE || + INV_CONTENT(ITEM_ARROW_ICE) != ITEM_NONE || + INV_CONTENT(ITEM_ARROW_LIGHT) != ITEM_NONE); + OPEN_DISPS(play->state.gfxCtx); Gfx_SetupDL_42Opa(play->state.gfxCtx); @@ -260,7 +487,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { pauseCtx->nameColorSet = 0; if ((pauseCtx->state == 6) && (pauseCtx->unk_1E4 == 0) && (pauseCtx->pageIndex == PAUSE_ITEM)) { - moveCursorResult = 0 || gSelectingMask || gSelectingAdultTrade; + moveCursorResult = 0 || gSelectingMask || gSelectingAdultTrade || gSelectingArrow; oldCursorPoint = pauseCtx->cursorPoint[PAUSE_ITEM]; cursorItem = pauseCtx->cursorItem[PAUSE_ITEM]; @@ -282,14 +509,14 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { // Seem necessary to match if (pauseCtx->cursorX[PAUSE_ITEM]) {} - if (gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]]) {} + if (KaleidoScope_ItemInSlot(pauseCtx->cursorPoint[PAUSE_ITEM])) {} while (moveCursorResult == 0) { if ((pauseCtx->stickRelX < -30) || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DLEFT))) { if (pauseCtx->cursorX[PAUSE_ITEM] != 0) { pauseCtx->cursorX[PAUSE_ITEM] -= 1; pauseCtx->cursorPoint[PAUSE_ITEM] -= 1; - if ((gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]] != ITEM_NONE) || + if ((KaleidoScope_ItemInSlot(pauseCtx->cursorPoint[PAUSE_ITEM]) != ITEM_NONE) || pauseAnyCursor) { moveCursorResult = 1; } @@ -321,7 +548,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { if (pauseCtx->cursorX[PAUSE_ITEM] < 5) { pauseCtx->cursorX[PAUSE_ITEM] += 1; pauseCtx->cursorPoint[PAUSE_ITEM] += 1; - if ((gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]] != ITEM_NONE) || + if ((KaleidoScope_ItemInSlot(pauseCtx->cursorPoint[PAUSE_ITEM]) != ITEM_NONE) || pauseAnyCursor) { moveCursorResult = 1; } @@ -353,7 +580,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { } if (moveCursorResult == 1) { - cursorItem = gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]]; + cursorItem = KaleidoScope_ItemInSlot(pauseCtx->cursorPoint[PAUSE_ITEM]); } osSyncPrintf("【X cursor=%d(%) (cur_xpt=%d)(ok_fg=%d)(ccc=%d)(key_angle=%d)】 ", @@ -369,7 +596,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { cursorPoint = cursorX = cursorY = 0; while (true) { - if (gSaveContext.inventory.items[cursorPoint] != ITEM_NONE) { + if (KaleidoScope_ItemInSlot(cursorPoint) != ITEM_NONE) { pauseCtx->cursorPoint[PAUSE_ITEM] = cursorPoint; pauseCtx->cursorX[PAUSE_ITEM] = cursorX; pauseCtx->cursorY[PAUSE_ITEM] = cursorY; @@ -404,7 +631,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { cursorPoint = cursorX = 5; cursorY = 0; while (true) { - if (gSaveContext.inventory.items[cursorPoint] != ITEM_NONE) { + if (KaleidoScope_ItemInSlot(cursorPoint) != ITEM_NONE) { pauseCtx->cursorPoint[PAUSE_ITEM] = cursorPoint; pauseCtx->cursorX[PAUSE_ITEM] = cursorX; pauseCtx->cursorY[PAUSE_ITEM] = cursorY; @@ -434,7 +661,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { if (pauseCtx->cursorSpecialPos == 0) { if (cursorItem != PAUSE_ITEM_NONE) { if ((ABS(pauseCtx->stickRelY) > 30) || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DDOWN | BTN_DUP))) { - moveCursorResult = 0 || gSelectingMask || gSelectingAdultTrade; + moveCursorResult = 0 || gSelectingMask || gSelectingAdultTrade || gSelectingArrow; cursorPoint = pauseCtx->cursorPoint[PAUSE_ITEM]; cursorY = pauseCtx->cursorY[PAUSE_ITEM]; @@ -443,7 +670,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { if (pauseCtx->cursorY[PAUSE_ITEM] != 0) { pauseCtx->cursorY[PAUSE_ITEM] -= 1; pauseCtx->cursorPoint[PAUSE_ITEM] -= 6; - if ((gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]] != ITEM_NONE) || + if ((KaleidoScope_ItemInSlot(pauseCtx->cursorPoint[PAUSE_ITEM]) != ITEM_NONE) || pauseAnyCursor) { moveCursorResult = 1; } @@ -457,7 +684,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { if (pauseCtx->cursorY[PAUSE_ITEM] < 3) { pauseCtx->cursorY[PAUSE_ITEM] += 1; pauseCtx->cursorPoint[PAUSE_ITEM] += 6; - if ((gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]] != ITEM_NONE) || + if ((KaleidoScope_ItemInSlot(pauseCtx->cursorPoint[PAUSE_ITEM]) != ITEM_NONE) || pauseAnyCursor) { moveCursorResult = 1; } @@ -482,9 +709,9 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { pauseCtx->cursorColorSet = 4; if (moveCursorResult == 1) { - cursorItem = gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]]; + cursorItem = KaleidoScope_ItemInSlot(pauseCtx->cursorPoint[PAUSE_ITEM]); } else if (moveCursorResult != 2) { - cursorItem = gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]]; + cursorItem = KaleidoScope_ItemInSlot(pauseCtx->cursorPoint[PAUSE_ITEM]); } pauseCtx->cursorItem[PAUSE_ITEM] = cursorItem; @@ -499,7 +726,9 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { KaleidoScope_SetCursorVtx(pauseCtx, index, pauseCtx->itemVtx); if ((pauseCtx->debugState == 0) && (pauseCtx->state == 6) && (pauseCtx->unk_1E4 == 0)) { - if (canMaskSelect && cursorSlot == SLOT_TRADE_CHILD && CHECK_BTN_ALL(input->press.button, BTN_A)) { + if (canMaskSelect && cursorSlot == (altItemMenu ? SLOT_ALT_TRADE_CHILD : SLOT_TRADE_CHILD) && + CHECK_BTN_ALL(input->press.button, BTN_A) + ) { Audio_PlaySoundGeneral(NA_SE_SY_DECIDE, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); gSelectingMask = !gSelectingMask; } @@ -535,7 +764,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { } } } - gSelectingMask = cursorSlot == SLOT_TRADE_CHILD; + gSelectingMask = cursorSlot == (altItemMenu ? SLOT_ALT_TRADE_CHILD : SLOT_TRADE_CHILD); gSlotAgeReqs[SLOT_TRADE_CHILD] = gItemAgeReqs[ITEM_MASK_BUNNY] = ((((CVarGetInteger("gMMBunnyHood", BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA) && CVarGetInteger("gAdultBunnyHood", 0)) || CVarGetInteger("gTimelessEquipment", 0)) && @@ -544,7 +773,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { : AGE_REQ_CHILD; } if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_ADULT_TRADE) && - cursorSlot == SLOT_TRADE_ADULT && CHECK_BTN_ALL(input->press.button, BTN_A)) { + cursorSlot == (altItemMenu ? SLOT_ALT_TRADE_ADULT : SLOT_TRADE_ADULT) && CHECK_BTN_ALL(input->press.button, BTN_A)) { Audio_PlaySoundGeneral(NA_SE_SY_DECIDE, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); gSelectingAdultTrade = !gSelectingAdultTrade; } @@ -559,11 +788,77 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { Audio_PlaySoundGeneral(NA_SE_SY_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); Inventory_ReplaceItem(play, INV_CONTENT(ITEM_TRADE_ADULT), Randomizer_GetPrevAdultTradeItem()); } - gSelectingAdultTrade = cursorSlot == SLOT_TRADE_ADULT; + gSelectingAdultTrade = cursorSlot == (altItemMenu ? SLOT_ALT_TRADE_ADULT : SLOT_TRADE_ADULT); + } + if (canArrowSelect && cursorSlot == SLOT_ALT_BOW && CHECK_BTN_ALL(input->press.button, BTN_A)) { + Audio_PlaySoundGeneral(NA_SE_SY_DECIDE, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); + gSelectingArrow = !gSelectingArrow; } + if (gSelectingArrow) { + pauseCtx->cursorColorSet = 8; + + // Select arrow with stick/d-pad + // Up = Fire + // Left = Ice + // Right = Light + // Down = Normal + u8 newBow = ITEM_NONE; + u16 arrowCursorSlot; + if (pauseCtx->stickRelY < -30 || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DDOWN))) { + newBow = ITEM_BOW; + arrowCursorSlot = cursorSlot + 6; + } else if (pauseCtx->stickRelY > 30 || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DUP))) { + newBow = (INV_CONTENT(ITEM_ARROW_FIRE) != ITEM_NONE) ? ITEM_BOW_ARROW_FIRE : ITEM_NONE; + arrowCursorSlot = cursorSlot - 6; + } else if (pauseCtx->stickRelX < -30 || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DLEFT))) { + newBow = (INV_CONTENT(ITEM_ARROW_ICE) != ITEM_NONE) ? ITEM_BOW_ARROW_ICE : ITEM_NONE; + arrowCursorSlot = cursorSlot - 1; + } else if (pauseCtx->stickRelX > 30 || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DRIGHT))) { + newBow = (INV_CONTENT(ITEM_ARROW_LIGHT) != ITEM_NONE) ? ITEM_BOW_ARROW_LIGHT : ITEM_NONE; + arrowCursorSlot = cursorSlot + 1; + } + + // If you double select an arrow, reset to normal + if (newBow == INV_CONTENT(ITEM_BOW)) { + newBow = ITEM_BOW; + } + + // If double selecting regular arrows, cancel + if (newBow == ITEM_BOW && INV_CONTENT(ITEM_BOW) == ITEM_BOW) { + Audio_PlaySoundGeneral(NA_SE_SY_DECIDE, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); + gSelectingArrow = false; + newBow = ITEM_NONE; + } + + if (newBow != ITEM_NONE) { + u16 sfxId; + if (newBow == ITEM_BOW || CVarGetInteger("gSkipArrowAnimation", 0)) { + sfxId = NA_SE_SY_DECIDE; + pauseCtx->equipTargetItem = newBow; + pauseCtx->equipAnimAlpha = 255; + sEquipState = ES_MAGIC_ARROW_APPLYING; + } else { + sfxId = NA_SE_SY_SET_FIRE_ARROW + (newBow - ITEM_BOW_ARROW_FIRE); + pauseCtx->equipTargetItem = 0xBF - ITEM_BOW_ARROW_FIRE + newBow; + pauseCtx->equipAnimAlpha = 0; + sEquipState = ES_MAGIC_ARROW_GLOWING; + } + + Audio_PlaySoundGeneral(sfxId, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); + pauseCtx->equipAnimX = pauseCtx->itemVtx[arrowCursorSlot * 4].v.ob[0] * 10; + pauseCtx->equipAnimY = pauseCtx->itemVtx[arrowCursorSlot * 4].v.ob[1] * 10; + sEquipMoveTimer = 6; + sEquipAnimTimer = 0; + pauseCtx->unk_1E4 = 3; + } + } + u16 buttonsToCheck = BTN_CLEFT | BTN_CDOWN | BTN_CRIGHT; if (CVarGetInteger("gDpadEquips", 0) && (!CVarGetInteger("gDpadPause", 0) || CHECK_BTN_ALL(input->cur.button, BTN_CUP))) { - buttonsToCheck |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; + if (!altItemMenu) { + buttonsToCheck |= BTN_DUP; + } + buttonsToCheck |= BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; } if (CHECK_BTN_ANY(input->press.button, buttonsToCheck)) { if (CHECK_AGE_REQ_SLOT(cursorSlot) && @@ -602,9 +897,14 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { gDPSetEnvColor(POLY_KAL_DISP++, 0, 0, 0, 0); for (i = 0, j = 24 * 4; i < ARRAY_COUNT(gSaveContext.equips.cButtonSlots); i++, j += 4) { - if ((gSaveContext.equips.buttonItems[i + 1] != ITEM_NONE) && - !((gSaveContext.equips.buttonItems[i + 1] >= ITEM_SHIELD_DEKU) && - (gSaveContext.equips.buttonItems[i + 1] <= ITEM_BOOTS_HOVER))) { + u8 buttonItem = gSaveContext.equips.buttonItems[i + 1]; + u8 drawBg = (buttonItem != ITEM_NONE) && // equipped + !((buttonItem >= ITEM_SHIELD_DEKU) && (buttonItem <= ITEM_BOOTS_HOVER)); // not equipment + u8 drawBgAlt = (buttonItem != ITEM_NONE) && // equipped + (buttonItem < ARRAY_COUNT(gAltItemSlots)) && // has an assigned slot + (gAltItemSlots[buttonItem] < 24) && // assigned slot is on the menu + !((buttonItem >= ITEM_SHIELD_DEKU) && (buttonItem <= ITEM_BOOTS_KOKIRI)); // not equipment (except boots) + if (altItemMenu ? drawBgAlt : drawBg) { gSPVertex(POLY_KAL_DISP++, &pauseCtx->itemVtx[j], 4, 0); POLY_KAL_DISP = KaleidoScope_QuadTextureIA8(POLY_KAL_DISP, gEquippedItemOutlineTex, 32, 32, 0); } @@ -616,10 +916,10 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { for (i = j = 0; i < 24; i++, j += 4) { gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, 255, 255, 255, pauseCtx->alpha); - if (gSaveContext.inventory.items[i] != ITEM_NONE) { + if (KaleidoScope_ItemInSlot(i) != ITEM_NONE) { if ((pauseCtx->unk_1E4 == 0) && (pauseCtx->pageIndex == PAUSE_ITEM) && (pauseCtx->cursorSpecialPos == 0)) { if (CHECK_AGE_REQ_SLOT(i)) { - if ((sEquipState == 2) && (i == 3)) { + if ((sEquipState == ES_MAGIC_ARROW_EQUIPPING) && (i == SLOT_BOW) && !altItemMenu) { gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, magicArrowEffectsR[pauseCtx->equipTargetItem - 0xBF], magicArrowEffectsG[pauseCtx->equipTargetItem - 0xBF], magicArrowEffectsB[pauseCtx->equipTargetItem - 0xBF], pauseCtx->alpha); @@ -652,7 +952,7 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { } gSPVertex(POLY_KAL_DISP++, &pauseCtx->itemVtx[j + 0], 4, 0); - int itemId = gSaveContext.inventory.items[i]; + int itemId = KaleidoScope_ItemInSlot(i); bool not_acquired = !CHECK_AGE_REQ_ITEM(itemId); if (not_acquired) { gDPSetGrayscaleColor(POLY_KAL_DISP++, 109, 109, 109, 255); @@ -679,14 +979,17 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { } // Adult trade item cycle - KaleidoScope_DrawItemCycleExtras(play, SLOT_TRADE_ADULT, gSelectingAdultTrade, + KaleidoScope_DrawItemCycleExtras(play, altItemMenu ? SLOT_ALT_TRADE_ADULT : SLOT_TRADE_ADULT, + gSelectingAdultTrade, IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_ADULT_TRADE), Randomizer_GetPrevAdultTradeItem(), Randomizer_GetNextAdultTradeItem()); // Child mask item cycle (mimics the left/right item behavior from the cycling logic above) u8 childTradeItem = INV_CONTENT(ITEM_TRADE_CHILD); - KaleidoScope_DrawItemCycleExtras(play, SLOT_TRADE_CHILD, gSelectingMask, canMaskSelect, + KaleidoScope_DrawItemCycleExtras(play, altItemMenu ? SLOT_ALT_TRADE_CHILD : SLOT_TRADE_CHILD, + gSelectingMask, canMaskSelect, childTradeItem <= ITEM_MASK_KEATON ? ITEM_MASK_TRUTH : childTradeItem - 1, childTradeItem >= ITEM_MASK_TRUTH ? ITEM_MASK_KEATON : childTradeItem + 1); + KaleidoScope_DrawArrowSelectExtras(play, SLOT_ALT_BOW, gSelectingArrow, canArrowSelect); CLOSE_DISPS(play->state.gfxCtx); } @@ -696,6 +999,8 @@ void KaleidoScope_SetupItemEquip(PlayState* play, u16 item, u16 slot, s16 animX, PauseContext* pauseCtx = &play->pauseCtx; gSelectingMask = false; gSelectingAdultTrade = false; + gSelectingArrow = false; + int altItemMenu = CVarGetInteger("gAltItemMenu", 0); if (CHECK_BTN_ALL(input->press.button, BTN_CLEFT)) { pauseCtx->equipTargetCBtn = 0; @@ -704,7 +1009,7 @@ void KaleidoScope_SetupItemEquip(PlayState* play, u16 item, u16 slot, s16 animX, } else if (CHECK_BTN_ALL(input->press.button, BTN_CRIGHT)) { pauseCtx->equipTargetCBtn = 2; } else if (CVarGetInteger("gDpadEquips", 0)) { - if (CHECK_BTN_ALL(input->press.button, BTN_DUP)) { + if (CHECK_BTN_ALL(input->press.button, BTN_DUP) && !altItemMenu) { pauseCtx->equipTargetCBtn = 3; } else if (CHECK_BTN_ALL(input->press.button, BTN_DDOWN)) { pauseCtx->equipTargetCBtn = 4; @@ -716,13 +1021,13 @@ void KaleidoScope_SetupItemEquip(PlayState* play, u16 item, u16 slot, s16 animX, } pauseCtx->equipTargetItem = item; - pauseCtx->equipTargetSlot = slot; + pauseCtx->equipTargetSlot = altItemMenu ? gAltToMainSlot[slot] : slot; pauseCtx->unk_1E4 = 3; pauseCtx->equipAnimX = animX; pauseCtx->equipAnimY = animY; pauseCtx->equipAnimAlpha = 255; sEquipAnimTimer = 0; - sEquipState = 3; + sEquipState = ES_DEFAULT; sEquipMoveTimer = 10; if ((pauseCtx->equipTargetItem == ITEM_ARROW_FIRE) || (pauseCtx->equipTargetItem == ITEM_ARROW_ICE) || (pauseCtx->equipTargetItem == ITEM_ARROW_LIGHT)) { @@ -738,7 +1043,7 @@ void KaleidoScope_SetupItemEquip(PlayState* play, u16 item, u16 slot, s16 animX, } Audio_PlaySoundGeneral(NA_SE_SY_SET_FIRE_ARROW + index, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); pauseCtx->equipTargetItem = 0xBF + index; - sEquipState = 0; + sEquipState = ES_MAGIC_ARROW_GLOWING; pauseCtx->equipAnimAlpha = 0; sEquipMoveTimer = 6; } @@ -922,17 +1227,19 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) { sCButtonPosX[6] = sCButtonPosX[6] - 160; sCButtonPosY[6] = 120 - sCButtonPosY[6]; - if (sEquipState == 0) { + if (sEquipState == ES_MAGIC_ARROW_GLOWING) { pauseCtx->equipAnimAlpha += 14; if (pauseCtx->equipAnimAlpha > 255) { pauseCtx->equipAnimAlpha = 254; - sEquipState++; + sEquipState = ES_MAGIC_ARROW_APPLYING; } sEquipAnimTimer = 5; return; } - if (sEquipState == 2) { + int altItemMenu = CVarGetInteger("gAltItemMenu", 0); + int vtx = 4 * (altItemMenu ? SLOT_ALT_BOW : SLOT_BOW); + if (sEquipState == ES_MAGIC_ARROW_EQUIPPING && !altItemMenu) { D_8082A488--; if (D_8082A488 == 0) { @@ -949,8 +1256,8 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) { return; } - if (sEquipState == 1) { - bowItemVtx = &pauseCtx->itemVtx[12]; + if (sEquipState == ES_MAGIC_ARROW_APPLYING) { + bowItemVtx = &pauseCtx->itemVtx[vtx]; offsetX = ABS(pauseCtx->equipAnimX - bowItemVtx->v.ob[0] * 10) / sEquipMoveTimer; offsetY = ABS(pauseCtx->equipAnimY - bowItemVtx->v.ob[1] * 10) / sEquipMoveTimer; } else { @@ -971,14 +1278,14 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) { WREG(90) -= WREG(87) / sEquipMoveTimer; WREG(87) -= WREG(87) / sEquipMoveTimer; - if (sEquipState == 1) { - if (pauseCtx->equipAnimX >= (pauseCtx->itemVtx[12].v.ob[0] * 10)) { + if (sEquipState == ES_MAGIC_ARROW_APPLYING) { + if (pauseCtx->equipAnimX >= (pauseCtx->itemVtx[vtx].v.ob[0] * 10)) { pauseCtx->equipAnimX -= offsetX; } else { pauseCtx->equipAnimX += offsetX; } - if (pauseCtx->equipAnimY >= (pauseCtx->itemVtx[12].v.ob[1] * 10)) { + if (pauseCtx->equipAnimY >= (pauseCtx->itemVtx[vtx].v.ob[1] * 10)) { pauseCtx->equipAnimY -= offsetY; } else { pauseCtx->equipAnimY += offsetY; @@ -1000,9 +1307,19 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) { sEquipMoveTimer--; if (sEquipMoveTimer == 0) { - if (sEquipState == 1) { - sEquipState++; - D_8082A488 = 4; + if (sEquipState == ES_MAGIC_ARROW_APPLYING) { + if (altItemMenu) { + pauseCtx->unk_1E4 = 0; + s16 magicArrowType = pauseCtx->equipTargetItem - 0xBF; + if (magicArrowType >= 0 && magicArrowType <= 2) { + pauseCtx->equipTargetItem = ITEM_BOW_ARROW_FIRE + magicArrowType; + } + Inventory_ReplaceItem(play, INV_CONTENT(ITEM_BOW), pauseCtx->equipTargetItem); + gSelectingArrow = false; + } else { + sEquipState = ES_MAGIC_ARROW_EQUIPPING; + D_8082A488 = 4; + } return; } @@ -1064,6 +1381,16 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) { } } + const bool ddown = 5; + if (CVarGetInteger("gMapOnDDown", 0) && + pauseCtx->equipTargetCBtn == ddown && + gSaveContext.equips.buttonItems[ddown + 1] == pauseCtx->equipTargetItem && + gSaveContext.equips.cButtonSlots[ddown] == pauseCtx->equipTargetSlot + ) { + pauseCtx->equipTargetItem = ITEM_NONE; + pauseCtx->equipTargetSlot = SLOT_NONE; + } + gSaveContext.equips.buttonItems[targetButtonIndex] = pauseCtx->equipTargetItem; gSaveContext.equips.cButtonSlots[pauseCtx->equipTargetCBtn] = pauseCtx->equipTargetSlot; Interface_LoadItemIcon1(play, targetButtonIndex); @@ -1084,4 +1411,5 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) { void KaleidoScope_ResetTradeSelect() { gSelectingMask = false; gSelectingAdultTrade = false; + gSelectingArrow = false; } 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 28723d3d029..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); @@ -349,10 +356,6 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) { 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, 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); diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_prompt.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_prompt.c index 0807b8356a8..53e30257fe7 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_prompt.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_prompt.c @@ -9,7 +9,7 @@ void KaleidoScope_UpdatePrompt(PlayState* play) { s16 step; bool dpad = CVarGetInteger("gDpadPause", 0); - if (((pauseCtx->state == 7) && (pauseCtx->unk_1EC == 1)) || (pauseCtx->state == 0xE) || (pauseCtx->state == 0x10)) { + if (((pauseCtx->state == 7) && (pauseCtx->unk_1EC == 1 || pauseCtx->unk_1EC == 7)) || (pauseCtx->state == 0xE) || (pauseCtx->state == 0x10)) { if ((pauseCtx->promptChoice == 0) && ((relStickX >= 30) || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DRIGHT)))) { Audio_PlaySoundGeneral(NA_SE_SY_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); pauseCtx->promptChoice = 4; 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 8c214d9b0ae..364d491312a 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 @@ -12,6 +12,7 @@ extern u8 gSlotAgeReqs[]; extern u8 gItemAgeReqs[]; extern u8 gAreaGsFlags[]; extern bool gSelectingMask; +extern bool gSelectingArrow; #define MAP_48x85_TEX_WIDTH 48 #define MAP_48x85_TEX_HEIGHT 85 @@ -22,7 +23,6 @@ extern bool gSelectingMask; #define AGE_REQ_NONE 9 #define CHECK_AGE_REQ_EQUIP(i, j) (CVarGetInteger("gTimelessEquipment", 0) || (gEquipAgeReqs[i][j] == AGE_REQ_NONE) || (gEquipAgeReqs[i][j] == ((void)0, gSaveContext.linkAge))) -#define CHECK_AGE_REQ_SLOT(slotIndex) (CVarGetInteger("gTimelessEquipment", 0) || (gSlotAgeReqs[slotIndex] == AGE_REQ_NONE) || gSlotAgeReqs[slotIndex] == ((void)0, gSaveContext.linkAge)) #define CHECK_AGE_REQ_ITEM(itemIndex) (CVarGetInteger("gTimelessEquipment", 0) || (gItemAgeReqs[itemIndex] == AGE_REQ_NONE) || (gItemAgeReqs[itemIndex] == gSaveContext.linkAge)) void KaleidoScope_DrawQuestStatus(PlayState* play, GraphicsContext* gfxCtx); @@ -52,4 +52,22 @@ void KaleidoScope_UpdateCursorSize(PauseContext* pauseCtx); void KaleidoScope_ResetTradeSelect(); +/* Alternate item menu */ +typedef enum { + SLOT_ALT_DINS_FIRE, SLOT_ALT_BOMB, SLOT_ALT_BOMBCHU, SLOT_ALT_NUT, SLOT_ALT_LENS, SLOT_ALT_BEAN, + SLOT_ALT_FARORES_WIND, SLOT_ALT_SLINGSHOT, SLOT_ALT_BOOMERANG, SLOT_ALT_STICK, SLOT_ALT_BOOTS_HOVER, SLOT_ALT_TRADE_CHILD, + SLOT_ALT_NAYRUS_LOVE, SLOT_ALT_BOW, SLOT_ALT_HOOKSHOT, SLOT_ALT_HAMMER, SLOT_ALT_BOOTS_IRON, SLOT_ALT_TRADE_ADULT, + SLOT_ALT_EMPTY_LEFT, SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_2, SLOT_ALT_BOTTLE_3, SLOT_ALT_BOTTLE_4, SLOT_ALT_EMPTY_RIGHT, + SLOT_ALT_NONE = 0xFF +} InventorySlotAlt; + +extern u8 gSlotAgeReqsAlt[24]; +extern u8 gAltItemSlots[71]; +extern u8 gAltToMainSlot[24]; +#define _CHECK_AGE_REQ_SLOT_MAIN(slotIndex) (CVarGetInteger("gTimelessEquipment", 0) || (gSlotAgeReqs[slotIndex] == AGE_REQ_NONE) || gSlotAgeReqs[slotIndex] == ((void)0, gSaveContext.linkAge)) +#define _CHECK_AGE_REQ_SLOT_ALT(slotIndex) (CVarGetInteger("gTimelessEquipment", 0) || (gSlotAgeReqsAlt[slotIndex] == AGE_REQ_NONE) || gSlotAgeReqsAlt[slotIndex] == ((void)0, gSaveContext.linkAge)) +#define CHECK_AGE_REQ_SLOT(slotIndex) (CVarGetInteger("gAltItemMenu", 0) ? _CHECK_AGE_REQ_SLOT_ALT(slotIndex) : _CHECK_AGE_REQ_SLOT_MAIN(slotIndex)) +#define KALEIDO_SLOT(item) (CVarGetInteger("gAltItemMenu", 0) ? gAltItemSlots[item] : SLOT(item)) +u8 KaleidoScope_ItemInSlot(u8 slot); + #endif 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 877ea7e6f42..02e6648e7cb 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 @@ -760,6 +760,33 @@ u8 gSlotAgeReqs[] = { AGE_REQ_CHILD, // SLOT_TRADE_CHILD }; +u8 gSlotAgeReqsAlt[] = { + AGE_REQ_NONE, // SLOT_ALT_DINS_FIRE + AGE_REQ_NONE, // SLOT_ALT_BOMB + AGE_REQ_NONE, // SLOT_ALT_BOMBCHU + AGE_REQ_NONE, // SLOT_ALT_NUT + AGE_REQ_NONE, // SLOT_ALT_LENS + AGE_REQ_CHILD, // SLOT_ALT_BEAN + AGE_REQ_NONE, // SLOT_ALT_FARORES_WIND + AGE_REQ_CHILD, // SLOT_ALT_SLINGSHOT + AGE_REQ_CHILD, // SLOT_ALT_BOOMERANG + AGE_REQ_CHILD, // SLOT_ALT_STICK + AGE_REQ_ADULT, // SLOT_ALT_BOOTS_HOVER + AGE_REQ_CHILD, // SLOT_ALT_TRADE_CHILD + AGE_REQ_NONE, // SLOT_ALT_NAYRUS_LOVE + AGE_REQ_ADULT, // SLOT_ALT_BOW + AGE_REQ_ADULT, // SLOT_ALT_HOOKSHOT + AGE_REQ_ADULT, // SLOT_ALT_HAMMER + AGE_REQ_ADULT, // SLOT_ALT_BOOTS_IRON + AGE_REQ_ADULT, // SLOT_ALT_TRADE_ADULT + AGE_REQ_NONE, // SLOT_ALT_EMPTY_LEFT + AGE_REQ_NONE, // SLOT_ALT_BOTTLE_1 + AGE_REQ_NONE, // SLOT_ALT_BOTTLE_2 + AGE_REQ_NONE, // SLOT_ALT_BOTTLE_3 + AGE_REQ_NONE, // SLOT_ALT_BOTTLE_4 + AGE_REQ_NONE, // SLOT_ALT_EMPTY_RIGHT +}; + u8 gEquipAgeReqs[][4] = { { AGE_REQ_ADULT, // 0 UPG_QUIVER @@ -877,6 +904,38 @@ u8 gItemAgeReqs[] = { AGE_REQ_ADULT, // ITEM_GIANTS_KNIFE }; +u8 gMainToAltSlot[] = { + SLOT_ALT_STICK, SLOT_ALT_NUT, SLOT_ALT_BOMB, SLOT_ALT_BOW, SLOT_ALT_NONE, SLOT_ALT_DINS_FIRE, + SLOT_ALT_SLINGSHOT, SLOT_ALT_NONE, SLOT_ALT_BOMBCHU, SLOT_ALT_HOOKSHOT, SLOT_ALT_NONE, SLOT_ALT_FARORES_WIND, + SLOT_ALT_BOOMERANG, SLOT_ALT_LENS, SLOT_ALT_BEAN, SLOT_ALT_HAMMER, SLOT_ALT_NONE, SLOT_ALT_NAYRUS_LOVE, + SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_2, SLOT_ALT_BOTTLE_3, SLOT_ALT_BOTTLE_4, SLOT_ALT_TRADE_ADULT, SLOT_ALT_TRADE_CHILD, +}; + +u8 gAltToMainSlot[] = { + SLOT_DINS_FIRE, SLOT_BOMB, SLOT_BOMBCHU, SLOT_NUT, SLOT_LENS, SLOT_BEAN, + SLOT_FARORES_WIND, SLOT_SLINGSHOT, SLOT_BOOMERANG, SLOT_STICK, SLOT_BOOTS_IRON, SLOT_TRADE_CHILD, + SLOT_NAYRUS_LOVE, SLOT_BOW, SLOT_HOOKSHOT, SLOT_HAMMER, SLOT_BOOTS_HOVER, SLOT_TRADE_ADULT, + SLOT_NONE, SLOT_BOTTLE_1, SLOT_BOTTLE_2, SLOT_BOTTLE_3, SLOT_BOTTLE_4, SLOT_NONE, +}; + +u8 gAltItemSlots[] = { + SLOT_ALT_STICK, SLOT_ALT_NUT, SLOT_ALT_BOMB, SLOT_ALT_BOW, SLOT_ALT_NONE, + SLOT_ALT_DINS_FIRE, SLOT_ALT_SLINGSHOT, SLOT_ALT_NONE, SLOT_ALT_NONE, SLOT_ALT_BOMBCHU, + SLOT_ALT_HOOKSHOT, SLOT_ALT_HOOKSHOT, SLOT_ALT_NONE, SLOT_ALT_FARORES_WIND, SLOT_ALT_BOOMERANG, + SLOT_ALT_LENS, SLOT_ALT_BEAN, SLOT_ALT_HAMMER, SLOT_ALT_NONE, SLOT_ALT_NAYRUS_LOVE, + SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_1, + SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_1, + SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_1, SLOT_ALT_BOTTLE_1, SLOT_ALT_TRADE_CHILD, SLOT_ALT_TRADE_CHILD, + SLOT_ALT_TRADE_CHILD, SLOT_ALT_TRADE_CHILD, SLOT_ALT_TRADE_CHILD, SLOT_ALT_TRADE_CHILD, SLOT_ALT_TRADE_CHILD, + SLOT_ALT_TRADE_CHILD, SLOT_ALT_TRADE_CHILD, SLOT_ALT_TRADE_CHILD, SLOT_ALT_TRADE_CHILD, SLOT_ALT_TRADE_CHILD, + SLOT_ALT_TRADE_ADULT, SLOT_ALT_TRADE_ADULT, SLOT_ALT_TRADE_ADULT, SLOT_ALT_TRADE_ADULT, SLOT_ALT_TRADE_ADULT, + SLOT_ALT_TRADE_ADULT, SLOT_ALT_TRADE_ADULT, SLOT_ALT_TRADE_ADULT, SLOT_ALT_TRADE_ADULT, SLOT_ALT_TRADE_ADULT, + SLOT_ALT_TRADE_ADULT, SLOT_ALT_BOW, SLOT_ALT_BOW, SLOT_ALT_BOW, SLOT_ALT_NONE, + SLOT_ALT_NONE, SLOT_ALT_NONE, SLOT_ALT_NONE, SLOT_ALT_NONE, SLOT_ALT_NONE, + SLOT_ALT_NONE, SLOT_ALT_NONE, SLOT_ALT_NONE, SLOT_ALT_NONE, SLOT_ALT_BOOTS_IRON, + SLOT_ALT_BOOTS_HOVER, +}; + u8 gAreaGsFlags[] = { 0x0F, 0x1F, 0x0F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x07, 0x07, 0x03, 0x0F, 0x07, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0x1F, 0x0F, 0x03, 0x0F, @@ -1009,14 +1068,15 @@ void KaleidoScope_SetDefaultCursor(PlayState* play) { s16 s; s16 i; gSelectingMask = false; + gSelectingArrow = false; switch (pauseCtx->pageIndex) { case PAUSE_ITEM: s = pauseCtx->cursorSlot[PAUSE_ITEM]; - if (gSaveContext.inventory.items[s] == ITEM_NONE) { + if (KaleidoScope_ItemInSlot(s) == ITEM_NONE) { i = s + 1; while (true) { - if (gSaveContext.inventory.items[i] != ITEM_NONE) { + if (KaleidoScope_ItemInSlot(i) != ITEM_NONE) { break; } i++; @@ -1028,7 +1088,7 @@ void KaleidoScope_SetDefaultCursor(PlayState* play) { return; } } - pauseCtx->cursorItem[PAUSE_ITEM] = gSaveContext.inventory.items[i]; + pauseCtx->cursorItem[PAUSE_ITEM] = KaleidoScope_ItemInSlot(i); pauseCtx->cursorSlot[PAUSE_ITEM] = i; } break; @@ -1043,6 +1103,7 @@ void KaleidoScope_SwitchPage(PauseContext* pauseCtx, u8 pt) { pauseCtx->unk_1E4 = 1; pauseCtx->unk_1EA = 0; gSelectingMask = false; + gSelectingArrow = false; if (!pt) { pauseCtx->mode = pauseCtx->pageIndex * 2 + 1; @@ -1205,8 +1266,6 @@ Gfx* KaleidoScope_DrawPageSections(Gfx* gfx, Vtx* vertices, void** textures) { return gfx; } -static uint8_t mapBlendMask[MAP_48x85_TEX_WIDTH * MAP_48x85_TEX_HEIGHT]; - void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) { static Color_RGB8 D_8082ACF4[12] = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 255, 255, 0 }, { 0, 0, 0 }, @@ -1375,10 +1434,6 @@ void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) { } } - // Need to invalidate the blend mask every frame. Ideally this would be done in KaleidoScope_DrawDungeonMap - // but the reference is not shared between files - gSPInvalidateTexCache(POLY_KAL_DISP++, mapBlendMask); - if (pauseCtx->pageIndex) { // pageIndex != PAUSE_ITEM gDPPipeSync(OVERLAY_DISP++); gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA, G_CC_MODULATEIA); @@ -1601,9 +1656,17 @@ void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) { gSPVertex(POLY_KAL_DISP++, &pauseCtx->saveVtx[60], 32, 0); - if (((pauseCtx->state == 7) && (pauseCtx->unk_1EC < 4)) || (pauseCtx->state == 0xE)) { - POLY_KAL_DISP = - KaleidoScope_QuadTextureIA8(POLY_KAL_DISP, sSavePromptTexs[gSaveContext.language], 152, 16, 0); + if (((pauseCtx->state == 7) && + (pauseCtx->unk_1EC < 4 || pauseCtx->unk_1EC == 7 || + (CVarGetInteger("gSaveAndQuit", 0) && (pauseCtx->unk_1EC == 5 || pauseCtx->unk_1EC == 8)))) || + (pauseCtx->state == 0xE)) { + if (pauseCtx->unk_1EC == 5 || pauseCtx->unk_1EC == 7 || pauseCtx->unk_1EC == 8) { + POLY_KAL_DISP = + KaleidoScope_QuadTextureIA8(POLY_KAL_DISP, sContinuePromptTexs[gSaveContext.language], 152, 16, 0); + } else { + POLY_KAL_DISP = + KaleidoScope_QuadTextureIA8(POLY_KAL_DISP, sSavePromptTexs[gSaveContext.language], 152, 16, 0); + } gDPSetCombineLERP(POLY_KAL_DISP++, 1, 0, PRIMITIVE, 0, TEXEL0, 0, PRIMITIVE, 0, 1, 0, PRIMITIVE, 0, TEXEL0, 0, PRIMITIVE, 0); @@ -2063,7 +2126,7 @@ void KaleidoScope_DrawInfoPanel(PlayState* play) { bool pauseAnyCursor = (CVarGetInteger("gPauseAnyCursor", 0) == PAUSE_ANY_CURSOR_RANDO_ONLY && IS_RANDO) || (CVarGetInteger("gPauseAnyCursor", 0) == PAUSE_ANY_CURSOR_ALWAYS_ON); - if (!pauseCtx->pageIndex && (!pauseAnyCursor || (gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]] != ITEM_NONE))) { // pageIndex == PAUSE_ITEM + if (!pauseCtx->pageIndex && (!pauseAnyCursor || (KaleidoScope_ItemInSlot(pauseCtx->cursorPoint[PAUSE_ITEM]) != ITEM_NONE))) { // pageIndex == PAUSE_ITEM pauseCtx->infoPanelVtx[16].v.ob[0] = pauseCtx->infoPanelVtx[18].v.ob[0] = WREG(49 + gSaveContext.language); @@ -2212,7 +2275,7 @@ void KaleidoScope_UpdateNamePanel(PlayState* play) { if (pauseAnyCursor && ((pauseCtx->pageIndex == PAUSE_EQUIP && pauseCtx->cursorX[PAUSE_EQUIP] != 0 && !CHECK_OWNED_EQUIP(pauseCtx->cursorY[PAUSE_EQUIP], pauseCtx->cursorX[PAUSE_EQUIP] - 1)) || - (pauseCtx->pageIndex == PAUSE_ITEM && gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]] == ITEM_NONE))) { + (pauseCtx->pageIndex == PAUSE_ITEM && KaleidoScope_ItemInSlot(pauseCtx->cursorPoint[PAUSE_ITEM]) == ITEM_NONE))) { pauseCtx->namedItem = PAUSE_ITEM_NONE; } @@ -2230,6 +2293,18 @@ void KaleidoScope_UpdateNamePanel(PlayState* play) { } else { osSyncPrintf("zoom_name=%d\n", pauseCtx->namedItem); + switch (sp2A) { + case ITEM_BOW_ARROW_FIRE: + sp2A = ITEM_ARROW_FIRE; + break; + case ITEM_BOW_ARROW_ICE: + sp2A = ITEM_ARROW_ICE; + break; + case ITEM_BOW_ARROW_LIGHT: + sp2A = ITEM_ARROW_LIGHT; + break; + } + if (gSaveContext.language) { sp2A += 123; } @@ -2619,7 +2694,9 @@ s16 func_80823A0C(PlayState* play, Vtx* vtx, s16 arg2, s16 arg3) { return phi_t1; } -static s16 D_8082B11C[] = { 0, 4, 8, 12, 24, 32, 56 }; +/* Maps an ammo item to an inventory slot by 4x its slot number */ +static s16 sAmmoVtxTableIdx[] = { 0, 4, 8, 12, 24, 32, 56 }; +static s16 sAmmoVtxTableIdxAlt[] = { 36, 12, 4, 52, 28, 8, 20 }; static s16 D_8082B12C[] = { -114, 12, 44, 76 }; @@ -2785,7 +2862,11 @@ void KaleidoScope_InitVertices(PlayState* play, GraphicsContext* gfxCtx) { for (phi_t3 = 1; phi_t3 < ARRAY_COUNT(gSaveContext.equips.buttonItems); phi_t3++, phi_t2 += 4) { if (gSaveContext.equips.cButtonSlots[phi_t3 - 1] != ITEM_NONE && ((phi_t3 < 4) || CVarGetInteger("gDpadEquips", 0))) { - phi_t4 = gSaveContext.equips.cButtonSlots[phi_t3 - 1] * 4; + int slot = gSaveContext.equips.cButtonSlots[phi_t3 - 1]; + if (CVarGetInteger("gAltItemMenu", 0)) { + slot = gMainToAltSlot[slot]; + } + phi_t4 = slot * 4; pauseCtx->itemVtx[phi_t2 + 0].v.ob[0] = pauseCtx->itemVtx[phi_t2 + 2].v.ob[0] = pauseCtx->itemVtx[phi_t4].v.ob[0] - 2; @@ -2834,7 +2915,11 @@ void KaleidoScope_InitVertices(PlayState* play, GraphicsContext* gfxCtx) { } for (phi_t3 = 0; phi_t3 < 7; phi_t3++) { - phi_t4 = D_8082B11C[phi_t3]; + if (CVarGetInteger("gAltItemMenu", 0)) { + phi_t4 = sAmmoVtxTableIdxAlt[phi_t3]; + } else { + phi_t4 = sAmmoVtxTableIdx[phi_t3]; + } pauseCtx->itemVtx[phi_t2 + 0].v.ob[0] = pauseCtx->itemVtx[phi_t2 + 2].v.ob[0] = pauseCtx->itemVtx[phi_t4].v.ob[0]; @@ -2895,7 +2980,7 @@ void KaleidoScope_InitVertices(PlayState* play, GraphicsContext* gfxCtx) { pauseCtx->equipVtx[phi_t4 + 0].v.ob[0] + 28; pauseCtx->equipVtx[phi_t4 + 0].v.ob[1] = pauseCtx->equipVtx[phi_t4 + 1].v.ob[1] = - phi_t5 + pauseCtx->offsetY - 2; + phi_t5 + pauseCtx->offsetY - 2 - ((CVarGetInteger("gAltItemMenu", 0) && phi_t3 > 0) ? 16 : 0); pauseCtx->equipVtx[phi_t4 + 2].v.ob[1] = pauseCtx->equipVtx[phi_t4 + 3].v.ob[1] = pauseCtx->equipVtx[phi_t4 + 0].v.ob[1] - 28; @@ -3326,6 +3411,7 @@ 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 @@ -3357,19 +3443,16 @@ void KaleidoScope_LoadDungeonMap(PlayState* play) { size_t size = (width * height) / 2; // account for CI4 size // Resource size being larger than the calculated CI size means it is most likely not a CI4 texture - // Abort early end undo the blended effect by clearing the mask to avoid crashing + // Abort early and unregister the blended effect to avoid crashing if (size < ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0])) { - if (mapBlendMask[0] != 0) { - for (size_t i = 0; i < ARRAY_COUNT(mapBlendMask); i++) { - mapBlendMask[i] = 0; - } - } - interfaceCtx->mapSegment[0] = NULL; interfaceCtx->mapSegment[1] = NULL; - Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[0], mapBlendMask, NULL); - Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[1], mapBlendMask, NULL); + Gfx_UnregisterBlendedTexture(interfaceCtx->mapSegmentName[0]); + Gfx_UnregisterBlendedTexture(interfaceCtx->mapSegmentName[1]); + + Gfx_TextureCacheDelete(interfaceCtx->mapSegmentName[0]); + Gfx_TextureCacheDelete(interfaceCtx->mapSegmentName[1]); return; } @@ -3404,6 +3487,11 @@ void KaleidoScope_LoadDungeonMap(PlayState* play) { 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; @@ -3444,6 +3532,11 @@ 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])) { @@ -4047,6 +4140,12 @@ void KaleidoScope_Update(PlayState* play) case 1: if (CHECK_BTN_ALL(input->press.button, BTN_A)) { if (pauseCtx->promptChoice != 0) { + if (CVarGetInteger("gSaveAndQuit", 0)) { + pauseCtx->promptChoice = 0; + Audio_PlaySoundGeneral(NA_SE_SY_DECIDE, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); + pauseCtx->unk_1EC = 7; + break; + } Interface_SetDoAction(play, DO_ACTION_NONE); gSaveContext.buttonStatus[0] = gSaveContext.buttonStatus[1] = gSaveContext.buttonStatus[2] = gSaveContext.buttonStatus[3] = BTN_ENABLED; @@ -4084,6 +4183,10 @@ void KaleidoScope_Update(PlayState* play) case 4: if (CHECK_BTN_ALL(input->press.button, BTN_B) || CHECK_BTN_ALL(input->press.button, BTN_A) || CHECK_BTN_ALL(input->press.button, BTN_START) || (--D_8082B25C == 0)) { + if (CVarGetInteger("gSaveAndQuit", 0)) { + pauseCtx->unk_1EC = 7; + break; + } Interface_SetDoAction(play, DO_ACTION_NONE); gSaveContext.buttonStatus[0] = gSaveContext.buttonStatus[1] = gSaveContext.buttonStatus[2] = gSaveContext.buttonStatus[3] = BTN_ENABLED; @@ -4134,6 +4237,46 @@ void KaleidoScope_Update(PlayState* play) pauseCtx->unk_204 = -434.0f; } break; + + // 7 and 8 are used by "Prompt to quit after saving" enhancement + case 7: + if (CHECK_BTN_ALL(input->press.button, BTN_A) || CHECK_BTN_ALL(input->press.button, BTN_B) || + CHECK_BTN_ALL(input->press.button, BTN_START)) { + if (pauseCtx->promptChoice == 0 || CHECK_BTN_ALL(input->press.button, BTN_B)) { + Interface_SetDoAction(play, DO_ACTION_NONE); + gSaveContext.buttonStatus[0] = gSaveContext.buttonStatus[1] = gSaveContext.buttonStatus[2] = + gSaveContext.buttonStatus[3] = BTN_ENABLED; + gSaveContext.buttonStatus[5] = gSaveContext.buttonStatus[6] = gSaveContext.buttonStatus[7] = + gSaveContext.buttonStatus[8] = BTN_ENABLED; + gSaveContext.unk_13EA = 0; + Interface_ChangeAlpha(50); + pauseCtx->unk_1EC = 5; + WREG(2) = -6240; + YREG(8) = pauseCtx->unk_204; + func_800F64E0(0); + } else { + Audio_PlaySoundGeneral(NA_SE_SY_DECIDE, &D_801333D4, 4, &D_801333E0, &D_801333E0, + &D_801333E8); + pauseCtx->unk_1EC = 8; + } + } + break; + + case 8: + if (interfaceCtx->unk_244 != 255) { + interfaceCtx->unk_244 += 10; + if (interfaceCtx->unk_244 >= 255) { + interfaceCtx->unk_244 = 255; + pauseCtx->state = 0; + R_UPDATE_RATE = 3; + R_PAUSE_MENU_MODE = 0; + func_800981B8(&play->objectCtx); + func_800418D0(&play->colCtx, play); + play->state.running = 0; + SET_NEXT_GAMESTATE(&play->state, Opening_Init, OpeningContext); + } + } + break; } break;