diff --git a/.github/workflows/apt-deps.txt b/.github/workflows/apt-deps.txt index 2a12d368c..e9f7e07e1 100644 --- a/.github/workflows/apt-deps.txt +++ b/.github/workflows/apt-deps.txt @@ -1 +1 @@ -libsdl2-dev libsdl2-net-dev libpng-dev libglew-dev ninja-build +libusb-dev libusb-1.0-0-dev libsdl2-dev libsdl2-net-dev libpng-dev libglew-dev libzip-dev zipcmp zipmerge ziptool ninja-build diff --git a/.github/workflows/generate-builds.yml b/.github/workflows/generate-builds.yml index f95051515..34f5c21c0 100644 --- a/.github/workflows/generate-builds.yml +++ b/.github/workflows/generate-builds.yml @@ -13,33 +13,37 @@ jobs: with: submodules: true - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.11 with: - key: ${{ runner.os }}-soh-otr-ccache + key: ${{ runner.os }}-otr-ccache-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-otr-ccache-${{ github.ref }} + ${{ runner.os }}-otr-ccache- - name: Install dependencies if: ${{ !vars.LINUX_RUNNER }} run: | sudo apt-get update sudo apt-get install -y $(cat .github/workflows/apt-deps.txt) + - name: Cache build folders + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-otr-build-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-otr-build-${{ github.ref }} + ${{ runner.os }}-otr-build- + path: | + build-cmake + SDL2-2.28.5 - name: Install latest SDL if: ${{ !vars.LINUX_RUNNER }} run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - wget https://www.libsdl.org/release/SDL2-2.26.1.tar.gz - tar -xzf SDL2-2.26.1.tar.gz - cd SDL2-2.26.1 - ./configure - make -j 10 - sudo make install - sudo cp -av /usr/local/lib/libSDL* /lib/x86_64-linux-gnu/ - - name: Install latest SDL_net - if: ${{ !vars.LINUX_RUNNER }} - run: | - export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - wget https://www.libsdl.org/projects/SDL_net/release/SDL2_net-2.2.0.tar.gz - tar -xzf SDL2_net-2.2.0.tar.gz - cd SDL2_net-2.2.0 - ./configure + if [ ! -d "SDL2-2.28.5" ]; then + wget https://www.libsdl.org/release/SDL2-2.28.5.tar.gz + tar -xzf SDL2-2.28.5.tar.gz + fi + cd SDL2-2.28.5 + ./configure --enable-hidapi-libusb make -j 10 sudo make install sudo cp -av /usr/local/lib/libSDL* /lib/x86_64-linux-gnu/ @@ -48,7 +52,7 @@ jobs: export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release cmake --build build-cmake --config Release --target GenerateSohOtr - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: soh.otr path: soh.otr @@ -61,9 +65,12 @@ jobs: with: submodules: true - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.11 with: - key: ${{ runner.os }}-ccache + key: ${{ runner.os }}-ccache-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-ccache-${{ github.ref }} + ${{ runner.os }}-ccache- - name: Install gtar wrapper if: ${{ !vars.MAC_RUNNER }} run: | @@ -85,24 +92,24 @@ jobs: if [ -d /opt/local/ ]; then echo "MacPorts already installed" else - wget https://github.com/macports/macports-base/releases/download/v2.7.2/MacPorts-2.7.2-12-Monterey.pkg - sudo installer -pkg ./MacPorts-2.7.2-12-Monterey.pkg -target / + wget https://github.com/macports/macports-base/releases/download/v2.9.1/MacPorts-2.9.1-12-Monterey.pkg + sudo installer -pkg ./MacPorts-2.9.1-12-Monterey.pkg -target / fi echo "/opt/local/bin:/opt/local/sbin" >> $GITHUB_PATH - name: Install dependencies if: ${{ !vars.MAC_RUNNER }} run: | - brew uninstall --ignore-dependencies libpng + brew uninstall --ignore-dependencies libpng libzip sudo port install $(cat .github/workflows/macports-deps.txt) brew install ninja - name: Download soh.otr - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: soh.otr - name: Build SoH run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" + cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DBUILD_REMOTE_CONTROL=1 cmake --build build-cmake --config Release --parallel 10 mv soh.otr build-cmake/soh (cd build-cmake && cpack) @@ -110,7 +117,7 @@ jobs: mv _packages/*.dmg SoH.dmg mv README.md readme.txt - name: Upload build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: soh-mac path: | @@ -139,39 +146,71 @@ jobs: sudo apt-get update sudo apt-get install -y $(cat .github/workflows/apt-deps.txt) - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.11 with: - key: ${{ matrix.os }}-ccache + key: ${{ matrix.os }}-ccache-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ matrix.os }}-ccache-${{ github.ref }} + ${{ matrix.os }}-ccache- + - name: Cache build folders + uses: actions/cache@v4 + with: + key: ${{ matrix.os }}-build-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ matrix.os }}-build-${{ github.ref }} + ${{ matrix.os }}-build- + path: | + SDL2-2.28.5 + SDL2_net-2.2.0 - name: Install latest SDL if: ${{ (matrix.os == 'ubuntu-20.04' && !vars.LINUX_COMPATIBILITY_RUNNER) || (matrix.os == 'ubuntu-22.04' && !vars.LINUX_PERFORMANCE_RUNNER) }} run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - wget https://www.libsdl.org/release/SDL2-2.26.1.tar.gz - tar -xzf SDL2-2.26.1.tar.gz - cd SDL2-2.26.1 - ./configure + if [ ! -d "SDL2-2.28.5" ]; then + wget https://www.libsdl.org/release/SDL2-2.28.5.tar.gz + tar -xzf SDL2-2.28.5.tar.gz + fi + cd SDL2-2.28.5 + ./configure --enable-hidapi-libusb make -j 10 sudo make install sudo cp -av /usr/local/lib/libSDL* /lib/x86_64-linux-gnu/ + - name: Install latest libzip + if: ${{ (matrix.os == 'ubuntu-20.04' && !vars.LINUX_COMPATIBILITY_RUNNER) }} + run: | + sudo apt-get remove libzip-dev zipcmp zipmerge ziptool + export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" + if [ ! -d "libzip-1.10.1" ]; then + wget https://libzip.org/download/libzip-1.10.1.tar.gz + tar -xzvf libzip-1.10.1.tar.gz + fi + cd libzip-1.10.1 + mkdir build + cd build + cmake .. + make + sudo make install - name: Install latest SDL_net if: ${{ (matrix.os == 'ubuntu-20.04' && !vars.LINUX_COMPATIBILITY_RUNNER) || (matrix.os == 'ubuntu-22.04' && !vars.LINUX_PERFORMANCE_RUNNER) }} run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - wget https://www.libsdl.org/projects/SDL_net/release/SDL2_net-2.2.0.tar.gz - tar -xzf SDL2_net-2.2.0.tar.gz + if [ ! -d "SDL2_net-2.2.0" ]; then + wget https://www.libsdl.org/projects/SDL_net/release/SDL2_net-2.2.0.tar.gz + tar -xzf SDL2_net-2.2.0.tar.gz + fi cd SDL2_net-2.2.0 ./configure make -j 10 sudo make install sudo cp -av /usr/local/lib/libSDL* /lib/x86_64-linux-gnu/ - name: Download soh.otr - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: soh.otr - name: Build SoH run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release + cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release -DBUILD_REMOTE_CONTROL=1 cmake --build build-cmake --config Release -j3 (cd build-cmake && cpack -G External) @@ -181,7 +220,7 @@ jobs: CC: gcc-${{ matrix.gcc }} CXX: g++-${{ matrix.gcc }} - name: Upload build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: soh-linux-${{ matrix.archive-suffix }} path: | @@ -191,12 +230,23 @@ 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: | sudo apt-get update sudo apt-get install -y ninja-build + sudo apt-get remove -y cmake + wget https://github.com/Kitware/CMake/releases/download/v3.28.3/cmake-3.28.3-linux-x86_64.sh -O /tmp/cmake.sh + sudo sh /tmp/cmake.sh --prefix=/usr/local/ --exclude-subdir + wget https://libzip.org/download/libzip-1.10.1.tar.gz + tar -xzvf libzip-1.10.1.tar.gz + cd libzip-1.10.1 + mkdir build + cd build + cmake -H.. -B. -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake + make + make install - name: Fix dubious ownership error if: ${{ vars.LINUX_RUNNER }} run: git config --global --add safe.directory '*' @@ -204,9 +254,12 @@ jobs: with: submodules: true - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.11 with: - key: ${{ runner.os }}-switch-ccache + key: ${{ runner.os }}-switch-ccache-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-switch-ccache-${{ github.ref }} + ${{ runner.os }}-switch-ccache- - name: Build SoH run: | cmake -H. -Bbuild-switch -GNinja -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache @@ -215,11 +268,11 @@ jobs: mv build-switch/soh/*.nro soh.nro mv README.md readme.txt - name: Download soh.otr - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: soh.otr - name: Upload build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: soh-switch path: | @@ -237,13 +290,27 @@ jobs: run: | sudo apt-get update sudo apt-get install -y ninja-build + sudo apt-get remove -y cmake + wget https://github.com/Kitware/CMake/releases/download/v3.28.3/cmake-3.28.3-linux-x86_64.sh -O /tmp/cmake.sh + sudo sh /tmp/cmake.sh --prefix=/usr/local/ --exclude-subdir + wget https://libzip.org/download/libzip-1.10.1.tar.gz + tar -xzvf libzip-1.10.1.tar.gz + cd libzip-1.10.1 + mkdir build + cd build + cmake -H.. -B. -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/WiiU.cmake + make + make install - uses: actions/checkout@v3 with: submodules: true - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.11 with: - key: ${{ runner.os }}-wiiu-ccache + key: ${{ runner.os }}-wiiu-ccache-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-wiiu-ccache-${{ github.ref }} + ${{ runner.os }}-wiiu-ccache- - name: Build SoH run: | cmake -H. -Bbuild-wiiu -GNinja -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/WiiU.cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache @@ -256,11 +323,11 @@ jobs: DEVKITPRO: /opt/devkitpro DEVKITPPC: /opt/devkitpro/devkitPPC - name: Download soh.otr - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: soh.otr - name: Upload build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: soh-wiiu path: | @@ -281,15 +348,25 @@ jobs: with: submodules: true - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.11 with: - key: ${{ runner.os }}-ccache - - name: vcpkg - uses: johnwason/vcpkg-action@v5 + variant: sccache + max-size: "1G" + key: ${{ runner.os }}-ccache-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-ccache-${{ github.ref }} + ${{ runner.os }}-ccache- + - name: Cache build folder + uses: actions/cache@v4 with: - pkgs: zlib bzip2 libpng sdl2 sdl2-net glew glfw3 - token: ${{ github.token }} - triplet: 'x64-windows-static' + save-always: true + key: ${{ runner.os }}-build-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-build-${{ github.ref }} + ${{ runner.os }}-build- + path: | + build-windows + vcpkg - name: Configure Developer Command Prompt uses: ilammy/msvc-dev-cmd@v1 - name: Build SoH @@ -297,7 +374,7 @@ jobs: VCPKG_ROOT: ${{github.workspace}}/vcpkg run: | set $env:PATH="$env:USERPROFILE/.cargo/bin;$env:PATH" - cmake -S . -B build-windows -G Ninja -DCMAKE_MAKE_PROGRAM=ninja -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake -S . -B build-windows -G Ninja -DCMAKE_MAKE_PROGRAM=ninja -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DBUILD_REMOTE_CONTROL=1 cmake --build build-windows --config Release --parallel 10 mkdir soh-windows @@ -310,12 +387,12 @@ jobs: mv ./build-windows/gamecontrollerdb.txt ./soh-windows/gamecontrollerdb.txt mv ./x64/Release/assets ./soh-windows - name: Download soh.otr - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: soh.otr path: soh-windows - name: Upload build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: soh-windows path: soh-windows diff --git a/.github/workflows/macports-deps.txt b/.github/workflows/macports-deps.txt index 02c11cc68..084428d16 100644 --- a/.github/workflows/macports-deps.txt +++ b/.github/workflows/macports-deps.txt @@ -1 +1 @@ -libsdl2 +universal libpng +universal glew +universal +libsdl2 +universal libsdl2_net +universal libpng +universal glew +universal libzip +universal \ No newline at end of file diff --git a/.github/workflows/test-builds-on-distros.yml b/.github/workflows/test-builds-on-distros.yml new file mode 100644 index 000000000..190599a44 --- /dev/null +++ b/.github/workflows/test-builds-on-distros.yml @@ -0,0 +1,61 @@ +name: test-builds-on-distros +on: + workflow_dispatch: # by request +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + build: + strategy: + matrix: + image: ["archlinux:base", "opensuse/tumbleweed:latest", "ubuntu:mantic", "debian:bookworm", "fedora:39"] + cc: ["gcc", "clang"] + include: + - cxx: g++ + cc: gcc + - cxx: clang++ + cc: clang + runs-on: ${{ (vars.LINUX_RUNNER && fromJSON(vars.LINUX_RUNNER)) || 'ubuntu-latest' }} + container: + image: ${{ matrix.image }} + steps: + - name: Install dependencies (pacman) + if: ${{ matrix.image == 'archlinux:base' }} + run: | + echo arch + echo pacman -S ${{ matrix.cc }} git cmake ninja lsb-release sdl2 libpng libzip sdl2_net boost + pacman -Syu --noconfirm + pacman -S --noconfirm ${{ matrix.cc }} git cmake ninja lsb-release sdl2 libpng libzip sdl2_net boost + - name: Install dependencies (dnf) + if: ${{ matrix.image == 'fedora:39' }} + run: | + echo fedora + echo dnf install ${{ matrix.cc }} ${{ (matrix.cxx == 'g++' && 'gcc-c++') || '' }} git cmake ninja-build lsb_release SDL2-devel libpng-devel libzip-devel libzip-tools boost-devel + dnf -y upgrade + dnf -y install ${{ matrix.cc }} ${{ (matrix.cxx == 'g++' && 'gcc-c++') || '' }} git cmake ninja-build lsb_release SDL2-devel libpng-devel libzip-devel libzip-tools boost-devel + - name: Install dependencies (apt) + if: ${{ matrix.image == 'ubuntu:mantic' || matrix.image == 'debian:bookworm' }} + run: | + echo debian based + echo apt-get install ${{ matrix.cc }} ${{ (matrix.cxx == 'g++' && 'g++') || '' }} git cmake ninja-build lsb-release libsdl2-dev libpng-dev libsdl2-net-dev libzip-dev zipcmp zipmerge ziptool libboost-dev libopengl-dev + apt-get update + apt-get -y full-upgrade + apt-get -y install ${{ matrix.cc }} ${{ (matrix.cxx == 'g++' && 'g++') || '' }} git cmake ninja-build lsb-release libsdl2-dev libpng-dev libsdl2-net-dev libzip-dev zipcmp zipmerge ziptool libboost-dev libopengl-dev + - name: Install dependencies (zypper) + if: ${{ matrix.image == 'opensuse/tumbleweed:latest' }} + run: | + echo openSUSE + echo zypper in ${{ matrix.cc }} ${{ (matrix.cxx == 'g++' && 'gcc-c++') || '' }} ${{ matrix.cc == 'clang' && 'libstdc++-devel' || '' }} git cmake ninja SDL2-devel libpng16-devel libzip-devel libzip-tools + zypper --non-interactive dup + zypper --non-interactive in ${{ matrix.cc }} ${{ (matrix.cxx == 'g++' && 'gcc-c++') || '' }} ${{ matrix.cc == 'clang' && 'libstdc++-devel' || '' }} git cmake ninja SDL2-devel libpng16-devel libzip-devel libzip-tools + - uses: actions/checkout@v3 + with: + submodules: true + - name: Build SoH + run: | + export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" + cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release -DBUILD_REMOTE_CONTROL=1 + cmake --build build-cmake --config Release -j3 + env: + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} diff --git a/CMake/DefaultCXX.cmake b/CMake/DefaultCXX.cmake index e87721511..6ed89d9e0 100644 --- a/CMake/DefaultCXX.cmake +++ b/CMake/DefaultCXX.cmake @@ -8,5 +8,9 @@ if(MSVC) set_target_properties("${PROPS_TARGET}" PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") set_config_specific_property("DEFAULT_CXX_EXCEPTION_HANDLING" "/EHsc") - set_config_specific_property("DEFAULT_CXX_DEBUG_INFORMATION_FORMAT" "/Zi") + if (CMAKE_C_COMPILER_LAUNCHER MATCHES "ccache|sccache") + set_config_specific_property("DEFAULT_CXX_DEBUG_INFORMATION_FORMAT" "/Z7") + else() + set_config_specific_property("DEFAULT_CXX_DEBUG_INFORMATION_FORMAT" "/Zi") + endif() endif() \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ae0360d8..f5227169d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,28 +5,26 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") -project(Ship VERSION 8.0.3 LANGUAGES C CXX) -set(PROJECT_BUILD_NAME "MacReady Delta" CACHE STRING "") +project(Ship VERSION 8.0.5 LANGUAGES C CXX) +set(PROJECT_BUILD_NAME "MacReady Foxtrot" CACHE STRING "") set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "") set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh) add_compile_options($<$:/MP>) add_compile_options($<$:/utf-8>) -if (CMAKE_SYSTEM_NAME MATCHES "Windows|Linux") - if(NOT DEFINED BUILD_CROWD_CONTROL) - set(BUILD_CROWD_CONTROL ON) - endif() -endif() - if (CMAKE_SYSTEM_NAME STREQUAL "Windows") -include(CMake/automate-vcpkg.cmake) + include(CMake/automate-vcpkg.cmake) -set(VCPKG_TRIPLET x64-windows-static) -set(VCPKG_TARGET_TRIPLET x64-windows-static) + set(VCPKG_TRIPLET x64-windows-static) + set(VCPKG_TARGET_TRIPLET x64-windows-static) -vcpkg_bootstrap() -vcpkg_install_packages(zlib bzip2 libpng sdl2 sdl2-net glew glfw3) + vcpkg_bootstrap() + vcpkg_install_packages(zlib bzip2 libzip libpng sdl2 sdl2-net glew glfw3) + + if (CMAKE_C_COMPILER_LAUNCHER MATCHES "ccache|sccache") + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT Embedded) + endif() endif() ################################################################################ diff --git a/OTRExporter b/OTRExporter index 04b85b95f..e93bd2be0 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit 04b85b95fab07a394b62dcd28a502a3040f08e0c +Subproject commit e93bd2be062b13106fdb29d98cf4ada4d7ad6827 diff --git a/README.md b/README.md index ef96bd8e4..ad80f7f2f 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ If you want to playtest a continuous integration build, you can find them at the * [Windows](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-windows.zip) * [macOS](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-mac.zip) * [Linux (performance)](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-linux-performance.zip) _(requires `glibc 2.35` or newer, but will be more performant than the compatibility build.)_ -* [Linux (compatibility)](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-linux-compatiblity.zip) _(compatible with most Linux distributions, but may not be as performant as the performance build.)_ +* [Linux (compatibility)](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-linux-compatibility.zip) _(compatible with most Linux distributions, but may not be as performant as the performance build.)_ * [Switch](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-switch.zip) * [Wii U](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-wiiu.zip) diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 56ff9718f..6c9222242 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -84,41 +84,66 @@ cd "build/x64" ``` ## Linux -Requires `gcc >= 10, x11, curl, python3, sdl2 >= 2.0.22, libpng, glew >= 2.2, ninja, cmake, lld, pulseaudio-libs` +### Install dependencies +#### Debian/Ubuntu +```sh +# using gcc +apt-get install gcc g++ git cmake ninja-build lsb-release libsdl2-dev libpng-dev libsdl2-net-dev libzip-dev zipcmp zipmerge ziptool libboost-dev libopengl-dev -**Important: For maximum performance make sure you have ninja build tools installed!** +# or using clang +apt-get install clang git cmake ninja-build lsb-release libsdl2-dev libpng-dev libsdl2-net-dev libzip-dev zipcmp zipmerge ziptool libboost-dev libopengl-dev +``` +#### Arch +```sh +# using gcc +pacman -S gcc git cmake ninja lsb-release sdl2 libpng libzip sdl2_net boost -_Note: If you're using Visual Studio Code, the [cpack plugin](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) makes it very easy to just press run and debug._ +# or using clang +pacman -S clang git cmake ninja lsb-release sdl2 libpng libzip sdl2_net boost +``` +#### Fedora +```sh +# using gcc +dnf install gcc gcc-c++ git cmake ninja-build lsb_release SDL2-devel libpng-devel libzip-devel libzip-tools boost-devel + +# or using clang +dnf install clang git cmake ninja-build lsb_release SDL2-devel libpng-devel libzip-devel libzip-tools boost-devel +``` +#### openSUSE +```sh +# using gcc +zypper in gcc gcc-c++ git cmake ninja SDL2-devel libpng16-devel libzip-devel libzip-tools + +# or using clang +zypper in clang libstdc++-devel git cmake ninja SDL2-devel libpng16-devel libzip-devel libzip-tools +``` + +### Build + +_Note: If you're using Visual Studio Code, the [CMake Tools plugin](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) makes it very easy to just press run and debug._ ```bash -# Clone the repo +# Clone the repo and enter the directory git clone https://github.com/HarbourMasters/Shipwright.git cd Shipwright -# Clone the submodule libultraship + +# Clone the submodules git submodule update --init -# Copy the baserom to the OTRExporter folder -cp OTRExporter + # Generate Ninja project cmake -H. -Bbuild-cmake -GNinja # -DCMAKE_BUILD_TYPE:STRING=Release (if you're packaging) -DPython3_EXECUTABLE=$(which python3) (if you are using non-standard Python installations such as PyEnv) -# Extract assets & generate OTR (run this anytime you need to regenerate OTR) -cmake --build build-cmake --target ExtractAssets + +# Generate soh.otr +cmake --build build-cmake --target GenerateSohOtr + # Compile the project cmake --build build-cmake # --config Release (if you're packaging) # Now you can run the executable in ./build-cmake/soh/soh.elf # To develop the project open the repository in VSCode (or your preferred editor) - -# If you need to clean the project you can run -cmake --build build-cmake --target clean - -# If you need to regenerate the asset headers to check them into source -cmake --build build-cmake --target ExtractAssetHeaders - -# If you need a newer soh.otr only -cmake --build build-cmake --target GenerateSohOtr ``` -### Generating a distributable +### Generate a distributable After compiling the project you can generate a distributable by running of the following: ```bash # Go to build folder @@ -129,6 +154,20 @@ cpack -G ZIP cpack -G External (creates appimage) ``` +### Additional CMake Targets +#### Clean +```bash +# If you need to clean the project you can run +cmake --build build-cmake --target clean +``` + +#### Regenerate Asset Headers +```bash +# If you need to regenerate the asset headers to check them into source +cp OTRExporter +cmake --build build-cmake --target ExtractAssetHeaders +``` + ## macOS Requires Xcode (or xcode-tools) && `sdl2, libpng, glew, ninja, cmake` (can be installed via homebrew, macports, etc) diff --git a/docs/MODDING.md b/docs/MODDING.md index ebd7cf071..75293b64a 100644 --- a/docs/MODDING.md +++ b/docs/MODDING.md @@ -188,4 +188,4 @@ Assuming all went well, you can now push your changes to your fork with the foll ```bash git push origin -``` \ No newline at end of file +``` diff --git a/libultraship b/libultraship index 4600eedcc..c3a699403 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit 4600eedcc18f496319c99e07b8b2b4f11a0f6e64 +Subproject commit c3a699403793c9ac97733179fe078d2e2f271ee1 diff --git a/scripts/linux/appimage/soh.sh.in b/scripts/linux/appimage/soh.sh.in index bfc3cfec6..71121a5ff 100644 --- a/scripts/linux/appimage/soh.sh.in +++ b/scripts/linux/appimage/soh.sh.in @@ -21,12 +21,17 @@ fi while [[ (! -e "$SHIP_HOME"/oot.otr) || (! -e "$SHIP_HOME"/oot-mq.otr) ]]; do for romfile in "$SHIP_HOME"/*.*64 do - if [[ -e $romfile ]]; then + if [[ -e "$romfile" ]] || [[ -L "$romfile" ]]; then export ASSETDIR="$(mktemp -d /tmp/assets-XXXXX)" - ln -s "$HERE"/usr/bin/{assets,soh.elf,ZAPD} "$ASSETDIR" + ln -s "$SHIP_BIN_DIR"/{assets,soh.elf,ZAPD} "$ASSETDIR" export OLDPWD="$PWD" mkdir -p "$ASSETDIR"/tmp - ln -s "$romfile" "$ASSETDIR"/tmp/rom.z64 + if [[ -e "$romfile" ]]; then + ln -s "$romfile" "$ASSETDIR"/tmp/rom.z64 + else + ORIG_ROM_PATH=$(readlink "$romfile") + ln -s "$ORIG_ROM_PATH" "$ASSETDIR"/tmp/rom.z64 + fi cd "$ASSETDIR" ROMHASH=$(sha1sum -b "$ASSETDIR"/tmp/rom.z64 | awk '{ print $1 }') diff --git a/soh/CMake/DefaultCXX.cmake b/soh/CMake/DefaultCXX.cmake index 7b052b9cc..032326a13 100644 --- a/soh/CMake/DefaultCXX.cmake +++ b/soh/CMake/DefaultCXX.cmake @@ -8,5 +8,9 @@ if(MSVC) set_target_properties("${PROPS_TARGET}" PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") set_config_specific_property("DEFAULT_CXX_EXCEPTION_HANDLING" "/EHsc") - set_config_specific_property("DEFAULT_CXX_DEBUG_INFORMATION_FORMAT" "/Zi") + if (CMAKE_C_COMPILER_LAUNCHER MATCHES "ccache|sccache") + set_config_specific_property("DEFAULT_CXX_DEBUG_INFORMATION_FORMAT" "/Z7") + else() + set_config_specific_property("DEFAULT_CXX_DEBUG_INFORMATION_FORMAT" "/Zi") + endif() endif() \ No newline at end of file diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index fd0c6ac25..f653c5de5 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -154,7 +154,7 @@ list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/gfx.*") # handle crowd control removals list(REMOVE_ITEM soh__Enhancements "soh/Enhancements/crowd-control/soh.cs") list(REMOVE_ITEM soh__Enhancements "soh/Enhancements/crowd-control/soh.ccpak") -if (!BUILD_CROWD_CONTROL) +if (!BUILD_REMOTE_CONTROL) list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/crowd-control/*") endif() @@ -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 @@ -354,9 +354,15 @@ endif() find_package(SDL2) set(SDL2-INCLUDE ${SDL2_INCLUDE_DIRS}) -if (BUILD_CROWD_CONTROL) +if (BUILD_REMOTE_CONTROL) find_package(SDL2_net) - set(SDL2-NET-INCLUDE ${SDL_NET_INCLUDE_DIRS}) + + if(NOT SDL2_net_FOUND) + message(STATUS "SDL2_net not found (it's possible the version installed is too old). Disabling BUILD_REMOTE_CONTROL.") + set(BUILD_REMOTE_CONTROL 0) + else() + set(SDL2-NET-INCLUDE ${SDL_NET_INCLUDE_DIRS}) + endif() endif() target_include_directories(${PROJECT_NAME} PRIVATE assets @@ -408,9 +414,8 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") "$<$:" "NDEBUG" ">" - "$<$:ENABLE_CROWD_CONTROL>" + "$<$:ENABLE_REMOTE_CONTROL>" "INCLUDE_GAME_PRINTF;" - "ENABLE_CROWD_CONTROL;" "UNICODE;" "_UNICODE" STORMLIB_NO_AUTO_LINK @@ -455,7 +460,7 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU|Clang|AppleClang") "$<$:" "NDEBUG" ">" - "$<$:ENABLE_CROWD_CONTROL>" + "$<$:ENABLE_REMOTE_CONTROL>" "SPDLOG_ACTIVE_LEVEL=0;" "_CONSOLE;" "_CRT_SECURE_NO_WARNINGS;" @@ -624,6 +629,8 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") -Wno-parentheses -Wno-narrowing -Wno-missing-braces + -Wno-int-conversion + -Wno-implicit-int $<$: -Werror-implicit-function-declaration -Wno-incompatible-pointer-types @@ -689,7 +696,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") "glu32;" "SDL2::SDL2;" "SDL2::SDL2main;" - "$<$:SDL2_net::SDL2_net-static>" + "$<$:SDL2_net::SDL2_net-static>" "glfw;" "winmm;" "imm32;" @@ -742,7 +749,7 @@ else() "ZAPDUtils;" "ZAPDLib;" SDL2::SDL2 - "$<$:SDL2_net::SDL2_net>" + "$<$:SDL2_net::SDL2_net>" ${CMAKE_DL_LIBS} Threads::Threads ) diff --git a/soh/SHIPOFHARKINIAN.manifest b/soh/SHIPOFHARKINIAN.manifest index 80d9f6036..6f47a937f 100644 --- a/soh/SHIPOFHARKINIAN.manifest +++ b/soh/SHIPOFHARKINIAN.manifest @@ -28,4 +28,10 @@ + + + true/pm + permonitorv2,permonitor + + diff --git a/soh/assets/.gitignore b/soh/assets/.gitignore index dce395e6f..d75078bf1 100644 --- a/soh/assets/.gitignore +++ b/soh/assets/.gitignore @@ -4,4 +4,4 @@ *.cfg *.vtx.inc *.dlist.inc -*.txt \ No newline at end of file +!*.png diff --git a/soh/assets/custom/accessibility/texts/scenes_fra.json b/soh/assets/custom/accessibility/texts/scenes_fra.json index fa36c8840..3f73410e7 100644 --- a/soh/assets/custom/accessibility/texts/scenes_fra.json +++ b/soh/assets/custom/accessibility/texts/scenes_fra.json @@ -1,5 +1,5 @@ { - "0": "Abre Mojo", + "0": "Arbre Mojo", "1": "Caverne Dodongo", "2": "Ventre de Jabu-Jabu", "3": "Temple de la Forêt", @@ -58,9 +58,9 @@ "56": "Laboratoire du Lac", "57": "", // Tente du Marathonien (No title card) "58": "Cabane du fossoyeur", - "59": "Fountaine Royale des Fées", - "60": "Fountaine des Fées", - "61": "Fountaine Royale des Fées", + "59": "Fontaine Royale des Fées", + "60": "Fontaine des Fées", + "61": "Fontaine Royale des Fées", "62": "", // Grottes (No title card) "63": "", // Tombe 1 (No title card) "64": "", // Tombe 2 (No title card) @@ -109,4 +109,4 @@ "107": "", "108": "", // Debug: SRD Room (No title card) "109": "" // Debug: Treasure Chest Warp (No title card) -} \ No newline at end of file +} diff --git a/soh/assets/custom/textures/buttons/ABtn.png b/soh/assets/custom/textures/buttons/ABtn.png index c1b06571c..031924cb9 100644 Binary files a/soh/assets/custom/textures/buttons/ABtn.png and b/soh/assets/custom/textures/buttons/ABtn.png differ diff --git a/soh/assets/custom/textures/buttons/ABtnOutline.png b/soh/assets/custom/textures/buttons/ABtnOutline.png new file mode 100644 index 000000000..b3c9d3aab Binary files /dev/null and b/soh/assets/custom/textures/buttons/ABtnOutline.png differ diff --git a/soh/assets/custom/textures/buttons/AnalogStick.png b/soh/assets/custom/textures/buttons/AnalogStick.png new file mode 100644 index 000000000..d3fec7d01 Binary files /dev/null and b/soh/assets/custom/textures/buttons/AnalogStick.png differ diff --git a/soh/assets/custom/textures/buttons/AnalogStickOutline.png b/soh/assets/custom/textures/buttons/AnalogStickOutline.png new file mode 100644 index 000000000..e4d8d7190 Binary files /dev/null and b/soh/assets/custom/textures/buttons/AnalogStickOutline.png differ diff --git a/soh/assets/custom/textures/buttons/BBtn.png b/soh/assets/custom/textures/buttons/BBtn.png index 99b1197c9..8e2fbb54b 100644 Binary files a/soh/assets/custom/textures/buttons/BBtn.png and b/soh/assets/custom/textures/buttons/BBtn.png differ diff --git a/soh/assets/custom/textures/buttons/BBtnOutline.png b/soh/assets/custom/textures/buttons/BBtnOutline.png new file mode 100644 index 000000000..fab802f3b Binary files /dev/null and b/soh/assets/custom/textures/buttons/BBtnOutline.png differ diff --git a/soh/assets/custom/textures/buttons/CDown.png b/soh/assets/custom/textures/buttons/CDown.png index 741188eaf..f30cec2b3 100644 Binary files a/soh/assets/custom/textures/buttons/CDown.png and b/soh/assets/custom/textures/buttons/CDown.png differ diff --git a/soh/assets/custom/textures/buttons/CDownOutline.png b/soh/assets/custom/textures/buttons/CDownOutline.png new file mode 100644 index 000000000..9324c282d Binary files /dev/null and b/soh/assets/custom/textures/buttons/CDownOutline.png differ diff --git a/soh/assets/custom/textures/buttons/CLeft.png b/soh/assets/custom/textures/buttons/CLeft.png index 5e26a2067..43b04412f 100644 Binary files a/soh/assets/custom/textures/buttons/CLeft.png and b/soh/assets/custom/textures/buttons/CLeft.png differ diff --git a/soh/assets/custom/textures/buttons/CLeftOutline.png b/soh/assets/custom/textures/buttons/CLeftOutline.png new file mode 100644 index 000000000..c0d48f659 Binary files /dev/null and b/soh/assets/custom/textures/buttons/CLeftOutline.png differ diff --git a/soh/assets/custom/textures/buttons/CRight.png b/soh/assets/custom/textures/buttons/CRight.png index 9e6180639..c2d1afabf 100644 Binary files a/soh/assets/custom/textures/buttons/CRight.png and b/soh/assets/custom/textures/buttons/CRight.png differ diff --git a/soh/assets/custom/textures/buttons/CRightOutline.png b/soh/assets/custom/textures/buttons/CRightOutline.png new file mode 100644 index 000000000..d450084ea Binary files /dev/null and b/soh/assets/custom/textures/buttons/CRightOutline.png differ diff --git a/soh/assets/custom/textures/buttons/CUp.png b/soh/assets/custom/textures/buttons/CUp.png index 6c0e29d2d..aca464728 100644 Binary files a/soh/assets/custom/textures/buttons/CUp.png and b/soh/assets/custom/textures/buttons/CUp.png differ diff --git a/soh/assets/custom/textures/buttons/CUpOutline.png b/soh/assets/custom/textures/buttons/CUpOutline.png new file mode 100644 index 000000000..b21cd3ae9 Binary files /dev/null and b/soh/assets/custom/textures/buttons/CUpOutline.png differ diff --git a/soh/assets/custom/textures/buttons/DPadDown.png b/soh/assets/custom/textures/buttons/DPadDown.png new file mode 100644 index 000000000..cec0af1e5 Binary files /dev/null and b/soh/assets/custom/textures/buttons/DPadDown.png differ diff --git a/soh/assets/custom/textures/buttons/DPadDownOutline.png b/soh/assets/custom/textures/buttons/DPadDownOutline.png new file mode 100644 index 000000000..e8dca39b6 Binary files /dev/null and b/soh/assets/custom/textures/buttons/DPadDownOutline.png differ diff --git a/soh/assets/custom/textures/buttons/DPadLeft.png b/soh/assets/custom/textures/buttons/DPadLeft.png new file mode 100644 index 000000000..2a4a09b79 Binary files /dev/null and b/soh/assets/custom/textures/buttons/DPadLeft.png differ diff --git a/soh/assets/custom/textures/buttons/DPadLeftOutline.png b/soh/assets/custom/textures/buttons/DPadLeftOutline.png new file mode 100644 index 000000000..ba3dbf4e8 Binary files /dev/null and b/soh/assets/custom/textures/buttons/DPadLeftOutline.png differ diff --git a/soh/assets/custom/textures/buttons/DPadRight.png b/soh/assets/custom/textures/buttons/DPadRight.png new file mode 100644 index 000000000..e7854a219 Binary files /dev/null and b/soh/assets/custom/textures/buttons/DPadRight.png differ diff --git a/soh/assets/custom/textures/buttons/DPadRightOutline.png b/soh/assets/custom/textures/buttons/DPadRightOutline.png new file mode 100644 index 000000000..f6b4764c6 Binary files /dev/null and b/soh/assets/custom/textures/buttons/DPadRightOutline.png differ diff --git a/soh/assets/custom/textures/buttons/DPadUp.png b/soh/assets/custom/textures/buttons/DPadUp.png new file mode 100644 index 000000000..8d70d96da Binary files /dev/null and b/soh/assets/custom/textures/buttons/DPadUp.png differ diff --git a/soh/assets/custom/textures/buttons/DPadUpOutline.png b/soh/assets/custom/textures/buttons/DPadUpOutline.png new file mode 100644 index 000000000..8ad2d7959 Binary files /dev/null and b/soh/assets/custom/textures/buttons/DPadUpOutline.png differ diff --git a/soh/assets/custom/textures/buttons/InputViewerBackground.png b/soh/assets/custom/textures/buttons/InputViewerBackground.png new file mode 100644 index 000000000..091d686c0 Binary files /dev/null and b/soh/assets/custom/textures/buttons/InputViewerBackground.png differ diff --git a/soh/assets/custom/textures/buttons/LBtn.png b/soh/assets/custom/textures/buttons/LBtn.png index 2e0a8f00c..351ea383a 100644 Binary files a/soh/assets/custom/textures/buttons/LBtn.png and b/soh/assets/custom/textures/buttons/LBtn.png differ diff --git a/soh/assets/custom/textures/buttons/LBtnOutline.png b/soh/assets/custom/textures/buttons/LBtnOutline.png new file mode 100644 index 000000000..10cca9c8f Binary files /dev/null and b/soh/assets/custom/textures/buttons/LBtnOutline.png differ diff --git a/soh/assets/custom/textures/buttons/RBtn.png b/soh/assets/custom/textures/buttons/RBtn.png index c255643c3..ecb96bd6c 100644 Binary files a/soh/assets/custom/textures/buttons/RBtn.png and b/soh/assets/custom/textures/buttons/RBtn.png differ diff --git a/soh/assets/custom/textures/buttons/RBtnOutline.png b/soh/assets/custom/textures/buttons/RBtnOutline.png new file mode 100644 index 000000000..afeba32eb Binary files /dev/null and b/soh/assets/custom/textures/buttons/RBtnOutline.png differ diff --git a/soh/assets/custom/textures/buttons/RightStick.png b/soh/assets/custom/textures/buttons/RightStick.png new file mode 100644 index 000000000..6b8490aaf Binary files /dev/null and b/soh/assets/custom/textures/buttons/RightStick.png differ diff --git a/soh/assets/custom/textures/buttons/RightStickOutline.png b/soh/assets/custom/textures/buttons/RightStickOutline.png new file mode 100644 index 000000000..8fbd54fcc Binary files /dev/null and b/soh/assets/custom/textures/buttons/RightStickOutline.png differ diff --git a/soh/assets/custom/textures/buttons/StartBtn.png b/soh/assets/custom/textures/buttons/StartBtn.png index c3e08dc36..ec85f2619 100644 Binary files a/soh/assets/custom/textures/buttons/StartBtn.png and b/soh/assets/custom/textures/buttons/StartBtn.png differ diff --git a/soh/assets/custom/textures/buttons/StartBtnOutline.png b/soh/assets/custom/textures/buttons/StartBtnOutline.png new file mode 100644 index 000000000..a7902edbd Binary files /dev/null and b/soh/assets/custom/textures/buttons/StartBtnOutline.png differ diff --git a/soh/assets/custom/textures/buttons/ZBtn.png b/soh/assets/custom/textures/buttons/ZBtn.png index def8d9a6d..4fee52d57 100644 Binary files a/soh/assets/custom/textures/buttons/ZBtn.png and b/soh/assets/custom/textures/buttons/ZBtn.png differ diff --git a/soh/assets/custom/textures/buttons/ZBtnOutline.png b/soh/assets/custom/textures/buttons/ZBtnOutline.png new file mode 100644 index 000000000..5832ed339 Binary files /dev/null and b/soh/assets/custom/textures/buttons/ZBtnOutline.png differ diff --git a/soh/assets/sources/triforce-hunt/paths.txt b/soh/assets/sources/triforce-hunt/paths.txt new file mode 100644 index 000000000..40b397186 --- /dev/null +++ b/soh/assets/sources/triforce-hunt/paths.txt @@ -0,0 +1,15 @@ +Complete triforce: + DL name: gTriforcePieceCompletedDL + Export Path: objects/object_triforce_completed + +Shard 0: + DL name: gTriforcePiece0DL + Export Path: objects/object_triforce_piece_0 + +Shard 1: + DL name: gTriforcePiece1DL + Export Path: objects/object_triforce_piece_1 + +Shard 2: + DL name: gTriforcePiece2DL + Export Path: objects/object_triforce_piece_2 diff --git a/soh/assets/sources/triforce-hunt/textures/noise_tex.png b/soh/assets/sources/triforce-hunt/textures/noise_tex.png new file mode 100644 index 000000000..e4329f5d1 Binary files /dev/null and b/soh/assets/sources/triforce-hunt/textures/noise_tex.png differ diff --git a/soh/assets/sources/triforce-hunt/triforce_complete.blend b/soh/assets/sources/triforce-hunt/triforce_complete.blend new file mode 100644 index 000000000..e5e3fbd13 Binary files /dev/null and b/soh/assets/sources/triforce-hunt/triforce_complete.blend differ diff --git a/soh/assets/sources/triforce-hunt/triforce_shard_0.blend b/soh/assets/sources/triforce-hunt/triforce_shard_0.blend new file mode 100644 index 000000000..cd13e8859 Binary files /dev/null and b/soh/assets/sources/triforce-hunt/triforce_shard_0.blend differ diff --git a/soh/assets/sources/triforce-hunt/triforce_shard_1.blend b/soh/assets/sources/triforce-hunt/triforce_shard_1.blend new file mode 100644 index 000000000..aca2f0600 Binary files /dev/null and b/soh/assets/sources/triforce-hunt/triforce_shard_1.blend differ diff --git a/soh/assets/sources/triforce-hunt/triforce_shard_2.blend b/soh/assets/sources/triforce-hunt/triforce_shard_2.blend new file mode 100644 index 000000000..0ead9e5a6 Binary files /dev/null and b/soh/assets/sources/triforce-hunt/triforce_shard_2.blend differ diff --git a/soh/include/functions.h b/soh/include/functions.h index 037ba9d08..08179b341 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -1230,8 +1230,8 @@ Gfx* Gfx_EnvColor(GraphicsContext* gfxCtx, s32 r, s32 g, s32 b, s32 a); void Gfx_SetupFrame(GraphicsContext* gfxCtx, u8 r, u8 g, u8 b); void func_80095974(GraphicsContext* gfxCtx); void func_80095AA0(PlayState* play, Room* room, Input* arg2, UNK_TYPE arg3); -void func_8009638C(Gfx** displayList, void* source, void* tlut, u16 width, u16 height, u8 fmt, u8 siz, u16 mode0, - u16 tlutCount, f32 frameX, f32 frameY); +void Room_DrawBackground2D(Gfx** gfxP, void* tex, void* tlut, u16 width, u16 height, u8 fmt, u8 siz, u16 tlutMode, + u16 tlutCount, f32 offsetX, f32 offsetY); void func_80096FD4(PlayState* play, Room* room); u32 func_80096FE8(PlayState* play, RoomContext* roomCtx); s32 func_8009728C(PlayState* play, RoomContext* roomCtx, s32 roomNum); @@ -1828,8 +1828,8 @@ MtxF* Matrix_CheckFloats(MtxF* mf, char* file, s32 line); void Matrix_SetTranslateScaleMtx2(Mtx* mtx, f32 scaleX, f32 scaleY, f32 scaleZ, f32 translateX, f32 translateY, f32 translateZ); uintptr_t SysUcode_GetUCodeBoot(void); -uintptr_t SysUcode_GetUCodeBootSize(void); -uintptr_t SysUcode_GetUCode(void); +size_t SysUcode_GetUCodeBootSize(void); +uint32_t SysUcode_GetUCode(void); uintptr_t SysUcode_GetUCodeData(void); void func_800D2E30(UnkRumbleStruct* arg0); void func_800D3140(UnkRumbleStruct* arg0); @@ -2171,7 +2171,7 @@ void func_800FA18C(u8, u8); void Audio_SetVolScale(u8 playerIdx, u8 scaleIdx, u8 targetVol, u8 volFadeTimer); void func_800FA3DC(void); u8 func_800FAD34(void); -void func_800FADF8(void); +void Audio_ResetActiveSequences(void); void func_800FAEB4(void); void GfxPrint_SetColor(GfxPrint* this, u32 r, u32 g, u32 b, u32 a); void GfxPrint_SetPosPx(GfxPrint* this, s32 x, s32 y); @@ -2210,6 +2210,14 @@ s8 PadUtils_GetRelYImpl(Input* input); s8 PadUtils_GetRelX(Input* input); s8 PadUtils_GetRelY(Input* input); void PadUtils_UpdateRelXY(Input* input); +s8 PadUtils_GetCurRX(Input* input); +s8 PadUtils_GetCurRY(Input* input); +void PadUtils_SetRelRXY(Input* input, s32 x, s32 y); +s8 PadUtils_GetRelRXImpl(Input* input); +s8 PadUtils_GetRelRYImpl(Input* input); +s8 PadUtils_GetRelRX(Input* input); +s8 PadUtils_GetRelRY(Input* input); +void PadUtils_UpdateRelRXY(Input* input); s32 PadSetup_Init(OSMesgQueue* mq, u8* outMask, OSContStatus* status); f32 Math_FTanF(f32 x); f32 Math_FFloorF(f32 x); @@ -2459,6 +2467,10 @@ void Message_DrawText(PlayState* play, Gfx** gfxP); void Interface_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH); void Interface_RandoRestoreSwordless(void); +//Pause Warp +void PauseWarp_HandleSelection(); +void PauseWarp_Execute(); + // #endregion #ifdef __cplusplus diff --git a/soh/include/variables.h b/soh/include/variables.h index 154ef578a..9fd6b7247 100644 --- a/soh/include/variables.h +++ b/soh/include/variables.h @@ -224,7 +224,7 @@ extern "C" extern u16 gAudioSfxSwapSource[10]; extern u16 gAudioSfxSwapTarget[10]; extern u8 gAudioSfxSwapMode[10]; - extern unk_D_8016E750 D_8016E750[4]; + extern ActiveSequence gActiveSeqs[4]; extern AudioContext gAudioContext; extern void(*D_801755D0)(void); diff --git a/soh/include/z64.h b/soh/include/z64.h index e668b2e7a..bad2e1ca7 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/include/z64audio.h b/soh/include/z64audio.h index a7587ea2c..81775feba 100644 --- a/soh/include/z64audio.h +++ b/soh/include/z64audio.h @@ -970,43 +970,43 @@ typedef struct { } AudioContextInitSizes; // size = 0xC typedef struct { - /* 0x00 */ f32 unk_00; - /* 0x04 */ f32 unk_04; - /* 0x08 */ f32 unk_08; - /* 0x0C */ u16 unk_0C; - /* 0x10 */ f32 unk_10; - /* 0x14 */ f32 unk_14; - /* 0x18 */ f32 unk_18; - /* 0x1C */ u16 unk_1C; -} unk_50_s; // size = 0x20 + /* 0x00 */ f32 volCur; + /* 0x04 */ f32 volTarget; + /* 0x08 */ f32 volStep; + /* 0x0C */ u16 volTimer; + /* 0x10 */ f32 freqScaleCur; + /* 0x14 */ f32 freqScaleTarget; + /* 0x18 */ f32 freqScaleStep; + /* 0x1C */ u16 freqScaleTimer; +} ActiveSequenceChannelData; // size = 0x20 typedef struct { /* 0x000 */ f32 volCur; /* 0x004 */ f32 volTarget; - /* 0x008 */ f32 unk_08; - /* 0x00C */ u16 unk_0C; - /* 0x00E */ u8 volScales[0x4]; + /* 0x008 */ f32 volStep; + /* 0x00C */ u16 volTimer; + /* 0x00E */ u8 volScales[4]; /* 0x012 */ u8 volFadeTimer; /* 0x013 */ u8 fadeVolUpdate; - /* 0x014 */ u32 unk_14; - /* 0x018 */ u16 unk_18; - /* 0x01C */ f32 unk_1C; - /* 0x020 */ f32 unk_20; - /* 0x024 */ f32 unk_24; - /* 0x028 */ u16 unk_28; - /* 0x02C */ u32 unk_2C[8]; - /* 0x04C */ u8 unk_4C; - /* 0x04D */ u8 unk_4D; - /* 0x04E */ u8 unk_4E; - /* 0x050 */ unk_50_s unk_50[0x10]; - /* 0x250 */ u16 unk_250; - /* 0x252 */ u16 unk_252; - /* 0x254 */ u16 unk_254; - /* 0x256 */ u16 unk_256; - /* 0x258 */ u16 unk_258; - /* 0x25C */ u32 unk_25C; - /* 0x260 */ u8 unk_260; -} unk_D_8016E750; // size = 0x264 + /* 0x014 */ u32 tempoCmd; + /* 0x018 */ u16 tempoOriginal; // stores the original tempo before modifying it (to reset back to) + /* 0x01C */ f32 tempoCur; + /* 0x020 */ f32 tempoTarget; + /* 0x024 */ f32 tempoStep; + /* 0x028 */ u16 tempoTimer; + /* 0x02C */ u32 setupCmd[8]; // a queue of cmds to execute once the player is disabled + /* 0x04C */ u8 setupCmdTimer; // only execute setup commands when the timer is at 0. + /* 0x04D */ u8 setupCmdNum; // number of setup commands requested once the player is disabled + /* 0x04E */ u8 setupFadeTimer; + /* 0x050 */ ActiveSequenceChannelData channelData[16]; + /* 0x250 */ u16 freqScaleChannelFlags; + /* 0x252 */ u16 volChannelFlags; + /* 0x254 */ u16 seqId; // active seqId currently playing. Resets when sequence stops + /* 0x256 */ u16 prevSeqId; // last seqId played on a player. Does not reset when sequence stops + /* 0x258 */ u16 channelPortMask; + /* 0x25C */ u32 startSeqCmd; // This name comes from MM + /* 0x260 */ u8 isWaitingForFonts; // This name comes from MM +} ActiveSequence; // size = 0x264 typedef enum { /* 0 */ BANK_PLAYER, diff --git a/soh/include/z64player.h b/soh/include/z64player.h index 73b5166e2..d03b64da5 100644 --- a/soh/include/z64player.h +++ b/soh/include/z64player.h @@ -132,16 +132,6 @@ typedef enum { /* 0x40 */ PLAYER_IA_MASK_GERUDO, /* 0x41 */ PLAYER_IA_MASK_TRUTH, /* 0x42 */ PLAYER_IA_LENS_OF_TRUTH, - // Upstream TODO: Document why these entries were added - /* 0x43 */ PLAYER_IA_SHIELD_DEKU, - /* 0x44 */ PLAYER_IA_SHIELD_HYLIAN, - /* 0x45 */ PLAYER_IA_SHIELD_MIRROR, - /* 0x46 */ PLAYER_IA_TUNIC_KOKIRI, - /* 0x47 */ PLAYER_IA_TUNIC_GORON, - /* 0x48 */ PLAYER_IA_TUNIC_ZORA, - /* 0x49 */ PLAYER_IA_BOOTS_KOKIRI, - /* 0x4A */ PLAYER_IA_BOOTS_IRON, - /* 0x4B */ PLAYER_IA_BOOTS_HOVER, /* 0x4C */ PLAYER_IA_MAX } PlayerItemAction; @@ -355,7 +345,7 @@ typedef enum { #define PLAYER_LIMB_BUF_COUNT LIMB_BUF_COUNT(PLAYER_LIMB_MAX) typedef struct { - /* 0x00 */ f32 unk_00; + /* 0x00 */ f32 ceilingCheckHeight; /* 0x04 */ f32 unk_04; /* 0x08 */ f32 unk_08; /* 0x0C */ f32 unk_0C; @@ -369,7 +359,7 @@ typedef struct { /* 0x2C */ f32 unk_2C; /* 0x30 */ f32 unk_30; /* 0x34 */ f32 unk_34; - /* 0x38 */ f32 unk_38; + /* 0x38 */ f32 wallCheckRadius; /* 0x3C */ f32 unk_3C; /* 0x40 */ f32 unk_40; /* 0x44 */ Vec3s unk_44; @@ -499,180 +489,187 @@ typedef s32 (*UpperActionFunc)(struct Player*, struct PlayState*); typedef void (*PlayerFuncA74)(struct PlayState*, struct Player*); typedef struct Player { - /* 0x0000 */ Actor actor; - /* 0x014C */ s8 currentTunic; // current tunic from `PlayerTunic` - /* 0x014D */ s8 currentSwordItemId; - /* 0x014E */ s8 currentShield; // current shield from `PlayerShield` - /* 0x014F */ s8 currentBoots; // current boots from `PlayerBoots` - /* 0x0150 */ s8 heldItemButton; // Button index for the item currently used - /* 0x0151 */ s8 heldItemAction; // Item action for the item currently used - /* 0x0152 */ u8 heldItemId; // Item id for the item currently used - /* 0x0153 */ s8 prevBoots; // previous boots from `PlayerBoots` - /* 0x0154 */ s8 itemAction; // the difference between this and heldItemAction is unclear - /* 0x0155 */ char unk_155[0x003]; - /* 0x0158 */ u8 modelGroup; - /* 0x0159 */ u8 nextModelGroup; - /* 0x015A */ s8 itemChangeType; - /* 0x015B */ u8 modelAnimType; - /* 0x015C */ u8 leftHandType; - /* 0x015D */ u8 rightHandType; - /* 0x015E */ u8 sheathType; - /* 0x015F */ u8 currentMask; // current mask equipped from `PlayerMask` - /* 0x0160 */ Gfx** rightHandDLists; - /* 0x0164 */ Gfx** leftHandDLists; - /* 0x0168 */ Gfx** sheathDLists; - /* 0x016C */ Gfx** waistDLists; - /* 0x0170 */ u8 giObjectLoading; + /* 0x0000 */ Actor actor; + /* 0x014C */ s8 currentTunic; // current tunic from `PlayerTunic` + /* 0x014D */ s8 currentSwordItemId; + /* 0x014E */ s8 currentShield; // current shield from `PlayerShield` + /* 0x014F */ s8 currentBoots; // current boots from `PlayerBoots` + /* 0x0150 */ s8 heldItemButton; // Button index for the item currently used + /* 0x0151 */ s8 heldItemAction; // Item action for the item currently used + /* 0x0152 */ u8 heldItemId; // Item id for the item currently used + /* 0x0153 */ s8 prevBoots; // previous boots from `PlayerBoots` + /* 0x0154 */ s8 itemAction; // the difference between this and heldItemAction is unclear + /* 0x0155 */ char unk_155[0x003]; + /* 0x0158 */ u8 modelGroup; + /* 0x0159 */ u8 nextModelGroup; + /* 0x015A */ s8 itemChangeType; + /* 0x015B */ u8 modelAnimType; + /* 0x015C */ u8 leftHandType; + /* 0x015D */ u8 rightHandType; + /* 0x015E */ u8 sheathType; + /* 0x015F */ u8 currentMask; // current mask equipped from `PlayerMask` + /* 0x0160 */ Gfx** rightHandDLists; + /* 0x0164 */ Gfx** leftHandDLists; + /* 0x0168 */ Gfx** sheathDLists; + /* 0x016C */ Gfx** waistDLists; + /* 0x0170 */ u8 giObjectLoading; /* 0x0174 */ DmaRequest giObjectDmaRequest; /* 0x0194 */ OSMesgQueue giObjectLoadQueue; - /* 0x01AC */ OSMesg giObjectLoadMsg; - /* 0x01B0 */ void* giObjectSegment; // also used for title card textures - /* 0x01B4 */ SkelAnime skelAnime; - /* 0x01F8 */ Vec3s jointTable[PLAYER_LIMB_BUF_COUNT]; - /* 0x0288 */ Vec3s morphTable[PLAYER_LIMB_BUF_COUNT]; - /* 0x0318 */ Vec3s blendTable[PLAYER_LIMB_BUF_COUNT]; - /* 0x03A8 */ s16 unk_3A8[2]; - /* 0x03AC */ Actor* heldActor; - /* 0x03B0 */ Vec3f leftHandPos; - /* 0x03BC */ Vec3s unk_3BC; - /* 0x03C4 */ Actor* unk_3C4; - /* 0x03C8 */ Vec3f unk_3C8; - /* 0x03D4 */ char unk_3D4[0x058]; - /* 0x042C */ s8 doorType; - /* 0x042D */ s8 doorDirection; - /* 0x042E */ s16 doorTimer; - /* 0x0430 */ Actor* doorActor; - /* 0x0434 */ s16 getItemId; // Upstream TODO: Document why this is s16 while it's s8 upstream - /* 0x0436 */ u16 getItemDirection; - /* 0x0438 */ Actor* interactRangeActor; - /* 0x043C */ s8 mountSide; - /* 0x043D */ char unk_43D[0x003]; - /* 0x0440 */ Actor* rideActor; - /* 0x0444 */ u8 csAction; - /* 0x0445 */ u8 prevCsAction; - /* 0x0446 */ u8 cueId; - /* 0x0447 */ u8 unk_447; - /* 0x0448 */ Actor* csActor; - /* 0x044C */ char unk_44C[0x004]; - /* 0x0450 */ Vec3f unk_450; - /* 0x045C */ Vec3f unk_45C; - /* 0x0468 */ char unk_468[0x002]; - /* 0x046A */ s16 doorBgCamIndex; - /* 0x046C */ s16 subCamId; - /* 0x046E */ char unk_46E[0x02A]; + /* 0x01AC */ OSMesg giObjectLoadMsg; + /* 0x01B0 */ void* giObjectSegment; // also used for title card textures + /* 0x01B4 */ SkelAnime skelAnime; + /* 0x01F8 */ Vec3s jointTable[PLAYER_LIMB_BUF_COUNT]; + /* 0x0288 */ Vec3s morphTable[PLAYER_LIMB_BUF_COUNT]; + /* 0x0318 */ Vec3s blendTable[PLAYER_LIMB_BUF_COUNT]; + /* 0x03A8 */ s16 unk_3A8[2]; + /* 0x03AC */ Actor* heldActor; + /* 0x03B0 */ Vec3f leftHandPos; + /* 0x03BC */ Vec3s unk_3BC; + /* 0x03C4 */ Actor* unk_3C4; + /* 0x03C8 */ Vec3f unk_3C8; + /* 0x03D4 */ char unk_3D4[0x058]; + /* 0x042C */ s8 doorType; + /* 0x042D */ s8 doorDirection; + /* 0x042E */ s16 doorTimer; + /* 0x0430 */ Actor* doorActor; + /* 0x0434 */ s16 getItemId; // Upstream TODO: Document why this is s16 while it's s8 upstream + /* 0x0436 */ u16 getItemDirection; + /* 0x0438 */ Actor* interactRangeActor; + /* 0x043C */ s8 mountSide; + /* 0x043D */ char unk_43D[0x003]; + /* 0x0440 */ Actor* rideActor; + /* 0x0444 */ u8 csAction; + /* 0x0445 */ u8 prevCsAction; + /* 0x0446 */ u8 cueId; + /* 0x0447 */ u8 unk_447; + /* 0x0448 */ Actor* csActor; // Actor involved in a `csAction`. Typically the actor that invoked the cutscene. + /* 0x044C */ char unk_44C[0x004]; + /* 0x0450 */ Vec3f unk_450; + /* 0x045C */ Vec3f unk_45C; + /* 0x0468 */ char unk_468[0x002]; + /* 0x046A */ s16 doorBgCamIndex; + /* 0x046C */ s16 subCamId; + /* 0x046E */ char unk_46E[0x02A]; /* 0x0498 */ ColliderCylinder cylinder; /* 0x04E4 */ ColliderQuad meleeWeaponQuads[2]; /* 0x05E4 */ ColliderQuad shieldQuad; - /* 0x0664 */ Actor* unk_664; - /* 0x0668 */ char unk_668[0x004]; - /* 0x066C */ s32 unk_66C; - /* 0x0670 */ s32 meleeWeaponEffectIndex; + /* 0x0664 */ Actor* unk_664; + /* 0x0668 */ char unk_668[0x004]; + /* 0x066C */ s32 unk_66C; + /* 0x0670 */ s32 meleeWeaponEffectIndex; /* 0x0674 */ PlayerActionFunc actionFunc; /* 0x0678 */ PlayerAgeProperties* ageProperties; - /* 0x067C */ u32 stateFlags1; - /* 0x0680 */ u32 stateFlags2; - /* 0x0684 */ Actor* unk_684; - /* 0x0688 */ Actor* boomerangActor; - /* 0x068C */ Actor* naviActor; - /* 0x0690 */ s16 naviTextId; - /* 0x0692 */ u8 stateFlags3; - /* 0x0693 */ s8 exchangeItemId; - /* 0x0694 */ Actor* targetActor; - /* 0x0698 */ f32 targetActorDistance; - /* 0x069C */ char unk_69C[0x004]; - /* 0x06A0 */ f32 unk_6A0; - /* 0x06A4 */ f32 closestSecretDistSq; - /* 0x06A8 */ Actor* unk_6A8; - /* 0x06AC */ s8 unk_6AC; - /* 0x06AD */ u8 unk_6AD; - /* 0x06AE */ u16 unk_6AE; - /* 0x06B0 */ s16 unk_6B0; - /* 0x06B2 */ char unk_6B4[0x004]; - /* 0x06B6 */ s16 unk_6B6; - /* 0x06B8 */ s16 unk_6B8; - /* 0x06BA */ s16 unk_6BA; - /* 0x06BC */ s16 unk_6BC; - /* 0x06BE */ s16 unk_6BE; - /* 0x06C0 */ s16 unk_6C0; - /* 0x06C2 */ s16 unk_6C2; - /* 0x06C4 */ f32 unk_6C4; - /* 0x06C8 */ SkelAnime upperSkelAnime; - /* 0x070C */ Vec3s upperJointTable[PLAYER_LIMB_BUF_COUNT]; - /* 0x079C */ Vec3s upperMorphTable[PLAYER_LIMB_BUF_COUNT]; + /* 0x067C */ u32 stateFlags1; + /* 0x0680 */ u32 stateFlags2; + /* 0x0684 */ Actor* unk_684; + /* 0x0688 */ Actor* boomerangActor; + /* 0x068C */ Actor* naviActor; + /* 0x0690 */ s16 naviTextId; + /* 0x0692 */ u8 stateFlags3; + /* 0x0693 */ s8 exchangeItemId; + /* 0x0694 */ Actor* targetActor; + /* 0x0698 */ f32 targetActorDistance; + /* 0x069C */ char unk_69C[0x004]; + /* 0x06A0 */ f32 unk_6A0; + /* 0x06A4 */ f32 closestSecretDistSq; + /* 0x06A8 */ Actor* unk_6A8; + /* 0x06AC */ s8 unk_6AC; + /* 0x06AD */ u8 unk_6AD; + /* 0x06AE */ u16 unk_6AE; + /* 0x06B0 */ s16 unk_6B0; + /* 0x06B2 */ char unk_6B4[0x004]; + /* 0x06B6 */ s16 unk_6B6; + /* 0x06B8 */ s16 unk_6B8; + /* 0x06BA */ s16 unk_6BA; + /* 0x06BC */ s16 unk_6BC; + /* 0x06BE */ s16 unk_6BE; + /* 0x06C0 */ s16 unk_6C0; + /* 0x06C2 */ s16 unk_6C2; + /* 0x06C4 */ f32 unk_6C4; + /* 0x06C8 */ SkelAnime upperSkelAnime; + /* 0x070C */ Vec3s upperJointTable[PLAYER_LIMB_BUF_COUNT]; + /* 0x079C */ Vec3s upperMorphTable[PLAYER_LIMB_BUF_COUNT]; /* 0x082C */ UpperActionFunc upperActionFunc; - /* 0x0830 */ f32 upperAnimBlendWeight; - /* 0x0834 */ s16 unk_834; - /* 0x0836 */ s8 unk_836; - /* 0x0837 */ u8 unk_837; - /* 0x0838 */ f32 linearVelocity; - /* 0x083C */ s16 currentYaw; - /* 0x083E */ s16 targetYaw; - /* 0x0840 */ u16 underwaterTimer; - /* 0x0842 */ s8 meleeWeaponAnimation; - /* 0x0843 */ s8 meleeWeaponState; - /* 0x0844 */ s8 unk_844; - /* 0x0845 */ u8 unk_845; - /* 0x0846 */ u8 unk_846; - /* 0x0847 */ s8 unk_847[4]; - /* 0x084B */ s8 unk_84B[4]; - /* 0x084F */ s8 unk_84F; - /* 0x0850 */ s16 unk_850; // multipurpose timer - /* 0x0854 */ f32 unk_854; - /* 0x0858 */ f32 unk_858; - /* 0x085C */ f32 unk_85C; // stick length among other things - /* 0x0860 */ s16 unk_860; // stick flame timer among other things - /* 0x0862 */ s16 unk_862; // get item draw ID + 1 - /* 0x0864 */ f32 unk_864; - /* 0x0868 */ f32 unk_868; - /* 0x086C */ f32 unk_86C; - /* 0x0870 */ f32 unk_870; - /* 0x0874 */ f32 unk_874; - /* 0x0878 */ f32 unk_878; - /* 0x087C */ s16 unk_87C; - /* 0x087E */ s16 unk_87E; - /* 0x0880 */ f32 unk_880; - /* 0x0884 */ f32 yDistToLedge; // y distance to ground above an interact wall. LEDGE_DIST_MAX if no ground is found - /* 0x0888 */ f32 distToInteractWall; // distance to the colliding wall plane - /* 0x088C */ u8 unk_88C; - /* 0x088D */ u8 unk_88D; - /* 0x088E */ u8 unk_88E; - /* 0x088F */ u8 unk_88F; - /* 0x0890 */ u8 unk_890; - /* 0x0891 */ u8 shockTimer; - /* 0x0892 */ u8 unk_892; - /* 0x0893 */ u8 hoverBootsTimer; - /* 0x0894 */ s16 fallStartHeight; // last truncated Y position before falling - /* 0x0896 */ s16 fallDistance; // truncated Y distance the player has fallen so far (positive is down) - /* 0x0898 */ s16 floorPitch; // angle of the floor slope in the direction of current world yaw (positive for ascending slope) - /* 0x089A */ s16 floorPitchAlt; // the calculation for this value is bugged and doesn't represent anything meaningful - /* 0x089C */ s16 unk_89C; - /* 0x089E */ u16 floorSfxOffset; - /* 0x08A0 */ u8 unk_8A0; - /* 0x08A1 */ u8 unk_8A1; - /* 0x08A2 */ s16 unk_8A2; - /* 0x08A4 */ f32 unk_8A4; - /* 0x08A8 */ f32 unk_8A8; - /* 0x08AC */ f32 pushedSpeed; // Pushing player, examples include water currents, floor conveyors, climbing sloped surfaces - /* 0x08B0 */ s16 pushedYaw; // Yaw direction of player being pushed + /* 0x0830 */ f32 upperAnimBlendWeight; + /* 0x0834 */ s16 unk_834; + /* 0x0836 */ s8 unk_836; + /* 0x0837 */ u8 unk_837; + /* 0x0838 */ f32 linearVelocity; + /* 0x083C */ s16 yaw; // General yaw value, used both for world and shape rotation. Current or target value depending on context. + /* 0x083E */ s16 zTargetYaw; // yaw relating to Z targeting/"parallel" mode + /* 0x0840 */ u16 underwaterTimer; + /* 0x0842 */ s8 meleeWeaponAnimation; + /* 0x0843 */ s8 meleeWeaponState; + /* 0x0844 */ s8 unk_844; + /* 0x0845 */ u8 unk_845; + /* 0x0846 */ u8 unk_846; + /* 0x0847 */ s8 unk_847[4]; + /* 0x084B */ s8 unk_84B[4]; + + /* 0x084F */ union { + s8 actionVar1; + } av1; // "Action Variable 1": context dependent variable that has different meanings depending on what action is currently running + + /* 0x0850 */ union { + s16 actionVar2; + } av2; // "Action Variable 2": context dependent variable that has different meanings depending on what action is currently running + + /* 0x0854 */ f32 unk_854; + /* 0x0858 */ f32 unk_858; + /* 0x085C */ f32 unk_85C; // stick length among other things + /* 0x0860 */ s16 unk_860; // stick flame timer among other things + /* 0x0862 */ s16 unk_862; // get item draw ID + 1 + /* 0x0864 */ f32 unk_864; + /* 0x0868 */ f32 unk_868; + /* 0x086C */ f32 unk_86C; + /* 0x0870 */ f32 unk_870; + /* 0x0874 */ f32 unk_874; + /* 0x0878 */ f32 unk_878; + /* 0x087C */ s16 unk_87C; + /* 0x087E */ s16 unk_87E; + /* 0x0880 */ f32 unk_880; + /* 0x0884 */ f32 yDistToLedge; // y distance to ground above an interact wall. LEDGE_DIST_MAX if no ground is found + /* 0x0888 */ f32 distToInteractWall; // xyz distance to the interact wall + /* 0x088C */ u8 ledgeClimbType; + /* 0x088D */ u8 ledgeClimbDelayTimer; + /* 0x088E */ u8 unk_88E; + /* 0x088F */ u8 unk_88F; + /* 0x0890 */ u8 unk_890; + /* 0x0891 */ u8 bodyShockTimer; + /* 0x0892 */ u8 unk_892; + /* 0x0893 */ u8 hoverBootsTimer; + /* 0x0894 */ s16 fallStartHeight; // last truncated Y position before falling + /* 0x0896 */ s16 fallDistance; // truncated Y distance the player has fallen so far (positive is down) + /* 0x0898 */ s16 floorPitch; // angle of the floor slope in the direction of current world yaw (positive for ascending slope) + /* 0x089A */ s16 floorPitchAlt; // the calculation for this value is bugged and doesn't represent anything meaningful + /* 0x089C */ s16 unk_89C; + /* 0x089E */ u16 floorSfxOffset; + /* 0x08A0 */ u8 unk_8A0; + /* 0x08A1 */ u8 unk_8A1; + /* 0x08A2 */ s16 unk_8A2; + /* 0x08A4 */ f32 unk_8A4; + /* 0x08A8 */ f32 unk_8A8; + /* 0x08AC */ f32 pushedSpeed; // Pushing player, examples include water currents, floor conveyors, climbing sloped surfaces + /* 0x08B0 */ s16 pushedYaw; // Yaw direction of player being pushed /* 0x08B4 */ WeaponInfo meleeWeaponInfo[3]; - /* 0x0908 */ Vec3f bodyPartsPos[PLAYER_BODYPART_MAX]; - /* 0x09E0 */ MtxF mf_9E0; - /* 0x0A20 */ MtxF shieldMf; - /* 0x0A60 */ u8 isBurning; - /* 0x0A61 */ u8 flameTimers[PLAYER_BODYPART_MAX]; // one flame per body part - /* 0x0A73 */ u8 unk_A73; + /* 0x0908 */ Vec3f bodyPartsPos[PLAYER_BODYPART_MAX]; + /* 0x09E0 */ MtxF mf_9E0; + /* 0x0A20 */ MtxF shieldMf; + /* 0x0A60 */ u8 bodyIsBurning; + /* 0x0A61 */ u8 bodyFlameTimers[PLAYER_BODYPART_MAX]; // one flame per body part + /* 0x0A73 */ u8 unk_A73; /* 0x0A74 */ PlayerFuncA74 func_A74; - /* 0x0A78 */ s8 invincibilityTimer; // prevents damage when nonzero (positive = visible, counts towards zero each frame) - /* 0x0A79 */ u8 unk_A79; - /* 0x0A7A */ u8 unk_A7A; - /* 0x0A7B */ u8 unk_A7B; - /* 0x0A7C */ f32 unk_A7C; - /* 0x0A80 */ s16 unk_A80; - /* 0x0A82 */ u16 unk_A82; - /* 0x0A84 */ s16 unk_A84; - /* 0x0A86 */ s8 unk_A86; - /* 0x0A87 */ u8 unk_A87; - /* 0x0A88 */ Vec3f unk_A88; // previous body part 0 position + /* 0x0A78 */ s8 invincibilityTimer; // prevents damage when nonzero (positive = visible, counts towards zero each frame) + /* 0x0A79 */ u8 floorTypeTimer; // counts up every frame the current floor type is the same as the last frame + /* 0x0A7A */ u8 floorProperty; + /* 0x0A7B */ u8 prevFloorType; + /* 0x0A7C */ f32 prevControlStickMagnitude; + /* 0x0A80 */ s16 prevControlStickAngle; + /* 0x0A82 */ u16 prevFloorSfxOffset; + /* 0x0A84 */ s16 unk_A84; + /* 0x0A86 */ s8 unk_A86; + /* 0x0A87 */ u8 unk_A87; + /* 0x0A88 */ Vec3f unk_A88; // previous body part 0 position // #region SOH [General] // Upstream TODO: Rename these to be more obviously SoH specific /* */ PendingFlag pendingFlag; @@ -680,7 +677,7 @@ typedef struct Player { // #endregion // #region SOH [Enhancements] // Upstream TODO: Rename this to make it more obvious it is apart of an enhancement - /* */ u8 boomerangQuickRecall; // Has the player pressed the boomerang button while it's in the air still? + /* */ u8 boomerangQuickRecall; // Has the player pressed the boomerang button while it's in the air still? // #endregion u8 ivanFloating; u8 ivanDamageMultiplier; diff --git a/soh/macosx/Info.plist.in b/soh/macosx/Info.plist.in index dd0a49e8f..61316a18f 100644 --- a/soh/macosx/Info.plist.in +++ b/soh/macosx/Info.plist.in @@ -33,5 +33,10 @@ public.app-category.games LSMinimumSystemVersion 10.15 + LSArchitecturePriority + + arm64 + x86_64 + diff --git a/soh/macosx/soh-macos.sh.in b/soh/macosx/soh-macos.sh.in index 0983f63b1..b90bd22e1 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,183 +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 +"$RESPATH"/soh-macos - # 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 - launch_arch="x86_64" -fi - -arch -${launch_arch} "$RESPATH"/soh-macos exit diff --git a/soh/soh/ActorDB.cpp b/soh/soh/ActorDB.cpp index a66048b32..ea8450ac4 100644 --- a/soh/soh/ActorDB.cpp +++ b/soh/soh/ActorDB.cpp @@ -74,7 +74,7 @@ static std::unordered_map actorDescriptions = { { ACTOR_EN_BUBBLE, "Shabom" }, { ACTOR_DOOR_SHUTTER, "Shutter Door" }, { ACTOR_EN_DODOJR, "Baby Dodongo" }, - { ACTOR_EN_BDFIRE, "Empty" }, + { ACTOR_EN_BDFIRE, "King Dodongo's Fire Breath" }, { ACTOR_EN_BOOM, "Boomerang" }, { ACTOR_EN_TORCH2, "Dark Link" }, { ACTOR_EN_BILI, "Biri" }, @@ -132,7 +132,7 @@ static std::unordered_map actorDescriptions = { { ACTOR_BG_TOKI_HIKARI, "Windows (Temple of Time)" }, { ACTOR_EN_YUKABYUN, "Flying Floor Tile" }, { ACTOR_BG_TOKI_SWD, "Master Sword" }, - { ACTOR_EN_FHG_FIRE, "Empty" }, + { ACTOR_EN_FHG_FIRE, "Phantom Ganon's Lighting Attack" }, { ACTOR_BG_MJIN, "Warp Song Pad" }, { ACTOR_BG_HIDAN_KOUSI, "Sliding Metal Gate" }, { ACTOR_DOOR_TOKI, "Door of Time Collision" }, @@ -548,6 +548,10 @@ int ActorDB::RetrieveId(const std::string& name) { return entry->second; } +int ActorDB::GetEntryCount() { + return db.size(); +} + ActorDB::Entry::Entry() { entry.name = nullptr; entry.desc = nullptr; diff --git a/soh/soh/ActorDB.h b/soh/soh/ActorDB.h index afb033a2f..8bcbb3f6a 100644 --- a/soh/soh/ActorDB.h +++ b/soh/soh/ActorDB.h @@ -64,6 +64,7 @@ public: static void AddBuiltInCustomActors(); + int GetEntryCount(); private: Entry& AddEntry(const std::string& name, const std::string& desc, size_t index); Entry& AddEntry(const std::string& name, const std::string& desc, const ActorInit& init); diff --git a/soh/soh/Enhancements/audio/AudioEditor.cpp b/soh/soh/Enhancements/audio/AudioEditor.cpp index 8b0615a83..8c0d415a3 100644 --- a/soh/soh/Enhancements/audio/AudioEditor.cpp +++ b/soh/soh/Enhancements/audio/AudioEditor.cpp @@ -12,6 +12,7 @@ #include #include "../../UIWidgets.hpp" #include "AudioCollection.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" Vec3f pos = { 0.0f, 0.0f, 0.0f }; f32 freqScale = 1.0f; @@ -78,7 +79,12 @@ void UpdateCurrentBGM(u16 seqKey, SeqType seqType) { void RandomizeGroup(SeqType type) { std::vector values; - + + // An empty IncludedSequences set means that the AudioEditor window has never been drawn + if (AudioCollection::Instance->GetIncludedSequences().empty()) { + AudioCollection::Instance->InitializeShufflePool(); + } + // use a while loop to add duplicates if we don't have enough included sequences while (values.size() < AuthenticCountBySequenceType(type)) { for (const auto& seqData : AudioCollection::Instance->GetIncludedSequences()) { @@ -123,6 +129,34 @@ void ResetGroup(const std::map& map, SeqType type) { } } +void LockGroup(const std::map& map, SeqType type) { + for (const auto& [defaultValue, seqData] : map) { + if (seqData.category == type) { + // Only save authentic sequence CVars + if (seqData.category == SEQ_FANFARE && defaultValue >= MAX_AUTHENTIC_SEQID) { + continue; + } + const std::string cvarKey = AudioCollection::Instance->GetCvarKey(seqData.sfxKey); + const std::string cvarLockKey = AudioCollection::Instance->GetCvarLockKey(seqData.sfxKey); + CVarSetInteger(cvarLockKey.c_str(), 1); + } + } +} + +void UnlockGroup(const std::map& map, SeqType type) { + for (const auto& [defaultValue, seqData] : map) { + if (seqData.category == type) { + // Only save authentic sequence CVars + if (seqData.category == SEQ_FANFARE && defaultValue >= MAX_AUTHENTIC_SEQID) { + continue; + } + const std::string cvarKey = AudioCollection::Instance->GetCvarKey(seqData.sfxKey); + const std::string cvarLockKey = AudioCollection::Instance->GetCvarLockKey(seqData.sfxKey); + CVarSetInteger(cvarLockKey.c_str(), 0); + } + } +} + void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequenceType) { const std::string cvarKey = AudioCollection::Instance->GetCvarKey(sfxKey); const std::string hiddenKey = "##" + cvarKey; @@ -163,6 +197,8 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { const std::string hiddenTabId = "##" + tabId; const std::string resetAllButton = "Reset All" + hiddenTabId; const std::string randomizeAllButton = "Randomize All" + hiddenTabId; + const std::string lockAllButton = "Lock All" + hiddenTabId; + const std::string unlockAllButton = "Unlock All" + hiddenTabId; if (ImGui::Button(resetAllButton.c_str())) { auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); @@ -184,6 +220,28 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { ReplayCurrentBGM(); } } + ImGui::SameLine(); + if (ImGui::Button(lockAllButton.c_str())) { + auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); + auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + LockGroup(map, type); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + auto curReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + if (type == SEQ_BGM_WORLD && prevReplacement != curReplacement) { + ReplayCurrentBGM(); + } + } + ImGui::SameLine(); + if (ImGui::Button(unlockAllButton.c_str())) { + auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); + auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + UnlockGroup(map, type); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + auto curReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + if (type == SEQ_BGM_WORLD && prevReplacement != curReplacement) { + ReplayCurrentBGM(); + } + } ImGui::BeginTable(tabId.c_str(), 3, ImGuiTableFlags_SizingFixedFit); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); @@ -350,6 +408,19 @@ void DrawTypeChip(SeqType type) { ImGui::EndDisabled(); } + +void AudioEditorRegisterOnSceneInitHook() { + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { + if (CVarGetInteger("gAudioEditor.RandomizeAllOnNewScene", 0)) { + AudioEditor_RandomizeAll(); + } + }); +} + +void AudioEditor::InitElement() { + AudioEditorRegisterOnSceneInitHook(); +} + void AudioEditor::DrawElement() { AudioCollection::Instance->InitializeShufflePool(); @@ -359,6 +430,28 @@ void AudioEditor::DrawElement() { return; } + float buttonSegments = ImGui::GetContentRegionAvail().x / 4; + if (ImGui::Button("Randomize All Groups", ImVec2(buttonSegments, 30.0f))) { + AudioEditor_RandomizeAll(); + } + UIWidgets::Tooltip("Randomizes all unlocked music and sound effects across tab groups"); + ImGui::SameLine(); + if (ImGui::Button("Reset All Groups", ImVec2(buttonSegments, 30.0f))) { + AudioEditor_ResetAll(); + } + UIWidgets::Tooltip("Resets all unlocked music and sound effects across tab groups"); + ImGui::SameLine(); + if (ImGui::Button("Lock All Groups", ImVec2(buttonSegments, 30.0f))) { + AudioEditor_LockAll(); + } + UIWidgets::Tooltip("Locks all music and sound effects across tab groups"); + ImGui::SameLine(); + if (ImGui::Button("Unlock All Groups", ImVec2(buttonSegments, 30.0f))) { + AudioEditor_UnlockAll(); + } + UIWidgets::Tooltip("Unlocks all music and sound effects across tab groups"); + + if (ImGui::BeginTabBar("SfxContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Background Music")) { Draw_SfxTab("backgroundMusic", SEQ_BGM_WORLD); @@ -422,8 +515,8 @@ void AudioEditor::DrawElement() { ImGui::PopItemWidth(); ImGui::NewLine(); ImGui::PopItemWidth(); - UIWidgets::EnhancementSliderFloat("Link's voice pitch multiplier: %f", "##linkVoiceFreqMultiplier", - "gLinkVoiceFreqMultiplier", 0.4, 2.5, "", 1.0, false, false); + UIWidgets::EnhancementSliderFloat("Link's voice pitch multiplier: %.1f %%", "##linkVoiceFreqMultiplier", + "gLinkVoiceFreqMultiplier", 0.4, 2.5, "", 1.0, true, true); ImGui::SameLine(); const std::string resetButton = "Reset##linkVoiceFreqMultiplier"; if (ImGui::Button(resetButton.c_str())) { @@ -431,6 +524,10 @@ void AudioEditor::DrawElement() { LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } + ImGui::NewLine(); + UIWidgets::EnhancementCheckbox("Randomize All Music and Sound Effects on New Scene", "gAudioEditor.RandomizeAllOnNewScene"); + UIWidgets::Tooltip("Enables randomizing all unlocked music and sound effects when you enter a new scene."); + ImGui::NewLine(); ImGui::PushItemWidth(-FLT_MIN); UIWidgets::PaddedSeparator(); @@ -625,3 +722,19 @@ void AudioEditor_ResetAll() { LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); ReplayCurrentBGM(); } + +void AudioEditor_LockAll() { + for (auto type : allTypes) { + LockGroup(AudioCollection::Instance->GetAllSequences(), type); + } + + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); +} + +void AudioEditor_UnlockAll() { + for (auto type : allTypes) { + UnlockGroup(AudioCollection::Instance->GetAllSequences(), type); + } + + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); +} diff --git a/soh/soh/Enhancements/audio/AudioEditor.h b/soh/soh/Enhancements/audio/AudioEditor.h index dc058371d..9cca94efe 100644 --- a/soh/soh/Enhancements/audio/AudioEditor.h +++ b/soh/soh/Enhancements/audio/AudioEditor.h @@ -4,6 +4,9 @@ #ifdef __cplusplus #include +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif #include class AudioEditor : public LUS::GuiWindow { @@ -11,13 +14,15 @@ class AudioEditor : public LUS::GuiWindow { using LUS::GuiWindow::GuiWindow; void DrawElement() override; - void InitElement() override {}; + void InitElement() override; void UpdateElement() override {}; ~AudioEditor() {}; }; void AudioEditor_RandomizeAll(); void AudioEditor_ResetAll(); +void AudioEditor_LockAll(); +void AudioEditor_UnlockAll(); extern "C" { #endif diff --git a/soh/soh/Enhancements/controls/GameControlEditor.cpp b/soh/soh/Enhancements/controls/GameControlEditor.cpp deleted file mode 100644 index a452bf4b1..000000000 --- a/soh/soh/Enhancements/controls/GameControlEditor.cpp +++ /dev/null @@ -1,345 +0,0 @@ -#include "GameControlEditor.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "macros.h" - -#include "../../UIWidgets.hpp" - -namespace GameControlEditor { - const ImGuiTableFlags PANEL_TABLE_FLAGS = - ImGuiTableFlags_BordersH | - ImGuiTableFlags_BordersV; - const ImGuiTableColumnFlags PANEL_TABLE_COLUMN_FLAGS = - ImGuiTableColumnFlags_IndentEnable | - ImGuiTableColumnFlags_NoSort; - - namespace TableHelper { - void InitHeader(bool has_header = true) { - if (has_header) { - ImGui::TableHeadersRow(); - } - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); //This is to adjust Vertical pos of item in a cell to be normlized. - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - } - - void NextCol() { - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - } - - void NextLine() { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - } - } - - void DrawHelpIcon(const std::string& helptext) { - // place the ? button to the most of the right side of the cell it is using. - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 22); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 15); - ImGui::SmallButton("?"); - UIWidgets::Tooltip(helptext.c_str()); - } - - typedef uint32_t N64ButtonMask; - - // Used together for an incomplete linked hash map implementation in order to - // map button masks to their names and original mapping on N64 - static std::list> buttons; - static std::unordered_map buttonNames; - - void addButtonName(N64ButtonMask mask, const char* name) { - buttons.push_back(std::make_pair(mask, name)); - buttonNames[mask] = std::prev(buttons.end()); - } - - typedef struct { - const char* label; - const char* cVarName; - N64ButtonMask defaultBtn; - } CustomButtonMap; - - // Ocarina button maps - static CustomButtonMap ocarinaD5 = {"D5", "gOcarinaD5BtnMap", BTN_CUP}; - static CustomButtonMap ocarinaB4 = {"B4", "gOcarinaB4BtnMap", BTN_CLEFT}; - static CustomButtonMap ocarinaA4 = {"A4", "gOcarinaA4BtnMap", BTN_CRIGHT}; - static CustomButtonMap ocarinaF4 = {"F4", "gOcarinaF4BtnMap", BTN_CDOWN}; - static CustomButtonMap ocarinaD4 = {"D4", "gOcarinaD4BtnMap", BTN_A}; - static CustomButtonMap ocarinaSongDisable = {"Disable songs", "gOcarinaDisableBtnMap", BTN_L}; - static CustomButtonMap ocarinaSharp = {"Pitch up", "gOcarinaSharpBtnMap", BTN_R}; - static CustomButtonMap ocarinaFlat = {"Pitch down", "gOcarinaFlatBtnMap", BTN_Z}; - - void GameControlEditorWindow::InitElement() { - addButtonName(BTN_A, "A"); - addButtonName(BTN_B, "B"); - addButtonName(BTN_CUP, "C Up"); - addButtonName(BTN_CDOWN, "C Down"); - addButtonName(BTN_CLEFT, "C Left"); - addButtonName(BTN_CRIGHT, "C Right"); - addButtonName(BTN_L, "L"); - addButtonName(BTN_Z, "Z"); - addButtonName(BTN_R, "R"); - addButtonName(BTN_START, "Start"); - addButtonName(BTN_DUP, "D-pad up"); - addButtonName(BTN_DDOWN, "D-pad down"); - addButtonName(BTN_DLEFT, "D-pad left"); - addButtonName(BTN_DRIGHT, "D-pad right"); - addButtonName(0, "None"); - } - - // Draw a button mapping setting consisting of a padded label and button dropdown. - // excludedButtons indicates which buttons are unavailable to choose from. - void DrawMapping(CustomButtonMap& mapping, float labelWidth, N64ButtonMask excludedButtons) { - N64ButtonMask currentButton = CVarGetInteger(mapping.cVarName, mapping.defaultBtn); - - const char* preview; - if (buttonNames.contains(currentButton)) { - preview = buttonNames[currentButton]->second; - } else { - preview = "Unknown"; - } - - UIWidgets::Spacer(0); - ImVec2 cursorPos = ImGui::GetCursorPos(); - ImVec2 textSize = ImGui::CalcTextSize(mapping.label); - ImGui::SetCursorPosY(cursorPos.y + textSize.y / 4); - ImGui::SetCursorPosX(cursorPos.x + abs(textSize.x - labelWidth)); - ImGui::Text("%s", mapping.label); - ImGui::SameLine(); - ImGui::SetCursorPosY(cursorPos.y); - - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); - if (ImGui::BeginCombo(StringHelper::Sprintf("##%s", mapping.cVarName).c_str(), preview)) { - for (auto i = buttons.begin(); i != buttons.end(); i++) { - if ((i->first & excludedButtons) != 0) { - continue; - } - if (ImGui::Selectable(i->second, i->first == currentButton)) { - CVarSetInteger(mapping.cVarName, i->first); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - } - } - ImGui::EndCombo(); - } - UIWidgets::Spacer(0); - } - - void DrawOcarinaControlPanel(GameControlEditorWindow* window) { - if (!ImGui::CollapsingHeader("Ocarina Controls")) { - return; - } - - if (!ImGui::BeginTable("tableCustomOcarinaControls", 1, PANEL_TABLE_FLAGS)) { - return; - } - - ImGui::TableSetupColumn("Custom Ocarina Controls", PANEL_TABLE_COLUMN_FLAGS | ImGuiTableColumnFlags_WidthStretch); - TableHelper::InitHeader(false); - - ImVec2 cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); - UIWidgets::EnhancementCheckbox("Customize Ocarina Controls", "gCustomOcarinaControls"); - - if (CVarGetInteger("gCustomOcarinaControls", 0) == 1) { - if (ImGui::BeginTable("tableCustomMainOcarinaControls", 2, ImGuiTableFlags_SizingStretchProp)) { - float labelWidth; - N64ButtonMask disableMask = BTN_B; - if (CVarGetInteger("gDpadOcarina", 0)) { - disableMask |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; - } - - ImGui::TableSetupColumn("Notes##CustomOcarinaNotes", PANEL_TABLE_COLUMN_FLAGS); - ImGui::TableSetupColumn("Modifiers##CustomOcaranaModifiers", PANEL_TABLE_COLUMN_FLAGS); - TableHelper::InitHeader(false); - - window->BeginGroupPanelPublic("Notes", ImGui::GetContentRegionAvail()); - labelWidth = ImGui::CalcTextSize("D5").x + 10; - DrawMapping(ocarinaD5, labelWidth, disableMask); - DrawMapping(ocarinaB4, labelWidth, disableMask); - DrawMapping(ocarinaA4, labelWidth, disableMask); - DrawMapping(ocarinaF4, labelWidth, disableMask); - DrawMapping(ocarinaD4, labelWidth, disableMask); - ImGui::Dummy(ImVec2(0, 5)); - float cursorY = ImGui::GetCursorPosY(); - window->EndGroupPanelPublic(0); - - TableHelper::NextCol(); - - window->BeginGroupPanelPublic("Modifiers", ImGui::GetContentRegionAvail()); - labelWidth = ImGui::CalcTextSize(ocarinaSongDisable.label).x + 10; - DrawMapping(ocarinaSongDisable, labelWidth, disableMask); - DrawMapping(ocarinaSharp, labelWidth, disableMask); - DrawMapping(ocarinaFlat, labelWidth, disableMask); - window->EndGroupPanelPublic(cursorY - ImGui::GetCursorPosY() + 2); - - ImGui::EndTable(); - } - } else { - UIWidgets::Spacer(0); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); - ImGui::TextWrapped("To modify the main ocarina controls, select the \"Customize Ocarina Controls\" checkbox."); - UIWidgets::Spacer(0); - } - - window->BeginGroupPanelPublic("Alternate controls", ImGui::GetContentRegionAvail()); - if (ImGui::BeginTable("tableOcarinaAlternateControls", 2, ImGuiTableFlags_SizingFixedSame)) { - ImGui::TableSetupColumn("D-pad", PANEL_TABLE_COLUMN_FLAGS); - ImGui::TableSetupColumn("Right stick", PANEL_TABLE_COLUMN_FLAGS); - TableHelper::InitHeader(false); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); - UIWidgets::EnhancementCheckbox("Play with D-pad", "gDpadOcarina"); - TableHelper::NextCol(); - UIWidgets::EnhancementCheckbox("Play with camera stick", "gRStickOcarina"); - UIWidgets::Spacer(0); - ImGui::EndTable(); - } - window->EndGroupPanelPublic(0); - - ImGui::EndTable(); - } - - void DrawCameraControlPanel(GameControlEditorWindow* window) { - if (!ImGui::CollapsingHeader("Camera Controls")) { - return; - } - - UIWidgets::Spacer(0); - window->BeginGroupPanelPublic("Aiming/First-Person Camera", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Right Stick Aiming", "gRightStickAiming"); - DrawHelpIcon("Allows for aiming with the right stick in:\n-First-Person/C-Up view\n-Weapon Aiming"); - UIWidgets::PaddedEnhancementCheckbox("Invert Aiming X Axis", "gInvertAimingXAxis"); - DrawHelpIcon("Inverts the Camera X Axis in:\n-First-Person/C-Up view\n-Weapon Aiming"); - UIWidgets::PaddedEnhancementCheckbox("Invert Aiming Y Axis", "gInvertAimingYAxis", true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - DrawHelpIcon("Inverts the Camera Y Axis in:\n-First-Person/C-Up view\n-Weapon Aiming"); - UIWidgets::PaddedEnhancementCheckbox("Invert Shield Aiming Y Axis", "gInvertShieldAimingYAxis", true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - DrawHelpIcon("Inverts the Shield Aiming Y Axis"); - UIWidgets::PaddedEnhancementCheckbox("Invert Shield Aiming X Axis", "gInvertShieldAimingXAxis"); - DrawHelpIcon("Inverts the Shield Aiming X Axis"); - UIWidgets::PaddedEnhancementCheckbox("Disable Auto-Centering in First-Person View", "gDisableAutoCenterViewFirstPerson"); - DrawHelpIcon("Prevents the C-Up view from auto-centering, allowing for Gyro Aiming"); - if (UIWidgets::PaddedEnhancementCheckbox("Enable Custom Aiming/First-Person sensitivity", "gEnableFirstPersonSensitivity", true, false)) { - if (!CVarGetInteger("gEnableFirstPersonSensitivity", 0)) { - CVarClear("gFirstPersonCameraSensitivity"); - } - } - if (CVarGetInteger("gEnableFirstPersonSensitivity", 0)) { - UIWidgets::EnhancementSliderFloat("Aiming/First-Person Horizontal Sensitivity: %d %%", "##FirstPersonSensitivity Horizontal", - "gFirstPersonCameraSensitivityX", 0.01f, 5.0f, "", 1.0f, true); - UIWidgets::EnhancementSliderFloat("Aiming/First-Person Vertical Sensitivity: %d %%", "##FirstPersonSensitivity Vertical", - "gFirstPersonCameraSensitivityY", 0.01f, 5.0f, "", 1.0f, true); - } - UIWidgets::Spacer(0); - window->EndGroupPanelPublic(0); - - UIWidgets::Spacer(0); - window->BeginGroupPanelPublic("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 " - "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::Spacer(0); - UIWidgets::PaddedEnhancementSliderFloat("Third-Person 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", - "gThirdPersonCameraSensitivityY", 0.01f, 5.0f, "", 1.0f, true, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Camera Distance: %d", "##CamDist", - "gFreeCameraDistMax", 100, 900, "", 185, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Camera Transition Speed: %d", "##CamTranSpeed", - "gFreeCameraTransitionSpeed", 0, 900, "", 25, true, false, true); - window->EndGroupPanelPublic(0); - } - - void DrawDpadControlPanel(GameControlEditorWindow* window) { - if (!ImGui::CollapsingHeader("D-Pad Controls")) { - return; - } - - ImVec2 cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); - window->BeginGroupPanelPublic("D-Pad Options", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("D-pad Support on Pause Screen", "gDpadPause"); - DrawHelpIcon("Navigate Pause with the D-pad\nIf used with D-pad as Equip Items, you must hold C-Up to equip instead of navigate\n" - "To make the cursor only move a single space no matter how long a direction is held, manually set gDpadHoldChange to 0"); - UIWidgets::PaddedEnhancementCheckbox("D-pad Support in Text Boxes", "gDpadText"); - DrawHelpIcon("Navigate choices in text boxes, shop item selection, and the file select / name entry screens with the D-pad\n" - "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"); - window->EndGroupPanelPublic(0); - } - - void DrawMiscControlPanel(GameControlEditorWindow* window) { - if (!ImGui::CollapsingHeader("Miscellaneous Controls")) { - return; - } - - ImVec2 cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); - window->BeginGroupPanelPublic("Misc Controls", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedText("Allow the cursor to be on any slot"); - static const char* cursorOnAnySlot[3] = { "Only in Rando", "Always", "Never" }; - UIWidgets::EnhancementCombobox("gPauseAnyCursor", cursorOnAnySlot, PAUSE_ANY_CURSOR_RANDO_ONLY); - DrawHelpIcon("Allows the cursor on the pause menu to be over any slot. Sometimes required in rando to select " - "certain items."); - UIWidgets::Spacer(0); - ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0)); - UIWidgets::PaddedEnhancementCheckbox("Enable walk speed modifiers", "gEnableWalkModify", true, false); - DrawHelpIcon("Hold the assigned button to change the maximum walking speed\nTo change the assigned button, go into the Ports tabs above"); - if (CVarGetInteger("gEnableWalkModify", 0)) { - UIWidgets::Spacer(5); - window->BeginGroupPanelPublic("Walk Modifier", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Toggle modifier instead of holding", "gWalkSpeedToggle", true, false); - UIWidgets::PaddedEnhancementCheckbox("Don't affect jump distance/velocity", "gWalkModifierDoesntChangeJump", true, false); - UIWidgets::PaddedEnhancementSliderFloat("Modifier 1: %d %%", "##WalkMod1", "gWalkModifierOne", 0.0f, 5.0f, "", 1.0f, true, true, false, true); - UIWidgets::PaddedEnhancementSliderFloat("Modifier 2: %d %%", "##WalkMod2", "gWalkModifierTwo", 0.0f, 5.0f, "", 1.0f, true, true, false, true); - window->EndGroupPanelPublic(0); - } - ImGui::EndDisabled(); - UIWidgets::Spacer(0); - UIWidgets::PaddedEnhancementCheckbox("Answer Navi Prompt with L Button", "gNaviOnL"); - DrawHelpIcon("Speak to Navi with L but enter first-person camera with C-Up"); - window->EndGroupPanelPublic(0); - } - - - void GameControlEditorWindow::DrawElement() { - ImGui::SetNextWindowSize(ImVec2(465, 430), ImGuiCond_FirstUseEver); - if (ImGui::Begin("Game Controls Configuration", &mIsVisible)) { - DrawOcarinaControlPanel(this); - DrawCameraControlPanel(this); - DrawDpadControlPanel(this); - DrawMiscControlPanel(this); - } - ImGui::End(); - } - - void GameControlEditorWindow::BeginGroupPanelPublic(const char* name, const ImVec2& size) { - BeginGroupPanel(name, size); - } - - void GameControlEditorWindow::EndGroupPanelPublic(float minHeight) { - EndGroupPanel(minHeight); - } -} diff --git a/soh/soh/Enhancements/controls/GameControlEditor.h b/soh/soh/Enhancements/controls/GameControlEditor.h deleted file mode 100644 index 7cf306741..000000000 --- a/soh/soh/Enhancements/controls/GameControlEditor.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -namespace GameControlEditor { -class GameControlEditorWindow : public LUS::GuiWindow { - public: - using LUS::GuiWindow::GuiWindow; - - void BeginGroupPanelPublic(const char* name, const ImVec2& size); - void EndGroupPanelPublic(float minHeight); - - void InitElement() override; - void DrawElement() override; - void UpdateElement() override {}; -}; - -static int CurrentPort = 0; -static int BtnReading = -1; - -} // namespace GameControlEditor diff --git a/soh/soh/Enhancements/controls/InputViewer.cpp b/soh/soh/Enhancements/controls/InputViewer.cpp new file mode 100644 index 000000000..6b25589c6 --- /dev/null +++ b/soh/soh/Enhancements/controls/InputViewer.cpp @@ -0,0 +1,519 @@ +#include "InputViewer.h" + +#include "public/bridge/consolevariablebridge.h" +#include "libultraship/libultra/controller.h" +#include "Context.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include +#include +#include + +#include "../../UIWidgets.hpp" + +// Text colors +static ImVec4 textColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); +static ImVec4 range1Color = ImVec4(1.0f, 0.7f, 0, 1.0f); +static ImVec4 range2Color = ImVec4(0, 1.0f, 0, 1.0f); + +static const char* buttonOutlineOptions[4] = { "Always Shown", "Shown Only While Not Pressed", + "Shown Only While Pressed", "Always Hidden" }; + +static const char* stickModeOptions[3] = { "Always", "While In Use", "Never" }; + +static Color_RGBA8 vec2Color(ImVec4 vec) { + Color_RGBA8 color; + color.r = vec.x * 255.0; + color.g = vec.y * 255.0; + color.b = vec.z * 255.0; + color.a = vec.w * 255.0; + return color; +} + +static ImVec4 color2Vec(Color_RGBA8 color) { + return ImVec4(color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0); +} + +InputViewer::~InputViewer() { + SPDLOG_TRACE("destruct input viewer"); +} + +void InputViewer::RenderButton(std::string btnTexture, std::string btnOutlineTexture, int state, ImVec2 size, + int outlineMode) { + const ImVec2 pos = ImGui::GetCursorPos(); + ImGui::SetNextItemAllowOverlap(); + // Render Outline based on settings + if (outlineMode == BUTTON_OUTLINE_ALWAYS_SHOWN || (outlineMode == BUTTON_OUTLINE_NOT_PRESSED && !state) || + (outlineMode == BUTTON_OUTLINE_PRESSED && state)) { + ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(btnOutlineTexture), size, + ImVec2(0, 0), ImVec2(1.0f, 1.0f), ImVec4(255, 255, 255, 255)); + } + // Render button if pressed + if (state) { + ImGui::SetCursorPos(pos); + ImGui::SetNextItemAllowOverlap(); + ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(btnTexture), size, + ImVec2(0, 0), ImVec2(1.0f, 1.0f), ImVec4(255, 255, 255, 255)); + } +} + +void InputViewer::DrawElement() { + if (CVarGetInteger("gOpenWindows.InputViewer", 0)) { + static bool sButtonTexturesLoaded = false; + if (!sButtonTexturesLoaded) { + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage( + "Input-Viewer-Background", "textures/buttons/InputViewerBackground.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("A-Btn", "textures/buttons/ABtn.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("B-Btn", "textures/buttons/BBtn.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("L-Btn", "textures/buttons/LBtn.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("R-Btn", "textures/buttons/RBtn.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Z-Btn", "textures/buttons/ZBtn.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Start-Btn", + "textures/buttons/StartBtn.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("C-Left", "textures/buttons/CLeft.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("C-Right", "textures/buttons/CRight.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("C-Up", "textures/buttons/CUp.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("C-Down", "textures/buttons/CDown.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Analog-Stick", + "textures/buttons/AnalogStick.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Dpad-Left", + "textures/buttons/DPadLeft.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Dpad-Right", + "textures/buttons/DPadRight.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Dpad-Up", "textures/buttons/DPadUp.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Dpad-Down", + "textures/buttons/DPadDown.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Right-Stick", + "textures/buttons/RightStick.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("A-Btn Outline", + "textures/buttons/ABtnOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("B-Btn Outline", + "textures/buttons/BBtnOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("L-Btn Outline", + "textures/buttons/LBtnOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("R-Btn Outline", + "textures/buttons/RBtnOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Z-Btn Outline", + "textures/buttons/ZBtnOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Start-Btn Outline", + "textures/buttons/StartBtnOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("C-Left Outline", + "textures/buttons/CLeftOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("C-Right Outline", + "textures/buttons/CRightOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("C-Up Outline", + "textures/buttons/CUpOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("C-Down Outline", + "textures/buttons/CDownOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Analog-Stick Outline", + "textures/buttons/AnalogStickOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Dpad-Left Outline", + "textures/buttons/DPadLeftOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Dpad-Right Outline", + "textures/buttons/DPadRightOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Dpad-Up Outline", + "textures/buttons/DPadUpOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Dpad-Down Outline", + "textures/buttons/DPadDownOutline.png"); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Right-Stick Outline", + "textures/buttons/RightStickOutline.png"); + sButtonTexturesLoaded = true; + } + + ImVec2 mainPos = ImGui::GetWindowPos(); + ImVec2 size = ImGui::GetContentRegionAvail(); + +#ifdef __WIIU__ + const float scale = CVarGetFloat("gInputViewer.Scale", 1.0f) * 2.0f; +#else + const float scale = CVarGetFloat("gInputViewer.Scale", 1.0f); +#endif + const int showAnalogAngles = CVarGetInteger("gInputViewer.AnalogAngles.Enabled", 0); + const int buttonOutlineMode = CVarGetInteger("gInputViewer.ButtonOutlineMode", BUTTON_OUTLINE_NOT_PRESSED); + + ImVec2 bgSize = LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureSize("Input-Viewer-Background"); + ImVec2 scaledBGSize = ImVec2(bgSize.x * scale, bgSize.y * scale); + + ImGui::SetNextWindowSize(ImVec2( + scaledBGSize.x + 20, + scaledBGSize.y + + (showAnalogAngles ? ImGui::CalcTextSize("X").y : 0) * scale * CVarGetFloat("gInputViewer.AnalogAngles.Scale", 1.0f) + 20)); + ImGui::SetNextWindowContentSize( + ImVec2(scaledBGSize.x, scaledBGSize.y + (showAnalogAngles ? 15 : 0) * scale * + CVarGetFloat("gInputViewer.AnalogAngles.Scale", 1.0f))); + ImGui::SetNextWindowPos( + ImVec2(mainPos.x + size.x - scaledBGSize.x - 30, mainPos.y + size.y - scaledBGSize.y - 30), + ImGuiCond_FirstUseEver); + + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + + OSContPad* pads = LUS::Context::GetInstance()->GetControlDeck()->GetPads(); + + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground | + ImGuiWindowFlags_NoFocusOnAppearing; + + if (!CVarGetInteger("gInputViewer.EnableDragging", 1)) { + windowFlags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove; + } + + if (pads != nullptr && ImGui::Begin("Input Viewer", nullptr, windowFlags)) { + ImGui::SetCursorPos(ImVec2(10, 10)); + const ImVec2 aPos = ImGui::GetCursorPos(); + + if (CVarGetInteger("gInputViewer.ShowBackground", 1)) { + ImGui::SetNextItemAllowOverlap(); + // Background + ImGui::Image( + LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Input-Viewer-Background"), + scaledBGSize, ImVec2(0, 0), ImVec2(1.0f, 1.0f), ImVec4(255, 255, 255, 255)); + } + + // A/B + if (CVarGetInteger("gInputViewer.BBtn", 1)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("B-Btn", "B-Btn Outline", pads[0].button & BTN_B, scaledBGSize, buttonOutlineMode); + } + if (CVarGetInteger("gInputViewer.ABtn", 1)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("A-Btn", "A-Btn Outline", pads[0].button & BTN_A, scaledBGSize, buttonOutlineMode); + } + + // C buttons + if (CVarGetInteger("gInputViewer.CUp", 1)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("C-Up", "C-Up Outline", pads[0].button & BTN_CUP, scaledBGSize, buttonOutlineMode); + } + if (CVarGetInteger("gInputViewer.CLeft", 1)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("C-Left", "C-Left Outline", pads[0].button & BTN_CLEFT, scaledBGSize, buttonOutlineMode); + } + if (CVarGetInteger("gInputViewer.CRight", 1)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("C-Right", "C-Right Outline", pads[0].button & BTN_CRIGHT, scaledBGSize, + buttonOutlineMode); + } + if (CVarGetInteger("gInputViewer.CDown", 1)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("C-Down", "C-Down Outline", pads[0].button & BTN_CDOWN, scaledBGSize, buttonOutlineMode); + } + + // L/R/Z + if (CVarGetInteger("gInputViewer.LBtn", 1)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("L-Btn", "L-Btn Outline", pads[0].button & BTN_L, scaledBGSize, buttonOutlineMode); + } + if (CVarGetInteger("gInputViewer.RBtn", 1)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("R-Btn", "R-Btn Outline", pads[0].button & BTN_R, scaledBGSize, buttonOutlineMode); + } + if (CVarGetInteger("gInputViewer.ZBtn", 1)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("Z-Btn", "Z-Btn Outline", pads[0].button & BTN_Z, scaledBGSize, buttonOutlineMode); + } + + // Start + if (CVarGetInteger("gInputViewer.StartBtn", 1)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("Start-Btn", "Start-Btn Outline", pads[0].button & BTN_START, scaledBGSize, + buttonOutlineMode); + } + + // Dpad + if (CVarGetInteger("gInputViewer.Dpad", 0)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("Dpad-Left", "Dpad-Left Outline", pads[0].button & BTN_DLEFT, scaledBGSize, + buttonOutlineMode); + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("Dpad-Right", "Dpad-Right Outline", pads[0].button & BTN_DRIGHT, scaledBGSize, + buttonOutlineMode); + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("Dpad-Up", "Dpad-Up Outline", pads[0].button & BTN_DUP, scaledBGSize, buttonOutlineMode); + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + RenderButton("Dpad-Down", "Dpad-Down Outline", pads[0].button & BTN_DDOWN, scaledBGSize, + buttonOutlineMode); + } + + const bool analogStickIsInDeadzone = !pads[0].stick_x && !pads[0].stick_y; + const bool rightStickIsInDeadzone = !pads[0].right_stick_x && !pads[0].right_stick_y; + + // Analog Stick + const int analogOutlineMode = + CVarGetInteger("gInputViewer.AnalogStick.OutlineMode", STICK_MODE_ALWAYS_SHOWN); + const float maxStickDistance = CVarGetInteger("gInputViewer.AnalogStick.Movement", 12); + if (analogOutlineMode == STICK_MODE_ALWAYS_SHOWN || + (analogOutlineMode == STICK_MODE_HIDDEN_IN_DEADZONE && !analogStickIsInDeadzone)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + ImGui::Image( + LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Analog-Stick Outline"), + scaledBGSize, ImVec2(0, 0), ImVec2(1.0f, 1.0f), ImVec4(255, 255, 255, 255)); + } + const int analogStickMode = + CVarGetInteger("gInputViewer.AnalogStick.VisibilityMode", STICK_MODE_ALWAYS_SHOWN); + if (analogStickMode == STICK_MODE_ALWAYS_SHOWN || + (analogStickMode == STICK_MODE_HIDDEN_IN_DEADZONE && !analogStickIsInDeadzone)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos( + ImVec2(aPos.x + maxStickDistance * ((float)(pads[0].stick_x) / MAX_AXIS_RANGE) * scale, + aPos.y - maxStickDistance * ((float)(pads[0].stick_y) / MAX_AXIS_RANGE) * scale)); + ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Analog-Stick"), + scaledBGSize, ImVec2(0, 0), ImVec2(1.0f, 1.0f), ImVec4(255, 255, 255, 255)); + } + + // Right Stick + const float maxRightStickDistance = CVarGetInteger("gInputViewer.RightStick.Movement", 7); + const int rightOutlineMode = + CVarGetInteger("gInputViewer.RightStick.OutlineMode", STICK_MODE_ALWAYS_HIDDEN); + if (rightOutlineMode == STICK_MODE_ALWAYS_SHOWN || + (rightOutlineMode == STICK_MODE_HIDDEN_IN_DEADZONE && !rightStickIsInDeadzone)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos(aPos); + ImGui::Image( + LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Right-Stick Outline"), + scaledBGSize, ImVec2(0, 0), ImVec2(1.0f, 1.0f), ImVec4(255, 255, 255, 255)); + } + const int rightStickMode = + CVarGetInteger("gInputViewer.RightStick.VisibilityMode", STICK_MODE_ALWAYS_HIDDEN); + if (rightStickMode == STICK_MODE_ALWAYS_SHOWN || + (rightStickMode == STICK_MODE_HIDDEN_IN_DEADZONE && !rightStickIsInDeadzone)) { + ImGui::SetNextItemAllowOverlap(); + ImGui::SetCursorPos( + ImVec2(aPos.x + maxRightStickDistance * ((float)(pads[0].right_stick_x) / MAX_AXIS_RANGE) * scale, + aPos.y - maxRightStickDistance * ((float)(pads[0].right_stick_y) / MAX_AXIS_RANGE) * scale)); + ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Right-Stick"), + scaledBGSize, ImVec2(0, 0), ImVec2(1.0f, 1.0f), ImVec4(255, 255, 255, 255)); + } + + // Analog stick angle text + if (showAnalogAngles) { + ImGui::SetCursorPos(ImVec2(aPos.x + 10 + CVarGetInteger("gInputViewer.AnalogAngles.Offset", 0) * scale, + scaledBGSize.y + aPos.y + 10)); + // Scale font with input viewer scale + float oldFontScale = ImGui::GetFont()->Scale; + ImGui::GetFont()->Scale *= scale * CVarGetFloat("gInputViewer.AnalogAngles.Scale", 1.0f); + ImGui::PushFont(ImGui::GetFont()); + + // Calculate polar R coordinate from X and Y angles, squared to avoid sqrt + const float rSquared = pads[0].stick_x * pads[0].stick_x + pads[0].stick_y * pads[0].stick_y; + + // ESS range + const int range1Min = CVarGetInteger("gInputViewer.AnalogAngles.Range1.Min", 8); + const int range1Max = CVarGetInteger("gInputViewer.AnalogAngles.Range1.Max", 27); + // Walking speed range + const int range2Min = CVarGetInteger("gInputViewer.AnalogAngles.Range2.Min", 27); + const int range2Max = CVarGetInteger("gInputViewer.AnalogAngles.Range2.Max", 62); + + // Push color based on angle ranges + if (CVarGetInteger("gInputViewer.AnalogAngles.Range1.Enabled", 0) && + (rSquared >= (range1Min * range1Min)) && (rSquared < (range1Max * range1Max))) { + ImGui::PushStyleColor( + ImGuiCol_Text, + color2Vec(CVarGetColor("gInputViewer.AnalogAngles.Range1.Color", vec2Color(range1Color)))); + } else if (CVarGetInteger("gInputViewer.AnalogAngles.Range2.Enabled", 0) && + (rSquared >= (range2Min * range2Min)) && (rSquared < (range2Max * range2Max))) { + ImGui::PushStyleColor( + ImGuiCol_Text, + color2Vec(CVarGetColor("gInputViewer.AnalogAngles.Range2.Color", vec2Color(range2Color)))); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, color2Vec(CVarGetColor("gInputViewer.AnalogAngles.TextColor", + vec2Color(textColor)))); + } + + // Render text + ImGui::Text("X: %-3d Y: %-3d", pads[0].stick_x, pads[0].stick_y); + // Restore original color + ImGui::PopStyleColor(); + // Restore original font scale + ImGui::GetFont()->Scale = oldFontScale; + ImGui::PopFont(); + } + + ImGui::End(); + } + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + } +} + +InputViewerSettingsWindow::~InputViewerSettingsWindow() { + SPDLOG_TRACE("destruct input viewer settings window"); +} + +void InputViewerSettingsWindow::DrawElement() { + ImGui::SetNextWindowSize(ImVec2(450, 525), ImGuiCond_FirstUseEver); + + if (ImGui::Begin("Input Viewer Settings", &mIsVisible)) { + + // gInputViewer.Scale + UIWidgets::EnhancementSliderFloat("Input Viewer Scale: %.2f", "##Input", "gInputViewer.Scale", 0.1f, 5.0f, "", + 1.0f, false, true); + UIWidgets::Tooltip("Sets the on screen size of the input viewer"); + + // gInputViewer.EnableDragging + UIWidgets::EnhancementCheckbox("Enable Dragging", "gInputViewer.EnableDragging", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + + UIWidgets::PaddedSeparator(true, true); + + // gInputViewer.ShowBackground + UIWidgets::EnhancementCheckbox("Show Background Layer", "gInputViewer.ShowBackground", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + + UIWidgets::PaddedSeparator(true, true); + + if (ImGui::CollapsingHeader("Buttons")) { + // gInputViewer.ABtn + UIWidgets::EnhancementCheckbox("Show A-Button Layers", "gInputViewer.ABtn", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + // gInputViewer.BBtn + UIWidgets::EnhancementCheckbox("Show B-Button Layers", "gInputViewer.BBtn", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + // gInputViewer.CUp + UIWidgets::EnhancementCheckbox("Show C-Up Layers", "gInputViewer.CUp", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + // gInputViewer.CRight + UIWidgets::EnhancementCheckbox("Show C-Right Layers", "gInputViewer.CRight", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + // gInputViewer.CDown + UIWidgets::EnhancementCheckbox("Show C-Down Layers", "gInputViewer.CDown", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + // gInputViewer.CLeft + UIWidgets::EnhancementCheckbox("Show C-Left Layers", "gInputViewer.CLeft", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + // gInputViewer.LBtn + UIWidgets::EnhancementCheckbox("Show L-Button Layers", "gInputViewer.LBtn", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + // gInputViewer.RBtn + UIWidgets::EnhancementCheckbox("Show R-Button Layers", "gInputViewer.RBtn", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + // gInputViewer.ZBtn + UIWidgets::EnhancementCheckbox("Show Z-Button Layers", "gInputViewer.ZBtn", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + // gInputViewer.StartBtn + UIWidgets::EnhancementCheckbox("Show Start Button Layers", "gInputViewer.StartBtn", false, "", + UIWidgets::CheckboxGraphics::Checkmark, true); + // gInputViewer.Dpad + UIWidgets::EnhancementCheckbox("Show D-Pad Layers", "gInputViewer.Dpad", false, "", + UIWidgets::CheckboxGraphics::Checkmark, false); + + // gInputViewer.ButtonOutlineMode + UIWidgets::PaddedText("Button Outlines/Backgrounds", true, false); + UIWidgets::EnhancementCombobox("gInputViewer.ButtonOutlineMode", buttonOutlineOptions, + BUTTON_OUTLINE_NOT_PRESSED); + UIWidgets::Tooltip( + "Sets the desired visibility behavior for the button outline/background layers. Useful for " + "custom input viewers."); + + UIWidgets::PaddedSeparator(true, true); + } + + if (ImGui::CollapsingHeader("Analog Stick")) { + // gInputViewer.AnalogStick.VisibilityMode + UIWidgets::PaddedText("Analog Stick Visibility", true, false); + UIWidgets::EnhancementCombobox("gInputViewer.AnalogStick.VisibilityMode", stickModeOptions, + STICK_MODE_ALWAYS_SHOWN); + UIWidgets::Tooltip( + "Determines the conditions under which the moving layer of the analog stick texture is visible."); + + // gInputViewer.AnalogStick.OutlineMode + UIWidgets::PaddedText("Analog Stick Outline/Background Visibility", true, false); + UIWidgets::EnhancementCombobox("gInputViewer.AnalogStick.OutlineMode", stickModeOptions, + STICK_MODE_ALWAYS_SHOWN); + UIWidgets::Tooltip( + "Determines the conditions under which the analog stick outline/background texture is visible."); + + // gInputViewer.AnalogStick.Movement + UIWidgets::EnhancementSliderInt("Analog Stick Movement: %dpx", "##AnalogMovement", + "gInputViewer.AnalogStick.Movement", 0, 200, "", 12, true); + UIWidgets::Tooltip( + "Sets the distance to move the analog stick in the input viewer. Useful for custom input viewers."); + UIWidgets::PaddedSeparator(true, true); + } + + if (ImGui::CollapsingHeader("Additional (\"Right\") Stick")) { + // gInputViewer.RightStick.VisibilityMode + UIWidgets::PaddedText("Right Stick Visibility", true, false); + UIWidgets::EnhancementCombobox("gInputViewer.RightStick.VisibilityMode", stickModeOptions, + STICK_MODE_HIDDEN_IN_DEADZONE); + UIWidgets::Tooltip( + "Determines the conditions under which the moving layer of the right stick texture is visible."); + + // gInputViewer.RightStick.OutlineMode + UIWidgets::PaddedText("Right Stick Outline/Background Visibility", true, false); + UIWidgets::EnhancementCombobox("gInputViewer.RightStick.OutlineMode", stickModeOptions, + STICK_MODE_HIDDEN_IN_DEADZONE); + UIWidgets::Tooltip( + "Determines the conditions under which the right stick outline/background texture is visible."); + + // gInputViewer.RightStick.Movement + UIWidgets::EnhancementSliderInt("Right Stick Movement: %dpx", "##RightMovement", + "gInputViewer.RightStick.Movement", 0, 200, "", 7, true); + UIWidgets::Tooltip( + "Sets the distance to move the right stick in the input viewer. Useful for custom input viewers."); + UIWidgets::PaddedSeparator(true, true); + } + + if (ImGui::CollapsingHeader("Analog Angle Values")) { + // gAnalogAngles + UIWidgets::EnhancementCheckbox("Show Analog Stick Angle Values", "gInputViewer.AnalogAngles.Enabled"); + UIWidgets::Tooltip("Displays analog stick angle values in the input viewer"); + if (CVarGetInteger("gInputViewer.AnalogAngles.Enabled", 0)) { + // gInputViewer.AnalogAngles.TextColor + if (ImGui::ColorEdit4("Text Color", (float*)&textColor)) { + CVarSetColor("gInputViewer.AnalogAngles.TextColor", vec2Color(textColor)); + } + // gAnalogAngleScale + UIWidgets::EnhancementSliderFloat("Angle Text Scale: %.2f%%", "##AnalogAngleScale", + "gInputViewer.AnalogAngles.Scale", 0.1f, 5.0f, "", 1.0f, true, true); + // gInputViewer.AnalogAngles.Offset + UIWidgets::EnhancementSliderInt("Angle Text Offset: %dpx", "##AnalogAngleOffset", + "gInputViewer.AnalogAngles.Offset", 0, 400, "", 0, true); + UIWidgets::PaddedSeparator(true, true); + // gInputViewer.AnalogAngles.Range1.Enabled + UIWidgets::EnhancementCheckbox("Highlight ESS Position", "gInputViewer.AnalogAngles.Range1.Enabled"); + UIWidgets::Tooltip( + "Highlights the angle value text when the analog stick is in ESS position (on flat ground)"); + if (CVarGetInteger("gInputViewer.AnalogAngles.Range1.Enabled", 0)) { + // gInputViewer.AnalogAngles.Range1.Color + if (ImGui::ColorEdit4("ESS Color", (float*)&range1Color)) { + CVarSetColor("gInputViewer.AnalogAngles.Range1.Color", vec2Color(range1Color)); + } + } + + UIWidgets::PaddedSeparator(true, true); + // gInputViewer.AnalogAngles.Range2.Enabled + UIWidgets::EnhancementCheckbox("Highlight Walking Speed Angles", + "gInputViewer.AnalogAngles.Range2.Enabled"); + UIWidgets::Tooltip("Highlights the angle value text when the analog stick is at an angle that would " + "produce a walking speed (on flat ground)\n\n" + "Useful for 1.0 Empty Jumpslash Quick Put Away"); + if (CVarGetInteger("gInputViewer.AnalogAngles.Range2.Enabled", 0)) { + // gInputViewer.AnalogAngles.Range2.Color + if (ImGui::ColorEdit4("Walking Speed Color", (float*)&range2Color)) { + CVarSetColor("gInputViewer.AnalogAngles.Range2.Color", vec2Color(range2Color)); + } + } + } + } + + ImGui::End(); + } +} \ No newline at end of file diff --git a/soh/soh/Enhancements/controls/InputViewer.h b/soh/soh/Enhancements/controls/InputViewer.h new file mode 100644 index 000000000..6f2e2c587 --- /dev/null +++ b/soh/soh/Enhancements/controls/InputViewer.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +typedef enum { + BUTTON_OUTLINE_ALWAYS_SHOWN, + BUTTON_OUTLINE_NOT_PRESSED, + BUTTON_OUTLINE_PRESSED, + BUTTON_OUTLINE_ALWAYS_HIDDEN +} ButtonOutlineMode; + +typedef enum { + STICK_MODE_ALWAYS_SHOWN, + STICK_MODE_HIDDEN_IN_DEADZONE, + STICK_MODE_ALWAYS_HIDDEN, +} StickMode; + +class InputViewer : public LUS::GuiWindow { +public: + using LUS::GuiWindow::GuiWindow; + + void InitElement() override {}; + void DrawElement() override; + void UpdateElement() override {}; + + InputViewer(); + ~InputViewer(); + + void Draw(); + + private: + void RenderButton(std::string btn, std::string btnOutline, int state, ImVec2 size, int outlineMode); +}; + +class InputViewerSettingsWindow : public LUS::GuiWindow { +public: + using LUS::GuiWindow::GuiWindow; + + void InitElement() override {}; + void DrawElement() override; + void UpdateElement() override {}; + + InputViewerSettingsWindow(); + ~InputViewerSettingsWindow(); + + void Draw(); +}; diff --git a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp index ae8e8cc27..a1290c238 100644 --- a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp +++ b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp @@ -22,6 +22,22 @@ void SohInputEditorWindow::InitElement() { mButtonsBitmasks = { BTN_A, BTN_B, BTN_START, BTN_L, BTN_R, BTN_Z, BTN_CUP, BTN_CDOWN, BTN_CLEFT, BTN_CRIGHT }; mDpadBitmasks = { BTN_DUP, BTN_DDOWN, BTN_DLEFT, BTN_DRIGHT }; mModifierButtonsBitmasks = { BTN_MODIFIER1, BTN_MODIFIER2 }; + + addButtonName(BTN_A, "A"); + addButtonName(BTN_B, "B"); + addButtonName(BTN_CUP, "C Up"); + addButtonName(BTN_CDOWN, "C Down"); + addButtonName(BTN_CLEFT, "C Left"); + addButtonName(BTN_CRIGHT, "C Right"); + addButtonName(BTN_L, "L"); + addButtonName(BTN_Z, "Z"); + addButtonName(BTN_R, "R"); + addButtonName(BTN_START, "Start"); + addButtonName(BTN_DUP, "D-pad up"); + addButtonName(BTN_DDOWN, "D-pad down"); + addButtonName(BTN_DLEFT, "D-pad left"); + addButtonName(BTN_DRIGHT, "D-pad right"); + addButtonName(0, "None"); } #define INPUT_EDITOR_WINDOW_GAME_INPUT_BLOCK_ID 95237929 @@ -636,6 +652,45 @@ void SohInputEditorWindow::DrawStickSection(uint8_t port, uint8_t stick, int32_t ImGui::EndGroup(); ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode(StringHelper::Sprintf("Analog Stick Options##%d", id).c_str())) { + ImGui::Text("Sensitivity:"); + + int32_t sensitivityPercentage = controllerStick->GetSensitivityPercentage(); + if (sensitivityPercentage == 0) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("-##Sensitivity%d", id).c_str())) { + controllerStick->SetSensitivity(sensitivityPercentage - 1); + } + ImGui::PopButtonRepeat(); + if (sensitivityPercentage == 0) { + ImGui::EndDisabled(); + } + ImGui::SameLine(0.0f, 0.0f); + ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(160.0f)); + if (ImGui::SliderInt(StringHelper::Sprintf("##Sensitivity%d", id).c_str(), &sensitivityPercentage, 0, 200, "%d%%", + ImGuiSliderFlags_AlwaysClamp)) { + controllerStick->SetSensitivity(sensitivityPercentage); + } + ImGui::SameLine(0.0f, 0.0f); + if (sensitivityPercentage == 200) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("+##Sensitivity%d", id).c_str())) { + controllerStick->SetSensitivity(sensitivityPercentage + 1); + } + ImGui::PopButtonRepeat(); + if (sensitivityPercentage == 200) { + ImGui::EndDisabled(); + } + if (!controllerStick->SensitivityIsDefault()) { + ImGui::SameLine(); + if (ImGui::Button(StringHelper::Sprintf("Reset to Default###resetStickSensitivity%d", id).c_str())) { + controllerStick->ResetSensitivityToDefault(); + } + } + ImGui::Text("Deadzone:"); int32_t deadzonePercentage = controllerStick->GetDeadzonePercentage(); @@ -978,14 +1033,6 @@ void SohInputEditorWindow::DrawAddLEDMappingButton(uint8_t port) { } } -void SohInputEditorWindow::DrawHelpIcon(const std::string& helptext) { - // place the ? button to the most of the right side of the cell it is using. - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - SCALE_IMGUI_SIZE(22)); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - SCALE_IMGUI_SIZE(15)); - ImGui::SmallButton("?"); - UIWidgets::Tooltip(helptext.c_str()); -} - void SohInputEditorWindow::DrawLEDSection(uint8_t port) { for (auto [id, mapping] : LUS::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetLED()->GetAllLEDMappings()) { @@ -1024,11 +1071,11 @@ void SohInputEditorWindow::DrawLEDSection(uint8_t port) { }; UIWidgets::PaddedText("Source"); UIWidgets::EnhancementCombobox("gLedColorSource", ledSources, LED_SOURCE_TUNIC_ORIGINAL); - DrawHelpIcon("Health\n- Red when health critical (13-20% depending on max health)\n- Yellow when " - "health < 40%. Green otherwise.\n\n" - "Tunics: colors will mirror currently equipped tunic, whether original or the current " - "values in Cosmetics Editor.\n\n" - "Custom: single, solid color"); + UIWidgets::Tooltip("Health\n- Red when health critical (13-20% depending on max health)\n- Yellow when " + "health < 40%. Green otherwise.\n\n" + "Tunics: colors will mirror currently equipped tunic, whether original or the current " + "values in Cosmetics Editor.\n\n" + "Custom: single, solid color"); if (CVarGetInteger("gLedColorSource", 1) == LED_SOURCE_CUSTOM) { UIWidgets::Spacer(3); auto port1Color = CVarGetColor24("gLedPort1Color", { 255, 255, 255 }); @@ -1046,14 +1093,14 @@ void SohInputEditorWindow::DrawLEDSection(uint8_t port) { ImGui::SameLine(); ImGui::Text("Custom Color"); } - UIWidgets::PaddedEnhancementSliderFloat("Brightness: %d%%", "##LED_Brightness", "gLedBrightness", 0.0f, + UIWidgets::PaddedEnhancementSliderFloat("Brightness: %.1f %%", "##LED_Brightness", "gLedBrightness", 0.0f, 1.0f, "", 1.0f, true, true); - DrawHelpIcon("Sets the brightness of controller LEDs. 0% brightness = LEDs off."); + UIWidgets::Tooltip("Sets the brightness of controller LEDs. 0% brightness = LEDs off."); UIWidgets::PaddedEnhancementCheckbox( "Critical Health Override", "gLedCriticalOverride", true, true, CVarGetInteger("gLedColorSource", LED_SOURCE_TUNIC_ORIGINAL) == LED_SOURCE_HEALTH, "Override redundant for health source.", UIWidgets::CheckboxGraphics::Cross, true); - DrawHelpIcon("Shows red color when health is critical, otherwise displays according to color source."); + UIWidgets::Tooltip("Shows red color when health is critical, otherwise displays according to color source."); } ImGui::TreePop(); } @@ -1391,6 +1438,273 @@ void SohInputEditorWindow::DrawLEDDeviceIcons(uint8_t portIndex) { } } +const ImGuiTableFlags PANEL_TABLE_FLAGS = + ImGuiTableFlags_BordersH | + ImGuiTableFlags_BordersV; +const ImGuiTableColumnFlags PANEL_TABLE_COLUMN_FLAGS = + ImGuiTableColumnFlags_IndentEnable | + ImGuiTableColumnFlags_NoSort; + +namespace TableHelper { + void InitHeader(bool has_header = true) { + if (has_header) { + ImGui::TableHeadersRow(); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); //This is to adjust Vertical pos of item in a cell to be normlized. + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + } + + void NextCol() { + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + } + + void NextLine() { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + } +} + +typedef uint32_t N64ButtonMask; + +void SohInputEditorWindow::addButtonName(N64ButtonMask mask, const char* name) { + buttons.push_back(std::make_pair(mask, name)); + buttonNames[mask] = std::prev(buttons.end()); +} + +// Ocarina button maps +static CustomButtonMap ocarinaD5 = {"D5", "gOcarinaD5BtnMap", BTN_CUP}; +static CustomButtonMap ocarinaB4 = {"B4", "gOcarinaB4BtnMap", BTN_CLEFT}; +static CustomButtonMap ocarinaA4 = {"A4", "gOcarinaA4BtnMap", BTN_CRIGHT}; +static CustomButtonMap ocarinaF4 = {"F4", "gOcarinaF4BtnMap", BTN_CDOWN}; +static CustomButtonMap ocarinaD4 = {"D4", "gOcarinaD4BtnMap", BTN_A}; +static CustomButtonMap ocarinaSongDisable = {"Disable songs", "gOcarinaDisableBtnMap", BTN_L}; +static CustomButtonMap ocarinaSharp = {"Pitch up", "gOcarinaSharpBtnMap", BTN_R}; +static CustomButtonMap ocarinaFlat = {"Pitch down", "gOcarinaFlatBtnMap", BTN_Z}; + +// Draw a button mapping setting consisting of a padded label and button dropdown. +// excludedButtons indicates which buttons are unavailable to choose from. +void SohInputEditorWindow::DrawMapping(CustomButtonMap& mapping, float labelWidth, N64ButtonMask excludedButtons) { + N64ButtonMask currentButton = CVarGetInteger(mapping.cVarName, mapping.defaultBtn); + + const char* preview; + if (buttonNames.contains(currentButton)) { + preview = buttonNames[currentButton]->second; + } else { + preview = "Unknown"; + } + + UIWidgets::Spacer(0); + ImVec2 cursorPos = ImGui::GetCursorPos(); + ImVec2 textSize = ImGui::CalcTextSize(mapping.label); + ImGui::SetCursorPosY(cursorPos.y + textSize.y / 4); + ImGui::SetCursorPosX(cursorPos.x + abs(textSize.x - labelWidth)); + ImGui::Text("%s", mapping.label); + ImGui::SameLine(); + ImGui::SetCursorPosY(cursorPos.y); + + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + if (ImGui::BeginCombo(StringHelper::Sprintf("##%s", mapping.cVarName).c_str(), preview)) { + for (auto i = buttons.begin(); i != buttons.end(); i++) { + if ((i->first & excludedButtons) != 0) { + continue; + } + if (ImGui::Selectable(i->second, i->first == currentButton)) { + CVarSetInteger(mapping.cVarName, i->first); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + } + ImGui::EndCombo(); + } + UIWidgets::Spacer(0); +} + +void SohInputEditorWindow::DrawOcarinaControlPanel() { + if (!ImGui::BeginTable("tableCustomOcarinaControls", 1, PANEL_TABLE_FLAGS)) { + return; + } + + ImGui::TableSetupColumn("Custom Ocarina Controls", PANEL_TABLE_COLUMN_FLAGS | ImGuiTableColumnFlags_WidthStretch); + TableHelper::InitHeader(false); + + ImVec2 cursor = ImGui::GetCursorPos(); + ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); + UIWidgets::EnhancementCheckbox("Customize Ocarina Controls", "gCustomOcarinaControls"); + + if (CVarGetInteger("gCustomOcarinaControls", 0) == 1) { + if (ImGui::BeginTable("tableCustomMainOcarinaControls", 2, ImGuiTableFlags_SizingStretchProp)) { + float labelWidth; + N64ButtonMask disableMask = BTN_B; + if (CVarGetInteger("gDpadOcarina", 0)) { + disableMask |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT; + } + + ImGui::TableSetupColumn("Notes##CustomOcarinaNotes", PANEL_TABLE_COLUMN_FLAGS); + ImGui::TableSetupColumn("Modifiers##CustomOcaranaModifiers", PANEL_TABLE_COLUMN_FLAGS); + TableHelper::InitHeader(false); + + LUS::GuiWindow::BeginGroupPanel("Notes", ImGui::GetContentRegionAvail()); + labelWidth = ImGui::CalcTextSize("D5").x + 10; + DrawMapping(ocarinaD5, labelWidth, disableMask); + DrawMapping(ocarinaB4, labelWidth, disableMask); + DrawMapping(ocarinaA4, labelWidth, disableMask); + DrawMapping(ocarinaF4, labelWidth, disableMask); + DrawMapping(ocarinaD4, labelWidth, disableMask); + ImGui::Dummy(ImVec2(0, 5)); + float cursorY = ImGui::GetCursorPosY(); + LUS::GuiWindow::EndGroupPanel(0); + + TableHelper::NextCol(); + + LUS::GuiWindow::BeginGroupPanel("Modifiers", ImGui::GetContentRegionAvail()); + labelWidth = ImGui::CalcTextSize(ocarinaSongDisable.label).x + 10; + DrawMapping(ocarinaSongDisable, labelWidth, disableMask); + DrawMapping(ocarinaSharp, labelWidth, disableMask); + DrawMapping(ocarinaFlat, labelWidth, disableMask); + LUS::GuiWindow::EndGroupPanel(cursorY - ImGui::GetCursorPosY() + 2); + + ImGui::EndTable(); + } + } else { + UIWidgets::Spacer(0); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); + ImGui::TextWrapped("To modify the main ocarina controls, select the \"Customize Ocarina Controls\" checkbox."); + UIWidgets::Spacer(0); + } + + LUS::GuiWindow::BeginGroupPanel("Alternate controls", ImGui::GetContentRegionAvail()); + if (ImGui::BeginTable("tableOcarinaAlternateControls", 2, ImGuiTableFlags_SizingFixedSame)) { + ImGui::TableSetupColumn("D-pad", PANEL_TABLE_COLUMN_FLAGS); + ImGui::TableSetupColumn("Right stick", PANEL_TABLE_COLUMN_FLAGS); + TableHelper::InitHeader(false); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); + UIWidgets::EnhancementCheckbox("Play with D-pad", "gDpadOcarina"); + TableHelper::NextCol(); + UIWidgets::EnhancementCheckbox("Play with camera stick", "gRStickOcarina"); + UIWidgets::Spacer(0); + ImGui::EndTable(); + } + LUS::GuiWindow::EndGroupPanel(0); + + ImGui::EndTable(); +} + +void SohInputEditorWindow::DrawCameraControlPanel() { + ImVec2 cursor = ImGui::GetCursorPos(); + ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); + LUS::GuiWindow::BeginGroupPanel("Aiming/First-Person Camera", ImGui::GetContentRegionAvail()); + UIWidgets::PaddedEnhancementCheckbox("Right Stick Aiming", "gRightStickAiming"); + UIWidgets::Tooltip("Allows for aiming with the right stick in:\n-First-Person/C-Up view\n-Weapon Aiming"); + if (CVarGetInteger("gRightStickAiming", 0)) { + UIWidgets::PaddedEnhancementCheckbox("Allow moving while in first person mode", "gMoveWhileFirstPerson"); + UIWidgets::Tooltip("Changes the left stick to move the player while in first person mode"); + } + UIWidgets::PaddedEnhancementCheckbox("Invert Aiming X Axis", "gInvertAimingXAxis"); + UIWidgets::Tooltip("Inverts the Camera X Axis in:\n-First-Person/C-Up view\n-Weapon Aiming"); + UIWidgets::PaddedEnhancementCheckbox("Invert Aiming Y Axis", "gInvertAimingYAxis", true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); + UIWidgets::Tooltip("Inverts the Camera Y Axis in:\n-First-Person/C-Up view\n-Weapon Aiming"); + UIWidgets::PaddedEnhancementCheckbox("Invert Shield Aiming Y Axis", "gInvertShieldAimingYAxis", true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); + UIWidgets::Tooltip("Inverts the Shield Aiming Y Axis"); + UIWidgets::PaddedEnhancementCheckbox("Invert Shield Aiming X Axis", "gInvertShieldAimingXAxis"); + UIWidgets::Tooltip("Inverts the Shield Aiming X Axis"); + UIWidgets::PaddedEnhancementCheckbox("Invert Z-Weapon Aiming Y Axis", "gInvertZAimingYAxis", true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); + UIWidgets::Tooltip("Inverts the Camera Y Axis in:\n-Z-Weapon Aiming"); + UIWidgets::PaddedEnhancementCheckbox("Disable Auto-Centering in First-Person View", "gDisableAutoCenterViewFirstPerson"); + UIWidgets::Tooltip("Prevents the C-Up view from auto-centering, allowing for Gyro Aiming"); + if (UIWidgets::PaddedEnhancementCheckbox("Enable Custom Aiming/First-Person sensitivity", "gEnableFirstPersonSensitivity", true, false)) { + if (!CVarGetInteger("gEnableFirstPersonSensitivity", 0)) { + CVarClear("gFirstPersonCameraSensitivityX"); + CVarClear("gFirstPersonCameraSensitivityY"); + } + } + if (CVarGetInteger("gEnableFirstPersonSensitivity", 0)) { + UIWidgets::EnhancementSliderFloat("Aiming/First-Person Horizontal Sensitivity: %.0f %%", "##FirstPersonSensitivity Horizontal", + "gFirstPersonCameraSensitivityX", 0.01f, 5.0f, "", 1.0f, true); + UIWidgets::EnhancementSliderFloat("Aiming/First-Person Vertical Sensitivity: %.0f %%", "##FirstPersonSensitivity Vertical", + "gFirstPersonCameraSensitivityY", 0.01f, 5.0f, "", 1.0f, true); + } + UIWidgets::Spacer(0); + LUS::GuiWindow::EndGroupPanel(0); + + UIWidgets::Spacer(0); + cursor = ImGui::GetCursorPos(); + ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); + LUS::GuiWindow::BeginGroupPanel("Third-Person Camera", ImGui::GetContentRegionAvail()); + + UIWidgets::PaddedEnhancementCheckbox("Free Camera", "gFreeCamera"); + UIWidgets::Tooltip("Enables free 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"); + UIWidgets::Tooltip("Inverts the Camera X Axis in:\n-Free camera"); + UIWidgets::PaddedEnhancementCheckbox("Invert Camera Y Axis", "gInvertYAxis", true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); + UIWidgets::Tooltip("Inverts the Camera Y Axis in:\n-Free camera"); + UIWidgets::Spacer(0); + UIWidgets::PaddedEnhancementSliderFloat("Third-Person Horizontal Sensitivity: %.0f %%", "##ThirdPersonSensitivity Horizontal", + "gThirdPersonCameraSensitivityX", 0.01f, 5.0f, "", 1.0f, true, true, false, true); + UIWidgets::PaddedEnhancementSliderFloat("Third-Person Vertical Sensitivity: %.0f %%", "##ThirdPersonSensitivity Vertical", + "gThirdPersonCameraSensitivityY", 0.01f, 5.0f, "", 1.0f, true, true, false, true); + UIWidgets::PaddedEnhancementSliderInt("Camera Distance: %d", "##CamDist", + "gFreeCameraDistMax", 100, 900, "", 185, true, false, true); + UIWidgets::PaddedEnhancementSliderInt("Camera Transition Speed: %d", "##CamTranSpeed", + "gFreeCameraTransitionSpeed", 0, 900, "", 25, true, false, true); + LUS::GuiWindow::EndGroupPanel(0); +} + +void SohInputEditorWindow::DrawDpadControlPanel() { + ImVec2 cursor = ImGui::GetCursorPos(); + ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); + LUS::GuiWindow::BeginGroupPanel("D-Pad Options", ImGui::GetContentRegionAvail()); + UIWidgets::PaddedEnhancementCheckbox("D-pad Support on Pause Screen", "gDpadPause"); + UIWidgets::Tooltip("Navigate Pause with the D-pad\nIf used with D-pad as Equip Items, you must hold C-Up to equip instead of navigate\n" + "To make the cursor only move a single space no matter how long a direction is held, manually set gDpadHoldChange to 0"); + UIWidgets::PaddedEnhancementCheckbox("D-pad Support in Text Boxes", "gDpadText"); + UIWidgets::Tooltip("Navigate choices in text boxes, shop item selection, and the file select / name entry screens with the D-pad\n" + "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"); + UIWidgets::Tooltip("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"); + LUS::GuiWindow::EndGroupPanel(0); +} + +void SohInputEditorWindow::DrawMiscControlPanel() { + ImVec2 cursor = ImGui::GetCursorPos(); + ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); + LUS::GuiWindow::BeginGroupPanel("Misc Controls", ImGui::GetContentRegionAvail()); + UIWidgets::PaddedText("Allow the cursor to be on any slot"); + static const char* cursorOnAnySlot[3] = { "Only in Rando", "Always", "Never" }; + UIWidgets::EnhancementCombobox("gPauseAnyCursor", cursorOnAnySlot, PAUSE_ANY_CURSOR_RANDO_ONLY); + UIWidgets::Tooltip("Allows the cursor on the pause menu to be over any slot. Sometimes required in rando to select " + "certain items."); + UIWidgets::Spacer(0); + ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0)); + UIWidgets::PaddedEnhancementCheckbox("Enable speed modifiers", "gEnableWalkModify", true, false); + UIWidgets::Tooltip("Hold the assigned button to change the maximum walking or swimming speed"); + if (CVarGetInteger("gEnableWalkModify", 0)) { + UIWidgets::Spacer(5); + LUS::GuiWindow::BeginGroupPanel("Speed Modifier", ImGui::GetContentRegionAvail()); + UIWidgets::PaddedEnhancementCheckbox("Toggle modifier instead of holding", "gWalkSpeedToggle", true, false); + LUS::GuiWindow::BeginGroupPanel("Walk Modifier", ImGui::GetContentRegionAvail()); + UIWidgets::PaddedEnhancementCheckbox("Don't affect jump distance/velocity", "gWalkModifierDoesntChangeJump", true, false); + UIWidgets::PaddedEnhancementSliderFloat("Walk Modifier 1: %.0f %%", "##WalkMod1", "gWalkModifierOne", 0.0f, 5.0f, "", 1.0f, true, true, false, true); + UIWidgets::PaddedEnhancementSliderFloat("Walk Modifier 2: %.0f %%", "##WalkMod2", "gWalkModifierTwo", 0.0f, 5.0f, "", 1.0f, true, true, false, true); + LUS::GuiWindow::EndGroupPanel(0); + LUS::GuiWindow::BeginGroupPanel("Swim Modifier", ImGui::GetContentRegionAvail()); + UIWidgets::PaddedEnhancementSliderFloat("Swim Modifier 1: %.0f %%", "##SwimMod1", "gSwimModifierOne", 0.0f, 5.0f, "", 1.0f, true, true, false, true); + UIWidgets::PaddedEnhancementSliderFloat("Swim Modifier 2: %.0f %%", "##SwimMod2", "gSwimModifierTwo", 0.0f, 5.0f, "", 1.0f, true, true, false, true); + LUS::GuiWindow::EndGroupPanel(0); + LUS::GuiWindow::EndGroupPanel(0); + } + ImGui::EndDisabled(); + UIWidgets::Spacer(0); + UIWidgets::PaddedEnhancementCheckbox("Answer Navi Prompt with L Button", "gNaviOnL"); + UIWidgets::Tooltip("Speak to Navi with L but enter first-person camera with C-Up"); + LUS::GuiWindow::EndGroupPanel(0); +} + void SohInputEditorWindow::DrawLinkTab() { uint8_t portIndex = 0; if (ImGui::BeginTabItem(StringHelper::Sprintf("Link (P1)###port%d", portIndex).c_str())) { @@ -1477,6 +1791,46 @@ void SohInputEditorWindow::DrawLinkTab() { DrawButtonDeviceIcons(portIndex, mModifierButtonsBitmasks); } + if (ImGui::CollapsingHeader("Ocarina Controls")) { + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + DrawOcarinaControlPanel(); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.133f, 0.133f, 0.133f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + } + + if (ImGui::CollapsingHeader("Camera Controls")) { + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + DrawCameraControlPanel(); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.133f, 0.133f, 0.133f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + } + + if (ImGui::CollapsingHeader("D-Pad Controls")) { + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + DrawDpadControlPanel(); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.133f, 0.133f, 0.133f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + } + + if (ImGui::CollapsingHeader("Miscellaneous Controls")) { + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + DrawMiscControlPanel(); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.133f, 0.133f, 0.133f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + } + ImGui::PopStyleColor(); ImGui::PopStyleColor(); ImGui::PopStyleColor(); diff --git a/soh/soh/Enhancements/controls/SohInputEditorWindow.h b/soh/soh/Enhancements/controls/SohInputEditorWindow.h index 089719a58..e96b5b09a 100644 --- a/soh/soh/Enhancements/controls/SohInputEditorWindow.h +++ b/soh/soh/Enhancements/controls/SohInputEditorWindow.h @@ -2,11 +2,23 @@ #include "stdint.h" #include +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif #include #include #include #include #include +#include + +typedef uint32_t N64ButtonMask; + +typedef struct { + const char* label; + const char* cVarName; + N64ButtonMask defaultBtn; +} CustomButtonMap; class SohInputEditorWindow : public LUS::GuiWindow { public: @@ -48,6 +60,17 @@ class SohInputEditorWindow : public LUS::GuiWindow { void DrawRemoveGyroMappingButton(uint8_t port, std::string id); void DrawAddGyroMappingButton(uint8_t port); + // Used together for an incomplete linked hash map implementation in order to + // map button masks to their names and original mapping on N64 + std::list> buttons; + std::unordered_map buttonNames; + void addButtonName(N64ButtonMask mask, const char* name); + void DrawMapping(CustomButtonMap& mapping, float labelWidth, N64ButtonMask excludedButtons); + void DrawOcarinaControlPanel(); + void DrawCameraControlPanel(); + void DrawDpadControlPanel(); + void DrawMiscControlPanel(); + int32_t mGameInputBlockTimer; int32_t mMappingInputBlockTimer; int32_t mRumbleTimer; @@ -81,6 +104,4 @@ class SohInputEditorWindow : public LUS::GuiWindow { bool mInputEditorPopupOpen; void DrawSetDefaultsButton(uint8_t portIndex); void DrawClearAllButton(uint8_t portIndex); - - void DrawHelpIcon(const std::string& helptext); }; diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index 378197bcd..4f04bc64b 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -67,6 +67,7 @@ typedef enum { GROUP_EQUIPMENT, GROUP_CONSUMABLE, GROUP_HUD, + GROUP_KALEIDO, GROUP_TITLE, GROUP_NPC, GROUP_WORLD, @@ -75,6 +76,7 @@ typedef enum { GROUP_SPIN_ATTACK, GROUP_TRAILS, GROUP_NAVI, + GROUP_IVAN, } CosmeticGroup; std::map groupLabels = { @@ -85,6 +87,7 @@ std::map groupLabels = { { GROUP_EQUIPMENT, "Equipment" }, { GROUP_CONSUMABLE, "Consumables" }, { GROUP_HUD, "HUD" }, + { GROUP_KALEIDO, "Pause Menu" }, { GROUP_TITLE, "Title Screen" }, { GROUP_NPC, "NPCs" }, { GROUP_WORLD, "World" }, @@ -93,6 +96,7 @@ std::map groupLabels = { { GROUP_SPIN_ATTACK, "Spin Attack" }, { GROUP_TRAILS, "Trails" }, { GROUP_NAVI, "Navi" }, + { GROUP_IVAN, "Ivan" } }; typedef struct { @@ -265,6 +269,38 @@ static std::map cosmeticOptions = { COSMETIC_OPTION("Hud_NameTagActorText", "Nametag Text", GROUP_HUD, ImVec4(255, 255, 255, 255), true, true, false), COSMETIC_OPTION("Hud_NameTagActorBackground", "Nametag Background", GROUP_HUD, ImVec4(0, 0, 0, 80), true, false, true), + COSMETIC_OPTION("Kal_ItemSelA", "Item Select Color", GROUP_KALEIDO, ImVec4(10, 50, 80, 255), false, true, false), + COSMETIC_OPTION("Kal_ItemSelB", "Item Select Color B", GROUP_KALEIDO, ImVec4(70, 100, 130, 255), false, true, true), + COSMETIC_OPTION("Kal_ItemSelC", "Item Select Color C", GROUP_KALEIDO, ImVec4(70, 100, 130, 255), false, true, true), + COSMETIC_OPTION("Kal_ItemSelD", "Item Select Color D", GROUP_KALEIDO, ImVec4(10, 50, 80, 255), false, true, true), + + COSMETIC_OPTION("Kal_EquipSelA", "Equip Select Color", GROUP_KALEIDO, ImVec4(10, 50, 40, 255), false, true, false), + COSMETIC_OPTION("Kal_EquipSelB", "Equip Select Color B", GROUP_KALEIDO, ImVec4(90, 100, 60, 255), false, true, true), + COSMETIC_OPTION("Kal_EquipSelC", "Equip Select Color C", GROUP_KALEIDO, ImVec4(90, 100, 60, 255), false, true, true), + COSMETIC_OPTION("Kal_EquipSelD", "Equip Select Color D", GROUP_KALEIDO, ImVec4(10, 50, 80, 255), false, true, true), + + COSMETIC_OPTION("Kal_MapSelDunA", "Map Dungeon Color", GROUP_KALEIDO, ImVec4(80, 40, 30, 255), false, true, true), + COSMETIC_OPTION("Kal_MapSelDunB", "Map Dungeon Color B", GROUP_KALEIDO, ImVec4(140, 60, 60, 255), false, true, true), + COSMETIC_OPTION("Kal_MapSelDunC", "Map Dungeon Color C", GROUP_KALEIDO, ImVec4(140, 60, 60, 255), false, true, true), + COSMETIC_OPTION("Kal_MapSelDunD", "Map Dungeon Color D", GROUP_KALEIDO, ImVec4(80, 40, 30, 255), false, true, true), + + COSMETIC_OPTION("Kal_QuestStatusA", "Quest Status Color", GROUP_KALEIDO, ImVec4(80, 80, 50, 255), false, true, false), + COSMETIC_OPTION("Kal_QuestStatusB", "Quest Status Color B", GROUP_KALEIDO, ImVec4(120, 120, 70, 255), false, true, true), + COSMETIC_OPTION("Kal_QuestStatusC", "Quest Status Color C", GROUP_KALEIDO, ImVec4(120, 120, 70, 255), false, true, true), + COSMETIC_OPTION("Kal_QuestStatusD", "Quest Status Color D", GROUP_KALEIDO, ImVec4(80, 80, 50, 255), false, true, true), + + COSMETIC_OPTION("Kal_MapSelectA", "Map Color", GROUP_KALEIDO, ImVec4(80, 40, 30, 255), false, true, false), + COSMETIC_OPTION("Kal_MapSelectB", "Map Color B", GROUP_KALEIDO, ImVec4(140, 60, 60, 255), false, true, true), + COSMETIC_OPTION("Kal_MapSelectC", "Map Color C", GROUP_KALEIDO, ImVec4(140, 60, 60, 255), false, true, true), + COSMETIC_OPTION("Kal_MapSelectD", "Map Color D", GROUP_KALEIDO, ImVec4(80, 40, 30, 255), false, true, true), + + COSMETIC_OPTION("Kal_SaveA", "Save Color", GROUP_KALEIDO, ImVec4(50, 50, 50, 255), false, true, false), + COSMETIC_OPTION("Kal_SaveB", "Save Color B", GROUP_KALEIDO, ImVec4(110, 110, 110, 255), false, true, true), + COSMETIC_OPTION("Kal_SaveC", "Save Color C", GROUP_KALEIDO, ImVec4(110, 110, 110, 255), false, true, true), + COSMETIC_OPTION("Kal_SaveD", "Save Color D", GROUP_KALEIDO, ImVec4(50, 50, 50, 255), false, true, true), + + COSMETIC_OPTION("Kal_NamePanel", "Name Panel", GROUP_KALEIDO, ImVec4(90,100,130,255), true, true, false), + COSMETIC_OPTION("Title_FileChoose", "File Choose", GROUP_TITLE, ImVec4(100, 150, 255, 255), false, true, false), COSMETIC_OPTION("Title_NintendoLogo", "Nintendo Logo", GROUP_TITLE, ImVec4( 0, 0, 255, 255), false, true, true), COSMETIC_OPTION("Title_N64LogoRed", "N64 Red", GROUP_TITLE, ImVec4(150, 0, 0, 255), false, true, true), @@ -316,6 +352,9 @@ static std::map cosmeticOptions = { COSMETIC_OPTION("Navi_EnemySecondary", "Enemy Secondary", GROUP_NAVI, ImVec4(200, 155, 0, 0), false, true, true), COSMETIC_OPTION("Navi_PropsPrimary", "Props Primary", GROUP_NAVI, ImVec4( 0, 255, 0, 255), false, true, false), COSMETIC_OPTION("Navi_PropsSecondary", "Props Secondary", GROUP_NAVI, ImVec4( 0, 255, 0, 0), false, true, true), + + COSMETIC_OPTION("Ivan_IdlePrimary", "Ivan Idle Primary", GROUP_IVAN, ImVec4(255, 255, 255, 255), false, true, false), + COSMETIC_OPTION("Ivan_IdleSecondary", "Ivan Idle Secondary", GROUP_IVAN, ImVec4( 0, 255, 0, 255), false, true, true), COSMETIC_OPTION("NPC_FireKeesePrimary", "Fire Keese Primary", GROUP_NPC, ImVec4(255, 255, 255, 255), false, true, false), COSMETIC_OPTION("NPC_FireKeeseSecondary", "Fire Keese Secondary", GROUP_NPC, ImVec4(255, 255, 255, 255), false, true, true), @@ -1026,12 +1065,16 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { if (manualChange || CVarGetInteger(npcGoldenSkulltula.rainbowCvar, 0)) { static Color_RGBA8 defaultColor = {npcGoldenSkulltula.defaultColor.x, npcGoldenSkulltula.defaultColor.y, npcGoldenSkulltula.defaultColor.z, npcGoldenSkulltula.defaultColor.w}; Color_RGBA8 color = CVarGetColor(npcGoldenSkulltula.cvar, defaultColor); - PATCH_GFX(gGiSkulltulaTokenDL, "NPC_GoldenSkulltula1", npcGoldenSkulltula.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); - PATCH_GFX(gGiSkulltulaTokenDL, "NPC_GoldenSkulltula2", npcGoldenSkulltula.changedCvar, 6, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); - PATCH_GFX(gGiSkulltulaTokenFlameDL, "NPC_GoldenSkulltula3", npcGoldenSkulltula.changedCvar, 32, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); - PATCH_GFX(gGiSkulltulaTokenFlameDL, "NPC_GoldenSkulltula4", npcGoldenSkulltula.changedCvar, 33, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); - PATCH_GFX(object_st_DL_003FB0, "NPC_GoldenSkulltula5", npcGoldenSkulltula.changedCvar, 118, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); - PATCH_GFX(object_st_DL_003FB0, "NPC_GoldenSkulltula6", npcGoldenSkulltula.changedCvar, 119, gsDPSetEnvColor(color.r / 4, color.g / 4, color.b / 4, 255)); + PATCH_GFX(gSkulltulaTokenDL, "NPC_GoldenSkulltula1", npcGoldenSkulltula.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); + PATCH_GFX(gSkulltulaTokenDL, "NPC_GoldenSkulltula2", npcGoldenSkulltula.changedCvar, 6, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); + PATCH_GFX(gSkulltulaTokenFlameDL, "NPC_GoldenSkulltula3", npcGoldenSkulltula.changedCvar, 32, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); + PATCH_GFX(gSkulltulaTokenFlameDL, "NPC_GoldenSkulltula4", npcGoldenSkulltula.changedCvar, 33, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); + PATCH_GFX(gGiSkulltulaTokenDL, "NPC_GoldenSkulltula5", npcGoldenSkulltula.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); + PATCH_GFX(gGiSkulltulaTokenDL, "NPC_GoldenSkulltula6", npcGoldenSkulltula.changedCvar, 6, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); + PATCH_GFX(gGiSkulltulaTokenFlameDL, "NPC_GoldenSkulltula7", npcGoldenSkulltula.changedCvar, 32, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); + PATCH_GFX(gGiSkulltulaTokenFlameDL, "NPC_GoldenSkulltula8", npcGoldenSkulltula.changedCvar, 33, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); + PATCH_GFX(object_st_DL_003FB0, "NPC_GoldenSkulltula9", npcGoldenSkulltula.changedCvar, 118, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); + PATCH_GFX(object_st_DL_003FB0, "NPC_GoldenSkulltula10", npcGoldenSkulltula.changedCvar, 119, gsDPSetEnvColor(color.r / 4, color.g / 4, color.b / 4, 255)); } static CosmeticOption& npcGerudo = cosmeticOptions.at("NPC_Gerudo"); @@ -1142,6 +1185,43 @@ void DrawScaleSlider(const std::string CvarName,float DefaultValue){ //Disabled for now. feature not done and several fixes needed to be merged. //UIWidgets::EnhancementSliderFloat("Scale : %dx", InvisibleLabel.c_str(), CvarLabel.c_str(), 0.1f, 3.0f,"",DefaultValue,true); } +void Draw_Table_Dropdown(const char* Header_Title, const char* Table_ID, const char* Column_Title, const char* Slider_Title, const char* Slider_ID, int MinY, int MaxY, int MinX, int MaxX, float Default_Value) { + if (ImGui::CollapsingHeader(Header_Title)) { + if (ImGui::BeginTable(Table_ID, 1, FlagsTable)) { + ImGui::TableSetupColumn(Column_Title, FlagsCell, TablesCellsWidth); + Table_InitHeader(false); + DrawUseMarginsSlider(Slider_Title, Slider_ID); + DrawPositionsRadioBoxes(Slider_ID); + DrawPositionSlider(Slider_ID, MinY, MaxY, MinX, MaxX); + DrawScaleSlider(Slider_ID, Default_Value); + ImGui::NewLine(); + ImGui::EndTable(); + } + } +} +void C_Button_Dropdown(const char* Header_Title, const char* Table_ID, const char* Column_Title, const char* Slider_Title, const char* Slider_ID, const char* Int_Type, float Slider_Scale_Value) { + if (ImGui::CollapsingHeader(Header_Title)) { + if (ImGui::BeginTable(Table_ID, 1, FlagsTable)) { + ImGui::TableSetupColumn(Column_Title, FlagsCell, TablesCellsWidth); + Table_InitHeader(false); + DrawUseMarginsSlider(Slider_Title, Slider_ID); + DrawPositionsRadioBoxes(Slider_ID); + s16 Min_X_CU = 0; + s16 Max_X_CU = ImGui::GetWindowViewport()->Size.x/2; + if(CVarGetInteger(Int_Type,0) == 2){ + Max_X_CU = 294; + } else if(CVarGetInteger(Int_Type,0) == 3){ + Max_X_CU = ImGui::GetWindowViewport()->Size.x/2; + } else if(CVarGetInteger(Int_Type,0) == 4){ + Min_X_CU = (ImGui::GetWindowViewport()->Size.x/2)*-1; + } + DrawPositionSlider(Slider_ID, 0, ImGui::GetWindowViewport()->Size.y/2, Min_X_CU, Max_X_CU); + DrawScaleSlider(Slider_ID, Slider_Scale_Value); + ImGui::NewLine(); + ImGui::EndTable(); + } + } +} void Draw_Placements(){ if (ImGui::BeginTable("tableMargins", 1, FlagsTable)) { ImGui::TableSetupColumn("General margins settings", FlagsCell, TablesCellsWidth); @@ -1208,126 +1288,13 @@ void Draw_Placements(){ ImGui::EndTable(); } } - if (ImGui::CollapsingHeader("B Button position")) { - if (ImGui::BeginTable("tablebbtn", 1, FlagsTable)) { - ImGui::TableSetupColumn("B Button settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("B Button", "gBBtn"); - DrawPositionsRadioBoxes("gBBtn"); - DrawPositionSlider("gBBtn", 0, ImGui::GetWindowViewport()->Size.y/4+50, -1, ImGui::GetWindowViewport()->Size.x-50); - DrawScaleSlider("gBBtn",0.95f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("A Button position")) { - if (ImGui::BeginTable("tableabtn", 1, FlagsTable)) { - ImGui::TableSetupColumn("A Button settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("A Button", "gABtn"); - DrawPositionsRadioBoxes("gABtn"); - DrawPositionSlider("gABtn", -10, ImGui::GetWindowViewport()->Size.y/4+50, -20, ImGui::GetWindowViewport()->Size.x-50); - DrawScaleSlider("gABtn",0.95f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("Start Button position")) { - if (ImGui::BeginTable("tablestartbtn", 1, FlagsTable)) { - ImGui::TableSetupColumn("Start Button settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("Start Button", "gStartBtn"); - DrawPositionsRadioBoxes("gStartBtn"); - DrawPositionSlider("gStartBtn", 0, ImGui::GetWindowViewport()->Size.y/2, 0, ImGui::GetWindowViewport()->Size.x/2+70); - DrawScaleSlider("gStartBtn",0.75f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("C Button Up position")) { - if (ImGui::BeginTable("tablecubtn", 1, FlagsTable)) { - ImGui::TableSetupColumn("C Button Up settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("C Button Up", "gCBtnU"); - DrawPositionsRadioBoxes("gCBtnU"); - s16 Min_X_CU = 0; - s16 Max_X_CU = ImGui::GetWindowViewport()->Size.x/2; - if(CVarGetInteger("gCBtnUPosType",0) == 2){ - Max_X_CU = 294; - } else if(CVarGetInteger("gCBtnUPosType",0) == 3){ - Max_X_CU = ImGui::GetWindowViewport()->Size.x/2; - } else if(CVarGetInteger("gCBtnUPosType",0) == 4){ - Min_X_CU = (ImGui::GetWindowViewport()->Size.x/2)*-1; - } - DrawPositionSlider("gCBtnU", 0, ImGui::GetWindowViewport()->Size.y/2, Min_X_CU, Max_X_CU); - DrawScaleSlider("gCBtnU",0.5f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("C Button Down position")) { - if (ImGui::BeginTable("tablecdbtn", 1, FlagsTable)) { - ImGui::TableSetupColumn("C Button Down settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("C Button Down", "gCBtnD"); - DrawPositionsRadioBoxes("gCBtnD"); - s16 Min_X_CD = 0; - s16 Max_X_CD = ImGui::GetWindowViewport()->Size.x/2; - if(CVarGetInteger("gCBtnDPosType",0) == 2){ - Max_X_CD = 294; - } else if(CVarGetInteger("gCBtnDPosType",0) == 3){ - Max_X_CD = ImGui::GetWindowViewport()->Size.x/2; - } else if(CVarGetInteger("gCBtnDPosType",0) == 4){ - Min_X_CD = (ImGui::GetWindowViewport()->Size.x/2)*-1; - } - DrawPositionSlider("gCBtnD", 0, ImGui::GetWindowViewport()->Size.y/2, Min_X_CD, Max_X_CD); - DrawScaleSlider("gCBtnD",0.87f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("C Button Left position")) { - if (ImGui::BeginTable("tableclbtn", 1, FlagsTable)) { - ImGui::TableSetupColumn("C Button Left settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("C Button Left", "gCBtnL"); - DrawPositionsRadioBoxes("gCBtnL"); - s16 Min_X_CL = 0; - s16 Max_X_CL = ImGui::GetWindowViewport()->Size.x/2; - if(CVarGetInteger("gCBtnLPosType",0) == 2){ - Max_X_CL = 294; - } else if(CVarGetInteger("gCBtnLPosType",0) == 3){ - Max_X_CL = ImGui::GetWindowViewport()->Size.x/2; - } else if(CVarGetInteger("gCBtnLPosType",0) == 4){ - Min_X_CL = (ImGui::GetWindowViewport()->Size.x/2)*-1; - } - DrawPositionSlider("gCBtnL", 0, ImGui::GetWindowViewport()->Size.y/2, Min_X_CL, Max_X_CL); - DrawScaleSlider("gCBtnL",0.87f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("C Button Right position")) { - if (ImGui::BeginTable("tablecrnbtn", 1, FlagsTable)) { - ImGui::TableSetupColumn("C Button Right settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("C Button Right", "gCBtnR"); - DrawPositionsRadioBoxes("gCBtnR"); - s16 Min_X_CR = 0; - s16 Max_X_CR = ImGui::GetWindowViewport()->Size.x/2; - if(CVarGetInteger("gCBtnRPosType",0) == 2){ - Max_X_CR = 294; - } else if(CVarGetInteger("gCBtnRPosType",0) == 3){ - Max_X_CR = ImGui::GetWindowViewport()->Size.x/2; - } else if(CVarGetInteger("gCBtnRPosType",0) == 4){ - Min_X_CR = (ImGui::GetWindowViewport()->Size.x/2)*-1; - } - DrawPositionSlider("gCBtnR", 0, ImGui::GetWindowViewport()->Size.y/2, Min_X_CR, Max_X_CR); - DrawScaleSlider("gCBtnR",0.87f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } + Draw_Table_Dropdown("B Button position", "tablebbtn", "B Button settings", "B Button", "gBBtn", 0, ImGui::GetWindowViewport()->Size.y/4+50, -1, ImGui::GetWindowViewport()->Size.x-50, 0.95f); + Draw_Table_Dropdown("A Button position", "tableabtn", "A Button settings", "A Button", "gABtn", -10, ImGui::GetWindowViewport()->Size.y/4+50, -20, ImGui::GetWindowViewport()->Size.x-50, 0.95f); + Draw_Table_Dropdown("Start Button position", "tablestartbtn", "Start Button settings", "Start Button", "gStartBtn", 0, ImGui::GetWindowViewport()->Size.y/2, 0, ImGui::GetWindowViewport()->Size.x/2+70, 0.75f); + C_Button_Dropdown("C Button Up position", "tablecubtn", "C Button Up settings", "C Button Up", "gCBtnU", "gCBtnUPosType", 0.5f); + C_Button_Dropdown("C Button Down position", "tablecdbtn", "C Button Down settings", "C Button Down", "gCBtnD", "gCBtnDPosType", 0.87f); + C_Button_Dropdown("C Button Left position", "tableclbtn", "C Button Left settings", "C Button Left", "gCBtnL", "gCBtnLPosType", 0.87f); + C_Button_Dropdown("C Button Right position", "tablecrbtn", "C Button Right settings", "C Button Right", "gCBtnR", "gCBtnRPosType", 0.87f); if (CVarGetInteger("gDpadEquips",0) && ImGui::CollapsingHeader("DPad items position")) { if (ImGui::BeginTable("tabledpaditems", 1, FlagsTable)) { ImGui::TableSetupColumn("DPad items settings", FlagsCell, TablesCellsWidth); @@ -1347,115 +1314,15 @@ void Draw_Placements(){ ImGui::EndTable(); } } - if (ImGui::CollapsingHeader("Minimaps position")) { - if (ImGui::BeginTable("tableminimapspos", 1, FlagsTable)) { - ImGui::TableSetupColumn("minimaps settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("Minimap", "gMinimap"); - DrawPositionsRadioBoxes("gMinimap", false); - DrawPositionSlider("gMinimap", (ImGui::GetWindowViewport()->Size.y/3)*-1, ImGui::GetWindowViewport()->Size.y/3, ImGui::GetWindowViewport()->Size.x*-1, ImGui::GetWindowViewport()->Size.x/2); - DrawScaleSlider("gMinimap",1.0f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("Small Keys counter position")) { - if (ImGui::BeginTable("tablesmolekeys", 1, FlagsTable)) { - ImGui::TableSetupColumn("Small Keys counter settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("Small Keys counter", "gSKC"); - DrawPositionsRadioBoxes("gSKC"); - DrawPositionSlider("gSKC", 0, ImGui::GetWindowViewport()->Size.y/3, -1, ImGui::GetWindowViewport()->Size.x/2); - DrawScaleSlider("gSKC",1.0f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("Rupee counter position")) { - if (ImGui::BeginTable("tablerupeecount", 1, FlagsTable)) { - ImGui::TableSetupColumn("Rupee counter settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("Rupee counter", "gRC"); - DrawPositionsRadioBoxes("gRC"); - DrawPositionSlider("gRC", -2, ImGui::GetWindowViewport()->Size.y/3, -3, ImGui::GetWindowViewport()->Size.x/2); - DrawScaleSlider("gRC",1.0f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("Carrots position")) { - if (ImGui::BeginTable("tableCarrots", 1, FlagsTable)) { - ImGui::TableSetupColumn("Carrots settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("Carrots", "gCarrots"); - DrawPositionsRadioBoxes("gCarrots"); - DrawPositionSlider("gCarrots", 0, ImGui::GetWindowViewport()->Size.y/2, -50, ImGui::GetWindowViewport()->Size.x/2+25); - DrawScaleSlider("gCarrots",1.0f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("Timers position")) { - if (ImGui::BeginTable("tabletimers", 1, FlagsTable)) { - ImGui::TableSetupColumn("Timers settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("Timers", "gTimers"); - DrawPositionsRadioBoxes("gTimers"); - DrawPositionSlider("gTimers", 0, ImGui::GetWindowViewport()->Size.y/2, -50, ImGui::GetWindowViewport()->Size.x/2-50); - DrawScaleSlider("gTimers",1.0f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("Archery Scores position")) { - if (ImGui::BeginTable("tablearchery", 1, FlagsTable)) { - ImGui::TableSetupColumn("Archery Scores settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("Archery scores", "gAS"); - DrawPositionsRadioBoxes("gAS", false); - DrawPositionSlider("gAS", 0, ImGui::GetWindowViewport()->Size.y/2, -50, ImGui::GetWindowViewport()->Size.x/2-50); - DrawScaleSlider("gAS",1.0f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("Title cards (Maps) position")) { - if (ImGui::BeginTable("tabletcmaps", 1, FlagsTable)) { - ImGui::TableSetupColumn("Titlecard maps settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("Title cards (overworld)", "gTCM"); - DrawPositionsRadioBoxes("gTCM"); - DrawPositionSlider("gTCM", 0, ImGui::GetWindowViewport()->Size.y/2, -50, ImGui::GetWindowViewport()->Size.x/2+10); - DrawScaleSlider("gTCM",1.0f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("Title cards (Bosses) position")) { - if (ImGui::BeginTable("tabletcbosses", 1, FlagsTable)) { - ImGui::TableSetupColumn("Title cards (Bosses) settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("Title cards (Bosses)", "gTCB"); - DrawPositionsRadioBoxes("gTCB"); - DrawPositionSlider("gTCB", 0, ImGui::GetWindowViewport()->Size.y/2, -50, ImGui::GetWindowViewport()->Size.x/2+10); - DrawScaleSlider("gTCB",1.0f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } - if (ImGui::CollapsingHeader("In-game Gameplay Timer position")) { - if (ImGui::BeginTable("tablegameplaytimer", 1, FlagsTable)) { - ImGui::TableSetupColumn("In-game Gameplay Timer settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(false); - DrawUseMarginsSlider("In-game Gameplay Timer", "gIGT"); - DrawPositionsRadioBoxes("gIGT"); - DrawPositionSlider("gIGT", 0, ImGui::GetWindowViewport()->Size.y / 2, -50, - ImGui::GetWindowViewport()->Size.x / 2 + 10); - DrawScaleSlider("gIGT", 1.0f); - ImGui::NewLine(); - ImGui::EndTable(); - } - } + Draw_Table_Dropdown("Minimaps position", "tableminimapspos", "minimaps settings", "Minimap", "gMinimap", (ImGui::GetWindowViewport()->Size.y/3)*-1, ImGui::GetWindowViewport()->Size.y/3, ImGui::GetWindowViewport()->Size.x*-1, ImGui::GetWindowViewport()->Size.x/2, 1.0f); + Draw_Table_Dropdown("Small Keys counter position", "tablesmolekeys", "Small Keys counter settings", "Small Keys counter", "gSKC", 0, ImGui::GetWindowViewport()->Size.y/3, -1, ImGui::GetWindowViewport()->Size.x/2, 1.0f); + Draw_Table_Dropdown("Rupee counter position", "tablerupeecount", "Rupee counter settings", "Rupee counter", "gRC", -2, ImGui::GetWindowViewport()->Size.y/3, -3, ImGui::GetWindowViewport()->Size.x/2, 1.0f); + Draw_Table_Dropdown("Carrots position", "tableCarrots", "Carrots settings", "Carrots", "gCarrots", 0, ImGui::GetWindowViewport()->Size.y/2, -50, ImGui::GetWindowViewport()->Size.x/2+25, 1.0f); + Draw_Table_Dropdown("Timers position", "tabletimers", "Timers settings", "Timers", "gTimers", 0, ImGui::GetWindowViewport()->Size.y/2, -50, ImGui::GetWindowViewport()->Size.x/2-50, 1.0f); + Draw_Table_Dropdown("Archery Scores position", "tablearchery", "Archery Scores settings", "Archery scores", "gAS", 0, ImGui::GetWindowViewport()->Size.y/2, -50, ImGui::GetWindowViewport()->Size.x/2-50, 1.0f); + Draw_Table_Dropdown("Title cards (Maps) position", "tabletcmaps", "Titlecard maps settings", "Title cards (overworld)", "gTCM", 0, ImGui::GetWindowViewport()->Size.y/2, -50, ImGui::GetWindowViewport()->Size.x/2+10, 1.0f); + Draw_Table_Dropdown("Title cards (Bosses) position", "tabletcbosses", "Title cards (Bosses) settings", "Title cards (Bosses)", "gTCB", 0, ImGui::GetWindowViewport()->Size.y/2, -50, ImGui::GetWindowViewport()->Size.x/2+10, 1.0f); + Draw_Table_Dropdown("In-game Gameplay Timer position", "tablegameplaytimer", "In-game Gameplay Timer settings", "In-game Gameplay Timer", "gIGT", 0, ImGui::GetWindowViewport()->Size.y / 2, -50, ImGui::GetWindowViewport()->Size.x / 2 + 10, 1.0f); if (ImGui::CollapsingHeader("Enemy Health Bar position")) { if (ImGui::BeginTable("enemyhealthbar", 1, FlagsTable)) { ImGui::TableSetupColumn("Enemy Health Bar settings", FlagsCell, TablesCellsWidth); @@ -1483,7 +1350,21 @@ void Draw_Placements(){ } } } - +void Reset_Option_Single(const char* Button_Title, const char* name) { + ImGui::SameLine(); + if (ImGui::Button(Button_Title)) { + CVarClear(name); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } +} +void Reset_Option_Double(const char* Button_Title, const char* name) { + ImGui::SameLine(); + if (ImGui::Button(Button_Title)) { + CVarClear((std::string(name) + ".Value").c_str()); + CVarClear((std::string(name) + ".Changed").c_str()); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } +} void DrawSillyTab() { ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0)); if (CVarGetInteger("gLetItSnow", 0)) { @@ -1491,7 +1372,7 @@ void DrawSillyTab() { LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } } - if (UIWidgets::EnhancementSliderFloat("Link Body Scale: %f", "##Link_BodyScale", "gCosmetics.Link_BodyScale.Value", 0.001f, 0.025f, "", 0.01f, true)) { + if (UIWidgets::EnhancementSliderFloat("Link Body Scale: %.3fx", "##Link_BodyScale", "gCosmetics.Link_BodyScale.Value", 0.001f, 0.025f, "", 0.01f, true)) { CVarSetInteger("gCosmetics.Link_BodyScale.Changed", 1); } ImGui::SameLine(); @@ -1506,70 +1387,31 @@ void DrawSillyTab() { player->actor.scale.z = 0.01f; } } - if (UIWidgets::EnhancementSliderFloat("Link Head Scale: %f", "##Link_HeadScale", "gCosmetics.Link_HeadScale.Value", 0.4f, 4.0f, "", 1.0f, false)) { + if (UIWidgets::EnhancementSliderFloat("Link Head Scale: %.2fx", "##Link_HeadScale", "gCosmetics.Link_HeadScale.Value", 0.4f, 4.0f, "", 1.0f, false)) { CVarSetInteger("gCosmetics.Link_HeadScale.Changed", 1); } - ImGui::SameLine(); - if (ImGui::Button("Reset##Link_HeadScale")) { - CVarClear("gCosmetics.Link_HeadScale.Value"); - CVarClear("gCosmetics.Link_HeadScale.Changed"); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - } + Reset_Option_Double("Reset##Link_HeadScale", "gCosmetics.Link_HeadScale"); if (UIWidgets::EnhancementSliderFloat("Link Sword Scale: %f", "##Link_SwordScale", "gCosmetics.Link_SwordScale.Value", 1.0f, 2.5f, "", 1.0f, false)) { CVarSetInteger("gCosmetics.Link_SwordScale.Changed", 1); } - ImGui::SameLine(); - if (ImGui::Button("Reset##Link_SwordScale")) { - CVarClear("gCosmetics.Link_SwordScale.Value"); - CVarClear("gCosmetics.Link_SwordScale.Changed"); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - } + Reset_Option_Double("Reset##Link_SwordScale", "gCosmetics.Link_SwordScale"); UIWidgets::EnhancementSliderFloat("Bunny Hood Length: %f", "##BunnyHood_EarLength", "gCosmetics.BunnyHood_EarLength", -300.0f, 1000.0f, "", 0.0f, false); - ImGui::SameLine(); - if (ImGui::Button("Reset##BunnyHood_EarLength")) { - CVarClear("gCosmetics.BunnyHood_EarLength"); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - } + Reset_Option_Single("Reset##BunnyHood_EarLength", "gCosmetics.BunnyHood_EarLength"); UIWidgets::EnhancementSliderFloat("Bunny Hood Spread: %f", "##BunnyHood_EarSpread", "gCosmetics.BunnyHood_EarSpread", -300.0f, 500.0f, "", 0.0f, false); - ImGui::SameLine(); - if (ImGui::Button("Reset##BunnyHood_EarSpread")) { - CVarClear("gCosmetics.BunnyHood_EarSpread"); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - } - UIWidgets::EnhancementSliderFloat("Goron Neck Length: %f", "##Goron_NeckLength", "gCosmetics.Goron_NeckLength", 0.0f, 5000.0f, "", 0.0f, false); - ImGui::SameLine(); - if (ImGui::Button("Reset##Goron_NeckLength")) { - CVarClear("gCosmetics.Goron_NeckLength"); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - } + Reset_Option_Single("Reset##BunnyHood_EarSpread", "gCosmetics.BunnyHood_EarSpread"); + UIWidgets::EnhancementSliderFloat("Goron Neck Length: %f", "##Goron_NeckLength", "gCosmetics.Goron_NeckLength", 0.0f, 1000.0f, "", 0.0f, false); + Reset_Option_Single("Reset##Goron_NeckLength", "gCosmetics.Goron_NeckLength"); UIWidgets::EnhancementCheckbox("Unfix Goron Spin", "gUnfixGoronSpin"); UIWidgets::EnhancementSliderFloat("Fairies Size: %f", "##Fairies_Size", "gCosmetics.Fairies_Size", 0.25f, 5.0f, "", 1.0f, false); - ImGui::SameLine(); - if (ImGui::Button("Reset##Fairies_Size")) { - CVarClear("gCosmetics.Fairies_Size"); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - } + Reset_Option_Single("Reset##Fairies_Size", "gCosmetics.Fairies_Size"); UIWidgets::EnhancementSliderFloat("N64 Logo Spin Speed: %f", "##N64Logo_SpinSpeed", "gCosmetics.N64Logo_SpinSpeed", 0.25f, 5.0f, "", 1.0f, false); - ImGui::SameLine(); - if (ImGui::Button("Reset##N64Logo_SpinSpeed")) { - CVarClear("gCosmetics.N64Logo_SpinSpeed"); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - } + Reset_Option_Single("Reset##N64Logo_SpinSpeed", "gCosmetics.N64Logo_SpinSpeed"); UIWidgets::EnhancementSliderFloat("Moon Size: %f", "##Moon_Size", "gCosmetics.Moon_Size", 0.5f, 2.0f, "", 1.0f, false); - ImGui::SameLine(); - if (ImGui::Button("Reset##Moon_Size")) { - CVarClear("gCosmetics.Moon_Size"); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - } + Reset_Option_Single("Reset##Moon_Size", "gCosmetics.Moon_Size"); if (UIWidgets::EnhancementSliderFloat("Kak Windmill Speed: %f", "##Kak_Windmill_Speed", "gCosmetics.Kak_Windmill_Speed.Value", 100.0f, 6000.0f, "", 100.0f, false)) { CVarSetInteger("gCosmetics.Kak_Windmill_Speed.Changed", 1); } - ImGui::SameLine(); - if (ImGui::Button("Reset##Kak_Windmill_Speed")) { - CVarClear("gCosmetics.Kak_Windmill_Speed.Value"); - CVarClear("gCosmetics.Kak_Windmill_Speed.Changed"); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - } + Reset_Option_Double("Reset##Kak_Windmill_Speed", "gCosmetics.Kak_Windmill_Speed"); ImGui::EndDisabled(); } @@ -1592,6 +1434,104 @@ void CopyMultipliedColor(CosmeticOption& cosmeticOptionSrc, CosmeticOption& cosm CVarSetInteger((cosmeticOptionTarget.changedCvar), 1); } +void ToggleRainbow(CosmeticOption& cosmeticOption, bool state) { + if (state) { + CVarSetInteger(cosmeticOption.rainbowCvar, 1); + CVarSetInteger(cosmeticOption.changedCvar, 1); + } else { + CVarClear(cosmeticOption.rainbowCvar); + CVarClear(cosmeticOption.changedCvar); + } +} + +void ApplySideEffects(CosmeticOption& cosmeticOption) { + if (CVarGetInteger("gCosmetics.AdvancedMode", 0)) { + return; + } + + // This bit is kind of experimental, not sure how I feel about it yet, but it allows for + // advanced cosmetic options to be changed based on a non-advanced option. + if (cosmeticOption.label == "Bow Body") { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Equipment_BowTips"), 0.5f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Equipment_BowHandle"), 1.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOption, 4.0f); + } else if (cosmeticOption.label == "Idle Primary") { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Navi_IdleSecondary"), 0.5f); + } else if (cosmeticOption.label == "Enemy Primary") { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Navi_EnemySecondary"), 0.5f); + } else if (cosmeticOption.label == "NPC Primary") { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Navi_NPCSecondary"), 1.0f); + } else if (cosmeticOption.label == "Props Primary") { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Navi_PropsSecondary"), 1.0f); + } else if (cosmeticOption.label == "Ivan Idle Primary") { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Ivan_IdleSecondary"), 0.5f); + } else if (cosmeticOption.label == "Level 1 Secondary") { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("SpinAttack_Level1Primary"), 2.0f); + } else if (cosmeticOption.label == "Level 2 Secondary") { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("SpinAttack_Level2Primary"), 2.0f); + } else if (cosmeticOption.label == "Item Select Color") { + if (CVarGetInteger(cosmeticOption.rainbowCvar, 0)) { + ToggleRainbow(cosmeticOptions.at("Kal_ItemSelB"), true); + ToggleRainbow(cosmeticOptions.at("Kal_ItemSelC"), true); + ToggleRainbow(cosmeticOptions.at("Kal_ItemSelD"), true); + } else { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_ItemSelB"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_ItemSelC"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_ItemSelD"), 1.0f); + } + } else if (cosmeticOption.label == "Equip Select Color") { + if (CVarGetInteger(cosmeticOption.rainbowCvar, 0)) { + ToggleRainbow(cosmeticOptions.at("Kal_EquipSelB"), true); + ToggleRainbow(cosmeticOptions.at("Kal_EquipSelC"), true); + ToggleRainbow(cosmeticOptions.at("Kal_EquipSelD"), true); + } else { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_EquipSelB"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_EquipSelC"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_EquipSelD"), 1.0f); + } + } else if (cosmeticOption.label == "Map Dungeon Color") { + if (CVarGetInteger(cosmeticOption.rainbowCvar, 0)) { + ToggleRainbow(cosmeticOptions.at("Kal_MapSelDunB"), true); + ToggleRainbow(cosmeticOptions.at("Kal_MapSelDunC"), true); + ToggleRainbow(cosmeticOptions.at("Kal_MapSelDunD"), true); + } else { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_MapSelDunB"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_MapSelDunC"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_MapSelDunD"), 1.0f); + } + } else if (cosmeticOption.label == "Quest Status Color") { + if (CVarGetInteger(cosmeticOption.rainbowCvar, 0)) { + ToggleRainbow(cosmeticOptions.at("Kal_QuestStatusB"), true); + ToggleRainbow(cosmeticOptions.at("Kal_QuestStatusC"), true); + ToggleRainbow(cosmeticOptions.at("Kal_QuestStatusD"), true); + } else { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_QuestStatusB"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_QuestStatusC"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_QuestStatusD"), 1.0f); + } + } else if (cosmeticOption.label == "Map Color") { + if (CVarGetInteger(cosmeticOption.rainbowCvar, 0)) { + ToggleRainbow(cosmeticOptions.at("Kal_MapSelectB"), true); + ToggleRainbow(cosmeticOptions.at("Kal_MapSelectC"), true); + ToggleRainbow(cosmeticOptions.at("Kal_MapSelectD"), true); + } else { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_MapSelectB"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_MapSelectC"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_MapSelectD"), 1.0f); + } + } else if (cosmeticOption.label == "Save Color") { + if (CVarGetInteger(cosmeticOption.rainbowCvar, 0)) { + ToggleRainbow(cosmeticOptions.at("Kal_SaveB"), true); + ToggleRainbow(cosmeticOptions.at("Kal_SaveC"), true); + ToggleRainbow(cosmeticOptions.at("Kal_SaveD"), true); + } else { + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_SaveB"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_SaveC"), 2.0f); + CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Kal_SaveD"), 1.0f); + } + } +} + void RandomizeColor(CosmeticOption& cosmeticOption) { Color_RGBA8 newColor; newColor.r = Random(0, 255); @@ -1611,26 +1551,7 @@ void RandomizeColor(CosmeticOption& cosmeticOption) { CVarSetColor(cosmeticOption.cvar, newColor); CVarSetInteger((cosmeticOption.rainbowCvar), 0); CVarSetInteger((cosmeticOption.changedCvar), 1); - - // This bit is kind of experimental, not sure how I feel about it yet, but it allows for - // advanced cosmetic options to be changed based on a non-advanced option. - if (cosmeticOption.label == "Bow Body") { - CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Equipment_BowTips"), 0.5f); - CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Equipment_BowHandle"), 1.0f); - CopyMultipliedColor(cosmeticOption, cosmeticOption, 4.0f); - } else if (cosmeticOption.label == "Idle Primary") { - CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Navi_IdleSecondary"), 0.5f); - } else if (cosmeticOption.label == "Enemy Primary") { - CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Navi_EnemySecondary"), 0.5f); - } else if (cosmeticOption.label == "NPC Primary") { - CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Navi_NPCSecondary"), 1.0f); - } else if (cosmeticOption.label == "Props Primary") { - CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("Navi_PropsSecondary"), 1.0f); - } else if (cosmeticOption.label == "Level 1 Secondary") { - CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("SpinAttack_Level1Primary"), 2.0f); - } else if (cosmeticOption.label == "Level 2 Secondary") { - CopyMultipliedColor(cosmeticOption, cosmeticOptions.at("SpinAttack_Level2Primary"), 2.0f); - } + ApplySideEffects(cosmeticOption); } void ResetColor(CosmeticOption& cosmeticOption) { @@ -1650,7 +1571,7 @@ void ResetColor(CosmeticOption& cosmeticOption) { CVarClear((std::string(cosmeticOption.cvar) + ".A").c_str()); CVarClear((std::string(cosmeticOption.cvar) + ".Type").c_str()); - // This portion should match 1:1 the multiplied colors in `RandomizeColor()` + // This portion should match 1:1 the multiplied colors in `ApplySideEffect()` if (cosmeticOption.label == "Bow Body") { ResetColor(cosmeticOptions.at("Equipment_BowTips")); ResetColor(cosmeticOptions.at("Equipment_BowHandle")); @@ -1666,6 +1587,30 @@ void ResetColor(CosmeticOption& cosmeticOption) { ResetColor(cosmeticOptions.at("SpinAttack_Level1Primary")); } else if (cosmeticOption.label == "Level 2 Secondary") { ResetColor(cosmeticOptions.at("SpinAttack_Level2Primary")); + } else if (cosmeticOption.label == "Item Select Color") { + ResetColor(cosmeticOptions.at("Kal_ItemSelB")); + ResetColor(cosmeticOptions.at("Kal_ItemSelC")); + ResetColor(cosmeticOptions.at("Kal_ItemSelD")); + } else if (cosmeticOption.label == "Equip Select Color") { + ResetColor(cosmeticOptions.at("Kal_EquipSelB")); + ResetColor(cosmeticOptions.at("Kal_EquipSelC")); + ResetColor(cosmeticOptions.at("Kal_EquipSelD")); + } else if (cosmeticOption.label == "Map Dungeon Color") { + ResetColor(cosmeticOptions.at("Kal_MapSelDunB")); + ResetColor(cosmeticOptions.at("Kal_MapSelDunC")); + ResetColor(cosmeticOptions.at("Kal_MapSelDunD")); + } else if (cosmeticOption.label == "Quest Status Color") { + ResetColor(cosmeticOptions.at("Kal_QuestStatusB")); + ResetColor(cosmeticOptions.at("Kal_QuestStatusC")); + ResetColor(cosmeticOptions.at("Kal_QuestStatusD")); + } else if (cosmeticOption.label == "Map Color") { + ResetColor(cosmeticOptions.at("Kal_MapSelectB")); + ResetColor(cosmeticOptions.at("Kal_MapSelectC")); + ResetColor(cosmeticOptions.at("Kal_MapSelectD")); + } else if (cosmeticOption.label == "Save Color") { + ResetColor(cosmeticOptions.at("Kal_SaveB")); + ResetColor(cosmeticOptions.at("Kal_SaveC")); + ResetColor(cosmeticOptions.at("Kal_SaveD")); } } @@ -1686,6 +1631,7 @@ void DrawCosmeticRow(CosmeticOption& cosmeticOption) { CVarSetColor(cosmeticOption.cvar, color); CVarSetInteger((cosmeticOption.rainbowCvar), 0); CVarSetInteger((cosmeticOption.changedCvar), 1); + ApplySideEffects(cosmeticOption); ApplyOrResetCustomGfxPatches(); LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } @@ -1703,6 +1649,7 @@ void DrawCosmeticRow(CosmeticOption& cosmeticOption) { if (ImGui::Checkbox(("Rainbow##" + cosmeticOption.label).c_str(), &isRainbow)) { CVarSetInteger((cosmeticOption.rainbowCvar), isRainbow); CVarSetInteger((cosmeticOption.changedCvar), 1); + ApplySideEffects(cosmeticOption); ApplyOrResetCustomGfxPatches(); LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } @@ -1759,7 +1706,7 @@ static const char* colorSchemes[2] = { }; void CosmeticsEditorWindow::DrawElement() { - ImGui::SetNextWindowSize(ImVec2(480, 520), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(550, 520), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Cosmetics Editor", &mIsVisible)) { ImGui::End(); return; @@ -1793,15 +1740,12 @@ void CosmeticsEditorWindow::DrawElement() { } } UIWidgets::EnhancementCheckbox("Sync Rainbow colors", "gCosmetics.RainbowSync"); - UIWidgets::EnhancementSliderFloat("Rainbow Speed: %f", "##rainbowSpeed", "gCosmetics.RainbowSpeed", 0.03f, 1.0f, "", 0.6f, false); + UIWidgets::EnhancementSliderFloat("Rainbow Speed: %.3f", "##rainbowSpeed", "gCosmetics.RainbowSpeed", 0.03f, 1.0f, "", 0.6f, false, true); + UIWidgets::EnhancementCheckbox("Randomize All on New Scene", "gCosmetics.RandomizeAllOnNewScene"); + UIWidgets::Tooltip("Enables randomizing all unlocked cosmetics when you enter a new scene."); + if (ImGui::Button("Randomize All", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) { - for (auto& [id, cosmeticOption] : cosmeticOptions) { - if (!CVarGetInteger(cosmeticOption.lockedCvar, 0) && (!cosmeticOption.advancedOption || CVarGetInteger("gCosmetics.AdvancedMode", 0))) { - RandomizeColor(cosmeticOption); - } - } - ApplyOrResetCustomGfxPatches(); - LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + CosmeticsEditor_RandomizeAll(); } ImGui::SameLine(); if (ImGui::Button("Reset All", ImVec2(ImGui::GetContentRegionAvail().x, 30.0f))) { @@ -1861,6 +1805,7 @@ void CosmeticsEditorWindow::DrawElement() { if (ImGui::BeginTabItem("World & NPCs")) { DrawCosmeticGroup(GROUP_WORLD); DrawCosmeticGroup(GROUP_NAVI); + DrawCosmeticGroup(GROUP_IVAN); DrawCosmeticGroup(GROUP_NPC); ImGui::EndTabItem(); } @@ -1873,10 +1818,16 @@ void CosmeticsEditorWindow::DrawElement() { DrawCosmeticGroup(GROUP_TITLE); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("HUD Placement")) { Draw_Placements(); ImGui::EndTabItem(); } + + if (ImGui::BeginTabItem("Pause Menu")) { + DrawCosmeticGroup(GROUP_KALEIDO); + ImGui::EndTabItem(); + } ImGui::EndTabBar(); } ImGui::End(); @@ -1894,6 +1845,14 @@ void RegisterOnGameFrameUpdateHook() { }); } +void Cosmetics_RegisterOnSceneInitHook() { + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { + if (CVarGetInteger("gCosmetics.RandomizeAllOnNewScene", 0)) { + CosmeticsEditor_RandomizeAll(); + } + }); +} + void CosmeticsEditorWindow::InitElement() { // Convert the `current color` into the format that the ImGui color picker expects for (auto& [id, cosmeticOption] : cosmeticOptions) { @@ -1911,6 +1870,7 @@ void CosmeticsEditorWindow::InitElement() { RegisterOnLoadGameHook(); RegisterOnGameFrameUpdateHook(); + Cosmetics_RegisterOnSceneInitHook(); } void CosmeticsEditor_RandomizeAll() { diff --git a/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp b/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp index de97f3840..709854c35 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/crowd-control/CrowdControl.cpp b/soh/soh/Enhancements/crowd-control/CrowdControl.cpp index d2e8e03b1..9c0d5cb2d 100644 --- a/soh/soh/Enhancements/crowd-control/CrowdControl.cpp +++ b/soh/soh/Enhancements/crowd-control/CrowdControl.cpp @@ -1,4 +1,4 @@ -#ifdef ENABLE_CROWD_CONTROL +#ifdef ENABLE_REMOTE_CONTROL #include "CrowdControl.h" #include "CrowdControlTypes.h" @@ -17,25 +17,17 @@ extern "C" { extern PlayState* gPlayState; } -void CrowdControl::Init() { - SDLNet_Init(); -} - -void CrowdControl::Shutdown() { - SDLNet_Quit(); -} - void CrowdControl::Enable() { if (isEnabled) { return; } - if (SDLNet_ResolveHost(&ip, "127.0.0.1", 43384) == -1) { - SPDLOG_ERROR("[CrowdControl] SDLNet_ResolveHost: {}", SDLNet_GetError()); - } - isEnabled = true; - ccThreadReceive = std::thread(&CrowdControl::ListenToServer, this); + GameInteractor::Instance->EnableRemoteInteractor(); + GameInteractor::Instance->RegisterRemoteJsonHandler([&](nlohmann::json payload) { + HandleRemoteData(payload); + }); + ccThreadProcess = std::thread(&CrowdControl::ProcessActiveEffects, this); } @@ -45,87 +37,42 @@ void CrowdControl::Disable() { } isEnabled = false; - ccThreadReceive.join(); ccThreadProcess.join(); + GameInteractor::Instance->DisableRemoteInteractor(); } -void CrowdControl::ListenToServer() { - while (isEnabled) { - while (!connected && isEnabled) { - SPDLOG_TRACE("[CrowdControl] Attempting to make connection to server..."); - tcpsock = SDLNet_TCP_Open(&ip); +void CrowdControl::HandleRemoteData(nlohmann::json payload) { + Effect* incomingEffect = ParseMessage(payload); + if (!incomingEffect) { + return; + } - if (tcpsock) { - connected = true; - SPDLOG_TRACE("[CrowdControl] Connection to server established!"); + // If effect is not a timed effect, execute and return result. + if (!incomingEffect->timeRemaining) { + EffectResult result = CrowdControl::ExecuteEffect(incomingEffect); + EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result); + } else { + // If another timed effect is already active that conflicts with the incoming effect. + bool isConflictingEffectActive = false; + for (Effect* effect : activeEffects) { + if (effect != incomingEffect && effect->category == incomingEffect->category && effect->id < incomingEffect->id) { + isConflictingEffectActive = true; + EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, EffectResult::Retry); break; } } - SDLNet_SocketSet socketSet = SDLNet_AllocSocketSet(1); - if (tcpsock) { - SDLNet_TCP_AddSocket(socketSet, tcpsock); - } - - // Listen to socket messages - while (connected && tcpsock && isEnabled) { - // we check first if socket has data, to not block in the TCP_Recv - int socketsReady = SDLNet_CheckSockets(socketSet, 0); - - if (socketsReady == -1) { - SPDLOG_ERROR("[CrowdControl] SDLNet_CheckSockets: {}", SDLNet_GetError()); - break; + if (!isConflictingEffectActive) { + // Check if effect can be applied, if it can't, let CC know. + EffectResult result = CrowdControl::CanApplyEffect(incomingEffect); + if (result == EffectResult::Retry || result == EffectResult::Failure) { + EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result); + return; } - if (socketsReady == 0) { - continue; - } - - int len = SDLNet_TCP_Recv(tcpsock, &received, sizeof(received)); - if (!len || !tcpsock || len == -1) { - SPDLOG_ERROR("[CrowdControl] SDLNet_TCP_Recv: {}", SDLNet_GetError()); - break; - } - - Effect* incomingEffect = ParseMessage(received); - if (!incomingEffect) { - continue; - } - - // If effect is not a timed effect, execute and return result. - if (!incomingEffect->timeRemaining) { - EffectResult result = CrowdControl::ExecuteEffect(incomingEffect); - EmitMessage(tcpsock, incomingEffect->id, incomingEffect->timeRemaining, result); - } else { - // If another timed effect is already active that conflicts with the incoming effect. - bool isConflictingEffectActive = false; - for (Effect* effect : activeEffects) { - if (effect != incomingEffect && effect->category == incomingEffect->category && effect->id < incomingEffect->id) { - isConflictingEffectActive = true; - EmitMessage(tcpsock, incomingEffect->id, incomingEffect->timeRemaining, EffectResult::Retry); - break; - } - } - - if (!isConflictingEffectActive) { - // Check if effect can be applied, if it can't, let CC know. - EffectResult result = CrowdControl::CanApplyEffect(incomingEffect); - if (result == EffectResult::Retry || result == EffectResult::Failure) { - EmitMessage(tcpsock, incomingEffect->id, incomingEffect->timeRemaining, result); - continue; - } - - activeEffectsMutex.lock(); - activeEffects.push_back(incomingEffect); - activeEffectsMutex.unlock(); - } - } - } - - if (connected) { - SDLNet_TCP_Close(tcpsock); - connected = false; - SPDLOG_TRACE("[CrowdControl] Ending Listen thread..."); + activeEffectsMutex.lock(); + activeEffects.push_back(incomingEffect); + activeEffectsMutex.unlock(); } } } @@ -147,13 +94,13 @@ void CrowdControl::ProcessActiveEffects() { if (effect->timeRemaining <= 0) { it = activeEffects.erase(std::remove(activeEffects.begin(), activeEffects.end(), effect), activeEffects.end()); - GameInteractor::RemoveEffect(effect->giEffect); + GameInteractor::RemoveEffect(dynamic_cast(effect->giEffect)); delete effect; } else { // If we have a success after previously being paused, tell CC to resume timer. if (effect->isPaused) { effect->isPaused = false; - EmitMessage(tcpsock, effect->id, effect->timeRemaining, EffectResult::Resumed); + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Resumed); // If not paused before, subtract time from the timer and send a Success event if // the result is different from the last time this was ran. // Timed events are put on a thread that runs once per second. @@ -161,7 +108,7 @@ void CrowdControl::ProcessActiveEffects() { effect->timeRemaining -= 1000; if (result != effect->lastExecutionResult) { effect->lastExecutionResult = result; - EmitMessage(tcpsock, effect->id, effect->timeRemaining, EffectResult::Success); + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Success); } } it++; @@ -169,7 +116,7 @@ void CrowdControl::ProcessActiveEffects() { } else { // Timed effects only do Success or Retry if (!effect->isPaused && effect->timeRemaining > 0) { effect->isPaused = true; - EmitMessage(tcpsock, effect->id, effect->timeRemaining, EffectResult::Paused); + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Paused); } it++; } @@ -182,7 +129,7 @@ void CrowdControl::ProcessActiveEffects() { SPDLOG_TRACE("[CrowdControl] Ending Process thread..."); } -void CrowdControl::EmitMessage(TCPsocket socket, uint32_t eventId, long timeRemaining, EffectResult status) { +void CrowdControl::EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status) { nlohmann::json payload; payload["id"] = eventId; @@ -190,8 +137,9 @@ void CrowdControl::EmitMessage(TCPsocket socket, uint32_t eventId, long timeRema payload["timeRemaining"] = timeRemaining; payload["status"] = status; - std::string jsonPayload = payload.dump(); - SDLNet_TCP_Send(socket, jsonPayload.c_str(), jsonPayload.size() + 1); + SPDLOG_INFO("[CrowdControl] Sending payload:\n{}", payload.dump()); + + GameInteractor::Instance->TransmitJsonToRemote(payload); } CrowdControl::EffectResult CrowdControl::ExecuteEffect(Effect* effect) { @@ -229,13 +177,14 @@ CrowdControl::EffectResult CrowdControl::TranslateGiEnum(GameInteractionEffectQu return result; } -CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { - nlohmann::json dataReceived = nlohmann::json::parse(payload, nullptr, false); - if (dataReceived.is_discarded()) { - SPDLOG_ERROR("Error parsing JSON"); +CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) { + if (!dataReceived.contains("id") || !dataReceived.contains("type")) { + SPDLOG_ERROR("[CrowdControl] Invalid payload received:\n{}", dataReceived); return nullptr; } + SPDLOG_INFO("[CrowdControl] Received payload:\n{}", dataReceived.dump()); + Effect* effect = new Effect(); effect->lastExecutionResult = EffectResult::Initiate; effect->id = dataReceived["id"]; @@ -333,13 +282,13 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { effect->category = kEffectCatDamageTaken; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyDefenseModifier(); - effect->giEffect->parameters[0] = 2; + dynamic_cast(effect->giEffect)->parameters[0] = 2; break; case kEffectTakeDoubleDamage: effect->category = kEffectCatDamageTaken; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyDefenseModifier(); - effect->giEffect->parameters[0] = -2; + dynamic_cast(effect->giEffect)->parameters[0] = -2; break; case kEffectOneHitKo: effect->category = kEffectCatDamageTaken; @@ -356,37 +305,37 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { effect->category = kEffectCatSpeed; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyRunSpeedModifier(); - effect->giEffect->parameters[0] = 2; + dynamic_cast(effect->giEffect)->parameters[0] = 2; break; case kEffectDecreaseSpeed: effect->category = kEffectCatSpeed; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyRunSpeedModifier(); - effect->giEffect->parameters[0] = -2; + dynamic_cast(effect->giEffect)->parameters[0] = -2; break; case kEffectLowGravity: effect->category = kEffectCatGravity; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyGravity(); - effect->giEffect->parameters[0] = GI_GRAVITY_LEVEL_LIGHT; + dynamic_cast(effect->giEffect)->parameters[0] = GI_GRAVITY_LEVEL_LIGHT; break; case kEffectHighGravity: effect->category = kEffectCatGravity; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyGravity(); - effect->giEffect->parameters[0] = GI_GRAVITY_LEVEL_HEAVY; + dynamic_cast(effect->giEffect)->parameters[0] = GI_GRAVITY_LEVEL_HEAVY; break; case kEffectForceIronBoots: effect->category = kEffectCatBoots; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ForceEquipBoots(); - effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_IRON; + dynamic_cast(effect->giEffect)->parameters[0] = EQUIP_VALUE_BOOTS_IRON; break; case kEffectForceHoverBoots: effect->category = kEffectCatBoots; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ForceEquipBoots(); - effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_HOVER; + dynamic_cast(effect->giEffect)->parameters[0] = EQUIP_VALUE_BOOTS_HOVER; break; case kEffectSlipperyFloor: effect->category = kEffectCatSlipperyFloor; @@ -412,23 +361,23 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { // Hurt or Heal Link case kEffectEmptyHeart: effect->giEffect = new GameInteractionEffect::ModifyHealth(); - effect->giEffect->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; break; case kEffectFillHeart: effect->giEffect = new GameInteractionEffect::ModifyHealth(); - effect->giEffect->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; break; case kEffectKnockbackLinkWeak: effect->giEffect = new GameInteractionEffect::KnockbackPlayer(); - effect->giEffect->parameters[0] = 1; + dynamic_cast(effect->giEffect)->parameters[0] = 1; break; case kEffectKnockbackLinkStrong: effect->giEffect = new GameInteractionEffect::KnockbackPlayer(); - effect->giEffect->parameters[0] = 3; + dynamic_cast(effect->giEffect)->parameters[0] = 3; break; case kEffectKnockbackLinkMega: effect->giEffect = new GameInteractionEffect::KnockbackPlayer(); - effect->giEffect->parameters[0] = 6; + dynamic_cast(effect->giEffect)->parameters[0] = 6; break; case kEffectBurnLink: effect->giEffect = new GameInteractionEffect::BurnPlayer(); @@ -441,109 +390,109 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { break; case kEffectKillLink: effect->giEffect = new GameInteractionEffect::SetPlayerHealth(); - effect->giEffect->parameters[0] = 0; + dynamic_cast(effect->giEffect)->parameters[0] = 0; break; // Give Items and Consumables case kEffectAddHeartContainer: effect->giEffect = new GameInteractionEffect::ModifyHeartContainers(); - effect->giEffect->parameters[0] = 1; + dynamic_cast(effect->giEffect)->parameters[0] = 1; break; case kEffectFillMagic: effect->giEffect = new GameInteractionEffect::FillMagic(); break; case kEffectAddRupees: effect->giEffect = new GameInteractionEffect::ModifyRupees(); - effect->giEffect->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; break; case kEffectGiveDekuShield: effect->giEffect = new GameInteractionEffect::GiveOrTakeShield(); - effect->giEffect->parameters[0] = ITEM_SHIELD_DEKU; + dynamic_cast(effect->giEffect)->parameters[0] = ITEM_SHIELD_DEKU; break; case kEffectGiveHylianShield: effect->giEffect = new GameInteractionEffect::GiveOrTakeShield(); - effect->giEffect->parameters[0] = ITEM_SHIELD_HYLIAN; + dynamic_cast(effect->giEffect)->parameters[0] = ITEM_SHIELD_HYLIAN; break; case kEffectRefillSticks: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_STICK; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_STICK; break; case kEffectRefillNuts: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_NUT; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_NUT; break; case kEffectRefillBombs: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_BOMB; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOMB; break; case kEffectRefillSeeds: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_SLINGSHOT; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_SLINGSHOT; break; case kEffectRefillArrows: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_BOW; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOW; break; case kEffectRefillBombchus: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_BOMBCHU; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOMBCHU; break; // Take Items and Consumables case kEffectRemoveHeartContainer: effect->giEffect = new GameInteractionEffect::ModifyHeartContainers(); - effect->giEffect->parameters[0] = -1; + dynamic_cast(effect->giEffect)->parameters[0] = -1; break; case kEffectEmptyMagic: effect->giEffect = new GameInteractionEffect::EmptyMagic(); break; case kEffectRemoveRupees: effect->giEffect = new GameInteractionEffect::ModifyRupees(); - effect->giEffect->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; break; case kEffectTakeDekuShield: effect->giEffect = new GameInteractionEffect::GiveOrTakeShield(); - effect->giEffect->parameters[0] = -ITEM_SHIELD_DEKU; + dynamic_cast(effect->giEffect)->parameters[0] = -ITEM_SHIELD_DEKU; break; case kEffectTakeHylianShield: effect->giEffect = new GameInteractionEffect::GiveOrTakeShield(); - effect->giEffect->parameters[0] = -ITEM_SHIELD_HYLIAN; + dynamic_cast(effect->giEffect)->parameters[0] = -ITEM_SHIELD_HYLIAN; break; case kEffectTakeSticks: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_STICK; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_STICK; break; case kEffectTakeNuts: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_NUT; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_NUT; break; case kEffectTakeBombs: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_BOMB; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOMB; break; case kEffectTakeSeeds: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_SLINGSHOT; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_SLINGSHOT; break; case kEffectTakeArrows: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_BOW; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOW; break; case kEffectTakeBombchus: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_BOMBCHU; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOMBCHU; break; // Link Size Modifiers @@ -551,25 +500,25 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { effect->category = kEffectCatLinkSize; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); - effect->giEffect->parameters[0] = GI_LINK_SIZE_GIANT; + dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_GIANT; break; case kEffectMinishLink: effect->category = kEffectCatLinkSize; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); - effect->giEffect->parameters[0] = GI_LINK_SIZE_MINISH; + dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_MINISH; break; case kEffectPaperLink: effect->category = kEffectCatLinkSize; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); - effect->giEffect->parameters[0] = GI_LINK_SIZE_PAPER; + dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_PAPER; break; case kEffectSquishedLink: effect->category = kEffectCatLinkSize; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); - effect->giEffect->parameters[0] = GI_LINK_SIZE_SQUISHED; + dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_SQUISHED; break; case kEffectInvisibleLink: effect->category = kEffectCatLinkSize; @@ -585,11 +534,11 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { break; case kEffectSetTimeToDawn: effect->giEffect = new GameInteractionEffect::SetTimeOfDay(); - effect->giEffect->parameters[0] = GI_TIMEOFDAY_DAWN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TIMEOFDAY_DAWN; break; case kEffectSetTimeToDusk: effect->giEffect = new GameInteractionEffect::SetTimeOfDay(); - effect->giEffect->parameters[0] = GI_TIMEOFDAY_DUSK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TIMEOFDAY_DUSK; break; // Visual Effects @@ -632,186 +581,186 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { effect->category = kEffectCatRandomButtons; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::PressRandomButton(); - effect->giEffect->parameters[0] = 30; + dynamic_cast(effect->giEffect)->parameters[0] = 30; break; case kEffectClearCbuttons: effect->giEffect = new GameInteractionEffect::ClearAssignedButtons(); - effect->giEffect->parameters[0] = GI_BUTTONS_CBUTTONS; + dynamic_cast(effect->giEffect)->parameters[0] = GI_BUTTONS_CBUTTONS; break; case kEffectClearDpad: effect->giEffect = new GameInteractionEffect::ClearAssignedButtons(); - effect->giEffect->parameters[0] = GI_BUTTONS_DPAD; + dynamic_cast(effect->giEffect)->parameters[0] = GI_BUTTONS_DPAD; break; // Teleport Player case kEffectTpLinksHouse: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_LINKSHOUSE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_LINKSHOUSE; break; case kEffectTpMinuet: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_MINUET; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_MINUET; break; case kEffectTpBolero: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_BOLERO; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_BOLERO; break; case kEffectTpSerenade: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_SERENADE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_SERENADE; break; case kEffectTpRequiem: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_REQUIEM; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_REQUIEM; break; case kEffectTpNocturne: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_NOCTURNE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_NOCTURNE; break; case kEffectTpPrelude: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_PRELUDE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_PRELUDE; break; // Tunic Color (Bidding War) case kEffectTunicRed: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_RED; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_RED; break; case kEffectTunicGreen: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_GREEN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_GREEN; break; case kEffectTunicBlue: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_BLUE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLUE; break; case kEffectTunicOrange: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_ORANGE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE; break; case kEffectTunicYellow: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_YELLOW; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW; break; case kEffectTunicPurple: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_PURPLE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE; break; case kEffectTunicPink: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_PINK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PINK; break; case kEffectTunicBrown: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_BROWN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BROWN; break; case kEffectTunicBlack: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_BLACK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLACK; break; // Navi Color (Bidding War) case kEffectNaviRed: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_RED; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_RED; break; case kEffectNaviGreen: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_GREEN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_GREEN; break; case kEffectNaviBlue: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_BLUE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLUE; break; case kEffectNaviOrange: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_ORANGE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE; break; case kEffectNaviYellow: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_YELLOW; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW; break; case kEffectNaviPurple: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_PURPLE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE; break; case kEffectNaviPink: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_PINK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PINK; break; case kEffectNaviBrown: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_BROWN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BROWN; break; case kEffectNaviBlack: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_BLACK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLACK; break; // Link's Hair Color (Bidding War) case kEffectHairRed: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_RED; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_RED; break; case kEffectHairGreen: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_GREEN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_GREEN; break; case kEffectHairBlue: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_BLUE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLUE; break; case kEffectHairOrange: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_ORANGE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE; break; case kEffectHairYellow: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_YELLOW; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW; break; case kEffectHairPurple: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_PURPLE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE; break; case kEffectHairPink: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_PINK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PINK; break; case kEffectHairBrown: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_BROWN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BROWN; break; case kEffectHairBlack: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_BLACK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLACK; break; default: diff --git a/soh/soh/Enhancements/crowd-control/CrowdControl.h b/soh/soh/Enhancements/crowd-control/CrowdControl.h index 672e6d361..bb06cc5b1 100644 --- a/soh/soh/Enhancements/crowd-control/CrowdControl.h +++ b/soh/soh/Enhancements/crowd-control/CrowdControl.h @@ -1,4 +1,4 @@ -#ifdef ENABLE_CROWD_CONTROL +#ifdef ENABLE_REMOTE_CONTROL #ifndef _CROWDCONTROL_C #define _CROWDCONTROL_C @@ -73,33 +73,24 @@ class CrowdControl { EffectResult lastExecutionResult; } Effect; - std::thread ccThreadReceive; std::thread ccThreadProcess; - TCPsocket tcpsock; - IPaddress ip; - bool isEnabled; - bool connected; - - char received[512]; std::vector activeEffects; std::mutex activeEffectsMutex; - void ListenToServer(); + void HandleRemoteData(nlohmann::json payload); void ProcessActiveEffects(); - void EmitMessage(TCPsocket socket, uint32_t eventId, long timeRemaining, EffectResult status); - Effect* ParseMessage(char payload[512]); + void EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status); + Effect* ParseMessage(nlohmann::json payload); EffectResult ExecuteEffect(Effect* effect); EffectResult CanApplyEffect(Effect *effect); EffectResult TranslateGiEnum(GameInteractionEffectQueryResult giResult); public: static CrowdControl* Instance; - void Init(); - void Shutdown(); void Enable(); void Disable(); }; diff --git a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h index 916e1a7f8..9e0d7ec0d 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h @@ -38,6 +38,7 @@ typedef enum { TEXT_CARPET_SALESMAN_1 = 0x6077, TEXT_CARPET_SALESMAN_2 = 0x6078, TEXT_MARKET_GUARD_NIGHT = 0x7003, + TEXT_FISHERMAN_LEAVE = 0x409E, TEXT_SHEIK_NEED_HOOK = 0x700F, TEXT_SHEIK_HAVE_HOOK = 0x7010, TEXT_SCRUB_RANDOM = 0x9000, @@ -51,8 +52,25 @@ typedef enum { TEXT_WARP_NOCTURNE_OF_SHADOW = 0x891, TEXT_WARP_PRELUDE_OF_LIGHT = 0x892, TEXT_WARP_RANDOM_REPLACED_TEXT = 0x9200, + TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW = 0x9210, TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN = 0x346, // 0x3yy for cuttable sign range TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI = 0x1B3, // 0x1yy for Navi msg range + TEXT_SARIAS_SONG_CHANNELING_POWER = 0x016D, + TEXT_BEAN_SALESMAN_BUY_FOR_10 = 0x405E, + TEXT_BEAN_SALESMAN_BUY_FOR_20 = 0x405F, + TEXT_BEAN_SALESMAN_BUY_FOR_30 = 0x4060, + TEXT_BEAN_SALESMAN_BUY_FOR_40 = 0x4061, + TEXT_BEAN_SALESMAN_BUY_FOR_50 = 0x4062, + TEXT_BEAN_SALESMAN_BUY_FOR_60 = 0x4063, + TEXT_BEAN_SALESMAN_BUY_FOR_70 = 0x4064, + TEXT_BEAN_SALESMAN_BUY_FOR_80 = 0x4065, + TEXT_BEAN_SALESMAN_BUY_FOR_90 = 0x4066, + TEXT_BEAN_SALESMAN_BUY_FOR_100 = 0x4067, + TEXT_BEAN_SALESMAN_OH_WELL = 0x4068, + TEXT_BEAN_SALESMAN_NOT_ENOUGH_MONEY = 0x4069, + TEXT_BEAN_SALESMAN_SET_A_BEAN_TO_C = 0x406A, + TEXT_BEAN_SALESMAN_SOLD_OUT = 0x406B, + TEXT_BEAN_SALESMAN_WANT_TO_PLANT = 0x406C, } TextIDs; #ifdef __cplusplus diff --git a/soh/soh/Enhancements/debugconsole.cpp b/soh/soh/Enhancements/debugconsole.cpp index 6bc441d03..2c910a067 100644 --- a/soh/soh/Enhancements/debugconsole.cpp +++ b/soh/soh/Enhancements/debugconsole.cpp @@ -17,6 +17,10 @@ #include #include +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include #include #undef PATH_HACK #undef Path @@ -99,7 +103,7 @@ static bool ActorSpawnHandler(std::shared_ptr Console, const std:: static bool KillPlayerHandler(std::shared_ptr Console, const std::vector&, std::string* output) { GameInteractionEffectBase* effect = new GameInteractionEffect::SetPlayerHealth(); - effect->parameters[0] = 0; + dynamic_cast(effect)->parameters[0] = 0; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { INFO_MESSAGE("[SOH] You've met with a terrible fate, haven't you?"); @@ -130,7 +134,7 @@ static bool SetPlayerHealthHandler(std::shared_ptr Console, const } GameInteractionEffectBase* effect = new GameInteractionEffect::SetPlayerHealth(); - effect->parameters[0] = health; + dynamic_cast(effect)->parameters[0] = health; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { INFO_MESSAGE("[SOH] Player health updated to %d", health); @@ -247,8 +251,8 @@ static bool AddAmmoHandler(std::shared_ptr Console, const std::vec } GameInteractionEffectBase* effect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->parameters[0] = amount; - effect->parameters[1] = it->second; + dynamic_cast(effect)->parameters[0] = amount; + dynamic_cast(effect)->parameters[1] = it->second; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -287,8 +291,8 @@ static bool TakeAmmoHandler(std::shared_ptr Console, const std::ve } GameInteractionEffectBase* effect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->parameters[0] = -amount; - effect->parameters[1] = it->second; + dynamic_cast(effect)->parameters[0] = -amount; + dynamic_cast(effect)->parameters[1] = it->second; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -577,7 +581,7 @@ static bool InvisibleHandler(std::shared_ptr Console, const std::v return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::InvisibleLink(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::InvisibleLink(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -604,8 +608,8 @@ static bool GiantLinkHandler(std::shared_ptr Console, const std::v return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyLinkSize(); - effect->parameters[0] = GI_LINK_SIZE_GIANT; + RemovableGameInteractionEffect* effect = new GameInteractionEffect::ModifyLinkSize(); + dynamic_cast(effect)->parameters[0] = GI_LINK_SIZE_GIANT; GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -632,8 +636,8 @@ static bool MinishLinkHandler(std::shared_ptr Console, const std:: return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyLinkSize(); - effect->parameters[0] = GI_LINK_SIZE_MINISH; + RemovableGameInteractionEffect* effect = new GameInteractionEffect::ModifyLinkSize(); + dynamic_cast(effect)->parameters[0] = GI_LINK_SIZE_MINISH; GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -666,7 +670,7 @@ static bool AddHeartContainerHandler(std::shared_ptr Console, cons } GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyHeartContainers(); - effect->parameters[0] = hearts; + dynamic_cast(effect)->parameters[0] = hearts; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { INFO_MESSAGE("[SOH] Added %d heart containers", hearts); @@ -697,7 +701,7 @@ static bool RemoveHeartContainerHandler(std::shared_ptr Console, c } GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyHeartContainers(); - effect->parameters[0] = -hearts; + dynamic_cast(effect)->parameters[0] = -hearts; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { INFO_MESSAGE("[SOH] Removed %d heart containers", hearts); @@ -717,7 +721,7 @@ static bool GravityHandler(std::shared_ptr Console, const std::vec GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyGravity(); try { - effect->parameters[0] = LUS::Math::clamp(std::stoi(args[1], nullptr, 10), GI_GRAVITY_LEVEL_LIGHT, GI_GRAVITY_LEVEL_HEAVY); + dynamic_cast(effect)->parameters[0] = LUS::Math::clamp(std::stoi(args[1], nullptr, 10), GI_GRAVITY_LEVEL_LIGHT, GI_GRAVITY_LEVEL_HEAVY); } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Gravity value must be a number."); return 1; @@ -747,7 +751,7 @@ static bool NoUIHandler(std::shared_ptr Console, const std::vector return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::NoUI(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::NoUI(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -782,7 +786,7 @@ static bool DefenseModifierHandler(std::shared_ptr Console, const GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyDefenseModifier(); try { - effect->parameters[0] = std::stoi(args[1], nullptr, 10); + dynamic_cast(effect)->parameters[0] = std::stoi(args[1], nullptr, 10); } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Defense modifier value must be a number."); return 1; @@ -790,7 +794,7 @@ static bool DefenseModifierHandler(std::shared_ptr Console, const GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { - INFO_MESSAGE("[SOH] Defense modifier set to %d", effect->parameters[0]); + INFO_MESSAGE("[SOH] Defense modifier set to %d", dynamic_cast(effect)->parameters[0]); return 0; } else { INFO_MESSAGE("[SOH] Command failed: Could not set defense modifier."); @@ -812,7 +816,7 @@ static bool DamageHandler(std::shared_ptr Console, const std::vect return 1; } - effect->parameters[0] = -value; + dynamic_cast(effect)->parameters[0] = -value; } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Damage value must be a number."); return 1; @@ -842,7 +846,7 @@ static bool HealHandler(std::shared_ptr Console, const std::vector return 1; } - effect->parameters[0] = value; + dynamic_cast(effect)->parameters[0] = value; } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Damage value must be a number."); return 1; @@ -898,7 +902,7 @@ static bool NoZHandler(std::shared_ptr Console, const std::vector< return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::DisableZTargeting(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::DisableZTargeting(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -926,7 +930,7 @@ static bool OneHitKOHandler(std::shared_ptr Console, const std::ve return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::OneHitKO(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::OneHitKO(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -954,7 +958,7 @@ static bool PacifistHandler(std::shared_ptr Console, const std::ve return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::PacifistMode(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::PacifistMode(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -982,8 +986,8 @@ static bool PaperLinkHandler(std::shared_ptr Console, const std::v return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyLinkSize(); - effect->parameters[0] = GI_LINK_SIZE_PAPER; + RemovableGameInteractionEffect* effect = new GameInteractionEffect::ModifyLinkSize(); + dynamic_cast(effect)->parameters[0] = GI_LINK_SIZE_PAPER; GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -1011,7 +1015,7 @@ static bool RainstormHandler(std::shared_ptr Console, const std::v return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::WeatherRainstorm(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::WeatherRainstorm(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -1039,7 +1043,7 @@ static bool ReverseControlsHandler(std::shared_ptr Console, const return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::ReverseControls(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::ReverseControls(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -1062,7 +1066,7 @@ static bool UpdateRupeesHandler(std::shared_ptr Console, const std GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyRupees(); try { - effect->parameters[0] = std::stoi(args[1], nullptr, 10); + dynamic_cast(effect)->parameters[0] = std::stoi(args[1], nullptr, 10); } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Rupee value must be a number."); return 1; @@ -1086,7 +1090,7 @@ static bool SpeedModifierHandler(std::shared_ptr Console, const st GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyRunSpeedModifier(); try { - effect->parameters[0] = std::stoi(args[1], nullptr, 10); + dynamic_cast(effect)->parameters[0] = std::stoi(args[1], nullptr, 10); } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Speed modifier value must be a number."); return 1; @@ -1121,7 +1125,7 @@ static bool BootsHandler(std::shared_ptr Console, const std::vecto } GameInteractionEffectBase* effect = new GameInteractionEffect::ForceEquipBoots(); - effect->parameters[0] = it->second; + dynamic_cast(effect)->parameters[0] = it->second; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -1152,7 +1156,7 @@ static bool GiveShieldHandler(std::shared_ptr Console, const std:: } GameInteractionEffectBase* effect = new GameInteractionEffect::GiveOrTakeShield(); - effect->parameters[0] = it->second; + dynamic_cast(effect)->parameters[0] = it->second; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -1177,7 +1181,7 @@ static bool TakeShieldHandler(std::shared_ptr Console, const std:: } GameInteractionEffectBase* effect = new GameInteractionEffect::GiveOrTakeShield(); - effect->parameters[0] = it->second * -1; + dynamic_cast(effect)->parameters[0] = it->second * -1; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -1203,7 +1207,7 @@ static bool KnockbackHandler(std::shared_ptr Console, const std::v return 1; } - effect->parameters[0] = value; + dynamic_cast(effect)->parameters[0] = value; } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Knockback value must be a number."); return 1; diff --git a/soh/soh/Enhancements/debugger/MessageViewer.cpp b/soh/soh/Enhancements/debugger/MessageViewer.cpp new file mode 100644 index 000000000..51fe3c0b9 --- /dev/null +++ b/soh/soh/Enhancements/debugger/MessageViewer.cpp @@ -0,0 +1,271 @@ +#include "MessageViewer.h" + +#include +#include + +#include "../custom-message/CustomMessageManager.h" +#include "functions.h" +#include "macros.h" +#include "message_data_static.h" +#include "variables.h" +#include "soh/util.h" + +extern "C" u8 sMessageHasSetSfx; + +void MessageViewer::InitElement() { + CustomMessageManager::Instance->AddCustomMessageTable(TABLE_ID); + mTableIdBuf = static_cast(calloc(MAX_STRING_SIZE, sizeof(char))); + mTextIdBuf = static_cast(calloc(MAX_STRING_SIZE, sizeof(char))); + mCustomMessageBuf = static_cast(calloc(MAX_STRING_SIZE, sizeof(char))); +} + +void MessageViewer::DrawElement() { + ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Custom Message Debugger", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { + ImGui::End(); + return; + } + ImGui::Text("Table ID"); + ImGui::SameLine(); + ImGui::InputText("##TableID", mTableIdBuf, MAX_STRING_SIZE, ImGuiInputTextFlags_CallbackCharFilter, UIWidgets::TextFilters::FilterAlphaNum); + UIWidgets::InsertHelpHoverText("Leave blank for vanilla table"); + ImGui::Text("Text ID"); + ImGui::SameLine(); + switch (mTextIdBase) { + case DECIMAL: + ImGui::InputText("##TextID", mTextIdBuf, MAX_STRING_SIZE, ImGuiInputTextFlags_CharsDecimal); + UIWidgets::InsertHelpHoverText("Decimal Text ID of the message to load. Decimal digits only (0-9)."); + break; + case HEXADECIMAL: + default: + ImGui::InputText("##TextID", mTextIdBuf, MAX_STRING_SIZE, ImGuiInputTextFlags_CharsHexadecimal); + UIWidgets::InsertHelpHoverText("Hexadecimal Text ID of the message to load. Hexadecimal digits only (0-9/A-F)."); + break; + } + if (ImGui::RadioButton("Hexadecimal", &mTextIdBase, HEXADECIMAL)) { + memset(mTextIdBuf, 0, sizeof(char) * MAX_STRING_SIZE); + } + ImGui::SameLine(); + if (ImGui::RadioButton("Decimal", &mTextIdBase, DECIMAL)) { + memset(mTextIdBuf, 0, sizeof(char) * MAX_STRING_SIZE); + } + ImGui::Text("Language"); + ImGui::SameLine(); + if (ImGui::BeginCombo("##Language", mLanguages[mLanguage])) { + // ReSharper disable CppDFAUnreachableCode + for (size_t i = 0; i < mLanguages.size(); i++) { + if (strlen(mLanguages[i]) > 0) { + if (ImGui::Selectable(mLanguages[i], i == mLanguage)) { + mLanguage = i; + } + } + } + ImGui::EndCombo(); + } + UIWidgets::InsertHelpHoverText("Which language to load from the selected text ID"); + if (ImGui::Button("Display Message##ExistingMessage")) { + mDisplayExistingMessageClicked = true; + } + ImGui::Text("Custom Message"); + UIWidgets::InsertHelpHoverText("Enter a string using Custom Message Syntax to preview it in-game. " + "Any newline (\\n) characters inserted by the Enter key will be stripped " + "from the output."); + ImGui::InputTextMultiline("##CustomMessage", mCustomMessageBuf, MAX_STRING_SIZE); + if (ImGui::Button("Display Message##CustomMessage")) { + mDisplayCustomMessageClicked = true; + } + ImGui::End(); + // ReSharper restore CppDFAUnreachableCode +} + +void MessageViewer::UpdateElement() { + if (mDisplayExistingMessageClicked) { + mTableId = std::string(mTableIdBuf); + switch (mTextIdBase) { + case DECIMAL: + mTextId = std::stoi(std::string(mTextIdBuf), nullptr, 10); + break; + case HEXADECIMAL: + default: + mTextId = std::stoi(std::string(mTextIdBuf), nullptr, 16); + break; + } + DisplayExistingMessage(); + mDisplayExistingMessageClicked = false; + } + if (mDisplayCustomMessageClicked) { + mCustomMessageString = std::string(mCustomMessageBuf); + std::erase(mCustomMessageString, '\n'); + DisplayCustomMessage(); + mDisplayCustomMessageClicked = false; + } +} + +void MessageViewer::DisplayExistingMessage() const { + MessageDebug_StartTextBox(mTableId.c_str(), mTextId, mLanguage); +} + +void MessageViewer::DisplayCustomMessage() const { + MessageDebug_DisplayCustomMessage(mCustomMessageString.c_str()); +} + +extern "C" MessageTableEntry* sNesMessageEntryTablePtr; +extern "C" MessageTableEntry* sGerMessageEntryTablePtr; +extern "C" MessageTableEntry* sFraMessageEntryTablePtr; +extern "C" MessageTableEntry* sStaffMessageEntryTablePtr; + +void FindMessage(PlayState* play, const uint16_t textId, const uint8_t language) { + const char* foundSeg; + const char* nextSeg; + MessageTableEntry* messageTableEntry = sNesMessageEntryTablePtr; + Font* font; + u16 bufferId = textId; + // Use the better owl message if better owl is enabled + if (CVarGetInteger("gBetterOwl", 0) != 0 && (bufferId == 0x2066 || bufferId == 0x607B || + bufferId == 0x10C2 || bufferId == 0x10C6 || bufferId == 0x206A)) + { + bufferId = 0x71B3; + } + + if (language == LANGUAGE_GER) + messageTableEntry = sGerMessageEntryTablePtr; + else if (language == LANGUAGE_FRA) + messageTableEntry = sFraMessageEntryTablePtr; + + // If PAL languages are not present in the OTR file, default to English + if (messageTableEntry == nullptr) + messageTableEntry = sNesMessageEntryTablePtr; + + const char* seg = messageTableEntry->segment; + + while (messageTableEntry->textId != 0xFFFF) { + font = &play->msgCtx.font; + + if (messageTableEntry->textId == bufferId) { + foundSeg = messageTableEntry->segment; + font->charTexBuf[0] = messageTableEntry->typePos; + + nextSeg = messageTableEntry->segment; + font->msgOffset = reinterpret_cast(messageTableEntry->segment); + font->msgLength = messageTableEntry->msgSize; + return; + } + messageTableEntry++; + } + + font = &play->msgCtx.font; + messageTableEntry = sNesMessageEntryTablePtr; + + foundSeg = messageTableEntry->segment; + font->charTexBuf[0] = messageTableEntry->typePos; + messageTableEntry++; + nextSeg = messageTableEntry->segment; + font->msgOffset = foundSeg - seg; + font->msgLength = nextSeg - foundSeg; +} + +static const char* msgStaticTbl[] = +{ + gDefaultMessageBackgroundTex, + gSignMessageBackgroundTex, + gNoteStaffMessageBackgroundTex, + gFadingMessageBackgroundTex, + gMessageContinueTriangleTex, + gMessageEndSquareTex, + gMessageArrowTex +}; + +void MessageDebug_StartTextBox(const char* tableId, uint16_t textId, uint8_t language) { + PlayState* play = gPlayState; + static int16_t messageStaticIndices[] = { 0, 1, 3, 2 }; + const auto player = GET_PLAYER(gPlayState); + player->actor.flags |= ACTOR_FLAG_PLAYER_TALKED_TO; + MessageContext* msgCtx = &play->msgCtx; + msgCtx->ocarinaAction = 0xFFFF; + Font* font = &msgCtx->font; + sMessageHasSetSfx = 0; + for (u32 i = 0; i < FONT_CHAR_TEX_SIZE * 120; i += FONT_CHAR_TEX_SIZE) { + if (&font->charTexBuf[i] != nullptr) { + gSPInvalidateTexCache(play->state.gfxCtx->polyOpa.p++, reinterpret_cast(&font->charTexBuf[i])); + } + } + R_TEXT_CHAR_SCALE = 75; + R_TEXT_LINE_SPACING = 12; + R_TEXT_INIT_XPOS = 65; + char* buffer = font->msgBuf; + msgCtx->textId = textId; + if (strlen(tableId) == 0) { + FindMessage(play, textId, language); + msgCtx->msgLength = static_cast(font->msgLength); + const uintptr_t src = font->msgOffset; + memcpy(font->msgBuf, reinterpret_cast(src), font->msgLength); + } else { + constexpr int maxBufferSize = sizeof(font->msgBuf); + const CustomMessage messageEntry = CustomMessageManager::Instance->RetrieveMessage(tableId, textId); + font->charTexBuf[0] = (messageEntry.GetTextBoxType() << 4) | messageEntry.GetTextBoxPosition(); + switch (language) { + case LANGUAGE_FRA: + font->msgLength = SohUtils::CopyStringToCharBuffer(buffer, messageEntry.GetFrench(), maxBufferSize); + break; + case LANGUAGE_GER: + font->msgLength = SohUtils::CopyStringToCharBuffer(buffer, messageEntry.GetGerman(), maxBufferSize); + break; + case LANGUAGE_ENG: + default: + font->msgLength = SohUtils::CopyStringToCharBuffer(buffer, messageEntry.GetEnglish(), maxBufferSize); + break; + } + msgCtx->msgLength = static_cast(font->msgLength); + } + msgCtx->textBoxProperties = font->charTexBuf[0]; + msgCtx->textBoxType = msgCtx->textBoxProperties >> 4; + msgCtx->textBoxPos = msgCtx->textBoxProperties & 0xF; + const int16_t textBoxType = msgCtx->textBoxType; + // "Text Box Type" + osSyncPrintf("吹き出し種類=%d\n", msgCtx->textBoxType); + if (textBoxType < TEXTBOX_TYPE_NONE_BOTTOM) { + const char* textureName = msgStaticTbl[messageStaticIndices[textBoxType]]; + memcpy(msgCtx->textboxSegment, textureName, strlen(textureName) + 1); + if (textBoxType == TEXTBOX_TYPE_BLACK) { + msgCtx->textboxColorRed = 0; + msgCtx->textboxColorGreen = 0; + msgCtx->textboxColorBlue = 0; + } else if (textBoxType == TEXTBOX_TYPE_WOODEN) { + msgCtx->textboxColorRed = 70; + msgCtx->textboxColorGreen = 50; + msgCtx->textboxColorBlue = 30; + } else if (textBoxType == TEXTBOX_TYPE_BLUE) { + msgCtx->textboxColorRed = 0; + msgCtx->textboxColorGreen = 10; + msgCtx->textboxColorBlue = 50; + } else { + msgCtx->textboxColorRed = 255; + msgCtx->textboxColorGreen = 0; + msgCtx->textboxColorBlue = 0; + } + if (textBoxType == TEXTBOX_TYPE_WOODEN) { + msgCtx->textboxColorAlphaTarget = 230; + } else if (textBoxType == TEXTBOX_TYPE_OCARINA) { + msgCtx->textboxColorAlphaTarget = 180; + } else { + msgCtx->textboxColorAlphaTarget = 170; + } + msgCtx->textboxColorAlphaCurrent = 0; + } + msgCtx->choiceNum = msgCtx->textUnskippable = msgCtx->textboxEndType = 0; + msgCtx->msgBufPos = msgCtx->unk_E3D0 = msgCtx->textDrawPos = 0; + msgCtx->talkActor = &player->actor; + msgCtx->msgMode = MSGMODE_TEXT_START; + msgCtx->stateTimer = 0; + msgCtx->textDelayTimer = 0; + msgCtx->ocarinaMode = OCARINA_MODE_00; +} + +void MessageDebug_DisplayCustomMessage(const char* customMessage) { + CustomMessageManager::Instance->ClearMessageTable(MessageViewer::TABLE_ID); + CustomMessageManager::Instance->CreateMessage(MessageViewer::TABLE_ID, 0, + CustomMessage(customMessage, customMessage, customMessage)); + MessageDebug_StartTextBox(MessageViewer::TABLE_ID, 0, 0); +} + + diff --git a/soh/soh/Enhancements/debugger/MessageViewer.h b/soh/soh/Enhancements/debugger/MessageViewer.h new file mode 100644 index 000000000..702693793 --- /dev/null +++ b/soh/soh/Enhancements/debugger/MessageViewer.h @@ -0,0 +1,62 @@ +#ifndef CUSTOMMESSAGEDEBUGGER_H +#define CUSTOMMESSAGEDEBUGGER_H +#include "z64.h" + +#ifdef __cplusplus +#include "GuiWindow.h" +#include +extern "C" { +#endif +/** + * \brief Pulls a message from the specified message table and kicks off the process of displaying that message + * in a text box on screen. + * \param tableId the tableId string for the table we want to pull from. Empty string for authentic/vanilla messages + * \param textId The textId corresponding to the message to display. Putting in a textId that doesn't exist will + * probably result in a crash. + * \param language The Language to display on the screen. + */ +void MessageDebug_StartTextBox(const char* tableId, uint16_t textId, uint8_t language); + +/** + * \brief + * \param customMessage A string using Custom Message Syntax. + */ +void MessageDebug_DisplayCustomMessage(const char* customMessage); +#ifdef __cplusplus +} + + +class MessageViewer : public LUS::GuiWindow { +public: + static inline const char* TABLE_ID = "MessageViewer"; + using GuiWindow::GuiWindow; + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override; + + virtual ~MessageViewer() = default; + +private: + void DisplayExistingMessage() const; + void DisplayCustomMessage() const; + + static constexpr uint16_t MAX_STRING_SIZE = 1024; + static constexpr std::array mLanguages = {"English", "German", "French"}; + static constexpr int HEXADECIMAL = 0; + static constexpr int DECIMAL = 1; + char* mTableIdBuf; + std::string mTableId; + char* mTextIdBuf; + uint16_t mTextId; + int mTextIdBase = HEXADECIMAL; + size_t mLanguage = LANGUAGE_ENG; + char* mCustomMessageBuf; + std::string mCustomMessageString; + bool mDisplayExistingMessageClicked = false; + bool mDisplayCustomMessageClicked = false; +}; + + +#endif //__cplusplus +#endif //CUSTOMMESSAGEDEBUGGER_H diff --git a/soh/soh/Enhancements/debugger/actorViewer.cpp b/soh/soh/Enhancements/debugger/actorViewer.cpp index f83449efe..e2b45a419 100644 --- a/soh/soh/Enhancements/debugger/actorViewer.cpp +++ b/soh/soh/Enhancements/debugger/actorViewer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,7 @@ extern PlayState* gPlayState; #include "textures/icon_item_24_static/icon_item_24_static.h" } +#define DEKUNUTS_FLOWER 10 #define DEBUG_ACTOR_NAMETAG_TAG "debug_actor_viewer" typedef struct { @@ -107,6 +109,783 @@ void PopulateActorDropdown(int i, std::vector& data) { } } +//actors that don't use params at all +static std::vector noParamsActors = { + ACTOR_ARMS_HOOK, + ACTOR_ARROW_FIRE, + ACTOR_ARROW_ICE, + ACTOR_ARROW_LIGHT, + ACTOR_BG_BOM_GUARD, + ACTOR_BG_DY_YOSEIZO, + ACTOR_BG_GATE_SHUTTER, + ACTOR_BG_GJYO_BRIDGE, + ACTOR_BG_HIDAN_FSLIFT, + ACTOR_BG_HIDAN_RSEKIZOU, + ACTOR_BG_HIDAN_SYOKU, + ACTOR_BG_JYA_GOROIWA, + ACTOR_BG_MIZU_UZU, + ACTOR_BG_MORI_RAKKATENJO, + ACTOR_BG_PUSHBOX, + ACTOR_BG_SPOT01_FUSYA, + ACTOR_BG_SPOT01_IDOHASHIRA, + ACTOR_BG_SPOT01_IDOMIZU, + ACTOR_BG_SPOT01_IDOSOKO, + ACTOR_BG_SPOT11_OASIS, + ACTOR_BG_SPOT15_SAKU, + ACTOR_BG_SPOT18_FUTA, + ACTOR_BG_TOKI_SWD, + ACTOR_BG_TREEMOUTH, + ACTOR_BG_VB_SIMA, + ACTOR_BOSS_DODONGO, + ACTOR_BOSS_FD, + ACTOR_BOSS_GOMA, + ACTOR_DEMO_EXT, + ACTOR_DEMO_SHD, + ACTOR_DEMO_TRE_LGT, + ACTOR_DOOR_TOKI, + ACTOR_EFC_ERUPC, + ACTOR_EN_ANI, + ACTOR_EN_AROW_TRAP, + ACTOR_EN_BIRD, + ACTOR_EN_BLKOBJ, + ACTOR_EN_BOM_BOWL_MAN, + ACTOR_EN_BOM_BOWL_PIT, + ACTOR_EN_BOM_CHU, + ACTOR_EN_BUBBLE, + ACTOR_EN_DIVING_GAME, + ACTOR_EN_DNT_DEMO, + ACTOR_EN_DNT_JIJI, + ACTOR_EN_DS, + ACTOR_EN_DU, + ACTOR_EN_EG, + ACTOR_EN_FU, + ACTOR_EN_GB, + ACTOR_EN_GE3, + ACTOR_EN_GUEST, + ACTOR_EN_HATA, + ACTOR_EN_HORSE_GANON, + ACTOR_EN_HORSE_LINK_CHILD, + ACTOR_EN_HORSE_ZELDA, + ACTOR_EN_HS2, + ACTOR_EN_JS, + ACTOR_EN_KAKASI, + ACTOR_EN_KAKASI3, + ACTOR_EN_MA1, + ACTOR_EN_MA2, + ACTOR_EN_MA3, + ACTOR_EN_MAG, + ACTOR_EN_MK, + ACTOR_EN_MS, + ACTOR_EN_NIW_LADY, + ACTOR_EN_NWC, + ACTOR_EN_OE2, + ACTOR_EN_OKARINA_EFFECT, + ACTOR_EN_RR, + ACTOR_EN_SA, + ACTOR_EN_SCENE_CHANGE, + ACTOR_EN_SKJNEEDLE, + ACTOR_EN_SYATEKI_ITM, + ACTOR_EN_SYATEKI_MAN, + ACTOR_EN_TAKARA_MAN, + ACTOR_EN_TORYO, + ACTOR_EN_VASE, + ACTOR_EN_ZL1, + ACTOR_MAGIC_DARK, + ACTOR_MAGIC_FIRE, + ACTOR_OBJ_DEKUJR, + ACTOR_OCEFF_SPOT, + + ACTOR_UNSET_1, + ACTOR_UNSET_3, + ACTOR_UNSET_5, + ACTOR_UNSET_6, + ACTOR_UNSET_17, + ACTOR_UNSET_1A, + ACTOR_UNSET_1F, + ACTOR_UNSET_22, + ACTOR_UNSET_31, + ACTOR_UNSET_36, + ACTOR_UNSET_53, + ACTOR_UNSET_73, + ACTOR_UNSET_74, + ACTOR_UNSET_75, + ACTOR_UNSET_76, + ACTOR_UNSET_78, + ACTOR_UNSET_79, + ACTOR_UNSET_7A, + ACTOR_UNSET_7B, + ACTOR_UNSET_7E, + ACTOR_UNSET_7F, + ACTOR_UNSET_83, + ACTOR_UNSET_A0, + ACTOR_UNSET_B2, + ACTOR_UNSET_CE, + ACTOR_UNSET_D8, + ACTOR_UNSET_EA, + ACTOR_UNSET_EB, + ACTOR_UNSET_F2, + ACTOR_UNSET_F3, + ACTOR_UNSET_FB, + ACTOR_UNSET_109, + ACTOR_UNSET_10D, + ACTOR_UNSET_10E, + ACTOR_UNSET_128, + ACTOR_UNSET_129, + ACTOR_UNSET_134, + ACTOR_UNSET_154, + ACTOR_UNSET_15D, + ACTOR_UNSET_161, + ACTOR_UNSET_180, + ACTOR_UNSET_1AA +}; + +static std::unordered_map> actorSpecificData; + +void CreateActorSpecificData() { + if (!actorSpecificData.empty()) { + return; + } + + actorSpecificData[ACTOR_EN_DEKUNUTS] = [](s16 params) -> s16 { + bool isFlower = params == DEKUNUTS_FLOWER; + s16 shotsPerRound = (params >> 8) & 0xFF; + if (shotsPerRound == 0xFF || shotsPerRound == 0) { + shotsPerRound = 1; + } + ImGui::Checkbox("Flower", &isFlower); + if (!isFlower) { + ImGui::InputScalar("Shots Per Round", ImGuiDataType_S16, &shotsPerRound); + } + + return isFlower ? DEKUNUTS_FLOWER : (shotsPerRound << 8); + }; + + actorSpecificData[ACTOR_EN_TITE] = [](s16 params) -> s16 { + static const char* items[] = { "Blue", "Red" }; + if (params == 0) { + params = -2; + } + //the + 2 is because the params are -2 & -1 instead of 0 & 1 + int selectedItem = params + 2; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem - 2; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_AM] = [](s16 params) -> s16 { + static const char* items[] = { "Statue", "Enemy" }; + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_BG_ICE_TURARA] = [](s16 params) -> s16 { + static const char* items[] = { "Stalagmite", "Stalactite", "Stalactite (Regrow)" }; + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_BG_BREAKWALL] = [](s16 params) -> s16 { + static const char* items[] = { "DC Entrance", "Wall", "KD Floor", "KD Lava Cover" }; + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_TEST] = [](s16 params) -> s16 { + static const char* items[] = { "Invisible", "1", "2", "Ceiling", "4", "5" }; + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_TANA] = [](s16 params) -> s16 { + static const char* items[] = { "Wooden", "Stone (1)", "Stone (2)" }; + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_XC] = [](s16 params) -> s16 { + static const char* items[] = { "0", "1", "2", "3", "4", "5", "Minuet", "Bolero", "Serenade", "9" }; + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_SHOT_SUN] = [](s16 params) -> s16 { + static const char* items[] = { "Sun's Song", "Song of Storms", "LH Sun" }; + if (params == 0) { + params = 0x40; + } + //the - 0x40 is because the params are 0x40 & 0x41 instead of 0 & 1 + int selectedItem = params - 0x40; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem + 0x40; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_HONOTRAP] = [](s16 params) -> s16 { + static const char* items[] = { "Eye", "Flame Move", "Flame Drop" }; + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_REEBA] = [](s16 params) -> s16 { + bool isBig = params != 0; + ImGui::Checkbox("Big", &isBig); + + return isBig; + }; + + actorSpecificData[ACTOR_EN_TK] = [](s16 params) -> s16 { + bool canTurn = params >= 0; + ImGui::Checkbox("Can Turn", &canTurn); + + return canTurn ? 0 : -1; + }; + + actorSpecificData[ACTOR_EN_ITEM00] = [](s16 params) -> s16 { + bool autoCollect = params & 0x8000; + ImGui::Checkbox("Automatically Collect", &autoCollect); + u8 collectibleFlag = (params & 0x3F00) >> 8; + ImGui::InputScalar("Collectible Flag", ImGuiDataType_U8, &collectibleFlag); + if (collectibleFlag > 0x3F) { + collectibleFlag = 0x3F; + } + + static const char* items[] = { + "Green Rupee", + "Blue Rupee", + "Red Rupee", + "Recovery Heart", + "Bombs (A)", + "Arrow", + "Heart Piece", + "Heart Container", + "Arrows (5)", + "Arrows (10)", + "Arrows (30)", + "Bombs (B)", + "Deku Nuts (5)", + "Deku Stick", + "Magic (Large)", + "Magic (Small)", + "Deku Seeds (5)", + "Small Key", + "Flexible", + "Gold Rupee", + "Purple Rupee", + "Deku Shield", + "Hylian Shield", + "Zora Tunic", + "Goron Tunic", + "Bombs (Special)", + "Bombchus" + }; + + int selectedItem = params & 0xFF; + ImGui::Combo("Item", &selectedItem, items, IM_ARRAYSIZE(items)); + + return autoCollect * 0x8000 + (collectibleFlag << 8) + selectedItem; + }; + + actorSpecificData[ACTOR_OBJ_COMB] = [](s16 params) -> s16 { + static const char* items[] = { + "Green Rupee", + "Blue Rupee", + "Red Rupee", + "Recovery Heart", + "Bombs (A)", + "Arrow", + "Heart Piece", + "Heart Container", + "Arrows (5)", + "Arrows (10)", + "Arrows (30)", + "Bombs (B)", + "Deku Nuts (5)", + "Deku Stick", + "Magic (Large)", + "Magic (Small)", + "Deku Seeds (5)", + "Small Key", + "Flexible", + "Gold Rupee", + "Purple Rupee", + "Deku Shield", + "Hylian Shield", + "Zora Tunic", + "Goron Tunic", + "Bombs (Special)", + "Bombchus" + }; + + int selectedItem = params & 0xFF; + ImGui::Combo("Item Drop", &selectedItem, items, IM_ARRAYSIZE(items)); + + u8 collectibleFlag = (params & 0x3F00) >> 8; + if (selectedItem == 6) { + ImGui::InputScalar("PoH Collectible Flag", ImGuiDataType_U8, &collectibleFlag); + if (collectibleFlag > 0x3F) { + collectibleFlag = 0x3F; + } + } + + return (collectibleFlag << 8) + selectedItem; + }; + + actorSpecificData[ACTOR_EN_GM] = [](s16 params) -> s16 { + u8 switchFlag = (params & 0x3F00) >> 8; + + ImGui::InputScalar("Switch Flag", ImGuiDataType_U8, &switchFlag); + if (switchFlag > 0x3F) { + switchFlag = 0x3F; + } + + return switchFlag << 8; + }; + + actorSpecificData[ACTOR_EN_GIRLA] = [](s16 params) -> s16 { + static const char* items[] = { + "Deku Nuts (5)", + "Arrows (30)", + "Arrows (50)", + "Bombs (5) (25 Rupees)", + "Deku Nuts (10)", + "Deku Stick", + "Bombs (10)", + "Fish", + "Red Potion (30 Rupees)", + "Green Potion", + "Blue Potion", + "Longsword", + "Hylian Shield", + "Deku Shield", + "Goron Tunic", + "Zora Tunic", + "Heart", + "Milk Bottle", + "Weird Egg", + "19", + "20", + "Bomchu (10) [1]", + "Bomchu (20) [1]", + "Bomchu (20) [2]", + "Bomchu (10) [2]", + "Bomchu (10) [3]", + "Bomchu (20) [3]", + "Bomchu (20) [4]", + "Bomchu (10) [4]", + "Deku Seeds (30)", + "Keaton Mask", + "Spooky Mask", + "Skull Mask", + "Bunny Hood", + "Mask Of Truth", + "Zora Mask", + "Goron Mask", + "Gerudo Mask", + "Sold Out", + "Blue Fire", + "Bugs", + "Big Poe", + "Poe", + "Fairy", + "Arrows (10)", + "Bombs (20)", + "Bombs (30)", + "Bombs (5) (35 Rupees)", + "Red Potion (40 Rupees)", + "Red Potion (50 Rupees)", + "Randomizer Item" + }; + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_FIRE_ROCK] = [](s16 params) -> s16 { + static const char* items[] = { + "Spawned Falling (1)", + "Broken Piece (1)", + "Broken Piece (2)", + "Spawned Falling (2)", + //"INVALID", + "Ceiling Spot Spawner", + "On Floor" + }; + int selectedItem = params > 3 ? params - 1 : params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem > 3 ? selectedItem + 1 : selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_EX_ITEM] = [](s16 params) -> s16 { + static const char* items[] = { + "Bomb Bag Bowling", + "Heart Piece Bowling", + "Bombchus Bowling", + "Bombs Bowling", + "Purple Rupee Bowling", + "Bomb Bag Counter", + "Heart Piece Counter", + "Bombchus Counter", + "Bombs Counter", + "Purple Rupee Counter", + "Green Rupee Chest", + "Blue Rupee Chest", + "Red Rupee Chest", + "13", + "14", + "Small Key Chest", + "Magic Fire", + "Magic Wind", + "Magic Dark", + "Bullet Bag" + }; + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_ELF] = [](s16 params) -> s16 { + static const char* items[] = { + "Navi", + "Revive Bottle", + "Heal Timed", + "Kokiri", + "Spawner", + "Revive Death", + "Heal", + "Heal Big" + }; + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_CLEAR_TAG] = [](s16 params) -> s16 { + static const char* items[] = { + "Cutscene", //0 + "Normal", //1 + "Laser" //100 + }; + int selectedItem = params == 100 ? 2 : params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem == 2 ? 100 : selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_BOMBF] = [](s16 params) -> s16 { + static const char* items[] = { "Flower", "Body", "Explosion" }; + //the + 1 is because the params are -1, 0 & 1 instead of 0, 1 & 2 + int selectedItem = params + 1; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem - 1; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_BOM] = [](s16 params) -> s16 { + static const char* items[] = { "Body", "Explosion" }; + + int selectedItem = params; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_DOOR_WARP1] = [](s16 params) -> s16 { + static const char* items[] = { + "Blue Crystal", // -2 + "Dungeon Adult", + "Dungeon Child", + "Clear Flag", // Activate on temp clear flag + "Sages", // Used by sages warping into chamber of sages during their cutscene + "Purple Crystal", + "Yellow", // The colored variants don't warp, they are cutscene setpieces + "Blue Ruto", + "Destination", // Spawning in after having taken a warp + "UNK 7", + "Orange", + "Green", + "Red" + }; + int selectedItem = params + 2; + if (ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem - 2; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_DY_EXTRA] = [](s16 params) -> s16 { + static const char* items[] = { "Orange", "Green" }; + + int selectedItem = params; + if (ImGui::Combo("Color", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; + + actorSpecificData[ACTOR_EN_SKB] = [](s16 params) -> s16 { + u8 size = params; + ImGui::InputScalar("Size", ImGuiDataType_U8, &size); + + return size; + }; + + actorSpecificData[ACTOR_EN_WF] = [](s16 params) -> s16 { + static const char* items[] = { "Normal", "White" }; + + int selectedItem = params; + ImGui::Combo("Type", &selectedItem, items, IM_ARRAYSIZE(items)); + + u8 switchFlag = (params & 0x3F00) >> 8; + ImGui::InputScalar("Switch Flag", ImGuiDataType_U8, &switchFlag); + return (switchFlag << 8) + selectedItem; + }; + + actorSpecificData[ACTOR_EN_BOX] = [](s16 params) -> s16 { + /* + trasureFlag = params & 0x1F; //0b0000 0000 0001 1111 + itemId = (params >> 5) & 0x7F; //0b0000 1111 1110 0000 + type = (params >> 12) & 0xF; //0b1111 0000 0000 0000 + */ + u8 treasureFlag = params & 0x1F; + ImGui::InputScalar("Treasure Flag", ImGuiDataType_U8, &treasureFlag); + if (treasureFlag > 0x1F) { + treasureFlag = 0x1F; + } + + u8 itemId = (params >> 5) & 0x7F; + ImGui::InputScalar("Item Id", ImGuiDataType_U8, &itemId); + if (itemId > 0x7F) { + itemId = 0x7F; + } + + static const char* items[] = { + "Big (Default)", + "Room Clear Big", + "Decorated Big", + "Switch Flag Fall Big", + "4", + "Small", + "6", + "Room Clear Small", + "Switch Flag Fall Small", + "9", + "10", + "Switch Flag Big" + }; + + int type = (params >> 12) & 0xF; + ImGui::Combo("Type", &type, items, IM_ARRAYSIZE(items)); + if (type > 0xF) { + type = 0xF; + } + + return (type << 12) + (itemId << 5) + treasureFlag; + }; + + actorSpecificData[ACTOR_EN_DOOR] = [](s16 params) -> s16 { + /** + * Actor Parameters + * + * | | | | + * | Transition Index | Type | Double Door | Switch Flag OR Text Id - 0x0200 + * |------------------|-------|-------------|--------------------------------- + * | 0 0 0 0 0 0 | 0 0 0 | 0 | 0 0 0 0 0 0 + * | 6 | 3 | 1 | 6 + * | + * + * Transition Index 1111110000000000 Set by the actor engine when the door is spawned + * Type 0000001110000000 + * Double Door 0000000001000000 + * Switch Flag 0000000000111111 For use with the `DOOR_LOCKED` type + * Text id - 0x0200 0000000000111111 For use with the `DOOR_CHECKABLE` type + * + */ + u8 transitionIndex = params >> 10; + ImGui::InputScalar("Transition Index", ImGuiDataType_U8, &transitionIndex); + if (transitionIndex > 0x3F) { + transitionIndex = 0x3F; + } + + static const char* items[] = { + "Room Load", // loads rooms + "Locked", // small key locked door + "Room Load (2)", // loads rooms + "Scene Exit", // doesn't load rooms, used for doors paired with scene transition polygons + "Ajar", // open slightly but slams shut if Link gets too close + "Checkable", // doors that display a textbox when interacting + "Evening", // unlocked between 18:00 and 21:00, Dampé's hut + "Room Load (7)" // loads rooms + }; + + int type = (params >> 7) & 7; + ImGui::Combo("Type", &type, items, IM_ARRAYSIZE(items)); + if (type > 7) { + type = 7; + } + + bool doubleDoor = ((params >> 6) & 1) != 0; + ImGui::Checkbox("Double Door", &doubleDoor); + + u8 lowerBits = params & 0x3F; + if (type == 1) { + ImGui::InputScalar("Switch Flag", ImGuiDataType_U8, &lowerBits); + if (lowerBits > 0x3F) { + lowerBits = 0x3F; + } + } else if (type == 5) { + ImGui::InputScalar("Text ID - 0x200", ImGuiDataType_U8, &lowerBits); + if (lowerBits > 0x3F) { + lowerBits = 0x3F; + } + } else { + lowerBits = 0; + } + + return (transitionIndex << 10) + (type << 7) + (doubleDoor << 6) + lowerBits; + }; + + actorSpecificData[ACTOR_EN_PO_DESERT] = [](s16 params) -> s16 { + u8 switchFlag = params >> 8; + + ImGui::InputScalar("Path", ImGuiDataType_U8, &switchFlag); + + return switchFlag << 8; + }; + + actorSpecificData[ACTOR_EN_KANBAN] = [](s16 params) -> s16 { + bool piece = params == (s16)0xFFDD; + bool fishingSign = params == 0x300; + if (ImGui::Checkbox("Piece", &piece)) { + fishingSign = false; + } + if (ImGui::Checkbox("Fishing Sign", &fishingSign)) { + piece = false; + } + + u8 textId = params; + if (!piece && !fishingSign) { + if (ImGui::InputScalar("Text ID", ImGuiDataType_U8, &textId)) { + textId |= 0x300; + } + } + + return piece ? (s16)0xFFDD : (fishingSign ? 0x300 : textId); + }; + + actorSpecificData[ACTOR_EN_KUSA] = [](s16 params) -> s16 { + static const char* items[] = { + "0", + "1", + "2" + }; + + int type = params & 3; + ImGui::Combo("Type", &type, items, IM_ARRAYSIZE(items)); + + bool bugs = ((params >> 4) & 1) != 0; + ImGui::Checkbox("Bugs", &bugs); + + u8 drop = (params >> 8) & 0xF; + if (type == 2) { + ImGui::InputScalar("Random Drop Params", ImGuiDataType_U8, &drop); + if (drop > 0xD) { + drop = 0xD; + } + } else { + drop = 0; + } + + return (drop << 8) + (bugs << 4) + type; + }; + + actorSpecificData[ActorDB::Instance->RetrieveId("En_Partner")] = [](s16 params) -> s16 { + static const char* items[] = { + "Port 1", + "Port 2", + "Port 3", + "Port 4" + }; + int selectedItem = params; + if (ImGui::Combo("Controller Port", &selectedItem, items, IM_ARRAYSIZE(items))) { + return selectedItem; + } + + return params; + }; +} + +std::vector GetActorsWithDescriptionContainingString(std::string s) { + std::locale loc; + for (size_t i = 0; i < s.length(); i += 1) { + s[i] = std::tolower(s[i], loc); + } + + std::vector actors; + for (int i = 0; i < ActorDB::Instance->GetEntryCount(); i += 1) { + ActorDB::Entry actorEntry = ActorDB::Instance->RetrieveEntry(i); + std::string desc = actorEntry.desc; + for (size_t j = 0; j < desc.length(); j += 1) { + desc[j] = std::tolower(desc[j], loc); + } + if (desc.find(s) != std::string::npos) { + actors.push_back((u16)i); + } + } + return actors; +} + void ActorViewer_AddTagForActor(Actor* actor) { int val = CVarGetInteger("gDebugActorViewerNameTags", ACTORVIEWER_NAMETAGS_NONE); auto entry = ActorDB::Instance->RetrieveEntry(actor->id); @@ -163,6 +942,9 @@ void ActorViewerWindow::DrawElement() { static std::string filler = "Please select"; static std::vector list; static u16 lastSceneId = 0; + static char searchString[64] = ""; + static s16 currentSelectedInDropdown; + static std::vector actors; if (gPlayState != nullptr) { needs_reset = lastSceneId != gPlayState->sceneNum; @@ -173,6 +955,11 @@ void ActorViewerWindow::DrawElement() { filler = "Please Select"; list.clear(); needs_reset = false; + for (size_t i = 0; i < ARRAY_COUNT(searchString); i += 1) { + searchString[i] = 0; + } + currentSelectedInDropdown = -1; + actors.clear(); } lastSceneId = gPlayState->sceneNum; if (ImGui::BeginCombo("Actor Type", acMapping[category])) { @@ -316,9 +1103,48 @@ void ActorViewerWindow::DrawElement() { if (ImGui::TreeNode("New...")) { ImGui::PushItemWidth(ImGui::GetFontSize() * 10); + if (ImGui::InputText("Search Actor", searchString, ARRAY_COUNT(searchString))) { + actors = GetActorsWithDescriptionContainingString(std::string(searchString)); + currentSelectedInDropdown = -1; + } + + if (searchString[0] != 0 && !actors.empty()) { + std::string preview = currentSelectedInDropdown == -1 ? "Please Select" : ActorDB::Instance->RetrieveEntry(actors[currentSelectedInDropdown]).desc; + if (ImGui::BeginCombo("Results", preview.c_str())) { + for (u8 i = 0; i < actors.size(); i++) { + if (ImGui::Selectable( + ActorDB::Instance->RetrieveEntry(actors[i]).desc.c_str(), + i == currentSelectedInDropdown + )) { + currentSelectedInDropdown = i; + newActor.id = actors[i]; + } + } + ImGui::EndCombo(); + } + } + ImGui::Text("%s", GetActorDescription(newActor.id).c_str()); - ImGui::InputScalar("ID", ImGuiDataType_S16, &newActor.id, &one); - ImGui::InputScalar("params", ImGuiDataType_S16, &newActor.params, &one); + if (ImGui::InputScalar("ID", ImGuiDataType_S16, &newActor.id, &one)) { + newActor.params = 0; + } + + UIWidgets::EnhancementCheckbox("Advanced mode", "gActorViewerAdvancedParams"); + UIWidgets::InsertHelpHoverText("Changes the actor specific param menus with a direct input"); + + if (CVarGetInteger("gActorViewerAdvancedParams", 0)) { + ImGui::InputScalar("params", ImGuiDataType_S16, &newActor.params, &one); + } else if (std::find(noParamsActors.begin(), noParamsActors.end(), newActor.id) == noParamsActors.end()) { + CreateActorSpecificData(); + if (actorSpecificData.find(newActor.id) == actorSpecificData.end()) { + ImGui::InputScalar("params", ImGuiDataType_S16, &newActor.params, &one); + } else { + DrawGroupWithBorder([&]() { + ImGui::Text("Actor Specific Data"); + newActor.params = actorSpecificData[newActor.id](newActor.params); + }); + } + } ImGui::PushItemWidth(ImGui::GetFontSize() * 6); @@ -401,6 +1227,11 @@ void ActorViewerWindow::DrawElement() { filler = "Please Select"; list.clear(); needs_reset = false; + for (size_t i = 0; i < ARRAY_COUNT(searchString); i += 1) { + searchString[i] = 0; + } + currentSelectedInDropdown = -1; + actors.clear(); } } diff --git a/soh/soh/Enhancements/debugger/dlViewer.cpp b/soh/soh/Enhancements/debugger/dlViewer.cpp index 2fce8860a..028234fc1 100644 --- a/soh/soh/Enhancements/debugger/dlViewer.cpp +++ b/soh/soh/Enhancements/debugger/dlViewer.cpp @@ -18,15 +18,13 @@ extern "C" { #include "variables.h" #include "functions.h" #include "macros.h" -extern PlayState* gPlayState; - -char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize); } char searchString[64] = ""; -int displayListsSearchResultsCount; -char** displayListsSearchResults; -char* activeDisplayList = nullptr; +std::string activeDisplayList = ""; +std::vector displayListSearchResults; +int16_t searchDebounceFrames = -1; +bool doSearch = false; std::map cmdMap = { { G_SETPRIMCOLOR, "gsDPSetPrimColor" }, @@ -36,8 +34,61 @@ std::map cmdMap = { { G_SETINTENSITY, "gsDPSetGrayscaleColor" }, { G_LOADTLUT, "gsDPLoadTLUT" }, { G_ENDDL, "gsSPEndDisplayList" }, + { G_TEXTURE, "gsSPTexture" }, + { G_SETTIMG, "gsDPSetTextureImage" }, + { G_SETTIMG_OTR_HASH, "gsDPSetTextureImage" }, + { G_SETTIMG_OTR_FILEPATH, "gsDPSetTextureImage" }, + { G_RDPTILESYNC, "gsDPTileSync" }, + { G_SETTILE, "gsDPSetTile" }, + { G_RDPLOADSYNC, "gsDPLoadSync" }, + { G_LOADBLOCK, "gsDPLoadBlock" }, + { G_SETTILESIZE, "gsDPSetTileSize" }, + { G_DL, "gsSPDisplayList" }, + { G_DL_OTR_FILEPATH, "gsSPDisplayList" }, + { G_DL_OTR_HASH, "gsSPDisplayList" }, + { G_MTX, "gsSPMatrix" }, + { G_MTX_OTR, "gsSPMatrix" }, + { G_VTX, "gsSPVertex" }, + { G_VTX_OTR_FILEPATH, "gsSPVertex" }, + { G_VTX_OTR_HASH, "gsSPVertex" }, + { G_GEOMETRYMODE, "gsSPSetGeometryMode" }, + { G_SETOTHERMODE_H, "gsSPSetOtherMode_H" }, + { G_SETOTHERMODE_L, "gsSPSetOtherMode_L" }, + { G_TRI1, "gsSP1Triangle" }, + { G_TRI1_OTR, "gsSP1Triangle" }, + { G_TRI2, "gsSP2Triangles" }, + { G_SETCOMBINE, "gsDPSetCombineLERP" }, + { G_CULLDL, "gsSPCullDisplayList" }, + { G_NOOP, "gsDPNoOp" }, + { G_SPNOOP, "gsSPNoOp" }, + { G_MARKER, "LUS Custom Marker" }, }; +void PerformDisplayListSearch() { + auto result = LUS::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->ListFiles("*" + std::string(searchString) + "*DL*"); + + displayListSearchResults.clear(); + + // Filter the file results even further as StormLib can only use wildcard searching + for (size_t i = 0; i < result->size(); i++) { + std::string val = result->at(i); + if (val.ends_with("DL") || val.find("DL_") != std::string::npos) { + displayListSearchResults.push_back(val); + } + } + + // Sort the final list + std::sort(displayListSearchResults.begin(), displayListSearchResults.end(), [](const std::string& a, const std::string& b) { + return std::lexicographical_compare( + a.begin(), a.end(), + b.begin(), b.end(), + [](char c1, char c2) { + return std::tolower(c1) < std::tolower(c2); + } + ); + }); +} + void DLViewerWindow::DrawElement() { ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Display List Viewer", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { @@ -45,22 +96,48 @@ void DLViewerWindow::DrawElement() { return; } + // Debounce the search field as listing otr files is expensive if (ImGui::InputText("Search Display Lists", searchString, ARRAY_COUNT(searchString))) { - displayListsSearchResults = ResourceMgr_ListFiles(("*" + std::string(searchString) + "*DL").c_str(), &displayListsSearchResultsCount); + doSearch = true; + searchDebounceFrames = 30; } - if (ImGui::BeginCombo("Active Display List", activeDisplayList)) { - for (int i = 0; i < displayListsSearchResultsCount; i++) { - if (ImGui::Selectable(displayListsSearchResults[i])) { - activeDisplayList = displayListsSearchResults[i]; + if (doSearch) { + if (searchDebounceFrames == 0) { + doSearch = false; + PerformDisplayListSearch(); + } + + searchDebounceFrames--; + } + + if (ImGui::BeginCombo("Active Display List", activeDisplayList.c_str())) { + for (size_t i = 0; i < displayListSearchResults.size(); i++) { + if (ImGui::Selectable(displayListSearchResults[i].c_str())) { + activeDisplayList = displayListSearchResults[i]; break; } } ImGui::EndCombo(); } - if (activeDisplayList != nullptr) { + + if (activeDisplayList == "") { + ImGui::End(); + return; + } + + try { auto res = std::static_pointer_cast(LUS::Context::GetInstance()->GetResourceManager()->LoadResource(activeDisplayList)); - for (int i = 0; i < res->Instructions.size(); i++) { + + if (res->GetInitData()->Type != static_cast(LUS::ResourceType::DisplayList)) { + ImGui::Text("Resource type is not a Display List. Please choose another."); + ImGui::End(); + return; + } + + ImGui::Text("Total Instruction Size: %lu", res->Instructions.size()); + + for (size_t i = 0; i < res->Instructions.size(); i++) { std::string id = "##CMD" + std::to_string(i); Gfx* gfx = (Gfx*)&res->Instructions[i]; int cmd = gfx->words.w0 >> 24; @@ -70,10 +147,11 @@ void DLViewerWindow::DrawElement() { ImGui::BeginGroup(); ImGui::PushItemWidth(25.0f); - ImGui::Text("%d", i); + ImGui::Text("%lu", i); ImGui::PopItemWidth(); ImGui::SameLine(); - ImGui::PushItemWidth(150.0f); + ImGui::PushItemWidth(175.0f); + if (ImGui::BeginCombo(("CMD" + id).c_str(), cmdLabel.c_str())) { if (ImGui::Selectable("gsDPSetPrimColor") && cmd != G_SETPRIMCOLOR) { *gfx = gsDPSetPrimColor(0, 0, 0, 0, 0, 255); @@ -92,8 +170,10 @@ void DLViewerWindow::DrawElement() { } ImGui::EndCombo(); } + ImGui::PopItemWidth(); - if (gfx->words.w0 >> 24 == G_SETPRIMCOLOR || gfx->words.w0 >> 24 == G_SETINTENSITY || gfx->words.w0 >> 24 == G_SETENVCOLOR) { + + if (cmd == G_SETPRIMCOLOR || cmd == G_SETINTENSITY || cmd == G_SETENVCOLOR) { uint8_t r = _SHIFTR(gfx->words.w1, 24, 8); uint8_t g = _SHIFTR(gfx->words.w1, 16, 8); uint8_t b = _SHIFTR(gfx->words.w1, 8, 8); @@ -117,21 +197,141 @@ void DLViewerWindow::DrawElement() { } ImGui::PopItemWidth(); } - if (gfx->words.w0 >> 24 == G_RDPPIPESYNC) { + if (cmd == G_RDPPIPESYNC) { } - if (gfx->words.w0 >> 24 == G_SETGRAYSCALE) { + if (cmd == G_SETGRAYSCALE) { bool* state = (bool*)&gfx->words.w1; ImGui::SameLine(); if (ImGui::Checkbox(("state" + id).c_str(), state)) { // } } + if (cmd == G_SETTILE) { + ImGui::SameLine(); + ImGui::Text("FMT: %u", _SHIFTR(gfx->words.w0, 21, 3)); + ImGui::SameLine(); + ImGui::Text("SIZ: %u", _SHIFTR(gfx->words.w0, 19, 2)); + ImGui::SameLine(); + ImGui::Text("LINE: %u", _SHIFTR(gfx->words.w0, 9, 9)); + ImGui::SameLine(); + ImGui::Text("TMEM: %u", _SHIFTR(gfx->words.w0, 0, 9)); + ImGui::SameLine(); + ImGui::Text("TILE: %u", _SHIFTR(gfx->words.w1, 24, 3)); + ImGui::SameLine(); + ImGui::Text("PAL: %u", _SHIFTR(gfx->words.w1, 20, 4)); + ImGui::SameLine(); + ImGui::Text("CMT: %u", _SHIFTR(gfx->words.w1, 18, 2)); + ImGui::SameLine(); + ImGui::Text("MASKT: %u", _SHIFTR(gfx->words.w1, 14, 4)); + ImGui::SameLine(); + ImGui::Text("SHIFT: %u", _SHIFTR(gfx->words.w1, 10, 4)); + ImGui::SameLine(); + ImGui::Text("CMS: %u", _SHIFTR(gfx->words.w1, 8, 2)); + ImGui::SameLine(); + ImGui::Text("MASKS: %u", _SHIFTR(gfx->words.w1, 4, 4)); + ImGui::SameLine(); + ImGui::Text("SHIFTS: %u", _SHIFTR(gfx->words.w1, 0, 4)); + } + if (cmd == G_SETTIMG) { + ImGui::SameLine(); + ImGui::Text("FMT: %u", _SHIFTR(gfx->words.w0, 21, 3)); + ImGui::SameLine(); + ImGui::Text("SIZ: %u", _SHIFTR(gfx->words.w0, 19, 2)); + ImGui::SameLine(); + ImGui::Text("WIDTH: %u", _SHIFTR(gfx->words.w0, 0, 10)); + ImGui::SameLine(); + } + if (cmd == G_SETTIMG_OTR_HASH) { + gfx++; + uint64_t hash = ((uint64_t)gfx->words.w0 << 32) + (uint64_t)gfx->words.w1; + const char* fileName = ResourceGetNameByCrc(hash); + + gfx--; + ImGui::SameLine(); + ImGui::Text("FMT: %u", _SHIFTR(gfx->words.w0, 21, 3)); + ImGui::SameLine(); + ImGui::Text("SIZ: %u", _SHIFTR(gfx->words.w0, 19, 2)); + ImGui::SameLine(); + ImGui::Text("WIDTH: %u", _SHIFTR(gfx->words.w0, 0, 10)); + ImGui::SameLine(); + ImGui::Text("Texture Name: %s", fileName); + } + if (cmd == G_SETTIMG_OTR_FILEPATH) { + char* fileName = (char*)gfx->words.w1; + gfx++; + ImGui::SameLine(); + ImGui::Text("FMT: %u", _SHIFTR(gfx->words.w0, 21, 3)); + ImGui::SameLine(); + ImGui::Text("SIZ: %u", _SHIFTR(gfx->words.w0, 19, 2)); + ImGui::SameLine(); + ImGui::Text("WIDTH: %u", _SHIFTR(gfx->words.w0, 0, 10)); + ImGui::SameLine(); + ImGui::Text("Texture Name: %s", fileName); + } + if (cmd == G_VTX) { + ImGui::SameLine(); + ImGui::Text("Num VTX: %u", _SHIFTR(gfx->words.w0, 12, 8)); + ImGui::SameLine(); + ImGui::Text("Offset: %u", _SHIFTR(gfx->words.w0, 1, 7) - _SHIFTR(gfx->words.w0, 12, 8)); + } + if (cmd == G_VTX_OTR_HASH) { + gfx++; + uint64_t hash = ((uint64_t)gfx->words.w0 << 32) + (uint64_t)gfx->words.w1; + const char* fileName = ResourceGetNameByCrc(hash); + + gfx--; + ImGui::SameLine(); + ImGui::Text("Num VTX: %u", _SHIFTR(gfx->words.w0, 12, 8)); + ImGui::SameLine(); + ImGui::Text("Offset: %u", _SHIFTR(gfx->words.w0, 1, 7) - _SHIFTR(gfx->words.w0, 12, 8)); + + ImGui::SameLine(); + ImGui::Text("Vertex Name: %s", fileName); + } + if (cmd == G_VTX_OTR_FILEPATH) { + char* fileName = (char*)gfx->words.w1; + + gfx++; + ImGui::SameLine(); + ImGui::Text("Num VTX: %u", _SHIFTR(gfx->words.w0, 12, 8)); + ImGui::SameLine(); + ImGui::Text("Offset: %u", _SHIFTR(gfx->words.w0, 1, 7) - _SHIFTR(gfx->words.w0, 12, 8)); + + ImGui::SameLine(); + ImGui::Text("Vertex Name: %s", fileName); + } + if (cmd == G_DL) { + } + if (cmd == G_DL_OTR_HASH) { + gfx++; + uint64_t hash = ((uint64_t)gfx->words.w0 << 32) + (uint64_t)gfx->words.w1; + const char* fileName = ResourceGetNameByCrc(hash); + ImGui::SameLine(); + ImGui::Text("DL Name: %s", fileName); + } + if (cmd == G_DL_OTR_FILEPATH) { + char* fileName = (char*)gfx->words.w1; + ImGui::SameLine(); + ImGui::Text("DL Name: %s", fileName); + } + + // Skip second half of instructions that are over 128-bit wide + if (cmd == G_SETTIMG_OTR_HASH || cmd == G_DL_OTR_HASH || cmd == G_VTX_OTR_HASH || + cmd == G_BRANCH_Z_OTR || cmd == G_MARKER || cmd == G_MTX_OTR) { + i++; + ImGui::Text("%lu - Reserved - Second half of %s", i, cmdLabel.c_str()); + } ImGui::EndGroup(); } + } catch (const std::exception& e) { + ImGui::Text("Error displaying DL instructions."); + ImGui::End(); + return; } + ImGui::End(); } void DLViewerWindow::InitElement() { - displayListsSearchResults = ResourceMgr_ListFiles("*DL", &displayListsSearchResultsCount); + PerformDisplayListSearch(); } diff --git a/soh/soh/Enhancements/enhancementTypes.h b/soh/soh/Enhancements/enhancementTypes.h index c8461bbf1..56160567a 100644 --- a/soh/soh/Enhancements/enhancementTypes.h +++ b/soh/soh/Enhancements/enhancementTypes.h @@ -78,4 +78,10 @@ typedef enum { DEKU_STICK_UNBREAKABLE_AND_ALWAYS_ON_FIRE, } DekuStickType; +typedef enum { + SWORD_TOGGLE_NONE, + SWORD_TOGGLE_CHILD, + SWORD_TOGGLE_BOTH_AGES, +} SwordToggleMode; + #endif diff --git a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp index 206b26426..a0469d0c3 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp @@ -31,11 +31,11 @@ GameInteractionEffectQueryResult GameInteractionEffectBase::Apply() { } /// For most effects, CanBeRemoved is the same as CanBeApplied. When its not: please override `CanBeRemoved`. -GameInteractionEffectQueryResult GameInteractionEffectBase::CanBeRemoved() { +GameInteractionEffectQueryResult RemovableGameInteractionEffect::CanBeRemoved() { return CanBeApplied(); } -GameInteractionEffectQueryResult GameInteractionEffectBase::Remove() { +GameInteractionEffectQueryResult RemovableGameInteractionEffect::Remove() { GameInteractionEffectQueryResult result = CanBeRemoved(); if (result != GameInteractionEffectQueryResult::Possible) { return result; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h index c4a7f7eac..ebc1b6fac 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h @@ -15,38 +15,46 @@ enum GameInteractionEffectQueryResult { class GameInteractionEffectBase { public: virtual GameInteractionEffectQueryResult CanBeApplied() = 0; - virtual GameInteractionEffectQueryResult CanBeRemoved(); GameInteractionEffectQueryResult Apply(); - GameInteractionEffectQueryResult Remove(); - int32_t parameters[3]; - - protected: +protected: virtual void _Apply() = 0; +}; + +class RemovableGameInteractionEffect: public GameInteractionEffectBase { +public: + virtual GameInteractionEffectQueryResult CanBeRemoved(); + GameInteractionEffectQueryResult Remove(); +protected: virtual void _Remove() {}; }; +class ParameterizedGameInteractionEffect { +public: + int32_t parameters[3]; +}; + namespace GameInteractionEffect { - class SetSceneFlag: public GameInteractionEffectBase { + class SetSceneFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class UnsetSceneFlag: public GameInteractionEffectBase { + class UnsetSceneFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class SetFlag: public GameInteractionEffectBase { + class SetFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class UnsetFlag: public GameInteractionEffectBase { + class UnsetFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class ModifyHeartContainers: public GameInteractionEffectBase { + class ModifyHeartContainers: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; @@ -61,29 +69,29 @@ namespace GameInteractionEffect { void _Apply() override; }; - class ModifyRupees: public GameInteractionEffectBase { + class ModifyRupees: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class NoUI: public GameInteractionEffectBase { + class NoUI: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ModifyGravity: public GameInteractionEffectBase { + class ModifyGravity: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ModifyHealth: public GameInteractionEffectBase { + class ModifyHealth: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class SetPlayerHealth: public GameInteractionEffectBase { + class SetPlayerHealth: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; @@ -103,98 +111,98 @@ namespace GameInteractionEffect { void _Apply() override; }; - class KnockbackPlayer: public GameInteractionEffectBase { + class KnockbackPlayer: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class ModifyLinkSize: public GameInteractionEffectBase { + class ModifyLinkSize: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class InvisibleLink : public GameInteractionEffectBase { + class InvisibleLink : public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class PacifistMode : public GameInteractionEffectBase { + class PacifistMode : public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class DisableZTargeting: public GameInteractionEffectBase { + class DisableZTargeting: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class WeatherRainstorm: public GameInteractionEffectBase { + class WeatherRainstorm: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ReverseControls: public GameInteractionEffectBase { + class ReverseControls: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ForceEquipBoots: public GameInteractionEffectBase { + class ForceEquipBoots: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ModifyRunSpeedModifier: public GameInteractionEffectBase { + class ModifyRunSpeedModifier: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class OneHitKO : public GameInteractionEffectBase { + class OneHitKO : public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ModifyDefenseModifier: public GameInteractionEffectBase { + class ModifyDefenseModifier: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class GiveOrTakeShield: public GameInteractionEffectBase { + class GiveOrTakeShield: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class TeleportPlayer: public GameInteractionEffectBase { + class TeleportPlayer: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class ClearAssignedButtons: public GameInteractionEffectBase { + class ClearAssignedButtons: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class SetTimeOfDay: public GameInteractionEffectBase { + class SetTimeOfDay: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class SetCollisionViewer: public GameInteractionEffectBase { + class SetCollisionViewer: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class SetCosmeticsColor: public GameInteractionEffectBase { + class SetCosmeticsColor: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; @@ -204,52 +212,52 @@ namespace GameInteractionEffect { void _Apply() override; }; - class PressButton: public GameInteractionEffectBase { + class PressButton: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class PressRandomButton: public GameInteractionEffectBase { + class PressRandomButton: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class AddOrTakeAmmo: public GameInteractionEffectBase { + class AddOrTakeAmmo: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class RandomBombFuseTimer: public GameInteractionEffectBase { + class RandomBombFuseTimer: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class DisableLedgeGrabs: public GameInteractionEffectBase { + class DisableLedgeGrabs: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class RandomWind: public GameInteractionEffectBase { + class RandomWind: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class RandomBonks: public GameInteractionEffectBase { + class RandomBonks: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class PlayerInvincibility: public GameInteractionEffectBase { + class PlayerInvincibility: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class SlipperyFloor: public GameInteractionEffectBase { + class SlipperyFloor: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor.cpp index eb330947b..dbb4782fd 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.cpp @@ -31,18 +31,25 @@ GameInteractionEffectQueryResult GameInteractor::ApplyEffect(GameInteractionEffe return effect->Apply(); } -GameInteractionEffectQueryResult GameInteractor::RemoveEffect(GameInteractionEffectBase* effect) { +GameInteractionEffectQueryResult GameInteractor::RemoveEffect(RemovableGameInteractionEffect* effect) { return effect->Remove(); } // 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 56f0f4701..7503dbd78 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -7,6 +7,11 @@ #include "soh/Enhancements/item-tables/ItemTableTypes.h" #include +typedef enum { + GI_SCHEME_SAIL, + GI_SCHEME_CROWD_CONTROL, +} GIScheme; + typedef enum { /* 0x00 */ GI_LINK_SIZE_NORMAL, /* 0x01 */ GI_LINK_SIZE_GIANT, @@ -92,9 +97,15 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state); #ifdef __cplusplus - +#include #include #include +#include + +#ifdef ENABLE_REMOTE_CONTROL +#include +#include +#endif #define DEFINE_HOOK(name, type) \ struct name { \ @@ -132,17 +143,50 @@ public: static void SetPacifistMode(bool active); }; + #ifdef ENABLE_REMOTE_CONTROL + bool isRemoteInteractorEnabled; + bool isRemoteInteractorConnected; + + void EnableRemoteInteractor(); + void DisableRemoteInteractor(); + void RegisterRemoteDataHandler(std::function method); + void RegisterRemoteJsonHandler(std::function method); + void RegisterRemoteConnectedHandler(std::function method); + void RegisterRemoteDisconnectedHandler(std::function method); + void TransmitDataToRemote(const char* payload); + void TransmitJsonToRemote(nlohmann::json packet); + #endif + // Effects static GameInteractionEffectQueryResult CanApplyEffect(GameInteractionEffectBase* effect); static GameInteractionEffectQueryResult ApplyEffect(GameInteractionEffectBase* effect); - static GameInteractionEffectQueryResult RemoveEffect(GameInteractionEffectBase* effect); + static GameInteractionEffectQueryResult RemoveEffect(RemovableGameInteractionEffect* effect); // Game Hooks - template struct RegisteredGameHooks { inline static std::vector functions; }; - template void RegisterGameHook(typename H::fn h) { RegisteredGameHooks::functions.push_back(h); } + uint32_t nextHookId = 1; + template struct RegisteredGameHooks { inline static std::unordered_map functions; }; + template struct HooksToUnregister { inline static std::vector hooks; }; + template uint32_t RegisterGameHook(typename H::fn h) { + // Ensure hook id is unique and not 0, which is reserved for invalid hooks + if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; + while (RegisteredGameHooks::functions.find(this->nextHookId) != RegisteredGameHooks::functions.end()) { + this->nextHookId++; + } + + RegisteredGameHooks::functions[this->nextHookId] = h; + return this->nextHookId++; + } + template void UnregisterGameHook(uint32_t id) { + HooksToUnregister::hooks.push_back(id); + } + template void ExecuteHooks(Args&&... args) { - for (auto& fn : RegisteredGameHooks::functions) { - fn(std::forward(args)...); + for (auto& hookId : HooksToUnregister::hooks) { + RegisteredGameHooks::functions.erase(hookId); + } + HooksToUnregister::hooks.clear(); + for (auto& hook : RegisteredGameHooks::functions) { + hook.second(std::forward(args)...); } } @@ -194,10 +238,12 @@ public: DEFINE_HOOK(OnSetGameLanguage, void()); + DEFINE_HOOK(OnFileDropped, void(std::string filePath)); DEFINE_HOOK(OnAssetAltChange, void()); + DEFINE_HOOK(OnKaleidoUpdate, 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); @@ -238,6 +284,21 @@ public: static GameInteractionEffectQueryResult SpawnEnemyWithOffset(uint32_t enemyId, int32_t enemyParams); static GameInteractionEffectQueryResult SpawnActor(uint32_t actorId, int32_t actorParams); }; + + private: + #ifdef ENABLE_REMOTE_CONTROL + IPaddress remoteIP; + TCPsocket remoteSocket; + std::thread remoteThreadReceive; + std::function remoteDataHandler; + std::function remoteJsonHandler; + std::function remoteConnectedHandler; + std::function remoteDisconnectedHandler; + + void ReceiveFromServer(); + void HandleRemoteData(char payload[512]); + void HandleRemoteJson(std::string payload); + #endif }; #endif /* __cplusplus */ diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 4bd5354a6..911c47a71 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -187,3 +187,9 @@ void GameInteractor_ExecuteOnSetGameLanguage() { void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void)) { GameInteractor::Instance->RegisterGameHook(fn); } + +//MARK: Pause Menu + +void GameInteractor_ExecuteOnKaleidoUpdate() { + GameInteractor::Instance->ExecuteHooks(); +} diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index 7b7b226fa..5c86cb39b 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -60,6 +60,9 @@ void GameInteractor_ExecuteOnSetGameLanguage(); // MARK: - System void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void)); +//Mark: - Pause Menu +void GameInteractor_ExecuteOnKaleidoUpdate(); + #ifdef __cplusplus } #endif diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp index 854b7dc11..6d66d1c8b 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp @@ -111,9 +111,9 @@ void GameInteractor::RawAction::FreezePlayer() { void GameInteractor::RawAction::BurnPlayer() { Player* player = GET_PLAYER(gPlayState); for (int i = 0; i < 18; i++) { - player->flameTimers[i] = Rand_S16Offset(0, 200); + player->bodyFlameTimers[i] = Rand_S16Offset(0, 200); } - player->isBurning = true; + player->bodyIsBurning = true; func_80837C0C(gPlayState, player, 0, 0, 0, 0, 0); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Remote.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Remote.cpp new file mode 100644 index 000000000..2cbd6b379 --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Remote.cpp @@ -0,0 +1,182 @@ +#ifdef ENABLE_REMOTE_CONTROL + +#include "GameInteractor.h" +#include +#include +#include +#include +#include +#include +#include + +// MARK: - Remote + +void GameInteractor::EnableRemoteInteractor() { + if (isRemoteInteractorEnabled) { + return; + } + + if (SDLNet_ResolveHost(&remoteIP, CVarGetString("gRemote.IP", "127.0.0.1"), CVarGetInteger("gRemote.Port", 43384)) == -1) { + SPDLOG_ERROR("[GameInteractor] SDLNet_ResolveHost: {}", SDLNet_GetError()); + } + + isRemoteInteractorEnabled = true; + + // First check if there is a thread running, if so, join it + if (remoteThreadReceive.joinable()) { + remoteThreadReceive.join(); + } + + remoteThreadReceive = std::thread(&GameInteractor::ReceiveFromServer, this); +} + +/** + * Raw data handler + * + * If you are developing a new remote, you should probably use the json methods instead. This + * method requires you to parse the data and ensure packets are complete manually, we cannot + * gaurentee that the data will be complete, or that it will only contain one packet with this + */ +void GameInteractor::RegisterRemoteDataHandler(std::function method) { + remoteDataHandler = method; +} + +/** + * Json handler + * + * This method will be called when a complete json packet is received. All json packets must + * be delimited by a null terminator (\0). + */ +void GameInteractor::RegisterRemoteJsonHandler(std::function method) { + remoteJsonHandler = method; +} + +void GameInteractor::RegisterRemoteConnectedHandler(std::function method) { + remoteConnectedHandler = method; +} + +void GameInteractor::RegisterRemoteDisconnectedHandler(std::function method) { + remoteDisconnectedHandler = method; +} + +void GameInteractor::DisableRemoteInteractor() { + if (!isRemoteInteractorEnabled) { + return; + } + + isRemoteInteractorEnabled = false; + remoteThreadReceive.join(); + remoteDataHandler = nullptr; + remoteJsonHandler = nullptr; + remoteConnectedHandler = nullptr; + remoteDisconnectedHandler = nullptr; +} + +void GameInteractor::TransmitDataToRemote(const char* payload) { + SDLNet_TCP_Send(remoteSocket, payload, strlen(payload) + 1); +} + +// Appends a newline character to the end of the json payload and sends it to the remote +void GameInteractor::TransmitJsonToRemote(nlohmann::json payload) { + TransmitDataToRemote(payload.dump().c_str()); +} + +// MARK: - Private + +std::string receivedData; + +void GameInteractor::ReceiveFromServer() { + while (isRemoteInteractorEnabled) { + while (!isRemoteInteractorConnected && isRemoteInteractorEnabled) { + SPDLOG_TRACE("[GameInteractor] Attempting to make connection to server..."); + remoteSocket = SDLNet_TCP_Open(&remoteIP); + + if (remoteSocket) { + isRemoteInteractorConnected = true; + SPDLOG_INFO("[GameInteractor] Connection to server established!"); + + if (remoteConnectedHandler) { + remoteConnectedHandler(); + } + break; + } + } + + SDLNet_SocketSet socketSet = SDLNet_AllocSocketSet(1); + if (remoteSocket) { + SDLNet_TCP_AddSocket(socketSet, remoteSocket); + } + + // Listen to socket messages + while (isRemoteInteractorConnected && remoteSocket && isRemoteInteractorEnabled) { + // we check first if socket has data, to not block in the TCP_Recv + int socketsReady = SDLNet_CheckSockets(socketSet, 0); + + if (socketsReady == -1) { + SPDLOG_ERROR("[GameInteractor] SDLNet_CheckSockets: {}", SDLNet_GetError()); + break; + } + + if (socketsReady == 0) { + continue; + } + + char remoteDataReceived[512]; + memset(remoteDataReceived, 0, sizeof(remoteDataReceived)); + int len = SDLNet_TCP_Recv(remoteSocket, &remoteDataReceived, sizeof(remoteDataReceived)); + if (!len || !remoteSocket || len == -1) { + SPDLOG_ERROR("[GameInteractor] SDLNet_TCP_Recv: {}", SDLNet_GetError()); + break; + } + + HandleRemoteData(remoteDataReceived); + + receivedData.append(remoteDataReceived, len); + + // Proess all complete packets + size_t delimiterPos = receivedData.find('\0'); + while (delimiterPos != std::string::npos) { + // Extract the complete packet until the delimiter + std::string packet = receivedData.substr(0, delimiterPos); + // Remove the packet (including the delimiter) from the received data + receivedData.erase(0, delimiterPos + 1); + HandleRemoteJson(packet); + // Find the next delimiter + delimiterPos = receivedData.find('\0'); + } + } + + if (isRemoteInteractorConnected) { + SDLNet_TCP_Close(remoteSocket); + isRemoteInteractorConnected = false; + if (remoteDisconnectedHandler) { + remoteDisconnectedHandler(); + } + SPDLOG_INFO("[GameInteractor] Ending receiving thread..."); + } + } +} + +void GameInteractor::HandleRemoteData(char payload[512]) { + if (remoteDataHandler) { + remoteDataHandler(payload); + return; + } +} + +void GameInteractor::HandleRemoteJson(std::string payload) { + nlohmann::json jsonPayload; + try { + jsonPayload = nlohmann::json::parse(payload); + } catch (const std::exception& e) { + SPDLOG_ERROR("[GameInteractor] Failed to parse json: \n{}\n{}\n", payload, e.what()); + return; + } + + if (remoteJsonHandler) { + remoteJsonHandler(jsonPayload); + return; + } +} + +#endif diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Sail.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Sail.cpp new file mode 100644 index 000000000..a209d8c50 --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Sail.cpp @@ -0,0 +1,471 @@ +#ifdef ENABLE_REMOTE_CONTROL + +#include "GameInteractor_Sail.h" +#include +#include +#include + +template +bool IsType(const SrcType* src) { + return dynamic_cast(src) != nullptr; +} + +void GameInteractorSail::Enable() { + if (isEnabled) { + return; + } + + isEnabled = true; + GameInteractor::Instance->EnableRemoteInteractor(); + GameInteractor::Instance->RegisterRemoteJsonHandler([&](nlohmann::json payload) { + HandleRemoteJson(payload); + }); + GameInteractor::Instance->RegisterRemoteConnectedHandler([&]() { + RegisterHooks(); + }); +} + +void GameInteractorSail::Disable() { + if (!isEnabled) { + return; + } + + isEnabled = false; + GameInteractor::Instance->DisableRemoteInteractor(); +} + +void GameInteractorSail::HandleRemoteJson(nlohmann::json payload) { + SPDLOG_INFO("[GameInteractorSail] Received payload: \n{}", payload.dump()); + + nlohmann::json responsePayload; + responsePayload["type"] = "result"; + responsePayload["status"] = "failure"; + + try { + if (!payload.contains("id")) { + SPDLOG_ERROR("[GameInteractorSail] Received payload without ID"); + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } + + responsePayload["id"] = payload["id"]; + + if (!payload.contains("type")) { + SPDLOG_ERROR("[GameInteractorSail] Received payload without type"); + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } + + std::string payloadType = payload["type"].get(); + + if (payloadType == "command") { + if (!payload.contains("command")) { + SPDLOG_ERROR("[GameInteractorSail] Received command payload without command"); + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } + + std::string command = payload["command"].get(); + std::reinterpret_pointer_cast(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch(command); + responsePayload["status"] = "success"; + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } else if (payloadType == "effect") { + if (!payload.contains("effect") || !payload["effect"].contains("type")) { + SPDLOG_ERROR("[GameInteractorSail] Received effect payload without effect type"); + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } + + std::string effectType = payload["effect"]["type"].get(); + + // Special case for "command" effect, so we can also run commands from the `simple_twitch_sail` script + if (effectType == "command") { + if (!payload["effect"].contains("command")) { + SPDLOG_ERROR("[GameInteractorSail] Received command effect payload without command"); + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } + + std::string command = payload["effect"]["command"].get(); + std::reinterpret_pointer_cast(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch(command); + responsePayload["status"] = "success"; + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } + + if (effectType != "apply" && effectType != "remove") { + SPDLOG_ERROR("[GameInteractorSail] Received effect payload with unknown effect type: {}", effectType); + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } + + if (!GameInteractor::IsSaveLoaded()) { + responsePayload["status"] = "try_again"; + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } + + GameInteractionEffectBase* giEffect = EffectFromJson(payload["effect"]); + if (giEffect) { + GameInteractionEffectQueryResult result; + if (effectType == "remove") { + if (IsType(giEffect)) { + result = dynamic_cast(giEffect)->Remove(); + } else { + result = GameInteractionEffectQueryResult::NotPossible; + } + } else { + result = giEffect->Apply(); + } + + if (result == GameInteractionEffectQueryResult::Possible) { + responsePayload["status"] = "success"; + } else if (result == GameInteractionEffectQueryResult::TemporarilyNotPossible) { + responsePayload["status"] = "try_again"; + } + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } + } else { + SPDLOG_ERROR("[GameInteractorSail] Unknown payload type: {}", payloadType); + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + return; + } + + // If we get here, something went wrong, send the failure response + SPDLOG_ERROR("[GameInteractorSail] Failed to handle remote JSON, sending failure response"); + GameInteractor::Instance->TransmitJsonToRemote(responsePayload); + } catch (const std::exception& e) { + SPDLOG_ERROR("[GameInteractorSail] Exception handling remote JSON: {}", e.what()); + } catch (...) { + SPDLOG_ERROR("[GameInteractorSail] Unknown exception handling remote JSON"); + } +} + +GameInteractionEffectBase* GameInteractorSail::EffectFromJson(nlohmann::json payload) { + if (!payload.contains("name")) { + return nullptr; + } + + std::string name = payload["name"].get(); + + if (name == "SetSceneFlag") { + auto effect = new GameInteractionEffect::SetSceneFlag(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + effect->parameters[1] = payload["parameters"][1].get(); + effect->parameters[2] = payload["parameters"][2].get(); + } + return effect; + } else if (name == "UnsetSceneFlag") { + auto effect = new GameInteractionEffect::UnsetSceneFlag(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + effect->parameters[1] = payload["parameters"][1].get(); + effect->parameters[2] = payload["parameters"][2].get(); + } + return effect; + } else if (name == "SetFlag") { + auto effect = new GameInteractionEffect::SetFlag(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + effect->parameters[1] = payload["parameters"][1].get(); + } + return effect; + } else if (name == "UnsetFlag") { + auto effect = new GameInteractionEffect::UnsetFlag(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + effect->parameters[1] = payload["parameters"][1].get(); + } + return effect; + } else if (name == "ModifyHeartContainers") { + auto effect = new GameInteractionEffect::ModifyHeartContainers(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "FillMagic") { + return new GameInteractionEffect::FillMagic(); + } else if (name == "EmptyMagic") { + return new GameInteractionEffect::EmptyMagic(); + } else if (name == "ModifyRupees") { + auto effect = new GameInteractionEffect::ModifyRupees(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "NoUI") { + return new GameInteractionEffect::NoUI(); + } else if (name == "ModifyGravity") { + auto effect = new GameInteractionEffect::ModifyGravity(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "ModifyHealth") { + auto effect = new GameInteractionEffect::ModifyHealth(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "SetPlayerHealth") { + auto effect = new GameInteractionEffect::SetPlayerHealth(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "FreezePlayer") { + return new GameInteractionEffect::FreezePlayer(); + } else if (name == "BurnPlayer") { + return new GameInteractionEffect::BurnPlayer(); + } else if (name == "ElectrocutePlayer") { + return new GameInteractionEffect::ElectrocutePlayer(); + } else if (name == "KnockbackPlayer") { + auto effect = new GameInteractionEffect::KnockbackPlayer(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "ModifyLinkSize") { + auto effect = new GameInteractionEffect::ModifyLinkSize(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "InvisibleLink") { + return new GameInteractionEffect::InvisibleLink(); + } else if (name == "PacifistMode") { + return new GameInteractionEffect::PacifistMode(); + } else if (name == "DisableZTargeting") { + return new GameInteractionEffect::DisableZTargeting(); + } else if (name == "WeatherRainstorm") { + return new GameInteractionEffect::WeatherRainstorm(); + } else if (name == "ReverseControls") { + return new GameInteractionEffect::ReverseControls(); + } else if (name == "ForceEquipBoots") { + auto effect = new GameInteractionEffect::ForceEquipBoots(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "ModifyRunSpeedModifier") { + auto effect = new GameInteractionEffect::ModifyRunSpeedModifier(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "OneHitKO") { + return new GameInteractionEffect::OneHitKO(); + } else if (name == "ModifyDefenseModifier") { + auto effect = new GameInteractionEffect::ModifyDefenseModifier(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "GiveOrTakeShield") { + auto effect = new GameInteractionEffect::GiveOrTakeShield(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "TeleportPlayer") { + auto effect = new GameInteractionEffect::TeleportPlayer(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "ClearAssignedButtons") { + auto effect = new GameInteractionEffect::ClearAssignedButtons(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "SetTimeOfDay") { + auto effect = new GameInteractionEffect::SetTimeOfDay(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "SetCollisionViewer") { + return new GameInteractionEffect::SetCollisionViewer(); + } else if (name == "SetCosmeticsColor") { + auto effect = new GameInteractionEffect::SetCosmeticsColor(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + effect->parameters[1] = payload["parameters"][1].get(); + } + return effect; + } else if (name == "RandomizeCosmetics") { + return new GameInteractionEffect::RandomizeCosmetics(); + } else if (name == "PressButton") { + auto effect = new GameInteractionEffect::PressButton(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "PressRandomButton") { + auto effect = new GameInteractionEffect::PressRandomButton(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + } + return effect; + } else if (name == "AddOrTakeAmmo") { + auto effect = new GameInteractionEffect::AddOrTakeAmmo(); + if (payload.contains("parameters")) { + effect->parameters[0] = payload["parameters"][0].get(); + effect->parameters[1] = payload["parameters"][1].get(); + } + return effect; + } else if (name == "RandomBombFuseTimer") { + return new GameInteractionEffect::RandomBombFuseTimer(); + } else if (name == "DisableLedgeGrabs") { + return new GameInteractionEffect::DisableLedgeGrabs(); + } else if (name == "RandomWind") { + return new GameInteractionEffect::RandomWind(); + } else if (name == "RandomBonks") { + return new GameInteractionEffect::RandomBonks(); + } else if (name == "PlayerInvincibility") { + return new GameInteractionEffect::PlayerInvincibility(); + } else if (name == "SlipperyFloor") { + return new GameInteractionEffect::SlipperyFloor(); + } else { + SPDLOG_INFO("[GameInteractorSail] Unknown effect name: {}", name); + return nullptr; + } +} + +// Workaround until we have a way to unregister hooks +static bool hasRegisteredHooks = false; + +void GameInteractorSail::RegisterHooks() { + if (hasRegisteredHooks) { + return; + } + hasRegisteredHooks = true; + + GameInteractor::Instance->RegisterGameHook([](int32_t sceneNum) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return; + + nlohmann::json payload; + payload["id"] = std::rand(); + payload["type"] = "hook"; + payload["hook"]["type"] = "OnTransitionEnd"; + payload["hook"]["sceneNum"] = sceneNum; + + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](int32_t fileNum) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return; + + nlohmann::json payload; + payload["id"] = std::rand(); + payload["type"] = "hook"; + payload["hook"]["type"] = "OnLoadGame"; + payload["hook"]["fileNum"] = fileNum; + + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](int32_t fileNum) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return; + + nlohmann::json payload; + payload["id"] = std::rand(); + payload["type"] = "hook"; + payload["hook"]["type"] = "OnExitGame"; + payload["hook"]["fileNum"] = fileNum; + + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](GetItemEntry itemEntry) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return; + + nlohmann::json payload; + payload["id"] = std::rand(); + payload["type"] = "hook"; + payload["hook"]["type"] = "OnItemReceive"; + payload["hook"]["tableId"] = itemEntry.tableId; + payload["hook"]["getItemId"] = itemEntry.getItemId; + + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](void* refActor) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return; + + Actor* actor = (Actor*)refActor; + nlohmann::json payload; + payload["id"] = std::rand(); + payload["type"] = "hook"; + payload["hook"]["type"] = "OnEnemyDefeat"; + payload["hook"]["actorId"] = actor->id; + payload["hook"]["params"] = actor->params; + + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](void* refActor) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return; + + Actor* actor = (Actor*)refActor; + nlohmann::json payload; + payload["id"] = std::rand(); + payload["type"] = "hook"; + payload["hook"]["type"] = "OnActorInit"; + payload["hook"]["actorId"] = actor->id; + payload["hook"]["params"] = actor->params; + + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](int16_t flagType, int16_t flag) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return; + + nlohmann::json payload; + payload["id"] = std::rand(); + payload["type"] = "hook"; + payload["hook"]["type"] = "OnFlagSet"; + payload["hook"]["flagType"] = flagType; + payload["hook"]["flag"] = flag; + + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](int16_t flagType, int16_t flag) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return; + + nlohmann::json payload; + payload["id"] = std::rand(); + payload["type"] = "hook"; + payload["hook"]["type"] = "OnFlagUnset"; + payload["hook"]["flagType"] = flagType; + payload["hook"]["flag"] = flag; + + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum, int16_t flagType, int16_t flag) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return; + + nlohmann::json payload; + payload["id"] = std::rand(); + payload["type"] = "hook"; + payload["hook"]["type"] = "OnSceneFlagSet"; + payload["hook"]["flagType"] = flagType; + payload["hook"]["flag"] = flag; + payload["hook"]["sceneNum"] = sceneNum; + + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum, int16_t flagType, int16_t flag) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return; + + nlohmann::json payload; + payload["id"] = std::rand(); + payload["type"] = "hook"; + payload["hook"]["type"] = "OnSceneFlagUnset"; + payload["hook"]["flagType"] = flagType; + payload["hook"]["flag"] = flag; + payload["hook"]["sceneNum"] = sceneNum; + + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); +} + +#endif diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Sail.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Sail.h new file mode 100644 index 000000000..cb90c65c7 --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Sail.h @@ -0,0 +1,29 @@ +#ifdef ENABLE_REMOTE_CONTROL + +#ifdef __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./GameInteractor.h" + +class GameInteractorSail { + private: + bool isEnabled; + + void HandleRemoteJson(nlohmann::json payload); + GameInteractionEffectBase* EffectFromJson(nlohmann::json payload); + void RegisterHooks(); + public: + static GameInteractorSail* Instance; + void Enable(); + void Disable(); +}; +#endif +#endif diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index a6e5a47e8..efcf44efb 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -618,7 +618,7 @@ void DrawGameplayStatsOptionsTab() { } void GameplayStatsWindow::DrawElement() { - ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Gameplay Stats", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { ImGui::End(); return; diff --git a/soh/soh/Enhancements/item-tables/ItemTableTypes.h b/soh/soh/Enhancements/item-tables/ItemTableTypes.h index f16762db9..6282275b1 100644 --- a/soh/soh/Enhancements/item-tables/ItemTableTypes.h +++ b/soh/soh/Enhancements/item-tables/ItemTableTypes.h @@ -30,10 +30,13 @@ typedef enum GetItemCategory { } GetItemCategory; #define GET_ITEM(itemId, objectId, drawId, textId, field, chestAnim, itemCategory, modIndex, getItemId) \ - { itemId, field, (chestAnim != CHEST_ANIM_SHORT ? 1 : -1) * (drawId + 1), textId, objectId, modIndex, getItemId, drawId, true, ITEM_FROM_NPC, itemCategory, NULL } + { itemId, field, (chestAnim != CHEST_ANIM_SHORT ? 1 : -1) * (drawId + 1), textId, objectId, modIndex, modIndex, getItemId, drawId, true, ITEM_FROM_NPC, itemCategory, NULL } + +#define GET_ITEM_CUSTOM_TABLE(itemId, objectId, drawId, textId, field, chestAnim, itemCategory, modIndex, tableId, getItemId) \ + { itemId, field, (chestAnim != CHEST_ANIM_SHORT ? 1 : -1) * (drawId + 1), textId, objectId, modIndex, tableId, getItemId, drawId, true, ITEM_FROM_NPC, itemCategory, NULL } #define GET_ITEM_NONE \ - { ITEM_NONE, 0, 0, 0, 0, 0, 0, 0, false, ITEM_FROM_NPC, ITEM_CATEGORY_JUNK, NULL } + { ITEM_NONE, 0, 0, 0, 0, 0, 0, 0, 0, false, ITEM_FROM_NPC, ITEM_CATEGORY_JUNK, NULL } typedef struct PlayState PlayState; typedef struct GetItemEntry GetItemEntry; @@ -46,7 +49,8 @@ typedef struct GetItemEntry { /* 0x02 */ int16_t gi; // defines the draw id and chest opening animation /* 0x03 */ uint16_t textId; /* 0x04 */ uint16_t objectId; - /* 0x06 */ uint16_t modIndex; // 0 = Vanilla, 1 = Randomizer, future mods will increment up? + /* 0x06 */ uint16_t modIndex; // Primarily used for determining whether to use Item_Give or Randomizer_Item_Give + /* 0x07 */ uint16_t tableId; // GetItemEntry table this entry is in (usually the same as modIndex, but not always) /* 0x08 */ int16_t getItemId; /* 0x0A */ uint16_t gid; // Stores the GID value unmodified for future reference. /* 0x0C */ uint16_t collectable; // determines whether the item can be collected on the overworld. Will be true in most cases. diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 13d90a393..d1147f5fc 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -23,19 +23,37 @@ #include "src/overlays/actors/ovl_En_Tp/z_en_tp.h" #include "src/overlays/actors/ovl_En_Firefly/z_en_firefly.h" #include "src/overlays/actors/ovl_En_Xc/z_en_xc.h" +#include "src/overlays/actors/ovl_Obj_Switch/z_obj_switch.h" +#include "objects/object_link_boy/object_link_boy.h" +#include "objects/object_link_child/object_link_child.h" extern "C" { #include +#include "align_asset_macro.h" #include "macros.h" #include "functions.h" #include "variables.h" #include "functions.h" +#include "src/overlays/actors/ovl_En_Door/z_en_door.h" +void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction); +void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName); + extern SaveContext gSaveContext; extern PlayState* gPlayState; extern void Overlay_DisplayText(float duration, const char* text); uint32_t ResourceMgr_IsSceneMasterQuest(s16 sceneNum); } +// GreyScaleEndDlist +#define dgEndGrayscaleAndEndDlistDL "__OTR__helpers/cosmetics/gEndGrayscaleAndEndDlistDL" +static const ALIGN_ASSET(2) char gEndGrayscaleAndEndDlistDL[] = dgEndGrayscaleAndEndDlistDL; + +// This is used for the Temple of Time Medalions' color +#define dtokinoma_room_0DL_007A70 "__OTR__scenes/shared/tokinoma_scene/tokinoma_room_0DL_007A70" +static const ALIGN_ASSET(2) char tokinoma_room_0DL_007A70[] = dtokinoma_room_0DL_007A70; +#define dtokinoma_room_0DL_007FD0 "__OTR__scenes/shared/tokinoma_scene/tokinoma_room_0DL_007FD0" +static const ALIGN_ASSET(2) char tokinoma_room_0DL_007FD0[] = dtokinoma_room_0DL_007FD0; + // TODO: When there's more uses of something like this, create a new GI::RawAction? void ReloadSceneTogglingLinkAge() { gPlayState->nextEntranceIndex = gSaveContext.entranceIndex; @@ -47,7 +65,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 +76,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 +87,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 +124,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 +135,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 +144,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 +159,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 +171,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 +183,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 +211,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 +252,7 @@ void RegisterSwitchAge() { void RegisterOcarinaTimeTravel() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded()) { + if (!GameInteractor::IsSaveLoaded(true)) { CVarClear("gTimeTravel"); return; } @@ -400,6 +421,52 @@ void RegisterShadowTag() { }); } +static bool hasAffectedHealth = false; +void UpdatePermanentHeartLossState() { + if (!GameInteractor::IsSaveLoaded()) return; + + if (!CVarGetInteger("gPermanentHeartLoss", 0) && hasAffectedHealth) { + uint8_t heartContainers = gSaveContext.sohStats.heartContainers; // each worth 16 health + uint8_t heartPieces = gSaveContext.sohStats.heartPieces; // each worth 4 health, but only in groups of 4 + uint8_t startingHealth = 16 * 3; + + + uint8_t newCapacity = startingHealth + (heartContainers * 16) + ((heartPieces - (heartPieces % 4)) * 4); + gSaveContext.healthCapacity = MAX(newCapacity, gSaveContext.healthCapacity); + gSaveContext.health = MIN(gSaveContext.health, gSaveContext.healthCapacity); + hasAffectedHealth = false; + } +} + +void RegisterPermanentHeartLoss() { + GameInteractor::Instance->RegisterGameHook([](int16_t fileNum) { + hasAffectedHealth = false; + UpdatePermanentHeartLossState(); + }); + + GameInteractor::Instance->RegisterGameHook([]() { + if (!CVarGetInteger("gPermanentHeartLoss", 0) || !GameInteractor::IsSaveLoaded()) return; + + if (gSaveContext.healthCapacity > 16 && gSaveContext.healthCapacity - gSaveContext.health >= 16) { + gSaveContext.healthCapacity -= 16; + gSaveContext.health = MIN(gSaveContext.health, gSaveContext.healthCapacity); + hasAffectedHealth = true; + } + }); +}; + +void RegisterDeleteFileOnDeath() { + GameInteractor::Instance->RegisterGameHook([]() { + if (!CVarGetInteger("gDeleteFileOnDeath", 0) || !GameInteractor::IsSaveLoaded() || &gPlayState->gameOverCtx == NULL || &gPlayState->pauseCtx == NULL) return; + + if (gPlayState->gameOverCtx.state == GAMEOVER_DEATH_MENU && gPlayState->pauseCtx.state == 9) { + SaveManager::Instance->DeleteZeldaFile(gSaveContext.fileNum); + hasAffectedHealth = false; + std::reinterpret_pointer_cast(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset"); + } + }); +} + struct DayTimeGoldSkulltulas { uint16_t scene; uint16_t room; @@ -456,70 +523,93 @@ void RegisterDaytimeGoldSkultullas() { }); } -void RegisterHyperBosses() { - GameInteractor::Instance->RegisterGameHook([](void* refActor) { - // Run the update function a second time to make bosses move and act twice as fast. +bool IsHyperBossesActive() { + return CVarGetInteger("gHyperBosses", 0) || + (IS_BOSS_RUSH && gSaveContext.bossRushOptions[BR_OPTIONS_HYPERBOSSES] == BR_CHOICE_HYPERBOSSES_YES); +} - Player* player = GET_PLAYER(gPlayState); - Actor* actor = static_cast(refActor); +void UpdateHyperBossesState() { + static uint32_t actorUpdateHookId = 0; + if (actorUpdateHookId != 0) { + GameInteractor::Instance->UnregisterGameHook(actorUpdateHookId); + actorUpdateHookId = 0; + } - uint8_t isBossActor = - actor->id == ACTOR_BOSS_GOMA || // Gohma - actor->id == ACTOR_BOSS_DODONGO || // King Dodongo - actor->id == ACTOR_EN_BDFIRE || // King Dodongo Fire Breath - actor->id == ACTOR_BOSS_VA || // Barinade - actor->id == ACTOR_BOSS_GANONDROF || // Phantom Ganon - actor->id == ACTOR_EN_FHG_FIRE || // Phantom Ganon/Ganondorf Energy Ball/Thunder - actor->id == ACTOR_EN_FHG || // Phantom Ganon's Horse - actor->id == ACTOR_BOSS_FD || actor->id == ACTOR_BOSS_FD2 || // Volvagia (grounded/flying) - actor->id == ACTOR_EN_VB_BALL || // Volvagia Rocks - actor->id == ACTOR_BOSS_MO || // Morpha - actor->id == ACTOR_BOSS_SST || // Bongo Bongo - actor->id == ACTOR_BOSS_TW || // Twinrova - actor->id == ACTOR_BOSS_GANON || // Ganondorf - actor->id == ACTOR_BOSS_GANON2; // Ganon + if (IsHyperBossesActive()) { + actorUpdateHookId = GameInteractor::Instance->RegisterGameHook([](void* refActor) { + // Run the update function a second time to make bosses move and act twice as fast. - uint8_t hyperBossesActive = - CVarGetInteger("gHyperBosses", 0) || - (IS_BOSS_RUSH && - gSaveContext.bossRushOptions[BR_OPTIONS_HYPERBOSSES] == BR_CHOICE_HYPERBOSSES_YES); + Player* player = GET_PLAYER(gPlayState); + Actor* actor = static_cast(refActor); - // Don't apply during cutscenes because it causes weird behaviour and/or crashes on some bosses. - if (hyperBossesActive && isBossActor && !Player_InBlockingCsMode(gPlayState, player)) { - // Barinade needs to be updated in sequence to avoid unintended behaviour. - if (actor->id == ACTOR_BOSS_VA) { - // params -1 is BOSSVA_BODY - if (actor->params == -1) { - Actor* actorList = gPlayState->actorCtx.actorLists[ACTORCAT_BOSS].head; - while (actorList != NULL) { - GameInteractor::RawAction::UpdateActor(actorList); - actorList = actorList->next; + uint8_t isBossActor = + actor->id == ACTOR_BOSS_GOMA || // Gohma + actor->id == ACTOR_BOSS_DODONGO || // King Dodongo + actor->id == ACTOR_EN_BDFIRE || // King Dodongo Fire Breath + actor->id == ACTOR_BOSS_VA || // Barinade + actor->id == ACTOR_BOSS_GANONDROF || // Phantom Ganon + actor->id == ACTOR_EN_FHG_FIRE || // Phantom Ganon/Ganondorf Energy Ball/Thunder + actor->id == ACTOR_EN_FHG || // Phantom Ganon's Horse + actor->id == ACTOR_BOSS_FD || actor->id == ACTOR_BOSS_FD2 || // Volvagia (grounded/flying) + actor->id == ACTOR_EN_VB_BALL || // Volvagia Rocks + actor->id == ACTOR_BOSS_MO || // Morpha + actor->id == ACTOR_BOSS_SST || // Bongo Bongo + actor->id == ACTOR_BOSS_TW || // Twinrova + actor->id == ACTOR_BOSS_GANON || // Ganondorf + actor->id == ACTOR_BOSS_GANON2; // Ganon + + // Don't apply during cutscenes because it causes weird behaviour and/or crashes on some bosses. + if (IsHyperBossesActive() && isBossActor && !Player_InBlockingCsMode(gPlayState, player)) { + // Barinade needs to be updated in sequence to avoid unintended behaviour. + if (actor->id == ACTOR_BOSS_VA) { + // params -1 is BOSSVA_BODY + if (actor->params == -1) { + Actor* actorList = gPlayState->actorCtx.actorLists[ACTORCAT_BOSS].head; + while (actorList != NULL) { + GameInteractor::RawAction::UpdateActor(actorList); + actorList = actorList->next; + } } + } else { + GameInteractor::RawAction::UpdateActor(actor); } - } else { - GameInteractor::RawAction::UpdateActor(actor); } - } + }); + } +} + +void RegisterHyperBosses() { + UpdateHyperBossesState(); + GameInteractor::Instance->RegisterGameHook([](int16_t fileNum) { + UpdateHyperBossesState(); }); } -void RegisterHyperEnemies() { - GameInteractor::Instance->RegisterGameHook([](void* refActor) { - // Run the update function a second time to make enemies and minibosses move and act twice as fast. +void UpdateHyperEnemiesState() { + static uint32_t actorUpdateHookId = 0; + if (actorUpdateHookId != 0) { + GameInteractor::Instance->UnregisterGameHook(actorUpdateHookId); + actorUpdateHookId = 0; + } - Player* player = GET_PLAYER(gPlayState); - Actor* actor = static_cast(refActor); + if (CVarGetInteger("gHyperEnemies", 0)) { + actorUpdateHookId = GameInteractor::Instance->RegisterGameHook([](void* refActor) { + // Run the update function a second time to make enemies and minibosses move and act twice as fast. - // Some enemies are not in the ACTORCAT_ENEMY category, and some are that aren't really enemies. - bool isEnemy = actor->category == ACTORCAT_ENEMY || actor->id == ACTOR_EN_TORCH2; - bool isExcludedEnemy = actor->id == ACTOR_EN_FIRE_ROCK || actor->id == ACTOR_EN_ENCOUNT2; + Player* player = GET_PLAYER(gPlayState); + Actor* actor = static_cast(refActor); - // Don't apply during cutscenes because it causes weird behaviour and/or crashes on some cutscenes. - if (CVarGetInteger("gHyperEnemies", 0) && isEnemy && !isExcludedEnemy && - !Player_InBlockingCsMode(gPlayState, player)) { - GameInteractor::RawAction::UpdateActor(actor); - } - }); + // Some enemies are not in the ACTORCAT_ENEMY category, and some are that aren't really enemies. + bool isEnemy = actor->category == ACTORCAT_ENEMY || actor->id == ACTOR_EN_TORCH2; + bool isExcludedEnemy = actor->id == ACTOR_EN_FIRE_ROCK || actor->id == ACTOR_EN_ENCOUNT2; + + // Don't apply during cutscenes because it causes weird behaviour and/or crashes on some cutscenes. + if (CVarGetInteger("gHyperEnemies", 0) && isEnemy && !isExcludedEnemy && + !Player_InBlockingCsMode(gPlayState, player)) { + GameInteractor::RawAction::UpdateActor(actor); + } + }); + } } void RegisterBonkDamage() { @@ -627,6 +717,62 @@ void RegisterMirrorModeHandler() { }); } +void UpdatePatchHand() { + if ((CVarGetInteger("gEnhancements.EquimentAlwaysVisible", 0)) && LINK_IS_CHILD) { + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer1", 92, gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer2", 93, gsSPEndDisplayList()); + ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot1", 84, gsSPDisplayListOTRFilePath(gLinkChildRightHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot2", 85, gsSPEndDisplayList()); + ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow1", 51, gsSPDisplayListOTRFilePath(gLinkChildRightHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow2", 52, gsSPEndDisplayList()); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword1", 104, gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword2", 105, gsSPEndDisplayList()); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword1", 79, gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword2", 80, gsSPEndDisplayList()); + ResourceMgr_PatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife1", 76, gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife2", 77, gsSPEndDisplayList()); + + } else { + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife2"); + } + if ((CVarGetInteger("gEnhancements.EquimentAlwaysVisible", 0)) && LINK_IS_ADULT) { + ResourceMgr_PatchGfxByName(gLinkChildLeftFistAndKokiriSwordNearDL, "adultKokiriSword", 13, gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkChildRightHandHoldingSlingshotNearDL, "adultSlingshot", 13, gsSPDisplayListOTRFilePath(gLinkAdultRightHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkChildLeftFistAndBoomerangNearDL, "adultBoomerang", 50, gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkChildRightFistAndDekuShieldNearDL, "adultDekuShield", 49, gsSPDisplayListOTRFilePath(gLinkAdultRightHandClosedNearDL)); + } else { + ResourceMgr_UnpatchGfxByName(gLinkChildLeftFistAndKokiriSwordNearDL, "adultKokiriSword"); + ResourceMgr_UnpatchGfxByName(gLinkChildRightHandHoldingSlingshotNearDL, "adultSlingshot"); + ResourceMgr_UnpatchGfxByName(gLinkChildLeftFistAndBoomerangNearDL, "adultBoomerang"); + ResourceMgr_UnpatchGfxByName(gLinkChildRightFistAndDekuShieldNearDL, "adultDekuShield"); + } +} + +void RegisterPatchHandHandler() { + GameInteractor::Instance->RegisterGameHook([](int32_t sceneNum) { + UpdatePatchHand(); + }); +} + +void RegisterResetNaviTimer() { + GameInteractor::Instance->RegisterGameHook([](int32_t sceneNum) { + if (CVarGetInteger("gEnhancements.ResetNaviTimer", 0)) { + gSaveContext.naviTimer = 0; + } + }); +} + f32 triforcePieceScale; void RegisterTriforceHunt() { @@ -659,7 +805,7 @@ void RegisterTriforceHunt() { void RegisterGrantGanonsBossKey() { GameInteractor::Instance->RegisterGameHook([]() { // Triforce Hunt needs the check if the player isn't being teleported to the credits scene. - if (!GameInteractor::IsGameplayPaused() && + if (!GameInteractor::IsGameplayPaused() && IS_RANDO && Flags_GetRandomizerInf(RAND_INF_GRANT_GANONS_BOSSKEY) && gPlayState->transitionTrigger != TRANS_TRIGGER_START && (1 << 0 & gSaveContext.inventory.dungeonItems[SCENE_GANONS_TOWER]) == 0) { GetItemEntry getItemEntry = @@ -1030,6 +1176,29 @@ void RegisterRandomizerSheikSpawn() { }); } +void UpdateHurtContainerModeState(bool newState) { + static bool hurtEnabled = false; + if (hurtEnabled == newState) { + return; + } + + hurtEnabled = newState; + uint16_t getHeartPieces = gSaveContext.sohStats.heartPieces / 4; + uint16_t getHeartContainers = gSaveContext.sohStats.heartContainers; + + if (hurtEnabled) { + gSaveContext.healthCapacity = 320 - ((getHeartPieces + getHeartContainers) * 16); + } else { + gSaveContext.healthCapacity = 48 + ((getHeartPieces + getHeartContainers) * 16); + } +} + +void RegisterHurtContainerModeHandler() { + GameInteractor::Instance->RegisterGameHook([](int32_t fileNum) { + UpdateHurtContainerModeState(CVarGetInteger("gHurtContainer", 0)); + }); +} + void RegisterRandomizedEnemySizes() { GameInteractor::Instance->RegisterGameHook([](void* refActor) { // Randomized Enemy Sizes @@ -1041,8 +1210,8 @@ void RegisterRandomizedEnemySizes() { uint8_t excludedEnemy = actor->id == ACTOR_EN_BROB || actor->id == ACTOR_EN_DHA || (actor->id == ACTOR_BOSS_SST && actor->params == -1); // Dodongo, Volvagia and Dead Hand are always smaller because they're impossible when bigger. - uint8_t smallOnlyEnemy = - actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || actor->id == ACTOR_BOSS_FD2 || ACTOR_EN_DH; + uint8_t smallOnlyEnemy = actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || + actor->id == ACTOR_BOSS_FD2 || actor->id == ACTOR_EN_DH; // Only apply to enemies and bosses. if (!CVarGetInteger("gRandomizedEnemySizes", 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) { @@ -1067,6 +1236,161 @@ void RegisterRandomizedEnemySizes() { } Actor_SetScale(actor, actor->scale.z * randomScale); + + if (CVarGetInteger("gEnemySizeScalesHealth", 0) && (actor->category == ACTORCAT_ENEMY)) { + // Scale the health based on a smaller factor than randomScale + float healthScalingFactor = 0.8f; // Adjust this factor as needed + float scaledHealth = actor->colChkInfo.health * (randomScale * healthScalingFactor); + + // Ensure the scaled health doesn't go below zero + actor->colChkInfo.health = fmax(scaledHealth, 1.0f); + } else { + return; + } + }); +} + +void RegisterOpenAllHours() { + GameInteractor::Instance->RegisterGameHook([](void* refActor) { + Actor* actor = static_cast(refActor); + + if (CVarGetInteger("gEnhancements.OpenAllHours", 0) && (actor->id == ACTOR_EN_DOOR)) { + switch (actor->params) { + case 4753: // Night Market Bazaar + case 1678: // Night Potion Shop + case 2689: // Day Bombchu Shop + case 2703: // Night Slingshot Game + case 653: // Day Chest Game + case 6801: // Night Kak Bazaar + case 7822: // Night Kak Potion Shop + case 4751: // Night Kak Archery Game + case 3728: // Night Mask Shop + { + actor->params = (actor->params & 0xFC00) | (DOOR_SCENEEXIT << 7) | 0x3F; + EnDoor* enDoor = static_cast(refActor); + EnDoor_SetupType(enDoor, gPlayState); + break; + } + default: + break; + } + } + }); +} + +void PatchToTMedallions() { + // TODO: Refactor the DemoEffect_UpdateJewelAdult and DemoEffect_UpdateJewelChild from z_demo_effect + // effects to take effect in there + if (CVarGetInteger("gToTMedallionsColors", 0)) { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_StartGrayscale", 7, gsSPGrayscale(true)); + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_StartGrayscale", 7, gsSPGrayscale(true)); + + if (CHECK_QUEST_ITEM(QUEST_MEDALLION_WATER)) { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeBlue", 16, gsDPSetGrayscaleColor(0, 161, 255, 255)); + } else { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeBlue", 16, gsDPSetGrayscaleColor(255, 255, 255, 255)); + } + + if (CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT)) { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeOrange", 45, gsDPSetGrayscaleColor(255, 135, 0, 255)); + } else { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeOrange", 45, gsDPSetGrayscaleColor(255, 255, 255, 255)); + } + + if (CHECK_QUEST_ITEM(QUEST_MEDALLION_LIGHT)) { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeYellow", 69, gsDPSetGrayscaleColor(255, 255, 0, 255)); + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakeYellow", 16, gsDPSetGrayscaleColor(255, 255, 0, 255)); + } else { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeYellow", 69, gsDPSetGrayscaleColor(255, 255, 255, 255)); + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakeYellow", 16, gsDPSetGrayscaleColor(255, 255, 255, 255)); + } + + if (CHECK_QUEST_ITEM(QUEST_MEDALLION_FOREST)) { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeGreen", 94, gsDPSetGrayscaleColor(0, 255, 0, 255)); + } else { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeGreen", 94, gsDPSetGrayscaleColor(255, 255, 255, 255)); + } + + if (CHECK_QUEST_ITEM(QUEST_MEDALLION_FIRE)) { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeRed", 118, gsDPSetGrayscaleColor(255, 0, 0, 255)); + } else { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeRed", 118, gsDPSetGrayscaleColor(255, 255, 255, 255)); + } + + if (CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW)) { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakePurple", 142, gsDPSetGrayscaleColor(212, 0, 255, 255)); + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakePurple", 27, gsDPSetGrayscaleColor(212, 0, 255, 255)); + } else { + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakePurple", 142, gsDPSetGrayscaleColor(255, 255, 255, 255)); + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakePurple", 27, gsDPSetGrayscaleColor(255, 255, 255, 255)); + } + + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_EndGrayscaleAndEndDlist", 160, gsSPBranchListOTRFilePath(gEndGrayscaleAndEndDlistDL)); + ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_EndGrayscaleAndEndDlist", 51, gsSPBranchListOTRFilePath(gEndGrayscaleAndEndDlistDL)); + } else { + // Unpatch everything + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_StartGrayscale"); + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_StartGrayscale"); + + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeBlue"); + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeOrange"); + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeYellow"); + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakeYellow"); + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeRed"); + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakePurple"); + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakePurple"); + + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_EndGrayscaleAndEndDlist"); + ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_EndGrayscaleAndEndDlist"); + } +} + +void RegisterToTMedallions() { + GameInteractor::Instance->RegisterGameHook([](GetItemEntry _unused) { + if (!CVarGetInteger("gToTMedallionsColors", 0) || !gPlayState || gPlayState->sceneNum != SCENE_TEMPLE_OF_TIME) { + return; + } + PatchToTMedallions(); + }); + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { + if (!CVarGetInteger("gToTMedallionsColors", 0) || sceneNum != SCENE_TEMPLE_OF_TIME) { + return; + } + PatchToTMedallions(); + }); +} + + +void RegisterFloorSwitchesHook() { + GameInteractor::Instance->RegisterGameHook([](void* refActor) { + Actor* actor = static_cast(refActor); + if (actor->id != ACTOR_OBJ_SWITCH || !CVarGetInteger("gEnhancements.FixFloorSwitches", 0)) { + return; + } + + ObjSwitch* switchActor = reinterpret_cast(actor); + s32 type = (switchActor->dyna.actor.params & 7); + + if (switchActor->dyna.actor.params == 0x1200 || switchActor->dyna.actor.params == 0x3A00) { + switchActor->dyna.actor.world.pos.y -= 1; + } + }); +} + +void RegisterPauseMenuHooks() { + static bool pauseWarpHooksRegistered = false; + GameInteractor::Instance->RegisterGameHook([&]() { + if (!GameInteractor::IsSaveLoaded() || !CVarGetInteger("gPauseWarp", 0)) { + pauseWarpHooksRegistered = false; + return; + } + if (!pauseWarpHooksRegistered) { + GameInteractor::Instance->RegisterGameHook([]() {PauseWarp_HandleSelection();}); + GameInteractor::Instance->RegisterGameHook([]() { + PauseWarp_Execute(); + }); + pauseWarpHooksRegistered = true; + } }); } @@ -1088,16 +1412,25 @@ void InitMods() { RegisterDaytimeGoldSkultullas(); RegisterRupeeDash(); RegisterShadowTag(); + RegisterPermanentHeartLoss(); + RegisterDeleteFileOnDeath(); RegisterHyperBosses(); - RegisterHyperEnemies(); + UpdateHyperEnemiesState(); RegisterBonkDamage(); RegisterMenuPathFix(); RegisterMirrorModeHandler(); + RegisterResetNaviTimer(); RegisterTriforceHunt(); RegisterGrantGanonsBossKey(); RegisterEnemyDefeatCounts(); RegisterAltTrapTypes(); RegisterRandomizerSheikSpawn(); RegisterRandomizedEnemySizes(); + RegisterOpenAllHours(); + RegisterToTMedallions(); NameTag_RegisterHooks(); + RegisterFloorSwitchesHook(); + RegisterPatchHandHandler(); + RegisterHurtContainerModeHandler(); + RegisterPauseMenuHooks(); } diff --git a/soh/soh/Enhancements/mods.h b/soh/soh/Enhancements/mods.h index 2f0430475..2755924f2 100644 --- a/soh/soh/Enhancements/mods.h +++ b/soh/soh/Enhancements/mods.h @@ -9,7 +9,13 @@ extern "C" { void UpdateDirtPathFixState(int32_t sceneNum); void UpdateMirrorModeState(int32_t sceneNum); +void UpdateHurtContainerModeState(bool newState); +void PatchToTMedallions(); +void UpdatePermanentHeartLossState(); +void UpdateHyperEnemiesState(); +void UpdateHyperBossesState(); void InitMods(); +void UpdatePatchHand(); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/pausewarp.c b/soh/soh/Enhancements/pausewarp.c new file mode 100644 index 000000000..e0fd97e24 --- /dev/null +++ b/soh/soh/Enhancements/pausewarp.c @@ -0,0 +1,92 @@ +#include "custom-message/CustomMessageTypes.h" +#include "global.h" +#include "z64.h" +#include "game-interactor/GameInteractor.h" + +static const int songMessageMap[] = { + TEXT_WARP_MINUET_OF_FOREST, + TEXT_WARP_BOLERO_OF_FIRE, + TEXT_WARP_SERENADE_OF_WATER, + TEXT_WARP_REQUIEM_OF_SPIRIT, + TEXT_WARP_NOCTURNE_OF_SHADOW, + TEXT_WARP_PRELUDE_OF_LIGHT +}; + +static const int ocarinaSongMap[] = { + OCARINA_SONG_MINUET, + OCARINA_SONG_BOLERO, + OCARINA_SONG_SERENADE, + OCARINA_SONG_REQUIEM, + OCARINA_SONG_NOCTURNE, + OCARINA_SONG_PRELUDE +}; + +static const int entranceIndexMap[] = { + ENTR_SACRED_FOREST_MEADOW_2, // Minuet + ENTR_DEATH_MOUNTAIN_CRATER_4, // Bolero + ENTR_LAKE_HYLIA_8, // Serenade + ENTR_DESERT_COLOSSUS_5, // Requiem + ENTR_GRAVEYARD_7, // Nocturne + ENTR_TEMPLE_OF_TIME_7 // Prelude +}; + +static const int songAudioMap[] = { + NA_BGM_OCA_MINUET, + NA_BGM_OCA_BOLERO, + NA_BGM_OCA_SERENADE, + NA_BGM_OCA_REQUIEM, + NA_BGM_OCA_NOCTURNE, + NA_BGM_OCA_LIGHT +}; + +static bool isWarpActive = false; + +void PauseWarp_Execute() { + if (!isWarpActive || gPlayState->msgCtx.msgMode != MSGMODE_NONE) { + return; + } + isWarpActive = false; + GET_PLAYER(gPlayState)->stateFlags1 &= ~PLAYER_STATE1_IN_CUTSCENE; + if (gPlayState->msgCtx.choiceIndex != 0) { + return; + } + if (IS_RANDO) { + Entrance_SetWarpSongEntrance(); + return; + } + gPlayState->transitionTrigger = TRANS_TRIGGER_START; + gPlayState->transitionType = TRANS_TYPE_FADE_WHITE_FAST; + for (int i = 0; i < ARRAY_COUNT(ocarinaSongMap); i++) { + if (gPlayState->msgCtx.lastPlayedSong == ocarinaSongMap[i]) { + gPlayState->nextEntranceIndex = entranceIndexMap[i]; + return; + } + } + gPlayState->transitionTrigger = TRANS_TRIGGER_OFF; +} + +void ActivateWarp(PauseContext* pauseCtx, int song) { + Audio_OcaSetInstrument(0); + Interface_SetDoAction(gPlayState, DO_ACTION_NONE); + pauseCtx->state = 0x12; + WREG(2) = -6240; + func_800F64E0(0); + pauseCtx->unk_1E4 = 0; + int idx = song - QUEST_SONG_MINUET; + gPlayState->msgCtx.lastPlayedSong = ocarinaSongMap[idx]; + Audio_SetSoundBanksMute(0x20); + Audio_PlayFanfare(songAudioMap[idx]); + Message_StartTextbox(gPlayState, songMessageMap[idx], NULL); + GET_PLAYER(gPlayState)->stateFlags1 |= PLAYER_STATE1_IN_CUTSCENE; + isWarpActive = true; +} + +void PauseWarp_HandleSelection() { + if (gSaveContext.inventory.items[SLOT_OCARINA] != ITEM_NONE) { + int aButtonPressed = CHECK_BTN_ALL(gPlayState->state.input->press.button, BTN_A); + int song = gPlayState->pauseCtx.cursorPoint[PAUSE_QUEST]; + if (aButtonPressed && CHECK_QUEST_ITEM(song) && song >= QUEST_SONG_MINUET && song <= QUEST_SONG_PRELUDE) { + ActivateWarp(&gPlayState->pauseCtx, song); + } + } +} diff --git a/soh/soh/Enhancements/presets.cpp b/soh/soh/Enhancements/presets.cpp index 7b2ca6595..726863e05 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 98dd8db2d..64aba030d 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); @@ -70,6 +76,7 @@ const std::vector enhancementsCvars = { "gForgeTime", "gClimbSpeed", "gFasterBlockPush", + "gCrawlSpeed", "gFasterHeavyBlockLift", "gNoForcedNavi", "gSkulltulaFreeze", @@ -136,6 +143,7 @@ const std::vector enhancementsCvars = { "gInjectItemCounts", "gDayGravePull", "gDampeAllNight", + "gQuitFishingAtDoor", "gSkipSwimDeepEndAnim", "gSkipScarecrow", "gBlueFireArrows", @@ -184,6 +192,14 @@ const std::vector enhancementsCvars = { "gBombchuBowlingNoSmallCucco", "gBombchuBowlingNoBigCucco", "gBombchuBowlingAmmunition", + "gCustomizeOcarinaGame", + "gInstantOcarinaGameWin", + "gOcarinaGameNoteSpeed", + "gOcarinaUnlimitedFailTime", + "gOcarinaGameStartingNotes", + "gOcarinaGameRoundOneNotes", + "gOcarinaGameRoundTwoNotes", + "gOcarinaGameRoundThreeNotes", "gCreditsFix", "gSilverRupeeJingleExtend", "gStaticExplosionRadius", @@ -245,6 +261,25 @@ const std::vector enhancementsCvars = { "gAddTraps.Speed", "gAddTraps.Tele", "gAddTraps.Void", + "gToTMedallionsColors", + "gCuccoStayDurationMultiplier", + "gDeleteFileOnDeath", + "gEnemySizeScalesHealth", + "gEnhancements.BetterAmmoRendering", + "gEnhancements.EquimentAlwaysVisible", + "gEnhancements.FixDaruniaDanceSpeed", + "gEnhancements.OpenAllHours", + "gEnhancements.ResetNaviTimer", + "gEnhancements.ScaleAdultEquimentAsChild", + "gEnhancements.LeeverSpawnRate", + "gEnhancements.SwordToggle", + "gEnhancements.FixFloorSwitches", + "gFixZoraHintDialogue", + "gHurtContainer", + "gPauseWarp", + "gPermanentHeartLoss", + "gRemoveExplosiveLimit", + "gToggleStrength", }; const std::vector cheatCvars = { @@ -257,6 +292,8 @@ const std::vector cheatCvars = { "gWalkSpeedToggle", "gWalkModifierOne", "gWalkModifierTwo", + "gSwimModifierOne", + "gSwimModifierTwo", "gGoronPot", "gDampeWin", "gCustomizeShootingGallery", @@ -295,6 +332,7 @@ const std::vector cheatCvars = { "gSwitchAge", "gSwitchTimeline", "gNoRedeadFreeze", + "gNoKeeseGuayTarget", "gBombTimerMultiplier", "gNoFishDespawn", "gNoBugsDespawn", @@ -314,6 +352,7 @@ const std::vector cheatCvars = { "gCosmetics.Link_HeadScale.Value", "gCosmetics.Link_SwordScale.Changed", "gCosmetics.Link_SwordScale.Value", + "gEnhancements.RememberMapToggleState", }; const std::vector randomizerCvars = { @@ -508,6 +547,8 @@ const std::vector vanillaPlusPresetEntries = { PRESET_ENTRY_S32("gNaviTextFix", 1), // Extend Silver Rupee Jingle PRESET_ENTRY_S32("gSilverRupeeJingleExtend", 1), + // Fix some Floor Switches + PRESET_ENTRY_S32("gEnhancements.FixFloorSwitches", 1), // Red Ganon blood PRESET_ENTRY_S32("gRedGanonBlood", 1), @@ -579,6 +620,8 @@ const std::vector enhancedPresetEntries = { PRESET_ENTRY_S32("gSilverRupeeJingleExtend", 1), // Fix enemies not spawning on ground over water PRESET_ENTRY_S32("gEnemySpawnsOverWaterboxes", 1), + // Fix some Floor Switches + PRESET_ENTRY_S32("gEnhancements.FixFloorSwitches", 1), // Red Ganon blood PRESET_ENTRY_S32("gRedGanonBlood", 1), @@ -701,6 +744,8 @@ const std::vector randomizerPresetEntries = { PRESET_ENTRY_S32("gNaviTextFix", 1), // Extend Silver Rupee Jingle PRESET_ENTRY_S32("gSilverRupeeJingleExtend", 1), + // Fix some Floor Switches + PRESET_ENTRY_S32("gEnhancements.FixFloorSwitches", 1), // Red Ganon blood PRESET_ENTRY_S32("gRedGanonBlood", 1), @@ -784,6 +829,13 @@ const std::vector randomizerPresetEntries = { // Adult Minimum Weight (8 to 13) PRESET_ENTRY_S32("gAdultMinimumWeightFish", 6), + // Customize Lost Woods Ocarina Game Behavior + PRESET_ENTRY_S32("gCustomizeOcarinaGame", 1), + // Start With Five Notes + PRESET_ENTRY_S32("gOcarinaGameStartingNotes", 5), + // Round One Notes + PRESET_ENTRY_S32("gOcarinaGameRoundOneNotes", 5), + // Visual Stone of Agony PRESET_ENTRY_S32("gVisualAgony", 1), // Pull grave during the day @@ -793,6 +845,9 @@ const std::vector randomizerPresetEntries = { // Chest size & texture matches contents PRESET_ENTRY_S32("gChestSizeAndTextureMatchesContents", CSMC_BOTH), + // Color Temple of Time's Medallions + PRESET_ENTRY_S32("gToTMedallionsColors", 1), + // Pause link animation (0 to 16) PRESET_ENTRY_S32("gPauseLiveLink", 16), // Frames to wait @@ -869,7 +924,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), @@ -961,7 +1017,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), @@ -1014,7 +1071,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/entrance.cpp b/soh/soh/Enhancements/randomizer/3drando/entrance.cpp index 5df67bb87..8dd8b4c63 100644 --- a/soh/soh/Enhancements/randomizer/3drando/entrance.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/entrance.cpp @@ -26,7 +26,6 @@ typedef struct { AreaKey parentRegion; AreaKey connectedRegion; int16_t index; - int16_t blueWarp; } EntranceLinkInfo; EntranceLinkInfo NO_RETURN_ENTRANCE = {EntranceType::None, NONE, NONE, -1}; @@ -40,6 +39,11 @@ using EntranceInfoPair = std::pair; using EntrancePair = std::pair; using EntrancePools = std::map>; +// Construct entrance name from parent and connected region keys +std::string EntranceNameByRegions(uint32_t parentRegion, uint32_t connectedRegion) { + return AreaTable(parentRegion)->regionName + " -> " + AreaTable(connectedRegion)->regionName; +} + //The entrance randomization algorithm used here is a direct copy of //the algorithm used in the original N64 randomizer (except now in C++ instead //of python). It may be easier to understand the algorithm by looking at the @@ -77,26 +81,22 @@ void SetAllEntrancesData(std::vector& entranceShuffleTable) { //set data Entrance* forwardEntrance = AreaTable(forwardEntry.parentRegion)->GetExit(forwardEntry.connectedRegion); forwardEntrance->SetIndex(forwardEntry.index); - forwardEntrance->SetBlueWarp(forwardEntry.blueWarp); forwardEntrance->SetType(forwardEntry.type); forwardEntrance->SetAsPrimary(); - // When decouple entrances is on, mark it for entrances except boss rooms - if (Settings::DecoupleEntrances && forwardEntry.type != EntranceType::ChildBoss && - forwardEntry.type != EntranceType::AdultBoss) { + // When decouple entrances is on, mark the forward entrance + if (Settings::DecoupleEntrances) { forwardEntrance->SetDecoupled(); } if (returnEntry.parentRegion != NONE) { Entrance* returnEntrance = AreaTable(returnEntry.parentRegion)->GetExit(returnEntry.connectedRegion); returnEntrance->SetIndex(returnEntry.index); - returnEntrance->SetBlueWarp(returnEntry.blueWarp); returnEntrance->SetType(returnEntry.type); forwardEntrance->BindTwoWay(returnEntrance); // Mark reverse entrance as decoupled - if (Settings::DecoupleEntrances && returnEntry.type != EntranceType::ChildBoss && - returnEntry.type != EntranceType::AdultBoss) { + if (Settings::DecoupleEntrances) { returnEntrance->SetDecoupled(); } } @@ -687,31 +687,31 @@ int ShuffleAllEntrances() { curNumRandomizedEntrances = 0; std::vector entranceShuffleTable = { - //Parent Region Connected Region index blue warp + //Parent Region Connected Region index {{EntranceType::Dungeon, KF_OUTSIDE_DEKU_TREE, DEKU_TREE_ENTRYWAY, 0x0000}, - {EntranceType::Dungeon, DEKU_TREE_ENTRYWAY, KF_OUTSIDE_DEKU_TREE, 0x0209, 0x0457}}, + {EntranceType::Dungeon, DEKU_TREE_ENTRYWAY, KF_OUTSIDE_DEKU_TREE, 0x0209}}, {{EntranceType::Dungeon, DEATH_MOUNTAIN_TRAIL, DODONGOS_CAVERN_ENTRYWAY, 0x0004}, - {EntranceType::Dungeon, DODONGOS_CAVERN_ENTRYWAY, DEATH_MOUNTAIN_TRAIL, 0x0242, 0x047A}}, + {EntranceType::Dungeon, DODONGOS_CAVERN_ENTRYWAY, DEATH_MOUNTAIN_TRAIL, 0x0242}}, {{EntranceType::Dungeon, ZORAS_FOUNTAIN, JABU_JABUS_BELLY_ENTRYWAY, 0x0028}, - {EntranceType::Dungeon, JABU_JABUS_BELLY_ENTRYWAY, ZORAS_FOUNTAIN, 0x0221, 0x010E}}, + {EntranceType::Dungeon, JABU_JABUS_BELLY_ENTRYWAY, ZORAS_FOUNTAIN, 0x0221}}, {{EntranceType::Dungeon, SACRED_FOREST_MEADOW, FOREST_TEMPLE_ENTRYWAY, 0x0169}, - {EntranceType::Dungeon, FOREST_TEMPLE_ENTRYWAY, SACRED_FOREST_MEADOW, 0x0215, 0x0608}}, + {EntranceType::Dungeon, FOREST_TEMPLE_ENTRYWAY, SACRED_FOREST_MEADOW, 0x0215}}, {{EntranceType::Dungeon, DMC_CENTRAL_LOCAL, FIRE_TEMPLE_ENTRYWAY, 0x0165}, - {EntranceType::Dungeon, FIRE_TEMPLE_ENTRYWAY, DMC_CENTRAL_LOCAL, 0x024A, 0x0564}}, + {EntranceType::Dungeon, FIRE_TEMPLE_ENTRYWAY, DMC_CENTRAL_LOCAL, 0x024A}}, {{EntranceType::Dungeon, LAKE_HYLIA, WATER_TEMPLE_ENTRYWAY, 0x0010}, - {EntranceType::Dungeon, WATER_TEMPLE_ENTRYWAY, LAKE_HYLIA, 0x021D, 0x060C}}, + {EntranceType::Dungeon, WATER_TEMPLE_ENTRYWAY, LAKE_HYLIA, 0x021D}}, {{EntranceType::Dungeon, DESERT_COLOSSUS, SPIRIT_TEMPLE_ENTRYWAY, 0x0082}, - {EntranceType::Dungeon, SPIRIT_TEMPLE_ENTRYWAY, DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY, 0x01E1, 0x0610}}, + {EntranceType::Dungeon, SPIRIT_TEMPLE_ENTRYWAY, DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY, 0x01E1}}, {{EntranceType::Dungeon, GRAVEYARD_WARP_PAD_REGION, SHADOW_TEMPLE_ENTRYWAY, 0x0037}, - {EntranceType::Dungeon, SHADOW_TEMPLE_ENTRYWAY, GRAVEYARD_WARP_PAD_REGION, 0x0205, 0x0580}}, + {EntranceType::Dungeon, SHADOW_TEMPLE_ENTRYWAY, GRAVEYARD_WARP_PAD_REGION, 0x0205}}, {{EntranceType::Dungeon, KAKARIKO_VILLAGE, BOTTOM_OF_THE_WELL_ENTRYWAY, 0x0098}, {EntranceType::Dungeon, BOTTOM_OF_THE_WELL_ENTRYWAY, KAKARIKO_VILLAGE, 0x02A6}}, {{EntranceType::Dungeon, ZORAS_FOUNTAIN, ICE_CAVERN_ENTRYWAY, 0x0088}, {EntranceType::Dungeon, ICE_CAVERN_ENTRYWAY, ZORAS_FOUNTAIN, 0x03D4}}, {{EntranceType::Dungeon, GERUDO_FORTRESS, GERUDO_TRAINING_GROUNDS_ENTRYWAY, 0x0008}, {EntranceType::Dungeon, GERUDO_TRAINING_GROUNDS_ENTRYWAY, GERUDO_FORTRESS, 0x03A8}}, - {{EntranceType::GanonDungeon, GANONS_CASTLE_LEDGE, GANONS_CASTLE_ENTRYWAY, 0x0467}, - {EntranceType::GanonDungeon, GANONS_CASTLE_ENTRYWAY, CASTLE_GROUNDS_FROM_GANONS_CASTLE, 0x023D}}, + {{EntranceType::GanonDungeon, GANONS_CASTLE_LEDGE, GANONS_CASTLE_ENTRYWAY, 0x0467}, + {EntranceType::GanonDungeon, GANONS_CASTLE_ENTRYWAY, CASTLE_GROUNDS_FROM_GANONS_CASTLE, 0x023D}}, {{EntranceType::Interior, KOKIRI_FOREST, KF_MIDOS_HOUSE, 0x0433}, {EntranceType::Interior, KF_MIDOS_HOUSE, KOKIRI_FOREST, 0x0443}}, @@ -947,21 +947,30 @@ int ShuffleAllEntrances() { {{EntranceType::WarpSong, PRELUDE_OF_LIGHT_WARP, TEMPLE_OF_TIME, 0x05F4}, NO_RETURN_ENTRANCE}, {{EntranceType::ChildBoss, DEKU_TREE_BOSS_ENTRYWAY, DEKU_TREE_BOSS_ROOM, 0x040F}, - {EntranceType::ChildBoss, DEKU_TREE_BOSS_ROOM, DEKU_TREE_BOSS_ENTRYWAY, 0x0252, 0x0457}}, + {EntranceType::ChildBoss, DEKU_TREE_BOSS_ROOM, DEKU_TREE_BOSS_ENTRYWAY, 0x0252}}, {{EntranceType::ChildBoss, DODONGOS_CAVERN_BOSS_ENTRYWAY, DODONGOS_CAVERN_BOSS_ROOM, 0x040B}, - {EntranceType::ChildBoss, DODONGOS_CAVERN_BOSS_ROOM, DODONGOS_CAVERN_BOSS_ENTRYWAY, 0x00C5, 0x047A}}, + {EntranceType::ChildBoss, DODONGOS_CAVERN_BOSS_ROOM, DODONGOS_CAVERN_BOSS_ENTRYWAY, 0x00C5}}, {{EntranceType::ChildBoss, JABU_JABUS_BELLY_BOSS_ENTRYWAY, JABU_JABUS_BELLY_BOSS_ROOM, 0x0301}, - {EntranceType::ChildBoss, JABU_JABUS_BELLY_BOSS_ROOM, JABU_JABUS_BELLY_BOSS_ENTRYWAY, 0x0407, 0x010E}}, + {EntranceType::ChildBoss, JABU_JABUS_BELLY_BOSS_ROOM, JABU_JABUS_BELLY_BOSS_ENTRYWAY, 0x0407}}, {{EntranceType::AdultBoss, FOREST_TEMPLE_BOSS_ENTRYWAY, FOREST_TEMPLE_BOSS_ROOM, 0x000C}, - {EntranceType::AdultBoss, FOREST_TEMPLE_BOSS_ROOM, FOREST_TEMPLE_BOSS_ENTRYWAY, 0x024E, 0x0608}}, + {EntranceType::AdultBoss, FOREST_TEMPLE_BOSS_ROOM, FOREST_TEMPLE_BOSS_ENTRYWAY, 0x024E}}, {{EntranceType::AdultBoss, FIRE_TEMPLE_BOSS_ENTRYWAY, FIRE_TEMPLE_BOSS_ROOM, 0x0305}, - {EntranceType::AdultBoss, FIRE_TEMPLE_BOSS_ROOM, FIRE_TEMPLE_BOSS_ENTRYWAY, 0x0175, 0x0564}}, + {EntranceType::AdultBoss, FIRE_TEMPLE_BOSS_ROOM, FIRE_TEMPLE_BOSS_ENTRYWAY, 0x0175}}, {{EntranceType::AdultBoss, WATER_TEMPLE_BOSS_ENTRYWAY, WATER_TEMPLE_BOSS_ROOM, 0x0417}, - {EntranceType::AdultBoss, WATER_TEMPLE_BOSS_ROOM, WATER_TEMPLE_BOSS_ENTRYWAY, 0x0423, 0x060C}}, + {EntranceType::AdultBoss, WATER_TEMPLE_BOSS_ROOM, WATER_TEMPLE_BOSS_ENTRYWAY, 0x0423}}, {{EntranceType::AdultBoss, SPIRIT_TEMPLE_BOSS_ENTRYWAY, SPIRIT_TEMPLE_BOSS_ROOM, 0x008D}, - {EntranceType::AdultBoss, SPIRIT_TEMPLE_BOSS_ROOM, SPIRIT_TEMPLE_BOSS_ENTRYWAY, 0x02F5, 0x0610}}, + {EntranceType::AdultBoss, SPIRIT_TEMPLE_BOSS_ROOM, SPIRIT_TEMPLE_BOSS_ENTRYWAY, 0x02F5}}, {{EntranceType::AdultBoss, SHADOW_TEMPLE_BOSS_ENTRYWAY, SHADOW_TEMPLE_BOSS_ROOM, 0x0413}, - {EntranceType::AdultBoss, SHADOW_TEMPLE_BOSS_ROOM, SHADOW_TEMPLE_BOSS_ENTRYWAY, 0x02B2, 0x0580}}, + {EntranceType::AdultBoss, SHADOW_TEMPLE_BOSS_ROOM, SHADOW_TEMPLE_BOSS_ENTRYWAY, 0x02B2}}, + + {{EntranceType::BlueWarp, DEKU_TREE_BOSS_ROOM, KF_OUTSIDE_DEKU_TREE, 0x0457}, NO_RETURN_ENTRANCE}, + {{EntranceType::BlueWarp, DODONGOS_CAVERN_BOSS_ROOM, DEATH_MOUNTAIN_TRAIL, 0x047A}, NO_RETURN_ENTRANCE}, + {{EntranceType::BlueWarp, JABU_JABUS_BELLY_BOSS_ROOM, ZORAS_FOUNTAIN, 0x010E}, NO_RETURN_ENTRANCE}, + {{EntranceType::BlueWarp, FOREST_TEMPLE_BOSS_ROOM, SACRED_FOREST_MEADOW, 0x0608}, NO_RETURN_ENTRANCE}, + {{EntranceType::BlueWarp, FIRE_TEMPLE_BOSS_ROOM, DMC_CENTRAL_LOCAL, 0x0564}, NO_RETURN_ENTRANCE}, + {{EntranceType::BlueWarp, WATER_TEMPLE_BOSS_ROOM, LAKE_HYLIA, 0x060C}, NO_RETURN_ENTRANCE}, + {{EntranceType::BlueWarp, SPIRIT_TEMPLE_BOSS_ROOM, DESERT_COLOSSUS, 0x0610}, NO_RETURN_ENTRANCE}, + {{EntranceType::BlueWarp, SHADOW_TEMPLE_BOSS_ROOM, GRAVEYARD_WARP_PAD_REGION, 0x0580}, NO_RETURN_ENTRANCE}, }; std::map priorityEntranceTable = { @@ -1011,6 +1020,11 @@ int ShuffleAllEntrances() { FilterAndEraseFromPool(entrancePools[EntranceType::Boss], [](const Entrance* entrance){return entrance->GetParentRegionKey() == DEKU_TREE_BOSS_ENTRYWAY && entrance->GetConnectedRegionKey() == DEKU_TREE_BOSS_ROOM;}); } + if (Settings::DecoupleEntrances) { + for (Entrance* entrance : entrancePools[EntranceType::Boss]) { + entrancePools[EntranceType::BossReverse].push_back(entrance->GetReverse()); + } + } } else { entrancePools[EntranceType::ChildBoss] = GetShuffleableEntrances(EntranceType::ChildBoss); entrancePools[EntranceType::AdultBoss] = GetShuffleableEntrances(EntranceType::AdultBoss); @@ -1019,6 +1033,14 @@ int ShuffleAllEntrances() { FilterAndEraseFromPool(entrancePools[EntranceType::ChildBoss], [](const Entrance* entrance){return entrance->GetParentRegionKey() == DEKU_TREE_BOSS_ENTRYWAY && entrance->GetConnectedRegionKey() == DEKU_TREE_BOSS_ROOM;}); } + if (Settings::DecoupleEntrances) { + for (Entrance* entrance : entrancePools[EntranceType::ChildBoss]) { + entrancePools[EntranceType::ChildBossReverse].push_back(entrance->GetReverse()); + } + for (Entrance* entrance : entrancePools[EntranceType::AdultBoss]) { + entrancePools[EntranceType::AdultBossReverse].push_back(entrance->GetReverse()); + } + } } } @@ -1082,10 +1104,13 @@ int ShuffleAllEntrances() { SetShuffledEntrances(oneWayEntrancePools); //combine entrance pools if mixing pools. Only continue if more than one pool is selected. - int totalMixedPools = (Settings::MixDungeons ? 1 : 0) + (Settings::MixOverworld ? 1 : 0) + (Settings::MixInteriors ? 1 : 0) + (Settings::MixGrottos ? 1 : 0); + int totalMixedPools = (Settings::MixDungeons ? 1 : 0) + (Settings::MixBosses ? 1 : 0) + + (Settings::MixOverworld ? 1 : 0) + (Settings::MixInteriors ? 1 : 0) + + (Settings::MixGrottos ? 1 : 0); if (totalMixedPools < 2) { Settings::MixedEntrancePools.SetSelectedIndex(OFF); Settings::MixDungeons.SetSelectedIndex(OFF); + Settings::MixBosses.SetSelectedIndex(OFF); Settings::MixOverworld.SetSelectedIndex(OFF); Settings::MixInteriors.SetSelectedIndex(OFF); Settings::MixGrottos.SetSelectedIndex(OFF); @@ -1099,6 +1124,12 @@ int ShuffleAllEntrances() { poolsToMix.insert(EntranceType::DungeonReverse); } } + if (Settings::MixBosses) { + poolsToMix.insert(EntranceType::Boss); + if (Settings::DecoupleEntrances) { + poolsToMix.insert(EntranceType::BossReverse); + } + } if (Settings::MixOverworld) { poolsToMix.insert(EntranceType::Overworld); } @@ -1232,6 +1263,87 @@ int ShuffleAllEntrances() { } } + // Determine blue warp targets + if (true /* Settings.BlueWarps.Is(BLUEWARPS_DUNGEON) */) { // RANDOTODO: add bluewarp shuffle + // If a boss room is inside a boss door, make the blue warp go outside the dungeon's entrance + std::map bossExits = { + { EntranceNameByRegions(DEKU_TREE_BOSS_ROOM, DEKU_TREE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(DEKU_TREE_ENTRYWAY, KF_OUTSIDE_DEKU_TREE)) }, + { EntranceNameByRegions(DODONGOS_CAVERN_BOSS_ROOM, DODONGOS_CAVERN_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(DODONGOS_CAVERN_ENTRYWAY, DEATH_MOUNTAIN_TRAIL)) }, + { EntranceNameByRegions(JABU_JABUS_BELLY_BOSS_ROOM, JABU_JABUS_BELLY_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(JABU_JABUS_BELLY_ENTRYWAY, ZORAS_FOUNTAIN)) }, + { EntranceNameByRegions(FOREST_TEMPLE_BOSS_ROOM, FOREST_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(FOREST_TEMPLE_ENTRYWAY, SACRED_FOREST_MEADOW)) }, + { EntranceNameByRegions(FIRE_TEMPLE_BOSS_ROOM, FIRE_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(FIRE_TEMPLE_ENTRYWAY, DMC_CENTRAL_LOCAL)) }, + { EntranceNameByRegions(WATER_TEMPLE_BOSS_ROOM, WATER_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(WATER_TEMPLE_ENTRYWAY, LAKE_HYLIA)) }, + { EntranceNameByRegions(SPIRIT_TEMPLE_BOSS_ROOM, SPIRIT_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(SPIRIT_TEMPLE_ENTRYWAY, DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY)) }, + { EntranceNameByRegions(SHADOW_TEMPLE_BOSS_ROOM, SHADOW_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(SHADOW_TEMPLE_ENTRYWAY, GRAVEYARD_WARP_PAD_REGION)) }, + }; + + // If a boss room is inside a dungeon entrance (or inside a dungeon which is inside a dungeon entrance), make the blue warp go to that dungeon's blue warp target + std::map dungeonExits = { + { EntranceNameByRegions(DEKU_TREE_ENTRYWAY, KF_OUTSIDE_DEKU_TREE), + GetEntrance(EntranceNameByRegions(DEKU_TREE_BOSS_ROOM, KF_OUTSIDE_DEKU_TREE)) }, + { EntranceNameByRegions(DODONGOS_CAVERN_ENTRYWAY, DEATH_MOUNTAIN_TRAIL), + GetEntrance(EntranceNameByRegions(DODONGOS_CAVERN_BOSS_ROOM, DEATH_MOUNTAIN_TRAIL)) }, + { EntranceNameByRegions(JABU_JABUS_BELLY_ENTRYWAY, ZORAS_FOUNTAIN), + GetEntrance(EntranceNameByRegions(JABU_JABUS_BELLY_BOSS_ROOM, ZORAS_FOUNTAIN)) }, + { EntranceNameByRegions(FOREST_TEMPLE_ENTRYWAY, SACRED_FOREST_MEADOW), + GetEntrance(EntranceNameByRegions(FOREST_TEMPLE_BOSS_ROOM, SACRED_FOREST_MEADOW)) }, + { EntranceNameByRegions(FIRE_TEMPLE_ENTRYWAY, DMC_CENTRAL_LOCAL), + GetEntrance(EntranceNameByRegions(FIRE_TEMPLE_BOSS_ROOM, DMC_CENTRAL_LOCAL)) }, + { EntranceNameByRegions(WATER_TEMPLE_ENTRYWAY, LAKE_HYLIA), + GetEntrance(EntranceNameByRegions(WATER_TEMPLE_BOSS_ROOM, LAKE_HYLIA)) }, + { EntranceNameByRegions(SPIRIT_TEMPLE_ENTRYWAY, DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY), + GetEntrance(EntranceNameByRegions(SPIRIT_TEMPLE_BOSS_ROOM, DESERT_COLOSSUS)) }, + { EntranceNameByRegions(SHADOW_TEMPLE_ENTRYWAY, GRAVEYARD_WARP_PAD_REGION), + GetEntrance(EntranceNameByRegions(SHADOW_TEMPLE_BOSS_ROOM, GRAVEYARD_WARP_PAD_REGION)) }, + }; + + // Pair + std::vector bossRoomExitPairs = { + { GetEntrance(EntranceNameByRegions(DEKU_TREE_BOSS_ROOM, KF_OUTSIDE_DEKU_TREE)), + GetEntrance(EntranceNameByRegions(DEKU_TREE_BOSS_ROOM, DEKU_TREE_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(DODONGOS_CAVERN_BOSS_ROOM, DEATH_MOUNTAIN_TRAIL)), + GetEntrance(EntranceNameByRegions(DODONGOS_CAVERN_BOSS_ROOM, DODONGOS_CAVERN_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(JABU_JABUS_BELLY_BOSS_ROOM, ZORAS_FOUNTAIN)), + GetEntrance(EntranceNameByRegions(JABU_JABUS_BELLY_BOSS_ROOM, JABU_JABUS_BELLY_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(FOREST_TEMPLE_BOSS_ROOM, SACRED_FOREST_MEADOW)), + GetEntrance(EntranceNameByRegions(FOREST_TEMPLE_BOSS_ROOM, FOREST_TEMPLE_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(FIRE_TEMPLE_BOSS_ROOM, DMC_CENTRAL_LOCAL)), + GetEntrance(EntranceNameByRegions(FIRE_TEMPLE_BOSS_ROOM, FIRE_TEMPLE_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(WATER_TEMPLE_BOSS_ROOM, LAKE_HYLIA)), + GetEntrance(EntranceNameByRegions(WATER_TEMPLE_BOSS_ROOM, WATER_TEMPLE_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(SPIRIT_TEMPLE_BOSS_ROOM, DESERT_COLOSSUS)), + GetEntrance(EntranceNameByRegions(SPIRIT_TEMPLE_BOSS_ROOM, SPIRIT_TEMPLE_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(SHADOW_TEMPLE_BOSS_ROOM, GRAVEYARD_WARP_PAD_REGION)), + GetEntrance(EntranceNameByRegions(SHADOW_TEMPLE_BOSS_ROOM, SHADOW_TEMPLE_BOSS_ENTRYWAY)) }, + }; + + for (EntrancePair pair : bossRoomExitPairs) { + Entrance* target = pair.second->GetReplacement() != nullptr ? pair.second->GetReplacement() : pair.second; + + if (!Settings::DecoupleEntrances) { + while (bossExits.find(target->GetName()) != bossExits.end()) { + Entrance* next = bossExits.at(target->GetName()); + target = next->GetReplacement() != nullptr ? next->GetReplacement() : next; + } + + if (dungeonExits.find(target->GetName()) != dungeonExits.end()) { + target = dungeonExits.at(target->GetName()); + } + } + + pair.first->Connect(target->GetOriginalConnectedRegionKey()); + pair.first->SetReplacement(target); + } + } + // Validate the world one last time to ensure all special conditions are still valid if (!ValidateWorld(nullptr)) { return ENTRANCE_SHUFFLE_FAILURE; @@ -1259,8 +1371,8 @@ void CreateEntranceOverrides() { auto message = "Setting " + entrance->to_string() + "\n"; SPDLOG_DEBUG(message); + uint8_t type = (uint8_t)entrance->GetType(); int16_t originalIndex = entrance->GetIndex(); - int16_t originalBlueWarp = entrance->GetBlueWarp(); int16_t replacementIndex = entrance->GetReplacement()->GetIndex(); int16_t destinationIndex = -1; @@ -1274,9 +1386,9 @@ void CreateEntranceOverrides() { } entranceOverrides.push_back({ + .type = type, .index = originalIndex, .destination = destinationIndex, - .blueWarp = originalBlueWarp, .override = replacementIndex, .overrideDestination = replacementDestinationIndex, }); diff --git a/soh/soh/Enhancements/randomizer/3drando/entrance.hpp b/soh/soh/Enhancements/randomizer/3drando/entrance.hpp index b6a5cb432..e99b418d8 100644 --- a/soh/soh/Enhancements/randomizer/3drando/entrance.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/entrance.hpp @@ -18,12 +18,16 @@ enum class EntranceType { OwlDrop, Spawn, WarpSong, + BlueWarp, Dungeon, GanonDungeon, DungeonReverse, Boss, + BossReverse, ChildBoss, + ChildBossReverse, AdultBoss, + AdultBossReverse, Interior, InteriorReverse, SpecialInterior, @@ -40,6 +44,7 @@ public: Entrance(uint32_t connectedRegion_, std::vector conditions_met_) : connectedRegion(connectedRegion_) { + originalConnectedRegion = connectedRegion_; conditions_met.resize(2); for (size_t i = 0; i < conditions_met_.size(); i++) { conditions_met[i] = conditions_met_[i]; @@ -134,6 +139,10 @@ public: return connectedRegion; } + uint32_t GetOriginalConnectedRegionKey() const { + return originalConnectedRegion; + } + Area* GetConnectedRegion() const { return AreaTable(connectedRegion); } @@ -198,14 +207,6 @@ public: index = newIndex; } - int16_t GetBlueWarp() const { - return blueWarp; - } - - void SetBlueWarp(int16_t newBlueWarp) { - blueWarp = newBlueWarp; - } - Entrance* GetAssumed() const { return assumed; } @@ -251,7 +252,7 @@ public: AreaTable(ROOT)->AddExit(ROOT, connectedRegion, []{return true;}); Entrance* targetEntrance = AreaTable(ROOT)->GetExit(connectedRegion); targetEntrance->SetReplacement(this); - targetEntrance->SetName(GetParentRegion()->regionName + " -> " + GetConnectedRegion()->regionName); + targetEntrance->SetName(AreaTable(ROOT)->regionName + " -> " + GetConnectedRegion()->regionName); return targetEntrance; } @@ -266,6 +267,7 @@ public: private: uint32_t parentRegion; uint32_t connectedRegion; + uint32_t originalConnectedRegion; std::vector conditions_met; //Entrance Randomizer stuff @@ -275,7 +277,6 @@ private: Entrance* assumed = nullptr; Entrance* replacement = nullptr; int16_t index = 0xFFFF; - int16_t blueWarp = 0; bool shuffled = false; bool primary = false; bool addedToPool = false; @@ -285,6 +286,7 @@ private: int ShuffleAllEntrances(); void CreateEntranceOverrides(); +std::string EntranceNameByRegions(uint32_t parentRegion, uint32_t connectedRegion); extern std::vector> playthroughEntrances; extern bool noRandomEntrances; diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.cpp b/soh/soh/Enhancements/randomizer/3drando/fill.cpp index f9d3a9592..7c8d027f2 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.cpp @@ -294,7 +294,12 @@ std::vector GetAccessibleLocations(const std::vector& allowe } // Add shuffled entrances to the entrance playthrough - if (mode == SearchMode::GeneratePlaythrough && exit.IsShuffled() && !exit.IsAddedToPool() && !noRandomEntrances) { + // Include bluewarps when unshuffled but dungeon or boss shuffle is on + if (mode == SearchMode::GeneratePlaythrough && + (exit.IsShuffled() || (exit.GetType() == EntranceType::BlueWarp && + (Settings::ShuffleDungeonEntrances.IsNot(SHUFFLEDUNGEONS_OFF) || + Settings::ShuffleBossEntrances.IsNot(SHUFFLEBOSSES_OFF)))) && + !exit.IsAddedToPool() && !noRandomEntrances) { entranceSphere.push_back(&exit); exit.AddToPool(); // Don't list a two-way coupled entrance from both directions @@ -913,6 +918,8 @@ void VanillaFill() { ShuffleAllEntrances(); printf("\x1b[7;32HDone"); } + // Populate the playthrough for entrances so they are placed in the spoiler log + GeneratePlaythrough(); //Finish up CreateItemOverrides(); CreateEntranceOverrides(); diff --git a/soh/soh/Enhancements/randomizer/3drando/item_location.cpp b/soh/soh/Enhancements/randomizer/3drando/item_location.cpp index 0ab29d869..850157ad8 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_location.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_location.cpp @@ -26,7 +26,7 @@ void LocationTable_Init() { locationTable[LW_NEAR_SHORTCUTS_GROTTO_CHEST] = ItemLocation::Chest (RC_LW_NEAR_SHORTCUTS_GROTTO_CHEST, 0x3E, 0x14, "LW Near Shortcuts Grotto Chest", LW_NEAR_SHORTCUTS_GROTTO_CHEST, BLUE_RUPEE, {}, SpoilerCollectionCheckGroup::GROUP_LOST_WOODS); locationTable[LW_SKULL_KID] = ItemLocation::Base (RC_LW_SKULL_KID, 0x5B, "LW Skull Kid", LW_SKULL_KID, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(22), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS); locationTable[LW_TRADE_COJIRO] = ItemLocation::Base (RC_LW_TRADE_COJIRO, 0x5B, "LW Trade Cojiro", LW_TRADE_COJIRO, ODD_MUSHROOM, {Category::cAdultTrade}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS); - locationTable[LW_TRADE_ODD_POTION] = ItemLocation::Base (RC_LW_TRADE_ODD_POTION, 0x5B, "LW Trade Odd Potion", LW_TRADE_ODD_POTION, ODD_POTION, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(49), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS); + locationTable[LW_TRADE_ODD_POTION] = ItemLocation::Base (RC_LW_TRADE_ODD_POTION, 0x5B, "LW Trade Odd Potion", LW_TRADE_ODD_POTION, POACHERS_SAW, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(49), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS); locationTable[LW_OCARINA_MEMORY_GAME] = ItemLocation::Base (RC_LW_OCARINA_MEMORY_GAME, 0x5B, "LW Ocarina Memory Game", LW_OCARINA_MEMORY_GAME, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(23), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS); locationTable[LW_TARGET_IN_WOODS] = ItemLocation::Base (RC_LW_TARGET_IN_WOODS, 0x5B, "LW Target in Woods", LW_TARGET_IN_WOODS, PROGRESSIVE_SLINGSHOT, {}, SpoilerCollectionCheck::ItemGetInf(29), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS); locationTable[LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT] = ItemLocation::Base (RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT, 0x5B, "LW Deku Scrub Near Deku Theater Right",LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT, BUY_DEKU_NUT_5, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS); @@ -120,7 +120,7 @@ void LocationTable_Init() { locationTable[KAK_SHOOTING_GALLERY_REWARD] = ItemLocation::Base (RC_KAK_SHOOTING_GALLERY_REWARD, 0x42, "Kak Shooting Gallery Reward", KAK_SHOOTING_GALLERY_REWARD, PROGRESSIVE_BOW, {}, SpoilerCollectionCheck::Chest(0x42, 0x1F), SpoilerCollectionCheckGroup::GROUP_KAKARIKO); locationTable[KAK_TRADE_ODD_MUSHROOM] = ItemLocation::Base (RC_KAK_TRADE_ODD_MUSHROOM, 0x4E, "Kak Trade Odd Mushroom", KAK_TRADE_ODD_MUSHROOM, ODD_POTION, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(48), SpoilerCollectionCheckGroup::GROUP_KAKARIKO); locationTable[KAK_GRANNYS_SHOP] = ItemLocation::Base (RC_KAK_GRANNYS_SHOP, 0x4E, "Kak Granny's Shop", KAK_GRANNYS_SHOP, BLUE_POTION_REFILL, {Category::cMerchant}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_KAKARIKO); - locationTable[KAK_ANJU_AS_ADULT] = ItemLocation::Base (RC_KAK_ANJU_AS_ADULT, 0x52, "Kak Anju as Adult", KAK_ANJU_AS_ADULT, CLAIM_CHECK, {}, SpoilerCollectionCheck::ItemGetInf(44), SpoilerCollectionCheckGroup::GROUP_KAKARIKO); + locationTable[KAK_ANJU_AS_ADULT] = ItemLocation::Base (RC_KAK_ANJU_AS_ADULT, 0x52, "Kak Anju as Adult", KAK_ANJU_AS_ADULT, POCKET_EGG, {}, SpoilerCollectionCheck::ItemGetInf(44), SpoilerCollectionCheckGroup::GROUP_KAKARIKO); locationTable[KAK_ANJU_AS_CHILD] = ItemLocation::Base (RC_KAK_ANJU_AS_CHILD, 0x52, "Kak Anju as Child", KAK_ANJU_AS_CHILD, EMPTY_BOTTLE, {}, SpoilerCollectionCheck::ItemGetInf(12), SpoilerCollectionCheckGroup::GROUP_KAKARIKO); locationTable[KAK_TRADE_POCKET_CUCCO] = ItemLocation::Base (RC_KAK_TRADE_POCKET_CUCCO, 0x52, "Kak Trade Pocket Cucco", KAK_TRADE_POCKET_CUCCO, COJIRO, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(46), SpoilerCollectionCheckGroup::GROUP_KAKARIKO); locationTable[KAK_IMPAS_HOUSE_FREESTANDING_POH] = ItemLocation::Collectable(RC_KAK_IMPAS_HOUSE_FREESTANDING_POH, 0x37, 0x01, "Kak Impas House Freestanding PoH", KAK_IMPAS_HOUSE_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO); @@ -492,7 +492,7 @@ void LocationTable_Init() { locationTable[GERUDO_TRAINING_GROUNDS_MQ_MAZE_PATH_THIRD_CHEST] = ItemLocation::Chest (RC_GERUDO_TRAINING_GROUND_MQ_MAZE_PATH_THIRD_CHEST, 0x0B, 0x09, "Gerudo Training Grounds MQ Maze Path Third Chest", GERUDO_TRAINING_GROUNDS_MQ_MAZE_PATH_THIRD_CHEST, TREASURE_GAME_GREEN_RUPEE, {}, SpoilerCollectionCheckGroup::GROUP_GERUDO_TRAINING_GROUND); locationTable[GERUDO_TRAINING_GROUNDS_MQ_MAZE_PATH_SECOND_CHEST] = ItemLocation::Chest (RC_GERUDO_TRAINING_GROUND_MQ_MAZE_PATH_SECOND_CHEST, 0x0B, 0x0A, "Gerudo Training Grounds MQ Maze Path Second Chest", GERUDO_TRAINING_GROUNDS_MQ_MAZE_PATH_SECOND_CHEST, RED_RUPEE, {}, SpoilerCollectionCheckGroup::GROUP_GERUDO_TRAINING_GROUND); locationTable[GERUDO_TRAINING_GROUNDS_MQ_HIDDEN_CEILING_CHEST] = ItemLocation::Chest (RC_GERUDO_TRAINING_GROUND_MQ_HIDDEN_CEILING_CHEST, 0x0B, 0x0B, "Gerudo Training Grounds MQ Hidden Ceiling Chest", GERUDO_TRAINING_GROUNDS_MQ_HIDDEN_CEILING_CHEST, PURPLE_RUPEE, {}, SpoilerCollectionCheckGroup::GROUP_GERUDO_TRAINING_GROUND); - locationTable[GERUDO_TRAINING_GROUNDS_MQ_UNDERWATER_SILVER_RUPEE_CHEST] = ItemLocation::Chest (RC_GERUDO_TRAINING_GROUND_MQ_UNDERWATER_SILVER_RUPEE_CHEST, 0x0B, 0x0D, "Gerudo Training Grounds MQ Underwater Silver Rupee Chest",GERUDO_TRAINING_GROUNDS_MQ_UNDERWATER_SILVER_RUPEE_CHEST, TREASURE_GAME_GREEN_RUPEE, {Category::cVanillaSmallKey}, SpoilerCollectionCheckGroup::GROUP_GERUDO_TRAINING_GROUND); + locationTable[GERUDO_TRAINING_GROUNDS_MQ_UNDERWATER_SILVER_RUPEE_CHEST] = ItemLocation::Chest (RC_GERUDO_TRAINING_GROUND_MQ_UNDERWATER_SILVER_RUPEE_CHEST, 0x0B, 0x0D, "Gerudo Training Grounds MQ Underwater Silver Rupee Chest",GERUDO_TRAINING_GROUNDS_MQ_UNDERWATER_SILVER_RUPEE_CHEST, GERUDO_TRAINING_GROUNDS_SMALL_KEY, {Category::cVanillaSmallKey}, SpoilerCollectionCheckGroup::GROUP_GERUDO_TRAINING_GROUND); locationTable[GERUDO_TRAINING_GROUNDS_MQ_HEAVY_BLOCK_CHEST] = ItemLocation::Chest (RC_GERUDO_TRAINING_GROUND_MQ_HEAVY_BLOCK_CHEST, 0x0B, 0x02, "Gerudo Training Grounds MQ Heavy Block Chest", GERUDO_TRAINING_GROUNDS_MQ_HEAVY_BLOCK_CHEST, PURPLE_RUPEE, {}, SpoilerCollectionCheckGroup::GROUP_GERUDO_TRAINING_GROUND); //Ganons Castle Shared @@ -918,7 +918,7 @@ void LocationTable_Init() { locationTable[DMC_UPPER_GROTTO_GOSSIP_STONE] = ItemLocation::HintStone(RC_DMC_UPPER_GROTTO_GOSSIP_STONE, "DMC Upper Grotto Gossip Stone"); locationTable[GANONDORF_HINT] = ItemLocation::OtherHint(RC_GANONDORF_HINT, "Ganondorf Hint"); - locationTable[TRIFORCE_COMPLETED] = ItemLocation::Reward (RC_TRIFORCE_COMPLETED, 0xFF, "Completed Triforce", NONE, TRIFORCE_COMPLETED, {}, SpoilerCollectionCheck::None(), SpoilerCollectionCheckGroup::GROUP_NO_GROUP); + locationTable[TRIFORCE_COMPLETED] = ItemLocation::Reward (RC_TRIFORCE_COMPLETED, 0xFF, "Completed Triforce", NONE, NONE, {}, SpoilerCollectionCheck::None(), SpoilerCollectionCheckGroup::GROUP_NO_GROUP); for (int i = NONE; i != KEY_ENUM_MAX; i++) locationLookupTable.insert(std::make_pair(locationTable[i].GetRandomizerCheck(), static_cast(i))); diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access.cpp index 6be67ea84..684dd16e1 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access.cpp @@ -503,3 +503,16 @@ std::vector GetShuffleableEntrances(EntranceType type, bool onlyPrima } return entrancesToShuffle; } + +// Get the specific entrance by name +Entrance* GetEntrance(const std::string name) { + for (uint32_t area : Areas::GetAllAreas()) { + for (auto& exit : AreaTable(area)->exits) { + if (exit.GetName() == name) { + return &exit; + } + } + } + + return nullptr; +} diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access.hpp b/soh/soh/Enhancements/randomizer/3drando/location_access.hpp index 60529a2f6..672a97f5c 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access.hpp @@ -242,6 +242,7 @@ namespace Areas { void AreaTable_Init(); Area* AreaTable(const uint32_t areaKey); std::vector GetShuffleableEntrances(EntranceType type, bool onlyPrimary = true); +Entrance* GetEntrance(const std::string name); // Overworld void AreaTable_Init_LostWoods(); diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp index 8988b045b..19960f1be 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp @@ -104,7 +104,7 @@ void AreaTable_Init_DeathMountain() { Entrance(DEATH_MOUNTAIN_TRAIL, {[]{return true;}}), Entrance(GC_WOODS_WARP, {[]{return GCWoodsWarpOpen;}}), Entrance(GC_SHOP, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || (IsChild && (CanBlastOrSmash || GoronBracelet || GoronCityChildFire || CanUse(BOW)));}}), - Entrance(GC_DARUNIAS_CHAMBER, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || GCDaruniasDoorOpenChild;}}), + Entrance(GC_DARUNIAS_CHAMBER, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || (IsChild && GCDaruniasDoorOpenChild);}}), Entrance(GC_GROTTO_PLATFORM, {[]{return IsAdult && ((CanPlay(SongOfTime) && ((EffectiveHealth > 2) || CanUse(GORON_TUNIC) || CanUse(LONGSHOT) || CanUse(NAYRUS_LOVE))) || (EffectiveHealth > 1 && CanUse(GORON_TUNIC) && CanUse(HOOKSHOT)) || (CanUse(NAYRUS_LOVE) && CanUse(HOOKSHOT)) || (EffectiveHealth > 2 && CanUse(HOOKSHOT) && LogicGoronCityGrotto));}}), }); diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_deku_tree.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_deku_tree.cpp index 2224df4ca..c1231b29e 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_deku_tree.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_deku_tree.cpp @@ -267,5 +267,6 @@ void AreaTable_Init_DekuTree() { { // Exits Entrance(DEKU_TREE_BOSS_ENTRYWAY, { [] { return true; } }), + Entrance(KF_OUTSIDE_DEKU_TREE, { [] { return DekuTreeClear; } }), }); } diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_dodongos_cavern.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_dodongos_cavern.cpp index 9f8effbcc..dc88b1b2c 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_dodongos_cavern.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_dodongos_cavern.cpp @@ -309,5 +309,6 @@ void AreaTable_Init_DodongosCavern() { { // Exits Entrance(DODONGOS_CAVERN_BOSS_ENTRYWAY, { [] { return true; } }), + Entrance(DEATH_MOUNTAIN_TRAIL, { [] { return DodongosCavernClear; } }), }); } 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 440483af6..107028872 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, {}, {}, { @@ -420,5 +420,6 @@ void AreaTable_Init_FireTemple() { { // Exits Entrance(FIRE_TEMPLE_BOSS_ENTRYWAY, { [] { return false; } }), + Entrance(DMC_CENTRAL_LOCAL, { [] { return FireTempleClear; } }), }); } 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 fea9fda6f..16798906e 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, {}, {}, { @@ -433,5 +433,6 @@ void AreaTable_Init_ForestTemple() { { // Exits Entrance(FOREST_TEMPLE_BOSS_ENTRYWAY, { [] { return false; } }), + Entrance(SACRED_FOREST_MEADOW, { [] { return ForestTempleClear; } }), }); } diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_jabujabus_belly.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_jabujabus_belly.cpp index 108b69ccc..c8e38bb61 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_jabujabus_belly.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_jabujabus_belly.cpp @@ -179,17 +179,17 @@ void AreaTable_Init_JabuJabusBelly() { //Locations LocationAccess(JABU_JABUS_BELLY_MQ_SECOND_ROOM_LOWER_CHEST, {[]{return true;}}), LocationAccess(JABU_JABUS_BELLY_MQ_SECOND_ROOM_UPPER_CHEST, {[]{return (IsAdult && (CanUse(HOVER_BOOTS) || CanUse(HOOKSHOT))) || ChildCanAccess(JABU_JABUS_BELLY_MQ_BOSS_AREA);}}), - LocationAccess(JABU_JABUS_BELLY_MQ_COMPASS_CHEST, {[]{return true;}}), - LocationAccess(JABU_JABUS_BELLY_MQ_BASEMENT_NEAR_VINES_CHEST, {[]{return true;}}), - LocationAccess(JABU_JABUS_BELLY_MQ_BASEMENT_NEAR_SWITCHES_CHEST, {[]{return true;}}), + LocationAccess(JABU_JABUS_BELLY_MQ_COMPASS_CHEST, {[]{return (IsChild || CanDive || CanUse(IRON_BOOTS) || LogicJabuAlcoveJumpDive) && (CanUse(SLINGSHOT) || CanUse(BOW) || CanUse(HOOKSHOT) || HasBombchus || (LogicJabuMQRangJump && CanUse(BOOMERANG)));}}), + LocationAccess(JABU_JABUS_BELLY_MQ_BASEMENT_NEAR_VINES_CHEST, {[]{return CanUse(SLINGSHOT);}}), + LocationAccess(JABU_JABUS_BELLY_MQ_BASEMENT_NEAR_SWITCHES_CHEST, {[]{return CanUse(SLINGSHOT);}}), LocationAccess(JABU_JABUS_BELLY_MQ_BOOMERANG_ROOM_SMALL_CHEST, {[]{return true;}}), - LocationAccess(JABU_JABUS_BELLY_MQ_BOOMERANG_CHEST, {[]{return true;}}), + LocationAccess(JABU_JABUS_BELLY_MQ_BOOMERANG_CHEST, {[]{return CanUse(KOKIRI_SWORD) || CanUse(MASTER_SWORD) || CanUse(BIGGORON_SWORD) || CanUse(MEGATON_HAMMER) || CanUse(SLINGSHOT) || CanUse(BOW) || CanUse(STICKS) || Bombs;}}), LocationAccess(JABU_JABUS_BELLY_MQ_GS_BOOMERANG_CHEST_ROOM, {[]{return CanPlay(SongOfTime) || (LogicJabuMQSoTGS && IsChild && CanUse(BOOMERANG));}}), //Trick: CanPlay(SongOfTime) || (LogicJabuMQSoTGS && IsChild && CanUse(BOOMERANG)) }, { //Exits Entrance(JABU_JABUS_BELLY_MQ_BEGINNING, {[]{return true;}}), - Entrance(JABU_JABUS_BELLY_MQ_DEPTHS, {[]{return HasExplosives && IsChild && CanUse(BOOMERANG);}}), + Entrance(JABU_JABUS_BELLY_MQ_DEPTHS, {[]{return HasExplosives && CanUse(SLINGSHOT) && CanUse(BOOMERANG);}}), }); areaTable[JABU_JABUS_BELLY_MQ_DEPTHS] = Area("Jabu Jabus Belly MQ Depths", "Jabu Jabus Belly", JABU_JABUS_BELLY, NO_DAY_NIGHT_CYCLE, {}, { @@ -209,8 +209,8 @@ void AreaTable_Init_JabuJabusBelly() { }, { //Locations LocationAccess(JABU_JABUS_BELLY_MQ_COW, {[]{return CanPlay(EponasSong);}}), - LocationAccess(JABU_JABUS_BELLY_MQ_NEAR_BOSS_CHEST, {[]{return true;}}), - LocationAccess(JABU_JABUS_BELLY_MQ_GS_NEAR_BOSS, {[]{return true;}}), + LocationAccess(JABU_JABUS_BELLY_MQ_NEAR_BOSS_CHEST, {[]{return CanUse(SLINGSHOT);}}), + LocationAccess(JABU_JABUS_BELLY_MQ_GS_NEAR_BOSS, {[]{return CanUse(BOOMERANG) || (LogicJabuNearBossRanged && CanUse(HOOKSHOT));}}), }, { //Exits Entrance(JABU_JABUS_BELLY_MQ_MAIN, {[]{return true;}}), @@ -245,5 +245,6 @@ void AreaTable_Init_JabuJabusBelly() { { // Exits Entrance(JABU_JABUS_BELLY_BOSS_ENTRYWAY, { [] { return false; } }), + Entrance(ZORAS_FOUNTAIN, { [] { return JabuJabusBellyClear; } }), }); } diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_shadow_temple.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_shadow_temple.cpp index a3dda7521..61778d563 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_shadow_temple.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_shadow_temple.cpp @@ -209,5 +209,6 @@ void AreaTable_Init_ShadowTemple() { { // Exits Entrance(SHADOW_TEMPLE_BOSS_ENTRYWAY, { [] { return false; } }), + Entrance(GRAVEYARD_WARP_PAD_REGION, { [] { return ShadowTempleClear; } }), }); } diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_spirit_temple.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_spirit_temple.cpp index 2aa0c0516..f676c3c91 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_spirit_temple.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_spirit_temple.cpp @@ -271,5 +271,6 @@ void AreaTable_Init_SpiritTemple() { { // Exits Entrance(SPIRIT_TEMPLE_BOSS_ENTRYWAY, { [] { return false; } }), + Entrance(DESERT_COLOSSUS, { [] { return SpiritTempleClear; } }), }); } diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_water_temple.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_water_temple.cpp index 559b6c20c..f1c86abb0 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_water_temple.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_water_temple.cpp @@ -331,5 +331,6 @@ void AreaTable_Init_WaterTemple() { { // Exits Entrance(WATER_TEMPLE_BOSS_ENTRYWAY, { [] { return false; } }), + Entrance(LAKE_HYLIA, { [] { return WaterTempleClear; } }), }); } diff --git a/soh/soh/Enhancements/randomizer/3drando/logic.cpp b/soh/soh/Enhancements/randomizer/3drando/logic.cpp index 580c687d0..df127e699 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/3drando/menu.cpp b/soh/soh/Enhancements/randomizer/3drando/menu.cpp index 5da2b618c..9f6cc2796 100644 --- a/soh/soh/Enhancements/randomizer/3drando/menu.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/menu.cpp @@ -22,6 +22,14 @@ std::vector presetEntries; Option* currentSetting; } // namespace +static void RestoreOverrides() { + if (Settings::Logic.Is(LOGIC_VANILLA)) { + for (auto overridePair : Settings::vanillaLogicOverrides) { + overridePair.first->RestoreDelayedOption(); + } + } +} + std::string GenerateRandomizer(std::unordered_map cvarSettings, std::set excludedLocations, std::set enabledTricks, std::string seedString) { @@ -52,20 +60,17 @@ std::string GenerateRandomizer(std::unordered_map printf("\n\nFailed to generate after 5 tries.\nPress B to go back to the menu.\nA different seed might be " "successful."); SPDLOG_DEBUG("\nRANDOMIZATION FAILED COMPLETELY. PLZ FIX\n"); + RestoreOverrides(); return ""; } else { printf("\n\nError %d with fill.\nPress Select to exit or B to go back to the menu.\n", ret); + RestoreOverrides(); return ""; } } - // Restore settings that were set to a specific value for vanilla logic - if (Settings::Logic.Is(LOGIC_VANILLA)) { - for (Option* setting : Settings::vanillaLogicDefaults) { - setting->RestoreDelayedOption(); - } - Settings::Keysanity.RestoreDelayedOption(); - } + RestoreOverrides(); + std::ostringstream fileNameStream; for (int i = 0; i < Settings::hashIconIndexes.size(); i++) { if (i) { diff --git a/soh/soh/Enhancements/randomizer/3drando/settings.cpp b/soh/soh/Enhancements/randomizer/3drando/settings.cpp index 08d09cfa8..5dcd5a792 100644 --- a/soh/soh/Enhancements/randomizer/3drando/settings.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/settings.cpp @@ -94,6 +94,7 @@ namespace Settings { Option ShuffleOverworldSpawns = Option::Bool("Overworld Spawns", {"Off", "On"}); Option MixedEntrancePools = Option::Bool("Mixed Entrance Pools", {"Off", "On"}); Option MixDungeons = Option::Bool("Mix Dungeons", {"Off", "On"}); + Option MixBosses = Option::Bool("Mix Bosses", {"Off", "On"}); Option MixOverworld = Option::Bool("Mix Overworld", {"Off", "On"}); Option MixInteriors = Option::Bool("Mix Interiors", {"Off", "On"}); Option MixGrottos = Option::Bool("Mix Grottos", {"Off", "On"}); @@ -135,6 +136,7 @@ namespace Settings { &ShuffleOverworldSpawns, &MixedEntrancePools, &MixDungeons, + &MixBosses, &MixOverworld, &MixInteriors, &MixGrottos, @@ -1298,6 +1300,7 @@ namespace Settings { ctx.shuffleOverworldSpawns = (ShuffleOverworldSpawns) ? 1 : 0; ctx.mixedEntrancePools = (MixedEntrancePools) ? 1 : 0; ctx.mixDungeons = (MixDungeons) ? 1 : 0; + ctx.mixBosses = (MixBosses) ? 1 : 0; ctx.mixOverworld = (MixOverworld) ? 1 : 0; ctx.mixInteriors = (MixInteriors) ? 1 : 0; ctx.mixGrottos = (MixGrottos) ? 1 : 0; @@ -1891,6 +1894,13 @@ namespace Settings { MixDungeons.SetSelectedIndex(OFF); } + if (ShuffleBossEntrances.Is(SHUFFLEBOSSES_FULL)) { + MixBosses.Unhide(); + } else { + MixBosses.Hide(); + MixBosses.SetSelectedIndex(OFF); + } + if (ShuffleOverworldEntrances) { MixOverworld.Unhide(); } else { @@ -1914,6 +1924,8 @@ namespace Settings { } else { MixDungeons.Hide(); MixDungeons.SetSelectedIndex(OFF); + MixBosses.Hide(); + MixBosses.SetSelectedIndex(OFF); MixOverworld.Hide(); MixOverworld.SetSelectedIndex(OFF); MixInteriors.Hide(); @@ -2060,21 +2072,65 @@ namespace Settings { } //Options that should be saved, set to default, then restored after finishing when vanilla logic is enabled - std::vector